From 687e2753a1687cc497d3b47d29c41da6f1f6a3a7 Mon Sep 17 00:00:00 2001 From: Liam Stanley Date: Fri, 6 Jan 2017 08:30:09 -0500 Subject: [PATCH] implement GetServerOption, ServerName, NetworkName, ServerVersion, and ISUPPORT tracking; update todos --- README.md | 1 - client.go | 42 ++++++++++++++++++++++++++++++++++++++++++ handlers.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- state.go | 5 +++++ 4 files changed, 97 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 33ffa93..5b1de99 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ - [ ] ensure types `User` and `Channel` don't have any unexported fields, and that when they are given publically, it's not a pointer to internal state - [ ] track with `NAMES` as well? would require rewrite of user existance logic, could also help track user modes - [ ] write more function-specific examples as the api becomes much more stable -- [ ] would be cool to track things like `SERVERNAME`, `VERSION`, `UMODES`, `CMODES`, etc. also see `Config.DisableCapTracking`. [e.g. here](https://github.com/lrstanley/Code/blob/master/core/triggers.py#L40-L67) - [ ] client should support ping tracking (sending `PING`'s to the server) - [ ] with this, we can potentially find lag. `Client.Lag()` would be useful - [ ] users need to be exposed in state somehow (other than `GetChannels()`) diff --git a/client.go b/client.go index 1e69ebc..f8109c3 100644 --- a/client.go +++ b/client.go @@ -707,3 +707,45 @@ func (c *Client) Whowas(nick string, amount int) error { return c.Send(&Event{Command: WHOWAS, Params: []string{nick, string(amount)}}) } + +// GetServerOption retrieves a server capability setting that was retrieved +// during client connection. This is also known as ISUPPORT. Examples of usage: +// +// nickLen, success := GetServerOption("MAXNICKLEN") +// +func (c *Client) GetServerOption(key string) (result string, success bool) { + if c.Config.DisableTracking { + panic("GetServerOption() used when tracking is disabled") + } + + c.state.mu.Lock() + result, success = c.state.serverOptions[key] + c.state.mu.Unlock() + + return result, success +} + +// ServerName returns the server host/name that the server itself identifies +// as. May be empty if the server does not support RPL_MYINFO. +func (c *Client) ServerName() (name string) { + name, _ = c.GetServerOption("SERVER") + + return name +} + +// NetworkName returns the network identifier. E.g. "EsperNet", "ByteIRC". +// May be empty if the server does not support RPL_ISUPPORT. +func (c *Client) NetworkName() (name string) { + name, _ = c.GetServerOption("NETWORK") + + return name +} + +// ServerVersion returns the server software version, if the server has +// supplied this information during connection. May be empty if the server +// does not support RPL_MYINFO. +func (c *Client) ServerVersion() (version string) { + version, _ = c.GetServerOption("VERSION") + + return version +} diff --git a/handlers.go b/handlers.go index 02a1855..84c9182 100644 --- a/handlers.go +++ b/handlers.go @@ -4,7 +4,10 @@ package girc -import "time" +import ( + "strings" + "time" +) // registerHandlers sets up built-in callbacks/helpers, based on client // configuration. @@ -32,6 +35,8 @@ func (c *Client) registerHandlers() { // Other misc. useful stuff. c.Callbacks.register(true, TOPIC, CallbackFunc(handleTOPIC)) c.Callbacks.register(true, RPL_TOPIC, CallbackFunc(handleTOPIC)) + c.Callbacks.register(true, RPL_MYINFO, CallbackFunc(handleMYINFO)) + c.Callbacks.register(true, RPL_ISUPPORT, CallbackFunc(handleISUPPORT)) } // Nickname collisions. @@ -64,7 +69,7 @@ func handleConnect(c *Client, e Event) { c.state.nick = e.Params[0] } - time.Sleep(1 * time.Second) + time.Sleep(2 * time.Second) c.Events <- &Event{Command: CONNECTED} } @@ -226,3 +231,46 @@ func handleQUIT(c *Client, e Event) { c.state.deleteUser(e.Source.Name) c.state.mu.Unlock() } + +func handleMYINFO(c *Client, e Event) { + // Malformed or odd output. As this can differ strongly between networks, + // just skip it. + if len(e.Params) < 3 { + return + } + + c.state.mu.Lock() + c.state.serverOptions["SERVER"] = e.Params[1] + c.state.serverOptions["VERSION"] = e.Params[2] + c.state.mu.Unlock() +} + +func handleISUPPORT(c *Client, e Event) { + // Must be a ISUPPORT-based message. 005 is also used for server bounce + // related things, so this callback may be triggered during other + // situations. + if !strings.HasSuffix(e.Trailing, "this server") { + return + } + + // Must have at least one configuration. + if len(e.Params) < 2 { + return + } + + c.state.mu.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) // = + + if j < 1 || (j+1) == len(e.Params[i]) { + c.state.serverOptions[e.Params[i]] = "" + continue + } + + name := e.Params[i][0:j] + val := e.Params[i][j+1:] + c.state.serverOptions[name] = val + } + c.state.mu.Unlock() +} diff --git a/state.go b/state.go index 84585f8..bb89234 100644 --- a/state.go +++ b/state.go @@ -44,6 +44,10 @@ type state struct { // last capability check. These will get sent once we have received the // last capability list command from the server. tmpCap []string + // serverOptions are the standard capabilities and configurations + // supported by the server at connection time. This also includes ISUPPORT + // entries. + serverOptions map[string]string } // User represents an IRC user and the state attached to them. @@ -168,6 +172,7 @@ func newState() *state { s := &state{} s.channels = make(map[string]*Channel) + s.serverOptions = make(map[string]string) s.connected = false return s