go-ircevent/irc.go

319 lines
7.3 KiB
Go
Raw Normal View History

2012-11-05 22:46:47 +00:00
// Copyright 2009 Thomas Jager <mail@jager.no> All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
2014-02-14 11:06:09 +00:00
/*
This package provides an event based IRC client library. It allows to
register callbacks for the events you need to handle. Its features
include handling standard CTCP, reconnecting on errors and detecting
stones servers.
*/
2012-11-05 22:46:47 +00:00
package irc
import (
"bufio"
"crypto/tls"
"errors"
2012-11-05 22:46:47 +00:00
"fmt"
"log"
"net"
"os"
"strings"
"time"
)
const (
VERSION = "go-ircevent v2.1"
2012-11-05 22:46:47 +00:00
)
2012-11-05 23:39:31 +00:00
func (irc *Connection) readLoop() {
br := bufio.NewReaderSize(irc.socket, 512)
2012-03-22 03:50:21 +00:00
for {
select {
case <-irc.endread:
irc.readerExit <- true
return
default:
// Set a read deadline based on the combined timeout and ping frequency
// We should ALWAYS have received a response from the server within the timeout
// after our own pings
if irc.socket != nil {
irc.socket.SetReadDeadline(time.Now().Add(irc.Timeout + irc.PingFreq))
}
msg, err := br.ReadString('\n')
// We got past our blocking read, so bin timeout
if irc.socket != nil {
var zero time.Time
irc.socket.SetReadDeadline(zero)
}
2012-03-22 03:50:21 +00:00
if err != nil {
irc.Error <- err
break
}
irc.lastMessage = time.Now()
msg = msg[:len(msg)-2] //Remove \r\n
event := &Event{Raw: msg}
if msg[0] == ':' {
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)]
}
2012-11-05 22:46:47 +00:00
}
split := strings.SplitN(msg, " :", 2)
args := strings.Split(split[0], " ")
event.Code = strings.ToUpper(args[0])
event.Arguments = args[1:]
if len(split) > 1 {
event.Arguments = append(event.Arguments, split[1])
}
/* XXX: len(args) == 0: args should be empty */
irc.RunCallbacks(event)
2012-11-05 22:46:47 +00:00
}
}
2012-03-22 03:50:21 +00:00
irc.readerExit <- true
2012-11-05 22:46:47 +00:00
}
2012-11-05 23:39:31 +00:00
func (irc *Connection) writeLoop() {
for {
select {
case <-irc.endwrite:
irc.writerExit <- true
return
default:
b, ok := <-irc.pwrite
if !ok || b == "" || irc.socket == nil {
irc.writerExit <- true
return
}
if irc.Debug {
irc.Log.Printf("--> %s\n", b)
}
// Set a write deadline based on the time out
irc.socket.SetWriteDeadline(time.Now().Add(irc.Timeout))
_, err := irc.socket.Write([]byte(b))
// Past blocking write, bin timeout
var zero time.Time
irc.socket.SetWriteDeadline(zero)
if err != nil {
irc.Error <- err
irc.writerExit <- true
return
}
2012-11-05 22:46:47 +00:00
}
}
irc.writerExit <- true
2012-11-05 22:46:47 +00:00
}
//Pings the server if we have not received any messages for 5 minutes
2012-11-05 23:39:31 +00:00
func (irc *Connection) pingLoop() {
ticker := time.NewTicker(1 * time.Minute) // Tick every minute for monitoring
ticker2 := time.NewTicker(irc.PingFreq) // Tick at the ping frequency.
2012-11-05 22:46:47 +00:00
for {
select {
case <-ticker.C:
//Ping if we haven't received anything from the server within the keep alive period
if time.Since(irc.lastMessage) >= irc.KeepAlive {
2012-11-05 23:39:31 +00:00
irc.SendRawf("PING %d", time.Now().UnixNano())
2012-11-05 22:46:47 +00:00
}
case <-ticker2.C:
//Ping at the ping frequency
2012-11-05 23:39:31 +00:00
irc.SendRawf("PING %d", time.Now().UnixNano())
2012-11-05 22:46:47 +00:00
//Try to recapture nickname if it's not as configured.
2012-11-05 23:39:31 +00:00
if irc.nick != irc.nickcurrent {
irc.nickcurrent = irc.nick
irc.SendRawf("NICK %s", irc.nick)
2012-11-05 22:46:47 +00:00
}
case <-irc.endping:
ticker.Stop()
ticker2.Stop()
irc.pingerExit <- true
2012-11-11 09:51:14 +00:00
return
2012-11-05 22:46:47 +00:00
}
}
}
func (irc *Connection) Loop() {
for !irc.stopped {
err := <-irc.Error
if irc.stopped {
break
}
irc.Log.Printf("Error: %s\n", err)
irc.Disconnect()
for !irc.stopped {
if err = irc.Connect(irc.server); err != nil {
irc.Log.Printf("Error: %s\n", err)
time.Sleep(1 * time.Second)
} else {
break
}
}
}
2012-11-05 22:46:47 +00:00
}
func (irc *Connection) Quit() {
irc.SendRaw("QUIT")
irc.stopped = true
irc.Disconnect()
2012-11-05 22:46:47 +00:00
}
func (irc *Connection) Join(channel string) {
irc.pwrite <- fmt.Sprintf("JOIN %s\r\n", channel)
}
func (irc *Connection) Part(channel string) {
irc.pwrite <- fmt.Sprintf("PART %s\r\n", channel)
}
func (irc *Connection) Notice(target, message string) {
irc.pwrite <- fmt.Sprintf("NOTICE %s :%s\r\n", target, message)
}
2012-11-11 09:37:52 +00:00
func (irc *Connection) Noticef(target, format string, a ...interface{}) {
irc.Notice(target, fmt.Sprintf(format, a...))
}
2012-11-05 22:46:47 +00:00
func (irc *Connection) Privmsg(target, message string) {
irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message)
}
2012-11-11 09:37:52 +00:00
func (irc *Connection) Privmsgf(target, format string, a ...interface{}) {
irc.Privmsg(target, fmt.Sprintf(format, a...))
}
2012-11-05 22:46:47 +00:00
func (irc *Connection) SendRaw(message string) {
irc.pwrite <- message + "\r\n"
2012-11-05 22:46:47 +00:00
}
func (irc *Connection) SendRawf(format string, a ...interface{}) {
irc.SendRaw(fmt.Sprintf(format, a...))
2012-11-05 22:46:47 +00:00
}
2013-03-13 11:53:08 +00:00
func (irc *Connection) Nick(n string) {
irc.nick = n
irc.SendRawf("NICK %s", n)
}
2012-11-05 22:46:47 +00:00
func (irc *Connection) GetNick() string {
return irc.nickcurrent
2012-11-05 22:46:47 +00:00
}
func (irc *Connection) Whois(nick string) {
irc.SendRawf("WHOIS %s", nick)
}
func (irc *Connection) Who(nick string) {
irc.SendRawf("WHO %s", nick)
}
func (irc *Connection) Mode(target string, modestring ...string) {
if len(modestring) > 0 {
mode := strings.Join(modestring, " ")
irc.SendRawf("MODE %s %s", target, mode)
return
}
irc.SendRawf("MODE %s", target)
}
// Sends all buffered messages (if possible),
// stops all goroutines and then closes the socket.
func (irc *Connection) Disconnect() {
irc.endping <- true
irc.endwrite <- true
irc.endread <- true
2012-11-05 23:39:31 +00:00
close(irc.pwrite)
close(irc.pread)
2012-11-05 22:46:47 +00:00
<-irc.readerExit
<-irc.writerExit
<-irc.pingerExit
irc.socket.Close()
irc.socket = nil
if irc.netsock != nil {
irc.netsock.Close()
irc.netsock = nil
}
irc.Error <- errors.New("Disconnect Called")
}
2012-03-22 03:50:21 +00:00
func (irc *Connection) Reconnect() error {
return irc.Connect(irc.server)
2012-11-05 22:46:47 +00:00
}
func (irc *Connection) Connect(server string) error {
irc.server = server
irc.stopped = false
2012-11-05 22:46:47 +00:00
var err error
2012-11-07 20:55:33 +00:00
if irc.UseTLS {
if irc.netsock, err = net.DialTimeout("tcp", irc.server, irc.Timeout); err == nil {
irc.socket = tls.Client(irc.netsock, irc.TLSConfig)
}
} else {
irc.socket, err = net.DialTimeout("tcp", irc.server, irc.Timeout)
}
2012-11-05 22:46:47 +00:00
if err != nil {
return err
}
irc.Log.Printf("Connected to %s (%s)\n", irc.server, irc.socket.RemoteAddr())
irc.pread = make(chan string, 10)
irc.pwrite = make(chan string, 10)
irc.Error = make(chan error, 2)
go irc.readLoop()
go irc.writeLoop()
go irc.pingLoop()
if len(irc.Password) > 0 {
irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password)
2012-11-05 22:46:47 +00:00
}
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)
2012-11-05 22:46:47 +00:00
return nil
}
func IRC(nick, user string) *Connection {
irc := &Connection{
nick: nick,
user: user,
Log: log.New(os.Stdout, "", log.LstdFlags),
readerExit: make(chan bool),
writerExit: make(chan bool),
pingerExit: make(chan bool),
endping: make(chan bool),
endread: make(chan bool),
endwrite: make(chan bool),
Version: VERSION,
KeepAlive: 4 * time.Minute,
Timeout: 1 * time.Minute,
PingFreq: 15 * time.Minute,
}
2012-11-05 22:46:47 +00:00
irc.setupCallbacks()
return irc
}