diff --git a/irc.go b/irc.go index 1910704..6eb66f7 100644 --- a/irc.go +++ b/irc.go @@ -12,22 +12,25 @@ import ( "os" "strings" "time" + "crypto/tls" ) const ( - VERSION = "GolangBOT v1.0" + VERSION = "cleanirc v1.0" ) var error_ bool -func reader(irc *Connection) { +func readLoop(irc *Connection) { br := bufio.NewReader(irc.socket) - for !error_ { + + for !irc.reconnecting { msg, err := br.ReadString('\n') if err != nil { - irc.Error <- err + irc.Error <-err break } + irc.lastMessage = time.Now() msg = msg[0 : len(msg)-2] //Remove \r\n event := &Event{Raw: msg} @@ -35,48 +38,58 @@ func reader(irc *Connection) { if i := strings.Index(msg, " "); i > -1 { event.Source = msg[1:i] msg = msg[i+1 : len(msg)] + } else { irc.log.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.SplitN(msg, " :", 2) if len(args) > 1 { event.Message = args[1] } + args = strings.Split(args[0], " ") event.Code = strings.ToUpper(args[0]) + if len(args) > 1 { event.Arguments = args[1:len(args)] } + /* XXX: len(args) == 0: args should be empty */ + irc.RunCallbacks(event) } - irc.syncreader <- true + + irc.syncreader <-true } -func writer(irc *Connection) { +func writeLoop(irc *Connection) { b, ok := <-irc.pwrite - for !error_ && ok { + for !irc.reconnecting && ok { if b == "" || irc.socket == nil { break } + _, err := irc.socket.Write([]byte(b)) if err != nil { irc.log.Printf("%s\n", err) irc.Error <- err break } + b, ok = <-irc.pwrite } - irc.syncwriter <- true + irc.syncwriter <-true } //Pings the server if we have not recived any messages for 5 minutes -func pinger(i *Connection) { +func pingLoop(i *Connection) { i.ticker = time.Tick(1 * time.Minute) //Tick every minute. i.ticker2 = time.Tick(15 * time.Minute) //Tick every 15 minutes. for { @@ -86,7 +99,7 @@ func pinger(i *Connection) { if time.Since(i.lastMessage) >= (4 * time.Minute) { i.SendRawf("PING %d", time.Now().UnixNano()) } - case <-i.ticker2: + case <-irc.ticker2: //Ping every 15 minutes. i.SendRawf("PING %d", time.Now().UnixNano()) //Try to recapture nickname if it's not as configured. @@ -145,18 +158,13 @@ func (i *Connection) Reconnect() error { for { i.log.Printf("Reconnecting to %s\n", i.server) var err error - i.socket, err = net.Dial("tcp", i.server) + irc.Connect(irc.server) if err == nil { break } i.log.Printf("Error: %s\n", err) } error_ = false - i.log.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 } @@ -170,34 +178,47 @@ func (i *Connection) Loop() { error_ = true i.Reconnect() } - close(i.pwrite) - close(i.pread) - <-i.syncreader - <-i.syncwriter + + close(irc.pwrite) + close(irc.pread) + + <-irc.syncreader + <-irc.syncwriter } -func (i *Connection) Connect(server string) error { - i.server = server - i.log.Printf("Connecting to %s\n", i.server) + +func (irc *Connection) Connect(server string) error { + irc.server = server + irc.log.Printf("Connecting to %s\n", i.server) var err error - i.socket, err = net.Dial("tcp", i.server) + if irc.UseSSL { + irc.socket, err = tls.Dial("tcp", irc.server, irc.SSLConfig) + } else { + irc.socket, err = net.Dial("tcp", irc.server) + } if err != nil { return err } - i.log.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 error, 10) - i.syncreader = make(chan bool) - i.syncwriter = make(chan bool) - 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) - if len(i.Password) > 0 { - i.pwrite <- fmt.Sprintf("PASS %s\r\n", i.Password) + irc.log.Printf("Connected to %s (%s)\n", irc.server, irc.socket.RemoteAddr()) + return irc.postConnect() +} + +func (irc *Connection) postConnect() error { + irc.pread = make(chan string, 100) + irc.pwrite = make(chan string, 100) + irc.Error = make(chan error, 10) + irc.syncreader = make(chan bool) + irc.syncwriter = make(chan bool) + + go irc.readLoop() + go irc.writeLoop() + go irc.pingLoop() + + if len(irc.Password) > 0 { + irc.pwrite <-fmt.Sprintf("PASS %s\r\n", irc.Password) } + irc.pwrite <-fmt.Sprintf("NICK %s\r\n", irc.nick) + irc.pwrite <-fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, irc.user) return nil } diff --git a/irc_callback.go b/irc_callback.go index b8f4d69..0346669 100644 --- a/irc_callback.go +++ b/irc_callback.go @@ -8,8 +8,10 @@ import ( func (irc *Connection) AddCallback(eventcode string, callback func(*Event)) { eventcode = strings.ToUpper(eventcode) + if _, ok := irc.events[eventcode]; ok { irc.events[eventcode] = append(irc.events[eventcode], callback) + } else { irc.events[eventcode] = make([]func(*Event), 1) irc.events[eventcode][0] = callback @@ -18,6 +20,7 @@ func (irc *Connection) AddCallback(eventcode string, callback func(*Event)) { func (irc *Connection) ReplaceCallback(eventcode string, i int, callback func(*Event)) { eventcode = strings.ToUpper(eventcode) + if event, ok := irc.events[eventcode]; ok { if i < len(event) { event[i] = callback @@ -32,28 +35,37 @@ func (irc *Connection) ReplaceCallback(eventcode string, i int, callback func(*E func (irc *Connection) RunCallbacks(event *Event) { if event.Code == "PRIVMSG" && len(event.Message) > 0 && event.Message[0] == '\x01' { event.Code = "CTCP" //Unknown CTCP + if i := strings.LastIndex(event.Message, "\x01"); i > -1 { event.Message = event.Message[1:i] } + if event.Message == "VERSION" { event.Code = "CTCP_VERSION" + } else if event.Message == "TIME" { event.Code = "CTCP_TIME" + } else if event.Message[0:4] == "PING" { event.Code = "CTCP_PING" + } else if event.Message == "USERINFO" { event.Code = "CTCP_USERINFO" + } else if event.Message == "CLIENTINFO" { event.Code = "CTCP_CLIENTINFO" } } + if callbacks, ok := irc.events[event.Code]; ok { if irc.VerboseCallbackHandler { irc.log.Printf("%v (%v) >> %#v\n", event.Code, len(callbacks), event) } + for _, callback := range callbacks { go callback(event) } + } else if irc.VerboseCallbackHandler { irc.log.Printf("%v (0) >> %#v\n", event.Code, event) } @@ -93,6 +105,7 @@ func (irc *Connection) setupCallbacks() { irc.AddCallback("433", func(e *Event) { if len(irc.nickcurrent) > 8 { irc.nickcurrent = "_" + irc.nickcurrent + } else { irc.nickcurrent = irc.nickcurrent + "_" } diff --git a/irc_struct.go b/irc_struct.go index 70c7176..019f5a6 100644 --- a/irc_struct.go +++ b/irc_struct.go @@ -5,23 +5,30 @@ package irc import ( + "crypto/tls" "log" "net" - "time" + "time" ) type Connection struct { + Error chan error + Log chan string + Password string + UseSSL bool + SSLConfig *tls.Config + socket net.Conn pread, pwrite chan string - Error chan error syncreader, syncwriter chan bool - nick string //The nickname we want. - nickcurrent string //The nickname we currently have. - user string - registered bool - server string - Password string - events map[string][]func(*Event) + reconnecting bool + + nick string //The nickname we want. + nickcurrent string //The nickname we currently have. + user string + registered bool + server string + events map[string][]func(*IRCEvent) lastMessage time.Time ticker <-chan time.Time @@ -34,13 +41,12 @@ type Connection struct { } type Event struct { - Code string - Message string - Raw string - Nick string // - Host string //!@ - Source string // - User string // - + Code string + Message string + Raw string + Nick string // + Host string //!@ + Source string // + User string // Arguments []string }