2022-05-29 15:57:21 +00:00
|
|
|
package xirc
|
|
|
|
|
|
|
|
import (
|
2022-05-30 07:41:47 +00:00
|
|
|
"encoding/base64"
|
2022-05-29 15:57:21 +00:00
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
2022-11-14 11:06:58 +00:00
|
|
|
"gopkg.in/irc.v4"
|
2022-05-29 15:57:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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]
|
|
|
|
}
|
|
|
|
|
2023-08-23 17:50:37 +00:00
|
|
|
func GenerateIsupport(prefix *irc.Prefix, tokens []string) []*irc.Message {
|
2022-05-29 15:57:21 +00:00
|
|
|
maxTokens := maxMessageParams - 2 // 2 reserved params: nick + text
|
|
|
|
|
2022-05-29 16:24:10 +00:00
|
|
|
// TODO: take into account maxMessageLength as well
|
2022-05-29 15:57:21 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-05-29 16:24:10 +00:00
|
|
|
encodedTokens := make([]string, len(msgTokens))
|
|
|
|
for i, tok := range msgTokens {
|
|
|
|
encodedTokens[i] = isupportEncoder.Replace(tok)
|
|
|
|
}
|
|
|
|
|
2022-05-29 15:57:21 +00:00
|
|
|
msgs = append(msgs, &irc.Message{
|
|
|
|
Prefix: prefix,
|
|
|
|
Command: irc.RPL_ISUPPORT,
|
2023-08-23 17:50:37 +00:00
|
|
|
Params: append(append([]string{"*"}, encodedTokens...), "are supported"),
|
2022-05-29 15:57:21 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return msgs
|
|
|
|
}
|
|
|
|
|
2022-05-29 16:24:10 +00:00
|
|
|
var isupportEncoder = strings.NewReplacer(" ", "\\x20", "\\", "\\x5C")
|
|
|
|
|
2023-08-23 17:50:37 +00:00
|
|
|
func GenerateMOTD(prefix *irc.Prefix, motd string) []*irc.Message {
|
2022-05-29 15:57:21 +00:00
|
|
|
var msgs []*irc.Message
|
|
|
|
msgs = append(msgs, &irc.Message{
|
|
|
|
Prefix: prefix,
|
|
|
|
Command: irc.RPL_MOTDSTART,
|
2023-08-23 17:50:37 +00:00
|
|
|
Params: []string{"*", fmt.Sprintf("- Message of the Day -")},
|
2022-05-29 15:57:21 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
for _, l := range strings.Split(motd, "\n") {
|
|
|
|
msgs = append(msgs, &irc.Message{
|
|
|
|
Prefix: prefix,
|
|
|
|
Command: irc.RPL_MOTD,
|
2023-08-23 17:50:37 +00:00
|
|
|
Params: []string{"*", l},
|
2022-05-29 15:57:21 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
msgs = append(msgs, &irc.Message{
|
|
|
|
Prefix: prefix,
|
|
|
|
Command: irc.RPL_ENDOFMOTD,
|
2023-08-23 17:50:37 +00:00
|
|
|
Params: []string{"*", "End of /MOTD command."},
|
2022-05-29 15:57:21 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-08-23 17:50:37 +00:00
|
|
|
func GenerateNamesReply(prefix *irc.Prefix, channel string, status ChannelStatus, members []string) []*irc.Message {
|
2022-05-29 15:57:21 +00:00
|
|
|
emptyNameReply := irc.Message{
|
|
|
|
Prefix: prefix,
|
|
|
|
Command: irc.RPL_NAMREPLY,
|
2023-08-23 17:50:37 +00:00
|
|
|
Params: []string{"*", string(status), channel, ""},
|
2022-05-29 15:57:21 +00:00
|
|
|
}
|
|
|
|
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,
|
2023-08-23 17:50:37 +00:00
|
|
|
Params: []string{"*", string(status), channel, buf.String()},
|
2022-05-29 15:57:21 +00:00
|
|
|
})
|
|
|
|
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,
|
2023-08-23 17:50:37 +00:00
|
|
|
Params: []string{"*", string(status), channel, buf.String()},
|
2022-05-29 15:57:21 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
msgs = append(msgs, &irc.Message{
|
|
|
|
Prefix: prefix,
|
|
|
|
Command: irc.RPL_ENDOFNAMES,
|
2023-08-23 17:50:37 +00:00
|
|
|
Params: []string{"*", channel, "End of /NAMES list"},
|
2022-05-29 15:57:21 +00:00
|
|
|
})
|
|
|
|
return msgs
|
|
|
|
}
|
2022-05-30 07:41:47 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|