diff --git a/README.md b/README.md index 76fc5ad3..2cdbb904 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Matterbridge wouldn't exist without these libraries: * echo - https://github.com/labstack/echo * gitter - https://github.com/sromku/go-gitter * gops - https://github.com/google/gops -* irc - https://github.com/thoj/go-ircevent +* irc - https://github.com/lrstanley/girc * mattermost - https://github.com/mattermost/platform * matrix - https://github.com/matrix-org/gomatrix * slack - https://github.com/nlopes/slack diff --git a/vendor/github.com/lrstanley/girc/builtin.go b/vendor/github.com/lrstanley/girc/builtin.go index aecb1e11..e7ccc199 100644 --- a/vendor/github.com/lrstanley/girc/builtin.go +++ b/vendor/github.com/lrstanley/girc/builtin.go @@ -16,64 +16,62 @@ func (c *Client) registerBuiltins() { c.Handlers.mu.Lock() // Built-in things that should always be supported. - c.Handlers.register(true, RPL_WELCOME, HandlerFunc(func(c *Client, e Event) { - go handleConnect(c, e) - })) - c.Handlers.register(true, PING, HandlerFunc(handlePING)) - c.Handlers.register(true, PONG, HandlerFunc(handlePONG)) + c.Handlers.register(true, true, RPL_WELCOME, HandlerFunc(handleConnect)) + c.Handlers.register(true, false, PING, HandlerFunc(handlePING)) + c.Handlers.register(true, false, PONG, HandlerFunc(handlePONG)) if !c.Config.disableTracking { // Joins/parts/anything that may add/remove/rename users. - c.Handlers.register(true, JOIN, HandlerFunc(handleJOIN)) - c.Handlers.register(true, PART, HandlerFunc(handlePART)) - c.Handlers.register(true, KICK, HandlerFunc(handleKICK)) - c.Handlers.register(true, QUIT, HandlerFunc(handleQUIT)) - c.Handlers.register(true, NICK, HandlerFunc(handleNICK)) - c.Handlers.register(true, RPL_NAMREPLY, HandlerFunc(handleNAMES)) + c.Handlers.register(true, false, JOIN, HandlerFunc(handleJOIN)) + c.Handlers.register(true, false, PART, HandlerFunc(handlePART)) + c.Handlers.register(true, false, KICK, HandlerFunc(handleKICK)) + c.Handlers.register(true, false, QUIT, HandlerFunc(handleQUIT)) + c.Handlers.register(true, false, NICK, HandlerFunc(handleNICK)) + c.Handlers.register(true, false, RPL_NAMREPLY, HandlerFunc(handleNAMES)) // Modes. - c.Handlers.register(true, MODE, HandlerFunc(handleMODE)) - c.Handlers.register(true, RPL_CHANNELMODEIS, HandlerFunc(handleMODE)) + c.Handlers.register(true, false, MODE, HandlerFunc(handleMODE)) + c.Handlers.register(true, false, RPL_CHANNELMODEIS, HandlerFunc(handleMODE)) // WHO/WHOX responses. - c.Handlers.register(true, RPL_WHOREPLY, HandlerFunc(handleWHO)) - c.Handlers.register(true, RPL_WHOSPCRPL, HandlerFunc(handleWHO)) + c.Handlers.register(true, false, RPL_WHOREPLY, HandlerFunc(handleWHO)) + c.Handlers.register(true, false, RPL_WHOSPCRPL, HandlerFunc(handleWHO)) // Other misc. useful stuff. - c.Handlers.register(true, TOPIC, HandlerFunc(handleTOPIC)) - c.Handlers.register(true, RPL_TOPIC, HandlerFunc(handleTOPIC)) - c.Handlers.register(true, RPL_MYINFO, HandlerFunc(handleMYINFO)) - c.Handlers.register(true, RPL_ISUPPORT, HandlerFunc(handleISUPPORT)) - c.Handlers.register(true, RPL_MOTDSTART, HandlerFunc(handleMOTD)) - c.Handlers.register(true, RPL_MOTD, HandlerFunc(handleMOTD)) + c.Handlers.register(true, false, TOPIC, HandlerFunc(handleTOPIC)) + c.Handlers.register(true, false, RPL_TOPIC, HandlerFunc(handleTOPIC)) + c.Handlers.register(true, false, RPL_MYINFO, HandlerFunc(handleMYINFO)) + c.Handlers.register(true, false, RPL_ISUPPORT, HandlerFunc(handleISUPPORT)) + c.Handlers.register(true, false, RPL_MOTDSTART, HandlerFunc(handleMOTD)) + c.Handlers.register(true, false, RPL_MOTD, HandlerFunc(handleMOTD)) // Keep users lastactive times up to date. - c.Handlers.register(true, PRIVMSG, HandlerFunc(updateLastActive)) - c.Handlers.register(true, NOTICE, HandlerFunc(updateLastActive)) - c.Handlers.register(true, TOPIC, HandlerFunc(updateLastActive)) - c.Handlers.register(true, KICK, HandlerFunc(updateLastActive)) + c.Handlers.register(true, false, PRIVMSG, HandlerFunc(updateLastActive)) + c.Handlers.register(true, false, NOTICE, HandlerFunc(updateLastActive)) + c.Handlers.register(true, false, TOPIC, HandlerFunc(updateLastActive)) + c.Handlers.register(true, false, KICK, HandlerFunc(updateLastActive)) // CAP IRCv3-specific tracking and functionality. - c.Handlers.register(true, CAP, HandlerFunc(handleCAP)) - c.Handlers.register(true, CAP_CHGHOST, HandlerFunc(handleCHGHOST)) - c.Handlers.register(true, CAP_AWAY, HandlerFunc(handleAWAY)) - c.Handlers.register(true, CAP_ACCOUNT, HandlerFunc(handleACCOUNT)) - c.Handlers.register(true, ALL_EVENTS, HandlerFunc(handleTags)) + c.Handlers.register(true, false, CAP, HandlerFunc(handleCAP)) + c.Handlers.register(true, false, CAP_CHGHOST, HandlerFunc(handleCHGHOST)) + c.Handlers.register(true, false, CAP_AWAY, HandlerFunc(handleAWAY)) + c.Handlers.register(true, false, CAP_ACCOUNT, HandlerFunc(handleACCOUNT)) + c.Handlers.register(true, false, ALL_EVENTS, HandlerFunc(handleTags)) // SASL IRCv3 support. - c.Handlers.register(true, AUTHENTICATE, HandlerFunc(handleSASL)) - c.Handlers.register(true, RPL_SASLSUCCESS, HandlerFunc(handleSASL)) - c.Handlers.register(true, RPL_NICKLOCKED, HandlerFunc(handleSASLError)) - c.Handlers.register(true, ERR_SASLFAIL, HandlerFunc(handleSASLError)) - c.Handlers.register(true, ERR_SASLTOOLONG, HandlerFunc(handleSASLError)) - c.Handlers.register(true, ERR_SASLABORTED, HandlerFunc(handleSASLError)) - c.Handlers.register(true, RPL_SASLMECHS, HandlerFunc(handleSASLError)) + c.Handlers.register(true, false, AUTHENTICATE, HandlerFunc(handleSASL)) + c.Handlers.register(true, false, RPL_SASLSUCCESS, HandlerFunc(handleSASL)) + c.Handlers.register(true, false, RPL_NICKLOCKED, HandlerFunc(handleSASLError)) + c.Handlers.register(true, false, ERR_SASLFAIL, HandlerFunc(handleSASLError)) + c.Handlers.register(true, false, ERR_SASLTOOLONG, HandlerFunc(handleSASLError)) + c.Handlers.register(true, false, ERR_SASLABORTED, HandlerFunc(handleSASLError)) + c.Handlers.register(true, false, RPL_SASLMECHS, HandlerFunc(handleSASLError)) } // Nickname collisions. - c.Handlers.register(true, ERR_NICKNAMEINUSE, HandlerFunc(nickCollisionHandler)) - c.Handlers.register(true, ERR_NICKCOLLISION, HandlerFunc(nickCollisionHandler)) - c.Handlers.register(true, ERR_UNAVAILRESOURCE, HandlerFunc(nickCollisionHandler)) + c.Handlers.register(true, false, ERR_NICKNAMEINUSE, HandlerFunc(nickCollisionHandler)) + c.Handlers.register(true, false, ERR_NICKCOLLISION, HandlerFunc(nickCollisionHandler)) + c.Handlers.register(true, false, ERR_UNAVAILRESOURCE, HandlerFunc(nickCollisionHandler)) c.Handlers.mu.Unlock() } @@ -389,7 +387,7 @@ func handleISUPPORT(c *Client, e Event) { c.state.Lock() // Skip the first parameter, as it's our nickname. for i := 1; i < len(e.Params); i++ { - j := strings.IndexByte(e.Params[i], 0x3D) // = + j := strings.IndexByte(e.Params[i], '=') if j < 1 || (j+1) == len(e.Params[i]) { c.state.serverOptions[e.Params[i]] = "" diff --git a/vendor/github.com/lrstanley/girc/cap.go b/vendor/github.com/lrstanley/girc/cap.go index a63dfe9d..1d50460f 100644 --- a/vendor/github.com/lrstanley/girc/cap.go +++ b/vendor/github.com/lrstanley/girc/cap.go @@ -375,11 +375,11 @@ func handleTags(c *Client, e Event) { } const ( - prefixTag byte = 0x40 // @ - prefixTagValue byte = 0x3D // = - prefixUserTag byte = 0x2B // + - tagSeparator byte = 0x3B // ; - maxTagLength int = 511 // 510 + @ and " " (space), though space usually not included. + prefixTag byte = '@' + prefixTagValue byte = '=' + prefixUserTag byte = '+' + tagSeparator byte = ';' + maxTagLength int = 511 // 510 + @ and " " (space), though space usually not included. ) // Tags represents the key-value pairs in IRCv3 message tags. The map contains @@ -618,7 +618,7 @@ func validTag(name string) bool { for i := 0; i < len(name); i++ { // A-Z, a-z, 0-9, -/._ - if (name[i] < 0x41 || name[i] > 0x5A) && (name[i] < 0x61 || name[i] > 0x7A) && (name[i] < 0x2D || name[i] > 0x39) && name[i] != 0x5F { + if (name[i] < 'A' || name[i] > 'Z') && (name[i] < 'a' || name[i] > 'z') && (name[i] < '-' || name[i] > '9') && name[i] != '_' { return false } } @@ -631,7 +631,7 @@ func validTag(name string) bool { func validTagValue(value string) bool { for i := 0; i < len(value); i++ { // Don't allow any invisible chars within the tag, or semicolons. - if value[i] < 0x21 || value[i] > 0x7E || value[i] == 0x3B { + if value[i] < '!' || value[i] > '~' || value[i] == ';' { return false } } diff --git a/vendor/github.com/lrstanley/girc/client.go b/vendor/github.com/lrstanley/girc/client.go index 403a7aec..501554b9 100644 --- a/vendor/github.com/lrstanley/girc/client.go +++ b/vendor/github.com/lrstanley/girc/client.go @@ -464,9 +464,9 @@ func (c *Client) GetHost() string { return c.state.host } -// Channels returns the active list of channels that the client is in. +// ChannelList returns the active list of channel names that the client is in. // Panics if tracking is disabled. -func (c *Client) Channels() []string { +func (c *Client) ChannelList() []string { c.panicIfNotTracking() c.state.RLock() @@ -482,9 +482,26 @@ func (c *Client) Channels() []string { return channels } -// Users returns the active list of users that the client is tracking across -// all files. Panics if tracking is disabled. -func (c *Client) Users() []string { +// Channels returns the active channels that the client is in. Panics if +// tracking is disabled. +func (c *Client) Channels() []*Channel { + c.panicIfNotTracking() + + c.state.RLock() + channels := make([]*Channel, len(c.state.channels)) + var i int + for channel := range c.state.channels { + channels[i] = c.state.channels[channel].Copy() + i++ + } + c.state.RUnlock() + + return channels +} + +// UserList returns the active list of nicknames that the client is tracking +// across all networks. Panics if tracking is disabled. +func (c *Client) UserList() []string { c.panicIfNotTracking() c.state.RLock() @@ -500,6 +517,23 @@ func (c *Client) Users() []string { return users } +// Users returns the active users that the client is tracking across all +// networks. Panics if tracking is disabled. +func (c *Client) Users() []*User { + c.panicIfNotTracking() + + c.state.RLock() + users := make([]*User, len(c.state.users)) + var i int + for user := range c.state.users { + users[i] = c.state.users[user].Copy() + i++ + } + c.state.RUnlock() + + return users +} + // LookupChannel looks up a given channel in state. If the channel doesn't // exist, nil is returned. Panics if tracking is disabled. func (c *Client) LookupChannel(name string) *Channel { diff --git a/vendor/github.com/lrstanley/girc/commands.go b/vendor/github.com/lrstanley/girc/commands.go index 3f7d3bdc..d22f7616 100644 --- a/vendor/github.com/lrstanley/girc/commands.go +++ b/vendor/github.com/lrstanley/girc/commands.go @@ -272,6 +272,27 @@ func (cmd *Commands) Kick(channel, user, reason string) { cmd.c.Send(&Event{Command: KICK, Params: []string{channel, user}}) } +// Ban adds the +b mode on the given mask on a channel. +func (cmd *Commands) Ban(channel, mask string) { + cmd.Mode(channel, "+b", mask) +} + +// Unban removes the +b mode on the given mask on a channel. +func (cmd *Commands) Unban(channel, mask string) { + cmd.Mode(channel, "-b", mask) +} + +// Mode sends a mode change to the server which should be applied to target +// (usually a channel or user), along with a set of modes (generally "+m", +// "+mmmm", or "-m", where "m" is the mode you want to change). Params is only +// needed if the mode change requires a parameter (ban or invite-only exclude.) +func (cmd *Commands) Mode(target, modes string, params ...string) { + out := []string{target, modes} + out = append(out, params...) + + cmd.c.Send(&Event{Command: MODE, Params: out}) +} + // Invite sends a INVITE query to the server, to invite nick to channel. func (cmd *Commands) Invite(channel string, users ...string) { for i := 0; i < len(users); i++ { diff --git a/vendor/github.com/lrstanley/girc/ctcp.go b/vendor/github.com/lrstanley/girc/ctcp.go index c6c0bace..6076ab10 100644 --- a/vendor/github.com/lrstanley/girc/ctcp.go +++ b/vendor/github.com/lrstanley/girc/ctcp.go @@ -58,7 +58,7 @@ func decodeCTCP(e *Event) *CTCPEvent { if s < 0 { for i := 0; i < len(text); i++ { // Check for A-Z, 0-9. - if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) { + if (text[i] < 'A' || text[i] > 'Z') && (text[i] < '0' || text[i] > '9') { return nil } } @@ -74,7 +74,7 @@ func decodeCTCP(e *Event) *CTCPEvent { // Loop through checking the tag first. for i := 0; i < s; i++ { // Check for A-Z, 0-9. - if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) { + if (text[i] < 'A' || text[i] > 'Z') && (text[i] < '0' || text[i] > '9') { return nil } } @@ -168,7 +168,7 @@ func (c *CTCP) parseCMD(cmd string) string { for i := 0; i < len(cmd); i++ { // Check for A-Z, 0-9. - if (cmd[i] < 0x41 || cmd[i] > 0x5A) && (cmd[i] < 0x30 || cmd[i] > 0x39) { + if (cmd[i] < 'A' || cmd[i] > 'Z') && (cmd[i] < '0' || cmd[i] > '9') { return "" } } diff --git a/vendor/github.com/lrstanley/girc/event.go b/vendor/github.com/lrstanley/girc/event.go index 77d9b45e..ef7209ed 100644 --- a/vendor/github.com/lrstanley/girc/event.go +++ b/vendor/github.com/lrstanley/girc/event.go @@ -11,8 +11,8 @@ import ( ) const ( - eventSpace byte = 0x20 // Separator. - maxLength = 510 // Maximum length is 510 (2 for line endings). + eventSpace byte = ' ' // Separator. + maxLength = 510 // Maximum length is 510 (2 for line endings). ) // cutCRFunc is used to trim CR characters from prefixes/messages. @@ -256,7 +256,7 @@ func (e *Event) Bytes() []byte { // Strip newlines and carriage returns. for i := 0; i < len(out); i++ { - if out[i] == 0x0A || out[i] == 0x0D { + if out[i] == '\n' || out[i] == '\r' { out = append(out[:i], out[i+1:]...) i-- // Decrease the index so we can pick up where we left off. } @@ -432,9 +432,9 @@ func (e *Event) StripAction() string { } const ( - messagePrefix byte = 0x3A // ":" -- prefix or last argument - prefixIdent byte = 0x21 // "!" -- username - prefixHost byte = 0x40 // "@" -- hostname + messagePrefix byte = ':' // Prefix or last argument. + prefixIdent byte = '!' // Username. + prefixHost byte = '@' // Hostname. ) // Source represents the sender of an IRC event, see RFC1459 section 2.3.1. diff --git a/vendor/github.com/lrstanley/girc/format.go b/vendor/github.com/lrstanley/girc/format.go index 5e3b5a6a..78a1f7bb 100644 --- a/vendor/github.com/lrstanley/girc/format.go +++ b/vendor/github.com/lrstanley/girc/format.go @@ -12,8 +12,8 @@ import ( ) const ( - fmtOpenChar = 0x7B // { - fmtCloseChar = 0x7D // } + fmtOpenChar = '{' + fmtCloseChar = '}' ) var fmtColors = map[string]int{ @@ -113,7 +113,7 @@ func Fmt(text string) string { if last > -1 { // A-Z, a-z, and "," - if text[i] != 0x2c && (text[i] <= 0x41 || text[i] >= 0x5a) && (text[i] <= 0x61 || text[i] >= 0x7a) { + if text[i] != ',' && (text[i] <= 'A' || text[i] >= 'Z') && (text[i] <= 'a' || text[i] >= 'z') { last = -1 continue } @@ -127,10 +127,10 @@ func Fmt(text string) string { // See Fmt() for more information. func TrimFmt(text string) string { for color := range fmtColors { - text = strings.Replace(text, "{"+color+"}", "", -1) + text = strings.Replace(text, string(fmtOpenChar)+color+string(fmtCloseChar), "", -1) } for code := range fmtCodes { - text = strings.Replace(text, "{"+code+"}", "", -1) + text = strings.Replace(text, string(fmtOpenChar)+code+string(fmtCloseChar), "", -1) } return text @@ -175,9 +175,10 @@ func IsValidChannel(channel string) bool { return false } - // #, +, !, or & - // Including "*" in the prefix list, as this is commonly used (e.g. ZNC) - if bytes.IndexByte([]byte{0x21, 0x23, 0x26, 0x2A, 0x2B}, channel[0]) == -1 { + // #, +, !, ~, or & + // Including "*" and "~" in the prefix list, as these are commonly used + // (e.g. ZNC.) + if bytes.IndexByte([]byte{'!', '#', '&', '*', '~', '+'}, channel[0]) == -1 { return false } @@ -186,14 +187,14 @@ func IsValidChannel(channel string) bool { // 1 (prefix) + 5 (id) + 1 (+, channel name) // On some networks, this may be extended with ISUPPORT capabilities, // however this is extremely uncommon. - if channel[0] == 0x21 { + if channel[0] == '!' { if len(channel) < 7 { return false } // check for valid ID for i := 1; i < 6; i++ { - if (channel[i] < 0x30 || channel[i] > 0x39) && (channel[i] < 0x41 || channel[i] > 0x5A) { + if (channel[i] < '0' || channel[i] > '9') && (channel[i] < 'A' || channel[i] > 'Z') { return false } } @@ -222,18 +223,15 @@ func IsValidNick(nick string) bool { return false } - nick = ToRFC1459(nick) - // Check the first index. Some characters aren't allowed for the first // index of an IRC nickname. - if nick[0] < 0x41 || nick[0] > 0x7D { - // a-z, A-Z, and _\[]{}^| + if (nick[0] < 'A' || nick[0] > '}') && nick[0] != '?' { + // a-z, A-Z, '_\[]{}^|', and '?' in the case of znc. return false } for i := 1; i < len(nick); i++ { - if (nick[i] < 0x41 || nick[i] > 0x7E) && (nick[i] < 0x30 || nick[i] > 0x39) && nick[i] != 0x2D { - fmt.Println(nick[i], i, nick) + if (nick[i] < 'A' || nick[i] > '}') && (nick[i] < '0' || nick[i] > '9') && nick[i] != '-' { // a-z, A-Z, 0-9, -, and _\[]{}^| return false } @@ -262,10 +260,8 @@ func IsValidUser(name string) bool { return false } - name = ToRFC1459(name) - // "~" is prepended (commonly) if there was no ident server response. - if name[0] == 0x7E { + if name[0] == '~' { // Means name only contained "~". if len(name) < 2 { return false @@ -275,12 +271,12 @@ func IsValidUser(name string) bool { } // Check to see if the first index is alphanumeric. - if (name[0] < 0x41 || name[0] > 0x4A) && (name[0] < 0x61 || name[0] > 0x7A) && (name[0] < 0x30 || name[0] > 0x39) { + if (name[0] < 'A' || name[0] > 'J') && (name[0] < 'a' || name[0] > 'z') && (name[0] < '0' || name[0] > '9') { return false } for i := 1; i < len(name); i++ { - if (name[i] < 0x41 || name[i] > 0x7D) && (name[i] < 0x30 || name[i] > 0x39) && name[i] != 0x2D && name[i] != 0x2E { + if (name[i] < 'A' || name[i] > '}') && (name[i] < '0' || name[i] > '9') && name[i] != '-' && name[i] != '.' { // a-z, A-Z, 0-9, -, and _\[]{}^| return false } @@ -291,7 +287,10 @@ func IsValidUser(name string) bool { // ToRFC1459 converts a string to the stripped down conversion within RFC // 1459. This will do things like replace an "A" with an "a", "[]" with "{}", -// and so forth. Useful to compare two nicknames or channels. +// and so forth. Useful to compare two nicknames or channels. Note that this +// should not be used to normalize nicknames or similar, as this may convert +// valid input characters to non-rfc-valid characters. As such, it's main use +// is for comparing two nicks. func ToRFC1459(input string) string { var out string diff --git a/vendor/github.com/lrstanley/girc/handler.go b/vendor/github.com/lrstanley/girc/handler.go index f0c737f2..6c082708 100644 --- a/vendor/github.com/lrstanley/girc/handler.go +++ b/vendor/github.com/lrstanley/girc/handler.go @@ -29,11 +29,12 @@ func (c *Client) RunHandlers(event *Event) { } } - // Regular wildcard handlers. - c.Handlers.exec(ALL_EVENTS, c, event.Copy()) + // Background handlers first. + c.Handlers.exec(ALL_EVENTS, true, c, event.Copy()) + c.Handlers.exec(event.Command, true, c, event.Copy()) - // Then regular handlers. - c.Handlers.exec(event.Command, c, event.Copy()) + c.Handlers.exec(ALL_EVENTS, false, c, event.Copy()) + c.Handlers.exec(event.Command, false, c, event.Copy()) // Check if it's a CTCP. if ctcp := decodeCTCP(event.Copy()); ctcp != nil { @@ -144,7 +145,7 @@ func (c *Caller) cuid(cmd string, n int) (cuid, uid string) { // cuidToID allows easy mapping between a generated cuid and the caller // external/internal handler maps. func (c *Caller) cuidToID(input string) (cmd, uid string) { - i := strings.IndexByte(input, 0x3A) + i := strings.IndexByte(input, ':') if i < 0 { return "", "" } @@ -160,9 +161,9 @@ type execStack struct { // exec executes all handlers pertaining to specified event. Internal first, // then external. // -// Please note that there is no specific order/priority for which the -// handler types themselves or the handlers are executed. -func (c *Caller) exec(command string, client *Client, event *Event) { +// Please note that there is no specific order/priority for which the handlers +// are executed. +func (c *Caller) exec(command string, bg bool, client *Client, event *Event) { // Build a stack of handlers which can be executed concurrently. var stack []execStack @@ -170,13 +171,21 @@ func (c *Caller) exec(command string, client *Client, event *Event) { // Get internal handlers first. if _, ok := c.internal[command]; ok { for cuid := range c.internal[command] { + if (strings.HasSuffix(cuid, ":bg") && !bg) || (!strings.HasSuffix(cuid, ":bg") && bg) { + continue + } + stack = append(stack, execStack{c.internal[command][cuid], cuid}) } } - // Aaand then external handlers. + // Then external handlers. if _, ok := c.external[command]; ok { for cuid := range c.external[command] { + if (strings.HasSuffix(cuid, ":bg") && !bg) || (!strings.HasSuffix(cuid, ":bg") && bg) { + continue + } + stack = append(stack, execStack{c.external[command][cuid], cuid}) } } @@ -189,18 +198,29 @@ func (c *Caller) exec(command string, client *Client, event *Event) { wg.Add(len(stack)) for i := 0; i < len(stack); i++ { go func(index int) { - c.debug.Printf("executing handler %s for event %s (%d of %d)", stack[index].cuid, command, index+1, len(stack)) + defer wg.Done() + c.debug.Printf("[%d/%d] exec %s => %s", index+1, len(stack), stack[index].cuid, command) start := time.Now() - // If they want to catch any panics, add to defer stack. + if bg { + go func() { + if client.Config.RecoverFunc != nil { + defer recoverHandlerPanic(client, event, stack[index].cuid, 3) + } + + stack[index].Execute(client, *event) + c.debug.Printf("[%d/%d] done %s == %s", index+1, len(stack), stack[index].cuid, time.Since(start)) + }() + + return + } + if client.Config.RecoverFunc != nil { defer recoverHandlerPanic(client, event, stack[index].cuid, 3) } stack[index].Execute(client, *event) - - c.debug.Printf("execution of %s took %s (%d of %d)", stack[index].cuid, time.Since(start), index+1, len(stack)) - wg.Done() + c.debug.Printf("[%d/%d] done %s == %s", index+1, len(stack), stack[index].cuid, time.Since(start)) }(i) } @@ -281,9 +301,9 @@ func (c *Caller) remove(cuid string) (success bool) { // sregister is much like Caller.register(), except that it safely locks // the Caller mutex. -func (c *Caller) sregister(internal bool, cmd string, handler Handler) (cuid string) { +func (c *Caller) sregister(internal, bg bool, cmd string, handler Handler) (cuid string) { c.mu.Lock() - cuid = c.register(internal, cmd, handler) + cuid = c.register(internal, bg, cmd, handler) c.mu.Unlock() return cuid @@ -291,30 +311,34 @@ func (c *Caller) sregister(internal bool, cmd string, handler Handler) (cuid str // register will register a handler in the internal tracker. Unsafe (you // must lock c.mu yourself!) -func (c *Caller) register(internal bool, cmd string, handler Handler) (cuid string) { +func (c *Caller) register(internal, bg bool, cmd string, handler Handler) (cuid string) { var uid string cmd = strings.ToUpper(cmd) + cuid, uid = c.cuid(cmd, 20) + if bg { + uid += ":bg" + cuid += ":bg" + } + if internal { if _, ok := c.internal[cmd]; !ok { c.internal[cmd] = map[string]Handler{} } - cuid, uid = c.cuid(cmd, 20) c.internal[cmd][uid] = handler } else { if _, ok := c.external[cmd]; !ok { c.external[cmd] = map[string]Handler{} } - cuid, uid = c.cuid(cmd, 20) c.external[cmd][uid] = handler } _, file, line, _ := runtime.Caller(3) - c.debug.Printf("registering handler for %q with cuid %q (internal: %t) from: %s:%d", cmd, cuid, internal, file, line) + c.debug.Printf("reg %q => %s [int:%t bg:%t] %s:%d", uid, cmd, internal, bg, file, line) return cuid } @@ -323,31 +347,20 @@ func (c *Caller) register(internal bool, cmd string, handler Handler) (cuid stri // given event. cuid is the handler uid which can be used to remove the // handler with Caller.Remove(). func (c *Caller) AddHandler(cmd string, handler Handler) (cuid string) { - return c.sregister(false, cmd, handler) + return c.sregister(false, false, cmd, handler) } // Add registers the handler function for the given event. cuid is the // handler uid which can be used to remove the handler with Caller.Remove(). func (c *Caller) Add(cmd string, handler func(client *Client, event Event)) (cuid string) { - return c.sregister(false, cmd, HandlerFunc(handler)) + return c.sregister(false, false, cmd, HandlerFunc(handler)) } // AddBg registers the handler function for the given event and executes it // in a go-routine. cuid is the handler uid which can be used to remove the // handler with Caller.Remove(). func (c *Caller) AddBg(cmd string, handler func(client *Client, event Event)) (cuid string) { - return c.sregister(false, cmd, HandlerFunc(func(client *Client, event Event) { - // Setting up background-based handlers this way allows us to get - // clean call stacks for use with panic recovery. - go func() { - // If they want to catch any panics, add to defer stack. - if client.Config.RecoverFunc != nil { - defer recoverHandlerPanic(client, &event, "goroutine", 3) - } - - handler(client, event) - }() - })) + return c.sregister(false, true, cmd, HandlerFunc(handler)) } // AddTmp adds a "temporary" handler, which is good for one-time or few-time @@ -361,47 +374,37 @@ func (c *Caller) AddBg(cmd string, handler func(client *Client, event Event)) (c // // Additionally, AddTmp has a useful option, deadline. When set to greater // than 0, deadline will be the amount of time that passes before the handler -// is removed from the stack, regardless if the handler returns true or not. +// is removed from the stack, regardless of if the handler returns true or not. // This is useful in that it ensures that the handler is cleaned up if the // server does not respond appropriately, or takes too long to respond. // // Note that handlers supplied with AddTmp are executed in a goroutine to -// ensure that they are not blocking other handlers. Additionally, use cuid -// with Caller.Remove() to prematurely remove the handler from the stack, -// bypassing the timeout or waiting for the handler to return that it wants -// to be removed from the stack. +// ensure that they are not blocking other handlers. However, if you are +// creating a temporary handler from another handler, it should be a +// background handler. +// +// Use cuid with Caller.Remove() to prematurely remove the handler from the +// stack, bypassing the timeout or waiting for the handler to return that it +// wants to be removed from the stack. func (c *Caller) AddTmp(cmd string, deadline time.Duration, handler func(client *Client, event Event) bool) (cuid string, done chan struct{}) { - var uid string - cuid, uid = c.cuid(cmd, 20) - done = make(chan struct{}) - c.mu.Lock() - if _, ok := c.external[cmd]; !ok { - c.external[cmd] = map[string]Handler{} - } - c.external[cmd][uid] = HandlerFunc(func(client *Client, event Event) { - // Setting up background-based handlers this way allows us to get - // clean call stacks for use with panic recovery. - go func() { - // If they want to catch any panics, add to defer stack. - if client.Config.RecoverFunc != nil { - defer recoverHandlerPanic(client, &event, "tmp-goroutine", 3) + cuid = c.sregister(false, true, cmd, HandlerFunc(func(client *Client, event Event) { + remove := handler(client, event) + if remove { + if ok := c.Remove(cuid); ok { + close(done) } - - remove := handler(client, event) - if remove { - if ok := c.Remove(cuid); ok { - close(done) - } - } - }() - }) - c.mu.Unlock() + } + })) if deadline > 0 { go func() { - <-time.After(deadline) + select { + case <-time.After(deadline): + case <-done: + } + if ok := c.Remove(cuid); ok { close(done) } diff --git a/vendor/github.com/lrstanley/girc/modes.go b/vendor/github.com/lrstanley/girc/modes.go index c0ad7d11..864df71f 100644 --- a/vendor/github.com/lrstanley/girc/modes.go +++ b/vendor/github.com/lrstanley/girc/modes.go @@ -206,11 +206,11 @@ func (c *CModes) Parse(flags string, args []string) (out []CMode) { var argCount int for i := 0; i < len(flags); i++ { - if flags[i] == 0x2B { + if flags[i] == '+' { add = true continue } - if flags[i] == 0x2D { + if flags[i] == '-' { add = false continue } @@ -265,7 +265,7 @@ func IsValidChannelMode(raw string) bool { for i := 0; i < len(raw); i++ { // Allowed are: ",", A-Z and a-z. - if raw[i] != 0x2C && (raw[i] < 0x41 || raw[i] > 0x5A) && (raw[i] < 0x61 || raw[i] > 0x7A) { + if raw[i] != ',' && (raw[i] < 'A' || raw[i] > 'Z') && (raw[i] < 'a' || raw[i] > 'z') { return false } } @@ -279,7 +279,7 @@ func isValidUserPrefix(raw string) bool { return false } - if raw[0] != 0x28 { // (. + if raw[0] != '(' { return false } @@ -288,7 +288,7 @@ func isValidUserPrefix(raw string) bool { // Skip the first one as we know it's (. for i := 1; i < len(raw); i++ { - if raw[i] == 0x29 { // ). + if raw[i] == ')' { passedKeys = true continue } diff --git a/vendor/manifest b/vendor/manifest index 680cc709..98dcbd0e 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -282,7 +282,7 @@ "importpath": "github.com/lrstanley/girc", "repository": "https://github.com/lrstanley/girc", "vcs": "git", - "revision": "e698f0468e166574e122130ffd2f579aba7e2abc", + "revision": "5dff93b5453c1b2ac8382c9a38881635f47bba0e", "branch": "master", "notests": true },