package xirc import ( "encoding/base64" "fmt" "sort" "strings" "gopkg.in/irc.v4" ) func GenerateJoin(channels, keys []string) []*irc.Message { // Put channels with a key first js := joinSorter{channels, keys} sort.Sort(&js) // Two spaces because there are three words (JOIN, channels and keys) maxLength := maxMessageLength - (len("JOIN") + 2) var msgs []*irc.Message var channelsBuf, keysBuf strings.Builder for i, channel := range channels { key := keys[i] n := channelsBuf.Len() + keysBuf.Len() + 1 + len(channel) if key != "" { n += 1 + len(key) } if channelsBuf.Len() > 0 && n > maxLength { // No room for the new channel in this message params := []string{channelsBuf.String()} if keysBuf.Len() > 0 { params = append(params, keysBuf.String()) } msgs = append(msgs, &irc.Message{Command: "JOIN", Params: params}) channelsBuf.Reset() keysBuf.Reset() } if channelsBuf.Len() > 0 { channelsBuf.WriteByte(',') } channelsBuf.WriteString(channel) if key != "" { if keysBuf.Len() > 0 { keysBuf.WriteByte(',') } keysBuf.WriteString(key) } } if channelsBuf.Len() > 0 { params := []string{channelsBuf.String()} if keysBuf.Len() > 0 { params = append(params, keysBuf.String()) } msgs = append(msgs, &irc.Message{Command: "JOIN", Params: params}) } return msgs } type joinSorter struct { channels []string keys []string } func (js *joinSorter) Len() int { return len(js.channels) } func (js *joinSorter) Less(i, j int) bool { if (js.keys[i] != "") != (js.keys[j] != "") { // Only one of the channels has a key return js.keys[i] != "" } return js.channels[i] < js.channels[j] } func (js *joinSorter) Swap(i, j int) { js.channels[i], js.channels[j] = js.channels[j], js.channels[i] js.keys[i], js.keys[j] = js.keys[j], js.keys[i] } func GenerateIsupport(prefix *irc.Prefix, nick string, tokens []string) []*irc.Message { maxTokens := maxMessageParams - 2 // 2 reserved params: nick + text // TODO: take into account maxMessageLength as well var msgs []*irc.Message for len(tokens) > 0 { var msgTokens []string if len(tokens) > maxTokens { msgTokens = tokens[:maxTokens] tokens = tokens[maxTokens:] } else { msgTokens = tokens tokens = nil } encodedTokens := make([]string, len(msgTokens)) for i, tok := range msgTokens { encodedTokens[i] = isupportEncoder.Replace(tok) } msgs = append(msgs, &irc.Message{ Prefix: prefix, Command: irc.RPL_ISUPPORT, Params: append(append([]string{nick}, encodedTokens...), "are supported"), }) } return msgs } var isupportEncoder = strings.NewReplacer(" ", "\\x20", "\\", "\\x5C") func GenerateMOTD(prefix *irc.Prefix, nick string, motd string) []*irc.Message { var msgs []*irc.Message msgs = append(msgs, &irc.Message{ Prefix: prefix, Command: irc.RPL_MOTDSTART, Params: []string{nick, fmt.Sprintf("- Message of the Day -")}, }) for _, l := range strings.Split(motd, "\n") { msgs = append(msgs, &irc.Message{ Prefix: prefix, Command: irc.RPL_MOTD, Params: []string{nick, l}, }) } msgs = append(msgs, &irc.Message{ Prefix: prefix, Command: irc.RPL_ENDOFMOTD, Params: []string{nick, "End of /MOTD command."}, }) return msgs } func GenerateMonitor(subcmd string, targets []string) []*irc.Message { maxLength := maxMessageLength - len("MONITOR "+subcmd+" ") var msgs []*irc.Message var buf []string n := 0 for _, target := range targets { if n+len(target)+1 > maxLength { msgs = append(msgs, &irc.Message{ Command: "MONITOR", Params: []string{subcmd, strings.Join(buf, ",")}, }) buf = buf[:0] n = 0 } buf = append(buf, target) n += len(target) + 1 } if len(buf) > 0 { msgs = append(msgs, &irc.Message{ Command: "MONITOR", Params: []string{subcmd, strings.Join(buf, ",")}, }) } return msgs } func GenerateNamesReply(prefix *irc.Prefix, nick string, channel string, status ChannelStatus, members []string) []*irc.Message { emptyNameReply := irc.Message{ Prefix: prefix, Command: irc.RPL_NAMREPLY, Params: []string{nick, string(status), channel, ""}, } maxLength := maxMessageLength - len(emptyNameReply.String()) var msgs []*irc.Message var buf strings.Builder for _, s := range members { n := buf.Len() + 1 + len(s) if buf.Len() != 0 && n > maxLength { // There's not enough space for the next space + nick msgs = append(msgs, &irc.Message{ Prefix: prefix, Command: irc.RPL_NAMREPLY, Params: []string{nick, string(status), channel, buf.String()}, }) buf.Reset() } if buf.Len() != 0 { buf.WriteByte(' ') } buf.WriteString(s) } if buf.Len() != 0 { msgs = append(msgs, &irc.Message{ Prefix: prefix, Command: irc.RPL_NAMREPLY, Params: []string{nick, string(status), channel, buf.String()}, }) } msgs = append(msgs, &irc.Message{ Prefix: prefix, Command: irc.RPL_ENDOFNAMES, Params: []string{nick, channel, "End of /NAMES list"}, }) return msgs } func GenerateSASL(resp []byte) []*irc.Message { // <= instead of < because we need to send a final empty response if // the last chunk is exactly 400 bytes long var msgs []*irc.Message for i := 0; i <= len(resp); i += MaxSASLLength { j := i + MaxSASLLength if j > len(resp) { j = len(resp) } chunk := resp[i:j] var respStr = "+" if len(chunk) != 0 { respStr = base64.StdEncoding.EncodeToString(chunk) } msgs = append(msgs, &irc.Message{ Command: "AUTHENTICATE", Params: []string{respStr}, }) } return msgs }