Rewritten to use callbacks, bit easier to use.

This commit is contained in:
tj 2010-01-06 19:32:35 +01:00
parent 455e0cd0dd
commit 7019ec3d0e
5 changed files with 181 additions and 296 deletions

View File

@ -1,6 +1,6 @@
include $(GOROOT)/src/Make.$(GOARCH) include $(GOROOT)/src/Make.$(GOARCH)
TARG=irc TARG=irc
GOFILES=irc.go irc_struct.go GOFILES=irc.go irc_struct.go irc_callback.go
include $(GOROOT)/src/Make.pkg include $(GOROOT)/src/Make.pkg

View File

@ -6,9 +6,11 @@ Event based irc client library.
Features Features
--------- ---------
* Event based * Event based. Register Callbacks for the events you need to handle.
* Automatic Reconnect * Handles basic irc demands for you:
* Handles basic irc demands for you (PING, VERSION) ** Standard CTCP
** Reconnections on errors
** Detect stoned servers
Install Install
---------- ----------
@ -20,3 +22,36 @@ Install
Example Example
---------- ----------
See example/test.go 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("<string>") //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")

View File

@ -1,44 +1,19 @@
package main package main
import ( import (
"irc"; "irc"
"fmt"; "fmt"
"os"; "os"
) )
func main() { func main() {
events := make(chan *irc.IRCEvent, 100); irccon := irc.IRC("testgo", "testgo")
irccon, err := irc.IRC("irc.efnet.net:6667", "testgo", "testgo", events); err := irccon.Connect("irc.efnet.net:6667")
if err != nil { if err != nil {
fmt.Printf("%s\n", err); fmt.Printf("%s\n", err)
fmt.Printf("%#v\n", irccon); fmt.Printf("%#v\n", irccon)
os.Exit(1); 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);
} }
irccon.AddCallback("001", func(e *irc.IRCEvent) { irccon.Join("#testgo") })
irccon.Loop();
} }

310
irc.go
View File

@ -5,195 +5,80 @@
package irc package irc
import ( import (
"fmt"; "fmt"
"net"; "net"
"os"; "os"
"bufio"; "bufio"
"regexp"; "strings"
"strings"; "time"
)
const (
VERSION = "GolangBOT v1.0"
) )
func reader(irc *IRCConnection) { func reader(irc *IRCConnection) {
br := bufio.NewReader(irc.socket); br := bufio.NewReader(irc.socket)
for { for {
msg, err := br.ReadString('\n'); msg, err := br.ReadString('\n')
if err != nil { if err != nil {
fmt.Printf("%s\n", err); irc.Error <- err
irc.perror <- err; return
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) { func writer(irc *IRCConnection) {
for { for {
b := strings.Bytes(<-irc.pwrite); b := strings.Bytes(<-irc.pwrite)
_, err := irc.socket.Write(b); _, err := irc.socket.Write(b)
if err != nil { if err != nil {
fmt.Printf("%s\n", err); fmt.Printf("%s\n", err)
irc.perror <- err; irc.Error <- err
return;
}
}
}
func reconnector(i *IRCConnection) {
fmt.Printf("Reconnecting\n");
for {
i.Error = connect(i);
if i.Error == nil {
return return
} }
} }
} }
var rx_server_msg = regexp.MustCompile("^:([^ ]+) ([^ ]+) ([^ ]+) :(.*)\r\n") //Pings the server if we have not recived any messages for 5 minutes
var rx_server_msg_c = regexp.MustCompile("^:([^ ]+) ([^ ]+) ([^ ]+) [@]* ([^ ]+) :(.*)\r\n") func pinger(i *IRCConnection) {
var rx_server_msg_p = regexp.MustCompile("^:([^ ]+) ([^ ]+) ([^ ]+) (.*)\r\n") i.ticker = time.Tick(1000 * 1000 * 1000 * 60 * 4) //Every 4 minutes
var rx_server_cmd = regexp.MustCompile("^([^:]+) :(.*)\r\n") //AUTH NOTICE, PING, ERROR i.ticker2 = time.Tick(1000 * 1000 * 1000 * 60 * 15) //Every 15 minutes
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);
for { for {
select { select {
case msg := <-irc.pread: case <-i.ticker:
e := irc.handle_command(msg); if time.Seconds()-i.lastMessage > 60*4 {
switch e.Code { i.SendRaw(fmt.Sprintf("PING %d", time.Nanoseconds()))
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.ticker2:
irc.EventChan <- e; i.SendRaw(fmt.Sprintf("PING %d", time.Nanoseconds()))
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);
} }
} }
} }
@ -210,43 +95,64 @@ func (irc *IRCConnection) Privmsg(target, message string) {
irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message) irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message)
} }
//Try to reconnect func (irc *IRCConnection) SendRaw(message string) {
func (irc *IRCConnection) Reconnect() os.Error { fmt.Printf("--> %s\n", message)
irc.socket, irc.Error = net.Dial("tcp", "", irc.server); irc.pwrite <- fmt.Sprintf("%s\r\n", message)
if irc.Error != nil {
return irc.Error
}
return nil;
} }
func connect(i *IRCConnection) os.Error { func (i *IRCConnection) Reconnect() os.Error {
fmt.Printf("Connecting to %s\n", i.server); for {
i.socket, i.Error = net.Dial("tcp", "", i.server); fmt.Printf("Reconnecting to %s\n", i.server)
if i.Error != nil { var err os.Error
return i.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()); fmt.Printf("Connected to %s (%s)\n", i.server, i.socket.RemoteAddr())
i.pread = make(chan string, 100); go reader(i)
i.pwrite = make(chan string, 100); go writer(i)
i.perror = make(chan os.Error, 10); i.pwrite <- fmt.Sprintf("NICK %s\r\n", i.nick)
go reader(i); i.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", i.user, i.user)
go writer(i); return nil
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;
} }
func IRC(server string, nick string, user string, events chan *IRCEvent) (*IRCConnection, os.Error) { func (i *IRCConnection) Loop() {
irc := new(IRCConnection); for {
irc.server = server; <-i.Error
irc.registered = false; i.Reconnect()
irc.pread = make(chan string, 100); }
irc.pwrite = make(chan string, 100); }
irc.perror = make(chan os.Error, 10);
irc.EventChan = events; func (i *IRCConnection) Connect(server string) os.Error {
irc.nick = nick; i.server = server
irc.user = user; fmt.Printf("Connecting to %s\n", i.server)
connect(irc); var err os.Error
go handler(irc); i.socket, err = net.Dial("tcp", "", i.server)
return irc, nil; 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
} }

View File

@ -5,65 +5,34 @@
package irc package irc
import ( import (
"os"; "os"
"net"; "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;
) )
type IRCConnection struct { type IRCConnection struct {
socket net.Conn; socket net.Conn
pread, pwrite chan string; pread, pwrite chan string
perror chan os.Error; Error chan os.Error
EventChan chan *IRCEvent; nick string
Error os.Error; user string
nick string; registered bool
user string; server string
registered bool;
server string; events map[string][]func(*IRCEvent)
lastMessage int64;
ticker <-chan int64;
ticker2 <-chan int64;
} }
type IRCEvent struct { type IRCEvent struct {
Message string; Code string
RawMessage string; Message string
Sender string; Raw string
SenderHost string; Nick string //<nick>
SenderUser string; Host string //<nick>!<usr>@<host>
Target string; Source string //<host>
Channel string; User string //<usr>
Code IRCEventCode;
Error os.Error; Arguments []string
} }