From d4a8984e6308638c5efc66db1d81890b9366f975 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Sun, 8 Oct 2017 11:05:05 +1000 Subject: [PATCH 1/4] Initial implementation of labeled-responses for WHOIS --- irc/batch.go | 78 +++++++++++++++++++++++++++ irc/capability.go | 2 +- irc/client.go | 77 +++++++++++++++------------ irc/responsebuffer.go | 120 ++++++++++++++++++++++++++++++++++++++++++ irc/server.go | 37 +++++++------ 5 files changed, 264 insertions(+), 50 deletions(-) create mode 100644 irc/batch.go create mode 100644 irc/responsebuffer.go diff --git a/irc/batch.go b/irc/batch.go new file mode 100644 index 00000000..f5bfd852 --- /dev/null +++ b/irc/batch.go @@ -0,0 +1,78 @@ +// Copyright (c) 2017 Daniel Oaks +// released under the MIT license + +package irc + +import ( + "strconv" + "time" + + "github.com/goshuirc/irc-go/ircmsg" + "github.com/oragono/oragono/irc/caps" +) + +const ( + // maxBatchID is the maximum ID the batch counter can get to before it rotates. + // + // Batch IDs are made up of the current unix timestamp plus a rolling int ID that's + // incremented for every new batch. It's an alright solution and will work unless we get + // more than maxId batches per nanosecond. Later on when we have S2S linking, the batch + // ID will also contain the server ID to ensure they stay unique. + maxBatchID uint64 = 60000 +) + +// BatchManager helps generate new batches and new batch IDs. +type BatchManager struct { + idCounter uint64 +} + +// NewBatchManager returns a new Manager. +func NewBatchManager() *BatchManager { + return &BatchManager{} +} + +// NewID returns a new batch ID that should be unique. +func (bm *BatchManager) NewID() string { + bm.idCounter++ + if maxBatchID < bm.idCounter { + bm.idCounter = 0 + } + + return strconv.FormatInt(time.Now().UnixNano(), 10) + strconv.FormatUint(bm.idCounter, 10) +} + +// Batch represents an IRCv3 batch. +type Batch struct { + ID string + Type string + Params []string +} + +// New returns a new batch. +func (bm *BatchManager) New(batchType string, params ...string) *Batch { + newBatch := Batch{ + ID: bm.NewID(), + Type: batchType, + Params: params, + } + + return &newBatch +} + +// Start sends the batch start message to this client +func (b *Batch) Start(client *Client, tags *map[string]ircmsg.TagValue) { + if client.capabilities.Has(caps.Batch) { + params := []string{"+" + b.ID, b.Type} + for _, param := range b.Params { + params = append(params, param) + } + client.Send(tags, client.server.name, "BATCH", params...) + } +} + +// End sends the batch end message to this client +func (b *Batch) End(client *Client) { + if client.capabilities.Has(caps.Batch) { + client.Send(nil, client.server.name, "BATCH", "-"+b.ID) + } +} diff --git a/irc/capability.go b/irc/capability.go index afb4760e..1c197df6 100644 --- a/irc/capability.go +++ b/irc/capability.go @@ -14,7 +14,7 @@ import ( var ( // SupportedCapabilities are the caps we advertise. // MaxLine, SASL and STS are set during server startup. - SupportedCapabilities = caps.NewSet(caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.MessageTags, caps.MultiPrefix, caps.Rename, caps.ServerTime, caps.UserhostInNames) + SupportedCapabilities = caps.NewSet(caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.Batch, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.LabeledResponse, caps.MessageTags, caps.MultiPrefix, caps.Rename, caps.ServerTime, caps.UserhostInNames) // CapValues are the actual values we advertise to v3.2 clients. // actual values are set during server startup. diff --git a/irc/client.go b/irc/client.go index 241c8a51..7a0439d3 100644 --- a/irc/client.go +++ b/irc/client.go @@ -621,7 +621,7 @@ func (client *Client) SendFromClient(msgid string, from *Client, tags *map[strin var ( // these are all the output commands that MUST have their last param be a trailing. - // this is needed because silly clients like to treat trailing as separate from the + // this is needed because dumb clients like to treat trailing params separately from the // other params in messages. commandsThatMustUseTrailing = map[string]bool{ "PRIVMSG": true, @@ -632,6 +632,47 @@ var ( } ) +// SendRawMessage sends a raw message to the client. +func (client *Client) SendRawMessage(message ircmsg.IrcMessage) error { + // use dumb hack to force the last param to be a trailing param if required + var usedTrailingHack bool + if commandsThatMustUseTrailing[strings.ToUpper(message.Command)] && len(message.Params) > 0 { + lastParam := message.Params[len(message.Params)-1] + // to force trailing, we ensure the final param contains a space + if !strings.Contains(lastParam, " ") { + message.Params[len(message.Params)-1] = lastParam + " " + usedTrailingHack = true + } + } + + // assemble message + maxlenTags, maxlenRest := client.maxlens() + line, err := message.LineMaxLen(maxlenTags, maxlenRest) + if err != nil { + // try not to fail quietly - especially useful when running tests, as a note to dig deeper + // log.Println("Error assembling message:") + // spew.Dump(message) + // debug.PrintStack() + + message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending") + line, _ := message.Line() + + // if we used the trailing hack, we need to strip the final space we appended earlier on + if usedTrailingHack { + line = line[:len(line)-3] + "\r\n" + } + + client.socket.Write(line) + return err + } + + client.server.logger.Debug("useroutput", client.nick, " ->", strings.TrimRight(line, "\r\n")) + + client.socket.Write(line) + + return nil +} + // Send sends an IRC line to the client. func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) error { // attach server-time @@ -644,41 +685,9 @@ func (client *Client) Send(tags *map[string]ircmsg.TagValue, prefix string, comm } } - // force trailing, if message requires it - var usedTrailingHack bool - if commandsThatMustUseTrailing[strings.ToUpper(command)] && len(params) > 0 { - lastParam := params[len(params)-1] - // to force trailing, we ensure the final param contains a space - if !strings.Contains(lastParam, " ") { - params[len(params)-1] = lastParam + " " - usedTrailingHack = true - } - } - // send out the message message := ircmsg.MakeMessage(tags, prefix, command, params...) - maxlenTags, maxlenRest := client.maxlens() - line, err := message.LineMaxLen(maxlenTags, maxlenRest) - if err != nil { - // try not to fail quietly - especially useful when running tests, as a note to dig deeper - // log.Println("Error assembling message:") - // spew.Dump(message) - // debug.PrintStack() - - message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending") - line, _ := message.Line() - client.socket.Write(line) - return err - } - - // is we used the trailing hack, we need to strip the final space we appended earlier - if usedTrailingHack { - line = line[:len(line)-3] + "\r\n" - } - - client.server.logger.Debug("useroutput", client.nick, " ->", strings.TrimRight(line, "\r\n")) - - client.socket.Write(line) + client.SendRawMessage(message) return nil } diff --git a/irc/responsebuffer.go b/irc/responsebuffer.go new file mode 100644 index 00000000..29de865b --- /dev/null +++ b/irc/responsebuffer.go @@ -0,0 +1,120 @@ +// Copyright (c) 2016-2017 Daniel Oaks +// released under the MIT license + +package irc + +import ( + "time" + + "github.com/goshuirc/irc-go/ircmsg" + "github.com/oragono/oragono/irc/caps" +) + +// ResponseBuffer - put simply - buffers messages and then outputs them to a given client. +// +// Using a ResponseBuffer lets you really easily implement labeled-response, since the +// buffer will silently create a batch if required and label the outgoing messages as +// necessary (or leave it off and simply tag the outgoing message). +type ResponseBuffer struct { + Label string + target *Client + messages []ircmsg.IrcMessage +} + +// GetLabel returns the label from the given message. +func GetLabel(msg ircmsg.IrcMessage) string { + return msg.Tags["label"].Value +} + +// NewResponseBuffer returns a new ResponseBuffer. +func NewResponseBuffer(target *Client) *ResponseBuffer { + return &ResponseBuffer{ + target: target, + } +} + +// Add adds a standard new message to our queue. +func (rb *ResponseBuffer) Add(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) { + message := ircmsg.MakeMessage(tags, prefix, command, params...) + + rb.messages = append(rb.messages, message) +} + +// AddFromClient adds a new message from a specific client to our queue. +func (rb *ResponseBuffer) AddFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) { + // attach account-tag + if rb.target.capabilities.Has(caps.AccountTag) && from.account != &NoAccount { + if tags == nil { + tags = ircmsg.MakeTags("account", from.account.Name) + } else { + (*tags)["account"] = ircmsg.MakeTagValue(from.account.Name) + } + } + // attach message-id + if len(msgid) > 0 && rb.target.capabilities.Has(caps.MessageTags) { + if tags == nil { + tags = ircmsg.MakeTags("draft/msgid", msgid) + } else { + (*tags)["draft/msgid"] = ircmsg.MakeTagValue(msgid) + } + } + + rb.Add(tags, from.nickMaskString, command, params...) +} + +// AddSplitMessageFromClient adds a new split message from a specific client to our queue. +func (rb *ResponseBuffer) AddSplitMessageFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, target string, message SplitMessage) { + if rb.target.capabilities.Has(caps.MaxLine) { + rb.AddFromClient(msgid, from, tags, command, target, message.ForMaxLine) + } else { + for _, str := range message.For512 { + rb.AddFromClient(msgid, from, tags, command, target, str) + } + } +} + +// Send sends the message to our target client. +func (rb *ResponseBuffer) Send() error { + // make batch and all if required + var batch *Batch + useLabel := rb.target.capabilities.Has(caps.LabeledResponse) && rb.Label != "" + if useLabel && 1 < len(rb.messages) && rb.target.capabilities.Has(caps.Batch) { + batch = rb.target.server.batches.New("draft/labeled-response") + } + + // if label but no batch, add label to first message + if useLabel && batch == nil { + message := rb.messages[0] + message.Tags["label"] = ircmsg.MakeTagValue(rb.Label) + rb.messages[0] = message + } + + // start batch if required + if batch != nil { + batch.Start(rb.target, ircmsg.MakeTags("label", rb.Label)) + } + + // send each message out + for _, message := range rb.messages { + // attach server-time if needed + if rb.target.capabilities.Has(caps.ServerTime) { + t := time.Now().UTC().Format("2006-01-02T15:04:05.999Z") + message.Tags["time"] = ircmsg.MakeTagValue(t) + } + + // attach batch ID + if batch != nil { + message.Tags["batch"] = ircmsg.MakeTagValue(batch.ID) + } + + // send message out + rb.target.SendRawMessage(message) + } + + // end batch if required + if batch != nil { + batch.End(rb.target) + } + + return nil +} diff --git a/irc/server.go b/irc/server.go index 51048d01..213dd6c4 100644 --- a/irc/server.go +++ b/irc/server.go @@ -78,6 +78,7 @@ type Server struct { accountAuthenticationEnabled bool accountRegistration *AccountRegistration accounts map[string]*ClientAccount + batches *BatchManager channelRegistrationEnabled bool channels ChannelNameMap channelJoinPartMutex sync.Mutex // used when joining/parting channels to prevent stomping over each others' access and all @@ -146,6 +147,7 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) { // initialize data structures server := &Server{ accounts: make(map[string]*ClientAccount), + batches: NewBatchManager(), channels: *NewChannelNameMap(), clients: NewClientLookupSet(), commands: make(chan Command), @@ -988,8 +990,12 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { masksString = msg.Params[0] } + rb := NewResponseBuffer(client) + rb.Label = GetLabel(msg) + if len(strings.TrimSpace(masksString)) < 1 { - client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "No masks given") + rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, "No masks given") + rb.Send() return false } @@ -998,16 +1004,16 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { for _, mask := range masks { casefoldedMask, err := Casefold(mask) if err != nil { - client.Send(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, "No such nick") + rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, "No such nick") continue } matches := server.clients.FindAll(casefoldedMask) if len(matches) == 0 { - client.Send(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, "No such nick") + rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, mask, "No such nick") continue } for mclient := range matches { - client.getWhoisOf(mclient) + client.getWhoisOf(mclient, rb) } } } else { @@ -1015,36 +1021,37 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { casefoldedMask, err := Casefold(strings.Split(masksString, ",")[0]) mclient := server.clients.Get(casefoldedMask) if err != nil || mclient == nil { - client.Send(nil, client.server.name, ERR_NOSUCHNICK, client.nick, masksString, "No such nick") + rb.Add(nil, client.server.name, ERR_NOSUCHNICK, client.nick, masksString, "No such nick") // fall through, ENDOFWHOIS is always sent } else { - client.getWhoisOf(mclient) + client.getWhoisOf(mclient, rb) } } - client.Send(nil, server.name, RPL_ENDOFWHOIS, client.nick, masksString, "End of /WHOIS list") + rb.Add(nil, server.name, RPL_ENDOFWHOIS, client.nick, masksString, "End of /WHOIS list") + rb.Send() return false } -func (client *Client) getWhoisOf(target *Client) { - client.Send(nil, client.server.name, RPL_WHOISUSER, client.nick, target.nick, target.username, target.hostname, "*", target.realname) +func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) { + rb.Add(nil, client.server.name, RPL_WHOISUSER, client.nick, target.nick, target.username, target.hostname, "*", target.realname) whoischannels := client.WhoisChannelsNames(target) if whoischannels != nil { - client.Send(nil, client.server.name, RPL_WHOISCHANNELS, client.nick, target.nick, strings.Join(whoischannels, " ")) + rb.Add(nil, client.server.name, RPL_WHOISCHANNELS, client.nick, target.nick, strings.Join(whoischannels, " ")) } if target.class != nil { - client.Send(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, target.whoisLine) + rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, target.whoisLine) } if client.flags[Operator] || client == target { - client.Send(nil, client.server.name, RPL_WHOISACTUALLY, client.nick, target.nick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), "Actual user@host, Actual IP") + rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, client.nick, target.nick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), "Actual user@host, Actual IP") } if target.flags[TLS] { - client.Send(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, "is using a secure connection") + rb.Add(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, "is using a secure connection") } if target.certfp != "" && (client.flags[Operator] || client == target) { - client.Send(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf("has client certificate fingerprint %s", target.certfp)) + rb.Add(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf("has client certificate fingerprint %s", target.certfp)) } - client.Send(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), "seconds idle, signon time") + rb.Add(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), "seconds idle, signon time") } // RplWhoReplyNoMutex returns the WHO reply between one user and another channel/user. From 00099eb4ba383c88b58f190fdc8daf1af9769776 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Sun, 28 Jan 2018 00:45:16 +1000 Subject: [PATCH 2/4] Make batch IDs smaller --- irc/batch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irc/batch.go b/irc/batch.go index f5bfd852..0a602b22 100644 --- a/irc/batch.go +++ b/irc/batch.go @@ -38,7 +38,7 @@ func (bm *BatchManager) NewID() string { bm.idCounter = 0 } - return strconv.FormatInt(time.Now().UnixNano(), 10) + strconv.FormatUint(bm.idCounter, 10) + return strconv.FormatInt(time.Now().UnixNano(), 36) + strconv.FormatUint(bm.idCounter, 36) } // Batch represents an IRCv3 batch. From 16a55cbba890e71010d70d3976245a1cf04361d1 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Sun, 28 Jan 2018 10:51:46 +1000 Subject: [PATCH 3/4] Add labeled-responses for MODE --- irc/modes.go | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/irc/modes.go b/irc/modes.go index a8fd4c4e..ad28982a 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -326,11 +326,15 @@ func (client *Client) applyUserModeChanges(force bool, changes ModeChanges) Mode // MODE [ [...]] func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { + rb := NewResponseBuffer(client) + rb.Label = GetLabel(msg) + defer rb.Send() + nickname, err := CasefoldName(msg.Params[0]) target := server.clients.Get(nickname) if err != nil || target == nil { if len(msg.Params[0]) > 0 { - client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], client.t("No such nick")) + rb.Add(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], client.t("No such nick")) } return false } @@ -340,9 +344,9 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if !hasPrivs { if len(msg.Params) > 1 { - client.Send(nil, server.name, ERR_USERSDONTMATCH, client.nick, client.t("Can't change modes for other users")) + rb.Add(nil, server.name, ERR_USERSDONTMATCH, client.nick, client.t("Can't change modes for other users")) } else { - client.Send(nil, server.name, ERR_USERSDONTMATCH, client.nick, client.t("Can't view modes for other users")) + rb.Add(nil, server.name, ERR_USERSDONTMATCH, client.nick, client.t("Can't view modes for other users")) } return false } @@ -357,7 +361,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // alert for unknown mode changes for char := range unknown { - client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(char), client.t("is an unknown mode character to me")) + rb.Add(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(char), client.t("is an unknown mode character to me")) } if len(unknown) == 1 && len(changes) == 0 { return false @@ -368,13 +372,13 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } if len(applied) > 0 { - client.Send(nil, client.nickMaskString, "MODE", targetNick, applied.String()) + rb.Add(nil, client.nickMaskString, "MODE", targetNick, applied.String()) } else if hasPrivs { - client.Send(nil, target.nickMaskString, RPL_UMODEIS, targetNick, target.ModeString()) + rb.Add(nil, target.nickMaskString, RPL_UMODEIS, targetNick, target.ModeString()) if client.flags[LocalOperator] || client.flags[Operator] { masks := server.snomasks.String(client) if 0 < len(masks) { - client.Send(nil, target.nickMaskString, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks")) + rb.Add(nil, target.nickMaskString, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks")) } } } @@ -476,7 +480,7 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) { } // ApplyChannelModeChanges applies a given set of mode changes. -func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, changes ModeChanges) ModeChanges { +func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, changes ModeChanges, rb *ResponseBuffer) ModeChanges { // so we only output one warning for each list type when full listFullWarned := make(map[Mode]bool) @@ -521,7 +525,7 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c if !hasPrivs(change) { if !alreadySentPrivError { alreadySentPrivError = true - client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator")) + rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator")) } continue } @@ -543,7 +547,7 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c case Add: if channel.lists[change.mode].Length() >= client.server.Limits().ChanListModes { if !listFullWarned[change.mode] { - client.Send(nil, client.server.name, ERR_BANLISTFULL, client.Nick(), channel.Name(), change.mode.String(), client.t("Channel list is full")) + rb.Add(nil, client.server.name, ERR_BANLISTFULL, client.Nick(), channel.Name(), change.mode.String(), client.t("Channel list is full")) listFullWarned[change.mode] = true } continue @@ -608,11 +612,15 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c // MODE [ [...]] func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { + rb := NewResponseBuffer(client) + rb.Label = GetLabel(msg) + defer rb.Send() + channelName, err := CasefoldChannel(msg.Params[0]) channel := server.channels.Get(channelName) if err != nil || channel == nil { - client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, msg.Params[0], client.t("No such channel")) + rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, msg.Params[0], client.t("No such channel")) return false } @@ -626,14 +634,14 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // alert for unknown mode changes for char := range unknown { - client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(char), client.t("is an unknown mode character to me")) + rb.Add(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(char), client.t("is an unknown mode character to me")) } if len(unknown) == 1 && len(changes) == 0 { return false } // apply mode changes - applied = channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes) + applied = channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes, rb) } // save changes to banlist/exceptlist/invexlist @@ -661,8 +669,8 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } } else { args := append([]string{client.nick, channel.name}, channel.modeStrings(client)...) - client.Send(nil, client.nickMaskString, RPL_CHANNELMODEIS, args...) - client.Send(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nick, channel.name, strconv.FormatInt(channel.createdTime.Unix(), 10)) + rb.Add(nil, client.nickMaskString, RPL_CHANNELMODEIS, args...) + rb.Add(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nick, channel.name, strconv.FormatInt(channel.createdTime.Unix(), 10)) } return false } From 8bc2c0ee67d2b63f85982d9e101cec03da24d700 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Tue, 30 Jan 2018 14:20:12 +1000 Subject: [PATCH 4/4] temp *badbadbad* --- irc/commands.go | 9 +++++++-- irc/modes.go | 12 ++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/irc/commands.go b/irc/commands.go index fdb6fbf2..8dec4bb8 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -9,7 +9,7 @@ import "github.com/goshuirc/irc-go/ircmsg" // Command represents a command accepted from a client. type Command struct { - handler func(server *Server, client *Client, msg ircmsg.IrcMessage) bool + handler func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool oper bool usablePreReg bool leaveClientActive bool // if true, leaves the client active time alone. reversed because we can't default a struct element to True @@ -42,7 +42,12 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b if !cmd.leaveClientIdle { client.Touch() } - exiting := cmd.handler(server, client, msg) + rb := NewResponseBuffer(client) + rb.Label = GetLabel(msg) + + exiting := cmd.handler(server, client, msg, rb) + + rb.Send() // after each command, see if we can send registration to the client if !client.registered { diff --git a/irc/modes.go b/irc/modes.go index ad28982a..c79870e3 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -210,13 +210,13 @@ func GetLowestChannelModePrefix(prefixes string) *Mode { // // MODE [ [...]] -func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { +func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { _, errChan := CasefoldChannel(msg.Params[0]) if errChan == nil { - return cmodeHandler(server, client, msg) + return cmodeHandler(server, client, msg, rb) } - return umodeHandler(server, client, msg) + return umodeHandler(server, client, msg, rb) } // ParseUserModeChanges returns the valid changes, and the list of unknown chars. @@ -325,11 +325,7 @@ func (client *Client) applyUserModeChanges(force bool, changes ModeChanges) Mode } // MODE [ [...]] -func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { - rb := NewResponseBuffer(client) - rb.Label = GetLabel(msg) - defer rb.Send() - +func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { nickname, err := CasefoldName(msg.Params[0]) target := server.clients.Get(nickname) if err != nil || target == nil {