mirror of https://github.com/fluffle/goirc
Merge 4a4c7f397c
into fe4fae0479
This commit is contained in:
commit
be360c368d
|
@ -28,11 +28,11 @@ Synopsis:
|
||||||
|
|
||||||
// Add handlers to do things here!
|
// Add handlers to do things here!
|
||||||
// e.g. join a channel on connect.
|
// e.g. join a channel on connect.
|
||||||
c.HandleFunc("connected",
|
c.HandleFunc(irc.CONNECTED,
|
||||||
func(conn *irc.Conn, line *irc.Line) { conn.Join("#channel") })
|
func(conn *irc.Conn, line *irc.Line) { conn.Join("#channel") })
|
||||||
// And a signal on disconnect
|
// And a signal on disconnect
|
||||||
quit := make(chan bool)
|
quit := make(chan bool)
|
||||||
c.HandleFunc("disconnected",
|
c.HandleFunc(irc.DISCONNECTED,
|
||||||
func(conn *irc.Conn, line *irc.Line) { quit <- true })
|
func(conn *irc.Conn, line *irc.Line) { quit <- true })
|
||||||
|
|
||||||
// Tell client to connect.
|
// Tell client to connect.
|
||||||
|
|
35
client.go
35
client.go
|
@ -5,7 +5,9 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
irc "github.com/fluffle/goirc/client"
|
irc "github.com/fluffle/goirc/client"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,14 +20,40 @@ func main() {
|
||||||
// create new IRC connection
|
// create new IRC connection
|
||||||
c := irc.SimpleClient("GoTest", "gotest")
|
c := irc.SimpleClient("GoTest", "gotest")
|
||||||
c.EnableStateTracking()
|
c.EnableStateTracking()
|
||||||
c.HandleFunc("connected",
|
c.HandleFunc(irc.CONNECTED,
|
||||||
func(conn *irc.Conn, line *irc.Line) { conn.Join(*channel) })
|
func(conn *irc.Conn, line *irc.Line) { conn.Join(*channel) })
|
||||||
|
|
||||||
// Set up a handler to notify of disconnect events.
|
// Set up a handler to notify of disconnect events.
|
||||||
quit := make(chan bool)
|
quit := make(chan bool)
|
||||||
c.HandleFunc("disconnected",
|
c.HandleFunc(irc.DISCONNECTED,
|
||||||
func(conn *irc.Conn, line *irc.Line) { quit <- true })
|
func(conn *irc.Conn, line *irc.Line) { quit <- true })
|
||||||
|
|
||||||
|
// Set up some simple commands, !bark and !roll.
|
||||||
|
// The !roll command will also get the "!help roll" command also.
|
||||||
|
c.SimpleCommandFunc("bark", func(conn *irc.Conn, line *irc.Line) { conn.Privmsg(line.Target(), "Woof Woof") })
|
||||||
|
c.SimpleCommandHelpFunc("roll", `Rolls a d6, "roll <n>" to roll n dice at once.`, func(conn *irc.Conn, line *irc.Line) {
|
||||||
|
count := 1
|
||||||
|
fields := strings.Fields(line.Message())
|
||||||
|
if len(fields) > 1 {
|
||||||
|
var err error
|
||||||
|
if count, err = strconv.Atoi(fields[len(fields)-1]); err != nil {
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total := 0
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
total += rand.Intn(6) + 1
|
||||||
|
}
|
||||||
|
conn.Privmsg(line.Target(), fmt.Sprintf("%d", total))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set up some commands that are triggered by a regex in a message.
|
||||||
|
// It is important to see that UrlRegex could actually respond to some
|
||||||
|
// of the Url's that YouTubeRegex listens to, because of this we put the
|
||||||
|
// YouTube command at a higher priority, this way it will take precedence.
|
||||||
|
c.CommandFunc(irc.YouTubeRegex, irc.YouTubeFunc, 10)
|
||||||
|
c.CommandFunc(irc.UrlRegex, irc.UrlFunc, 0)
|
||||||
|
|
||||||
// set up a goroutine to read commands from stdin
|
// set up a goroutine to read commands from stdin
|
||||||
in := make(chan string, 4)
|
in := make(chan string, 4)
|
||||||
reallyquit := false
|
reallyquit := false
|
||||||
|
@ -36,6 +64,8 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// wha?, maybe ctrl-D...
|
// wha?, maybe ctrl-D...
|
||||||
close(in)
|
close(in)
|
||||||
|
reallyquit = true
|
||||||
|
c.Quit("")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// no point in sending empty lines down the channel
|
// no point in sending empty lines down the channel
|
||||||
|
@ -85,7 +115,6 @@ func main() {
|
||||||
fmt.Printf("Connection error: %s\n", err)
|
fmt.Printf("Connection error: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait on quit channel
|
// wait on quit channel
|
||||||
<-quit
|
<-quit
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,34 @@ package client
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
|
const (
|
||||||
|
REGISTER = "REGISTER"
|
||||||
|
CONNECTED = "CONNECTED"
|
||||||
|
DISCONNECTED = "DISCONNECTED"
|
||||||
|
ACTION = "ACTION"
|
||||||
|
AWAY = "AWAY"
|
||||||
|
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"
|
||||||
|
WHO = "WHO"
|
||||||
|
WHOIS = "WHOIS"
|
||||||
|
)
|
||||||
|
|
||||||
// this file contains the various commands you can
|
// this file contains the various commands you can
|
||||||
// send to the server using an Conn connection
|
// send to the server using an Conn connection
|
||||||
|
|
||||||
|
@ -14,18 +42,18 @@ import "strings"
|
||||||
func (conn *Conn) Raw(rawline string) { conn.out <- rawline }
|
func (conn *Conn) Raw(rawline string) { conn.out <- rawline }
|
||||||
|
|
||||||
// Pass() sends a PASS command to the server
|
// Pass() sends a PASS command to the server
|
||||||
func (conn *Conn) Pass(password string) { conn.out <- "PASS " + password }
|
func (conn *Conn) Pass(password string) { conn.out <- PASS + " " + password }
|
||||||
|
|
||||||
// Nick() sends a NICK command to the server
|
// Nick() sends a NICK command to the server
|
||||||
func (conn *Conn) Nick(nick string) { conn.out <- "NICK " + nick }
|
func (conn *Conn) Nick(nick string) { conn.out <- NICK + " " + nick }
|
||||||
|
|
||||||
// User() sends a USER command to the server
|
// User() sends a USER command to the server
|
||||||
func (conn *Conn) User(ident, name string) {
|
func (conn *Conn) User(ident, name string) {
|
||||||
conn.out <- "USER " + ident + " 12 * :" + name
|
conn.out <- USER + " " + ident + " 12 * :" + name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join() sends a JOIN command to the server
|
// Join() sends a JOIN command to the server
|
||||||
func (conn *Conn) Join(channel string) { conn.out <- "JOIN " + channel }
|
func (conn *Conn) Join(channel string) { conn.out <- JOIN + " " + channel }
|
||||||
|
|
||||||
// Part() sends a PART command to the server with an optional part message
|
// Part() sends a PART command to the server with an optional part message
|
||||||
func (conn *Conn) Part(channel string, message ...string) {
|
func (conn *Conn) Part(channel string, message ...string) {
|
||||||
|
@ -33,7 +61,7 @@ func (conn *Conn) Part(channel string, message ...string) {
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
msg = " :" + msg
|
msg = " :" + msg
|
||||||
}
|
}
|
||||||
conn.out <- "PART " + channel + msg
|
conn.out <- PART + " " + channel + msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kick() sends a KICK command to remove a nick from a channel
|
// Kick() sends a KICK command to remove a nick from a channel
|
||||||
|
@ -42,7 +70,7 @@ func (conn *Conn) Kick(channel, nick string, message ...string) {
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
msg = " :" + msg
|
msg = " :" + msg
|
||||||
}
|
}
|
||||||
conn.out <- "KICK " + channel + " " + nick + msg
|
conn.out <- KICK + " " + channel + " " + nick + msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quit() sends a QUIT command to the server with an optional quit message
|
// Quit() sends a QUIT command to the server with an optional quit message
|
||||||
|
@ -51,20 +79,20 @@ func (conn *Conn) Quit(message ...string) {
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
msg = "GoBye!"
|
msg = "GoBye!"
|
||||||
}
|
}
|
||||||
conn.out <- "QUIT :" + msg
|
conn.out <- QUIT + " :" + msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whois() sends a WHOIS command to the server
|
// Whois() sends a WHOIS command to the server
|
||||||
func (conn *Conn) Whois(nick string) { conn.out <- "WHOIS " + nick }
|
func (conn *Conn) Whois(nick string) { conn.out <- WHOIS + " " + nick }
|
||||||
|
|
||||||
//Who() sends a WHO command to the server
|
//Who() sends a WHO command to the server
|
||||||
func (conn *Conn) Who(nick string) { conn.out <- "WHO " + nick }
|
func (conn *Conn) Who(nick string) { conn.out <- WHO + " " + nick }
|
||||||
|
|
||||||
// Privmsg() sends a PRIVMSG to the target t
|
// Privmsg() sends a PRIVMSG to the target t
|
||||||
func (conn *Conn) Privmsg(t, msg string) { conn.out <- "PRIVMSG " + t + " :" + msg }
|
func (conn *Conn) Privmsg(t, msg string) { conn.out <- PRIVMSG + " " + t + " :" + msg }
|
||||||
|
|
||||||
// Notice() sends a NOTICE to the target t
|
// Notice() sends a NOTICE to the target t
|
||||||
func (conn *Conn) Notice(t, msg string) { conn.out <- "NOTICE " + t + " :" + msg }
|
func (conn *Conn) Notice(t, msg string) { conn.out <- NOTICE + " " + t + " :" + msg }
|
||||||
|
|
||||||
// Ctcp() sends a (generic) CTCP message to the target t
|
// Ctcp() sends a (generic) CTCP message to the target t
|
||||||
// with an optional argument
|
// with an optional argument
|
||||||
|
@ -87,10 +115,10 @@ func (conn *Conn) CtcpReply(t, ctcp string, arg ...string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version() sends a CTCP "VERSION" to the target t
|
// Version() sends a CTCP "VERSION" to the target t
|
||||||
func (conn *Conn) Version(t string) { conn.Ctcp(t, "VERSION") }
|
func (conn *Conn) Version(t string) { conn.Ctcp(t, VERSION) }
|
||||||
|
|
||||||
// Action() sends a CTCP "ACTION" to the target t
|
// Action() sends a CTCP "ACTION" to the target t
|
||||||
func (conn *Conn) Action(t, msg string) { conn.Ctcp(t, "ACTION", msg) }
|
func (conn *Conn) Action(t, msg string) { conn.Ctcp(t, ACTION, msg) }
|
||||||
|
|
||||||
// Topic() sends a TOPIC command to the channel
|
// Topic() sends a TOPIC command to the channel
|
||||||
// Topic(channel) retrieves the current channel topic (see "332" handler)
|
// Topic(channel) retrieves the current channel topic (see "332" handler)
|
||||||
|
@ -100,7 +128,7 @@ func (conn *Conn) Topic(channel string, topic ...string) {
|
||||||
if t != "" {
|
if t != "" {
|
||||||
t = " :" + t
|
t = " :" + t
|
||||||
}
|
}
|
||||||
conn.out <- "TOPIC " + channel + t
|
conn.out <- TOPIC + " " + channel + t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode() sends a MODE command to the server. This one can get complicated if
|
// Mode() sends a MODE command to the server. This one can get complicated if
|
||||||
|
@ -115,7 +143,7 @@ func (conn *Conn) Mode(t string, modestring ...string) {
|
||||||
if mode != "" {
|
if mode != "" {
|
||||||
mode = " " + mode
|
mode = " " + mode
|
||||||
}
|
}
|
||||||
conn.out <- "MODE " + t + mode
|
conn.out <- MODE + " " + t + mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Away() sends an AWAY command to the server
|
// Away() sends an AWAY command to the server
|
||||||
|
@ -126,15 +154,18 @@ func (conn *Conn) Away(message ...string) {
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
msg = " :" + msg
|
msg = " :" + msg
|
||||||
}
|
}
|
||||||
conn.out <- "AWAY" + msg
|
conn.out <- AWAY + msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invite() sends an INVITE command to the server
|
// Invite() sends an INVITE command to the server
|
||||||
func (conn *Conn) Invite(nick, channel string) {
|
func (conn *Conn) Invite(nick, channel string) { conn.out <- INVITE + " " + nick + " " + channel }
|
||||||
conn.out <- "INVITE " + nick + " " + channel
|
|
||||||
}
|
|
||||||
|
|
||||||
// Oper() sends an OPER command to the server
|
// Oper() sends an OPER command to the server
|
||||||
func (conn *Conn) Oper(user, pass string) {
|
func (conn *Conn) Oper(user, pass string) { conn.out <- OPER + " " + user + " " + pass }
|
||||||
conn.out <- "OPER " + user + " " + pass
|
|
||||||
}
|
// Ping() sends a PING command to the server
|
||||||
|
// A PONG response is to be expected afterwards
|
||||||
|
func (conn *Conn) Ping(message string) { conn.out <- PING + " :" + message }
|
||||||
|
|
||||||
|
// Pong() sends a PONG command to the server
|
||||||
|
func (conn *Conn) Pong(message string) { conn.out <- PONG + " :" + message }
|
||||||
|
|
|
@ -75,4 +75,10 @@ func TestClientCommands(t *testing.T) {
|
||||||
|
|
||||||
c.Oper("user", "pass")
|
c.Oper("user", "pass")
|
||||||
s.nc.Expect("OPER user pass")
|
s.nc.Expect("OPER user pass")
|
||||||
|
|
||||||
|
c.Ping("woot")
|
||||||
|
s.nc.Expect("PING :woot")
|
||||||
|
|
||||||
|
c.Pong("pwoot")
|
||||||
|
s.nc.Expect("PONG :pwoot")
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ type Conn struct {
|
||||||
cfg *Config
|
cfg *Config
|
||||||
|
|
||||||
// Handlers and Commands
|
// Handlers and Commands
|
||||||
handlers *hSet
|
handlers *handlerSet
|
||||||
commands *cSet
|
commands *commandList
|
||||||
|
|
||||||
// State tracker for nicks and channels
|
// State tracker for nicks and channels
|
||||||
st state.Tracker
|
st state.Tracker
|
||||||
|
@ -62,7 +62,7 @@ type Config struct {
|
||||||
PingFreq time.Duration
|
PingFreq time.Duration
|
||||||
|
|
||||||
// Controls what is stripped from line.Args[1] for Commands
|
// Controls what is stripped from line.Args[1] for Commands
|
||||||
CommandStripNick, CommandStripPrefix bool
|
CommandStripNick, SimpleCommandStripPrefix bool
|
||||||
|
|
||||||
// Set this to true to disable flood protection and false to re-enable
|
// Set this to true to disable flood protection and false to re-enable
|
||||||
Flood bool
|
Flood bool
|
||||||
|
@ -104,8 +104,8 @@ func Client(cfg *Config) (*Conn, error) {
|
||||||
cSend: make(chan bool),
|
cSend: make(chan bool),
|
||||||
cLoop: make(chan bool),
|
cLoop: make(chan bool),
|
||||||
cPing: make(chan bool),
|
cPing: make(chan bool),
|
||||||
handlers: handlerSet(),
|
handlers: newHandlerSet(),
|
||||||
commands: commandSet(),
|
commands: newCommandList(),
|
||||||
stRemovers: make([]Remover, 0, len(stHandlers)),
|
stRemovers: make([]Remover, 0, len(stHandlers)),
|
||||||
lastsent: time.Now(),
|
lastsent: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ func setUp(t *testing.T, start ...bool) (*Conn, *testState) {
|
||||||
// Hack to allow tests of send, recv, write etc.
|
// Hack to allow tests of send, recv, write etc.
|
||||||
// NOTE: the value of the boolean doesn't matter.
|
// NOTE: the value of the boolean doesn't matter.
|
||||||
c.postConnect()
|
c.postConnect()
|
||||||
|
|
||||||
// Sleep 1ms to allow background routines to start.
|
// Sleep 1ms to allow background routines to start.
|
||||||
<-time.After(1e6)
|
<-time.After(1e6)
|
||||||
}
|
}
|
||||||
|
@ -56,7 +57,7 @@ func TestEOF(t *testing.T) {
|
||||||
|
|
||||||
// Set up a handler to detect whether disconnected handlers are called
|
// Set up a handler to detect whether disconnected handlers are called
|
||||||
dcon := false
|
dcon := false
|
||||||
c.HandleFunc("disconnected", func(conn *Conn, line *Line) {
|
c.HandleFunc(DISCONNECTED, func(conn *Conn, line *Line) {
|
||||||
dcon = true
|
dcon = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
"github.com/fluffle/golog/logging"
|
"github.com/fluffle/golog/logging"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -11,189 +15,141 @@ type Handler interface {
|
||||||
Handle(*Conn, *Line)
|
Handle(*Conn, *Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// And when they've been added to the client they are removable.
|
|
||||||
type Remover interface {
|
|
||||||
Remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
type HandlerFunc func(*Conn, *Line)
|
type HandlerFunc func(*Conn, *Line)
|
||||||
|
|
||||||
func (hf HandlerFunc) Handle(conn *Conn, line *Line) {
|
func (hf HandlerFunc) Handle(conn *Conn, line *Line) {
|
||||||
hf(conn, line)
|
hf(conn, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
type hList struct {
|
// And when they've been added to the client they are removable.
|
||||||
start, end *hNode
|
type Remover interface {
|
||||||
|
Remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
type hNode struct {
|
type RemoverFunc func()
|
||||||
next, prev *hNode
|
|
||||||
set *hSet
|
func (r RemoverFunc) Remove() {
|
||||||
event string
|
r()
|
||||||
handler Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hn *hNode) Handle(conn *Conn, line *Line) {
|
type handlerElement struct {
|
||||||
hn.handler.Handle(conn, line)
|
event string
|
||||||
|
handler Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hn *hNode) Remove() {
|
type handlerSet struct {
|
||||||
hn.set.remove(hn)
|
set map[string]*list.List
|
||||||
}
|
|
||||||
|
|
||||||
type hSet struct {
|
|
||||||
set map[string]*hList
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlerSet() *hSet {
|
func newHandlerSet() *handlerSet {
|
||||||
return &hSet{set: make(map[string]*hList)}
|
return &handlerSet{set: make(map[string]*list.List)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *hSet) add(ev string, h Handler) Remover {
|
func (hs *handlerSet) add(event string, handler Handler) (*list.Element, Remover) {
|
||||||
hs.Lock()
|
hs.Lock()
|
||||||
defer hs.Unlock()
|
defer hs.Unlock()
|
||||||
ev = strings.ToLower(ev)
|
event = strings.ToLower(event)
|
||||||
l, ok := hs.set[ev]
|
l, ok := hs.set[event]
|
||||||
if !ok {
|
if !ok {
|
||||||
l = &hList{}
|
l = list.New()
|
||||||
|
hs.set[event] = l
|
||||||
}
|
}
|
||||||
hn := &hNode{
|
element := l.PushBack(&handlerElement{event, handler})
|
||||||
set: hs,
|
return element, RemoverFunc(func() {
|
||||||
event: ev,
|
hs.remove(element)
|
||||||
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) {
|
func (hs *handlerSet) remove(element *list.Element) {
|
||||||
hs.Lock()
|
hs.Lock()
|
||||||
defer hs.Unlock()
|
defer hs.Unlock()
|
||||||
l, ok := hs.set[hn.event]
|
h := element.Value.(*handlerElement)
|
||||||
|
l, ok := hs.set[h.event]
|
||||||
if !ok {
|
if !ok {
|
||||||
logging.Error("Removing node for unknown event '%s'", hn.event)
|
logging.Error("Removing node for unknown event '%s'", h.event)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if hn.next == nil {
|
l.Remove(element)
|
||||||
l.end = hn.prev
|
if l.Len() == 0 {
|
||||||
} else {
|
delete(hs.set, h.event)
|
||||||
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) {
|
func (hs *handlerSet) dispatch(conn *Conn, line *Line) {
|
||||||
hs.RLock()
|
hs.RLock()
|
||||||
defer hs.RUnlock()
|
defer hs.RUnlock()
|
||||||
ev := strings.ToLower(line.Cmd)
|
event := strings.ToLower(line.Cmd)
|
||||||
list, ok := hs.set[ev]
|
l, ok := hs.set[event]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for hn := list.start; hn != nil; hn = hn.next {
|
|
||||||
go hn.Handle(conn, line)
|
for e := l.Front(); e != nil; e = e.Next() {
|
||||||
|
h := e.Value.(*handlerElement)
|
||||||
|
go h.handler.Handle(conn, line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An IRC command looks like this:
|
type commandElement struct {
|
||||||
type Command interface {
|
regex string
|
||||||
Execute(*Conn, *Line)
|
handler Handler
|
||||||
Help() string
|
priority int
|
||||||
}
|
}
|
||||||
|
|
||||||
type command struct {
|
type commandList struct {
|
||||||
fn HandlerFunc
|
list *list.List
|
||||||
help string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *command) Execute(conn *Conn, line *Line) {
|
|
||||||
c.fn(conn, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *command) Help() string {
|
|
||||||
return c.help
|
|
||||||
}
|
|
||||||
|
|
||||||
type cNode struct {
|
|
||||||
cmd Command
|
|
||||||
set *cSet
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *cNode) Execute(conn *Conn, line *Line) {
|
|
||||||
cn.cmd.Execute(conn, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *cNode) Help() string {
|
|
||||||
return cn.cmd.Help()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *cNode) Remove() {
|
|
||||||
cn.set.remove(cn)
|
|
||||||
}
|
|
||||||
|
|
||||||
type cSet struct {
|
|
||||||
set map[string]*cNode
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandSet() *cSet {
|
func newCommandList() *commandList {
|
||||||
return &cSet{set: make(map[string]*cNode)}
|
return &commandList{list: list.New()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *cSet) add(pf string, c Command) Remover {
|
func (cl *commandList) add(regex string, handler Handler, priority int) (element *list.Element, remover Remover) {
|
||||||
cs.Lock()
|
cl.Lock()
|
||||||
defer cs.Unlock()
|
defer cl.Unlock()
|
||||||
pf = strings.ToLower(pf)
|
c := &commandElement{
|
||||||
if _, ok := cs.set[pf]; ok {
|
regex: regex,
|
||||||
logging.Error("Command prefix '%s' already registered.", pf)
|
handler: handler,
|
||||||
return nil
|
priority: priority,
|
||||||
}
|
}
|
||||||
cn := &cNode{
|
// Check for exact regex matches. This will filter out any repeated SimpleCommands.
|
||||||
cmd: c,
|
for e := cl.list.Front(); e != nil; e = e.Next() {
|
||||||
set: cs,
|
c := e.Value.(*commandElement)
|
||||||
prefix: pf,
|
if c.regex == regex {
|
||||||
}
|
logging.Error("Command prefix '%s' already registered.", regex)
|
||||||
cs.set[pf] = cn
|
return
|
||||||
return cn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *cSet) remove(cn *cNode) {
|
|
||||||
cs.Lock()
|
|
||||||
defer cs.Unlock()
|
|
||||||
delete(cs.set, cn.prefix)
|
|
||||||
cn.set = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *cSet) match(txt string) (final Command, prefixlen int) {
|
|
||||||
cs.RLock()
|
|
||||||
defer cs.RUnlock()
|
|
||||||
txt = strings.ToLower(txt)
|
|
||||||
for prefix, cmd := range cs.set {
|
|
||||||
if !strings.HasPrefix(txt, prefix) {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if final == nil || len(prefix) > prefixlen {
|
}
|
||||||
prefixlen = len(prefix)
|
element = cl.list.PushBack(c)
|
||||||
final = cmd
|
remover = RemoverFunc(func() {
|
||||||
|
cl.remove(element)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *commandList) remove(element *list.Element) {
|
||||||
|
cl.Lock()
|
||||||
|
defer cl.Unlock()
|
||||||
|
cl.list.Remove(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches the command with the highest priority.
|
||||||
|
func (cl *commandList) match(text string) (handler Handler) {
|
||||||
|
cl.RLock()
|
||||||
|
defer cl.RUnlock()
|
||||||
|
maxPriority := math.MinInt32
|
||||||
|
text = strings.ToLower(text)
|
||||||
|
for e := cl.list.Front(); e != nil; e = e.Next() {
|
||||||
|
c := e.Value.(*commandElement)
|
||||||
|
if c.priority > maxPriority {
|
||||||
|
if regex, error := regexp.Compile(c.regex); error == nil {
|
||||||
|
if regex.MatchString(text) {
|
||||||
|
maxPriority = c.priority
|
||||||
|
handler = c.handler
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -205,26 +161,75 @@ func (cs *cSet) match(txt string) (final Command, prefixlen int) {
|
||||||
// "PRIVMSG", "JOIN", etc. but all the numeric replies are left as ascii
|
// "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
|
// strings of digits like "332" (mainly because I really didn't feel like
|
||||||
// putting massive constant tables in).
|
// putting massive constant tables in).
|
||||||
func (conn *Conn) Handle(name string, h Handler) Remover {
|
func (conn *Conn) Handle(name string, handler Handler) Remover {
|
||||||
return conn.handlers.add(name, h)
|
_, remover := conn.handlers.add(name, handler)
|
||||||
|
return remover
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
|
func (conn *Conn) HandleFunc(name string, handlerFunc HandlerFunc) Remover {
|
||||||
return conn.Handle(name, hf)
|
return conn.Handle(name, handlerFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) Command(prefix string, c Command) Remover {
|
func (conn *Conn) Command(regex string, handler Handler, priority int) Remover {
|
||||||
return conn.commands.add(prefix, c)
|
_, remover := conn.commands.add(regex, handler, priority)
|
||||||
|
return remover
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) CommandFunc(prefix string, hf HandlerFunc, help string) Remover {
|
func (conn *Conn) CommandFunc(regex string, handlerFunc HandlerFunc, priority int) Remover {
|
||||||
return conn.Command(prefix, &command{hf, help})
|
return conn.Command(regex, handlerFunc, priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SimpleCommandRegex string = `^!%v(\s|$)`
|
||||||
|
|
||||||
|
// Simple commands are commands that are triggered from a simple prefix
|
||||||
|
// SimpleCommand("roll" handler)
|
||||||
|
// !roll
|
||||||
|
// Because simple commands are simple, they get the highest priority.
|
||||||
|
func (conn *Conn) SimpleCommand(prefix string, handler Handler) Remover {
|
||||||
|
stripHandler := func(conn *Conn, line *Line) {
|
||||||
|
text := line.Message()
|
||||||
|
if conn.cfg.SimpleCommandStripPrefix {
|
||||||
|
text = strings.TrimSpace(text[len(prefix):])
|
||||||
|
}
|
||||||
|
if text != line.Message() {
|
||||||
|
line = line.Copy()
|
||||||
|
line.Args[1] = text
|
||||||
|
}
|
||||||
|
handler.Handle(conn, line)
|
||||||
|
}
|
||||||
|
return conn.CommandFunc(fmt.Sprintf(SimpleCommandRegex, strings.ToLower(prefix)), stripHandler, math.MaxInt32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) SimpleCommandFunc(prefix string, handlerFunc HandlerFunc) Remover {
|
||||||
|
return conn.SimpleCommand(prefix, handlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will also register a help command to go along with the simple command itself.
|
||||||
|
// eg. SimpleCommandHelp("bark", "Bot will bark", handler) will make the following commands:
|
||||||
|
// !bark
|
||||||
|
// !help bark
|
||||||
|
func (conn *Conn) SimpleCommandHelp(prefix string, help string, handler Handler) Remover {
|
||||||
|
commandCommand := conn.SimpleCommand(prefix, handler)
|
||||||
|
helpCommand := conn.SimpleCommandFunc(fmt.Sprintf("help %v", prefix), HandlerFunc(func(conn *Conn, line *Line) {
|
||||||
|
conn.Privmsg(line.Target(), help)
|
||||||
|
}))
|
||||||
|
return RemoverFunc(func() {
|
||||||
|
commandCommand.Remove()
|
||||||
|
helpCommand.Remove()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) SimpleCommandHelpFunc(prefix string, help string, handlerFunc HandlerFunc) Remover {
|
||||||
|
return conn.SimpleCommandHelp(prefix, help, handlerFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) dispatch(line *Line) {
|
func (conn *Conn) dispatch(line *Line) {
|
||||||
conn.handlers.dispatch(conn, line)
|
conn.handlers.dispatch(conn, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) cmdMatch(txt string) (Command, int) {
|
func (conn *Conn) command(line *Line) {
|
||||||
return conn.commands.match(txt)
|
command := conn.commands.match(line.Message())
|
||||||
|
if command != nil {
|
||||||
|
go command.Handle(conn, line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHandlerSet(t *testing.T) {
|
func TestHandlerSet(t *testing.T) {
|
||||||
hs := handlerSet()
|
hs := newHandlerSet()
|
||||||
if len(hs.set) != 0 {
|
if len(hs.set) != 0 {
|
||||||
t.Errorf("New set contains things!")
|
t.Errorf("New set contains things!")
|
||||||
}
|
}
|
||||||
|
@ -17,66 +17,40 @@ func TestHandlerSet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add one
|
// Add one
|
||||||
hn1 := hs.add("ONE", HandlerFunc(f)).(*hNode)
|
_, hn1 := hs.add("ONE", HandlerFunc(f))
|
||||||
hl, ok := hs.set["one"]
|
hl, ok := hs.set["one"]
|
||||||
if len(hs.set) != 1 || !ok {
|
if len(hs.set) != 1 || !ok {
|
||||||
t.Errorf("Set doesn't contain 'one' list after add().")
|
t.Errorf("Set doesn't contain 'one' list after add().")
|
||||||
}
|
}
|
||||||
if hn1.set != hs || hn1.event != "one" || hn1.prev != nil || hn1.next != nil {
|
if hl.Len() != 1 {
|
||||||
t.Errorf("First node for 'one' not created correctly")
|
t.Errorf("List doesn't contain 'one' after add().")
|
||||||
}
|
|
||||||
if hl.start != hn1 || hl.end != hn1 {
|
|
||||||
t.Errorf("Node not added to empty 'one' list correctly.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add another one...
|
// Add another one...
|
||||||
hn2 := hs.add("one", HandlerFunc(f)).(*hNode)
|
_, hn2 := hs.add("one", HandlerFunc(f))
|
||||||
if len(hs.set) != 1 {
|
if len(hs.set) != 1 {
|
||||||
t.Errorf("Set contains more than 'one' list after add().")
|
t.Errorf("Set contains more than 'one' list after add().")
|
||||||
}
|
}
|
||||||
if hn2.set != hs || hn2.event != "one" {
|
if hl.Len() != 2 {
|
||||||
t.Errorf("Second node for 'one' not created correctly")
|
t.Errorf("List doesn't contain second 'one' after add().")
|
||||||
}
|
|
||||||
if hn1.prev != nil || hn1.next != hn2 || hn2.prev != hn1 || hn2.next != nil {
|
|
||||||
t.Errorf("Nodes for 'one' not linked correctly.")
|
|
||||||
}
|
|
||||||
if hl.start != hn1 || hl.end != hn2 {
|
|
||||||
t.Errorf("Node not appended to 'one' list correctly.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a third one!
|
// Add a third one!
|
||||||
hn3 := hs.add("one", HandlerFunc(f)).(*hNode)
|
_, hn3 := hs.add("one", HandlerFunc(f))
|
||||||
if len(hs.set) != 1 {
|
if len(hs.set) != 1 {
|
||||||
t.Errorf("Set contains more than 'one' list after add().")
|
t.Errorf("Set contains more than 'one' list after add().")
|
||||||
}
|
}
|
||||||
if hn3.set != hs || hn3.event != "one" {
|
if hl.Len() != 3 {
|
||||||
t.Errorf("Third node for 'one' not created correctly")
|
t.Errorf("List doesn't contain third 'one' after add().")
|
||||||
}
|
|
||||||
if hn1.prev != nil || hn1.next != hn2 ||
|
|
||||||
hn2.prev != hn1 || hn2.next != hn3 ||
|
|
||||||
hn3.prev != hn2 || hn3.next != nil {
|
|
||||||
t.Errorf("Nodes for 'one' not linked correctly.")
|
|
||||||
}
|
|
||||||
if hl.start != hn1 || hl.end != hn3 {
|
|
||||||
t.Errorf("Node not appended to 'one' list correctly.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// And finally a fourth one!
|
// And finally a fourth one!
|
||||||
hn4 := hs.add("one", HandlerFunc(f)).(*hNode)
|
_, hn4 := hs.add("one", HandlerFunc(f))
|
||||||
if len(hs.set) != 1 {
|
if len(hs.set) != 1 {
|
||||||
t.Errorf("Set contains more than 'one' list after add().")
|
t.Errorf("Set contains more than 'one' list after add().")
|
||||||
}
|
}
|
||||||
if hn4.set != hs || hn4.event != "one" {
|
if hl.Len() != 4 {
|
||||||
t.Errorf("Fourth node for 'one' not created correctly.")
|
t.Errorf("List doesn't contain fourth 'one' after add().")
|
||||||
}
|
|
||||||
if hn1.prev != nil || hn1.next != hn2 ||
|
|
||||||
hn2.prev != hn1 || hn2.next != hn3 ||
|
|
||||||
hn3.prev != hn2 || hn3.next != hn4 ||
|
|
||||||
hn4.prev != hn3 || hn4.next != nil {
|
|
||||||
t.Errorf("Nodes for 'one' not linked correctly.")
|
|
||||||
}
|
|
||||||
if hl.start != hn1 || hl.end != hn4 {
|
|
||||||
t.Errorf("Node not appended to 'one' list correctly.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch should result in 4 additions.
|
// Dispatch should result in 4 additions.
|
||||||
|
@ -94,16 +68,8 @@ func TestHandlerSet(t *testing.T) {
|
||||||
if len(hs.set) != 1 {
|
if len(hs.set) != 1 {
|
||||||
t.Errorf("Set list count changed after remove().")
|
t.Errorf("Set list count changed after remove().")
|
||||||
}
|
}
|
||||||
if hn3.set != nil || hn3.prev != nil || hn3.next != nil {
|
if hl.Len() != 3 {
|
||||||
t.Errorf("Third node for 'one' not removed correctly.")
|
t.Errorf("Third 'one' not removed correctly.")
|
||||||
}
|
|
||||||
if hn1.prev != nil || hn1.next != hn2 ||
|
|
||||||
hn2.prev != hn1 || hn2.next != hn4 ||
|
|
||||||
hn4.prev != hn2 || hn4.next != nil {
|
|
||||||
t.Errorf("Third node for 'one' not unlinked correctly.")
|
|
||||||
}
|
|
||||||
if hl.start != hn1 || hl.end != hn4 {
|
|
||||||
t.Errorf("Third node for 'one' changed list pointers.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch should result in 3 additions.
|
// Dispatch should result in 3 additions.
|
||||||
|
@ -114,18 +80,12 @@ func TestHandlerSet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove node 1.
|
// Remove node 1.
|
||||||
hs.remove(hn1)
|
hn1.Remove()
|
||||||
if len(hs.set) != 1 {
|
if len(hs.set) != 1 {
|
||||||
t.Errorf("Set list count changed after remove().")
|
t.Errorf("Set list count changed after remove().")
|
||||||
}
|
}
|
||||||
if hn1.set != nil || hn1.prev != nil || hn1.next != nil {
|
if hl.Len() != 2 {
|
||||||
t.Errorf("First node for 'one' not removed correctly.")
|
t.Errorf("First 'one' not removed correctly.")
|
||||||
}
|
|
||||||
if hn2.prev != nil || hn2.next != hn4 || hn4.prev != hn2 || hn4.next != nil {
|
|
||||||
t.Errorf("First node for 'one' not unlinked correctly.")
|
|
||||||
}
|
|
||||||
if hl.start != hn2 || hl.end != hn4 {
|
|
||||||
t.Errorf("First node for 'one' didn't change list pointers.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch should result in 2 additions.
|
// Dispatch should result in 2 additions.
|
||||||
|
@ -140,14 +100,8 @@ func TestHandlerSet(t *testing.T) {
|
||||||
if len(hs.set) != 1 {
|
if len(hs.set) != 1 {
|
||||||
t.Errorf("Set list count changed after remove().")
|
t.Errorf("Set list count changed after remove().")
|
||||||
}
|
}
|
||||||
if hn4.set != nil || hn4.prev != nil || hn4.next != nil {
|
if hl.Len() != 1 {
|
||||||
t.Errorf("Fourth node for 'one' not removed correctly.")
|
t.Errorf("Fourth 'one' not removed correctly.")
|
||||||
}
|
|
||||||
if hn2.prev != nil || hn2.next != nil {
|
|
||||||
t.Errorf("Fourth node for 'one' not unlinked correctly.")
|
|
||||||
}
|
|
||||||
if hl.start != hn2 || hl.end != hn2 {
|
|
||||||
t.Errorf("Fourth node for 'one' didn't change list pointers.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch should result in 1 addition.
|
// Dispatch should result in 1 addition.
|
||||||
|
@ -158,16 +112,10 @@ func TestHandlerSet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove node 2.
|
// Remove node 2.
|
||||||
hs.remove(hn2)
|
hn2.Remove()
|
||||||
if len(hs.set) != 0 {
|
if len(hs.set) != 0 {
|
||||||
t.Errorf("Removing last node in 'one' didn't remove list.")
|
t.Errorf("Removing last node in 'one' didn't remove list.")
|
||||||
}
|
}
|
||||||
if hn2.set != nil || hn2.prev != nil || hn2.next != nil {
|
|
||||||
t.Errorf("Second node for 'one' not removed correctly.")
|
|
||||||
}
|
|
||||||
if hl.start != nil || hl.end != nil {
|
|
||||||
t.Errorf("Second node for 'one' didn't change list pointers.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch should result in NO additions.
|
// Dispatch should result in NO additions.
|
||||||
hs.dispatch(nil, &Line{Cmd: "One"})
|
hs.dispatch(nil, &Line{Cmd: "One"})
|
||||||
|
@ -178,52 +126,43 @@ func TestHandlerSet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSet(t *testing.T) {
|
func TestCommandSet(t *testing.T) {
|
||||||
cs := commandSet()
|
cl := newCommandList()
|
||||||
if len(cs.set) != 0 {
|
if cl.list.Len() != 0 {
|
||||||
t.Errorf("New set contains things!")
|
t.Errorf("New list contains things!")
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &command{
|
_, cn1 := cl.add("one", HandlerFunc(func(c *Conn, l *Line) {}), 0)
|
||||||
fn: func(c *Conn, l *Line) {},
|
if cl.list.Len() != 1 {
|
||||||
help: "wtf?",
|
t.Errorf("Command 'one' not added to list correctly.")
|
||||||
}
|
}
|
||||||
|
|
||||||
cn1 := cs.add("ONE", c).(*cNode)
|
_, cn2 := cl.add("one two", HandlerFunc(func(c *Conn, l *Line) {}), 0)
|
||||||
if _, ok := cs.set["one"]; !ok || cn1.set != cs || cn1.prefix != "one" {
|
if cl.list.Len() != 2 {
|
||||||
t.Errorf("Command 'one' not added to set correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fail := cs.add("one", c); fail != nil {
|
|
||||||
t.Errorf("Adding a second 'one' command did not fail as expected.")
|
|
||||||
}
|
|
||||||
|
|
||||||
cn2 := cs.add("One Two", c).(*cNode)
|
|
||||||
if _, ok := cs.set["one two"]; !ok || cn2.set != cs || cn2.prefix != "one two" {
|
|
||||||
t.Errorf("Command 'one two' not added to set correctly.")
|
t.Errorf("Command 'one two' not added to set correctly.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c, l := cs.match("foo"); c != nil || l != 0 {
|
if c := cl.match("foo"); c != nil {
|
||||||
t.Errorf("Matched 'foo' when we shouldn't.")
|
t.Errorf("Matched 'foo' when we shouldn't.")
|
||||||
}
|
}
|
||||||
if c, l := cs.match("one"); c.(*cNode) != cn1 || l != 3 {
|
if c := cl.match("one"); c == nil {
|
||||||
t.Errorf("Didn't match 'one' when we should have.")
|
t.Errorf("Didn't match when we should have.")
|
||||||
}
|
}
|
||||||
if c, l := cs.match("one two three"); c.(*cNode) != cn2 || l != 7 {
|
if c := cl.match("one two three"); c == nil {
|
||||||
t.Errorf("Didn't match 'one two' when we should have.")
|
t.Errorf("Didn't match when we should have.")
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.remove(cn2)
|
cn2.Remove()
|
||||||
if _, ok := cs.set["one two"]; ok || cn2.set != nil {
|
if cl.list.Len() != 1 {
|
||||||
t.Errorf("Command 'one two' not removed correctly.")
|
t.Errorf("Command 'one two' not removed correctly.")
|
||||||
}
|
}
|
||||||
if c, l := cs.match("one two three"); c.(*cNode) != cn1 || l != 3 {
|
if c := cl.match("one two three"); c == nil {
|
||||||
t.Errorf("Didn't match 'one' when we should have.")
|
t.Errorf("Didn't match when we should have.")
|
||||||
}
|
}
|
||||||
cn1.Remove()
|
cn1.Remove()
|
||||||
if _, ok := cs.set["one"]; ok || cn1.set != nil {
|
if cl.list.Len() != 0 {
|
||||||
t.Errorf("Command 'one' not removed correctly.")
|
t.Errorf("Command 'one two' not removed correctly.")
|
||||||
}
|
}
|
||||||
if c, l := cs.match("one two three"); c != nil || l != 0 {
|
if c := cl.match("one two three"); c != nil {
|
||||||
t.Errorf("Matched 'one' when we shouldn't have.")
|
t.Errorf("Matched 'one' when we shouldn't.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type youTubeVideo struct {
|
||||||
|
Entry struct {
|
||||||
|
Info struct {
|
||||||
|
Title struct {
|
||||||
|
Text string `json:"$t"`
|
||||||
|
} `json:"media$title"`
|
||||||
|
Description struct {
|
||||||
|
Text string `json:"$t"`
|
||||||
|
} `json:"media$description"`
|
||||||
|
} `json:"media$group"`
|
||||||
|
Rating struct {
|
||||||
|
Likes string `json:"numLikes"`
|
||||||
|
Dislikes string `json:"numDislikes"`
|
||||||
|
} `json:"yt$rating"`
|
||||||
|
Statistics struct {
|
||||||
|
Views string `json:"viewCount"`
|
||||||
|
} `json:"yt$statistics"`
|
||||||
|
} `json:entry`
|
||||||
|
}
|
||||||
|
|
||||||
|
const UrlRegex string = `(\s|^)(http://|https://)(.*?)(\s|$)`
|
||||||
|
|
||||||
|
func UrlFunc(conn *Conn, line *Line) {
|
||||||
|
text := line.Message()
|
||||||
|
if regex, err := regexp.Compile(UrlRegex); err == nil {
|
||||||
|
url := strings.TrimSpace(regex.FindString(text))
|
||||||
|
if url != "" {
|
||||||
|
if resp, err := http.Get(url); err == nil {
|
||||||
|
if strings.HasPrefix(resp.Header.Get("Content-Type"), "text/html") {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if content, err := ioutil.ReadAll(resp.Body); err == nil {
|
||||||
|
if regex, err := regexp.Compile(`<title>(.*?)</title>`); err == nil {
|
||||||
|
if regex.Match([]byte(content)) {
|
||||||
|
conn.Privmsg(line.Target(), strings.TrimSpace(regex.FindStringSubmatch(string(content))[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const YouTubeRegex string = `(\s|^)(http://|https://)?(www.)?(youtube.com/watch\?v=|youtu.be/)(.*?)(\s|$|\&|#)`
|
||||||
|
|
||||||
|
func YouTubeFunc(conn *Conn, line *Line) {
|
||||||
|
text := line.Message()
|
||||||
|
if regex, err := regexp.Compile(YouTubeRegex); err == nil {
|
||||||
|
if regex.Match([]byte(text)) {
|
||||||
|
matches := regex.FindStringSubmatch(text)
|
||||||
|
id := matches[len(matches)-2]
|
||||||
|
url := fmt.Sprintf("https://gdata.youtube.com/feeds/api/videos/%s?v=2&alt=json", id)
|
||||||
|
if resp, err := http.Get(url); err == nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if contents, err := ioutil.ReadAll(resp.Body); err == nil {
|
||||||
|
var data youTubeVideo
|
||||||
|
if err := json.Unmarshal(contents, &data); err == nil {
|
||||||
|
conn.Privmsg(line.Target(), fmt.Sprintf("%s - %s views (%s likes, %s dislikes)", data.Entry.Info.Title.Text, data.Entry.Statistics.Views, data.Entry.Rating.Likes, data.Entry.Rating.Dislikes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,20 +7,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
REGISTER = "REGISTER"
|
|
||||||
CONNECTED = "CONNECTED"
|
|
||||||
DISCONNECTED = "DISCONNECTED"
|
|
||||||
)
|
|
||||||
|
|
||||||
// sets up the internal event handlers to do essential IRC protocol things
|
// sets up the internal event handlers to do essential IRC protocol things
|
||||||
var intHandlers = map[string]HandlerFunc{
|
var intHandlers = map[string]HandlerFunc{
|
||||||
REGISTER: (*Conn).h_REGISTER,
|
REGISTER: (*Conn).h_REGISTER,
|
||||||
"001": (*Conn).h_001,
|
"001": (*Conn).h_001,
|
||||||
"433": (*Conn).h_433,
|
"433": (*Conn).h_433,
|
||||||
"CTCP": (*Conn).h_CTCP,
|
CTCP: (*Conn).h_CTCP,
|
||||||
"NICK": (*Conn).h_NICK,
|
NICK: (*Conn).h_NICK,
|
||||||
"PING": (*Conn).h_PING,
|
PING: (*Conn).h_PING,
|
||||||
|
PRIVMSG: (*Conn).h_PRIVMSG,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) addIntHandlers() {
|
func (conn *Conn) addIntHandlers() {
|
||||||
|
@ -33,7 +28,7 @@ func (conn *Conn) addIntHandlers() {
|
||||||
|
|
||||||
// Basic ping/pong handler
|
// Basic ping/pong handler
|
||||||
func (conn *Conn) h_PING(line *Line) {
|
func (conn *Conn) h_PING(line *Line) {
|
||||||
conn.Raw("PONG :" + line.Args[0])
|
conn.Pong(line.Args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler for initial registration with server once tcp connection is made.
|
// Handler for initial registration with server once tcp connection is made.
|
||||||
|
@ -86,10 +81,10 @@ func (conn *Conn) h_433(line *Line) {
|
||||||
|
|
||||||
// Handle VERSION requests and CTCP PING
|
// Handle VERSION requests and CTCP PING
|
||||||
func (conn *Conn) h_CTCP(line *Line) {
|
func (conn *Conn) h_CTCP(line *Line) {
|
||||||
if line.Args[0] == "VERSION" {
|
if line.Args[0] == VERSION {
|
||||||
conn.CtcpReply(line.Nick, "VERSION", "powered by goirc...")
|
conn.CtcpReply(line.Nick, VERSION, "powered by goirc...")
|
||||||
} else if line.Args[0] == "PING" {
|
} else if line.Args[0] == PING {
|
||||||
conn.CtcpReply(line.Nick, "PING", line.Args[2])
|
conn.CtcpReply(line.Nick, PING, line.Args[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,34 +97,19 @@ func (conn *Conn) h_NICK(line *Line) {
|
||||||
|
|
||||||
// Handle PRIVMSGs that trigger Commands
|
// Handle PRIVMSGs that trigger Commands
|
||||||
func (conn *Conn) h_PRIVMSG(line *Line) {
|
func (conn *Conn) h_PRIVMSG(line *Line) {
|
||||||
txt := line.Args[1]
|
text := line.Message()
|
||||||
if conn.cfg.CommandStripNick && strings.HasPrefix(txt, conn.cfg.Me.Nick) {
|
if conn.cfg.CommandStripNick && strings.HasPrefix(text, conn.cfg.Me.Nick) {
|
||||||
// Look for '^${nick}[:;>,-]? '
|
// Look for '^${nick}[:;>,-]? '
|
||||||
l := len(conn.cfg.Me.Nick)
|
l := len(conn.cfg.Me.Nick)
|
||||||
switch txt[l] {
|
switch text[l] {
|
||||||
case ':', ';', '>', ',', '-':
|
case ':', ';', '>', ',', '-':
|
||||||
l++
|
l++
|
||||||
}
|
}
|
||||||
if txt[l] == ' ' {
|
if text[l] == ' ' {
|
||||||
txt = strings.TrimSpace(txt[l:])
|
text = strings.TrimSpace(text[l:])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
cmd, l := conn.cmdMatch(txt)
|
|
||||||
if cmd == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if conn.cfg.CommandStripPrefix {
|
|
||||||
txt = strings.TrimSpace(txt[l:])
|
|
||||||
}
|
|
||||||
if txt != line.Args[1] {
|
|
||||||
line = line.Copy()
|
line = line.Copy()
|
||||||
line.Args[1] = txt
|
line.Args[1] = text
|
||||||
}
|
|
||||||
cmd.Execute(conn, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *Conn) c_HELP(line *Line) {
|
|
||||||
if cmd, _ := conn.cmdMatch(line.Args[1]); cmd != nil {
|
|
||||||
conn.Privmsg(line.Args[0], cmd.Help())
|
|
||||||
}
|
}
|
||||||
|
conn.command(line)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ func Test001(t *testing.T) {
|
||||||
l := parseLine(":irc.server.org 001 test :Welcome to IRC test!ident@somehost.com")
|
l := parseLine(":irc.server.org 001 test :Welcome to IRC test!ident@somehost.com")
|
||||||
// Set up a handler to detect whether connected handler is called from 001
|
// Set up a handler to detect whether connected handler is called from 001
|
||||||
hcon := false
|
hcon := false
|
||||||
c.HandleFunc("connected", func(conn *Conn, line *Line) {
|
c.HandleFunc(CONNECTED, func(conn *Conn, line *Line) {
|
||||||
hcon = true
|
hcon = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -166,7 +166,9 @@ func TestPRIVMSG(t *testing.T) {
|
||||||
f := func(conn *Conn, line *Line) {
|
f := func(conn *Conn, line *Line) {
|
||||||
conn.Privmsg(line.Args[0], line.Args[1])
|
conn.Privmsg(line.Args[0], line.Args[1])
|
||||||
}
|
}
|
||||||
c.CommandFunc("prefix", f, "")
|
// Test legacy simpleCommands, with out the !.
|
||||||
|
SimpleCommandRegex = `^%v(\s|$)`
|
||||||
|
c.SimpleCommandFunc("prefix", f)
|
||||||
|
|
||||||
// CommandStripNick and CommandStripPrefix are both false to begin
|
// CommandStripNick and CommandStripPrefix are both false to begin
|
||||||
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar"))
|
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar"))
|
||||||
|
@ -183,7 +185,7 @@ func TestPRIVMSG(t *testing.T) {
|
||||||
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar"))
|
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar"))
|
||||||
s.nc.Expect("PRIVMSG #foo :prefix bar")
|
s.nc.Expect("PRIVMSG #foo :prefix bar")
|
||||||
|
|
||||||
c.cfg.CommandStripPrefix = true
|
c.cfg.SimpleCommandStripPrefix = true
|
||||||
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar"))
|
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar"))
|
||||||
s.nc.Expect("PRIVMSG #foo :bar")
|
s.nc.Expect("PRIVMSG #foo :bar")
|
||||||
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar"))
|
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar"))
|
||||||
|
|
|
@ -62,7 +62,7 @@ func parseLine(s string) *Line {
|
||||||
// So, I think CTCP and (in particular) CTCP ACTION are better handled as
|
// So, I think CTCP and (in particular) CTCP ACTION are better handled as
|
||||||
// separate events as opposed to forcing people to have gargantuan
|
// separate events as opposed to forcing people to have gargantuan
|
||||||
// handlers to cope with the possibilities.
|
// handlers to cope with the possibilities.
|
||||||
if (line.Cmd == "PRIVMSG" || line.Cmd == "NOTICE") &&
|
if (line.Cmd == PRIVMSG || line.Cmd == NOTICE) &&
|
||||||
len(line.Args[1]) > 2 &&
|
len(line.Args[1]) > 2 &&
|
||||||
strings.HasPrefix(line.Args[1], "\001") &&
|
strings.HasPrefix(line.Args[1], "\001") &&
|
||||||
strings.HasSuffix(line.Args[1], "\001") {
|
strings.HasSuffix(line.Args[1], "\001") {
|
||||||
|
@ -72,19 +72,42 @@ func parseLine(s string) *Line {
|
||||||
// Replace the line with the unwrapped CTCP
|
// Replace the line with the unwrapped CTCP
|
||||||
line.Args[1] = t[1]
|
line.Args[1] = t[1]
|
||||||
}
|
}
|
||||||
if c := strings.ToUpper(t[0]); c == "ACTION" && line.Cmd == "PRIVMSG" {
|
if c := strings.ToUpper(t[0]); c == ACTION && line.Cmd == PRIVMSG {
|
||||||
// make a CTCP ACTION it's own event a-la PRIVMSG
|
// make a CTCP ACTION it's own event a-la PRIVMSG
|
||||||
line.Cmd = c
|
line.Cmd = c
|
||||||
} else {
|
} else {
|
||||||
// otherwise, dispatch a generic CTCP/CTCPREPLY event that
|
// otherwise, dispatch a generic CTCP/CTCPREPLY event that
|
||||||
// contains the type of CTCP in line.Args[0]
|
// contains the type of CTCP in line.Args[0]
|
||||||
if line.Cmd == "PRIVMSG" {
|
if line.Cmd == PRIVMSG {
|
||||||
line.Cmd = "CTCP"
|
line.Cmd = CTCP
|
||||||
} else {
|
} else {
|
||||||
line.Cmd = "CTCPREPLY"
|
line.Cmd = CTCPREPLY
|
||||||
}
|
}
|
||||||
line.Args = append([]string{c}, line.Args...)
|
line.Args = append([]string{c}, line.Args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the contents of the message portion of a line.
|
||||||
|
// This only really makes sense for messages with a :message portion, but there
|
||||||
|
// are a lot of them.
|
||||||
|
func (line *Line) Message() string {
|
||||||
|
if len(line.Args) > 0 {
|
||||||
|
return line.Args[len(line.Args)-1]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the target of the line. This only really makes sense for PRIVMSG.
|
||||||
|
// If the line was broadcast from a channel, the target will be that channel.
|
||||||
|
// If the line was broadcast by a user, the target will be that user.
|
||||||
|
func (line *Line) Target() string {
|
||||||
|
if line.Cmd == PRIVMSG {
|
||||||
|
if !strings.HasPrefix(line.Args[0], "#") {
|
||||||
|
return line.Nick
|
||||||
|
}
|
||||||
|
return line.Args[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue