diff --git a/irc/constants.go b/irc/constants.go index 95baf920..90647267 100644 --- a/irc/constants.go +++ b/irc/constants.go @@ -44,7 +44,8 @@ const ( RPL_YOURHOST NumericCode = 2 RPL_CREATED NumericCode = 3 RPL_MYINFO NumericCode = 4 - RPL_BOUNCE NumericCode = 5 + RPL_ISUPPORT NumericCode = 5 + RPL_BOUNCE NumericCode = 10 RPL_TRACELINK NumericCode = 200 RPL_TRACECONNECTING NumericCode = 201 RPL_TRACEHANDSHAKE NumericCode = 202 diff --git a/irc/isupport.go b/irc/isupport.go new file mode 100644 index 00000000..7faaed10 --- /dev/null +++ b/irc/isupport.go @@ -0,0 +1,66 @@ +package irc + +import ( + "fmt" + "strings" +) + +// ISupportList holds a list of ISUPPORT tokens +type ISupportList struct { + Tokens map[string]*string + CachedReply []string +} + +// NewISupportList returns a new ISupportList +func NewISupportList() *ISupportList { + var il ISupportList + il.Tokens = make(map[string]*string) + il.CachedReply = make([]string, 0) + return &il +} + +// Add adds an RPL_ISUPPORT token to our internal list +func (il *ISupportList) Add(name string, value string) { + il.Tokens[name] = &value +} + +// AddNoValue adds an RPL_ISUPPORT token that does not have a value +func (il *ISupportList) AddNoValue(name string) { + il.Tokens[name] = nil +} + +// RegenerateCachedReply regenerates the cached RPL_ISUPPORT reply +func (il *ISupportList) RegenerateCachedReply() { + il.CachedReply = make([]string, 0) + maxlen := 400 // Max length of a single ISUPPORT token line + var length int // Length of the current cache + var cache []string // Token list cache + + for name, value := range il.Tokens { + var token string + if value == nil { + token = name + } else { + token = fmt.Sprintf("%s=%s", name, *value) + } + + if len(token)+length <= maxlen { + // account for the space separating tokens + if len(cache) > 0 { + length++ + } + cache = append(cache, token) + length += len(token) + } + + if len(cache) == 13 || len(token)+length >= maxlen { + il.CachedReply = append(il.CachedReply, strings.Join(cache, " ")) + cache = make([]string, 0) + length = 0 + } + } + + if len(cache) > 0 { + il.CachedReply = append(il.CachedReply, strings.Join(cache, " ")) + } +} diff --git a/irc/reply.go b/irc/reply.go index 2434714b..cdf3bb19 100644 --- a/irc/reply.go +++ b/irc/reply.go @@ -205,6 +205,14 @@ func (target *Client) RplMyInfo() { target.server.name, SEM_VER, SupportedUserModes, SupportedChannelModes) } +func (target *Client) RplISupport() { + for _, tokenline := range target.server.isupport.CachedReply { + target.NumericReply(RPL_ISUPPORT, + "%s :are supported by this server", + tokenline) + } +} + func (target *Client) RplUModeIs(client *Client) { target.NumericReply(RPL_UMODEIS, client.ModeString()) } diff --git a/irc/server.go b/irc/server.go index 78e42c7a..a1479452 100644 --- a/irc/server.go +++ b/irc/server.go @@ -39,6 +39,7 @@ type Server struct { signals chan os.Signal whoWas *WhoWasList theaters map[Name][]byte + isupport *ISupportList } var ( @@ -79,6 +80,25 @@ func NewServer(config *Config) *Server { signal.Notify(server.signals, SERVER_SIGNALS...) + // add RPL_ISUPPORT tokens + server.isupport = NewISupportList() + server.isupport.Add("CASEMAPPING", "ascii") + server.isupport.Add("CHANMODES", "") //TODO(dan): Channel mode list here + server.isupport.Add("CHANNELLEN", "") //TODO(dan): Support channel length + server.isupport.Add("CHANTYPES", "#") + server.isupport.Add("EXCEPTS", "") + server.isupport.Add("INVEX", "") + server.isupport.Add("KICKLEN", "") //TODO(dan): Support kick length? + server.isupport.Add("MAXLIST", "") //TODO(dan): Support max list length? + server.isupport.Add("MODES", "") //TODO(dan): Support max modes? + server.isupport.Add("NETWORK", "NetNameHere") //TODO(dan): Support network name + server.isupport.Add("NICKLEN", "") //TODO(dan): Support nick length + server.isupport.Add("PREFIX", "(ov)@+") + server.isupport.Add("STATUSMSG", "@+") //TODO(dan): Autogenerate based on PREFIXes, make sure it's actually supported + server.isupport.Add("TARGMAX", "") //TODO(dan): Support this + server.isupport.Add("TOPICLEN", "") //TODO(dan): Support topic length + server.isupport.RegenerateCachedReply() + return server } @@ -258,6 +278,7 @@ func (s *Server) tryRegister(c *Client) { c.RplYourHost() c.RplCreated() c.RplMyInfo() + c.RplISupport() s.MOTD(c) }