From 7019ec3d0e44433a19c287769b1a44ac1515d3d7 Mon Sep 17 00:00:00 2001 From: tj Date: Wed, 6 Jan 2010 19:32:35 +0100 Subject: [PATCH] Rewritten to use callbacks, bit easier to use. --- Makefile | 2 +- README.markdown | 41 ++++++- example/test.go | 45 ++----- irc.go | 310 +++++++++++++++++------------------------------- irc_struct.go | 79 ++++-------- 5 files changed, 181 insertions(+), 296 deletions(-) diff --git a/Makefile b/Makefile index 086ef3a..d266c78 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include $(GOROOT)/src/Make.$(GOARCH) TARG=irc -GOFILES=irc.go irc_struct.go +GOFILES=irc.go irc_struct.go irc_callback.go include $(GOROOT)/src/Make.pkg diff --git a/README.markdown b/README.markdown index d15d7fd..33697ce 100644 --- a/README.markdown +++ b/README.markdown @@ -6,9 +6,11 @@ Event based irc client library. Features --------- -* Event based -* Automatic Reconnect -* Handles basic irc demands for you (PING, VERSION) +* Event based. Register Callbacks for the events you need to handle. +* Handles basic irc demands for you: +** Standard CTCP +** Reconnections on errors +** Detect stoned servers Install ---------- @@ -20,3 +22,36 @@ Install Example ---------- See example/test.go + +Events for callbacks +--------- +*001 Welcome +*PING +*CTCP Unknown CTCP +*CTCP_VERSION Version request (Handled internaly) +*CTCP_USERINFO +*CTCP_CLIENTINFO +*CTCP_TIME +*CTCP_PING +*PRIVMSG +*MODE +*JOIN + ++Many more + + +AddCallback Example +--------- + ircobj.AddCallback("PRIVMSG", func(event *irc.IRCEvent) { + //e.Message contains the message + //e.Nick Contains the sender + //e.Arguments[0] Contains the channel + }); + +Commands +-------- + ircobj.Sendraw("") //sends string to server. Adds \r\n + ircobj.Join("#channel [password]") + ircobj.Privmsg("#channel", "msg") + ircobj.Privmsg("nickname", "msg") + ircobj.Notice("nickname or #channel", "msg") diff --git a/example/test.go b/example/test.go index 6857ce7..4759dbc 100644 --- a/example/test.go +++ b/example/test.go @@ -1,44 +1,19 @@ package main import ( - "irc"; - "fmt"; - "os"; + "irc" + "fmt" + "os" ) func main() { - events := make(chan *irc.IRCEvent, 100); - irccon, err := irc.IRC("irc.efnet.net:6667", "testgo", "testgo", events); + irccon := irc.IRC("testgo", "testgo") + err := irccon.Connect("irc.efnet.net:6667") if err != nil { - fmt.Printf("%s\n", err); - fmt.Printf("%#v\n", irccon); - os.Exit(1); - } - for { - event := <-events; -/* switch event.Code { - case UNKNOWN: - fmt.Printf("%#v\n", event) - case 0: - fmt.Printf("%#v\n", event) - case IRC_PRIVMSG: - fmt.Printf("%#v\n", event) - case IRC_CHAN_TOPIC: - fmt.Printf("%#v\n", event) - case IRC_CHAN_MODE: - fmt.Printf("%#v\n", event) - case IRC_ACTION: - fmt.Printf("%#v\n", event) - case IRC_WELCOME: - irc.Join("#ggpre") - }*/ - if event.Code == irc.IRC_WELCOME { - irccon.Join("#gotestchan") - } else if event.Code == irc.IRC_PRIVMSG { - if event.Message == "!test" { - irccon.Privmsg(event.Target, "Whatever man!"); - } - } - fmt.Printf("%#v\n", event); + fmt.Printf("%s\n", err) + fmt.Printf("%#v\n", irccon) + os.Exit(1) } + irccon.AddCallback("001", func(e *irc.IRCEvent) { irccon.Join("#testgo") }) + irccon.Loop(); } diff --git a/irc.go b/irc.go index 3440810..b1d7350 100644 --- a/irc.go +++ b/irc.go @@ -5,195 +5,80 @@ package irc import ( - "fmt"; - "net"; - "os"; - "bufio"; - "regexp"; - "strings"; + "fmt" + "net" + "os" + "bufio" + "strings" + "time" +) + +const ( + VERSION = "GolangBOT v1.0" ) func reader(irc *IRCConnection) { - br := bufio.NewReader(irc.socket); + br := bufio.NewReader(irc.socket) for { - msg, err := br.ReadString('\n'); + msg, err := br.ReadString('\n') if err != nil { - fmt.Printf("%s\n", err); - irc.perror <- err; - return; + irc.Error <- err + return } - irc.pread <- msg; + irc.lastMessage = time.Seconds() + msg = msg[0 : len(msg)-2] //Remove \r\n + event := &IRCEvent{Raw: msg} + if msg[0] == ':' { + if i := strings.Index(msg, " "); i > -1 { + event.Source = msg[1:i] + msg = msg[i+1 : len(msg)] + } else { + fmt.Printf("Misformed msg from server: %#s\n", msg) + } + if i, j := strings.Index(event.Source, "!"), strings.Index(event.Source, "@"); i > -1 && j > -1 { + event.Nick = event.Source[0:i] + event.User = event.Source[i+1 : j] + event.Host = event.Source[j+1 : len(event.Source)] + } + } + args := strings.Split(msg, " :", 2) + if len(args) > 1 { + event.Message = args[1] + } + args = strings.Split(args[0], " ", 0) + event.Code = strings.ToUpper(args[0]) + if len(args) > 1 { + event.Arguments = args[1:len(args)] + } + irc.RunCallbacks(event) } } func writer(irc *IRCConnection) { for { - b := strings.Bytes(<-irc.pwrite); - _, err := irc.socket.Write(b); + b := strings.Bytes(<-irc.pwrite) + _, err := irc.socket.Write(b) if err != nil { - fmt.Printf("%s\n", err); - irc.perror <- err; - return; - } - } -} - -func reconnector(i *IRCConnection) { - fmt.Printf("Reconnecting\n"); - for { - i.Error = connect(i); - if i.Error == nil { + fmt.Printf("%s\n", err) + irc.Error <- err return } } } -var rx_server_msg = regexp.MustCompile("^:([^ ]+) ([^ ]+) ([^ ]+) :(.*)\r\n") -var rx_server_msg_c = regexp.MustCompile("^:([^ ]+) ([^ ]+) ([^ ]+) [@]* ([^ ]+) :(.*)\r\n") -var rx_server_msg_p = regexp.MustCompile("^:([^ ]+) ([^ ]+) ([^ ]+) (.*)\r\n") -var rx_server_cmd = regexp.MustCompile("^([^:]+) :(.*)\r\n") //AUTH NOTICE, PING, ERROR -var rx_user_action = regexp.MustCompile("^:([^!]+)!([^@]+)@([^ ]+) ([^ ]+) [:]*(.*)\r\n") -var rx_user_msg = regexp.MustCompile("^:([^!]+)!([^@]+)@([^ ]+) ([^ ]+) ([^ ]+) :(.*)\r\n") - -func (irc *IRCConnection) handle_command(msg string) *IRCEvent { - e := new(IRCEvent); - e.RawMessage = msg; - if matches := rx_user_msg.MatchStrings(msg); len(matches) == 7 { - e.Sender = matches[1]; - e.SenderUser = matches[2]; - e.SenderHost = matches[3]; - e.Message = matches[6]; - e.Target = matches[5]; - switch matches[4] { - case "PRIVMSG": - e.Code = IRC_PRIVMSG - case "ACTION": - e.Code = IRC_ACTION - } - return e; - } else if matches := rx_user_action.MatchStrings(msg); len(matches) == 6 { - e.Sender = matches[1]; - e.SenderUser = matches[2]; - e.SenderHost = matches[3]; - e.Message = matches[5]; - e.Target = matches[5]; - e.Channel = matches[5]; - switch matches[4] { - case "JOIN": - e.Code = IRC_JOIN - case "MODE": - e.Code = IRC_CHAN_MODE - } - return e; - } else if matches := rx_server_msg_c.MatchStrings(msg); len(matches) == 6 { - e.Sender = matches[1]; - e.Target = matches[3]; - e.Channel = matches[4]; - e.Message = matches[5]; - switch matches[2] { - case "366": - e.Code = IRC_CHAN_NICKLIST - case "332": - e.Code = IRC_CHAN_TOPIC - } - return e; - } else if matches := rx_server_msg.MatchStrings(msg); len(matches) == 5 { - e.Sender = matches[1]; - e.Target = matches[3]; - e.Message = matches[4]; - switch matches[2] { - case "001": - e.Code = IRC_WELCOME - case "002": - e.Code = IRC_SERVER_INFO - case "003": - e.Code = IRC_SERVER_UPTIME - case "250": - e.Code = IRC_STAT_USERS - case "251": - e.Code = IRC_STAT_USERS - case "255": - e.Code = IRC_STAT_USERS - case "372": - e.Code = IRC_MOTD - case "375": - e.Code = IRC_START_MOTD - case "376": - e.Code = IRC_END_MOTD - case "MODE": - e.Code = IRC_MODE - } - return e; - } else if matches := rx_server_msg_p.MatchStrings(msg); len(matches) == 5 { - e.Sender = matches[1]; - e.Target = matches[3]; - e.Message = matches[4]; - switch matches[2] { - case "252": - e.Code = IRC_STAT_OPERS - case "253": - e.Code = IRC_STAT_UNKN - case "254": - e.Code = IRC_STAT_CONNS - case "265": - e.Code = IRC_STAT_USERS - case "266": - e.Code = IRC_STAT_USERS - case "004": - e.Code = IRC_SERVER_VERSION - case "005": - e.Code = IRC_CHANINFO - case "332": - e.Code = IRC_CHAN_TIMESTAMP - case "353": - e.Code = IRC_CHAN_NICKLIST - } - return e; - } else if matches := rx_server_cmd.MatchStrings(msg); len(matches) == 3 { - switch matches[1] { - case "NOTICE AUTH": - e.Code = IRC_NOTICE_AUTH; - e.Message = matches[2]; - case "PING": - e.Code = IRC_PING; - e.Message = matches[2]; - // case "ERROR": - // e.Code = IRC_PING; - // e.Message = matches[2]; - // e.Error = os.ErrorString(matches[2]); - } - return e; - } - e.Message = msg; - e.Code = UNKNOWN; - return e; -} - -func handler(irc *IRCConnection) { - go reader(irc); - go writer(irc); +//Pings the server if we have not recived any messages for 5 minutes +func pinger(i *IRCConnection) { + i.ticker = time.Tick(1000 * 1000 * 1000 * 60 * 4) //Every 4 minutes + i.ticker2 = time.Tick(1000 * 1000 * 1000 * 60 * 15) //Every 15 minutes for { select { - case msg := <-irc.pread: - e := irc.handle_command(msg); - switch e.Code { - case IRC_PING: - irc.pwrite <- fmt.Sprintf("PONG %s\r\n", e.Message) - case IRC_PRIVMSG: - if e.Message == "\x01VERSION\x01" { - irc.pwrite <- fmt.Sprintf("NOTICE %s :\x01VERSION GolangBOT (tj)\x01\r\n", e.Sender) - } + case <-i.ticker: + if time.Seconds()-i.lastMessage > 60*4 { + i.SendRaw(fmt.Sprintf("PING %d", time.Nanoseconds())) } - - irc.EventChan <- e; - case error := <-irc.perror: - fmt.Printf("Piped error: %s\n", error); - ee := new(IRCEvent); - ee.Error = error; - ee.Code = ERROR; - irc.EventChan <- ee; - reconnector(irc); + case <-i.ticker2: + i.SendRaw(fmt.Sprintf("PING %d", time.Nanoseconds())) } } } @@ -210,43 +95,64 @@ func (irc *IRCConnection) Privmsg(target, message string) { irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message) } -//Try to reconnect -func (irc *IRCConnection) Reconnect() os.Error { - irc.socket, irc.Error = net.Dial("tcp", "", irc.server); - if irc.Error != nil { - return irc.Error - } - return nil; +func (irc *IRCConnection) SendRaw(message string) { + fmt.Printf("--> %s\n", message) + irc.pwrite <- fmt.Sprintf("%s\r\n", message) } -func connect(i *IRCConnection) os.Error { - fmt.Printf("Connecting to %s\n", i.server); - i.socket, i.Error = net.Dial("tcp", "", i.server); - if i.Error != nil { - return i.Error +func (i *IRCConnection) Reconnect() os.Error { + for { + fmt.Printf("Reconnecting to %s\n", i.server) + var err os.Error + i.socket, err = net.Dial("tcp", "", i.server) + if err == nil { + break + } + fmt.Printf("Error: %s\n", err) } - fmt.Printf("Connected to %s (%s)\n", i.server, i.socket.RemoteAddr()); - i.pread = make(chan string, 100); - i.pwrite = make(chan string, 100); - i.perror = make(chan os.Error, 10); - go reader(i); - go writer(i); - i.pwrite <- fmt.Sprintf("NICK %s\r\n", i.nick); - i.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :GolangBOT\r\n", i.user); - return nil; + fmt.Printf("Connected to %s (%s)\n", i.server, i.socket.RemoteAddr()) + go reader(i) + go writer(i) + i.pwrite <- fmt.Sprintf("NICK %s\r\n", i.nick) + i.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", i.user, i.user) + return nil } -func IRC(server string, nick string, user string, events chan *IRCEvent) (*IRCConnection, os.Error) { - irc := new(IRCConnection); - irc.server = server; - irc.registered = false; - irc.pread = make(chan string, 100); - irc.pwrite = make(chan string, 100); - irc.perror = make(chan os.Error, 10); - irc.EventChan = events; - irc.nick = nick; - irc.user = user; - connect(irc); - go handler(irc); - return irc, nil; +func (i *IRCConnection) Loop() { + for { + <-i.Error + i.Reconnect() + } +} + +func (i *IRCConnection) Connect(server string) os.Error { + i.server = server + fmt.Printf("Connecting to %s\n", i.server) + var err os.Error + i.socket, err = net.Dial("tcp", "", i.server) + if err != nil { + return err + } + fmt.Printf("Connected to %s (%s)\n", i.server, i.socket.RemoteAddr()) + i.pread = make(chan string, 100) + i.pwrite = make(chan string, 100) + i.Error = make(chan os.Error, 10) + go reader(i) + go writer(i) + go pinger(i) + i.pwrite <- fmt.Sprintf("NICK %s\r\n", i.nick) + i.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", i.user, i.user) + return nil +} + +func IRC(nick string, user string) *IRCConnection { + irc := new(IRCConnection) + irc.registered = false + irc.pread = make(chan string, 100) + irc.pwrite = make(chan string, 100) + irc.Error = make(chan os.Error) + irc.nick = nick + irc.user = user + irc.setupCallbacks() + return irc } diff --git a/irc_struct.go b/irc_struct.go index 14f4618..bd99452 100644 --- a/irc_struct.go +++ b/irc_struct.go @@ -5,65 +5,34 @@ package irc import ( - "os"; - "net"; -) - -type IRCEventCode int - -const ( - IRC_NOTICE_AUTH IRCEventCode = 1 << iota; - IRC_PING; - IRC_QUIT; - IRC_WELCOME; - IRC_SERVER_INFO; - IRC_SERVER_UPTIME; - IRC_SERVER_VERSION; - IRC_START_MOTD; - IRC_MOTD; - IRC_END_MOTD; - IRC_CHANINFO; - - IRC_STAT_USERS; - IRC_STAT_OPERS; - IRC_STAT_UNKN; - IRC_STAT_CONNS; - - IRC_CHAN_TIMESTAMP; - IRC_CHAN_NICKLIST; - IRC_CHAN_TOPIC; - IRC_CHAN_MODE; - - IRC_PRIVMSG; - IRC_ACTION; - IRC_JOIN; - - IRC_MODE; - - ERROR; - UNKNOWN; + "os" + "net" ) type IRCConnection struct { - socket net.Conn; - pread, pwrite chan string; - perror chan os.Error; - EventChan chan *IRCEvent; - Error os.Error; - nick string; - user string; - registered bool; - server string; + socket net.Conn + pread, pwrite chan string + Error chan os.Error + nick string + user string + registered bool + server string + + events map[string][]func(*IRCEvent) + + lastMessage int64; + ticker <-chan int64; + ticker2 <-chan int64; } type IRCEvent struct { - Message string; - RawMessage string; - Sender string; - SenderHost string; - SenderUser string; - Target string; - Channel string; - Code IRCEventCode; - Error os.Error; + Code string + Message string + Raw string + Nick string // + Host string //!@ + Source string // + User string // + + Arguments []string }