govendor
This commit is contained in:
27
vendor/github.com/fluffle/goirc/LICENSE
generated
vendored
Normal file
27
vendor/github.com/fluffle/goirc/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009+ Alex Bramley. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
303
vendor/github.com/fluffle/goirc/client/commands.go
generated
vendored
Normal file
303
vendor/github.com/fluffle/goirc/client/commands.go
generated
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
REGISTER = "REGISTER"
|
||||
CONNECTED = "CONNECTED"
|
||||
DISCONNECTED = "DISCONNECTED"
|
||||
ACTION = "ACTION"
|
||||
AWAY = "AWAY"
|
||||
CAP = "CAP"
|
||||
CTCP = "CTCP"
|
||||
CTCPREPLY = "CTCPREPLY"
|
||||
INVITE = "INVITE"
|
||||
JOIN = "JOIN"
|
||||
KICK = "KICK"
|
||||
MODE = "MODE"
|
||||
NICK = "NICK"
|
||||
NOTICE = "NOTICE"
|
||||
OPER = "OPER"
|
||||
PART = "PART"
|
||||
PASS = "PASS"
|
||||
PING = "PING"
|
||||
PONG = "PONG"
|
||||
PRIVMSG = "PRIVMSG"
|
||||
QUIT = "QUIT"
|
||||
TOPIC = "TOPIC"
|
||||
USER = "USER"
|
||||
VERSION = "VERSION"
|
||||
VHOST = "VHOST"
|
||||
WHO = "WHO"
|
||||
WHOIS = "WHOIS"
|
||||
defaultSplit = 450
|
||||
)
|
||||
|
||||
// cutNewLines() pares down a string to the part before the first "\r" or "\n".
|
||||
func cutNewLines(s string) string {
|
||||
r := strings.SplitN(s, "\r", 2)
|
||||
r = strings.SplitN(r[0], "\n", 2)
|
||||
return r[0]
|
||||
}
|
||||
|
||||
// indexFragment looks for the last sentence split-point (defined as one of
|
||||
// the punctuation characters .:;,!?"' followed by a space) in the string s
|
||||
// and returns the index in the string after that split-point. If no split-
|
||||
// point is found it returns the index after the last space in s, or -1.
|
||||
func indexFragment(s string) int {
|
||||
max := -1
|
||||
for _, sep := range []string{". ", ": ", "; ", ", ", "! ", "? ", "\" ", "' "} {
|
||||
if idx := strings.LastIndex(s, sep); idx > max {
|
||||
max = idx
|
||||
}
|
||||
}
|
||||
if max > 0 {
|
||||
return max + 2
|
||||
}
|
||||
if idx := strings.LastIndex(s, " "); idx > 0 {
|
||||
return idx + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// splitMessage splits a message > splitLen chars at:
|
||||
// 1. the end of the last sentence fragment before splitLen
|
||||
// 2. the end of the last word before splitLen
|
||||
// 3. splitLen itself
|
||||
func splitMessage(msg string, splitLen int) (msgs []string) {
|
||||
// This is quite short ;-)
|
||||
if splitLen < 13 {
|
||||
splitLen = defaultSplit
|
||||
}
|
||||
for len(msg) > splitLen {
|
||||
idx := indexFragment(msg[:splitLen-3])
|
||||
if idx < 0 {
|
||||
idx = splitLen - 3
|
||||
}
|
||||
msgs = append(msgs, msg[:idx]+"...")
|
||||
msg = msg[idx:]
|
||||
}
|
||||
return append(msgs, msg)
|
||||
}
|
||||
|
||||
// Raw sends a raw line to the server, should really only be used for
|
||||
// debugging purposes but may well come in handy.
|
||||
func (conn *Conn) Raw(rawline string) {
|
||||
// Avoid command injection by enforcing one command per line.
|
||||
conn.out <- cutNewLines(rawline)
|
||||
}
|
||||
|
||||
// Pass sends a PASS command to the server.
|
||||
// PASS password
|
||||
func (conn *Conn) Pass(password string) { conn.Raw(PASS + " " + password) }
|
||||
|
||||
// Nick sends a NICK command to the server.
|
||||
// NICK nick
|
||||
func (conn *Conn) Nick(nick string) { conn.Raw(NICK + " " + nick) }
|
||||
|
||||
// User sends a USER command to the server.
|
||||
// USER ident 12 * :name
|
||||
func (conn *Conn) User(ident, name string) {
|
||||
conn.Raw(USER + " " + ident + " 12 * :" + name)
|
||||
}
|
||||
|
||||
// Join sends a JOIN command to the server with an optional key.
|
||||
// JOIN channel [key]
|
||||
func (conn *Conn) Join(channel string, key ...string) {
|
||||
k := ""
|
||||
if len(key) > 0 {
|
||||
k = " " + key[0]
|
||||
}
|
||||
conn.Raw(JOIN + " " + channel + k)
|
||||
}
|
||||
|
||||
// Part sends a PART command to the server with an optional part message.
|
||||
// PART channel [:message]
|
||||
func (conn *Conn) Part(channel string, message ...string) {
|
||||
msg := strings.Join(message, " ")
|
||||
if msg != "" {
|
||||
msg = " :" + msg
|
||||
}
|
||||
conn.Raw(PART + " " + channel + msg)
|
||||
}
|
||||
|
||||
// Kick sends a KICK command to remove a nick from a channel.
|
||||
// KICK channel nick [:message]
|
||||
func (conn *Conn) Kick(channel, nick string, message ...string) {
|
||||
msg := strings.Join(message, " ")
|
||||
if msg != "" {
|
||||
msg = " :" + msg
|
||||
}
|
||||
conn.Raw(KICK + " " + channel + " " + nick + msg)
|
||||
}
|
||||
|
||||
// Quit sends a QUIT command to the server with an optional quit message.
|
||||
// QUIT [:message]
|
||||
func (conn *Conn) Quit(message ...string) {
|
||||
msg := strings.Join(message, " ")
|
||||
if msg == "" {
|
||||
msg = conn.cfg.QuitMessage
|
||||
}
|
||||
conn.Raw(QUIT + " :" + msg)
|
||||
}
|
||||
|
||||
// Whois sends a WHOIS command to the server.
|
||||
// WHOIS nick
|
||||
func (conn *Conn) Whois(nick string) { conn.Raw(WHOIS + " " + nick) }
|
||||
|
||||
// Who sends a WHO command to the server.
|
||||
// WHO nick
|
||||
func (conn *Conn) Who(nick string) { conn.Raw(WHO + " " + nick) }
|
||||
|
||||
// Privmsg sends a PRIVMSG to the target nick or channel t.
|
||||
// If msg is longer than Config.SplitLen characters, multiple PRIVMSGs
|
||||
// will be sent to the target containing sequential parts of msg.
|
||||
// PRIVMSG t :msg
|
||||
func (conn *Conn) Privmsg(t, msg string) {
|
||||
prefix := PRIVMSG + " " + t + " :"
|
||||
for _, s := range splitMessage(msg, conn.cfg.SplitLen) {
|
||||
conn.Raw(prefix + s)
|
||||
}
|
||||
}
|
||||
|
||||
// Privmsgln is the variadic version of Privmsg that formats the message
|
||||
// that is sent to the target nick or channel t using the
|
||||
// fmt.Sprintln function.
|
||||
// Note: Privmsgln doesn't add the '\n' character at the end of the message.
|
||||
func (conn *Conn) Privmsgln(t string, a ...interface{}) {
|
||||
msg := fmt.Sprintln(a...)
|
||||
// trimming the new-line character added by the fmt.Sprintln function,
|
||||
// since it's irrelevant.
|
||||
msg = msg[:len(msg)-1]
|
||||
conn.Privmsg(t, msg)
|
||||
}
|
||||
|
||||
// Privmsgf is the variadic version of Privmsg that formats the message
|
||||
// that is sent to the target nick or channel t using the
|
||||
// fmt.Sprintf function.
|
||||
func (conn *Conn) Privmsgf(t, format string, a ...interface{}) {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
conn.Privmsg(t, msg)
|
||||
}
|
||||
|
||||
// Notice sends a NOTICE to the target nick or channel t.
|
||||
// If msg is longer than Config.SplitLen characters, multiple NOTICEs
|
||||
// will be sent to the target containing sequential parts of msg.
|
||||
// NOTICE t :msg
|
||||
func (conn *Conn) Notice(t, msg string) {
|
||||
for _, s := range splitMessage(msg, conn.cfg.SplitLen) {
|
||||
conn.Raw(NOTICE + " " + t + " :" + s)
|
||||
}
|
||||
}
|
||||
|
||||
// Ctcp sends a (generic) CTCP message to the target nick
|
||||
// or channel t, with an optional argument.
|
||||
// PRIVMSG t :\001CTCP arg\001
|
||||
func (conn *Conn) Ctcp(t, ctcp string, arg ...string) {
|
||||
// We need to split again here to ensure
|
||||
for _, s := range splitMessage(strings.Join(arg, " "), conn.cfg.SplitLen) {
|
||||
if s != "" {
|
||||
s = " " + s
|
||||
}
|
||||
// Using Raw rather than PRIVMSG here to avoid double-split problems.
|
||||
conn.Raw(PRIVMSG + " " + t + " :\001" + strings.ToUpper(ctcp) + s + "\001")
|
||||
}
|
||||
}
|
||||
|
||||
// CtcpReply sends a (generic) CTCP reply to the target nick
|
||||
// or channel t, with an optional argument.
|
||||
// NOTICE t :\001CTCP arg\001
|
||||
func (conn *Conn) CtcpReply(t, ctcp string, arg ...string) {
|
||||
for _, s := range splitMessage(strings.Join(arg, " "), conn.cfg.SplitLen) {
|
||||
if s != "" {
|
||||
s = " " + s
|
||||
}
|
||||
// Using Raw rather than NOTICE here to avoid double-split problems.
|
||||
conn.Raw(NOTICE + " " + t + " :\001" + strings.ToUpper(ctcp) + s + "\001")
|
||||
}
|
||||
}
|
||||
|
||||
// Version sends a CTCP "VERSION" to the target nick or channel t.
|
||||
func (conn *Conn) Version(t string) { conn.Ctcp(t, VERSION) }
|
||||
|
||||
// Action sends a CTCP "ACTION" to the target nick or channel t.
|
||||
func (conn *Conn) Action(t, msg string) { conn.Ctcp(t, ACTION, msg) }
|
||||
|
||||
// Topic() sends a TOPIC command for a channel.
|
||||
// If no topic is provided this requests that a 332 response is sent by the
|
||||
// server for that channel, which can then be handled to retrieve the current
|
||||
// channel topic. If a topic is provided the channel's topic will be set.
|
||||
// TOPIC channel
|
||||
// TOPIC channel :topic
|
||||
func (conn *Conn) Topic(channel string, topic ...string) {
|
||||
t := strings.Join(topic, " ")
|
||||
if t != "" {
|
||||
t = " :" + t
|
||||
}
|
||||
conn.Raw(TOPIC + " " + channel + t)
|
||||
}
|
||||
|
||||
// Mode sends a MODE command for a target nick or channel t.
|
||||
// If no mode strings are provided this requests that a 324 response is sent
|
||||
// by the server for the target. Otherwise the mode strings are concatenated
|
||||
// with spaces and sent to the server. This allows e.g.
|
||||
// conn.Mode("#channel", "+nsk", "mykey")
|
||||
//
|
||||
// MODE t
|
||||
// MODE t modestring
|
||||
func (conn *Conn) Mode(t string, modestring ...string) {
|
||||
mode := strings.Join(modestring, " ")
|
||||
if mode != "" {
|
||||
mode = " " + mode
|
||||
}
|
||||
conn.Raw(MODE + " " + t + mode)
|
||||
}
|
||||
|
||||
// Away sends an AWAY command to the server.
|
||||
// If a message is provided it sets the client's away status with that message,
|
||||
// otherwise it resets the client's away status.
|
||||
// AWAY
|
||||
// AWAY :message
|
||||
func (conn *Conn) Away(message ...string) {
|
||||
msg := strings.Join(message, " ")
|
||||
if msg != "" {
|
||||
msg = " :" + msg
|
||||
}
|
||||
conn.Raw(AWAY + msg)
|
||||
}
|
||||
|
||||
// Invite sends an INVITE command to the server.
|
||||
// INVITE nick channel
|
||||
func (conn *Conn) Invite(nick, channel string) {
|
||||
conn.Raw(INVITE + " " + nick + " " + channel)
|
||||
}
|
||||
|
||||
// Oper sends an OPER command to the server.
|
||||
// OPER user pass
|
||||
func (conn *Conn) Oper(user, pass string) { conn.Raw(OPER + " " + user + " " + pass) }
|
||||
|
||||
// VHost sends a VHOST command to the server.
|
||||
// VHOST user pass
|
||||
func (conn *Conn) VHost(user, pass string) { conn.Raw(VHOST + " " + user + " " + pass) }
|
||||
|
||||
// Ping sends a PING command to the server, which should PONG.
|
||||
// PING :message
|
||||
func (conn *Conn) Ping(message string) { conn.Raw(PING + " :" + message) }
|
||||
|
||||
// Pong sends a PONG command to the server.
|
||||
// PONG :message
|
||||
func (conn *Conn) Pong(message string) { conn.Raw(PONG + " :" + message) }
|
||||
|
||||
// Cap sends a CAP command to the server.
|
||||
// CAP subcommand
|
||||
// CAP subcommand :message
|
||||
func (conn *Conn) Cap(subcommmand string, capabilities ...string) {
|
||||
if len(capabilities) == 0 {
|
||||
conn.Raw(CAP + " " + subcommmand)
|
||||
} else {
|
||||
conn.Raw(CAP + " " + subcommmand + " :" + strings.Join(capabilities, " "))
|
||||
}
|
||||
}
|
570
vendor/github.com/fluffle/goirc/client/connection.go
generated
vendored
Normal file
570
vendor/github.com/fluffle/goirc/client/connection.go
generated
vendored
Normal file
@@ -0,0 +1,570 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fluffle/goirc/logging"
|
||||
"github.com/fluffle/goirc/state"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
// Conn encapsulates a connection to a single IRC server. Create
|
||||
// one with Client or SimpleClient.
|
||||
type Conn struct {
|
||||
// For preventing races on (dis)connect.
|
||||
mu sync.RWMutex
|
||||
|
||||
// Contains parameters that people can tweak to change client behaviour.
|
||||
cfg *Config
|
||||
|
||||
// Handlers
|
||||
intHandlers *hSet
|
||||
fgHandlers *hSet
|
||||
bgHandlers *hSet
|
||||
|
||||
// State tracker for nicks and channels
|
||||
st state.Tracker
|
||||
stRemovers []Remover
|
||||
|
||||
// I/O stuff to server
|
||||
dialer *net.Dialer
|
||||
proxyDialer proxy.Dialer
|
||||
sock net.Conn
|
||||
io *bufio.ReadWriter
|
||||
in chan *Line
|
||||
out chan string
|
||||
connected bool
|
||||
|
||||
// Control channel and WaitGroup for goroutines
|
||||
die chan struct{}
|
||||
wg sync.WaitGroup
|
||||
|
||||
// Internal counters for flood protection
|
||||
badness time.Duration
|
||||
lastsent time.Time
|
||||
}
|
||||
|
||||
// Config contains options that can be passed to Client to change the
|
||||
// behaviour of the library during use. It is recommended that NewConfig
|
||||
// is used to create this struct rather than instantiating one directly.
|
||||
// Passing a Config with no Nick in the Me field to Client will result
|
||||
// in unflattering consequences.
|
||||
type Config struct {
|
||||
// Set this to provide the Nick, Ident and Name for the client to use.
|
||||
// It is recommended to call Conn.Me to get up-to-date information
|
||||
// about the current state of the client's IRC nick after connecting.
|
||||
Me *state.Nick
|
||||
|
||||
// Hostname to connect to and optional connect password.
|
||||
// Changing these after connection will have no effect until the
|
||||
// client reconnects.
|
||||
Server, Pass string
|
||||
|
||||
// Are we connecting via SSL? Do we care about certificate validity?
|
||||
// Changing these after connection will have no effect until the
|
||||
// client reconnects.
|
||||
SSL bool
|
||||
SSLConfig *tls.Config
|
||||
|
||||
// To connect via proxy set the proxy url here.
|
||||
// Changing these after connection will have no effect until the
|
||||
// client reconnects.
|
||||
Proxy string
|
||||
|
||||
// Local address to bind to when connecting to the server.
|
||||
LocalAddr string
|
||||
|
||||
// Replaceable function to customise the 433 handler's new nick.
|
||||
// By default an underscore "_" is appended to the current nick.
|
||||
NewNick func(string) string
|
||||
|
||||
// Client->server ping frequency, in seconds. Defaults to 3m.
|
||||
// Set to 0 to disable client-side pings.
|
||||
PingFreq time.Duration
|
||||
|
||||
// The duration before a connection timeout is triggered. Defaults to 1m.
|
||||
// Set to 0 to wait indefinitely.
|
||||
Timeout time.Duration
|
||||
|
||||
// Set this to true to disable flood protection and false to re-enable.
|
||||
Flood bool
|
||||
|
||||
// Sent as the reply to a CTCP VERSION message.
|
||||
Version string
|
||||
|
||||
// Sent as the default QUIT message if Quit is called with no args.
|
||||
QuitMessage string
|
||||
|
||||
// Configurable panic recovery for all handlers.
|
||||
// Defaults to logging an error, see LogPanic.
|
||||
Recover func(*Conn, *Line)
|
||||
|
||||
// Split PRIVMSGs, NOTICEs and CTCPs longer than SplitLen characters
|
||||
// over multiple lines. Default to 450 if not set.
|
||||
SplitLen int
|
||||
}
|
||||
|
||||
// NewConfig creates a Config struct containing sensible defaults.
|
||||
// It takes one required argument: the nick to use for the client.
|
||||
// Subsequent string arguments set the client's ident and "real"
|
||||
// name, but these are optional.
|
||||
func NewConfig(nick string, args ...string) *Config {
|
||||
cfg := &Config{
|
||||
Me: &state.Nick{Nick: nick},
|
||||
PingFreq: 3 * time.Minute,
|
||||
NewNick: func(s string) string { return s + "_" },
|
||||
Recover: (*Conn).LogPanic, // in dispatch.go
|
||||
SplitLen: defaultSplit,
|
||||
Timeout: 60 * time.Second,
|
||||
}
|
||||
cfg.Me.Ident = "goirc"
|
||||
if len(args) > 0 && args[0] != "" {
|
||||
cfg.Me.Ident = args[0]
|
||||
}
|
||||
cfg.Me.Name = "Powered by GoIRC"
|
||||
if len(args) > 1 && args[1] != "" {
|
||||
cfg.Me.Name = args[1]
|
||||
}
|
||||
cfg.Version = "Powered by GoIRC"
|
||||
cfg.QuitMessage = "GoBye!"
|
||||
return cfg
|
||||
}
|
||||
|
||||
// SimpleClient creates a new Conn, passing its arguments to NewConfig.
|
||||
// If you don't need to change any client options and just want to get
|
||||
// started quickly, this is a convenient shortcut.
|
||||
func SimpleClient(nick string, args ...string) *Conn {
|
||||
conn := Client(NewConfig(nick, args...))
|
||||
return conn
|
||||
}
|
||||
|
||||
// Client takes a Config struct and returns a new Conn ready to have
|
||||
// handlers added and connect to a server.
|
||||
func Client(cfg *Config) *Conn {
|
||||
if cfg == nil {
|
||||
cfg = NewConfig("__idiot__")
|
||||
}
|
||||
if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" {
|
||||
cfg.Me = &state.Nick{Nick: "__idiot__"}
|
||||
cfg.Me.Ident = "goirc"
|
||||
cfg.Me.Name = "Powered by GoIRC"
|
||||
}
|
||||
|
||||
dialer := new(net.Dialer)
|
||||
dialer.Timeout = cfg.Timeout
|
||||
if cfg.LocalAddr != "" {
|
||||
if !hasPort(cfg.LocalAddr) {
|
||||
cfg.LocalAddr += ":0"
|
||||
}
|
||||
|
||||
local, err := net.ResolveTCPAddr("tcp", cfg.LocalAddr)
|
||||
if err == nil {
|
||||
dialer.LocalAddr = local
|
||||
} else {
|
||||
logging.Error("irc.Client(): Cannot resolve local address %s: %s", cfg.LocalAddr, err)
|
||||
}
|
||||
}
|
||||
|
||||
conn := &Conn{
|
||||
cfg: cfg,
|
||||
dialer: dialer,
|
||||
intHandlers: handlerSet(),
|
||||
fgHandlers: handlerSet(),
|
||||
bgHandlers: handlerSet(),
|
||||
stRemovers: make([]Remover, 0, len(stHandlers)),
|
||||
lastsent: time.Now(),
|
||||
}
|
||||
conn.addIntHandlers()
|
||||
return conn
|
||||
}
|
||||
|
||||
// Connected returns true if the client is successfully connected to
|
||||
// an IRC server. It becomes true when the TCP connection is established,
|
||||
// and false again when the connection is closed.
|
||||
func (conn *Conn) Connected() bool {
|
||||
conn.mu.RLock()
|
||||
defer conn.mu.RUnlock()
|
||||
return conn.connected
|
||||
}
|
||||
|
||||
// Config returns a pointer to the Config struct used by the client.
|
||||
// Many of the elements of Config may be changed at any point to
|
||||
// affect client behaviour. To disable flood protection temporarily,
|
||||
// for example, a handler could do:
|
||||
//
|
||||
// conn.Config().Flood = true
|
||||
// // Send many lines to the IRC server, risking "excess flood"
|
||||
// conn.Config().Flood = false
|
||||
//
|
||||
func (conn *Conn) Config() *Config {
|
||||
return conn.cfg
|
||||
}
|
||||
|
||||
// Me returns a state.Nick that reflects the client's IRC nick at the
|
||||
// time it is called. If state tracking is enabled, this comes from
|
||||
// the tracker, otherwise it is equivalent to conn.cfg.Me.
|
||||
func (conn *Conn) Me() *state.Nick {
|
||||
if conn.st != nil {
|
||||
conn.cfg.Me = conn.st.Me()
|
||||
}
|
||||
return conn.cfg.Me
|
||||
}
|
||||
|
||||
// StateTracker returns the state tracker being used by the client,
|
||||
// if tracking is enabled, and nil otherwise.
|
||||
func (conn *Conn) StateTracker() state.Tracker {
|
||||
return conn.st
|
||||
}
|
||||
|
||||
// EnableStateTracking causes the client to track information about
|
||||
// all channels it is joined to, and all the nicks in those channels.
|
||||
// This can be rather handy for a number of bot-writing tasks. See
|
||||
// the state package for more details.
|
||||
//
|
||||
// NOTE: Calling this while connected to an IRC server may cause the
|
||||
// state tracker to become very confused all over STDERR if logging
|
||||
// is enabled. State tracking should enabled before connecting or
|
||||
// at a pinch while the client is not joined to any channels.
|
||||
func (conn *Conn) EnableStateTracking() {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
if conn.st == nil {
|
||||
n := conn.cfg.Me
|
||||
conn.st = state.NewTracker(n.Nick)
|
||||
conn.st.NickInfo(n.Nick, n.Ident, n.Host, n.Name)
|
||||
conn.cfg.Me = conn.st.Me()
|
||||
conn.addSTHandlers()
|
||||
}
|
||||
}
|
||||
|
||||
// DisableStateTracking causes the client to stop tracking information
|
||||
// about the channels and nicks it knows of. It will also wipe current
|
||||
// state from the state tracker.
|
||||
func (conn *Conn) DisableStateTracking() {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
if conn.st != nil {
|
||||
conn.cfg.Me = conn.st.Me()
|
||||
conn.delSTHandlers()
|
||||
conn.st.Wipe()
|
||||
conn.st = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Per-connection state initialisation.
|
||||
func (conn *Conn) initialise() {
|
||||
conn.io = nil
|
||||
conn.sock = nil
|
||||
conn.in = make(chan *Line, 32)
|
||||
conn.out = make(chan string, 32)
|
||||
conn.die = make(chan struct{})
|
||||
if conn.st != nil {
|
||||
conn.st.Wipe()
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectTo connects the IRC client to "host[:port]", which should be either
|
||||
// a hostname or an IP address, with an optional port. It sets the client's
|
||||
// Config.Server to host, Config.Pass to pass if one is provided, and then
|
||||
// calls Connect.
|
||||
func (conn *Conn) ConnectTo(host string, pass ...string) error {
|
||||
conn.cfg.Server = host
|
||||
if len(pass) > 0 {
|
||||
conn.cfg.Pass = pass[0]
|
||||
}
|
||||
return conn.Connect()
|
||||
}
|
||||
|
||||
// Connect connects the IRC client to the server configured in Config.Server.
|
||||
// To enable explicit SSL on the connection to the IRC server, set Config.SSL
|
||||
// to true before calling Connect(). The port will default to 6697 if SSL is
|
||||
// enabled, and 6667 otherwise.
|
||||
// To enable connecting via a proxy server, set Config.Proxy to the proxy URL
|
||||
// (example socks5://localhost:9000) before calling Connect().
|
||||
//
|
||||
// Upon successful connection, Connected will return true and a REGISTER event
|
||||
// will be fired. This is mostly for internal use; it is suggested that a
|
||||
// handler for the CONNECTED event is used to perform any initial client work
|
||||
// like joining channels and sending messages.
|
||||
func (conn *Conn) Connect() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
conn.initialise()
|
||||
|
||||
if conn.cfg.Server == "" {
|
||||
return fmt.Errorf("irc.Connect(): cfg.Server must be non-empty")
|
||||
}
|
||||
if conn.connected {
|
||||
return fmt.Errorf("irc.Connect(): Cannot connect to %s, already connected.", conn.cfg.Server)
|
||||
}
|
||||
|
||||
if !hasPort(conn.cfg.Server) {
|
||||
if conn.cfg.SSL {
|
||||
conn.cfg.Server = net.JoinHostPort(conn.cfg.Server, "6697")
|
||||
} else {
|
||||
conn.cfg.Server = net.JoinHostPort(conn.cfg.Server, "6667")
|
||||
}
|
||||
}
|
||||
|
||||
if conn.cfg.Proxy != "" {
|
||||
proxyURL, err := url.Parse(conn.cfg.Proxy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.proxyDialer, err = proxy.FromURL(proxyURL, conn.dialer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server)
|
||||
if s, err := conn.proxyDialer.Dial("tcp", conn.cfg.Server); err == nil {
|
||||
conn.sock = s
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logging.Info("irc.Connect(): Connecting to %s.", conn.cfg.Server)
|
||||
if s, err := conn.dialer.Dial("tcp", conn.cfg.Server); err == nil {
|
||||
conn.sock = s
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if conn.cfg.SSL {
|
||||
logging.Info("irc.Connect(): Performing SSL handshake.")
|
||||
s := tls.Client(conn.sock, conn.cfg.SSLConfig)
|
||||
if err := s.Handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
conn.sock = s
|
||||
}
|
||||
|
||||
conn.postConnect(true)
|
||||
conn.connected = true
|
||||
conn.dispatch(&Line{Cmd: REGISTER, Time: time.Now()})
|
||||
return nil
|
||||
}
|
||||
|
||||
// postConnect performs post-connection setup, for ease of testing.
|
||||
func (conn *Conn) postConnect(start bool) {
|
||||
conn.io = bufio.NewReadWriter(
|
||||
bufio.NewReader(conn.sock),
|
||||
bufio.NewWriter(conn.sock))
|
||||
if start {
|
||||
conn.wg.Add(3)
|
||||
go conn.send()
|
||||
go conn.recv()
|
||||
go conn.runLoop()
|
||||
if conn.cfg.PingFreq > 0 {
|
||||
conn.wg.Add(1)
|
||||
go conn.ping()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hasPort returns true if the string hostname has a :port suffix.
|
||||
// It was copied from net/http for great justice.
|
||||
func hasPort(s string) bool {
|
||||
return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
|
||||
}
|
||||
|
||||
// send is started as a goroutine after a connection is established.
|
||||
// It shuttles data from the output channel to write(), and is killed
|
||||
// when Conn.die is closed.
|
||||
func (conn *Conn) send() {
|
||||
for {
|
||||
select {
|
||||
case line := <-conn.out:
|
||||
if err := conn.write(line); err != nil {
|
||||
logging.Error("irc.send(): %s", err.Error())
|
||||
// We can't defer this, because Close() waits for it.
|
||||
conn.wg.Done()
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
case <-conn.die:
|
||||
// control channel closed, bail out
|
||||
conn.wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recv is started as a goroutine after a connection is established.
|
||||
// It receives "\r\n" terminated lines from the server, parses them into
|
||||
// Lines, and sends them to the input channel.
|
||||
func (conn *Conn) recv() {
|
||||
for {
|
||||
s, err := conn.io.ReadString('\n')
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
logging.Error("irc.recv(): %s", err.Error())
|
||||
}
|
||||
// We can't defer this, because Close() waits for it.
|
||||
conn.wg.Done()
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
s = strings.Trim(s, "\r\n")
|
||||
logging.Debug("<- %s", s)
|
||||
|
||||
if line := ParseLine(s); line != nil {
|
||||
line.Time = time.Now()
|
||||
conn.in <- line
|
||||
} else {
|
||||
logging.Warn("irc.recv(): problems parsing line:\n %s", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ping is started as a goroutine after a connection is established, as
|
||||
// long as Config.PingFreq >0. It pings the server every PingFreq seconds.
|
||||
func (conn *Conn) ping() {
|
||||
defer conn.wg.Done()
|
||||
tick := time.NewTicker(conn.cfg.PingFreq)
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
conn.Ping(fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||
case <-conn.die:
|
||||
// control channel closed, bail out
|
||||
tick.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runLoop is started as a goroutine after a connection is established.
|
||||
// It pulls Lines from the input channel and dispatches them to any
|
||||
// handlers that have been registered for that IRC verb.
|
||||
func (conn *Conn) runLoop() {
|
||||
defer conn.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case line := <-conn.in:
|
||||
conn.dispatch(line)
|
||||
case <-conn.die:
|
||||
// control channel closed, bail out
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write writes a \r\n terminated line of output to the connected server,
|
||||
// using Hybrid's algorithm to rate limit if conn.cfg.Flood is false.
|
||||
func (conn *Conn) write(line string) error {
|
||||
if !conn.cfg.Flood {
|
||||
if t := conn.rateLimit(len(line)); t != 0 {
|
||||
// sleep for the current line's time value before sending it
|
||||
logging.Info("irc.rateLimit(): Flood! Sleeping for %.2f secs.",
|
||||
t.Seconds())
|
||||
<-time.After(t)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := conn.io.WriteString(line + "\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := conn.io.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(line, "PASS") {
|
||||
line = "PASS **************"
|
||||
}
|
||||
logging.Debug("-> %s", line)
|
||||
return nil
|
||||
}
|
||||
|
||||
// rateLimit implements Hybrid's flood control algorithm for outgoing lines.
|
||||
func (conn *Conn) rateLimit(chars int) time.Duration {
|
||||
// Hybrid's algorithm allows for 2 seconds per line and an additional
|
||||
// 1/120 of a second per character on that line.
|
||||
linetime := 2*time.Second + time.Duration(chars)*time.Second/120
|
||||
elapsed := time.Now().Sub(conn.lastsent)
|
||||
if conn.badness += linetime - elapsed; conn.badness < 0 {
|
||||
// negative badness times are badness...
|
||||
conn.badness = 0
|
||||
}
|
||||
conn.lastsent = time.Now()
|
||||
// If we've sent more than 10 second's worth of lines according to the
|
||||
// calculation above, then we're at risk of "Excess Flood".
|
||||
if conn.badness > 10*time.Second {
|
||||
return linetime
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Close tears down all connection-related state. It is called when either
|
||||
// the sending or receiving goroutines encounter an error.
|
||||
// It may also be used to forcibly shut down the connection to the server.
|
||||
func (conn *Conn) Close() error {
|
||||
// Guard against double-call of Close() if we get an error in send()
|
||||
// as calling sock.Close() will cause recv() to receive EOF in readstring()
|
||||
conn.mu.Lock()
|
||||
if !conn.connected {
|
||||
conn.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
logging.Info("irc.Close(): Disconnected from server.")
|
||||
conn.connected = false
|
||||
err := conn.sock.Close()
|
||||
close(conn.die)
|
||||
// Drain both in and out channels to avoid a deadlock if the buffers
|
||||
// have filled. See TestSendDeadlockOnFullBuffer in connection_test.go.
|
||||
conn.drainIn()
|
||||
conn.drainOut()
|
||||
conn.wg.Wait()
|
||||
conn.mu.Unlock()
|
||||
// Dispatch after closing connection but before reinit
|
||||
// so event handlers can still access state information.
|
||||
conn.dispatch(&Line{Cmd: DISCONNECTED, Time: time.Now()})
|
||||
return err
|
||||
}
|
||||
|
||||
// drainIn sends all data buffered in conn.in to /dev/null.
|
||||
func (conn *Conn) drainIn() {
|
||||
for {
|
||||
select {
|
||||
case <-conn.in:
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// drainOut does the same for conn.out. Generics!
|
||||
func (conn *Conn) drainOut() {
|
||||
for {
|
||||
select {
|
||||
case <-conn.out:
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dumps a load of information about the current state of the connection to a
|
||||
// string for debugging state tracking and other such things.
|
||||
func (conn *Conn) String() string {
|
||||
str := "GoIRC Connection\n"
|
||||
str += "----------------\n\n"
|
||||
if conn.Connected() {
|
||||
str += "Connected to " + conn.cfg.Server + "\n\n"
|
||||
} else {
|
||||
str += "Not currently connected!\n\n"
|
||||
}
|
||||
str += conn.Me().String() + "\n"
|
||||
if conn.st != nil {
|
||||
str += conn.st.String() + "\n"
|
||||
}
|
||||
return str
|
||||
}
|
192
vendor/github.com/fluffle/goirc/client/dispatch.go
generated
vendored
Normal file
192
vendor/github.com/fluffle/goirc/client/dispatch.go
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/fluffle/goirc/logging"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Handlers are triggered on incoming Lines from the server, with the handler
|
||||
// "name" being equivalent to Line.Cmd. Read the RFCs for details on what
|
||||
// replies could come from the server. They'll generally be things like
|
||||
// "PRIVMSG", "JOIN", etc. but all the numeric replies are left as ascii
|
||||
// strings of digits like "332" (mainly because I really didn't feel like
|
||||
// putting massive constant tables in).
|
||||
//
|
||||
// Foreground handlers have a guarantee of protocol consistency: all the
|
||||
// handlers for one event will have finished before the handlers for the
|
||||
// next start processing. They are run in parallel but block the event
|
||||
// loop, so care should be taken to ensure these handlers are quick :-)
|
||||
//
|
||||
// Background handlers are run in parallel and do not block the event loop.
|
||||
// This is useful for things that may need to do significant work.
|
||||
type Handler interface {
|
||||
Handle(*Conn, *Line)
|
||||
}
|
||||
|
||||
// Removers allow for a handler that has been previously added to the client
|
||||
// to be removed.
|
||||
type Remover interface {
|
||||
Remove()
|
||||
}
|
||||
|
||||
// HandlerFunc allows a bare function with this signature to implement the
|
||||
// Handler interface. It is used by Conn.HandleFunc.
|
||||
type HandlerFunc func(*Conn, *Line)
|
||||
|
||||
func (hf HandlerFunc) Handle(conn *Conn, line *Line) {
|
||||
hf(conn, line)
|
||||
}
|
||||
|
||||
// Handlers are organised using a map of linked-lists, with each map
|
||||
// key representing an IRC verb or numeric, and the linked list values
|
||||
// being handlers that are executed in parallel when a Line from the
|
||||
// server with that verb or numeric arrives.
|
||||
type hSet struct {
|
||||
set map[string]*hList
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type hList struct {
|
||||
start, end *hNode
|
||||
}
|
||||
|
||||
// Storing the forward and backward links in the node allows O(1) removal.
|
||||
// This probably isn't strictly necessary but I think it's kinda nice.
|
||||
type hNode struct {
|
||||
next, prev *hNode
|
||||
set *hSet
|
||||
event string
|
||||
handler Handler
|
||||
}
|
||||
|
||||
// A hNode implements both Handler (with configurable panic recovery)...
|
||||
func (hn *hNode) Handle(conn *Conn, line *Line) {
|
||||
defer conn.cfg.Recover(conn, line)
|
||||
hn.handler.Handle(conn, line)
|
||||
}
|
||||
|
||||
// ... and Remover.
|
||||
func (hn *hNode) Remove() {
|
||||
hn.set.remove(hn)
|
||||
}
|
||||
|
||||
func handlerSet() *hSet {
|
||||
return &hSet{set: make(map[string]*hList)}
|
||||
}
|
||||
|
||||
// When a new Handler is added for an event, it is wrapped in a hNode and
|
||||
// returned as a Remover so the caller can remove it at a later time.
|
||||
func (hs *hSet) add(ev string, h Handler) Remover {
|
||||
hs.Lock()
|
||||
defer hs.Unlock()
|
||||
ev = strings.ToLower(ev)
|
||||
l, ok := hs.set[ev]
|
||||
if !ok {
|
||||
l = &hList{}
|
||||
}
|
||||
hn := &hNode{
|
||||
set: hs,
|
||||
event: ev,
|
||||
handler: h,
|
||||
}
|
||||
if !ok {
|
||||
l.start = hn
|
||||
} else {
|
||||
hn.prev = l.end
|
||||
l.end.next = hn
|
||||
}
|
||||
l.end = hn
|
||||
hs.set[ev] = l
|
||||
return hn
|
||||
}
|
||||
|
||||
func (hs *hSet) remove(hn *hNode) {
|
||||
hs.Lock()
|
||||
defer hs.Unlock()
|
||||
l, ok := hs.set[hn.event]
|
||||
if !ok {
|
||||
logging.Error("Removing node for unknown event '%s'", hn.event)
|
||||
return
|
||||
}
|
||||
if hn.next == nil {
|
||||
l.end = hn.prev
|
||||
} else {
|
||||
hn.next.prev = hn.prev
|
||||
}
|
||||
if hn.prev == nil {
|
||||
l.start = hn.next
|
||||
} else {
|
||||
hn.prev.next = hn.next
|
||||
}
|
||||
hn.next = nil
|
||||
hn.prev = nil
|
||||
hn.set = nil
|
||||
if l.start == nil || l.end == nil {
|
||||
delete(hs.set, hn.event)
|
||||
}
|
||||
}
|
||||
|
||||
func (hs *hSet) dispatch(conn *Conn, line *Line) {
|
||||
hs.RLock()
|
||||
defer hs.RUnlock()
|
||||
ev := strings.ToLower(line.Cmd)
|
||||
list, ok := hs.set[ev]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
wg := &sync.WaitGroup{}
|
||||
for hn := list.start; hn != nil; hn = hn.next {
|
||||
wg.Add(1)
|
||||
go func(hn *hNode) {
|
||||
hn.Handle(conn, line.Copy())
|
||||
wg.Done()
|
||||
}(hn)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Handle adds the provided handler to the foreground set for the named event.
|
||||
// It will return a Remover that allows that handler to be removed again.
|
||||
func (conn *Conn) Handle(name string, h Handler) Remover {
|
||||
return conn.fgHandlers.add(name, h)
|
||||
}
|
||||
|
||||
// HandleBG adds the provided handler to the background set for the named
|
||||
// event. It may go away in the future.
|
||||
// It will return a Remover that allows that handler to be removed again.
|
||||
func (conn *Conn) HandleBG(name string, h Handler) Remover {
|
||||
return conn.bgHandlers.add(name, h)
|
||||
}
|
||||
|
||||
func (conn *Conn) handle(name string, h Handler) Remover {
|
||||
return conn.intHandlers.add(name, h)
|
||||
}
|
||||
|
||||
// HandleFunc adds the provided function as a handler in the foreground set
|
||||
// for the named event.
|
||||
// It will return a Remover that allows that handler to be removed again.
|
||||
func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
|
||||
return conn.Handle(name, hf)
|
||||
}
|
||||
|
||||
func (conn *Conn) dispatch(line *Line) {
|
||||
// We run the internal handlers first, including all state tracking ones.
|
||||
// This ensures that user-supplied handlers that use the tracker have a
|
||||
// consistent view of the connection state in handlers that mutate it.
|
||||
conn.intHandlers.dispatch(conn, line)
|
||||
go conn.bgHandlers.dispatch(conn, line)
|
||||
conn.fgHandlers.dispatch(conn, line)
|
||||
}
|
||||
|
||||
// LogPanic is used as the default panic catcher for the client. If, like me,
|
||||
// you are not good with computer, and you'd prefer your bot not to vanish into
|
||||
// the ether whenever you make unfortunate programming mistakes, you may find
|
||||
// this useful: it will recover panics from handler code and log the errors.
|
||||
func (conn *Conn) LogPanic(line *Line) {
|
||||
if err := recover(); err != nil {
|
||||
_, f, l, _ := runtime.Caller(2)
|
||||
logging.Error("%s:%d: panic: %v", f, l, err)
|
||||
}
|
||||
}
|
34
vendor/github.com/fluffle/goirc/client/doc.go
generated
vendored
Normal file
34
vendor/github.com/fluffle/goirc/client/doc.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Package client implements an IRC client. It handles protocol basics
|
||||
// such as initial connection and responding to server PINGs, and has
|
||||
// optional state tracking support which will keep tabs on every nick
|
||||
// present in the same channels as the client. Other features include
|
||||
// SSL support, automatic splitting of long lines, and panic recovery
|
||||
// for handlers.
|
||||
//
|
||||
// Incoming IRC messages are parsed into client.Line structs and trigger
|
||||
// events based on the IRC verb (e.g. PRIVMSG) of the message. Handlers
|
||||
// for these events conform to the client.Handler interface; a HandlerFunc
|
||||
// type to wrap bare functions is provided a-la the net/http package.
|
||||
//
|
||||
// Creating a client, adding a handler and connecting to a server looks
|
||||
// soemthing like this, for the simple case:
|
||||
//
|
||||
// // Create a new client, which will connect with the nick "myNick"
|
||||
// irc := client.SimpleClient("myNick")
|
||||
//
|
||||
// // Add a handler that waits for the "disconnected" event and
|
||||
// // closes a channel to signal everything is done.
|
||||
// disconnected := make(chan struct{})
|
||||
// c.HandleFunc("disconnected", func(c *client.Conn, l *client.Line) {
|
||||
// close(disconnected)
|
||||
// })
|
||||
//
|
||||
// // Connect to an IRC server.
|
||||
// if err := c.ConnectTo("irc.freenode.net"); err != nil {
|
||||
// log.Fatalf("Connection error: %v\n", err)
|
||||
// }
|
||||
//
|
||||
// // Wait for disconnection.
|
||||
// <-disconnected
|
||||
//
|
||||
package client
|
105
vendor/github.com/fluffle/goirc/client/handlers.go
generated
vendored
Normal file
105
vendor/github.com/fluffle/goirc/client/handlers.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
package client
|
||||
|
||||
// this file contains the basic set of event handlers
|
||||
// to manage tracking an irc connection etc.
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// sets up the internal event handlers to do essential IRC protocol things
|
||||
var intHandlers = map[string]HandlerFunc{
|
||||
REGISTER: (*Conn).h_REGISTER,
|
||||
"001": (*Conn).h_001,
|
||||
"433": (*Conn).h_433,
|
||||
CTCP: (*Conn).h_CTCP,
|
||||
NICK: (*Conn).h_NICK,
|
||||
PING: (*Conn).h_PING,
|
||||
}
|
||||
|
||||
func (conn *Conn) addIntHandlers() {
|
||||
for n, h := range intHandlers {
|
||||
// internal handlers are essential for the IRC client
|
||||
// to function, so we don't save their Removers here
|
||||
conn.handle(n, h)
|
||||
}
|
||||
}
|
||||
|
||||
// Basic ping/pong handler
|
||||
func (conn *Conn) h_PING(line *Line) {
|
||||
conn.Pong(line.Args[0])
|
||||
}
|
||||
|
||||
// Handler for initial registration with server once tcp connection is made.
|
||||
func (conn *Conn) h_REGISTER(line *Line) {
|
||||
if conn.cfg.Pass != "" {
|
||||
conn.Pass(conn.cfg.Pass)
|
||||
}
|
||||
conn.Nick(conn.cfg.Me.Nick)
|
||||
conn.User(conn.cfg.Me.Ident, conn.cfg.Me.Name)
|
||||
}
|
||||
|
||||
// Handler to trigger a CONNECTED event on receipt of numeric 001
|
||||
func (conn *Conn) h_001(line *Line) {
|
||||
// we're connected!
|
||||
conn.dispatch(&Line{Cmd: CONNECTED, Time: time.Now()})
|
||||
// and we're being given our hostname (from the server's perspective)
|
||||
t := line.Args[len(line.Args)-1]
|
||||
if idx := strings.LastIndex(t, " "); idx != -1 {
|
||||
t = t[idx+1:]
|
||||
if idx = strings.Index(t, "@"); idx != -1 {
|
||||
if conn.st != nil {
|
||||
me := conn.Me()
|
||||
conn.st.NickInfo(me.Nick, me.Ident, t[idx+1:], me.Name)
|
||||
} else {
|
||||
conn.cfg.Me.Host = t[idx+1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: do we need 005 protocol support message parsing here?
|
||||
// probably in the future, but I can't quite be arsed yet.
|
||||
/*
|
||||
:irc.pl0rt.org 005 GoTest CMDS=KNOCK,MAP,DCCALLOW,USERIP UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=20 CHANLIMIT=#:20 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 :are supported by this server
|
||||
:irc.pl0rt.org 005 GoTest MAXTARGETS=20 WALLCHOPS WATCH=128 WATCHOPTS=A SILENCE=15 MODES=12 CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beI,kfL,lj,psmntirRcOAQKVCuzNSMT NETWORK=bb101.net CASEMAPPING=ascii EXTBAN=~,cqnr ELIST=MNUCT :are supported by this server
|
||||
:irc.pl0rt.org 005 GoTest STATUSMSG=~&@%+ EXCEPTS INVEX :are supported by this server
|
||||
*/
|
||||
|
||||
// Handler to deal with "433 :Nickname already in use"
|
||||
func (conn *Conn) h_433(line *Line) {
|
||||
// Args[1] is the new nick we were attempting to acquire
|
||||
me := conn.Me()
|
||||
neu := conn.cfg.NewNick(line.Args[1])
|
||||
conn.Nick(neu)
|
||||
if !line.argslen(1) {
|
||||
return
|
||||
}
|
||||
// if this is happening before we're properly connected (i.e. the nick
|
||||
// we sent in the initial NICK command is in use) we will not receive
|
||||
// a NICK message to confirm our change of nick, so ReNick here...
|
||||
if line.Args[1] == me.Nick {
|
||||
if conn.st != nil {
|
||||
conn.cfg.Me = conn.st.ReNick(me.Nick, neu)
|
||||
} else {
|
||||
conn.cfg.Me.Nick = neu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle VERSION requests and CTCP PING
|
||||
func (conn *Conn) h_CTCP(line *Line) {
|
||||
if line.Args[0] == VERSION {
|
||||
conn.CtcpReply(line.Nick, VERSION, conn.cfg.Version)
|
||||
} else if line.Args[0] == PING && line.argslen(2) {
|
||||
conn.CtcpReply(line.Nick, PING, line.Args[2])
|
||||
}
|
||||
}
|
||||
|
||||
// Handle updating our own NICK if we're not using the state tracker
|
||||
func (conn *Conn) h_NICK(line *Line) {
|
||||
if conn.st == nil && line.Nick == conn.cfg.Me.Nick {
|
||||
conn.cfg.Me.Nick = line.Args[0]
|
||||
}
|
||||
}
|
216
vendor/github.com/fluffle/goirc/client/line.go
generated
vendored
Normal file
216
vendor/github.com/fluffle/goirc/client/line.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fluffle/goirc/logging"
|
||||
)
|
||||
|
||||
var tagsReplacer = strings.NewReplacer("\\:", ";", "\\s", " ", "\\r", "\r", "\\n", "\n")
|
||||
|
||||
// We parse an incoming line into this struct. Line.Cmd is used as the trigger
|
||||
// name for incoming event handlers and is the IRC verb, the first sequence
|
||||
// of non-whitespace characters after ":nick!user@host", e.g. PRIVMSG.
|
||||
// Raw =~ ":nick!user@host cmd args[] :text"
|
||||
// Src == "nick!user@host"
|
||||
// Cmd == e.g. PRIVMSG, 332
|
||||
type Line struct {
|
||||
Tags map[string]string
|
||||
Nick, Ident, Host, Src string
|
||||
Cmd, Raw string
|
||||
Args []string
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the Line.
|
||||
func (l *Line) Copy() *Line {
|
||||
nl := *l
|
||||
nl.Args = make([]string, len(l.Args))
|
||||
copy(nl.Args, l.Args)
|
||||
if l.Tags != nil {
|
||||
nl.Tags = make(map[string]string)
|
||||
for k, v := range l.Tags {
|
||||
nl.Tags[k] = v
|
||||
}
|
||||
}
|
||||
return &nl
|
||||
}
|
||||
|
||||
// Text returns the contents of the text portion of a line. This only really
|
||||
// makes sense for lines with a :text part, but there are a lot of them.
|
||||
func (line *Line) Text() string {
|
||||
if len(line.Args) > 0 {
|
||||
return line.Args[len(line.Args)-1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Target returns the contextual target of the line, usually the first Arg
|
||||
// for the IRC verb. If the line was broadcast from a channel, the target
|
||||
// will be that channel. If the line was sent directly by a user, the target
|
||||
// will be that user.
|
||||
func (line *Line) Target() string {
|
||||
// TODO(fluffle): Add 005 CHANTYPES parsing for this?
|
||||
switch line.Cmd {
|
||||
case PRIVMSG, NOTICE, ACTION:
|
||||
if !line.Public() {
|
||||
return line.Nick
|
||||
}
|
||||
case CTCP, CTCPREPLY:
|
||||
if !line.Public() {
|
||||
return line.Nick
|
||||
}
|
||||
return line.Args[1]
|
||||
}
|
||||
if len(line.Args) > 0 {
|
||||
return line.Args[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Public returns true if the line is the result of an IRC user sending
|
||||
// a message to a channel the client has joined instead of directly
|
||||
// to the client.
|
||||
//
|
||||
// NOTE: This is very permissive, allowing all 4 RFC channel types even if
|
||||
// your server doesn't technically support them.
|
||||
func (line *Line) Public() bool {
|
||||
switch line.Cmd {
|
||||
case PRIVMSG, NOTICE, ACTION:
|
||||
switch line.Args[0][0] {
|
||||
case '#', '&', '+', '!':
|
||||
return true
|
||||
}
|
||||
case CTCP, CTCPREPLY:
|
||||
// CTCP prepends the CTCP verb to line.Args, thus for the message
|
||||
// :nick!user@host PRIVMSG #foo :\001BAR baz\001
|
||||
// line.Args contains: []string{"BAR", "#foo", "baz"}
|
||||
// TODO(fluffle): Arguably this is broken, and we should have
|
||||
// line.Args containing: []string{"#foo", "BAR", "baz"}
|
||||
// ... OR change conn.Ctcp()'s argument order to be consistent.
|
||||
switch line.Args[1][0] {
|
||||
case '#', '&', '+', '!':
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ParseLine creates a Line from an incoming message from the IRC server.
|
||||
//
|
||||
// It contains special casing for CTCP messages, most notably CTCP ACTION.
|
||||
// All CTCP messages have the \001 bytes stripped from the message and the
|
||||
// CTCP command separated from any subsequent text. Then, CTCP ACTIONs are
|
||||
// rewritten such that Line.Cmd == ACTION. Other CTCP messages have Cmd
|
||||
// set to CTCP or CTCPREPLY, and the CTCP command prepended to line.Args.
|
||||
//
|
||||
// ParseLine also parses IRCv3 tags, if received. If a line does not have
|
||||
// the tags section, Line.Tags will be nil. Tags are optional, and will
|
||||
// only be included after the correct CAP command.
|
||||
//
|
||||
// http://ircv3.net/specs/core/capability-negotiation-3.1.html
|
||||
// http://ircv3.net/specs/core/message-tags-3.2.html
|
||||
func ParseLine(s string) *Line {
|
||||
line := &Line{Raw: s}
|
||||
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s[0] == '@' {
|
||||
var rawTags string
|
||||
line.Tags = make(map[string]string)
|
||||
if idx := strings.Index(s, " "); idx != -1 {
|
||||
rawTags, s = s[1:idx], s[idx+1:]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ; is represented as \: in a tag, so it's safe to split on ;
|
||||
for _, tag := range strings.Split(rawTags, ";") {
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
pair := strings.SplitN(tagsReplacer.Replace(tag), "=", 2)
|
||||
if len(pair) < 2 {
|
||||
line.Tags[tag] = ""
|
||||
} else {
|
||||
line.Tags[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s[0] == ':' {
|
||||
// remove a source and parse it
|
||||
if idx := strings.Index(s, " "); idx != -1 {
|
||||
line.Src, s = s[1:idx], s[idx+1:]
|
||||
} else {
|
||||
// pretty sure we shouldn't get here ...
|
||||
return nil
|
||||
}
|
||||
|
||||
// src can be the hostname of the irc server or a nick!user@host
|
||||
line.Host = line.Src
|
||||
nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@")
|
||||
if uidx != -1 && nidx != -1 {
|
||||
line.Nick = line.Src[:nidx]
|
||||
line.Ident = line.Src[nidx+1 : uidx]
|
||||
line.Host = line.Src[uidx+1:]
|
||||
}
|
||||
}
|
||||
|
||||
// now we're here, we've parsed a :nick!user@host or :server off
|
||||
// s should contain "cmd args[] :text"
|
||||
args := strings.SplitN(s, " :", 2)
|
||||
if len(args) > 1 {
|
||||
args = append(strings.Fields(args[0]), args[1])
|
||||
} else {
|
||||
args = strings.Fields(args[0])
|
||||
}
|
||||
line.Cmd = strings.ToUpper(args[0])
|
||||
if len(args) > 1 {
|
||||
line.Args = args[1:]
|
||||
}
|
||||
|
||||
// So, I think CTCP and (in particular) CTCP ACTION are better handled as
|
||||
// separate events as opposed to forcing people to have gargantuan
|
||||
// handlers to cope with the possibilities.
|
||||
if (line.Cmd == PRIVMSG || line.Cmd == NOTICE) &&
|
||||
len(line.Args[1]) > 2 &&
|
||||
strings.HasPrefix(line.Args[1], "\001") &&
|
||||
strings.HasSuffix(line.Args[1], "\001") {
|
||||
// WOO, it's a CTCP message
|
||||
t := strings.SplitN(strings.Trim(line.Args[1], "\001"), " ", 2)
|
||||
if len(t) > 1 {
|
||||
// Replace the line with the unwrapped CTCP
|
||||
line.Args[1] = t[1]
|
||||
}
|
||||
if c := strings.ToUpper(t[0]); c == ACTION && line.Cmd == PRIVMSG {
|
||||
// make a CTCP ACTION it's own event a-la PRIVMSG
|
||||
line.Cmd = c
|
||||
} else {
|
||||
// otherwise, dispatch a generic CTCP/CTCPREPLY event that
|
||||
// contains the type of CTCP in line.Args[0]
|
||||
if line.Cmd == PRIVMSG {
|
||||
line.Cmd = CTCP
|
||||
} else {
|
||||
line.Cmd = CTCPREPLY
|
||||
}
|
||||
line.Args = append([]string{c}, line.Args...)
|
||||
}
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
func (line *Line) argslen(minlen int) bool {
|
||||
pc, _, _, _ := runtime.Caller(1)
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if len(line.Args) <= minlen {
|
||||
logging.Warn("%s: too few arguments: %s", fn.Name(), strings.Join(line.Args, " "))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
262
vendor/github.com/fluffle/goirc/client/state_handlers.go
generated
vendored
Normal file
262
vendor/github.com/fluffle/goirc/client/state_handlers.go
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
package client
|
||||
|
||||
// this file contains the extra set of event handlers
|
||||
// to manage tracking state for an IRC connection
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fluffle/goirc/logging"
|
||||
)
|
||||
|
||||
var stHandlers = map[string]HandlerFunc{
|
||||
"JOIN": (*Conn).h_JOIN,
|
||||
"KICK": (*Conn).h_KICK,
|
||||
"MODE": (*Conn).h_MODE,
|
||||
"NICK": (*Conn).h_STNICK,
|
||||
"PART": (*Conn).h_PART,
|
||||
"QUIT": (*Conn).h_QUIT,
|
||||
"TOPIC": (*Conn).h_TOPIC,
|
||||
"311": (*Conn).h_311,
|
||||
"324": (*Conn).h_324,
|
||||
"332": (*Conn).h_332,
|
||||
"352": (*Conn).h_352,
|
||||
"353": (*Conn).h_353,
|
||||
"671": (*Conn).h_671,
|
||||
}
|
||||
|
||||
func (conn *Conn) addSTHandlers() {
|
||||
for n, h := range stHandlers {
|
||||
conn.stRemovers = append(conn.stRemovers, conn.handle(n, h))
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) delSTHandlers() {
|
||||
for _, h := range conn.stRemovers {
|
||||
h.Remove()
|
||||
}
|
||||
conn.stRemovers = conn.stRemovers[:0]
|
||||
}
|
||||
|
||||
// Handle NICK messages that need to update the state tracker
|
||||
func (conn *Conn) h_STNICK(line *Line) {
|
||||
// all nicks should be handled the same way, our own included
|
||||
conn.st.ReNick(line.Nick, line.Args[0])
|
||||
}
|
||||
|
||||
// Handle JOINs to channels to maintain state
|
||||
func (conn *Conn) h_JOIN(line *Line) {
|
||||
ch := conn.st.GetChannel(line.Args[0])
|
||||
nk := conn.st.GetNick(line.Nick)
|
||||
if ch == nil {
|
||||
// first we've seen of this channel, so should be us joining it
|
||||
// NOTE this will also take care of nk == nil && ch == nil
|
||||
if !conn.Me().Equals(nk) {
|
||||
logging.Warn("irc.JOIN(): JOIN to unknown channel %s received "+
|
||||
"from (non-me) nick %s", line.Args[0], line.Nick)
|
||||
return
|
||||
}
|
||||
conn.st.NewChannel(line.Args[0])
|
||||
// since we don't know much about this channel, ask server for info
|
||||
// we get the channel users automatically in 353 and the channel
|
||||
// topic in 332 on join, so we just need to get the modes
|
||||
conn.Mode(line.Args[0])
|
||||
// sending a WHO for the channel is MUCH more efficient than
|
||||
// triggering a WHOIS on every nick from the 353 handler
|
||||
conn.Who(line.Args[0])
|
||||
}
|
||||
if nk == nil {
|
||||
// this is the first we've seen of this nick
|
||||
conn.st.NewNick(line.Nick)
|
||||
conn.st.NickInfo(line.Nick, line.Ident, line.Host, "")
|
||||
// since we don't know much about this nick, ask server for info
|
||||
conn.Who(line.Nick)
|
||||
}
|
||||
// this takes care of both nick and channel linking \o/
|
||||
conn.st.Associate(line.Args[0], line.Nick)
|
||||
}
|
||||
|
||||
// Handle PARTs from channels to maintain state
|
||||
func (conn *Conn) h_PART(line *Line) {
|
||||
conn.st.Dissociate(line.Args[0], line.Nick)
|
||||
}
|
||||
|
||||
// Handle KICKs from channels to maintain state
|
||||
func (conn *Conn) h_KICK(line *Line) {
|
||||
if !line.argslen(1) {
|
||||
return
|
||||
}
|
||||
// XXX: this won't handle autorejoining channels on KICK
|
||||
// it's trivial to do this in a seperate handler...
|
||||
conn.st.Dissociate(line.Args[0], line.Args[1])
|
||||
}
|
||||
|
||||
// Handle other people's QUITs
|
||||
func (conn *Conn) h_QUIT(line *Line) {
|
||||
conn.st.DelNick(line.Nick)
|
||||
}
|
||||
|
||||
// Handle MODE changes for channels we know about (and our nick personally)
|
||||
func (conn *Conn) h_MODE(line *Line) {
|
||||
if !line.argslen(1) {
|
||||
return
|
||||
}
|
||||
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
|
||||
// channel modes first
|
||||
conn.st.ChannelModes(line.Args[0], line.Args[1], line.Args[2:]...)
|
||||
} else if nk := conn.st.GetNick(line.Args[0]); nk != nil {
|
||||
// nick mode change, should be us
|
||||
if !conn.Me().Equals(nk) {
|
||||
logging.Warn("irc.MODE(): recieved MODE %s for (non-me) nick %s",
|
||||
line.Args[1], line.Args[0])
|
||||
return
|
||||
}
|
||||
conn.st.NickModes(line.Args[0], line.Args[1])
|
||||
} else {
|
||||
logging.Warn("irc.MODE(): not sure what to do with MODE %s",
|
||||
strings.Join(line.Args, " "))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle TOPIC changes for channels
|
||||
func (conn *Conn) h_TOPIC(line *Line) {
|
||||
if !line.argslen(1) {
|
||||
return
|
||||
}
|
||||
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
|
||||
conn.st.Topic(line.Args[0], line.Args[1])
|
||||
} else {
|
||||
logging.Warn("irc.TOPIC(): topic change on unknown channel %s",
|
||||
line.Args[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 311 whois reply
|
||||
func (conn *Conn) h_311(line *Line) {
|
||||
if !line.argslen(5) {
|
||||
return
|
||||
}
|
||||
if nk := conn.st.GetNick(line.Args[1]); (nk != nil) && !conn.Me().Equals(nk) {
|
||||
conn.st.NickInfo(line.Args[1], line.Args[2], line.Args[3], line.Args[5])
|
||||
} else {
|
||||
logging.Warn("irc.311(): received WHOIS info for unknown nick %s",
|
||||
line.Args[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 324 mode reply
|
||||
func (conn *Conn) h_324(line *Line) {
|
||||
if !line.argslen(2) {
|
||||
return
|
||||
}
|
||||
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
|
||||
conn.st.ChannelModes(line.Args[1], line.Args[2], line.Args[3:]...)
|
||||
} else {
|
||||
logging.Warn("irc.324(): received MODE settings for unknown channel %s",
|
||||
line.Args[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 332 topic reply on join to channel
|
||||
func (conn *Conn) h_332(line *Line) {
|
||||
if !line.argslen(2) {
|
||||
return
|
||||
}
|
||||
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
|
||||
conn.st.Topic(line.Args[1], line.Args[2])
|
||||
} else {
|
||||
logging.Warn("irc.332(): received TOPIC value for unknown channel %s",
|
||||
line.Args[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 352 who reply
|
||||
func (conn *Conn) h_352(line *Line) {
|
||||
if !line.argslen(5) {
|
||||
return
|
||||
}
|
||||
nk := conn.st.GetNick(line.Args[5])
|
||||
if nk == nil {
|
||||
logging.Warn("irc.352(): received WHO reply for unknown nick %s",
|
||||
line.Args[5])
|
||||
return
|
||||
}
|
||||
if conn.Me().Equals(nk) {
|
||||
return
|
||||
}
|
||||
// XXX: do we care about the actual server the nick is on?
|
||||
// or the hop count to this server?
|
||||
// last arg contains "<hop count> <real name>"
|
||||
a := strings.SplitN(line.Args[len(line.Args)-1], " ", 2)
|
||||
conn.st.NickInfo(nk.Nick, line.Args[2], line.Args[3], a[1])
|
||||
if !line.argslen(6) {
|
||||
return
|
||||
}
|
||||
if idx := strings.Index(line.Args[6], "*"); idx != -1 {
|
||||
conn.st.NickModes(nk.Nick, "+o")
|
||||
}
|
||||
if idx := strings.Index(line.Args[6], "B"); idx != -1 {
|
||||
conn.st.NickModes(nk.Nick, "+B")
|
||||
}
|
||||
if idx := strings.Index(line.Args[6], "H"); idx != -1 {
|
||||
conn.st.NickModes(nk.Nick, "+i")
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 353 names reply
|
||||
func (conn *Conn) h_353(line *Line) {
|
||||
if !line.argslen(2) {
|
||||
return
|
||||
}
|
||||
if ch := conn.st.GetChannel(line.Args[2]); ch != nil {
|
||||
nicks := strings.Split(line.Args[len(line.Args)-1], " ")
|
||||
for _, nick := range nicks {
|
||||
// UnrealIRCd's coders are lazy and leave a trailing space
|
||||
if nick == "" {
|
||||
continue
|
||||
}
|
||||
switch c := nick[0]; c {
|
||||
case '~', '&', '@', '%', '+':
|
||||
nick = nick[1:]
|
||||
fallthrough
|
||||
default:
|
||||
if conn.st.GetNick(nick) == nil {
|
||||
// we don't know this nick yet!
|
||||
conn.st.NewNick(nick)
|
||||
}
|
||||
if _, ok := conn.st.IsOn(ch.Name, nick); !ok {
|
||||
// This nick isn't associated with this channel yet!
|
||||
conn.st.Associate(ch.Name, nick)
|
||||
}
|
||||
switch c {
|
||||
case '~':
|
||||
conn.st.ChannelModes(ch.Name, "+q", nick)
|
||||
case '&':
|
||||
conn.st.ChannelModes(ch.Name, "+a", nick)
|
||||
case '@':
|
||||
conn.st.ChannelModes(ch.Name, "+o", nick)
|
||||
case '%':
|
||||
conn.st.ChannelModes(ch.Name, "+h", nick)
|
||||
case '+':
|
||||
conn.st.ChannelModes(ch.Name, "+v", nick)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logging.Warn("irc.353(): received NAMES list for unknown channel %s",
|
||||
line.Args[2])
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 671 whois reply (nick connected via SSL)
|
||||
func (conn *Conn) h_671(line *Line) {
|
||||
if !line.argslen(1) {
|
||||
return
|
||||
}
|
||||
if nk := conn.st.GetNick(line.Args[1]); nk != nil {
|
||||
conn.st.NickModes(nk.Nick, "+z")
|
||||
} else {
|
||||
logging.Warn("irc.671(): received WHOIS SSL info for unknown nick %s",
|
||||
line.Args[1])
|
||||
}
|
||||
}
|
32
vendor/github.com/fluffle/goirc/logging/glog/glog.go
generated
vendored
Normal file
32
vendor/github.com/fluffle/goirc/logging/glog/glog.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package glog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"github.com/fluffle/goirc/logging"
|
||||
)
|
||||
|
||||
// Simple adapter to utilise Google's GLog package with goirc.
|
||||
// Just import this package alongside goirc/client and call
|
||||
// glog.Init() in your main() to set things up.
|
||||
type GLogger struct{}
|
||||
|
||||
func (gl GLogger) Debug(f string, a ...interface{}) {
|
||||
// GLog doesn't have a "Debug" level, so use V(2) instead.
|
||||
if glog.V(2) {
|
||||
glog.InfoDepth(3, fmt.Sprintf(f, a...))
|
||||
}
|
||||
}
|
||||
func (gl GLogger) Info(f string, a ...interface{}) {
|
||||
glog.InfoDepth(3, fmt.Sprintf(f, a...))
|
||||
}
|
||||
func (gl GLogger) Warn(f string, a ...interface{}) {
|
||||
glog.WarningDepth(3, fmt.Sprintf(f, a...))
|
||||
}
|
||||
func (gl GLogger) Error(f string, a ...interface{}) {
|
||||
glog.ErrorDepth(3, fmt.Sprintf(f, a...))
|
||||
}
|
||||
|
||||
func Init() {
|
||||
logging.SetLogger(GLogger{})
|
||||
}
|
43
vendor/github.com/fluffle/goirc/logging/logging.go
generated
vendored
Normal file
43
vendor/github.com/fluffle/goirc/logging/logging.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package logging
|
||||
|
||||
// The IRC client will log things using these methods
|
||||
type Logger interface {
|
||||
// Debug logging of raw socket comms to/from server.
|
||||
Debug(format string, args ...interface{})
|
||||
// Informational logging about client behaviour.
|
||||
Info(format string, args ...interface{})
|
||||
// Warnings of inconsistent or unexpected data, mostly
|
||||
// related to state tracking of IRC nicks/chans.
|
||||
Warn(format string, args ...interface{})
|
||||
// Errors, mostly to do with network communication.
|
||||
Error(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// By default we do no logging. Logging is enabled or disabled
|
||||
// at the package level, since I'm lazy and re-re-reorganising
|
||||
// my code to pass a per-client-struct Logger around to all the
|
||||
// state objects is a pain in the arse.
|
||||
var logger Logger = nullLogger{}
|
||||
|
||||
// SetLogger sets the internal goirc Logger to l. If l is nil,
|
||||
// a dummy logger that does nothing is installed instead.
|
||||
func SetLogger(l Logger) {
|
||||
if l == nil {
|
||||
logger = nullLogger{}
|
||||
} else {
|
||||
logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// A nullLogger does nothing while fulfilling Logger.
|
||||
type nullLogger struct{}
|
||||
func (nl nullLogger) Debug(f string, a ...interface{}) {}
|
||||
func (nl nullLogger) Info(f string, a ...interface{}) {}
|
||||
func (nl nullLogger) Warn(f string, a ...interface{}) {}
|
||||
func (nl nullLogger) Error(f string, a ...interface{}) {}
|
||||
|
||||
// Shim functions so that the package can be used directly
|
||||
func Debug(f string, a ...interface{}) { logger.Debug(f, a...) }
|
||||
func Info(f string, a ...interface{}) { logger.Info(f, a...) }
|
||||
func Warn(f string, a ...interface{}) { logger.Warn(f, a...) }
|
||||
func Error(f string, a ...interface{}) { logger.Error(f, a...) }
|
350
vendor/github.com/fluffle/goirc/state/channel.go
generated
vendored
Normal file
350
vendor/github.com/fluffle/goirc/state/channel.go
generated
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/fluffle/goirc/logging"
|
||||
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A Channel is returned from the state tracker and contains
|
||||
// a copy of the channel state at a particular time.
|
||||
type Channel struct {
|
||||
Name, Topic string
|
||||
Modes *ChanMode
|
||||
Nicks map[string]*ChanPrivs
|
||||
}
|
||||
|
||||
// Internal bookkeeping struct for channels.
|
||||
type channel struct {
|
||||
name, topic string
|
||||
modes *ChanMode
|
||||
lookup map[string]*nick
|
||||
nicks map[*nick]*ChanPrivs
|
||||
}
|
||||
|
||||
// A struct representing the modes of an IRC Channel
|
||||
// (the ones we care about, at least).
|
||||
// http://www.unrealircd.com/files/docs/unreal32docs.html#userchannelmodes
|
||||
type ChanMode struct {
|
||||
// MODE +p, +s, +t, +n, +m
|
||||
Private, Secret, ProtectedTopic, NoExternalMsg, Moderated bool
|
||||
|
||||
// MODE +i, +O, +z
|
||||
InviteOnly, OperOnly, SSLOnly bool
|
||||
|
||||
// MODE +r, +Z
|
||||
Registered, AllSSL bool
|
||||
|
||||
// MODE +k
|
||||
Key string
|
||||
|
||||
// MODE +l
|
||||
Limit int
|
||||
}
|
||||
|
||||
// A struct representing the modes a Nick can have on a Channel
|
||||
type ChanPrivs struct {
|
||||
// MODE +q, +a, +o, +h, +v
|
||||
Owner, Admin, Op, HalfOp, Voice bool
|
||||
}
|
||||
|
||||
// Map ChanMode fields to IRC mode characters
|
||||
var StringToChanMode = map[string]string{}
|
||||
var ChanModeToString = map[string]string{
|
||||
"Private": "p",
|
||||
"Secret": "s",
|
||||
"ProtectedTopic": "t",
|
||||
"NoExternalMsg": "n",
|
||||
"Moderated": "m",
|
||||
"InviteOnly": "i",
|
||||
"OperOnly": "O",
|
||||
"SSLOnly": "z",
|
||||
"Registered": "r",
|
||||
"AllSSL": "Z",
|
||||
"Key": "k",
|
||||
"Limit": "l",
|
||||
}
|
||||
|
||||
// Map *irc.ChanPrivs fields to IRC mode characters
|
||||
var StringToChanPriv = map[string]string{}
|
||||
var ChanPrivToString = map[string]string{
|
||||
"Owner": "q",
|
||||
"Admin": "a",
|
||||
"Op": "o",
|
||||
"HalfOp": "h",
|
||||
"Voice": "v",
|
||||
}
|
||||
|
||||
// Map *irc.ChanPrivs fields to the symbols used to represent these modes
|
||||
// in NAMES and WHOIS responses
|
||||
var ModeCharToChanPriv = map[byte]string{}
|
||||
var ChanPrivToModeChar = map[string]byte{
|
||||
"Owner": '~',
|
||||
"Admin": '&',
|
||||
"Op": '@',
|
||||
"HalfOp": '%',
|
||||
"Voice": '+',
|
||||
}
|
||||
|
||||
// Init function to fill in reverse mappings for *toString constants.
|
||||
func init() {
|
||||
for k, v := range ChanModeToString {
|
||||
StringToChanMode[v] = k
|
||||
}
|
||||
for k, v := range ChanPrivToString {
|
||||
StringToChanPriv[v] = k
|
||||
}
|
||||
for k, v := range ChanPrivToModeChar {
|
||||
ModeCharToChanPriv[v] = k
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************\
|
||||
* Channel methods for state management
|
||||
\******************************************************************************/
|
||||
|
||||
func newChannel(name string) *channel {
|
||||
return &channel{
|
||||
name: name,
|
||||
modes: new(ChanMode),
|
||||
nicks: make(map[*nick]*ChanPrivs),
|
||||
lookup: make(map[string]*nick),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a copy of the internal tracker channel state at this time.
|
||||
// Relies on tracker-level locking for concurrent access.
|
||||
func (ch *channel) Channel() *Channel {
|
||||
c := &Channel{
|
||||
Name: ch.name,
|
||||
Topic: ch.topic,
|
||||
Modes: ch.modes.Copy(),
|
||||
Nicks: make(map[string]*ChanPrivs),
|
||||
}
|
||||
for n, cp := range ch.nicks {
|
||||
c.Nicks[n.nick] = cp.Copy()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (ch *channel) isOn(nk *nick) (*ChanPrivs, bool) {
|
||||
cp, ok := ch.nicks[nk]
|
||||
return cp.Copy(), ok
|
||||
}
|
||||
|
||||
// Associates a Nick with a Channel
|
||||
func (ch *channel) addNick(nk *nick, cp *ChanPrivs) {
|
||||
if _, ok := ch.nicks[nk]; !ok {
|
||||
ch.nicks[nk] = cp
|
||||
ch.lookup[nk.nick] = nk
|
||||
} else {
|
||||
logging.Warn("Channel.addNick(): %s already on %s.", nk.nick, ch.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Disassociates a Nick from a Channel.
|
||||
func (ch *channel) delNick(nk *nick) {
|
||||
if _, ok := ch.nicks[nk]; ok {
|
||||
delete(ch.nicks, nk)
|
||||
delete(ch.lookup, nk.nick)
|
||||
} else {
|
||||
logging.Warn("Channel.delNick(): %s not on %s.", nk.nick, ch.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Parses mode strings for a channel.
|
||||
func (ch *channel) parseModes(modes string, modeargs ...string) {
|
||||
var modeop bool // true => add mode, false => remove mode
|
||||
var modestr string
|
||||
for i := 0; i < len(modes); i++ {
|
||||
switch m := modes[i]; m {
|
||||
case '+':
|
||||
modeop = true
|
||||
modestr = string(m)
|
||||
case '-':
|
||||
modeop = false
|
||||
modestr = string(m)
|
||||
case 'i':
|
||||
ch.modes.InviteOnly = modeop
|
||||
case 'm':
|
||||
ch.modes.Moderated = modeop
|
||||
case 'n':
|
||||
ch.modes.NoExternalMsg = modeop
|
||||
case 'p':
|
||||
ch.modes.Private = modeop
|
||||
case 'r':
|
||||
ch.modes.Registered = modeop
|
||||
case 's':
|
||||
ch.modes.Secret = modeop
|
||||
case 't':
|
||||
ch.modes.ProtectedTopic = modeop
|
||||
case 'z':
|
||||
ch.modes.SSLOnly = modeop
|
||||
case 'Z':
|
||||
ch.modes.AllSSL = modeop
|
||||
case 'O':
|
||||
ch.modes.OperOnly = modeop
|
||||
case 'k':
|
||||
if modeop && len(modeargs) != 0 {
|
||||
ch.modes.Key, modeargs = modeargs[0], modeargs[1:]
|
||||
} else if !modeop {
|
||||
ch.modes.Key = ""
|
||||
} else {
|
||||
logging.Warn("Channel.ParseModes(): not enough arguments to "+
|
||||
"process MODE %s %s%c", ch.name, modestr, m)
|
||||
}
|
||||
case 'l':
|
||||
if modeop && len(modeargs) != 0 {
|
||||
ch.modes.Limit, _ = strconv.Atoi(modeargs[0])
|
||||
modeargs = modeargs[1:]
|
||||
} else if !modeop {
|
||||
ch.modes.Limit = 0
|
||||
} else {
|
||||
logging.Warn("Channel.ParseModes(): not enough arguments to "+
|
||||
"process MODE %s %s%c", ch.name, modestr, m)
|
||||
}
|
||||
case 'q', 'a', 'o', 'h', 'v':
|
||||
if len(modeargs) != 0 {
|
||||
if nk, ok := ch.lookup[modeargs[0]]; ok {
|
||||
cp := ch.nicks[nk]
|
||||
switch m {
|
||||
case 'q':
|
||||
cp.Owner = modeop
|
||||
case 'a':
|
||||
cp.Admin = modeop
|
||||
case 'o':
|
||||
cp.Op = modeop
|
||||
case 'h':
|
||||
cp.HalfOp = modeop
|
||||
case 'v':
|
||||
cp.Voice = modeop
|
||||
}
|
||||
modeargs = modeargs[1:]
|
||||
} else {
|
||||
logging.Warn("Channel.ParseModes(): untracked nick %s "+
|
||||
"received MODE on channel %s", modeargs[0], ch.name)
|
||||
}
|
||||
} else {
|
||||
logging.Warn("Channel.ParseModes(): not enough arguments to "+
|
||||
"process MODE %s %s%c", ch.name, modestr, m)
|
||||
}
|
||||
default:
|
||||
logging.Info("Channel.ParseModes(): unknown mode char %c", m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the Nick is associated with the Channel
|
||||
func (ch *Channel) IsOn(nk string) (*ChanPrivs, bool) {
|
||||
cp, ok := ch.Nicks[nk]
|
||||
return cp, ok
|
||||
}
|
||||
|
||||
// Test Channel equality.
|
||||
func (ch *Channel) Equals(other *Channel) bool {
|
||||
return reflect.DeepEqual(ch, other)
|
||||
}
|
||||
|
||||
// Duplicates a ChanMode struct.
|
||||
func (cm *ChanMode) Copy() *ChanMode {
|
||||
if cm == nil { return nil }
|
||||
c := *cm
|
||||
return &c
|
||||
}
|
||||
|
||||
// Test ChanMode equality.
|
||||
func (cm *ChanMode) Equals(other *ChanMode) bool {
|
||||
return reflect.DeepEqual(cm, other)
|
||||
}
|
||||
|
||||
// Duplicates a ChanPrivs struct.
|
||||
func (cp *ChanPrivs) Copy() *ChanPrivs {
|
||||
if cp == nil { return nil }
|
||||
c := *cp
|
||||
return &c
|
||||
}
|
||||
|
||||
// Test ChanPrivs equality.
|
||||
func (cp *ChanPrivs) Equals(other *ChanPrivs) bool {
|
||||
return reflect.DeepEqual(cp, other)
|
||||
}
|
||||
|
||||
// Returns a string representing the channel. Looks like:
|
||||
// Channel: <channel name> e.g. #moo
|
||||
// Topic: <channel topic> e.g. Discussing the merits of cows!
|
||||
// Mode: <channel modes> e.g. +nsti
|
||||
// Nicks:
|
||||
// <nick>: <privs> e.g. CowMaster: +o
|
||||
// ...
|
||||
func (ch *Channel) String() string {
|
||||
str := "Channel: " + ch.Name + "\n\t"
|
||||
str += "Topic: " + ch.Topic + "\n\t"
|
||||
str += "Modes: " + ch.Modes.String() + "\n\t"
|
||||
str += "Nicks: \n"
|
||||
for nk, cp := range ch.Nicks {
|
||||
str += "\t\t" + nk + ": " + cp.String() + "\n"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (ch *channel) String() string {
|
||||
return ch.Channel().String()
|
||||
}
|
||||
|
||||
// Returns a string representing the channel modes. Looks like:
|
||||
// +npk key
|
||||
func (cm *ChanMode) String() string {
|
||||
str := "+"
|
||||
a := make([]string, 0)
|
||||
v := reflect.Indirect(reflect.ValueOf(cm))
|
||||
t := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
switch f := v.Field(i); f.Kind() {
|
||||
case reflect.Bool:
|
||||
if f.Bool() {
|
||||
str += ChanModeToString[t.Field(i).Name]
|
||||
}
|
||||
case reflect.String:
|
||||
if f.String() != "" {
|
||||
str += ChanModeToString[t.Field(i).Name]
|
||||
a = append(a, f.String())
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if f.Int() != 0 {
|
||||
str += ChanModeToString[t.Field(i).Name]
|
||||
a = append(a, strconv.FormatInt(f.Int(), 10))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range a {
|
||||
if s != "" {
|
||||
str += " " + s
|
||||
}
|
||||
}
|
||||
if str == "+" {
|
||||
str = "No modes set"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Returns a string representing the channel privileges. Looks like:
|
||||
// +o
|
||||
func (cp *ChanPrivs) String() string {
|
||||
str := "+"
|
||||
v := reflect.Indirect(reflect.ValueOf(cp))
|
||||
t := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
switch f := v.Field(i); f.Kind() {
|
||||
// only bools here at the mo too!
|
||||
case reflect.Bool:
|
||||
if f.Bool() {
|
||||
str += ChanPrivToString[t.Field(i).Name]
|
||||
}
|
||||
}
|
||||
}
|
||||
if str == "+" {
|
||||
str = "No modes set"
|
||||
}
|
||||
return str
|
||||
}
|
201
vendor/github.com/fluffle/goirc/state/mock_tracker.go
generated
vendored
Normal file
201
vendor/github.com/fluffle/goirc/state/mock_tracker.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
// Automatically generated by MockGen. DO NOT EDIT!
|
||||
// Source: tracker.go
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// Mock of Tracker interface
|
||||
type MockTracker struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *_MockTrackerRecorder
|
||||
}
|
||||
|
||||
// Recorder for MockTracker (not exported)
|
||||
type _MockTrackerRecorder struct {
|
||||
mock *MockTracker
|
||||
}
|
||||
|
||||
func NewMockTracker(ctrl *gomock.Controller) *MockTracker {
|
||||
mock := &MockTracker{ctrl: ctrl}
|
||||
mock.recorder = &_MockTrackerRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
func (_m *MockTracker) EXPECT() *_MockTrackerRecorder {
|
||||
return _m.recorder
|
||||
}
|
||||
|
||||
func (_m *MockTracker) NewNick(nick string) *Nick {
|
||||
ret := _m.ctrl.Call(_m, "NewNick", nick)
|
||||
ret0, _ := ret[0].(*Nick)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) NewNick(arg0 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "NewNick", arg0)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) GetNick(nick string) *Nick {
|
||||
ret := _m.ctrl.Call(_m, "GetNick", nick)
|
||||
ret0, _ := ret[0].(*Nick)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) GetNick(arg0 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "GetNick", arg0)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) ReNick(old string, neu string) *Nick {
|
||||
ret := _m.ctrl.Call(_m, "ReNick", old, neu)
|
||||
ret0, _ := ret[0].(*Nick)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) ReNick(arg0, arg1 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "ReNick", arg0, arg1)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) DelNick(nick string) *Nick {
|
||||
ret := _m.ctrl.Call(_m, "DelNick", nick)
|
||||
ret0, _ := ret[0].(*Nick)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) DelNick(arg0 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "DelNick", arg0)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) NickInfo(nick string, ident string, host string, name string) *Nick {
|
||||
ret := _m.ctrl.Call(_m, "NickInfo", nick, ident, host, name)
|
||||
ret0, _ := ret[0].(*Nick)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) NickInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "NickInfo", arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) NickModes(nick string, modestr string) *Nick {
|
||||
ret := _m.ctrl.Call(_m, "NickModes", nick, modestr)
|
||||
ret0, _ := ret[0].(*Nick)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) NickModes(arg0, arg1 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "NickModes", arg0, arg1)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) NewChannel(channel string) *Channel {
|
||||
ret := _m.ctrl.Call(_m, "NewChannel", channel)
|
||||
ret0, _ := ret[0].(*Channel)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) NewChannel(arg0 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "NewChannel", arg0)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) GetChannel(channel string) *Channel {
|
||||
ret := _m.ctrl.Call(_m, "GetChannel", channel)
|
||||
ret0, _ := ret[0].(*Channel)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) GetChannel(arg0 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "GetChannel", arg0)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) DelChannel(channel string) *Channel {
|
||||
ret := _m.ctrl.Call(_m, "DelChannel", channel)
|
||||
ret0, _ := ret[0].(*Channel)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) DelChannel(arg0 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "DelChannel", arg0)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) Topic(channel string, topic string) *Channel {
|
||||
ret := _m.ctrl.Call(_m, "Topic", channel, topic)
|
||||
ret0, _ := ret[0].(*Channel)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) Topic(arg0, arg1 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "Topic", arg0, arg1)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) ChannelModes(channel string, modestr string, modeargs ...string) *Channel {
|
||||
_s := []interface{}{channel, modestr}
|
||||
for _, _x := range modeargs {
|
||||
_s = append(_s, _x)
|
||||
}
|
||||
ret := _m.ctrl.Call(_m, "ChannelModes", _s...)
|
||||
ret0, _ := ret[0].(*Channel)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) ChannelModes(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
_s := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "ChannelModes", _s...)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) Me() *Nick {
|
||||
ret := _m.ctrl.Call(_m, "Me")
|
||||
ret0, _ := ret[0].(*Nick)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) Me() *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "Me")
|
||||
}
|
||||
|
||||
func (_m *MockTracker) IsOn(channel string, nick string) (*ChanPrivs, bool) {
|
||||
ret := _m.ctrl.Call(_m, "IsOn", channel, nick)
|
||||
ret0, _ := ret[0].(*ChanPrivs)
|
||||
ret1, _ := ret[1].(bool)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) IsOn(arg0, arg1 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "IsOn", arg0, arg1)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) Associate(channel string, nick string) *ChanPrivs {
|
||||
ret := _m.ctrl.Call(_m, "Associate", channel, nick)
|
||||
ret0, _ := ret[0].(*ChanPrivs)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) Associate(arg0, arg1 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "Associate", arg0, arg1)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) Dissociate(channel string, nick string) {
|
||||
_m.ctrl.Call(_m, "Dissociate", channel, nick)
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) Dissociate(arg0, arg1 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "Dissociate", arg0, arg1)
|
||||
}
|
||||
|
||||
func (_m *MockTracker) Wipe() {
|
||||
_m.ctrl.Call(_m, "Wipe")
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) Wipe() *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "Wipe")
|
||||
}
|
||||
|
||||
func (_m *MockTracker) String() string {
|
||||
ret := _m.ctrl.Call(_m, "String")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (_mr *_MockTrackerRecorder) String() *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "String")
|
||||
}
|
200
vendor/github.com/fluffle/goirc/state/nick.go
generated
vendored
Normal file
200
vendor/github.com/fluffle/goirc/state/nick.go
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/fluffle/goirc/logging"
|
||||
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// A Nick is returned from the state tracker and contains
|
||||
// a copy of the nick state at a particular time.
|
||||
type Nick struct {
|
||||
Nick, Ident, Host, Name string
|
||||
Modes *NickMode
|
||||
Channels map[string]*ChanPrivs
|
||||
}
|
||||
|
||||
// Internal bookkeeping struct for nicks.
|
||||
type nick struct {
|
||||
nick, ident, host, name string
|
||||
modes *NickMode
|
||||
lookup map[string]*channel
|
||||
chans map[*channel]*ChanPrivs
|
||||
}
|
||||
|
||||
// A struct representing the modes of an IRC Nick (User Modes)
|
||||
// (again, only the ones we care about)
|
||||
//
|
||||
// This is only really useful for me, as we can't see other people's modes
|
||||
// without IRC operator privileges (and even then only on some IRCd's).
|
||||
type NickMode struct {
|
||||
// MODE +B, +i, +o, +w, +x, +z
|
||||
Bot, Invisible, Oper, WallOps, HiddenHost, SSL bool
|
||||
}
|
||||
|
||||
// Map *irc.NickMode fields to IRC mode characters and vice versa
|
||||
var StringToNickMode = map[string]string{}
|
||||
var NickModeToString = map[string]string{
|
||||
"Bot": "B",
|
||||
"Invisible": "i",
|
||||
"Oper": "o",
|
||||
"WallOps": "w",
|
||||
"HiddenHost": "x",
|
||||
"SSL": "z",
|
||||
}
|
||||
|
||||
func init() {
|
||||
for k, v := range NickModeToString {
|
||||
StringToNickMode[v] = k
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************\
|
||||
* nick methods for state management
|
||||
\******************************************************************************/
|
||||
|
||||
func newNick(n string) *nick {
|
||||
return &nick{
|
||||
nick: n,
|
||||
modes: new(NickMode),
|
||||
chans: make(map[*channel]*ChanPrivs),
|
||||
lookup: make(map[string]*channel),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a copy of the internal tracker nick state at this time.
|
||||
// Relies on tracker-level locking for concurrent access.
|
||||
func (nk *nick) Nick() *Nick {
|
||||
n := &Nick{
|
||||
Nick: nk.nick,
|
||||
Ident: nk.ident,
|
||||
Host: nk.host,
|
||||
Name: nk.name,
|
||||
Modes: nk.modes.Copy(),
|
||||
Channels: make(map[string]*ChanPrivs),
|
||||
}
|
||||
for c, cp := range nk.chans {
|
||||
n.Channels[c.name] = cp.Copy()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (nk *nick) isOn(ch *channel) (*ChanPrivs, bool) {
|
||||
cp, ok := nk.chans[ch]
|
||||
return cp.Copy(), ok
|
||||
}
|
||||
|
||||
// Associates a Channel with a Nick.
|
||||
func (nk *nick) addChannel(ch *channel, cp *ChanPrivs) {
|
||||
if _, ok := nk.chans[ch]; !ok {
|
||||
nk.chans[ch] = cp
|
||||
nk.lookup[ch.name] = ch
|
||||
} else {
|
||||
logging.Warn("Nick.addChannel(): %s already on %s.", nk.nick, ch.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Disassociates a Channel from a Nick.
|
||||
func (nk *nick) delChannel(ch *channel) {
|
||||
if _, ok := nk.chans[ch]; ok {
|
||||
delete(nk.chans, ch)
|
||||
delete(nk.lookup, ch.name)
|
||||
} else {
|
||||
logging.Warn("Nick.delChannel(): %s not on %s.", nk.nick, ch.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse mode strings for a Nick.
|
||||
func (nk *nick) parseModes(modes string) {
|
||||
var modeop bool // true => add mode, false => remove mode
|
||||
for i := 0; i < len(modes); i++ {
|
||||
switch m := modes[i]; m {
|
||||
case '+':
|
||||
modeop = true
|
||||
case '-':
|
||||
modeop = false
|
||||
case 'B':
|
||||
nk.modes.Bot = modeop
|
||||
case 'i':
|
||||
nk.modes.Invisible = modeop
|
||||
case 'o':
|
||||
nk.modes.Oper = modeop
|
||||
case 'w':
|
||||
nk.modes.WallOps = modeop
|
||||
case 'x':
|
||||
nk.modes.HiddenHost = modeop
|
||||
case 'z':
|
||||
nk.modes.SSL = modeop
|
||||
default:
|
||||
logging.Info("Nick.ParseModes(): unknown mode char %c", m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the Nick is associated with the Channel.
|
||||
func (nk *Nick) IsOn(ch string) (*ChanPrivs, bool) {
|
||||
cp, ok := nk.Channels[ch]
|
||||
return cp, ok
|
||||
}
|
||||
|
||||
// Tests Nick equality.
|
||||
func (nk *Nick) Equals(other *Nick) bool {
|
||||
return reflect.DeepEqual(nk, other)
|
||||
}
|
||||
|
||||
// Duplicates a NickMode struct.
|
||||
func (nm *NickMode) Copy() *NickMode {
|
||||
if nm == nil { return nil }
|
||||
n := *nm
|
||||
return &n
|
||||
}
|
||||
|
||||
// Tests NickMode equality.
|
||||
func (nm *NickMode) Equals(other *NickMode) bool {
|
||||
return reflect.DeepEqual(nm, other)
|
||||
}
|
||||
|
||||
// Returns a string representing the nick. Looks like:
|
||||
// Nick: <nick name> e.g. CowMaster
|
||||
// Hostmask: <ident@host> e.g. moo@cows.org
|
||||
// Real Name: <real name> e.g. Steve "CowMaster" Bush
|
||||
// Modes: <nick modes> e.g. +z
|
||||
// Channels:
|
||||
// <channel>: <privs> e.g. #moo: +o
|
||||
// ...
|
||||
func (nk *Nick) String() string {
|
||||
str := "Nick: " + nk.Nick + "\n\t"
|
||||
str += "Hostmask: " + nk.Ident + "@" + nk.Host + "\n\t"
|
||||
str += "Real Name: " + nk.Name + "\n\t"
|
||||
str += "Modes: " + nk.Modes.String() + "\n\t"
|
||||
str += "Channels: \n"
|
||||
for ch, cp := range nk.Channels {
|
||||
str += "\t\t" + ch + ": " + cp.String() + "\n"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (nk *nick) String() string {
|
||||
return nk.Nick().String()
|
||||
}
|
||||
|
||||
// Returns a string representing the nick modes. Looks like:
|
||||
// +iwx
|
||||
func (nm *NickMode) String() string {
|
||||
str := "+"
|
||||
v := reflect.Indirect(reflect.ValueOf(nm))
|
||||
t := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
switch f := v.Field(i); f.Kind() {
|
||||
// only bools here at the mo!
|
||||
case reflect.Bool:
|
||||
if f.Bool() {
|
||||
str += NickModeToString[t.Field(i).Name]
|
||||
}
|
||||
}
|
||||
}
|
||||
if str == "+" {
|
||||
str = "No modes set"
|
||||
}
|
||||
return str
|
||||
}
|
366
vendor/github.com/fluffle/goirc/state/tracker.go
generated
vendored
Normal file
366
vendor/github.com/fluffle/goirc/state/tracker.go
generated
vendored
Normal file
@@ -0,0 +1,366 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/fluffle/goirc/logging"
|
||||
|
||||
"sync"
|
||||
)
|
||||
|
||||
// The state manager interface
|
||||
type Tracker interface {
|
||||
// Nick methods
|
||||
NewNick(nick string) *Nick
|
||||
GetNick(nick string) *Nick
|
||||
ReNick(old, neu string) *Nick
|
||||
DelNick(nick string) *Nick
|
||||
NickInfo(nick, ident, host, name string) *Nick
|
||||
NickModes(nick, modestr string) *Nick
|
||||
// Channel methods
|
||||
NewChannel(channel string) *Channel
|
||||
GetChannel(channel string) *Channel
|
||||
DelChannel(channel string) *Channel
|
||||
Topic(channel, topic string) *Channel
|
||||
ChannelModes(channel, modestr string, modeargs ...string) *Channel
|
||||
// Information about ME!
|
||||
Me() *Nick
|
||||
// And the tracking operations
|
||||
IsOn(channel, nick string) (*ChanPrivs, bool)
|
||||
Associate(channel, nick string) *ChanPrivs
|
||||
Dissociate(channel, nick string)
|
||||
Wipe()
|
||||
// The state tracker can output a debugging string
|
||||
String() string
|
||||
}
|
||||
|
||||
// ... and a struct to implement it ...
|
||||
type stateTracker struct {
|
||||
// Map of channels we're on
|
||||
chans map[string]*channel
|
||||
// Map of nicks we know about
|
||||
nicks map[string]*nick
|
||||
|
||||
// We need to keep state on who we are :-)
|
||||
me *nick
|
||||
|
||||
// And we need to protect against data races *cough*.
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var _ Tracker = (*stateTracker)(nil)
|
||||
|
||||
// ... and a constructor to make it ...
|
||||
func NewTracker(mynick string) *stateTracker {
|
||||
st := &stateTracker{
|
||||
chans: make(map[string]*channel),
|
||||
nicks: make(map[string]*nick),
|
||||
}
|
||||
st.me = newNick(mynick)
|
||||
st.nicks[mynick] = st.me
|
||||
return st
|
||||
}
|
||||
|
||||
// ... and a method to wipe the state clean.
|
||||
func (st *stateTracker) Wipe() {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
// Deleting all the channels implicitly deletes every nick but me.
|
||||
for _, ch := range st.chans {
|
||||
st.delChannel(ch)
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************\
|
||||
* tracker methods to create/look up nicks/channels
|
||||
\******************************************************************************/
|
||||
|
||||
// Creates a new nick, initialises it, and stores it so it
|
||||
// can be properly tracked for state management purposes.
|
||||
func (st *stateTracker) NewNick(n string) *Nick {
|
||||
if n == "" {
|
||||
logging.Warn("Tracker.NewNick(): Not tracking empty nick.")
|
||||
return nil
|
||||
}
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
if _, ok := st.nicks[n]; ok {
|
||||
logging.Warn("Tracker.NewNick(): %s already tracked.", n)
|
||||
return nil
|
||||
}
|
||||
st.nicks[n] = newNick(n)
|
||||
return st.nicks[n].Nick()
|
||||
}
|
||||
|
||||
// Returns a nick for the nick n, if we're tracking it.
|
||||
func (st *stateTracker) GetNick(n string) *Nick {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
if nk, ok := st.nicks[n]; ok {
|
||||
return nk.Nick()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Signals to the tracker that a nick should be tracked
|
||||
// under a "neu" nick rather than the old one.
|
||||
func (st *stateTracker) ReNick(old, neu string) *Nick {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
nk, ok := st.nicks[old]
|
||||
if !ok {
|
||||
logging.Warn("Tracker.ReNick(): %s not tracked.", old)
|
||||
return nil
|
||||
}
|
||||
if _, ok := st.nicks[neu]; ok {
|
||||
logging.Warn("Tracker.ReNick(): %s already exists.", neu)
|
||||
return nil
|
||||
}
|
||||
|
||||
nk.nick = neu
|
||||
delete(st.nicks, old)
|
||||
st.nicks[neu] = nk
|
||||
for ch, _ := range nk.chans {
|
||||
// We also need to update the lookup maps of all the channels
|
||||
// the nick is on, to keep things in sync.
|
||||
delete(ch.lookup, old)
|
||||
ch.lookup[neu] = nk
|
||||
}
|
||||
return nk.Nick()
|
||||
}
|
||||
|
||||
// Removes a nick from being tracked.
|
||||
func (st *stateTracker) DelNick(n string) *Nick {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
if nk, ok := st.nicks[n]; ok {
|
||||
if nk == st.me {
|
||||
logging.Warn("Tracker.DelNick(): won't delete myself.")
|
||||
return nil
|
||||
}
|
||||
st.delNick(nk)
|
||||
return nk.Nick()
|
||||
}
|
||||
logging.Warn("Tracker.DelNick(): %s not tracked.", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *stateTracker) delNick(nk *nick) {
|
||||
// st.mu lock held by DelNick, DelChannel or Wipe
|
||||
if nk == st.me {
|
||||
// Shouldn't get here => internal state tracking code is fubar.
|
||||
logging.Error("Tracker.DelNick(): TRYING TO DELETE ME :-(")
|
||||
return
|
||||
}
|
||||
delete(st.nicks, nk.nick)
|
||||
for ch, _ := range nk.chans {
|
||||
nk.delChannel(ch)
|
||||
ch.delNick(nk)
|
||||
if len(ch.nicks) == 0 {
|
||||
// Deleting a nick from tracking shouldn't empty any channels as
|
||||
// *we* should be on the channel with them to be tracking them.
|
||||
logging.Error("Tracker.delNick(): deleting nick %s emptied "+
|
||||
"channel %s, this shouldn't happen!", nk.nick, ch.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sets ident, host and "real" name for the nick.
|
||||
func (st *stateTracker) NickInfo(n, ident, host, name string) *Nick {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
nk, ok := st.nicks[n]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
nk.ident = ident
|
||||
nk.host = host
|
||||
nk.name = name
|
||||
return nk.Nick()
|
||||
}
|
||||
|
||||
// Sets user modes for the nick.
|
||||
func (st *stateTracker) NickModes(n, modes string) *Nick {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
nk, ok := st.nicks[n]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
nk.parseModes(modes)
|
||||
return nk.Nick()
|
||||
}
|
||||
|
||||
// Creates a new Channel, initialises it, and stores it so it
|
||||
// can be properly tracked for state management purposes.
|
||||
func (st *stateTracker) NewChannel(c string) *Channel {
|
||||
if c == "" {
|
||||
logging.Warn("Tracker.NewChannel(): Not tracking empty channel.")
|
||||
return nil
|
||||
}
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
if _, ok := st.chans[c]; ok {
|
||||
logging.Warn("Tracker.NewChannel(): %s already tracked.", c)
|
||||
return nil
|
||||
}
|
||||
st.chans[c] = newChannel(c)
|
||||
return st.chans[c].Channel()
|
||||
}
|
||||
|
||||
// Returns a Channel for the channel c, if we're tracking it.
|
||||
func (st *stateTracker) GetChannel(c string) *Channel {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
if ch, ok := st.chans[c]; ok {
|
||||
return ch.Channel()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Removes a Channel from being tracked.
|
||||
func (st *stateTracker) DelChannel(c string) *Channel {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
if ch, ok := st.chans[c]; ok {
|
||||
st.delChannel(ch)
|
||||
return ch.Channel()
|
||||
}
|
||||
logging.Warn("Tracker.DelChannel(): %s not tracked.", c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *stateTracker) delChannel(ch *channel) {
|
||||
// st.mu lock held by DelChannel or Wipe
|
||||
delete(st.chans, ch.name)
|
||||
for nk, _ := range ch.nicks {
|
||||
ch.delNick(nk)
|
||||
nk.delChannel(ch)
|
||||
if len(nk.chans) == 0 && nk != st.me {
|
||||
// We're no longer in any channels with this nick.
|
||||
st.delNick(nk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the topic of a channel.
|
||||
func (st *stateTracker) Topic(c, topic string) *Channel {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
ch, ok := st.chans[c]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ch.topic = topic
|
||||
return ch.Channel()
|
||||
}
|
||||
|
||||
// Sets modes for a channel, including privileges like +o.
|
||||
func (st *stateTracker) ChannelModes(c, modes string, args ...string) *Channel {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
ch, ok := st.chans[c]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ch.parseModes(modes, args...)
|
||||
return ch.Channel()
|
||||
}
|
||||
|
||||
// Returns the Nick the state tracker thinks is Me.
|
||||
func (st *stateTracker) Me() *Nick {
|
||||
return st.me.Nick()
|
||||
}
|
||||
|
||||
// Returns true if both the channel c and the nick n are tracked
|
||||
// and the nick is associated with the channel.
|
||||
func (st *stateTracker) IsOn(c, n string) (*ChanPrivs, bool) {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
nk, nok := st.nicks[n]
|
||||
ch, cok := st.chans[c]
|
||||
if nok && cok {
|
||||
return nk.isOn(ch)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Associates an already known nick with an already known channel.
|
||||
func (st *stateTracker) Associate(c, n string) *ChanPrivs {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
nk, nok := st.nicks[n]
|
||||
ch, cok := st.chans[c]
|
||||
|
||||
if !cok {
|
||||
// As we can implicitly delete both nicks and channels from being
|
||||
// tracked by dissociating one from the other, we should verify that
|
||||
// we're not being passed an old Nick or Channel.
|
||||
logging.Error("Tracker.Associate(): channel %s not found in "+
|
||||
"internal state.", c)
|
||||
return nil
|
||||
} else if !nok {
|
||||
logging.Error("Tracker.Associate(): nick %s not found in "+
|
||||
"internal state.", n)
|
||||
return nil
|
||||
} else if _, ok := nk.isOn(ch); ok {
|
||||
logging.Warn("Tracker.Associate(): %s already on %s.",
|
||||
nk, ch)
|
||||
return nil
|
||||
}
|
||||
cp := new(ChanPrivs)
|
||||
ch.addNick(nk, cp)
|
||||
nk.addChannel(ch, cp)
|
||||
return cp.Copy()
|
||||
}
|
||||
|
||||
// Dissociates an already known nick from an already known channel.
|
||||
// Does some tidying up to stop tracking nicks we're no longer on
|
||||
// any common channels with, and channels we're no longer on.
|
||||
func (st *stateTracker) Dissociate(c, n string) {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
nk, nok := st.nicks[n]
|
||||
ch, cok := st.chans[c]
|
||||
|
||||
if !cok {
|
||||
// As we can implicitly delete both nicks and channels from being
|
||||
// tracked by dissociating one from the other, we should verify that
|
||||
// we're not being passed an old Nick or Channel.
|
||||
logging.Error("Tracker.Dissociate(): channel %s not found in "+
|
||||
"internal state.", c)
|
||||
} else if !nok {
|
||||
logging.Error("Tracker.Dissociate(): nick %s not found in "+
|
||||
"internal state.", n)
|
||||
} else if _, ok := nk.isOn(ch); !ok {
|
||||
logging.Warn("Tracker.Dissociate(): %s not on %s.",
|
||||
nk.nick, ch.name)
|
||||
} else if nk == st.me {
|
||||
// I'm leaving the channel for some reason, so it won't be tracked.
|
||||
st.delChannel(ch)
|
||||
} else {
|
||||
// Remove the nick from the channel and the channel from the nick.
|
||||
ch.delNick(nk)
|
||||
nk.delChannel(ch)
|
||||
if len(nk.chans) == 0 {
|
||||
// We're no longer in any channels with this nick.
|
||||
st.delNick(nk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (st *stateTracker) String() string {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
str := "GoIRC Channels\n"
|
||||
str += "--------------\n\n"
|
||||
for _, ch := range st.chans {
|
||||
str += ch.String() + "\n"
|
||||
}
|
||||
str += "GoIRC NickNames\n"
|
||||
str += "---------------\n\n"
|
||||
for _, n := range st.nicks {
|
||||
if n != st.me {
|
||||
str += n.String() + "\n"
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
Reference in New Issue
Block a user