From 107776252175982bf701e47ebf69f7af9425f449 Mon Sep 17 00:00:00 2001 From: Liam Stanley Date: Mon, 14 Nov 2016 06:50:14 -0500 Subject: [PATCH] overhaul of docs; add additional Config.Disable* directives. --- callback.go | 45 +++++----- conn.go | 4 + contants.go | 24 +++--- event.go | 4 + example/main.go | 19 +++-- helpers.go | 85 +++++++++++-------- main.go | 213 +++++++++++++++++++++++++++++++++--------------- sender.go | 12 ++- state.go | 84 ++++++++++++------- 9 files changed, 317 insertions(+), 173 deletions(-) diff --git a/callback.go b/callback.go index 07578c1..5b5e6af 100644 --- a/callback.go +++ b/callback.go @@ -1,29 +1,32 @@ -package girc +// Copyright 2016 Liam Stanley . All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. -// TODO: ClearCallback(code string) +package girc // handleEvent runs the necessary callbacks for the incoming event func (c *Client) handleEvent(event *Event) { - // log the event + // Log the event. c.log.Print("<-- " + event.String()) - // wildcard callbacks first + // Wildcard callbacks first. if callbacks, ok := c.callbacks[ALLEVENTS]; ok { for i := 0; i < len(callbacks); i++ { callbacks[i].Execute(c, *event) } } - // regular non-threaded callbacks + // Regular non-threaded callbacks. if callbacks, ok := c.callbacks[event.Command]; ok { for i := 0; i < len(callbacks); i++ { callbacks[i].Execute(c, *event) } } - // callbacks that should be ran concurrently - // callbacks which should be ran in a go-routine should be prefixed with - // "routine_". e.g. "routine_JOIN". + // Callbacks that should be ran concurrently. + // + // Callbacks which should be ran in a go-routine should be prefixed + // with "routine_". E.g. "routine_JOIN". if callbacks, ok := c.callbacks["routine_"+event.Command]; ok { for i := 0; i < len(callbacks); i++ { go callbacks[i].Execute(c, *event) @@ -31,44 +34,48 @@ func (c *Client) handleEvent(event *Event) { } } -// ClearCallbacks clears all callbacks currently setup within the client +// ClearCallbacks clears all callbacks currently setup within the +// client. func (c *Client) ClearCallbacks() { - // registerHelpers should clean all callbacks and setup internal ones - // as necessary. + // registerHelpers should clean all callbacks and setup internal + // ones as necessary. c.registerHelpers() } -// RunCallbacks manually runs callbacks for a given event +// RunCallbacks manually runs callbacks for a given event. func (c *Client) RunCallbacks(event *Event) { c.handleEvent(event) } -// AddCallbackHandler registers a callback (matching the Callback interface) -// for the given command +// AddCallbackHandler registers a callback (matching the Callback +// interface) for the given command. func (c *Client) AddCallbackHandler(cmd string, callback Callback) { c.callbacks[cmd] = append(c.callbacks[cmd], callback) } -// AddCallback registers the callback function for the given command +// AddCallback registers the callback function for the given command. func (c *Client) AddCallback(cmd string, callback func(c *Client, e Event)) { c.callbacks[cmd] = append(c.callbacks[cmd], CallbackFunc(callback)) } // AddBgCallback registers the callback function for the given command -// and executes it in a go-routine, after all other callbacks have been ran +// and executes it in a go-routine. +// +// Runs after all other callbacks have been ran. func (c *Client) AddBgCallback(cmd string, callback func(c *Client, e Event)) { c.callbacks["routine_"+cmd] = append(c.callbacks["routine_"+cmd], CallbackFunc(callback)) } -// Callback is an interface to handle IRC events +// Callback is lower level implementation of Client.AddCallback(). type Callback interface { Execute(*Client, Event) } -// CallbackFunc is a type that represents the function necessary to implement Callback +// CallbackFunc is a type that represents the function necessary to +// implement Callback. type CallbackFunc func(c *Client, e Event) -// Execute calls the CallbackFunc with the sender and irc message +// Execute calls the CallbackFunc with the sender and irc message. func (f CallbackFunc) Execute(c *Client, e Event) { f(c, e) } diff --git a/conn.go b/conn.go index fbbe705..3043e08 100644 --- a/conn.go +++ b/conn.go @@ -1,3 +1,7 @@ +// Copyright 2016 Liam Stanley . All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + package girc import ( diff --git a/contants.go b/contants.go index 2af85cc..3d53776 100644 --- a/contants.go +++ b/contants.go @@ -1,13 +1,17 @@ +// Copyright 2016 Liam Stanley . All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + package girc -// misc constants for use with the client +// Misc constants for use with the client. const ( ALLEVENTS = "*" // trigger on all events CONNECTED = "CONNECTED" // event command which can be used to start responding, after SUCCESS SUCCESS = "001" // RPL_WELCOME alias, assumes successful connection ) -// user/channel prefixes :: RFC1459 +// User/channel prefixes :: RFC1459 const ( ChannelPrefix = "#" // regular channel DistributedPrefix = "&" // distributed channel @@ -18,7 +22,7 @@ const ( VoicePrefix = "+" // user has voice +v ) -// user modes :: RFC1459; section 4.2.3.2 +// User modes :: RFC1459; section 4.2.3.2 const ( UserModeInvisible = "i" // invisible UserModeOperator = "o" // server operator @@ -26,7 +30,7 @@ const ( UserModeWallops = "w" // user wants to receive wallops ) -// channel modes :: RFC1459; section 4.2.3.1 +// Channel modes :: RFC1459; section 4.2.3.1 const ( ModeAdmin = "a" // admin privileges (non-rfc) ModeHalfOperator = "h" // half-operator privileges (non-rfc) @@ -42,7 +46,7 @@ const ( ModeVoice = "v" // speak during moderation mode ) -// irc commands :: RFC2812; section 3 :: RFC2813; section 4 +// IRC commands :: RFC2812; section 3 :: RFC2813; section 4 const ( ADMIN = "ADMIN" AWAY = "AWAY" @@ -93,7 +97,7 @@ const ( WHOWAS = "WHOWAS" ) -// numeric IRC reply mapping :: RFC2812; section 5 +// Numeric IRC reply mapping :: RFC2812; section 5 const ( RPL_WELCOME = "001" RPL_YOURHOST = "002" @@ -235,7 +239,7 @@ const ( ERR_USERSDONTMATCH = "502" ) -// ircv3 commands :: http://ircv3.net/irc/ +// IRCv3 commands :: http://ircv3.net/irc/ const ( AUTHENTICATE = "AUTHENTICATE" CAP = "CAP" @@ -248,7 +252,7 @@ const ( CAP_REQ = "REQ" ) -// numeric IRC reply mapping for ircv3 :: http://ircv3.net/irc/ +// Numeric IRC reply mapping for ircv3 :: http://ircv3.net/irc/ const ( RPL_LOGGEDIN = "900" RPL_LOGGEDOUT = "901" @@ -261,7 +265,7 @@ const ( RPL_SASLMECHS = "908" ) -// numeric IRC event mapping :: RFC2812; section 5.3 +// Numeric IRC event mapping :: RFC2812; section 5.3 const ( RPL_STATSCLINE = "213" RPL_STATSNLINE = "214" @@ -289,7 +293,7 @@ const ( ERR_NOSERVICEHOST = "492" ) -// misc. +// Misc. const ( ERR_TOOMANYMATCHES = "416" // IRCNet RPL_GLOBALUSERS = "266" // aircd/hybrid/bahamut, used on freenode diff --git a/event.go b/event.go index 4016148..41f9674 100644 --- a/event.go +++ b/event.go @@ -1,3 +1,7 @@ +// Copyright 2016 Liam Stanley . All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + package girc import ( diff --git a/example/main.go b/example/main.go index d4f3de1..27e870d 100644 --- a/example/main.go +++ b/example/main.go @@ -1,3 +1,7 @@ +// Copyright 2016 Liam Stanley . All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + package main import ( @@ -11,14 +15,13 @@ import ( func main() { conf := girc.Config{ - Server: "irc.byteirc.org", - Port: 6667, - Nick: "test", - User: "test1", - Name: "Example bot", - MaxRetries: 3, - Logger: os.Stdout, - DisableHelpers: false, + Server: "irc.byteirc.org", + Port: 6667, + Nick: "test", + User: "test1", + Name: "Example bot", + MaxRetries: 3, + Logger: os.Stdout, } client := girc.New(conf) diff --git a/helpers.go b/helpers.go index 44ea37b..203ada7 100644 --- a/helpers.go +++ b/helpers.go @@ -1,41 +1,49 @@ +// Copyright 2016 Liam Stanley . All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + package girc import "time" +// registerHelpers sets up built-in callbacks/helpers, based on client +// configuration. func (c *Client) registerHelpers() { c.callbacks = make(map[string][]Callback) - if c.Config.DisableHelpers { - return - } - + // Built-in things that should always be supported. c.AddBgCallback(SUCCESS, handleWelcome) c.AddCallback(PING, handlePING) - // joins/parts/anything that may add/remove/rename users - c.AddCallback(JOIN, handleJOIN) - c.AddCallback(PART, handlePART) - c.AddCallback(KICK, handleKICK) - c.AddCallback(QUIT, handleQUIT) - c.AddCallback(NICK, handleNICK) + if !c.Config.DisableTracking { + // Joins/parts/anything that may add/remove/rename users. + c.AddCallback(JOIN, handleJOIN) + c.AddCallback(PART, handlePART) + c.AddCallback(KICK, handleKICK) + c.AddCallback(QUIT, handleQUIT) + c.AddCallback(NICK, handleNICK) - // WHO/WHOX responses - c.AddCallback(RPL_WHOREPLY, handleWHO) - c.AddCallback(RPL_WHOSPCRPL, handleWHO) + // WHO/WHOX responses. + c.AddCallback(RPL_WHOREPLY, handleWHO) + c.AddCallback(RPL_WHOSPCRPL, handleWHO) + } - // nickname collisions - c.AddCallback(ERR_NICKNAMEINUSE, nickCollisionHandler) - c.AddCallback(ERR_NICKCOLLISION, nickCollisionHandler) - c.AddCallback(ERR_UNAVAILRESOURCE, nickCollisionHandler) + // Nickname collisions. + if !c.Config.DisableNickCollision { + c.AddCallback(ERR_NICKNAMEINUSE, nickCollisionHandler) + c.AddCallback(ERR_NICKCOLLISION, nickCollisionHandler) + c.AddCallback(ERR_UNAVAILRESOURCE, nickCollisionHandler) + } } -// handleWelcome is a helper function which lets the client know -// that enough time has passed and now they can send commands +// handleWelcome is a helper function which lets the client know that enough +// time has passed and now they can send commands. // -// should always run in separate thread +// Should always run in separate thread due to blocking delay. func handleWelcome(c *Client, e Event) { - // this should be the nick that the server gives us. 99% of the time, it's the - // one we supplied during connection, but some networks will insta-rename users. + // This should be the nick that the server gives us. 99% of the time, it's + // the one we supplied during connection, but some networks will rename + // users on connect. if len(e.Params) > 0 { c.State.nick = e.Params[0] } @@ -46,37 +54,36 @@ func handleWelcome(c *Client, e Event) { } // nickCollisionHandler helps prevent the client from having conflicting -// nicknames with another bot, user, etc +// nicknames with another bot, user, etc. func nickCollisionHandler(c *Client, e Event) { c.SetNick(c.GetNick() + "_") } -// handlePING helps respond to ping requests from the server +// handlePING helps respond to ping requests from the server. func handlePING(c *Client, e Event) { c.Send(&Event{Command: PONG, Params: e.Params, Trailing: e.Trailing}) } -// handleJOIN ensures that the state has updated users and channels +// handleJOIN ensures that the state has updated users and channels. func handleJOIN(c *Client, e Event) { if len(e.Params) != 1 { return } - // create it in state c.State.createChanIfNotExists(e.Params[0]) if e.Prefix.Name == c.GetNick() { - // if it's us, don't just add our user to the list. run a WHO - // which will tell us who exactly is in the channel + // If it's us, don't just add our user to the list. Run a WHO which + // will tell us who exactly is in the channel. c.Who(e.Params[0]) return } - // create the user in state. only WHO the user, which is more efficient. + // Create the user in state. Only WHO the user, which is more efficient. c.Who(e.Prefix.Name) } -// handlePART ensures that the state is clean of old user and channel entries +// handlePART ensures that the state is clean of old user and channel entries. func handlePART(c *Client, e Event) { if len(e.Params) == 0 { return @@ -90,18 +97,20 @@ func handlePART(c *Client, e Event) { c.State.deleteUser(e.Prefix.Name) } +// handlWHO updates our internal tracking of users/channels with WHO/WHOX +// information. func handleWHO(c *Client, e Event) { var channel, user, host, nick string - // assume WHOX related + // Assume WHOX related. if e.Command == RPL_WHOSPCRPL { if len(e.Params) != 6 { - // assume there was some form of error or invalid WHOX response + // Assume there was some form of error or invalid WHOX response. return } if e.Params[1] != "1" { - // we should always be sending 1, and we should receive 1. if this + // We should always be sending 1, and we should receive 1. If this // is anything but, then we didn't send the request and we can // ignore it. return @@ -115,9 +124,11 @@ func handleWHO(c *Client, e Event) { c.State.createUserIfNotExists(channel, nick, user, host) } +// handleKICK ensures that users are cleaned up after being kicked from the +// channel func handleKICK(c *Client, e Event) { if len(e.Params) < 2 { - // needs at least channel and user + // Needs at least channel and user. return } @@ -126,13 +137,15 @@ func handleKICK(c *Client, e Event) { return } - // assume it's just another user + // Assume it's just another user. c.State.deleteUser(e.Params[1]) } +// handleNICK ensures that users are renamed in state, or the client name is +// up to date. func handleNICK(c *Client, e Event) { if len(e.Params) != 1 { - // something erronous was sent to us + // Something erronous was sent to us. return } diff --git a/main.go b/main.go index 0e9a2c4..301938b 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,18 @@ +// Copyright 2016 Liam Stanley . All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +// Package girc provides a high level, yet flexible IRC library for use +// with interacting with IRC servers. girc has support for user/channel +// tracking, as well as a few other neat features (like auto-reconnect). +// +// Much of what girc can do, can also be disabled. The goal is to +// provide a solid API that you don't necessarily have to work with out +// of the box if you don't want to. +// +// See "example/main.go" for a brief and very useful example taking +// advantage of girc, that should give you a general idea of how the API +// works. package girc import ( @@ -7,55 +22,100 @@ import ( "io" "io/ioutil" "log" - "math" "net" "time" ) // TODO's: +// * ClearCallbacks(CODE)? // * NOTICE (Notice?), SendRaw? // * track connection time (conntime? in state) // * with conntime, find lag. Client.Lag() would be useful -// * would be cool to track things like SERVERNAME, VERSION, UMODES, CMODES, etc. +// * would be cool to track things like SERVERNAME, VERSION, UMODES, +// CMODES, etc. also see Config.DisableCapTracking. // -- https://github.com/Liamraystanley/Code/blob/master/core/triggers.py#L40-L67 // * client should support ping tracking (sending PING's to the server) // * users need to be exposed in state somehow (other than GetChannels()) // * ip/host binding? // * IsValidNick? +// * User.Age()? (FirstActive()?) (time since first seen) +// * State -> state +// * cleanup docs in conn.go & event.go. -// Client contains all of the information necessary to run a single IRC client +// Client contains all of the information necessary to run a single IRC +// client. type Client struct { - Config Config // configuration for client - State *State // state for the client - Events chan *Event // queue of events to handle - Sender Sender // send wrapper for conn - - initTime time.Time // time when the client was created - callbacks map[string][]Callback // mapping of callbacks - reader *Decoder // for use with reading from conn stream - writer *Encoder // for use with writing to conn stream - conn net.Conn // network connection to the irc server - tries int // number of attempts to connect to the server - log *log.Logger // package logger - quitChan chan bool // channel used for disconnect/quitting - hasQuit bool // used to let the reconnect functionality know a manual quit has been completed + // Config represents the configuration + Config Config + // State represents the internal state + State *State + // Events is a buffer of events waiting to be processed. + Events chan *Event + // Sender is a Sender{} interface implementation. + Sender Sender + // initTime represents the creation time of the client. + initTime time.Time + // callbacks is an internal mapping of COMMAND -> callback. + callbacks map[string][]Callback + // reader is the socket buffer reader from the IRC server. + reader *Decoder + // reader is the socket buffer write to the IRC server. + writer *Encoder + // conn is a net.Conn reference to the IRC server. + conn net.Conn + // tries represents the internal reconnect count to the IRC server. + tries int + // log is used if a writer is supplied for Client.Config.Logger. + log *log.Logger + // quitChan is used to close the connection to the IRC server. + quitChan chan bool + // hasQuit is used to determine if we've finished quitting/cleaning up. + hasQuit bool } // Config contains configuration options for an IRC client type Config struct { - Server string // server to connect to - Port int // port to use for server - Password string // password for the irc server - Nick string // nickname to attempt to use on connect - User string // username to attempt to use on connect - Name string // "realname" to attempt to use on connect - TLSConfig *tls.Config // tls/ssl configuration - MaxRetries int // max number of reconnect retries - Logger io.Writer // writer for which to write logs to - DisableHelpers bool // if default event handlers should be used (to respond to ping, user tracking, etc) + // Server is a host/ip of the server you want to connect to. + Server string + // Port is the port that will be used during server connection. + Port int + // Password is the server password used to authenticate. + Password string + // Nick is an rfc-valid nickname used during connect. + Nick string + // User is the username/ident to use on connect. Ignored if identd server + // is used. + User string + // Name is the "realname" that's used during connect. + Name string + // TLSConfig is an optional user-supplied tls configuration, used during + // socket creation to the server. + TLSConfig *tls.Config + // MaxRetries is the number of times the client will attempt to reconnect + // to the server after the last disconnect. + MaxRetries int + // Logger is an optional, user supplied logger to log the raw lines sent + // from the server. Useful for debugging. Defaults to ioutil.Discard. + Logger io.Writer + // ReconnectDelay is the a duration of time to delay before attempting a + // reconnection. Defaults to 10s (minimum of 10s). + ReconnectDelay time.Duration + // DisableTracking disables all channel and user-level tracking. Useful + // for highly embedded scripts with single purposes. + DisableTracking bool + // DisableCapTracking disables all network/server capability tracking. + // This includes determining what feature the IRC server supports, what + // the "NETWORK=" variables are, and other useful stuff. + DisableCapTracking bool + // DisableNickCollision disables the clients auto-response to nickname + // collisions. For example, if "test" is already in use, or is blocked by + // the network/a service, the client will try and use "test_", then it + // will attempt "test__", "test___", and so on. + DisableNickCollision bool } -// New creates a new IRC client with the specified server, name and config +// New creates a new IRC client with the specified server, name and +// config. func New(config Config) *Client { client := &Client{ Config: config, @@ -66,13 +126,13 @@ func New(config Config) *Client { initTime: time.Now(), } - // register builtin helpers + // Register builtin helpers. client.registerHelpers() return client } -// Quit disconnects from the server +// Quit disconnects from the server.s func (c *Client) Quit(message string) { c.Send(&Event{Command: QUIT, Trailing: message}) @@ -85,7 +145,8 @@ func (c *Client) Quit(message string) { c.quitChan <- true } -// Uptime returns the amount of time that has passed since the client was created +// Uptime returns the amount of time that has passed since the +// client was created. func (c *Client) Uptime() time.Duration { return time.Since(c.initTime) } @@ -95,7 +156,8 @@ func (c *Client) Server() string { return fmt.Sprintf("%s:%d", c.Config.Server, c.Config.Port) } -// Send is a handy wrapper around Sender +// Send sends an event to the server. Use Client.RunCallback() if you are +// are simply looking to trigger callbacks with an event. func (c *Client) Send(event *Event) error { // log the event if !event.Sensitive { @@ -110,12 +172,12 @@ func (c *Client) Connect() error { var conn net.Conn var err error - // sanity check a few things here... + // Sanity check a few options. if c.Config.Server == "" || c.Config.Port == 0 || c.Config.Nick == "" || c.Config.User == "" { return errors.New("invalid configuration (server/port/nick/user)") } - // reset our state here + // Reset the state. c.State = NewState() if c.Config.Logger == nil { @@ -146,24 +208,24 @@ func (c *Client) Connect() error { c.tries = 0 go c.ReadLoop() - // consider the connection a success at this point + // Consider the connection a success at this point. c.State.connected = true return nil } -// connectMessages is a list of IRC messages to send when attempting to -// connect to the IRC server. +// connectMessages is a list of IRC messages to send when attempting +// to connect to the IRC server. func (c *Client) connectMessages() (events []*Event) { - // passwords first + // Passwords first. if c.Config.Password != "" { events = append(events, &Event{Command: PASS, Params: []string{c.Config.Password}}) } - // then nickname + // Then nickname. events = append(events, &Event{Command: NICK, Params: []string{c.Config.Nick}}) - // then username and realname + // Then username and realname. if c.Config.Name == "" { c.Config.Name = c.Config.User } @@ -178,27 +240,31 @@ func (c *Client) connectMessages() (events []*Event) { } // Reconnect checks to make sure we want to, and then attempts to -// reconnect to the server -func (c *Client) Reconnect() error { +// reconnect to the server. +func (c *Client) Reconnect() (err error) { if c.hasQuit { return nil } + if c.Config.ReconnectDelay < (10 * time.Second) { + c.Config.ReconnectDelay = 10 * time.Second + } + if c.Config.MaxRetries > 0 { + var err error c.conn.Close() - var err error - - // re-setup events + // Re-setup events. c.Events = make(chan *Event, 40) - // sleep for 10 seconds so we're not slaughtering the server - c.log.Printf("reconnecting to %s in 10 seconds", c.Server()) - time.Sleep(10 * time.Second) + // Delay so we're not slaughtering the server with a bunch of + // connections. + c.log.Printf("reconnecting to %s in %s", c.Server(), c.Config.ReconnectDelay) + time.Sleep(c.Config.ReconnectDelay) for err = c.Connect(); err != nil && c.tries < c.Config.MaxRetries; c.tries++ { - duration := time.Duration(math.Pow(2.0, float64(c.tries))*200) * time.Millisecond - time.Sleep(duration) + c.log.Printf("reconnecting to %s in %s (%d tries)", c.Server(), c.Config.ReconnectDelay, c.tries) + time.Sleep(c.Config.ReconnectDelay) } return err @@ -209,7 +275,7 @@ func (c *Client) Reconnect() error { } // ReadLoop sets a timeout of 300 seconds, and then attempts to read -// from the IRC server. If there is an error, it calls Reconnect +// from the IRC server. If there is an error, it calls Reconnect. func (c *Client) ReadLoop() error { for { c.conn.SetDeadline(time.Now().Add(300 * time.Second)) @@ -222,8 +288,8 @@ func (c *Client) ReadLoop() error { } } -// Loop reads from the events channel and sends the events to be handled -// for every message it receives. +// Loop reads from the events channel and sends the events to be +// handled for every message it receives. func (c *Client) Loop() { for { select { @@ -235,7 +301,7 @@ func (c *Client) Loop() { } } -// IsConnected returns true if the client is connected to the server +// IsConnected returns true if the client is connected to the server. func (c *Client) IsConnected() bool { c.State.m.RLock() defer c.State.m.RUnlock() @@ -243,10 +309,14 @@ func (c *Client) IsConnected() bool { return c.State.connected } -// GetNick returns the current nickname of the active connection +// GetNick returns the current nickname of the active connection. // -// Helpers MUST be enabled for this to work. +// Returns empty string if tracking is disabled. func (c *Client) GetNick() string { + if c.Config.DisableTracking { + return "" + } + c.State.m.RLock() defer c.State.m.RUnlock() @@ -257,7 +327,7 @@ func (c *Client) GetNick() string { return c.State.nick } -// SetNick changes the client nickname +// SetNick changes the client nickname. func (c *Client) SetNick(name string) { c.State.m.Lock() defer c.State.m.Unlock() @@ -266,22 +336,29 @@ func (c *Client) SetNick(name string) { c.Send(&Event{Command: NICK, Params: []string{name}}) } -// GetChannels returns the active list of channels that the client is in +// GetChannels returns the active list of channels that the client +// is in. // -// Helpers MUST be enabled for this to work. +// Returns nil if tracking is disabled. func (c *Client) GetChannels() map[string]*Channel { + if c.Config.DisableTracking { + return nil + } + c.State.m.RLock() defer c.State.m.RUnlock() return c.State.channels } -// Who tells the client to update it's channel/user records +// Who tells the client to update it's channel/user records. +// +// Does not update internal state if tracking is disabled. func (c *Client) Who(target string) { c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhn,1"}}) } -// Join attempts to enter an IRC channel with an optional password +// Join attempts to enter an IRC channel with an optional password. func (c *Client) Join(channel, password string) { if password != "" { c.Send(&Event{Command: JOIN, Params: []string{channel, password}}) @@ -291,7 +368,7 @@ func (c *Client) Join(channel, password string) { c.Send(&Event{Command: JOIN, Params: []string{channel}}) } -// Part leaves an IRC channel with an optional leave message +// Part leaves an IRC channel with an optional leave message. func (c *Client) Part(channel, message string) { if message != "" { c.Send(&Event{Command: JOIN, Params: []string{channel}, Trailing: message}) @@ -301,22 +378,26 @@ func (c *Client) Part(channel, message string) { c.Send(&Event{Command: JOIN, Params: []string{channel}}) } -// Message sends a PRIVMSG to target (either channel, service, or user) +// Message sends a PRIVMSG to target (either channel, service, or +// user). func (c *Client) Message(target, message string) { c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message}) } -// Messagef sends a formated PRIVMSG to target (either channel, service, or user) +// Messagef sends a formated PRIVMSG to target (either channel, +// service, or user). func (c *Client) Messagef(target, format string, a ...interface{}) { c.Message(target, fmt.Sprintf(format, a...)) } -// Action sends a PRIVMSG ACTION (/me) to target (either channel, service, or user) +// Action sends a PRIVMSG ACTION (/me) to target (either channel, +// service, or user). func (c *Client) Action(target, message string) { c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: fmt.Sprintf("\001ACTION %s\001", message)}) } -// Actionf sends a formated PRIVMSG ACTION (/me) to target (either channel, service, or user) +// Actionf sends a formated PRIVMSG ACTION (/me) to target (either +// channel, service, or user). func (c *Client) Actionf(target, format string, a ...interface{}) { c.Action(target, fmt.Sprintf(format, a...)) } diff --git a/sender.go b/sender.go index 44bdf0f..17d6c1c 100644 --- a/sender.go +++ b/sender.go @@ -1,18 +1,22 @@ +// Copyright 2016 Liam Stanley . All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + package girc -// Sender is an interface for sending IRC messages +// Sender is an interface for sending IRC messages. type Sender interface { // Send sends the given message and returns any errors. Send(*Event) error } -// serverSender is a barebones writer used -// as the default sender for all callbacks +// serverSender is a barebones writer used as the default sender for all +// callbacks. type serverSender struct { writer *Encoder } -// Send sends the specified event +// Send sends the specified event. func (s serverSender) Send(event *Event) error { return s.writer.Encode(event) } diff --git a/state.go b/state.go index c34a031..32cc16b 100644 --- a/state.go +++ b/state.go @@ -1,3 +1,7 @@ +// Copyright 2016 Liam Stanley . All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + package girc import ( @@ -6,30 +10,51 @@ import ( "time" ) -// State represents the actively-changing variables within the client runtime +// State represents the actively-changing variables within the client +// runtime. type State struct { - m sync.RWMutex // lock, primarily used for writing things in state - connected bool // if we're connected to the server or not - nick string // internal tracker for our nickname - channels map[string]*Channel // map of channels that the client is in + // m is a RW mutex lock, used to guard the state from goroutines causing + // corruption. + m sync.RWMutex + // connected is true if we're actively connected to a server. + connected bool + // nick is the tracker for our nickname on the server. + nick string + // channels represents all channels we're active in. + channels map[string]*Channel } -// User represents an IRC user and the state attached to them +// User represents an IRC user and the state attached to them. type User struct { - Nick string // nickname of the user - Ident string // ident (often referred to as "user") of the user - Host string // host that server is providing for the user, may not always be accurate - FirstSeen time.Time // the first time they were seen by the client + // Nick is the users current nickname. + Nick string + // Ident is the users username/ident. Ident is commonly prefixed with a + // "~", which indicates that they do not have a identd server setup for + // authentication. + Ident string + // Host is the visible host of the users connection that the server has + // provided to us for their connection. May not always be accurate due to + // many networks spoofing/hiding parts of the hostname for privacy + // reasons. + Host string + // FirstSeen represents the first time that the user was seen by the + // client for the given channel. + FirstSeen time.Time } -// Channel represents an IRC channel and the state attached to it +// Channel represents an IRC channel and the state attached to it. type Channel struct { - Name string // name of the channel, always lowercase - users map[string]*User - Joined time.Time // when the channel was joined + // Name of the channel. Must be rfc compliant. Always represented as + // lower-case, to ensure that the channel is only being tracked once. + Name string + // users represents the users that we can currently see within the + // channel. + users map[string]*User + // Joined represents the first time that the client joined the channel. + Joined time.Time } -// NewState returns a clean state +// NewState returns a clean client state. func NewState() *State { s := &State{} @@ -39,11 +64,11 @@ func NewState() *State { return s } -// createChanIfNotExists creates the channel in state, if not already done +// createChanIfNotExists creates the channel in state, if not already done. func (s *State) createChanIfNotExists(channel string) { channel = strings.ToLower(channel) - // not a valid channel + // Not a valid channel. if !IsValidChannel(channel) { return } @@ -59,7 +84,7 @@ func (s *State) createChanIfNotExists(channel string) { s.m.Unlock() } -// deleteChannel removes the channel from state, if not already done +// deleteChannel removes the channel from state, if not already done. func (s *State) deleteChannel(channel string) { channel = strings.ToLower(channel) s.createChanIfNotExists(channel) @@ -71,8 +96,8 @@ func (s *State) deleteChannel(channel string) { s.m.Unlock() } -// createUserIfNotExists creates the channel and user in state, -// if not already done +// createUserIfNotExists creates the channel and user in state, if not already +// done. func (s *State) createUserIfNotExists(channel, nick, ident, host string) { channel = strings.ToLower(channel) s.createChanIfNotExists(channel) @@ -89,11 +114,11 @@ func (s *State) createUserIfNotExists(channel, nick, ident, host string) { s.m.Unlock() } -// deleteUser removes the user from channel state +// deleteUser removes the user from channel state. func (s *State) deleteUser(nick string) { s.m.Lock() for k := range s.channels { - // check to see if they're in this channel + // Check to see if they're in this channel. if _, ok := s.channels[k].users[nick]; !ok { continue } @@ -103,29 +128,28 @@ func (s *State) deleteUser(nick string) { s.m.Unlock() } -// renameUser renames the user in state, in all locations where -// relevant +// renameUser renames the user in state, in all locations where relevant. func (s *State) renameUser(from, to string) { s.m.Lock() defer s.m.Unlock() for k := range s.channels { - // check to see if they're in this channel + // Check to see if they're in this channel. if _, ok := s.channels[k].users[from]; !ok { continue } - // take the actual reference to the pointer + // Take the actual reference to the pointer. source := *s.channels[k].users[from] - // update the nick field (as we not only have a key, but a - // matching struct field) + // Update the nick field (as we not only have a key, but a matching + // struct field). source.Nick = to - // delete the old + // Delete the old reference. delete(s.channels[k].users, from) - // in with the new + // In with the new. s.channels[k].users[to] = &source } }