Merge pull request #51 from slingamn/noevent.2

rename to Message and Reader; remove Event
This commit is contained in:
Shivaram Lingamneni 2021-03-10 19:43:46 -05:00 committed by GitHub
commit ea7a188a73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 183 additions and 207 deletions

@ -23,12 +23,12 @@ irc := ircevent.Connection{
RequestCaps: []string{"server-time", "message-tags"}, RequestCaps: []string{"server-time", "message-tags"},
} }
irc.AddCallback("001", func(e ircevent.Event) { irc.Join("#ircevent-test") }) irc.AddCallback("001", func(e ircmsg.Message) { irc.Join("#ircevent-test") })
irc.AddCallback("PRIVMSG", func(event ircevent.Event) { irc.AddCallback("PRIVMSG", func(event ircmsg.Message) {
//event.Message() contains the message // event.Prefix is the source;
//event.Nick() Contains the sender // event.Params[0] is the target (the channel or nickname the message was sent to)
//event.Params[0] Contains the channel // and event.Params[1] is the message itself
}); });
err := irc.Connect() err := irc.Connect()

@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/goshuirc/irc-go/ircevent" "github.com/goshuirc/irc-go/ircevent"
"github.com/goshuirc/irc-go/ircmsg"
) )
func getenv(key, defaultValue string) (value string) { func getenv(key, defaultValue string) (value string) {
@ -36,15 +37,15 @@ func main() {
SASLPassword: saslPassword, SASLPassword: saslPassword,
} }
irc.AddConnectCallback(func(e ircevent.Event) { irc.AddConnectCallback(func(e ircmsg.Message) {
// attempt to set the BOT mode on ourself: // attempt to set the BOT mode on ourself:
if botMode := irc.ISupport()["BOT"]; botMode != "" { if botMode := irc.ISupport()["BOT"]; botMode != "" {
irc.Send("MODE", irc.CurrentNick(), "+"+botMode) irc.Send("MODE", irc.CurrentNick(), "+"+botMode)
} }
irc.Join(channel) irc.Join(channel)
}) })
irc.AddCallback("JOIN", func(e ircevent.Event) {}) // TODO try to rejoin if we *don't* get this irc.AddCallback("JOIN", func(e ircmsg.Message) {}) // TODO try to rejoin if we *don't* get this
irc.AddCallback("PRIVMSG", func(e ircevent.Event) { irc.AddCallback("PRIVMSG", func(e ircmsg.Message) {
if len(e.Params) < 2 { if len(e.Params) < 2 {
return return
} }
@ -65,7 +66,7 @@ func main() {
// example client-to-client extension via message-tags: // example client-to-client extension via message-tags:
// have the bot maintain a running sum of integers // have the bot maintain a running sum of integers
var sum int64 // doesn't need synchronization as long as it's only visible from a single callback var sum int64 // doesn't need synchronization as long as it's only visible from a single callback
irc.AddCallback("TAGMSG", func(e ircevent.Event) { irc.AddCallback("TAGMSG", func(e ircmsg.Message) {
_, tv := e.GetTag("+summand") _, tv := e.GetTag("+summand")
if v, err := strconv.ParseInt(tv, 10, 64); err == nil { if v, err := strconv.ParseInt(tv, 10, 64); err == nil {
sum += v sum += v

@ -9,6 +9,7 @@ import (
_ "net/http/pprof" _ "net/http/pprof"
"github.com/goshuirc/irc-go/ircevent" "github.com/goshuirc/irc-go/ircevent"
"github.com/goshuirc/irc-go/ircmsg"
) )
/* /*
@ -48,11 +49,11 @@ func main() {
RequestCaps: []string{"server-time", "echo-message"}, RequestCaps: []string{"server-time", "echo-message"},
} }
irc.AddCallback("001", func(e ircevent.Event) { irc.Join(channel) }) irc.AddCallback("001", func(e ircmsg.Message) { irc.Join(channel) })
irc.AddCallback("JOIN", func(e ircevent.Event) { irc.Privmsg(channel, "hi there friend!") }) irc.AddCallback("JOIN", func(e ircmsg.Message) { irc.Privmsg(channel, "hi there friend!") })
// echo whatever we get back // echo whatever we get back
count := 0 count := 0
irc.AddCallback("PRIVMSG", func(e ircevent.Event) { irc.AddCallback("PRIVMSG", func(e ircmsg.Message) {
if limit != 0 && count >= limit { if limit != 0 && count >= limit {
irc.Quit() irc.Quit()
} else { } else {

@ -142,7 +142,7 @@ func (irc *Connection) readLoop() {
} }
func readMsgLoop(socket net.Conn, maxLineLen int, msgChan chan string, errChan chan error, end chan empty) { func readMsgLoop(socket net.Conn, maxLineLen int, msgChan chan string, errChan chan error, end chan empty) {
var reader ircreader.IRCReader var reader ircreader.Reader
reader.Initialize(socket, 1024, maxLineLen+maxlenTags) reader.Initialize(socket, 1024, maxLineLen+maxlenTags)
for { for {
msgBytes, err := reader.ReadLine() msgBytes, err := reader.ReadLine()
@ -348,8 +348,8 @@ func (irc *Connection) sendInternal(b []byte) (err error) {
} }
} }
// Send a built ircmsg.IRCMessage. // Send a built ircmsg.Message.
func (irc *Connection) SendIRCMessage(msg ircmsg.IRCMessage) error { func (irc *Connection) SendIRCMessage(msg ircmsg.Message) error {
b, err := msg.LineBytesStrict(true, irc.MaxLineLen) b, err := msg.LineBytesStrict(true, irc.MaxLineLen)
if err != nil { if err != nil {
if irc.Debug { if irc.Debug {

@ -18,21 +18,21 @@ const (
// Tuple type for uniquely identifying callbacks // Tuple type for uniquely identifying callbacks
type CallbackID struct { type CallbackID struct {
eventCode string command string
id uint64 id uint64
} }
// Register a callback to a connection and event code. A callback is a function // Register a callback to handle an IRC command (or numeric). A callback is a
// which takes only an Event object as parameter. Valid event codes are all // function which takes only an ircmsg.Message object as parameter. Valid commands
// IRC/CTCP commands and error/response codes. This function returns the ID of the // are all IRC commands, including numerics. This function returns the ID of the
// registered callback for later management. // registered callback for later management.
func (irc *Connection) AddCallback(eventCode string, callback func(Event)) CallbackID { func (irc *Connection) AddCallback(command string, callback func(ircmsg.Message)) CallbackID {
return irc.addCallback(eventCode, Callback(callback), false, 0) return irc.addCallback(command, Callback(callback), false, 0)
} }
func (irc *Connection) addCallback(eventCode string, callback Callback, prepend bool, idNum uint64) CallbackID { func (irc *Connection) addCallback(command string, callback Callback, prepend bool, idNum uint64) CallbackID {
eventCode = strings.ToUpper(eventCode) command = strings.ToUpper(command)
if eventCode == "" || strings.HasPrefix(eventCode, "*") || eventCode == "BATCH" { if command == "" || strings.HasPrefix(command, "*") || command == "BATCH" {
return CallbackID{} return CallbackID{}
} }
@ -47,9 +47,9 @@ func (irc *Connection) addCallback(eventCode string, callback Callback, prepend
idNum = irc.callbackCounter idNum = irc.callbackCounter
irc.callbackCounter++ irc.callbackCounter++
} }
id := CallbackID{eventCode: eventCode, id: idNum} id := CallbackID{command: command, id: idNum}
newPair := callbackPair{id: id.id, callback: callback} newPair := callbackPair{id: id.id, callback: callback}
current := irc.events[eventCode] current := irc.events[command]
newList := make([]callbackPair, len(current)+1) newList := make([]callbackPair, len(current)+1)
start := 0 start := 0
if prepend { if prepend {
@ -60,7 +60,7 @@ func (irc *Connection) addCallback(eventCode string, callback Callback, prepend
if !prepend { if !prepend {
newList[len(newList)-1] = newPair newList[len(newList)-1] = newPair
} }
irc.events[eventCode] = newList irc.events[command] = newList
return id return id
} }
@ -68,14 +68,14 @@ func (irc *Connection) addCallback(eventCode string, callback Callback, prepend
func (irc *Connection) RemoveCallback(id CallbackID) { func (irc *Connection) RemoveCallback(id CallbackID) {
irc.eventsMutex.Lock() irc.eventsMutex.Lock()
defer irc.eventsMutex.Unlock() defer irc.eventsMutex.Unlock()
switch id.eventCode { switch id.command {
case registrationEvent: case registrationEvent:
irc.removeCallbackNoMutex(RPL_ENDOFMOTD, id.id) irc.removeCallbackNoMutex(RPL_ENDOFMOTD, id.id)
irc.removeCallbackNoMutex(ERR_NOMOTD, id.id) irc.removeCallbackNoMutex(ERR_NOMOTD, id.id)
case "BATCH": case "BATCH":
irc.removeBatchCallbackNoMutex(id.id) irc.removeBatchCallbackNoMutex(id.id)
default: default:
irc.removeCallbackNoMutex(id.eventCode, id.id) irc.removeCallbackNoMutex(id.command, id.id)
} }
} }
@ -94,20 +94,20 @@ func (irc *Connection) removeCallbackNoMutex(code string, id uint64) {
} }
// Remove all callbacks from a given event code. // Remove all callbacks from a given event code.
func (irc *Connection) ClearCallback(eventcode string) { func (irc *Connection) ClearCallback(command string) {
eventcode = strings.ToUpper(eventcode) command = strings.ToUpper(command)
irc.eventsMutex.Lock() irc.eventsMutex.Lock()
defer irc.eventsMutex.Unlock() defer irc.eventsMutex.Unlock()
delete(irc.events, eventcode) delete(irc.events, command)
} }
// Replace callback i (ID) associated with a given event code with a new callback function. // Replace callback i (ID) associated with a given event code with a new callback function.
func (irc *Connection) ReplaceCallback(id CallbackID, callback func(Event)) bool { func (irc *Connection) ReplaceCallback(id CallbackID, callback func(ircmsg.Message)) bool {
irc.eventsMutex.Lock() irc.eventsMutex.Lock()
defer irc.eventsMutex.Unlock() defer irc.eventsMutex.Unlock()
list := irc.events[id.eventCode] list := irc.events[id.command]
for i, p := range list { for i, p := range list {
if p.id == id.id { if p.id == id.id {
list[i] = callbackPair{id: id.id, callback: callback} list[i] = callbackPair{id: id.id, callback: callback}
@ -133,7 +133,7 @@ func (irc *Connection) AddBatchCallback(callback func(*Batch) bool) CallbackID {
copy(nbc, irc.batchCallbacks) copy(nbc, irc.batchCallbacks)
nbc[len(nbc)-1] = batchCallbackPair{id: idNum, callback: callback} nbc[len(nbc)-1] = batchCallbackPair{id: idNum, callback: callback}
irc.batchCallbacks = nbc irc.batchCallbacks = nbc
return CallbackID{eventCode: "BATCH", id: idNum} return CallbackID{command: "BATCH", id: idNum}
} }
func (irc *Connection) removeBatchCallbackNoMutex(idNum uint64) { func (irc *Connection) removeBatchCallbackNoMutex(idNum uint64) {
@ -153,11 +153,11 @@ func (irc *Connection) removeBatchCallbackNoMutex(idNum uint64) {
// Convenience function to add a callback that will be called once the // Convenience function to add a callback that will be called once the
// connection is completed (this is traditionally referred to as "connection // connection is completed (this is traditionally referred to as "connection
// registration"). // registration").
func (irc *Connection) AddConnectCallback(callback func(Event)) (id CallbackID) { func (irc *Connection) AddConnectCallback(callback func(ircmsg.Message)) (id CallbackID) {
// XXX: forcibly use the same ID number for both copies of the callback // XXX: forcibly use the same ID number for both copies of the callback
id376 := irc.AddCallback(RPL_ENDOFMOTD, callback) id376 := irc.AddCallback(RPL_ENDOFMOTD, callback)
irc.addCallback(ERR_NOMOTD, callback, false, id376.id) irc.addCallback(ERR_NOMOTD, callback, false, id376.id)
return CallbackID{eventCode: registrationEvent, id: id376.id} return CallbackID{command: registrationEvent, id: id376.id}
} }
func (irc *Connection) getCallbacks(code string) (result []callbackPair) { func (irc *Connection) getCallbacks(code string) (result []callbackPair) {
@ -184,7 +184,7 @@ var (
errorUnknownLabel = errors.New("received labeled response from server, but we don't recognize the label") errorUnknownLabel = errors.New("received labeled response from server, but we don't recognize the label")
) )
func (irc *Connection) handleBatchCommand(msg ircmsg.IRCMessage) { func (irc *Connection) handleBatchCommand(msg ircmsg.Message) {
if len(msg.Params) < 1 || len(msg.Params[0]) < 2 { if len(msg.Params) < 1 || len(msg.Params[0]) < 2 {
irc.Log.Printf("Invalid BATCH command from server\n") irc.Log.Printf("Invalid BATCH command from server\n")
return return
@ -214,7 +214,7 @@ func (irc *Connection) handleBatchCommand(msg ircmsg.IRCMessage) {
return return
} }
batchObj := new(Batch) batchObj := new(Batch)
batchObj.IRCMessage = msg batchObj.Message = msg
irc.batches[batchID] = batchInProgress{ irc.batches[batchID] = batchInProgress{
createdAt: time.Now(), createdAt: time.Now(),
batch: batchObj, batch: batchObj,
@ -295,14 +295,14 @@ func (irc *Connection) HandleBatch(batch *Batch) {
// recursively "flatten" the nested batch; process every command individually // recursively "flatten" the nested batch; process every command individually
func (irc *Connection) handleBatchNaively(batch *Batch) { func (irc *Connection) handleBatchNaively(batch *Batch) {
if batch.Command != "BATCH" { if batch.Command != "BATCH" {
irc.HandleEvent(Event{IRCMessage: batch.IRCMessage}) irc.HandleMessage(batch.Message)
} }
for _, item := range batch.Items { for _, item := range batch.Items {
irc.handleBatchNaively(item) irc.handleBatchNaively(item)
} }
} }
func (irc *Connection) handleBatchedCommand(msg ircmsg.IRCMessage, batchID string) { func (irc *Connection) handleBatchedCommand(msg ircmsg.Message, batchID string) {
irc.batchMutex.Lock() irc.batchMutex.Lock()
defer irc.batchMutex.Unlock() defer irc.batchMutex.Unlock()
@ -311,11 +311,11 @@ func (irc *Connection) handleBatchedCommand(msg ircmsg.IRCMessage, batchID strin
irc.Log.Printf("ignoring command with unknown batch ID %s\n", batchID) irc.Log.Printf("ignoring command with unknown batch ID %s\n", batchID)
return return
} }
bip.batch.Items = append(bip.batch.Items, &Batch{IRCMessage: msg}) bip.batch.Items = append(bip.batch.Items, &Batch{Message: msg})
} }
// Execute all callbacks associated with a given event. // Execute all callbacks associated with a given event.
func (irc *Connection) runCallbacks(msg ircmsg.IRCMessage) { func (irc *Connection) runCallbacks(msg ircmsg.Message) {
if !irc.AllowPanic { if !irc.AllowPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -347,7 +347,7 @@ func (irc *Connection) runCallbacks(msg ircmsg.IRCMessage) {
return return
} else { } else {
labelCallback(&Batch{ labelCallback(&Batch{
IRCMessage: msg, Message: msg,
}) })
} }
return return
@ -355,12 +355,12 @@ func (irc *Connection) runCallbacks(msg ircmsg.IRCMessage) {
} }
// OK, it's a normal IRC command // OK, it's a normal IRC command
irc.HandleEvent(Event{IRCMessage: msg}) irc.HandleMessage(msg)
} }
// HandleEvent handles an IRC line using the available handlers. This can be // HandleMessage handles an IRC line using the available handlers. This can be
// used in a batch or labeled-response callback to process an individual line. // used in a batch or labeled-response callback to process an individual line.
func (irc *Connection) HandleEvent(event Event) { func (irc *Connection) HandleMessage(event ircmsg.Message) {
if irc.EnableCTCP { if irc.EnableCTCP {
eventRewriteCTCP(&event) eventRewriteCTCP(&event)
} }
@ -386,12 +386,10 @@ func (irc *Connection) setupCallbacks() {
} }
// PING: we must respond with the correct PONG // PING: we must respond with the correct PONG
irc.AddCallback("PING", func(e Event) { irc.Send("PONG", e.Message()) }) irc.AddCallback("PING", func(e ircmsg.Message) { irc.Send("PONG", lastParam(&e)) })
// PONG: record time to make sure the server is responding to us // PONG: record time to make sure the server is responding to us
irc.AddCallback("PONG", func(e Event) { irc.AddCallback("PONG", func(e ircmsg.Message) { irc.recordPong(lastParam(&e)) })
irc.recordPong(e.Message())
})
// 433: ERR_NICKNAMEINUSE "<nick> :Nickname is already in use" // 433: ERR_NICKNAMEINUSE "<nick> :Nickname is already in use"
// 437: ERR_UNAVAILRESOURCE "<nick/channel> :Nick/channel is temporarily unavailable" // 437: ERR_UNAVAILRESOURCE "<nick/channel> :Nick/channel is temporarily unavailable"
@ -406,13 +404,13 @@ func (irc *Connection) setupCallbacks() {
irc.AddCallback(RPL_ISUPPORT, irc.handleISupport) irc.AddCallback(RPL_ISUPPORT, irc.handleISupport)
// respond to NICK from the server (in response to our own NICK, or sent unprompted) // respond to NICK from the server (in response to our own NICK, or sent unprompted)
irc.AddCallback("NICK", func(e Event) { irc.AddCallback("NICK", func(e ircmsg.Message) {
if e.Nick() == irc.CurrentNick() && len(e.Params) > 0 { if ExtractNick(e.Prefix) == irc.CurrentNick() && len(e.Params) > 0 {
irc.setCurrentNick(e.Params[0]) irc.setCurrentNick(e.Params[0])
} }
}) })
irc.AddCallback("ERROR", func(e Event) { irc.AddCallback("ERROR", func(e ircmsg.Message) {
if !irc.isQuitting() { if !irc.isQuitting() {
irc.Log.Printf("ERROR received from server: %s", strings.Join(e.Params, " ")) irc.Log.Printf("ERROR received from server: %s", strings.Join(e.Params, " "))
} }
@ -438,7 +436,7 @@ func (irc *Connection) setupCallbacks() {
irc.AddCallback("NOTE", irc.handleStandardReplies) irc.AddCallback("NOTE", irc.handleStandardReplies)
} }
func (irc *Connection) handleRplWelcome(e Event) { func (irc *Connection) handleRplWelcome(e ircmsg.Message) {
irc.stateMutex.Lock() irc.stateMutex.Lock()
defer irc.stateMutex.Unlock() defer irc.stateMutex.Unlock()
@ -448,7 +446,7 @@ func (irc *Connection) handleRplWelcome(e Event) {
} }
} }
func (irc *Connection) handleRegistration(e Event) { func (irc *Connection) handleRegistration(e ircmsg.Message) {
// wake up Connect() if applicable // wake up Connect() if applicable
defer func() { defer func() {
select { select {
@ -471,7 +469,7 @@ func (irc *Connection) handleRegistration(e Event) {
} }
func (irc *Connection) handleUnavailableNick(e Event) { func (irc *Connection) handleUnavailableNick(e ircmsg.Message) {
// only try to change the nick if we're not registered yet, // only try to change the nick if we're not registered yet,
// otherwise we'll change in response to pingLoop unsuccessfully // otherwise we'll change in response to pingLoop unsuccessfully
// trying to restore the intended nick (swapping one undesired nick // trying to restore the intended nick (swapping one undesired nick
@ -489,7 +487,7 @@ func (irc *Connection) handleUnavailableNick(e Event) {
} }
} }
func (irc *Connection) handleISupport(e Event) { func (irc *Connection) handleISupport(e ircmsg.Message) {
irc.stateMutex.Lock() irc.stateMutex.Lock()
defer irc.stateMutex.Unlock() defer irc.stateMutex.Unlock()
@ -530,7 +528,7 @@ func unescapeISupportValue(in string) (out string) {
return buf.String() return buf.String()
} }
func (irc *Connection) handleCAP(e Event) { func (irc *Connection) handleCAP(e ircmsg.Message) {
if len(e.Params) < 3 { if len(e.Params) < 3 {
return return
} }
@ -666,7 +664,7 @@ func splitCAPToken(token string) (name, value string) {
} }
} }
func (irc *Connection) handleStandardReplies(e Event) { func (irc *Connection) handleStandardReplies(e ircmsg.Message) {
// unconditionally print messages for FAIL and WARN; // unconditionally print messages for FAIL and WARN;
// re. NOTE, if debug is enabled, we print the raw line anyway // re. NOTE, if debug is enabled, we print the raw line anyway
switch e.Command { switch e.Command {

@ -4,9 +4,11 @@ import (
"fmt" "fmt"
"strings" "strings"
"time" "time"
"github.com/goshuirc/irc-go/ircmsg"
) )
func eventRewriteCTCP(event *Event) { func eventRewriteCTCP(event *ircmsg.Message) {
// XXX rewrite event.Command for CTCP // XXX rewrite event.Command for CTCP
if !(event.Command == "PRIVMSG" && len(event.Params) == 2 && strings.HasPrefix(event.Params[1], "\x01")) { if !(event.Command == "PRIVMSG" && len(event.Params) == 2 && strings.HasPrefix(event.Params[1], "\x01")) {
return return
@ -44,21 +46,23 @@ func eventRewriteCTCP(event *Event) {
} }
func (irc *Connection) setupCTCPCallbacks() { func (irc *Connection) setupCTCPCallbacks() {
irc.AddCallback("CTCP_VERSION", func(e Event) { irc.AddCallback("CTCP_VERSION", func(e ircmsg.Message) {
irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01VERSION %s\x01", e.Nick(), irc.Version)) irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01VERSION %s\x01", ExtractNick(e.Prefix), irc.Version))
}) })
irc.AddCallback("CTCP_USERINFO", func(e Event) { irc.AddCallback("CTCP_USERINFO", func(e ircmsg.Message) {
irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01USERINFO %s\x01", e.Nick(), irc.User)) irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01USERINFO %s\x01", ExtractNick(e.Prefix), irc.User))
}) })
irc.AddCallback("CTCP_CLIENTINFO", func(e Event) { irc.AddCallback("CTCP_CLIENTINFO", func(e ircmsg.Message) {
irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01CLIENTINFO PING VERSION TIME USERINFO CLIENTINFO\x01", e.Nick())) irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01CLIENTINFO PING VERSION TIME USERINFO CLIENTINFO\x01", ExtractNick(e.Prefix)))
}) })
irc.AddCallback("CTCP_TIME", func(e Event) { irc.AddCallback("CTCP_TIME", func(e ircmsg.Message) {
irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01TIME %s\x01", e.Nick(), time.Now().UTC().Format(time.RFC1123))) irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01TIME %s\x01", ExtractNick(e.Prefix), time.Now().UTC().Format(time.RFC1123)))
}) })
irc.AddCallback("CTCP_PING", func(e Event) { irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01%s\x01", e.Nick(), e.Message())) }) irc.AddCallback("CTCP_PING", func(e ircmsg.Message) {
irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01%s\x01", ExtractNick(e.Prefix), e.Params[1]))
})
} }

@ -6,6 +6,8 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"testing" "testing"
"github.com/goshuirc/irc-go/ircmsg"
) )
const ( const (
@ -21,7 +23,7 @@ func TestLabeledResponse(t *testing.T) {
irccon.RequestCaps = []string{"message-tags", "batch", "labeled-response"} irccon.RequestCaps = []string{"message-tags", "batch", "labeled-response"}
irccon.RealName = "ecf61da38b58" irccon.RealName = "ecf61da38b58"
results := make(map[string]string) results := make(map[string]string)
irccon.AddConnectCallback(func(e Event) { irccon.AddConnectCallback(func(e ircmsg.Message) {
irccon.SendWithLabel(func(batch *Batch) { irccon.SendWithLabel(func(batch *Batch) {
if batch == nil { if batch == nil {
return return
@ -192,7 +194,7 @@ func TestBatchHandlers(t *testing.T) {
} }
return false return false
}) })
alice.AddCallback("PRIVMSG", func(e Event) { alice.AddCallback("PRIVMSG", func(e ircmsg.Message) {
alicePrivmsgCount++ alicePrivmsgCount++
}) })

@ -7,16 +7,16 @@ import (
) )
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
event := new(Event) source := "nick!~user@host"
event.Prefix = "nick!~user@host" nick, user, host := SplitNUH(source)
if event.Nick() != "nick" { if nick != "nick" {
t.Fatal("Parse failed: nick") t.Fatal("Parse failed: nick")
} }
if event.User() != "~user" { if user != "~user" {
t.Fatal("Parse failed: user") t.Fatal("Parse failed: user")
} }
if event.Host() != "host" { if host != "host" {
t.Fatal("Parse failed: host") t.Fatal("Parse failed: host")
} }
} }

@ -4,6 +4,8 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"github.com/goshuirc/irc-go/ircmsg"
) )
type saslResult struct { type saslResult struct {
@ -28,35 +30,35 @@ func (irc *Connection) submitSASLResult(r saslResult) {
} }
func (irc *Connection) setupSASLCallbacks() { func (irc *Connection) setupSASLCallbacks() {
irc.AddCallback("AUTHENTICATE", func(e Event) { irc.AddCallback("AUTHENTICATE", func(e ircmsg.Message) {
str := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s\x00%s\x00%s", irc.SASLLogin, irc.SASLLogin, irc.SASLPassword))) str := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s\x00%s\x00%s", irc.SASLLogin, irc.SASLLogin, irc.SASLPassword)))
irc.Send("AUTHENTICATE", str) irc.Send("AUTHENTICATE", str)
}) })
irc.AddCallback(RPL_LOGGEDOUT, func(e Event) { irc.AddCallback(RPL_LOGGEDOUT, func(e ircmsg.Message) {
irc.SendRaw("CAP END") irc.SendRaw("CAP END")
irc.SendRaw("QUIT") irc.SendRaw("QUIT")
irc.submitSASLResult(saslResult{true, errors.New(e.Params[1])}) irc.submitSASLResult(saslResult{true, errors.New(e.Params[1])})
}) })
irc.AddCallback(ERR_NICKLOCKED, func(e Event) { irc.AddCallback(ERR_NICKLOCKED, func(e ircmsg.Message) {
irc.SendRaw("CAP END") irc.SendRaw("CAP END")
irc.SendRaw("QUIT") irc.SendRaw("QUIT")
irc.submitSASLResult(saslResult{true, errors.New(e.Params[1])}) irc.submitSASLResult(saslResult{true, errors.New(e.Params[1])})
}) })
irc.AddCallback(RPL_SASLSUCCESS, func(e Event) { irc.AddCallback(RPL_SASLSUCCESS, func(e ircmsg.Message) {
irc.submitSASLResult(saslResult{false, nil}) irc.submitSASLResult(saslResult{false, nil})
}) })
irc.AddCallback(ERR_SASLFAIL, func(e Event) { irc.AddCallback(ERR_SASLFAIL, func(e ircmsg.Message) {
irc.SendRaw("CAP END") irc.SendRaw("CAP END")
irc.SendRaw("QUIT") irc.SendRaw("QUIT")
irc.submitSASLResult(saslResult{true, errors.New(e.Params[1])}) irc.submitSASLResult(saslResult{true, errors.New(e.Params[1])})
}) })
// this could potentially happen with auto-login via certfp? // this could potentially happen with auto-login via certfp?
irc.AddCallback(ERR_SASLALREADY, func(e Event) { irc.AddCallback(ERR_SASLALREADY, func(e ircmsg.Message) {
irc.submitSASLResult(saslResult{false, nil}) irc.submitSASLResult(saslResult{false, nil})
}) })
} }

@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"os" "os"
"testing" "testing"
"github.com/goshuirc/irc-go/ircmsg"
) )
const ( const (
@ -48,9 +50,9 @@ func runCAPTest(caps []string, useSASL bool, t *testing.T) {
} }
irccon.RequestCaps = caps irccon.RequestCaps = caps
irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
irccon.AddCallback("001", func(e Event) { irccon.Join("#go-eventirc") }) irccon.AddCallback("001", func(e ircmsg.Message) { irccon.Join("#go-eventirc") })
irccon.AddCallback("366", func(e Event) { irccon.AddCallback("366", func(e ircmsg.Message) {
irccon.Privmsg("#go-eventirc", "Test Message SASL") irccon.Privmsg("#go-eventirc", "Test Message SASL")
irccon.Quit() irccon.Quit()
}) })
@ -96,7 +98,7 @@ func TestSASLFail(t *testing.T) {
irccon.UseTLS = true irccon.UseTLS = true
setSaslTestCreds(irccon, t) setSaslTestCreds(irccon, t)
irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
irccon.AddCallback("001", func(e Event) { irccon.Join("#go-eventirc") }) irccon.AddCallback("001", func(e ircmsg.Message) { irccon.Join("#go-eventirc") })
// intentionally break the password // intentionally break the password
irccon.SASLPassword = irccon.SASLPassword + "_" irccon.SASLPassword = irccon.SASLPassword + "_"
err := irccon.Connect() err := irccon.Connect()

@ -17,7 +17,7 @@ import (
type empty struct{} type empty struct{}
type Callback func(Event) type Callback func(ircmsg.Message)
type callbackPair struct { type callbackPair struct {
id uint64 id uint64
@ -65,7 +65,7 @@ type Connection struct {
// networking and synchronization // networking and synchronization
stateMutex sync.Mutex // innermost mutex: don't block while holding this stateMutex sync.Mutex // innermost mutex: don't block while holding this
end chan empty // closing this causes the goroutines to exit (think threading.Event) end chan empty // closing this causes the goroutines to exit
pwrite chan []byte // receives IRC lines to be sent to the socket pwrite chan []byte // receives IRC lines to be sent to the socket
wg sync.WaitGroup // after closing end, wait on this for all the goroutines to stop wg sync.WaitGroup // after closing end, wait on this for all the goroutines to stop
socket net.Conn socket net.Conn
@ -123,74 +123,40 @@ type pendingLabel struct {
callback LabelCallback callback LabelCallback
} }
// Event represents an individual IRC line.
type Event struct {
ircmsg.IRCMessage
}
// Batch represents an IRCv3 batch, or a line within one. There are // Batch represents an IRCv3 batch, or a line within one. There are
// two cases: // two cases:
// 1. (Batch).Command == "BATCH". This indicates the start of an IRCv3 // 1. (Batch).Command == "BATCH". This indicates the start of an IRCv3
// batch; the embedded IRCMessage is the initial BATCH command, which // batch; the embedded Message is the initial BATCH command, which
// may contain tags that pertain to the batch as a whole. (Batch).Items // may contain tags that pertain to the batch as a whole. (Batch).Items
// contains zero or more *Batch elements, pointing to the contents of // contains zero or more *Batch elements, pointing to the contents of
// the batch in order. // the batch in order.
// 2. (Batch).Command != "BATCH". This is an ordinary IRC line; its // 2. (Batch).Command != "BATCH". This is an ordinary IRC line; its
// tags, command, and parameters are available as members of the embedded // tags, command, and parameters are available as members of the embedded
// IRCMessage. // Message.
// In the context of labeled-response, there is a third case: a `nil` // In the context of labeled-response, there is a third case: a `nil`
// value of *Batch indicates that the server failed to respond in time. // value of *Batch indicates that the server failed to respond in time.
type Batch struct { type Batch struct {
ircmsg.IRCMessage ircmsg.Message
Items []*Batch Items []*Batch
} }
// Retrieve the last message from Event arguments. func ExtractNick(source string) string {
// This function leaves the arguments untouched and nick, _, _ := SplitNUH(source)
// returns an empty string if there are none.
func (e *Event) Message() string {
if len(e.Params) == 0 {
return ""
}
return e.Params[len(e.Params)-1]
}
/*
// https://stackoverflow.com/a/10567935/6754440
// Regex of IRC formatting.
var ircFormat = regexp.MustCompile(`[\x02\x1F\x0F\x16\x1D\x1E]|\x03(\d\d?(,\d\d?)?)?`)
// Retrieve the last message from Event arguments, but without IRC formatting (color.
// This function leaves the arguments untouched and
// returns an empty string if there are none.
func (e *Event) MessageWithoutFormat() string {
if len(e.Arguments) == 0 {
return ""
}
return ircFormat.ReplaceAllString(e.Arguments[len(e.Arguments)-1], "")
}
*/
func (e *Event) Nick() string {
nick, _, _ := e.splitNUH()
return nick return nick
} }
func (e *Event) User() string { func SplitNUH(source string) (nick, user, host string) {
_, user, _ := e.splitNUH() if i, j := strings.Index(source, "!"), strings.Index(source, "@"); i > -1 && j > -1 && i < j {
return user nick = source[0:i]
} user = source[i+1 : j]
host = source[j+1:]
func (e *Event) Host() string { }
_, _, host := e.splitNUH() return
return host }
}
func lastParam(msg *ircmsg.Message) (result string) {
func (event *Event) splitNUH() (nick, user, host string) { if 0 < len(msg.Params) {
if i, j := strings.Index(event.Prefix, "!"), strings.Index(event.Prefix, "@"); i > -1 && j > -1 && i < j { return msg.Params[len(msg.Params)-1]
nick = event.Prefix[0:i]
user = event.Prefix[i+1 : j]
host = event.Prefix[j+1:]
} }
return return
} }

@ -27,7 +27,7 @@ func connForTesting(nick, user string, tls bool) *Connection {
return irc return irc
} }
func mockEvent(command string) ircmsg.IRCMessage { func mockEvent(command string) ircmsg.Message {
return ircmsg.MakeMessage(nil, ":server.name", command) return ircmsg.MakeMessage(nil, ":server.name", command)
} }
@ -37,9 +37,9 @@ func TestRemoveCallback(t *testing.T) {
done := make(chan int, 10) done := make(chan int, 10)
irccon.AddCallback("TEST", func(e Event) { done <- 1 }) irccon.AddCallback("TEST", func(e ircmsg.Message) { done <- 1 })
id := irccon.AddCallback("TEST", func(e Event) { done <- 2 }) id := irccon.AddCallback("TEST", func(e ircmsg.Message) { done <- 2 })
irccon.AddCallback("TEST", func(e Event) { done <- 3 }) irccon.AddCallback("TEST", func(e ircmsg.Message) { done <- 3 })
// Should remove callback at index 1 // Should remove callback at index 1
irccon.RemoveCallback(id) irccon.RemoveCallback(id)
@ -62,11 +62,11 @@ func TestClearCallback(t *testing.T) {
done := make(chan int, 10) done := make(chan int, 10)
irccon.AddCallback("TEST", func(e Event) { done <- 0 }) irccon.AddCallback("TEST", func(e ircmsg.Message) { done <- 0 })
irccon.AddCallback("TEST", func(e Event) { done <- 1 }) irccon.AddCallback("TEST", func(e ircmsg.Message) { done <- 1 })
irccon.ClearCallback("TEST") irccon.ClearCallback("TEST")
irccon.AddCallback("TEST", func(e Event) { done <- 2 }) irccon.AddCallback("TEST", func(e ircmsg.Message) { done <- 2 })
irccon.AddCallback("TEST", func(e Event) { done <- 3 }) irccon.AddCallback("TEST", func(e ircmsg.Message) { done <- 3 })
irccon.runCallbacks(mockEvent("TEST")) irccon.runCallbacks(mockEvent("TEST"))
@ -106,10 +106,10 @@ func TestConnection(t *testing.T) {
teststr := randStr(20) teststr := randStr(20)
testmsgok := make(chan bool, 1) testmsgok := make(chan bool, 1)
irccon1.AddCallback("001", func(e Event) { irccon1.Join(channel) }) irccon1.AddCallback("001", func(e ircmsg.Message) { irccon1.Join(channel) })
irccon2.AddCallback("001", func(e Event) { irccon2.Join(channel) }) irccon2.AddCallback("001", func(e ircmsg.Message) { irccon2.Join(channel) })
irccon1.AddCallback("366", func(e Event) { irccon1.AddCallback("366", func(e ircmsg.Message) {
go func(e Event) { go func(e ircmsg.Message) {
tick := time.NewTicker(1 * time.Second) tick := time.NewTicker(1 * time.Second)
i := 10 i := 10
for { for {
@ -131,14 +131,14 @@ func TestConnection(t *testing.T) {
}(e) }(e)
}) })
irccon2.AddCallback("366", func(e Event) { irccon2.AddCallback("366", func(e ircmsg.Message) {
ircnick2 = randStr(8) ircnick2 = randStr(8)
irccon2.SetNick(ircnick2) irccon2.SetNick(ircnick2)
}) })
irccon2.AddCallback("PRIVMSG", func(e Event) { irccon2.AddCallback("PRIVMSG", func(e ircmsg.Message) {
if e.Message() == teststr { if e.Params[1] == teststr {
if e.Nick() == ircnick1 { if ExtractNick(e.Prefix) == ircnick1 {
testmsgok <- true testmsgok <- true
irccon2.Quit() irccon2.Quit()
} else { } else {
@ -150,8 +150,8 @@ func TestConnection(t *testing.T) {
} }
}) })
irccon2.AddCallback("NICK", func(e Event) { irccon2.AddCallback("NICK", func(e ircmsg.Message) {
if !(e.Nick() == ircnick2orig && e.Message() == ircnick2) { if !(ExtractNick(e.Prefix) == ircnick2orig && e.Params[0] == ircnick2) {
t.Errorf("Nick change did not work!") t.Errorf("Nick change did not work!")
} }
}) })
@ -181,9 +181,9 @@ func runReconnectTest(useSASL bool, t *testing.T) {
debugTest(irccon) debugTest(irccon)
connects := 0 connects := 0
irccon.AddCallback("001", func(e Event) { irccon.Join(channel) }) irccon.AddCallback("001", func(e ircmsg.Message) { irccon.Join(channel) })
irccon.AddCallback("366", func(e Event) { irccon.AddCallback("366", func(e ircmsg.Message) {
connects += 1 connects += 1
if connects > 2 { if connects > 2 {
irccon.Privmsgf(channel, "Connection nr %d (test done)", connects) irccon.Privmsgf(channel, "Connection nr %d (test done)", connects)
@ -226,9 +226,9 @@ func TestConnectionSSL(t *testing.T) {
debugTest(irccon) debugTest(irccon)
irccon.UseTLS = true irccon.UseTLS = true
irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
irccon.AddCallback("001", func(e Event) { irccon.Join(channel) }) irccon.AddCallback("001", func(e ircmsg.Message) { irccon.Join(channel) })
irccon.AddCallback("366", func(e Event) { irccon.AddCallback("366", func(e ircmsg.Message) {
irccon.Privmsg(channel, "Test Message from SSL") irccon.Privmsg(channel, "Test Message from SSL")
irccon.Quit() irccon.Quit()
}) })
@ -284,8 +284,8 @@ func TestConnectionNickInUse(t *testing.T) {
n2 := make(chan string, 1) n2 := make(chan string, 1)
// check the actual nick after 001 is processed // check the actual nick after 001 is processed
irccon1.AddCallback("002", func(e Event) { n1 <- irccon1.CurrentNick() }) irccon1.AddCallback("002", func(e ircmsg.Message) { n1 <- irccon1.CurrentNick() })
irccon2.AddCallback("002", func(e Event) { n2 <- irccon2.CurrentNick() }) irccon2.AddCallback("002", func(e ircmsg.Message) { n2 <- irccon2.CurrentNick() })
err := irccon1.Connect() err := irccon1.Connect()
if err != nil { if err != nil {
@ -318,7 +318,7 @@ func TestConnectionCallbacks(t *testing.T) {
irccon1 := connForTesting(ircnick, "IRCTest1", false) irccon1 := connForTesting(ircnick, "IRCTest1", false)
debugTest(irccon1) debugTest(irccon1)
resultChan := make(chan map[string]string, 1) resultChan := make(chan map[string]string, 1)
irccon1.AddConnectCallback(func(e Event) { irccon1.AddConnectCallback(func(e ircmsg.Message) {
resultChan <- irccon1.ISupport() resultChan <- irccon1.ISupport()
}) })
err := irccon1.Connect() err := irccon1.Connect()

@ -61,10 +61,10 @@ var (
ErrorBadParam = errors.New("Cannot have an empty param, a param with spaces, or a param that starts with ':' before the last parameter") ErrorBadParam = errors.New("Cannot have an empty param, a param with spaces, or a param that starts with ':' before the last parameter")
) )
// IRCMessage represents an IRC message, as defined by the RFCs and as // Message represents an IRC message, as defined by the RFCs and as
// extended by the IRCv3 Message Tags specification with the introduction // extended by the IRCv3 Message Tags specification with the introduction
// of message tags. // of message tags.
type IRCMessage struct { type Message struct {
Prefix string Prefix string
Command string Command string
Params []string Params []string
@ -77,12 +77,12 @@ type IRCMessage struct {
// will be encoded as a "trailing parameter" (preceded by a colon). This is // will be encoded as a "trailing parameter" (preceded by a colon). This is
// almost never necessary and should not be used except when having to interact // almost never necessary and should not be used except when having to interact
// with broken implementations that don't correctly interpret IRC messages. // with broken implementations that don't correctly interpret IRC messages.
func (msg *IRCMessage) ForceTrailing() { func (msg *Message) ForceTrailing() {
msg.forceTrailing = true msg.forceTrailing = true
} }
// GetTag returns whether a tag is present, and if so, what its value is. // GetTag returns whether a tag is present, and if so, what its value is.
func (msg *IRCMessage) GetTag(tagName string) (present bool, value string) { func (msg *Message) GetTag(tagName string) (present bool, value string) {
if len(tagName) == 0 { if len(tagName) == 0 {
return return
} else if tagName[0] == '+' { } else if tagName[0] == '+' {
@ -95,13 +95,13 @@ func (msg *IRCMessage) GetTag(tagName string) (present bool, value string) {
} }
// HasTag returns whether a tag is present. // HasTag returns whether a tag is present.
func (msg *IRCMessage) HasTag(tagName string) (present bool) { func (msg *Message) HasTag(tagName string) (present bool) {
present, _ = msg.GetTag(tagName) present, _ = msg.GetTag(tagName)
return return
} }
// SetTag sets a tag. // SetTag sets a tag.
func (msg *IRCMessage) SetTag(tagName, tagValue string) { func (msg *Message) SetTag(tagName, tagValue string) {
if len(tagName) == 0 { if len(tagName) == 0 {
return return
} else if tagName[0] == '+' { } else if tagName[0] == '+' {
@ -118,7 +118,7 @@ func (msg *IRCMessage) SetTag(tagName, tagValue string) {
} }
// DeleteTag deletes a tag. // DeleteTag deletes a tag.
func (msg *IRCMessage) DeleteTag(tagName string) { func (msg *Message) DeleteTag(tagName string) {
if len(tagName) == 0 { if len(tagName) == 0 {
return return
} else if tagName[0] == '+' { } else if tagName[0] == '+' {
@ -129,14 +129,14 @@ func (msg *IRCMessage) DeleteTag(tagName string) {
} }
// UpdateTags is a convenience to set multiple tags at once. // UpdateTags is a convenience to set multiple tags at once.
func (msg *IRCMessage) UpdateTags(tags map[string]string) { func (msg *Message) UpdateTags(tags map[string]string) {
for name, value := range tags { for name, value := range tags {
msg.SetTag(name, value) msg.SetTag(name, value)
} }
} }
// AllTags returns all tags as a single map. // AllTags returns all tags as a single map.
func (msg *IRCMessage) AllTags() (result map[string]string) { func (msg *Message) AllTags() (result map[string]string) {
result = make(map[string]string, len(msg.tags)+len(msg.clientOnlyTags)) result = make(map[string]string, len(msg.tags)+len(msg.clientOnlyTags))
for name, value := range msg.tags { for name, value := range msg.tags {
result[name] = value result[name] = value
@ -148,23 +148,23 @@ func (msg *IRCMessage) AllTags() (result map[string]string) {
} }
// ClientOnlyTags returns the client-only tags (the tags with the + prefix). // ClientOnlyTags returns the client-only tags (the tags with the + prefix).
// The returned map may be internal storage of the IRCMessage object and // The returned map may be internal storage of the Message object and
// should not be modified. // should not be modified.
func (msg *IRCMessage) ClientOnlyTags() map[string]string { func (msg *Message) ClientOnlyTags() map[string]string {
return msg.clientOnlyTags return msg.clientOnlyTags
} }
// ParseLine creates and returns a message from the given IRC line. // ParseLine creates and returns a message from the given IRC line.
func ParseLine(line string) (ircmsg IRCMessage, err error) { func ParseLine(line string) (ircmsg Message, err error) {
return parseLine(line, 0, 0) return parseLine(line, 0, 0)
} }
// ParseLineStrict creates and returns an IRCMessage from the given IRC line, // ParseLineStrict creates and returns an Message from the given IRC line,
// taking the maximum length into account and truncating the message as appropriate. // taking the maximum length into account and truncating the message as appropriate.
// If fromClient is true, it enforces the client limit on tag data length (4094 bytes), // If fromClient is true, it enforces the client limit on tag data length (4094 bytes),
// allowing the server to return ERR_INPUTTOOLONG as appropriate. If truncateLen is // allowing the server to return ERR_INPUTTOOLONG as appropriate. If truncateLen is
// nonzero, it is the length at which the non-tag portion of the message is truncated. // nonzero, it is the length at which the non-tag portion of the message is truncated.
func ParseLineStrict(line string, fromClient bool, truncateLen int) (ircmsg IRCMessage, err error) { func ParseLineStrict(line string, fromClient bool, truncateLen int) (ircmsg Message, err error) {
maxTagDataLength := MaxlenTagData maxTagDataLength := MaxlenTagData
if fromClient { if fromClient {
maxTagDataLength = MaxlenClientTagData maxTagDataLength = MaxlenClientTagData
@ -180,7 +180,7 @@ func trimInitialSpaces(str string) string {
return str[i:] return str[i:]
} }
func parseLine(line string, maxTagDataLength int, truncateLen int) (ircmsg IRCMessage, err error) { func parseLine(line string, maxTagDataLength int, truncateLen int) (ircmsg Message, err error) {
// remove either \n or \r\n from the end of the line: // remove either \n or \r\n from the end of the line:
line = strings.TrimSuffix(line, "\n") line = strings.TrimSuffix(line, "\n")
line = strings.TrimSuffix(line, "\r") line = strings.TrimSuffix(line, "\r")
@ -279,7 +279,7 @@ func parseLine(line string, maxTagDataLength int, truncateLen int) (ircmsg IRCMe
} }
// helper to parse tags // helper to parse tags
func (ircmsg *IRCMessage) parseTags(tags string) (err error) { func (ircmsg *Message) parseTags(tags string) (err error) {
for 0 < len(tags) { for 0 < len(tags) {
tagEnd := strings.IndexByte(tags, ';') tagEnd := strings.IndexByte(tags, ';')
endPos := tagEnd endPos := tagEnd
@ -311,8 +311,8 @@ func (ircmsg *IRCMessage) parseTags(tags string) (err error) {
return nil return nil
} }
// MakeMessage provides a simple way to create a new IRCMessage. // MakeMessage provides a simple way to create a new Message.
func MakeMessage(tags map[string]string, prefix string, command string, params ...string) (ircmsg IRCMessage) { func MakeMessage(tags map[string]string, prefix string, command string, params ...string) (ircmsg Message) {
ircmsg.Prefix = prefix ircmsg.Prefix = prefix
ircmsg.Command = command ircmsg.Command = command
ircmsg.Params = params ircmsg.Params = params
@ -320,8 +320,8 @@ func MakeMessage(tags map[string]string, prefix string, command string, params .
return ircmsg return ircmsg
} }
// Line returns a sendable line created from an IRCMessage. // Line returns a sendable line created from an Message.
func (ircmsg *IRCMessage) Line() (result string, err error) { func (ircmsg *Message) Line() (result string, err error) {
bytes, err := ircmsg.line(0, 0, 0, 0) bytes, err := ircmsg.line(0, 0, 0, 0)
if err == nil { if err == nil {
result = string(bytes) result = string(bytes)
@ -329,17 +329,17 @@ func (ircmsg *IRCMessage) Line() (result string, err error) {
return return
} }
// LineBytes returns a sendable line created from an IRCMessage. // LineBytes returns a sendable line created from an Message.
func (ircmsg *IRCMessage) LineBytes() (result []byte, err error) { func (ircmsg *Message) LineBytes() (result []byte, err error) {
result, err = ircmsg.line(0, 0, 0, 0) result, err = ircmsg.line(0, 0, 0, 0)
return return
} }
// LineBytesStrict returns a sendable line, as a []byte, created from an IRCMessage. // LineBytesStrict returns a sendable line, as a []byte, created from an Message.
// fromClient controls whether the server-side or client-side tag length limit // fromClient controls whether the server-side or client-side tag length limit
// is enforced. If truncateLen is nonzero, it is the length at which the // is enforced. If truncateLen is nonzero, it is the length at which the
// non-tag portion of the message is truncated. // non-tag portion of the message is truncated.
func (ircmsg *IRCMessage) LineBytesStrict(fromClient bool, truncateLen int) ([]byte, error) { func (ircmsg *Message) LineBytesStrict(fromClient bool, truncateLen int) ([]byte, error) {
var tagLimit, clientOnlyTagDataLimit, serverAddedTagDataLimit int var tagLimit, clientOnlyTagDataLimit, serverAddedTagDataLimit int
if fromClient { if fromClient {
// enforce client max tags: // enforce client max tags:
@ -359,8 +359,8 @@ func paramRequiresTrailing(param string) bool {
return len(param) == 0 || strings.IndexByte(param, ' ') != -1 || param[0] == ':' return len(param) == 0 || strings.IndexByte(param, ' ') != -1 || param[0] == ':'
} }
// line returns a sendable line created from an IRCMessage. // line returns a sendable line created from an Message.
func (ircmsg *IRCMessage) line(tagLimit, clientOnlyTagDataLimit, serverAddedTagDataLimit, truncateLen int) (result []byte, err error) { func (ircmsg *Message) line(tagLimit, clientOnlyTagDataLimit, serverAddedTagDataLimit, truncateLen int) (result []byte, err error) {
if len(ircmsg.Command) == 0 { if len(ircmsg.Command) == 0 {
return nil, ErrorCommandMissing return nil, ErrorCommandMissing
} }

@ -11,12 +11,12 @@ import (
type testcode struct { type testcode struct {
raw string raw string
message IRCMessage message Message
} }
type testcodewithlen struct { type testcodewithlen struct {
raw string raw string
length int length int
message IRCMessage message Message
truncateExpected bool truncateExpected bool
} }
@ -256,7 +256,7 @@ func TestEncodeErrors(t *testing.T) {
} }
} }
var testMessages = []IRCMessage{ var testMessages = []Message{
{ {
tags: map[string]string{"time": "2019-02-27T04:38:57.489Z", "account": "dan-"}, tags: map[string]string{"time": "2019-02-27T04:38:57.489Z", "account": "dan-"},
clientOnlyTags: map[string]string{"+status": "typing"}, clientOnlyTags: map[string]string{"+status": "typing"},
@ -329,7 +329,7 @@ func TestEncodeDecode(t *testing.T) {
} }
func TestForceTrailing(t *testing.T) { func TestForceTrailing(t *testing.T) {
message := IRCMessage{ message := Message{
Prefix: "shivaram", Prefix: "shivaram",
Command: "PRIVMSG", Command: "PRIVMSG",
Params: []string{"#darwin", "nice"}, Params: []string{"#darwin", "nice"},
@ -352,7 +352,7 @@ func TestForceTrailing(t *testing.T) {
} }
func TestErrorLineTooLongGeneration(t *testing.T) { func TestErrorLineTooLongGeneration(t *testing.T) {
message := IRCMessage{ message := Message{
tags: map[string]string{"draft/msgid": "SAXV5OYJUr18CNJzdWa1qQ"}, tags: map[string]string{"draft/msgid": "SAXV5OYJUr18CNJzdWa1qQ"},
Prefix: "shivaram", Prefix: "shivaram",
Command: "PRIVMSG", Command: "PRIVMSG",

@ -30,7 +30,7 @@ func init() {
// EscapeTagValue takes a value, and returns an escaped message tag value. // EscapeTagValue takes a value, and returns an escaped message tag value.
// //
// This function is automatically used when lines are created from an // This function is automatically used when lines are created from an
// IRCMessage, so you don't need to call it yourself before creating a line. // Message, so you don't need to call it yourself before creating a line.
func EscapeTagValue(inString string) string { func EscapeTagValue(inString string) string {
return valtoescape.Replace(inString) return valtoescape.Replace(inString)
} }

@ -10,7 +10,7 @@ import (
) )
/* /*
IRCReader is an optimized line reader for IRC lines containing tags; Reader is an optimized line reader for IRC lines containing tags;
most IRC lines will not approach the maximum line length (8191 bytes most IRC lines will not approach the maximum line length (8191 bytes
of tag data, plus 512 bytes of message data), so we want a buffered of tag data, plus 512 bytes of message data), so we want a buffered
reader that can start with a smaller buffer and expand if necessary, reader that can start with a smaller buffer and expand if necessary,
@ -21,7 +21,7 @@ var (
ErrReadQ = errors.New("readQ exceeded (read too many bytes without terminating newline)") ErrReadQ = errors.New("readQ exceeded (read too many bytes without terminating newline)")
) )
type IRCReader struct { type Reader struct {
conn io.Reader conn io.Reader
initialSize int initialSize int
@ -34,17 +34,17 @@ type IRCReader struct {
eof bool eof bool
} }
// Returns a new *IRCReader with sane buffer size limits. // Returns a new *Reader with sane buffer size limits.
func NewIRCReader(conn io.Reader) *IRCReader { func NewIRCReader(conn io.Reader) *Reader {
var reader IRCReader var reader Reader
reader.Initialize(conn, 512, 8192+1024) reader.Initialize(conn, 512, 8192+1024)
return &reader return &reader
} }
// "Placement new" for an IRCReader; initializes it with custom buffer size // "Placement new" for a Reader; initializes it with custom buffer size
// limits. // limits.
func (cc *IRCReader) Initialize(conn io.Reader, initialSize, maxSize int) { func (cc *Reader) Initialize(conn io.Reader, initialSize, maxSize int) {
*cc = IRCReader{} *cc = Reader{}
cc.conn = conn cc.conn = conn
cc.initialSize = initialSize cc.initialSize = initialSize
cc.maxSize = maxSize cc.maxSize = maxSize
@ -54,7 +54,7 @@ func (cc *IRCReader) Initialize(conn io.Reader, initialSize, maxSize int) {
// or \r\n as the line terminator (but not \r in isolation). Passes through // or \r\n as the line terminator (but not \r in isolation). Passes through
// errors from the underlying connection. Returns ErrReadQ if the buffer limit // errors from the underlying connection. Returns ErrReadQ if the buffer limit
// was exceeded without a terminating \n. // was exceeded without a terminating \n.
func (cc *IRCReader) ReadLine() ([]byte, error) { func (cc *Reader) ReadLine() ([]byte, error) {
for { for {
// try to find a terminated line in the buffered data already read // try to find a terminated line in the buffered data already read
nlidx := bytes.IndexByte(cc.buf[cc.searchFrom:cc.end], '\n') nlidx := bytes.IndexByte(cc.buf[cc.searchFrom:cc.end], '\n')

@ -158,7 +158,7 @@ func TestRegression(t *testing.T) {
// this terminates the previous read, within the acceptable limit: // this terminates the previous read, within the acceptable limit:
c.reads = append(c.reads, makeLine(500, true)) c.reads = append(c.reads, makeLine(500, true))
var cc IRCReader var cc Reader
cc.Initialize(&c, 512, 4096+512) cc.Initialize(&c, 512, 4096+512)
line, err := cc.ReadLine() line, err := cc.ReadLine()