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!
|
||||
// e.g. join a channel on connect.
|
||||
c.HandleFunc("connected",
|
||||
c.HandleFunc(irc.CONNECTED,
|
||||
func(conn *irc.Conn, line *irc.Line) { conn.Join("#channel") })
|
||||
// And a signal on disconnect
|
||||
quit := make(chan bool)
|
||||
c.HandleFunc("disconnected",
|
||||
c.HandleFunc(irc.DISCONNECTED,
|
||||
func(conn *irc.Conn, line *irc.Line) { quit <- true })
|
||||
|
||||
// Tell client to connect.
|
||||
|
|
35
client.go
35
client.go
|
@ -5,7 +5,9 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
irc "github.com/fluffle/goirc/client"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -18,14 +20,40 @@ func main() {
|
|||
// create new IRC connection
|
||||
c := irc.SimpleClient("GoTest", "gotest")
|
||||
c.EnableStateTracking()
|
||||
c.HandleFunc("connected",
|
||||
c.HandleFunc(irc.CONNECTED,
|
||||
func(conn *irc.Conn, line *irc.Line) { conn.Join(*channel) })
|
||||
|
||||
// Set up a handler to notify of disconnect events.
|
||||
quit := make(chan bool)
|
||||
c.HandleFunc("disconnected",
|
||||
c.HandleFunc(irc.DISCONNECTED,
|
||||
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
|
||||
in := make(chan string, 4)
|
||||
reallyquit := false
|
||||
|
@ -36,6 +64,8 @@ func main() {
|
|||
if err != nil {
|
||||
// wha?, maybe ctrl-D...
|
||||
close(in)
|
||||
reallyquit = true
|
||||
c.Quit("")
|
||||
break
|
||||
}
|
||||
// no point in sending empty lines down the channel
|
||||
|
@ -85,7 +115,6 @@ func main() {
|
|||
fmt.Printf("Connection error: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// wait on quit channel
|
||||
<-quit
|
||||
}
|
||||
|
|
|
@ -2,6 +2,34 @@ package client
|
|||
|
||||
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
|
||||
// send to the server using an Conn connection
|
||||
|
||||
|
@ -14,18 +42,18 @@ import "strings"
|
|||
func (conn *Conn) Raw(rawline string) { conn.out <- rawline }
|
||||
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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
|
||||
func (conn *Conn) Part(channel string, message ...string) {
|
||||
|
@ -33,7 +61,7 @@ func (conn *Conn) Part(channel string, message ...string) {
|
|||
if 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
|
||||
|
@ -42,7 +70,7 @@ func (conn *Conn) Kick(channel, nick string, message ...string) {
|
|||
if 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
|
||||
|
@ -51,20 +79,20 @@ func (conn *Conn) Quit(message ...string) {
|
|||
if msg == "" {
|
||||
msg = "GoBye!"
|
||||
}
|
||||
conn.out <- "QUIT :" + msg
|
||||
conn.out <- QUIT + " :" + msg
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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
|
||||
// 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
|
||||
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
|
||||
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(channel) retrieves the current channel topic (see "332" handler)
|
||||
|
@ -100,7 +128,7 @@ func (conn *Conn) Topic(channel string, topic ...string) {
|
|||
if 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
|
||||
|
@ -115,7 +143,7 @@ func (conn *Conn) Mode(t string, modestring ...string) {
|
|||
if mode != "" {
|
||||
mode = " " + mode
|
||||
}
|
||||
conn.out <- "MODE " + t + mode
|
||||
conn.out <- MODE + " " + t + mode
|
||||
}
|
||||
|
||||
// Away() sends an AWAY command to the server
|
||||
|
@ -126,15 +154,18 @@ func (conn *Conn) Away(message ...string) {
|
|||
if msg != "" {
|
||||
msg = " :" + msg
|
||||
}
|
||||
conn.out <- "AWAY" + msg
|
||||
conn.out <- AWAY + msg
|
||||
}
|
||||
|
||||
// Invite() sends an INVITE command to the server
|
||||
func (conn *Conn) Invite(nick, channel string) {
|
||||
conn.out <- "INVITE " + nick + " " + channel
|
||||
}
|
||||
func (conn *Conn) Invite(nick, channel string) { conn.out <- INVITE + " " + nick + " " + channel }
|
||||
|
||||
// Oper() sends an OPER command to the server
|
||||
func (conn *Conn) Oper(user, pass string) {
|
||||
conn.out <- "OPER " + user + " " + pass
|
||||
}
|
||||
func (conn *Conn) Oper(user, pass string) { 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")
|
||||
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
|
||||
|
||||
// Handlers and Commands
|
||||
handlers *hSet
|
||||
commands *cSet
|
||||
handlers *handlerSet
|
||||
commands *commandList
|
||||
|
||||
// State tracker for nicks and channels
|
||||
st state.Tracker
|
||||
|
@ -62,7 +62,7 @@ type Config struct {
|
|||
PingFreq time.Duration
|
||||
|
||||
// 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
|
||||
Flood bool
|
||||
|
@ -104,8 +104,8 @@ func Client(cfg *Config) (*Conn, error) {
|
|||
cSend: make(chan bool),
|
||||
cLoop: make(chan bool),
|
||||
cPing: make(chan bool),
|
||||
handlers: handlerSet(),
|
||||
commands: commandSet(),
|
||||
handlers: newHandlerSet(),
|
||||
commands: newCommandList(),
|
||||
stRemovers: make([]Remover, 0, len(stHandlers)),
|
||||
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.
|
||||
// NOTE: the value of the boolean doesn't matter.
|
||||
c.postConnect()
|
||||
|
||||
// Sleep 1ms to allow background routines to start.
|
||||
<-time.After(1e6)
|
||||
}
|
||||
|
@ -56,7 +57,7 @@ func TestEOF(t *testing.T) {
|
|||
|
||||
// Set up a handler to detect whether disconnected handlers are called
|
||||
dcon := false
|
||||
c.HandleFunc("disconnected", func(conn *Conn, line *Line) {
|
||||
c.HandleFunc(DISCONNECTED, func(conn *Conn, line *Line) {
|
||||
dcon = true
|
||||
})
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"github.com/fluffle/golog/logging"
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
@ -11,189 +15,141 @@ type Handler interface {
|
|||
Handle(*Conn, *Line)
|
||||
}
|
||||
|
||||
// And when they've been added to the client they are removable.
|
||||
type Remover interface {
|
||||
Remove()
|
||||
}
|
||||
|
||||
type HandlerFunc func(*Conn, *Line)
|
||||
|
||||
func (hf HandlerFunc) Handle(conn *Conn, line *Line) {
|
||||
hf(conn, line)
|
||||
}
|
||||
|
||||
type hList struct {
|
||||
start, end *hNode
|
||||
// And when they've been added to the client they are removable.
|
||||
type Remover interface {
|
||||
Remove()
|
||||
}
|
||||
|
||||
type hNode struct {
|
||||
next, prev *hNode
|
||||
set *hSet
|
||||
event string
|
||||
handler Handler
|
||||
type RemoverFunc func()
|
||||
|
||||
func (r RemoverFunc) Remove() {
|
||||
r()
|
||||
}
|
||||
|
||||
func (hn *hNode) Handle(conn *Conn, line *Line) {
|
||||
hn.handler.Handle(conn, line)
|
||||
type handlerElement struct {
|
||||
event string
|
||||
handler Handler
|
||||
}
|
||||
|
||||
func (hn *hNode) Remove() {
|
||||
hn.set.remove(hn)
|
||||
}
|
||||
|
||||
type hSet struct {
|
||||
set map[string]*hList
|
||||
type handlerSet struct {
|
||||
set map[string]*list.List
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func handlerSet() *hSet {
|
||||
return &hSet{set: make(map[string]*hList)}
|
||||
func newHandlerSet() *handlerSet {
|
||||
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()
|
||||
defer hs.Unlock()
|
||||
ev = strings.ToLower(ev)
|
||||
l, ok := hs.set[ev]
|
||||
event = strings.ToLower(event)
|
||||
l, ok := hs.set[event]
|
||||
if !ok {
|
||||
l = &hList{}
|
||||
l = list.New()
|
||||
hs.set[event] = l
|
||||
}
|
||||
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
|
||||
element := l.PushBack(&handlerElement{event, handler})
|
||||
return element, RemoverFunc(func() {
|
||||
hs.remove(element)
|
||||
})
|
||||
}
|
||||
|
||||
func (hs *hSet) remove(hn *hNode) {
|
||||
func (hs *handlerSet) remove(element *list.Element) {
|
||||
hs.Lock()
|
||||
defer hs.Unlock()
|
||||
l, ok := hs.set[hn.event]
|
||||
h := element.Value.(*handlerElement)
|
||||
l, ok := hs.set[h.event]
|
||||
if !ok {
|
||||
logging.Error("Removing node for unknown event '%s'", hn.event)
|
||||
logging.Error("Removing node for unknown event '%s'", h.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)
|
||||
l.Remove(element)
|
||||
if l.Len() == 0 {
|
||||
delete(hs.set, h.event)
|
||||
}
|
||||
}
|
||||
|
||||
func (hs *hSet) dispatch(conn *Conn, line *Line) {
|
||||
func (hs *handlerSet) dispatch(conn *Conn, line *Line) {
|
||||
hs.RLock()
|
||||
defer hs.RUnlock()
|
||||
ev := strings.ToLower(line.Cmd)
|
||||
list, ok := hs.set[ev]
|
||||
event := strings.ToLower(line.Cmd)
|
||||
l, ok := hs.set[event]
|
||||
if !ok {
|
||||
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 Command interface {
|
||||
Execute(*Conn, *Line)
|
||||
Help() string
|
||||
type commandElement struct {
|
||||
regex string
|
||||
handler Handler
|
||||
priority int
|
||||
}
|
||||
|
||||
type command struct {
|
||||
fn HandlerFunc
|
||||
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
|
||||
type commandList struct {
|
||||
list *list.List
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func commandSet() *cSet {
|
||||
return &cSet{set: make(map[string]*cNode)}
|
||||
func newCommandList() *commandList {
|
||||
return &commandList{list: list.New()}
|
||||
}
|
||||
|
||||
func (cs *cSet) add(pf string, c Command) Remover {
|
||||
cs.Lock()
|
||||
defer cs.Unlock()
|
||||
pf = strings.ToLower(pf)
|
||||
if _, ok := cs.set[pf]; ok {
|
||||
logging.Error("Command prefix '%s' already registered.", pf)
|
||||
return nil
|
||||
func (cl *commandList) add(regex string, handler Handler, priority int) (element *list.Element, remover Remover) {
|
||||
cl.Lock()
|
||||
defer cl.Unlock()
|
||||
c := &commandElement{
|
||||
regex: regex,
|
||||
handler: handler,
|
||||
priority: priority,
|
||||
}
|
||||
cn := &cNode{
|
||||
cmd: c,
|
||||
set: cs,
|
||||
prefix: pf,
|
||||
}
|
||||
cs.set[pf] = cn
|
||||
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
|
||||
// Check for exact regex matches. This will filter out any repeated SimpleCommands.
|
||||
for e := cl.list.Front(); e != nil; e = e.Next() {
|
||||
c := e.Value.(*commandElement)
|
||||
if c.regex == regex {
|
||||
logging.Error("Command prefix '%s' already registered.", regex)
|
||||
return
|
||||
}
|
||||
if final == nil || len(prefix) > prefixlen {
|
||||
prefixlen = len(prefix)
|
||||
final = cmd
|
||||
}
|
||||
element = cl.list.PushBack(c)
|
||||
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
|
||||
|
@ -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
|
||||
// strings of digits like "332" (mainly because I really didn't feel like
|
||||
// putting massive constant tables in).
|
||||
func (conn *Conn) Handle(name string, h Handler) Remover {
|
||||
return conn.handlers.add(name, h)
|
||||
func (conn *Conn) Handle(name string, handler Handler) Remover {
|
||||
_, remover := conn.handlers.add(name, handler)
|
||||
return remover
|
||||
}
|
||||
|
||||
func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
|
||||
return conn.Handle(name, hf)
|
||||
func (conn *Conn) HandleFunc(name string, handlerFunc HandlerFunc) Remover {
|
||||
return conn.Handle(name, handlerFunc)
|
||||
}
|
||||
|
||||
func (conn *Conn) Command(prefix string, c Command) Remover {
|
||||
return conn.commands.add(prefix, c)
|
||||
func (conn *Conn) Command(regex string, handler Handler, priority int) Remover {
|
||||
_, remover := conn.commands.add(regex, handler, priority)
|
||||
return remover
|
||||
}
|
||||
|
||||
func (conn *Conn) CommandFunc(prefix string, hf HandlerFunc, help string) Remover {
|
||||
return conn.Command(prefix, &command{hf, help})
|
||||
func (conn *Conn) CommandFunc(regex string, handlerFunc HandlerFunc, priority int) Remover {
|
||||
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) {
|
||||
conn.handlers.dispatch(conn, line)
|
||||
}
|
||||
|
||||
func (conn *Conn) cmdMatch(txt string) (Command, int) {
|
||||
return conn.commands.match(txt)
|
||||
func (conn *Conn) command(line *Line) {
|
||||
command := conn.commands.match(line.Message())
|
||||
if command != nil {
|
||||
go command.Handle(conn, line)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
)
|
||||
|
||||
func TestHandlerSet(t *testing.T) {
|
||||
hs := handlerSet()
|
||||
hs := newHandlerSet()
|
||||
if len(hs.set) != 0 {
|
||||
t.Errorf("New set contains things!")
|
||||
}
|
||||
|
@ -17,66 +17,40 @@ func TestHandlerSet(t *testing.T) {
|
|||
}
|
||||
|
||||
// Add one
|
||||
hn1 := hs.add("ONE", HandlerFunc(f)).(*hNode)
|
||||
_, hn1 := hs.add("ONE", HandlerFunc(f))
|
||||
hl, ok := hs.set["one"]
|
||||
if len(hs.set) != 1 || !ok {
|
||||
t.Errorf("Set doesn't contain 'one' list after add().")
|
||||
}
|
||||
if hn1.set != hs || hn1.event != "one" || hn1.prev != nil || hn1.next != nil {
|
||||
t.Errorf("First node for 'one' not created correctly")
|
||||
}
|
||||
if hl.start != hn1 || hl.end != hn1 {
|
||||
t.Errorf("Node not added to empty 'one' list correctly.")
|
||||
if hl.Len() != 1 {
|
||||
t.Errorf("List doesn't contain 'one' after add().")
|
||||
}
|
||||
|
||||
// Add another one...
|
||||
hn2 := hs.add("one", HandlerFunc(f)).(*hNode)
|
||||
_, hn2 := hs.add("one", HandlerFunc(f))
|
||||
if len(hs.set) != 1 {
|
||||
t.Errorf("Set contains more than 'one' list after add().")
|
||||
}
|
||||
if hn2.set != hs || hn2.event != "one" {
|
||||
t.Errorf("Second node for 'one' not created correctly")
|
||||
}
|
||||
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.")
|
||||
if hl.Len() != 2 {
|
||||
t.Errorf("List doesn't contain second 'one' after add().")
|
||||
}
|
||||
|
||||
// Add a third one!
|
||||
hn3 := hs.add("one", HandlerFunc(f)).(*hNode)
|
||||
_, hn3 := hs.add("one", HandlerFunc(f))
|
||||
if len(hs.set) != 1 {
|
||||
t.Errorf("Set contains more than 'one' list after add().")
|
||||
}
|
||||
if hn3.set != hs || hn3.event != "one" {
|
||||
t.Errorf("Third node for 'one' not created correctly")
|
||||
}
|
||||
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.")
|
||||
if hl.Len() != 3 {
|
||||
t.Errorf("List doesn't contain third 'one' after add().")
|
||||
}
|
||||
|
||||
// And finally a fourth one!
|
||||
hn4 := hs.add("one", HandlerFunc(f)).(*hNode)
|
||||
_, hn4 := hs.add("one", HandlerFunc(f))
|
||||
if len(hs.set) != 1 {
|
||||
t.Errorf("Set contains more than 'one' list after add().")
|
||||
}
|
||||
if hn4.set != hs || hn4.event != "one" {
|
||||
t.Errorf("Fourth node for 'one' not created correctly.")
|
||||
}
|
||||
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.")
|
||||
if hl.Len() != 4 {
|
||||
t.Errorf("List doesn't contain fourth 'one' after add().")
|
||||
}
|
||||
|
||||
// Dispatch should result in 4 additions.
|
||||
|
@ -94,16 +68,8 @@ func TestHandlerSet(t *testing.T) {
|
|||
if len(hs.set) != 1 {
|
||||
t.Errorf("Set list count changed after remove().")
|
||||
}
|
||||
if hn3.set != nil || hn3.prev != nil || hn3.next != nil {
|
||||
t.Errorf("Third node for '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.")
|
||||
if hl.Len() != 3 {
|
||||
t.Errorf("Third 'one' not removed correctly.")
|
||||
}
|
||||
|
||||
// Dispatch should result in 3 additions.
|
||||
|
@ -114,18 +80,12 @@ func TestHandlerSet(t *testing.T) {
|
|||
}
|
||||
|
||||
// Remove node 1.
|
||||
hs.remove(hn1)
|
||||
hn1.Remove()
|
||||
if len(hs.set) != 1 {
|
||||
t.Errorf("Set list count changed after remove().")
|
||||
}
|
||||
if hn1.set != nil || hn1.prev != nil || hn1.next != nil {
|
||||
t.Errorf("First node for '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.")
|
||||
if hl.Len() != 2 {
|
||||
t.Errorf("First 'one' not removed correctly.")
|
||||
}
|
||||
|
||||
// Dispatch should result in 2 additions.
|
||||
|
@ -140,14 +100,8 @@ func TestHandlerSet(t *testing.T) {
|
|||
if len(hs.set) != 1 {
|
||||
t.Errorf("Set list count changed after remove().")
|
||||
}
|
||||
if hn4.set != nil || hn4.prev != nil || hn4.next != nil {
|
||||
t.Errorf("Fourth node for '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.")
|
||||
if hl.Len() != 1 {
|
||||
t.Errorf("Fourth 'one' not removed correctly.")
|
||||
}
|
||||
|
||||
// Dispatch should result in 1 addition.
|
||||
|
@ -158,16 +112,10 @@ func TestHandlerSet(t *testing.T) {
|
|||
}
|
||||
|
||||
// Remove node 2.
|
||||
hs.remove(hn2)
|
||||
hn2.Remove()
|
||||
if len(hs.set) != 0 {
|
||||
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.
|
||||
hs.dispatch(nil, &Line{Cmd: "One"})
|
||||
|
@ -178,52 +126,43 @@ func TestHandlerSet(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCommandSet(t *testing.T) {
|
||||
cs := commandSet()
|
||||
if len(cs.set) != 0 {
|
||||
t.Errorf("New set contains things!")
|
||||
cl := newCommandList()
|
||||
if cl.list.Len() != 0 {
|
||||
t.Errorf("New list contains things!")
|
||||
}
|
||||
|
||||
c := &command{
|
||||
fn: func(c *Conn, l *Line) {},
|
||||
help: "wtf?",
|
||||
_, cn1 := cl.add("one", HandlerFunc(func(c *Conn, l *Line) {}), 0)
|
||||
if cl.list.Len() != 1 {
|
||||
t.Errorf("Command 'one' not added to list correctly.")
|
||||
}
|
||||
|
||||
cn1 := cs.add("ONE", c).(*cNode)
|
||||
if _, ok := cs.set["one"]; !ok || cn1.set != cs || cn1.prefix != "one" {
|
||||
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" {
|
||||
_, cn2 := cl.add("one two", HandlerFunc(func(c *Conn, l *Line) {}), 0)
|
||||
if cl.list.Len() != 2 {
|
||||
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.")
|
||||
}
|
||||
if c, l := cs.match("one"); c.(*cNode) != cn1 || l != 3 {
|
||||
t.Errorf("Didn't match 'one' when we should have.")
|
||||
if c := cl.match("one"); c == nil {
|
||||
t.Errorf("Didn't match when we should have.")
|
||||
}
|
||||
if c, l := cs.match("one two three"); c.(*cNode) != cn2 || l != 7 {
|
||||
t.Errorf("Didn't match 'one two' when we should have.")
|
||||
if c := cl.match("one two three"); c == nil {
|
||||
t.Errorf("Didn't match when we should have.")
|
||||
}
|
||||
|
||||
cs.remove(cn2)
|
||||
if _, ok := cs.set["one two"]; ok || cn2.set != nil {
|
||||
cn2.Remove()
|
||||
if cl.list.Len() != 1 {
|
||||
t.Errorf("Command 'one two' not removed correctly.")
|
||||
}
|
||||
if c, l := cs.match("one two three"); c.(*cNode) != cn1 || l != 3 {
|
||||
t.Errorf("Didn't match 'one' when we should have.")
|
||||
if c := cl.match("one two three"); c == nil {
|
||||
t.Errorf("Didn't match when we should have.")
|
||||
}
|
||||
cn1.Remove()
|
||||
if _, ok := cs.set["one"]; ok || cn1.set != nil {
|
||||
t.Errorf("Command 'one' not removed correctly.")
|
||||
if cl.list.Len() != 0 {
|
||||
t.Errorf("Command 'one two' not removed correctly.")
|
||||
}
|
||||
if c, l := cs.match("one two three"); c != nil || l != 0 {
|
||||
t.Errorf("Matched 'one' when we shouldn't have.")
|
||||
if c := cl.match("one two three"); c != nil {
|
||||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
REGISTER = "REGISTER"
|
||||
CONNECTED = "CONNECTED"
|
||||
DISCONNECTED = "DISCONNECTED"
|
||||
)
|
||||
|
||||
// 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,
|
||||
"001": (*Conn).h_001,
|
||||
"433": (*Conn).h_433,
|
||||
CTCP: (*Conn).h_CTCP,
|
||||
NICK: (*Conn).h_NICK,
|
||||
PING: (*Conn).h_PING,
|
||||
PRIVMSG: (*Conn).h_PRIVMSG,
|
||||
}
|
||||
|
||||
func (conn *Conn) addIntHandlers() {
|
||||
|
@ -33,7 +28,7 @@ func (conn *Conn) addIntHandlers() {
|
|||
|
||||
// Basic ping/pong handler
|
||||
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.
|
||||
|
@ -86,10 +81,10 @@ func (conn *Conn) h_433(line *Line) {
|
|||
|
||||
// Handle VERSION requests and CTCP PING
|
||||
func (conn *Conn) h_CTCP(line *Line) {
|
||||
if line.Args[0] == "VERSION" {
|
||||
conn.CtcpReply(line.Nick, "VERSION", "powered by goirc...")
|
||||
} else if line.Args[0] == "PING" {
|
||||
conn.CtcpReply(line.Nick, "PING", line.Args[2])
|
||||
if line.Args[0] == VERSION {
|
||||
conn.CtcpReply(line.Nick, VERSION, "powered by goirc...")
|
||||
} else if line.Args[0] == PING {
|
||||
conn.CtcpReply(line.Nick, PING, line.Args[2])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,34 +97,19 @@ func (conn *Conn) h_NICK(line *Line) {
|
|||
|
||||
// Handle PRIVMSGs that trigger Commands
|
||||
func (conn *Conn) h_PRIVMSG(line *Line) {
|
||||
txt := line.Args[1]
|
||||
if conn.cfg.CommandStripNick && strings.HasPrefix(txt, conn.cfg.Me.Nick) {
|
||||
text := line.Message()
|
||||
if conn.cfg.CommandStripNick && strings.HasPrefix(text, conn.cfg.Me.Nick) {
|
||||
// Look for '^${nick}[:;>,-]? '
|
||||
l := len(conn.cfg.Me.Nick)
|
||||
switch txt[l] {
|
||||
switch text[l] {
|
||||
case ':', ';', '>', ',', '-':
|
||||
l++
|
||||
}
|
||||
if txt[l] == ' ' {
|
||||
txt = strings.TrimSpace(txt[l:])
|
||||
if text[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.Args[1] = txt
|
||||
}
|
||||
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())
|
||||
line.Args[1] = text
|
||||
}
|
||||
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")
|
||||
// Set up a handler to detect whether connected handler is called from 001
|
||||
hcon := false
|
||||
c.HandleFunc("connected", func(conn *Conn, line *Line) {
|
||||
c.HandleFunc(CONNECTED, func(conn *Conn, line *Line) {
|
||||
hcon = true
|
||||
})
|
||||
|
||||
|
@ -166,7 +166,9 @@ func TestPRIVMSG(t *testing.T) {
|
|||
f := func(conn *Conn, line *Line) {
|
||||
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
|
||||
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"))
|
||||
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"))
|
||||
s.nc.Expect("PRIVMSG #foo :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
|
||||
// separate events as opposed to forcing people to have gargantuan
|
||||
// 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 &&
|
||||
strings.HasPrefix(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
|
||||
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
|
||||
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"
|
||||
if line.Cmd == PRIVMSG {
|
||||
line.Cmd = CTCP
|
||||
} else {
|
||||
line.Cmd = "CTCPREPLY"
|
||||
line.Cmd = CTCPREPLY
|
||||
}
|
||||
line.Args = append([]string{c}, line.Args...)
|
||||
}
|
||||
}
|
||||
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