kunt

golang IRC bot
git clone git://git.2f30.org/kunt
Log | Files | Refs | LICENSE

commit 57f1c0f3030b6472ddfb1eb98fe055129d7753b6
parent 9bc41991a84d7e5c9a0c3421504fad1db5cfdc32
Author: sin <sin@2f30.org>
Date:   Thu,  2 May 2013 13:07:16 +0100

add better irc parsing

Diffstat:
Msrc/games/games.go | 48++++++++++++++++++++++++------------------------
Msrc/irc/events.go | 4++--
Msrc/irc/irc.go | 21++++-----------------
Msrc/irc/message.go | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/kunt/kunt.go | 99++++++++++++++++++++++++++++++++++++++-----------------------------------------
5 files changed, 133 insertions(+), 113 deletions(-)

diff --git a/src/games/games.go b/src/games/games.go @@ -47,11 +47,11 @@ func (s *hangmanState) String() string { //to the state channel. func hangmanInit(cs *hangmanState) { for msg := range *cs.gchan { - if msg.Cmd == "SETNICK" { - cs.botname = msg.Params[0] + if msg.Command == "SETNICK" { + cs.botname = msg.Args[0] } - if msg.Cmd == "SETCHAN" { - cs.channame = msg.Params[0] + if msg.Command == "SETCHAN" { + cs.channame = msg.Args[0] } if cs.botname != "" && cs.channame != "" { break @@ -65,11 +65,11 @@ func hangmanInit(cs *hangmanState) { func hangmanWaitGoal(cs *hangmanState) { cs.ictx.Privmsg(cs.channame, "Hangman is waiting for goal. When ready use hangman <letter|possibleword>") for msg := range *cs.gchan { - msg.Params[1] = strings.TrimSpace(msg.Params[1]) - if msg.Params[0] == cs.botname { - if strings.HasPrefix(msg.Params[1], ":setgoal ") { - cs.goal = msg.Params[1][9:] - who := msg.Prefix[:strings.Index(msg.Prefix, "!")] + msg.Args[1] = strings.TrimSpace(msg.Args[1]) + if msg.Args[0] == cs.botname { + if strings.HasPrefix(msg.Args[1], "setgoal ") { + cs.goal = msg.Args[1][8:] + who := msg.Nick cs.ictx.Privmsg(who, "Goal is now set to: "+cs.goal) runes := make([]rune, len(cs.goal)) for i := 0; i < len(runes); i++ { @@ -78,12 +78,12 @@ func hangmanWaitGoal(cs *hangmanState) { cs.foundnow = string(runes) break } else { - who := msg.Prefix[:strings.Index(msg.Prefix, "!")] + who := msg.Nick cs.ictx.Privmsg(who, "Now we are playing hangman. Use setgoal <WORD>") } } else { - if strings.HasPrefix(msg.Params[1], ":hangman ") { - cs.ictx.Privmsg(msg.Params[0], "Still waiting for goal...") + if strings.HasPrefix(msg.Args[1], "hangman ") { + cs.ictx.Privmsg(msg.Args[0], "Still waiting for goal...") } } } @@ -99,37 +99,37 @@ func hangmanWaitGoal(cs *hangmanState) { func hangmanMainLoop(cs *hangmanState) { cs.ictx.Privmsg(cs.channame, cs.String()) for msg := range *cs.gchan { - msg.Params[1] = strings.TrimSpace(msg.Params[1]) - if msg.Params[0] != cs.botname { + msg.Args[1] = strings.TrimSpace(msg.Args[1]) + if msg.Args[0] != cs.botname { if cs.lives < 1 { - cs.ictx.Privmsg(msg.Params[0], "You are hanged! Goal was ->"+cs.goal) + cs.ictx.Privmsg(msg.Args[0], "You are hanged! Goal was ->"+cs.goal) *cs.statchan <- hangmanEnd } - if strings.HasPrefix(msg.Params[1], ":hangman ") { - in := msg.Params[1][9:] + if strings.HasPrefix(msg.Args[1], "hangman ") { + in := msg.Args[1][8:] if len(in) > 1 { if in == cs.goal { - cs.ictx.Privmsg(msg.Params[0], "You won!") + cs.ictx.Privmsg(msg.Args[0], "You won!") *cs.statchan <- hangmanEnd } else { - cs.ictx.Privmsg(msg.Params[0], "You lost a life punk...") + cs.ictx.Privmsg(msg.Args[0], "You lost a life punk...") cs.lives -= 1 - cs.ictx.Privmsg(msg.Params[0], cs.String()) + cs.ictx.Privmsg(msg.Args[0], cs.String()) continue } } else { if strings.Index(cs.tried, string(in[0])) != -1 { - cs.ictx.Privmsg(msg.Params[0], "I heard you the first time") + cs.ictx.Privmsg(msg.Args[0], "I heard you the first time") continue } hm := cs.checkLetter([]rune(in)[0]) - cs.ictx.Privmsg(msg.Params[0], fmt.Sprintf("You got %d letters", hm)) + cs.ictx.Privmsg(msg.Args[0], fmt.Sprintf("You got %d letters", hm)) if hm == 0 { cs.lives -= 1 } - cs.ictx.Privmsg(msg.Params[0], cs.String()) + cs.ictx.Privmsg(msg.Args[0], cs.String()) if cs.foundnow == cs.goal { - cs.ictx.Privmsg(msg.Params[0], "You won!") + cs.ictx.Privmsg(msg.Args[0], "You won!") *cs.statchan <- hangmanEnd } } diff --git a/src/irc/events.go b/src/irc/events.go @@ -5,8 +5,8 @@ const ( ) type IrcEvent struct { - Cmd string - Fn func(IrcMessage) + Command string + Fn func(IrcMessage) } func (i *IrcContext) AddEventHandler(ev IrcEvent) int { diff --git a/src/irc/irc.go b/src/irc/irc.go @@ -155,8 +155,6 @@ func (i *IrcContext) read(buf []byte) (int, error) { // This is the actual raw read loop. Here we parse the incoming bytes // and form messages. func (i *IrcContext) rxLoop() error { - var msg IrcMessage - var cmdpos, lastddot int for { var buf [512]byte @@ -167,8 +165,6 @@ func (i *IrcContext) rxLoop() error { fmt.Println(string(buf[0 : n-1])) s := string(buf[0:n]) - // Handle this here, no need to create - // a message for it if strings.HasPrefix(s, "PING :") { r := strings.Replace(s, "PING :", "PONG :", 6) r = r + "\r\n" @@ -179,19 +175,10 @@ func (i *IrcContext) rxLoop() error { continue } - // At the moment - handle only PRIVMSG - cmdpos = strings.Index(s, "PRIVMSG ") // the space is needed for discarding init msgs. - if cmdpos != -1 { - //XXX this is too fragile but soon le parser will save us. - msg.Prefix = s[1 : cmdpos-1] //discard the first : - lastddot = strings.Index(s[1:], ":") - msg.Cmd = "PRIVMSG" - msg.Params = []string{s[cmdpos+8 : lastddot], - s[lastddot+1:]} - i.incomingMsg <- msg - for _, v := range i.intercepts { - *v <- msg - } + msg := i.ParseRawMessage(s) + i.incomingMsg <- msg + for _, v := range i.intercepts { + *v <- msg } } return nil diff --git a/src/irc/message.go b/src/irc/message.go @@ -1,17 +1,22 @@ package irc +import "strings" + type IrcMessage struct { - Prefix string // prefix part of the message - Cmd string // the actual command - Params []string // the tokenized parameters + Command string + Raw string + Nick string + Host string + Source string + User string + Args []string } // Send the nickname func (i *IrcContext) Nick() { msg := IrcMessage{ - Prefix: "", - Cmd: "NICK", - Params: []string{i.network.nick}, + Command: "NICK", + Args: []string{i.network.nick}, } i.outgoingMsg <- msg } @@ -19,9 +24,8 @@ func (i *IrcContext) Nick() { // Send the username func (i *IrcContext) User() { msg := IrcMessage{ - Prefix: "", - Cmd: "USER", - Params: []string{ + Command: "USER", + Args: []string{ i.network.user, "* 8", ":" + i.network.nick, @@ -33,9 +37,8 @@ func (i *IrcContext) User() { // Join a channel func (i *IrcContext) Join(channel string) { msg := IrcMessage{ - Prefix: "", - Cmd: "JOIN", - Params: []string{channel}, + Command: "JOIN", + Args: []string{channel}, } i.outgoingMsg <- msg } @@ -43,9 +46,8 @@ func (i *IrcContext) Join(channel string) { // Send a PRIVMSG func (i *IrcContext) Privmsg(channel string, text string) { msg := IrcMessage{ - Prefix: "", - Cmd: "PRIVMSG", - Params: []string{ + Command: "PRIVMSG", + Args: []string{ channel, ":" + text, }, @@ -53,11 +55,47 @@ func (i *IrcContext) Privmsg(channel string, text string) { i.outgoingMsg <- msg } +func (i *IrcContext) ParseRawMessage(raw string) IrcMessage { + var msg IrcMessage + msg.Raw = raw + if raw[0] == ':' { + if i := strings.Index(raw, " "); i != -1 { + msg.Source = raw[1:i] + raw = raw[1:] + } + if i := strings.Index(raw, "!"); i != -1 { + msg.Nick = raw[:i] + raw = raw[i+1:] + } + if i := strings.Index(raw, "@"); i != -1 { + msg.User = raw[:i] + raw = raw[i+1:] + } + if i := strings.Index(raw, " "); i != -1 { + msg.Host = raw[:i] + raw = raw[i+1:] + } + } + if i := strings.Index(raw, " "); i != -1 { + msg.Command = raw[:i] + raw = raw[i+1:] + } + if raw[0] == ':' { + msg.Args = []string{raw[1:]} + } else { + args := strings.SplitN(raw, " :", 2) + if len(args) == 2 { + msg.Args = []string{args[0], args[1]} + } + } + return msg +} + // Unpack a message into a byte array func (i *IrcContext) UnpackMessage(msg IrcMessage) ([]byte, error) { // No Prefix crap for TX paths - rawMsg := msg.Cmd + " " - for _, v := range msg.Params { + rawMsg := msg.Command + " " + for _, v := range msg.Args { rawMsg += v + " " } rawMsg = rawMsg[:len(rawMsg)-1] + "\r\n" @@ -83,7 +121,7 @@ func (i *IrcContext) incomingMsgLoop() error { // Check if the user has registered // a callback for this message for _, v := range i.ev { - if v.Cmd == msg.Cmd { + if v.Command == msg.Command { v.Fn(msg) break } diff --git a/src/kunt/kunt.go b/src/kunt/kunt.go @@ -51,7 +51,7 @@ func cmdKunt(msg irc.IrcMessage) { "no fucking way", } idx := rand.Intn(len(actions)) - kunt.ircCtx.Privmsg(msg.Params[0], string(actions[idx])) + kunt.ircCtx.Privmsg(msg.Args[0], string(actions[idx])) } func cmdWisdom(msg irc.IrcMessage) { @@ -64,7 +64,7 @@ func cmdWisdom(msg irc.IrcMessage) { if i == "" { continue } - kunt.ircCtx.Privmsg(msg.Params[0], i) + kunt.ircCtx.Privmsg(msg.Args[0], i) time.Sleep(512 * time.Millisecond) } } @@ -86,19 +86,19 @@ func cmdHelp(msg irc.IrcMessage) { "!version -> Show version", } for _, i := range help { - kunt.ircCtx.Privmsg(msg.Params[0], i) + kunt.ircCtx.Privmsg(msg.Args[0], i) time.Sleep(512 * time.Millisecond) } } func cmdVersion(msg irc.IrcMessage) { ver := "v0.2.8" - kunt.ircCtx.Privmsg(msg.Params[0], ver) + kunt.ircCtx.Privmsg(msg.Args[0], ver) } func cmdRandQuote(msg irc.IrcMessage) { if quoteDb.Empty() { - kunt.ircCtx.Privmsg(msg.Params[0], "Empty quote database") + kunt.ircCtx.Privmsg(msg.Args[0], "Empty quote database") return } quote, idx := quoteDb.Rand() @@ -106,28 +106,27 @@ func cmdRandQuote(msg irc.IrcMessage) { // Print idx in red text := fmt.Sprintf("%s%02d[%d]%s %s", "\003", '5', idx, "\003", string(quote)) - kunt.ircCtx.Privmsg(msg.Params[0], text) + kunt.ircCtx.Privmsg(msg.Args[0], text) } func cmdAddQuote(msg irc.IrcMessage) { - s := msg.Params[1] - hm := strings.Fields(s) - if len(hm) < 2 { - kunt.ircCtx.Privmsg(msg.Params[0], "Missing parameter for !addquote") + text := msg.Args[1] + if len(strings.Fields(text)) < 2 { + kunt.ircCtx.Privmsg(msg.Args[0], "Missing parameter for !addquote") return } - s = s[11:] - s = strings.TrimSpace(s) - s += "\n" - buf := []byte(s) + text = text[10:] + text = strings.TrimSpace(text) + text += "\n" + buf := []byte(text) if quoteDb.CountMatches(buf) > 0 { - kunt.ircCtx.Privmsg(msg.Params[0], + kunt.ircCtx.Privmsg(msg.Args[0], fmt.Errorf("Duplicate entry: %s", buf).Error()) return } _, err := quoteDb.Append(buf) if err != nil { - kunt.ircCtx.Privmsg(msg.Params[0], err.Error()) + kunt.ircCtx.Privmsg(msg.Args[0], err.Error()) return } err = quoteDb.Sync() @@ -138,12 +137,12 @@ func cmdAddQuote(msg irc.IrcMessage) { func cmdCountQuotes(msg irc.IrcMessage) { text := fmt.Sprintf("The quote DB has %d quotes", quoteDb.Size()) - kunt.ircCtx.Privmsg(msg.Params[0], text) + kunt.ircCtx.Privmsg(msg.Args[0], text) } func cmdRandUrl(msg irc.IrcMessage) { if urlDb.Empty() { - kunt.ircCtx.Privmsg(msg.Params[0], "Empty url database") + kunt.ircCtx.Privmsg(msg.Args[0], "Empty url database") return } url, idx := urlDb.Rand() @@ -151,33 +150,32 @@ func cmdRandUrl(msg irc.IrcMessage) { // Print idx in red r := fmt.Sprintf("%s%02d[%d]%s %s", "\003", '5', idx, "\003", string(url)) - kunt.ircCtx.Privmsg(msg.Params[0], r) + kunt.ircCtx.Privmsg(msg.Args[0], r) title, ok := resolveYoutubeTitle(string(url)) if ok { r = fmt.Sprintf("[YouTube] %s", title) - kunt.ircCtx.Privmsg(msg.Params[0], r) + kunt.ircCtx.Privmsg(msg.Args[0], r) } } func cmdAddUrl(msg irc.IrcMessage) { - s := msg.Params[1] - as := strings.Fields(s) - if len(as) < 2 { - kunt.ircCtx.Privmsg(msg.Params[0], "Missing parameter for !addurl") + text := msg.Args[1] + if len(strings.Fields(text)) < 2 { + kunt.ircCtx.Privmsg(msg.Args[0], "Missing parameter for !addurl") return } - s = s[9:] - s = strings.TrimSpace(s) - s += "\n" - buf := []byte(s) + text = text[8:] + text = strings.TrimSpace(text) + text += "\n" + buf := []byte(text) if urlDb.CountMatches(buf) > 0 { - kunt.ircCtx.Privmsg(msg.Params[0], + kunt.ircCtx.Privmsg(msg.Args[0], fmt.Errorf("Duplicate entry: %s", buf).Error()) return } _, err := urlDb.Append(buf) if err != nil { - kunt.ircCtx.Privmsg(msg.Params[0], err.Error()) + kunt.ircCtx.Privmsg(msg.Args[0], err.Error()) return } err = urlDb.Sync() @@ -188,38 +186,38 @@ func cmdAddUrl(msg irc.IrcMessage) { func cmdCountUrls(msg irc.IrcMessage) { r := fmt.Sprintf("The url DB has %d urls", urlDb.Size()) - kunt.ircCtx.Privmsg(msg.Params[0], r) + kunt.ircCtx.Privmsg(msg.Args[0], r) } func cmdRadio(msg irc.IrcMessage) { np, e := http.Get("http://radio.2f30.org:8000/npsong.xsl") if e != nil { - kunt.ircCtx.Privmsg(msg.Params[0], "Is radio broken?") + kunt.ircCtx.Privmsg(msg.Args[0], "Is radio broken?") } else { defer np.Body.Close() song, _ := ioutil.ReadAll(np.Body) r := fmt.Sprintf("[Radio] %s", song) - kunt.ircCtx.Privmsg(msg.Params[0], r) + kunt.ircCtx.Privmsg(msg.Args[0], r) r = "[Radio] http://radio.2f30.org:8000/live.ogg" - kunt.ircCtx.Privmsg(msg.Params[0], r) + kunt.ircCtx.Privmsg(msg.Args[0], r) } } func cmdUptime(msg irc.IrcMessage) { etime := time.Now() r := fmt.Sprintf("%v", etime.Sub(kunt.stime)) - kunt.ircCtx.Privmsg(msg.Params[0], r) + kunt.ircCtx.Privmsg(msg.Args[0], r) } func cmdSrc(msg irc.IrcMessage) { src := "http://amnezia.2f30.org/tmp/kunt-latest.tgz" - kunt.ircCtx.Privmsg(msg.Params[0], src) + kunt.ircCtx.Privmsg(msg.Args[0], src) } func cmdTodo(msg irc.IrcMessage) { todo, err := ioutil.ReadFile("TODO") if err != nil { - kunt.ircCtx.Privmsg(msg.Params[0], "No TODO list available") + kunt.ircCtx.Privmsg(msg.Args[0], "No TODO list available") return } sf := strings.FieldsFunc(string(todo), func(r rune) bool { @@ -230,32 +228,31 @@ func cmdTodo(msg irc.IrcMessage) { return false }) for _, v := range sf { - kunt.ircCtx.Privmsg(msg.Params[0], v) + kunt.ircCtx.Privmsg(msg.Args[0], v) time.Sleep(512 * time.Millisecond) } } func cmdGame(msg irc.IrcMessage) { - s := msg.Params[1] - as := strings.Fields(s) - if len(as) < 2 { - kunt.ircCtx.Privmsg(msg.Params[0], "Missing parameter for !game") + text := msg.Args[1] + if len(strings.Fields(text)) < 2 { + kunt.ircCtx.Privmsg(msg.Args[0], "Missing parameter for !game") return } - s = s[6:] - s = strings.TrimSpace(s) - switch s { + text = text[5:] + text = strings.TrimSpace(text) + switch text { case "list": for _, str := range geng.List() { - kunt.ircCtx.Privmsg(msg.Params[0], "[game] "+str) + kunt.ircCtx.Privmsg(msg.Args[0], "[game] "+str) } return } gchan := make(chan irc.IrcMessage) - geng.New(s, kunt.ircCtx, &gchan) + geng.New(text, kunt.ircCtx, &gchan) time.Sleep(512 * time.Millisecond) - gchan <- irc.IrcMessage{Prefix: "", Cmd: "SETNICK", Params: []string{botname}} //Let the game engine know. - gchan <- irc.IrcMessage{Prefix: "", Cmd: "SETCHAN", Params: []string{msg.Params[0]}} + gchan <- irc.IrcMessage{Command: "SETNICK", Args: []string{botname}} //Let the game engine know. + gchan <- irc.IrcMessage{Command: "SETCHAN", Args: []string{msg.Args[0]}} } var sslon = flag.Bool("s", false, "SSL support") @@ -349,9 +346,7 @@ func main() { kunt.ircCtx = irc.NewIrcContext(cfg) kunt.ircCtx.AddEventHandler(irc.IrcEvent{"PRIVMSG", func(msg irc.IrcMessage) { - cmd := msg.Params[1] - // Strip the leading ':' - cmd = cmd[1:] + cmd := msg.Args[1] for i, v := range dispatch { if strings.HasPrefix(cmd, i) { go v(msg)