overhaul of docs; add additional Config.Disable* directives.
This commit is contained in:
parent
fb87d9ff51
commit
1077762521
45
callback.go
45
callback.go
@ -1,29 +1,32 @@
|
|||||||
package girc
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. 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
|
// handleEvent runs the necessary callbacks for the incoming event
|
||||||
func (c *Client) handleEvent(event *Event) {
|
func (c *Client) handleEvent(event *Event) {
|
||||||
// log the event
|
// Log the event.
|
||||||
c.log.Print("<-- " + event.String())
|
c.log.Print("<-- " + event.String())
|
||||||
|
|
||||||
// wildcard callbacks first
|
// Wildcard callbacks first.
|
||||||
if callbacks, ok := c.callbacks[ALLEVENTS]; ok {
|
if callbacks, ok := c.callbacks[ALLEVENTS]; ok {
|
||||||
for i := 0; i < len(callbacks); i++ {
|
for i := 0; i < len(callbacks); i++ {
|
||||||
callbacks[i].Execute(c, *event)
|
callbacks[i].Execute(c, *event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// regular non-threaded callbacks
|
// Regular non-threaded callbacks.
|
||||||
if callbacks, ok := c.callbacks[event.Command]; ok {
|
if callbacks, ok := c.callbacks[event.Command]; ok {
|
||||||
for i := 0; i < len(callbacks); i++ {
|
for i := 0; i < len(callbacks); i++ {
|
||||||
callbacks[i].Execute(c, *event)
|
callbacks[i].Execute(c, *event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// callbacks that should be ran concurrently
|
// 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 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 {
|
if callbacks, ok := c.callbacks["routine_"+event.Command]; ok {
|
||||||
for i := 0; i < len(callbacks); i++ {
|
for i := 0; i < len(callbacks); i++ {
|
||||||
go callbacks[i].Execute(c, *event)
|
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() {
|
func (c *Client) ClearCallbacks() {
|
||||||
// registerHelpers should clean all callbacks and setup internal ones
|
// registerHelpers should clean all callbacks and setup internal
|
||||||
// as necessary.
|
// ones as necessary.
|
||||||
c.registerHelpers()
|
c.registerHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunCallbacks manually runs callbacks for a given event
|
// RunCallbacks manually runs callbacks for a given event.
|
||||||
func (c *Client) RunCallbacks(event *Event) {
|
func (c *Client) RunCallbacks(event *Event) {
|
||||||
c.handleEvent(event)
|
c.handleEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCallbackHandler registers a callback (matching the Callback interface)
|
// AddCallbackHandler registers a callback (matching the Callback
|
||||||
// for the given command
|
// interface) for the given command.
|
||||||
func (c *Client) AddCallbackHandler(cmd string, callback Callback) {
|
func (c *Client) AddCallbackHandler(cmd string, callback Callback) {
|
||||||
c.callbacks[cmd] = append(c.callbacks[cmd], 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)) {
|
func (c *Client) AddCallback(cmd string, callback func(c *Client, e Event)) {
|
||||||
c.callbacks[cmd] = append(c.callbacks[cmd], CallbackFunc(callback))
|
c.callbacks[cmd] = append(c.callbacks[cmd], CallbackFunc(callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBgCallback registers the callback function for the given command
|
// 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)) {
|
func (c *Client) AddBgCallback(cmd string, callback func(c *Client, e Event)) {
|
||||||
c.callbacks["routine_"+cmd] = append(c.callbacks["routine_"+cmd], CallbackFunc(callback))
|
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 {
|
type Callback interface {
|
||||||
Execute(*Client, Event)
|
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)
|
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) {
|
func (f CallbackFunc) Execute(c *Client, e Event) {
|
||||||
f(c, e)
|
f(c, e)
|
||||||
}
|
}
|
||||||
|
4
conn.go
4
conn.go
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package girc
|
package girc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
24
contants.go
24
contants.go
@ -1,13 +1,17 @@
|
|||||||
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package girc
|
package girc
|
||||||
|
|
||||||
// misc constants for use with the client
|
// Misc constants for use with the client.
|
||||||
const (
|
const (
|
||||||
ALLEVENTS = "*" // trigger on all events
|
ALLEVENTS = "*" // trigger on all events
|
||||||
CONNECTED = "CONNECTED" // event command which can be used to start responding, after SUCCESS
|
CONNECTED = "CONNECTED" // event command which can be used to start responding, after SUCCESS
|
||||||
SUCCESS = "001" // RPL_WELCOME alias, assumes successful connection
|
SUCCESS = "001" // RPL_WELCOME alias, assumes successful connection
|
||||||
)
|
)
|
||||||
|
|
||||||
// user/channel prefixes :: RFC1459
|
// User/channel prefixes :: RFC1459
|
||||||
const (
|
const (
|
||||||
ChannelPrefix = "#" // regular channel
|
ChannelPrefix = "#" // regular channel
|
||||||
DistributedPrefix = "&" // distributed channel
|
DistributedPrefix = "&" // distributed channel
|
||||||
@ -18,7 +22,7 @@ const (
|
|||||||
VoicePrefix = "+" // user has voice +v
|
VoicePrefix = "+" // user has voice +v
|
||||||
)
|
)
|
||||||
|
|
||||||
// user modes :: RFC1459; section 4.2.3.2
|
// User modes :: RFC1459; section 4.2.3.2
|
||||||
const (
|
const (
|
||||||
UserModeInvisible = "i" // invisible
|
UserModeInvisible = "i" // invisible
|
||||||
UserModeOperator = "o" // server operator
|
UserModeOperator = "o" // server operator
|
||||||
@ -26,7 +30,7 @@ const (
|
|||||||
UserModeWallops = "w" // user wants to receive wallops
|
UserModeWallops = "w" // user wants to receive wallops
|
||||||
)
|
)
|
||||||
|
|
||||||
// channel modes :: RFC1459; section 4.2.3.1
|
// Channel modes :: RFC1459; section 4.2.3.1
|
||||||
const (
|
const (
|
||||||
ModeAdmin = "a" // admin privileges (non-rfc)
|
ModeAdmin = "a" // admin privileges (non-rfc)
|
||||||
ModeHalfOperator = "h" // half-operator privileges (non-rfc)
|
ModeHalfOperator = "h" // half-operator privileges (non-rfc)
|
||||||
@ -42,7 +46,7 @@ const (
|
|||||||
ModeVoice = "v" // speak during moderation mode
|
ModeVoice = "v" // speak during moderation mode
|
||||||
)
|
)
|
||||||
|
|
||||||
// irc commands :: RFC2812; section 3 :: RFC2813; section 4
|
// IRC commands :: RFC2812; section 3 :: RFC2813; section 4
|
||||||
const (
|
const (
|
||||||
ADMIN = "ADMIN"
|
ADMIN = "ADMIN"
|
||||||
AWAY = "AWAY"
|
AWAY = "AWAY"
|
||||||
@ -93,7 +97,7 @@ const (
|
|||||||
WHOWAS = "WHOWAS"
|
WHOWAS = "WHOWAS"
|
||||||
)
|
)
|
||||||
|
|
||||||
// numeric IRC reply mapping :: RFC2812; section 5
|
// Numeric IRC reply mapping :: RFC2812; section 5
|
||||||
const (
|
const (
|
||||||
RPL_WELCOME = "001"
|
RPL_WELCOME = "001"
|
||||||
RPL_YOURHOST = "002"
|
RPL_YOURHOST = "002"
|
||||||
@ -235,7 +239,7 @@ const (
|
|||||||
ERR_USERSDONTMATCH = "502"
|
ERR_USERSDONTMATCH = "502"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ircv3 commands :: http://ircv3.net/irc/
|
// IRCv3 commands :: http://ircv3.net/irc/
|
||||||
const (
|
const (
|
||||||
AUTHENTICATE = "AUTHENTICATE"
|
AUTHENTICATE = "AUTHENTICATE"
|
||||||
CAP = "CAP"
|
CAP = "CAP"
|
||||||
@ -248,7 +252,7 @@ const (
|
|||||||
CAP_REQ = "REQ"
|
CAP_REQ = "REQ"
|
||||||
)
|
)
|
||||||
|
|
||||||
// numeric IRC reply mapping for ircv3 :: http://ircv3.net/irc/
|
// Numeric IRC reply mapping for ircv3 :: http://ircv3.net/irc/
|
||||||
const (
|
const (
|
||||||
RPL_LOGGEDIN = "900"
|
RPL_LOGGEDIN = "900"
|
||||||
RPL_LOGGEDOUT = "901"
|
RPL_LOGGEDOUT = "901"
|
||||||
@ -261,7 +265,7 @@ const (
|
|||||||
RPL_SASLMECHS = "908"
|
RPL_SASLMECHS = "908"
|
||||||
)
|
)
|
||||||
|
|
||||||
// numeric IRC event mapping :: RFC2812; section 5.3
|
// Numeric IRC event mapping :: RFC2812; section 5.3
|
||||||
const (
|
const (
|
||||||
RPL_STATSCLINE = "213"
|
RPL_STATSCLINE = "213"
|
||||||
RPL_STATSNLINE = "214"
|
RPL_STATSNLINE = "214"
|
||||||
@ -289,7 +293,7 @@ const (
|
|||||||
ERR_NOSERVICEHOST = "492"
|
ERR_NOSERVICEHOST = "492"
|
||||||
)
|
)
|
||||||
|
|
||||||
// misc.
|
// Misc.
|
||||||
const (
|
const (
|
||||||
ERR_TOOMANYMATCHES = "416" // IRCNet
|
ERR_TOOMANYMATCHES = "416" // IRCNet
|
||||||
RPL_GLOBALUSERS = "266" // aircd/hybrid/bahamut, used on freenode
|
RPL_GLOBALUSERS = "266" // aircd/hybrid/bahamut, used on freenode
|
||||||
|
4
event.go
4
event.go
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package girc
|
package girc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -11,14 +15,13 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
conf := girc.Config{
|
conf := girc.Config{
|
||||||
Server: "irc.byteirc.org",
|
Server: "irc.byteirc.org",
|
||||||
Port: 6667,
|
Port: 6667,
|
||||||
Nick: "test",
|
Nick: "test",
|
||||||
User: "test1",
|
User: "test1",
|
||||||
Name: "Example bot",
|
Name: "Example bot",
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
Logger: os.Stdout,
|
Logger: os.Stdout,
|
||||||
DisableHelpers: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := girc.New(conf)
|
client := girc.New(conf)
|
||||||
|
85
helpers.go
85
helpers.go
@ -1,41 +1,49 @@
|
|||||||
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package girc
|
package girc
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
// registerHelpers sets up built-in callbacks/helpers, based on client
|
||||||
|
// configuration.
|
||||||
func (c *Client) registerHelpers() {
|
func (c *Client) registerHelpers() {
|
||||||
c.callbacks = make(map[string][]Callback)
|
c.callbacks = make(map[string][]Callback)
|
||||||
|
|
||||||
if c.Config.DisableHelpers {
|
// Built-in things that should always be supported.
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AddBgCallback(SUCCESS, handleWelcome)
|
c.AddBgCallback(SUCCESS, handleWelcome)
|
||||||
c.AddCallback(PING, handlePING)
|
c.AddCallback(PING, handlePING)
|
||||||
|
|
||||||
// joins/parts/anything that may add/remove/rename users
|
if !c.Config.DisableTracking {
|
||||||
c.AddCallback(JOIN, handleJOIN)
|
// Joins/parts/anything that may add/remove/rename users.
|
||||||
c.AddCallback(PART, handlePART)
|
c.AddCallback(JOIN, handleJOIN)
|
||||||
c.AddCallback(KICK, handleKICK)
|
c.AddCallback(PART, handlePART)
|
||||||
c.AddCallback(QUIT, handleQUIT)
|
c.AddCallback(KICK, handleKICK)
|
||||||
c.AddCallback(NICK, handleNICK)
|
c.AddCallback(QUIT, handleQUIT)
|
||||||
|
c.AddCallback(NICK, handleNICK)
|
||||||
|
|
||||||
// WHO/WHOX responses
|
// WHO/WHOX responses.
|
||||||
c.AddCallback(RPL_WHOREPLY, handleWHO)
|
c.AddCallback(RPL_WHOREPLY, handleWHO)
|
||||||
c.AddCallback(RPL_WHOSPCRPL, handleWHO)
|
c.AddCallback(RPL_WHOSPCRPL, handleWHO)
|
||||||
|
}
|
||||||
|
|
||||||
// nickname collisions
|
// Nickname collisions.
|
||||||
c.AddCallback(ERR_NICKNAMEINUSE, nickCollisionHandler)
|
if !c.Config.DisableNickCollision {
|
||||||
c.AddCallback(ERR_NICKCOLLISION, nickCollisionHandler)
|
c.AddCallback(ERR_NICKNAMEINUSE, nickCollisionHandler)
|
||||||
c.AddCallback(ERR_UNAVAILRESOURCE, nickCollisionHandler)
|
c.AddCallback(ERR_NICKCOLLISION, nickCollisionHandler)
|
||||||
|
c.AddCallback(ERR_UNAVAILRESOURCE, nickCollisionHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleWelcome is a helper function which lets the client know
|
// handleWelcome is a helper function which lets the client know that enough
|
||||||
// that enough time has passed and now they can send commands
|
// 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) {
|
func handleWelcome(c *Client, e Event) {
|
||||||
// this should be the nick that the server gives us. 99% of the time, it's the
|
// This should be the nick that the server gives us. 99% of the time, it's
|
||||||
// one we supplied during connection, but some networks will insta-rename users.
|
// the one we supplied during connection, but some networks will rename
|
||||||
|
// users on connect.
|
||||||
if len(e.Params) > 0 {
|
if len(e.Params) > 0 {
|
||||||
c.State.nick = 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
|
// 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) {
|
func nickCollisionHandler(c *Client, e Event) {
|
||||||
c.SetNick(c.GetNick() + "_")
|
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) {
|
func handlePING(c *Client, e Event) {
|
||||||
c.Send(&Event{Command: PONG, Params: e.Params, Trailing: e.Trailing})
|
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) {
|
func handleJOIN(c *Client, e Event) {
|
||||||
if len(e.Params) != 1 {
|
if len(e.Params) != 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// create it in state
|
|
||||||
c.State.createChanIfNotExists(e.Params[0])
|
c.State.createChanIfNotExists(e.Params[0])
|
||||||
|
|
||||||
if e.Prefix.Name == c.GetNick() {
|
if e.Prefix.Name == c.GetNick() {
|
||||||
// if it's us, don't just add our user to the list. run a WHO
|
// If it's us, don't just add our user to the list. Run a WHO which
|
||||||
// which will tell us who exactly is in the channel
|
// will tell us who exactly is in the channel.
|
||||||
c.Who(e.Params[0])
|
c.Who(e.Params[0])
|
||||||
return
|
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)
|
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) {
|
func handlePART(c *Client, e Event) {
|
||||||
if len(e.Params) == 0 {
|
if len(e.Params) == 0 {
|
||||||
return
|
return
|
||||||
@ -90,18 +97,20 @@ func handlePART(c *Client, e Event) {
|
|||||||
c.State.deleteUser(e.Prefix.Name)
|
c.State.deleteUser(e.Prefix.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handlWHO updates our internal tracking of users/channels with WHO/WHOX
|
||||||
|
// information.
|
||||||
func handleWHO(c *Client, e Event) {
|
func handleWHO(c *Client, e Event) {
|
||||||
var channel, user, host, nick string
|
var channel, user, host, nick string
|
||||||
|
|
||||||
// assume WHOX related
|
// Assume WHOX related.
|
||||||
if e.Command == RPL_WHOSPCRPL {
|
if e.Command == RPL_WHOSPCRPL {
|
||||||
if len(e.Params) != 6 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Params[1] != "1" {
|
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
|
// is anything but, then we didn't send the request and we can
|
||||||
// ignore it.
|
// ignore it.
|
||||||
return
|
return
|
||||||
@ -115,9 +124,11 @@ func handleWHO(c *Client, e Event) {
|
|||||||
c.State.createUserIfNotExists(channel, nick, user, host)
|
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) {
|
func handleKICK(c *Client, e Event) {
|
||||||
if len(e.Params) < 2 {
|
if len(e.Params) < 2 {
|
||||||
// needs at least channel and user
|
// Needs at least channel and user.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,13 +137,15 @@ func handleKICK(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// assume it's just another user
|
// Assume it's just another user.
|
||||||
c.State.deleteUser(e.Params[1])
|
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) {
|
func handleNICK(c *Client, e Event) {
|
||||||
if len(e.Params) != 1 {
|
if len(e.Params) != 1 {
|
||||||
// something erronous was sent to us
|
// Something erronous was sent to us.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
213
main.go
213
main.go
@ -1,3 +1,18 @@
|
|||||||
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. 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
|
package girc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -7,55 +22,100 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO's:
|
// TODO's:
|
||||||
|
// * ClearCallbacks(CODE)?
|
||||||
// * NOTICE (Notice?), SendRaw?
|
// * NOTICE (Notice?), SendRaw?
|
||||||
// * track connection time (conntime? in state)
|
// * track connection time (conntime? in state)
|
||||||
// * with conntime, find lag. Client.Lag() would be useful
|
// * 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
|
// -- https://github.com/Liamraystanley/Code/blob/master/core/triggers.py#L40-L67
|
||||||
// * client should support ping tracking (sending PING's to the server)
|
// * client should support ping tracking (sending PING's to the server)
|
||||||
// * users need to be exposed in state somehow (other than GetChannels())
|
// * users need to be exposed in state somehow (other than GetChannels())
|
||||||
// * ip/host binding?
|
// * ip/host binding?
|
||||||
// * IsValidNick?
|
// * 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 {
|
type Client struct {
|
||||||
Config Config // configuration for client
|
// Config represents the configuration
|
||||||
State *State // state for the client
|
Config Config
|
||||||
Events chan *Event // queue of events to handle
|
// State represents the internal state
|
||||||
Sender Sender // send wrapper for conn
|
State *State
|
||||||
|
// Events is a buffer of events waiting to be processed.
|
||||||
initTime time.Time // time when the client was created
|
Events chan *Event
|
||||||
callbacks map[string][]Callback // mapping of callbacks
|
// Sender is a Sender{} interface implementation.
|
||||||
reader *Decoder // for use with reading from conn stream
|
Sender Sender
|
||||||
writer *Encoder // for use with writing to conn stream
|
// initTime represents the creation time of the client.
|
||||||
conn net.Conn // network connection to the irc server
|
initTime time.Time
|
||||||
tries int // number of attempts to connect to the server
|
// callbacks is an internal mapping of COMMAND -> callback.
|
||||||
log *log.Logger // package logger
|
callbacks map[string][]Callback
|
||||||
quitChan chan bool // channel used for disconnect/quitting
|
// reader is the socket buffer reader from the IRC server.
|
||||||
hasQuit bool // used to let the reconnect functionality know a manual quit has been completed
|
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
|
// Config contains configuration options for an IRC client
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server string // server to connect to
|
// Server is a host/ip of the server you want to connect to.
|
||||||
Port int // port to use for server
|
Server string
|
||||||
Password string // password for the irc server
|
// Port is the port that will be used during server connection.
|
||||||
Nick string // nickname to attempt to use on connect
|
Port int
|
||||||
User string // username to attempt to use on connect
|
// Password is the server password used to authenticate.
|
||||||
Name string // "realname" to attempt to use on connect
|
Password string
|
||||||
TLSConfig *tls.Config // tls/ssl configuration
|
// Nick is an rfc-valid nickname used during connect.
|
||||||
MaxRetries int // max number of reconnect retries
|
Nick string
|
||||||
Logger io.Writer // writer for which to write logs to
|
// User is the username/ident to use on connect. Ignored if identd server
|
||||||
DisableHelpers bool // if default event handlers should be used (to respond to ping, user tracking, etc)
|
// 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 {
|
func New(config Config) *Client {
|
||||||
client := &Client{
|
client := &Client{
|
||||||
Config: config,
|
Config: config,
|
||||||
@ -66,13 +126,13 @@ func New(config Config) *Client {
|
|||||||
initTime: time.Now(),
|
initTime: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// register builtin helpers
|
// Register builtin helpers.
|
||||||
client.registerHelpers()
|
client.registerHelpers()
|
||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quit disconnects from the server
|
// Quit disconnects from the server.s
|
||||||
func (c *Client) Quit(message string) {
|
func (c *Client) Quit(message string) {
|
||||||
c.Send(&Event{Command: QUIT, Trailing: message})
|
c.Send(&Event{Command: QUIT, Trailing: message})
|
||||||
|
|
||||||
@ -85,7 +145,8 @@ func (c *Client) Quit(message string) {
|
|||||||
c.quitChan <- true
|
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 {
|
func (c *Client) Uptime() time.Duration {
|
||||||
return time.Since(c.initTime)
|
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)
|
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 {
|
func (c *Client) Send(event *Event) error {
|
||||||
// log the event
|
// log the event
|
||||||
if !event.Sensitive {
|
if !event.Sensitive {
|
||||||
@ -110,12 +172,12 @@ func (c *Client) Connect() error {
|
|||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
var err error
|
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 == "" {
|
if c.Config.Server == "" || c.Config.Port == 0 || c.Config.Nick == "" || c.Config.User == "" {
|
||||||
return errors.New("invalid configuration (server/port/nick/user)")
|
return errors.New("invalid configuration (server/port/nick/user)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset our state here
|
// Reset the state.
|
||||||
c.State = NewState()
|
c.State = NewState()
|
||||||
|
|
||||||
if c.Config.Logger == nil {
|
if c.Config.Logger == nil {
|
||||||
@ -146,24 +208,24 @@ func (c *Client) Connect() error {
|
|||||||
c.tries = 0
|
c.tries = 0
|
||||||
go c.ReadLoop()
|
go c.ReadLoop()
|
||||||
|
|
||||||
// consider the connection a success at this point
|
// Consider the connection a success at this point.
|
||||||
c.State.connected = true
|
c.State.connected = true
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// connectMessages is a list of IRC messages to send when attempting to
|
// connectMessages is a list of IRC messages to send when attempting
|
||||||
// connect to the IRC server.
|
// to connect to the IRC server.
|
||||||
func (c *Client) connectMessages() (events []*Event) {
|
func (c *Client) connectMessages() (events []*Event) {
|
||||||
// passwords first
|
// Passwords first.
|
||||||
if c.Config.Password != "" {
|
if c.Config.Password != "" {
|
||||||
events = append(events, &Event{Command: PASS, Params: []string{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}})
|
events = append(events, &Event{Command: NICK, Params: []string{c.Config.Nick}})
|
||||||
|
|
||||||
// then username and realname
|
// Then username and realname.
|
||||||
if c.Config.Name == "" {
|
if c.Config.Name == "" {
|
||||||
c.Config.Name = c.Config.User
|
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 checks to make sure we want to, and then attempts to
|
||||||
// reconnect to the server
|
// reconnect to the server.
|
||||||
func (c *Client) Reconnect() error {
|
func (c *Client) Reconnect() (err error) {
|
||||||
if c.hasQuit {
|
if c.hasQuit {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Config.ReconnectDelay < (10 * time.Second) {
|
||||||
|
c.Config.ReconnectDelay = 10 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
if c.Config.MaxRetries > 0 {
|
if c.Config.MaxRetries > 0 {
|
||||||
|
var err error
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
|
|
||||||
var err error
|
// Re-setup events.
|
||||||
|
|
||||||
// re-setup events
|
|
||||||
c.Events = make(chan *Event, 40)
|
c.Events = make(chan *Event, 40)
|
||||||
|
|
||||||
// sleep for 10 seconds so we're not slaughtering the server
|
// Delay so we're not slaughtering the server with a bunch of
|
||||||
c.log.Printf("reconnecting to %s in 10 seconds", c.Server())
|
// connections.
|
||||||
time.Sleep(10 * time.Second)
|
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++ {
|
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
|
c.log.Printf("reconnecting to %s in %s (%d tries)", c.Server(), c.Config.ReconnectDelay, c.tries)
|
||||||
time.Sleep(duration)
|
time.Sleep(c.Config.ReconnectDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@ -209,7 +275,7 @@ func (c *Client) Reconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadLoop sets a timeout of 300 seconds, and then attempts to read
|
// 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 {
|
func (c *Client) ReadLoop() error {
|
||||||
for {
|
for {
|
||||||
c.conn.SetDeadline(time.Now().Add(300 * time.Second))
|
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
|
// Loop reads from the events channel and sends the events to be
|
||||||
// for every message it receives.
|
// handled for every message it receives.
|
||||||
func (c *Client) Loop() {
|
func (c *Client) Loop() {
|
||||||
for {
|
for {
|
||||||
select {
|
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 {
|
func (c *Client) IsConnected() bool {
|
||||||
c.State.m.RLock()
|
c.State.m.RLock()
|
||||||
defer c.State.m.RUnlock()
|
defer c.State.m.RUnlock()
|
||||||
@ -243,10 +309,14 @@ func (c *Client) IsConnected() bool {
|
|||||||
return c.State.connected
|
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 {
|
func (c *Client) GetNick() string {
|
||||||
|
if c.Config.DisableTracking {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
c.State.m.RLock()
|
c.State.m.RLock()
|
||||||
defer c.State.m.RUnlock()
|
defer c.State.m.RUnlock()
|
||||||
|
|
||||||
@ -257,7 +327,7 @@ func (c *Client) GetNick() string {
|
|||||||
return c.State.nick
|
return c.State.nick
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNick changes the client nickname
|
// SetNick changes the client nickname.
|
||||||
func (c *Client) SetNick(name string) {
|
func (c *Client) SetNick(name string) {
|
||||||
c.State.m.Lock()
|
c.State.m.Lock()
|
||||||
defer c.State.m.Unlock()
|
defer c.State.m.Unlock()
|
||||||
@ -266,22 +336,29 @@ func (c *Client) SetNick(name string) {
|
|||||||
c.Send(&Event{Command: NICK, Params: []string{name}})
|
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 {
|
func (c *Client) GetChannels() map[string]*Channel {
|
||||||
|
if c.Config.DisableTracking {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
c.State.m.RLock()
|
c.State.m.RLock()
|
||||||
defer c.State.m.RUnlock()
|
defer c.State.m.RUnlock()
|
||||||
|
|
||||||
return c.State.channels
|
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) {
|
func (c *Client) Who(target string) {
|
||||||
c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhn,1"}})
|
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) {
|
func (c *Client) Join(channel, password string) {
|
||||||
if password != "" {
|
if password != "" {
|
||||||
c.Send(&Event{Command: JOIN, Params: []string{channel, 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}})
|
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) {
|
func (c *Client) Part(channel, message string) {
|
||||||
if message != "" {
|
if message != "" {
|
||||||
c.Send(&Event{Command: JOIN, Params: []string{channel}, Trailing: 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}})
|
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) {
|
func (c *Client) Message(target, message string) {
|
||||||
c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message})
|
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{}) {
|
func (c *Client) Messagef(target, format string, a ...interface{}) {
|
||||||
c.Message(target, fmt.Sprintf(format, a...))
|
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) {
|
func (c *Client) Action(target, message string) {
|
||||||
c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: fmt.Sprintf("\001ACTION %s\001", message)})
|
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{}) {
|
func (c *Client) Actionf(target, format string, a ...interface{}) {
|
||||||
c.Action(target, fmt.Sprintf(format, a...))
|
c.Action(target, fmt.Sprintf(format, a...))
|
||||||
}
|
}
|
||||||
|
12
sender.go
12
sender.go
@ -1,18 +1,22 @@
|
|||||||
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package girc
|
package girc
|
||||||
|
|
||||||
// Sender is an interface for sending IRC messages
|
// Sender is an interface for sending IRC messages.
|
||||||
type Sender interface {
|
type Sender interface {
|
||||||
// Send sends the given message and returns any errors.
|
// Send sends the given message and returns any errors.
|
||||||
Send(*Event) error
|
Send(*Event) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// serverSender is a barebones writer used
|
// serverSender is a barebones writer used as the default sender for all
|
||||||
// as the default sender for all callbacks
|
// callbacks.
|
||||||
type serverSender struct {
|
type serverSender struct {
|
||||||
writer *Encoder
|
writer *Encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send sends the specified event
|
// Send sends the specified event.
|
||||||
func (s serverSender) Send(event *Event) error {
|
func (s serverSender) Send(event *Event) error {
|
||||||
return s.writer.Encode(event)
|
return s.writer.Encode(event)
|
||||||
}
|
}
|
||||||
|
84
state.go
84
state.go
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package girc
|
package girc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -6,30 +10,51 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// State represents the actively-changing variables within the client runtime
|
// State represents the actively-changing variables within the client
|
||||||
|
// runtime.
|
||||||
type State struct {
|
type State struct {
|
||||||
m sync.RWMutex // lock, primarily used for writing things in state
|
// m is a RW mutex lock, used to guard the state from goroutines causing
|
||||||
connected bool // if we're connected to the server or not
|
// corruption.
|
||||||
nick string // internal tracker for our nickname
|
m sync.RWMutex
|
||||||
channels map[string]*Channel // map of channels that the client is in
|
// 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 {
|
type User struct {
|
||||||
Nick string // nickname of the user
|
// Nick is the users current nickname.
|
||||||
Ident string // ident (often referred to as "user") of the user
|
Nick string
|
||||||
Host string // host that server is providing for the user, may not always be accurate
|
// Ident is the users username/ident. Ident is commonly prefixed with a
|
||||||
FirstSeen time.Time // the first time they were seen by the client
|
// "~", 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 {
|
type Channel struct {
|
||||||
Name string // name of the channel, always lowercase
|
// Name of the channel. Must be rfc compliant. Always represented as
|
||||||
users map[string]*User
|
// lower-case, to ensure that the channel is only being tracked once.
|
||||||
Joined time.Time // when the channel was joined
|
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 {
|
func NewState() *State {
|
||||||
s := &State{}
|
s := &State{}
|
||||||
|
|
||||||
@ -39,11 +64,11 @@ func NewState() *State {
|
|||||||
return s
|
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) {
|
func (s *State) createChanIfNotExists(channel string) {
|
||||||
channel = strings.ToLower(channel)
|
channel = strings.ToLower(channel)
|
||||||
|
|
||||||
// not a valid channel
|
// Not a valid channel.
|
||||||
if !IsValidChannel(channel) {
|
if !IsValidChannel(channel) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -59,7 +84,7 @@ func (s *State) createChanIfNotExists(channel string) {
|
|||||||
s.m.Unlock()
|
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) {
|
func (s *State) deleteChannel(channel string) {
|
||||||
channel = strings.ToLower(channel)
|
channel = strings.ToLower(channel)
|
||||||
s.createChanIfNotExists(channel)
|
s.createChanIfNotExists(channel)
|
||||||
@ -71,8 +96,8 @@ func (s *State) deleteChannel(channel string) {
|
|||||||
s.m.Unlock()
|
s.m.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// createUserIfNotExists creates the channel and user in state,
|
// createUserIfNotExists creates the channel and user in state, if not already
|
||||||
// if not already done
|
// done.
|
||||||
func (s *State) createUserIfNotExists(channel, nick, ident, host string) {
|
func (s *State) createUserIfNotExists(channel, nick, ident, host string) {
|
||||||
channel = strings.ToLower(channel)
|
channel = strings.ToLower(channel)
|
||||||
s.createChanIfNotExists(channel)
|
s.createChanIfNotExists(channel)
|
||||||
@ -89,11 +114,11 @@ func (s *State) createUserIfNotExists(channel, nick, ident, host string) {
|
|||||||
s.m.Unlock()
|
s.m.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteUser removes the user from channel state
|
// deleteUser removes the user from channel state.
|
||||||
func (s *State) deleteUser(nick string) {
|
func (s *State) deleteUser(nick string) {
|
||||||
s.m.Lock()
|
s.m.Lock()
|
||||||
for k := range s.channels {
|
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 {
|
if _, ok := s.channels[k].users[nick]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -103,29 +128,28 @@ func (s *State) deleteUser(nick string) {
|
|||||||
s.m.Unlock()
|
s.m.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// renameUser renames the user in state, in all locations where
|
// renameUser renames the user in state, in all locations where relevant.
|
||||||
// relevant
|
|
||||||
func (s *State) renameUser(from, to string) {
|
func (s *State) renameUser(from, to string) {
|
||||||
s.m.Lock()
|
s.m.Lock()
|
||||||
defer s.m.Unlock()
|
defer s.m.Unlock()
|
||||||
|
|
||||||
for k := range s.channels {
|
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 {
|
if _, ok := s.channels[k].users[from]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// take the actual reference to the pointer
|
// Take the actual reference to the pointer.
|
||||||
source := *s.channels[k].users[from]
|
source := *s.channels[k].users[from]
|
||||||
|
|
||||||
// update the nick field (as we not only have a key, but a
|
// Update the nick field (as we not only have a key, but a matching
|
||||||
// matching struct field)
|
// struct field).
|
||||||
source.Nick = to
|
source.Nick = to
|
||||||
|
|
||||||
// delete the old
|
// Delete the old reference.
|
||||||
delete(s.channels[k].users, from)
|
delete(s.channels[k].users, from)
|
||||||
|
|
||||||
// in with the new
|
// In with the new.
|
||||||
s.channels[k].users[to] = &source
|
s.channels[k].users[to] = &source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user