girc-atomic/helpers.go

214 lines
5.7 KiB
Go
Raw Normal View History

// 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.
2016-11-13 08:30:43 +00:00
package girc
import "time"
2016-11-13 10:46:32 +00:00
// registerHelpers sets up built-in callbacks/helpers, based on client
// configuration.
2016-11-13 08:30:43 +00:00
func (c *Client) registerHelpers() {
c.Callbacks.mu.Lock()
// Built-in things that should always be supported.
c.Callbacks.register(true, "routine", SUCCESS, CallbackFunc(handleConnect))
c.Callbacks.register(true, "std", PING, CallbackFunc(handlePING))
2016-11-13 08:30:43 +00:00
if !c.Config.DisableTracking {
// Joins/parts/anything that may add/remove/rename users.
c.Callbacks.register(true, "std", JOIN, CallbackFunc(handleJOIN))
c.Callbacks.register(true, "std", PART, CallbackFunc(handlePART))
c.Callbacks.register(true, "std", KICK, CallbackFunc(handleKICK))
c.Callbacks.register(true, "std", QUIT, CallbackFunc(handleQUIT))
c.Callbacks.register(true, "std", NICK, CallbackFunc(handleNICK))
// WHO/WHOX responses.
c.Callbacks.register(true, "std", RPL_WHOREPLY, CallbackFunc(handleWHO))
c.Callbacks.register(true, "std", RPL_WHOSPCRPL, CallbackFunc(handleWHO))
// Other misc. useful stuff.
c.Callbacks.register(true, "std", TOPIC, CallbackFunc(handleTOPIC))
c.Callbacks.register(true, "std", RPL_TOPIC, CallbackFunc(handleTOPIC))
}
// Nickname collisions.
if !c.Config.DisableNickCollision {
c.Callbacks.register(true, "std", ERR_NICKNAMEINUSE, CallbackFunc(nickCollisionHandler))
c.Callbacks.register(true, "std", ERR_NICKCOLLISION, CallbackFunc(nickCollisionHandler))
c.Callbacks.register(true, "std", ERR_UNAVAILRESOURCE, CallbackFunc(nickCollisionHandler))
}
c.Callbacks.mu.Unlock()
2016-11-13 08:30:43 +00:00
}
// handleConnect is a helper function which lets the client know that enough
// time has passed and now they can send commands.
2016-11-13 08:30:43 +00:00
//
// Should always run in separate thread due to blocking delay.
func handleConnect(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 rename
// users on connect.
2016-11-13 08:30:43 +00:00
if len(e.Params) > 0 {
2016-11-14 11:59:08 +00:00
c.state.nick = e.Params[0]
2016-11-13 08:30:43 +00:00
}
time.Sleep(1 * time.Second)
2016-11-13 08:30:43 +00:00
c.Events <- &Event{Command: CONNECTED}
}
// nickCollisionHandler helps prevent the client from having conflicting
// nicknames with another bot, user, etc.
2016-11-13 10:27:53 +00:00
func nickCollisionHandler(c *Client, e Event) {
2016-12-10 10:58:42 +00:00
c.Nick(c.GetNick() + "_")
2016-11-13 08:30:43 +00:00
}
// handlePING helps respond to ping requests from the server.
2016-11-13 10:27:53 +00:00
func handlePING(c *Client, e Event) {
2016-11-13 08:30:43 +00:00
c.Send(&Event{Command: PONG, Params: e.Params, Trailing: e.Trailing})
}
// handleJOIN ensures that the state has updated users and channels.
2016-11-13 10:27:53 +00:00
func handleJOIN(c *Client, e Event) {
if len(e.Params) < 1 {
2016-11-13 08:30:43 +00:00
return
}
// Create the user in state. 2This will also verify the channel.
c.state.mu.Lock()
user := c.state.createUserIfNotExists(e.Params[0], e.Source.Name)
c.state.mu.Unlock()
if user == nil {
return
}
2016-11-13 08:30:43 +00:00
2016-11-19 14:36:17 +00:00
if e.Source.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 entire channel.
c.Send(&Event{Command: WHO, Params: []string{e.Params[0], "%tcuhnr,1"}})
return
}
// Only WHO the user, which is more efficient.
c.Send(&Event{Command: WHO, Params: []string{e.Source.Name, "%tcuhnr,1"}})
2016-11-13 08:30:43 +00:00
}
// handlePART ensures that the state is clean of old user and channel entries.
2016-11-13 10:27:53 +00:00
func handlePART(c *Client, e Event) {
2016-11-13 08:30:43 +00:00
if len(e.Params) == 0 {
return
}
2016-11-19 14:36:17 +00:00
if e.Source.Name == c.GetNick() {
c.state.mu.Lock()
2016-11-14 11:59:08 +00:00
c.state.deleteChannel(e.Params[0])
c.state.mu.Unlock()
2016-11-13 08:30:43 +00:00
return
}
c.state.mu.Lock()
2016-11-19 14:36:17 +00:00
c.state.deleteUser(e.Source.Name)
c.state.mu.Unlock()
2016-11-13 08:30:43 +00:00
}
func handleTOPIC(c *Client, e Event) {
var name string
switch len(e.Params) {
case 0:
return
case 1:
name = e.Params[0]
default:
name = e.Params[len(e.Params)-1]
}
c.state.mu.Lock()
channel := c.state.createChanIfNotExists(name)
if channel == nil {
c.state.mu.Unlock()
return
}
channel.Topic = e.Trailing
c.state.mu.Unlock()
}
// handlWHO updates our internal tracking of users/channels with WHO/WHOX
// information.
2016-11-13 10:27:53 +00:00
func handleWHO(c *Client, e Event) {
var channel, ident, host, nick string
2016-11-13 08:30:43 +00:00
// Assume WHOX related.
2016-11-13 08:30:43 +00:00
if e.Command == RPL_WHOSPCRPL {
if len(e.Params) != 6 {
// Assume there was some form of error or invalid WHOX response.
2016-11-13 08:30:43 +00:00
return
}
if e.Params[1] != "1" {
// We should always be sending 1, and we should receive 1. If this
2016-11-13 08:30:43 +00:00
// is anything but, then we didn't send the request and we can
// ignore it.
return
}
channel, ident, host, nick = e.Params[2], e.Params[3], e.Params[4], e.Params[5]
2016-11-13 08:30:43 +00:00
} else {
channel, ident, host, nick = e.Params[1], e.Params[2], e.Params[3], e.Params[5]
2016-11-13 08:30:43 +00:00
}
c.state.mu.Lock()
user := c.state.createUserIfNotExists(channel, nick)
if user == nil {
c.state.mu.Unlock()
return
}
user.Host = host
user.Ident = ident
user.Name = e.Trailing
c.state.mu.Unlock()
2016-11-13 08:30:43 +00:00
}
// handleKICK ensures that users are cleaned up after being kicked from the
// channel
2016-11-13 10:27:53 +00:00
func handleKICK(c *Client, e Event) {
2016-11-13 08:30:43 +00:00
if len(e.Params) < 2 {
// Needs at least channel and user.
2016-11-13 08:30:43 +00:00
return
}
2016-11-13 10:46:32 +00:00
if e.Params[1] == c.GetNick() {
c.state.mu.Lock()
2016-11-14 11:59:08 +00:00
c.state.deleteChannel(e.Params[0])
c.state.mu.Unlock()
2016-11-13 10:46:32 +00:00
return
}
// Assume it's just another user.
c.state.mu.Lock()
2016-11-14 11:59:08 +00:00
c.state.deleteUser(e.Params[1])
c.state.mu.Unlock()
}
// 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.
return
}
c.state.mu.Lock()
2016-11-19 14:36:17 +00:00
c.state.renameUser(e.Source.Name, e.Params[0])
c.state.mu.Unlock()
}
func handleQUIT(c *Client, e Event) {
c.state.mu.Lock()
2016-11-19 14:36:17 +00:00
c.state.deleteUser(e.Source.Name)
c.state.mu.Unlock()
2016-11-13 08:30:43 +00:00
}