reformat source with gofmt to nuke all of those unneeded semicolons

This commit is contained in:
Alex Bramley 2009-12-17 21:30:18 +00:00
parent 1c31d5fb47
commit ae8e34ff0e
6 changed files with 474 additions and 477 deletions

View File

@ -13,17 +13,17 @@ You can build the test client also with:
make make
./gobot ./gobot
This will connect to freenode and join #go-lang by default, so be careful ;-) This will connect to freenode and join `#go-lang` by default, so be careful ;-)
### Using the framework ### Using the framework
The test client provides a good (if basic) example of how to use the framework. The test client provides a good (if basic) example of how to use the framework.
Reading irc/handlers.go gives a more in-depth look at how handlers can be Reading `irc/handlers.go` gives a more in-depth look at how handlers can be
written. Commands to be sent to the server (e.g. PRIVMSG) are methods of the written. Commands to be sent to the server (e.g. PRIVMSG) are methods of the
main \*irc.Conn object, and can be found in irc/commands.go (not all of the main `*irc.Conn` object, and can be found in `irc/commands.go` (not all of the
possible IRC commands are implemented yet). Events are produced directly from possible IRC commands are implemented yet). Events are produced directly from
the messages from the IRC server, so you have to handle e.g. "332" for the messages from the IRC server, so you have to handle e.g. "332" for
RPL\_TOPIC to get the topic for a channel. `RPL_TOPIC` to get the topic for a channel.
The vast majority of handlers implemented within the framework implement state The vast majority of handlers implemented within the framework implement state
tracking of all nicks in channels that the client is also present in. It's tracking of all nicks in channels that the client is also present in. It's

View File

@ -1,73 +1,70 @@
package main package main
import ( import (
"./irc/_obj/irc"; "./irc/_obj/irc"
"fmt"; "fmt"
"os"; "os"
"bufio"; "bufio"
"strings"; "strings"
) )
func main() { func main() {
// create new IRC connection // create new IRC connection
c := irc.New("GoTest", "gotest", "GoBot"); c := irc.New("GoTest", "gotest", "GoBot")
c.AddHandler("connected", c.AddHandler("connected",
func(conn *irc.Conn, line *irc.Line) { func(conn *irc.Conn, line *irc.Line) { conn.Join("#go-nuts") })
conn.Join("#go-lang");
}
);
// connect to server // connect to server
if err := c.Connect("irc.freenode.net", ""); err != nil { if err := c.Connect("irc.freenode.net", ""); err != nil {
fmt.Printf("Connection error: %s\n", err); fmt.Printf("Connection error: %s\n", err)
return; return
} }
// set up a goroutine to read commands from stdin // set up a goroutine to read commands from stdin
in := make(chan string, 4); in := make(chan string, 4)
reallyquit := false; reallyquit := false
go func() { go func() {
con := bufio.NewReader(os.Stdin); con := bufio.NewReader(os.Stdin)
for { for {
s, err := con.ReadString('\n'); s, err := con.ReadString('\n')
if err != nil { if err != nil {
// wha?, maybe ctrl-D... // wha?, maybe ctrl-D...
close(in); close(in)
break; break
} }
// no point in sending empty lines down the channel // no point in sending empty lines down the channel
if len(s) > 2 { if len(s) > 2 {
in <- s[0:len(s)-1] in <- s[0:len(s)-1]
} }
} }
}(); }()
// set up a goroutine to do parsey things with the stuff from stdin // set up a goroutine to do parsey things with the stuff from stdin
go func() { go func() {
for { for {
if closed(in) { if closed(in) {
break; break
} }
cmd := <-in; cmd := <-in
if cmd[0] == ':' { if cmd[0] == ':' {
switch idx := strings.Index(cmd, " "); { switch idx := strings.Index(cmd, " "); {
case idx == -1: case idx == -1:
continue; continue
case cmd[1] == 'q': case cmd[1] == 'q':
reallyquit = true; reallyquit = true
c.Quit(cmd[idx+1:len(cmd)]); c.Quit(cmd[idx+1 : len(cmd)])
case cmd[1] == 'j': case cmd[1] == 'j':
c.Join(cmd[idx+1:len(cmd)]); c.Join(cmd[idx+1 : len(cmd)])
case cmd[1] == 'p': case cmd[1] == 'p':
c.Part(cmd[idx+1:len(cmd)]); c.Part(cmd[idx+1 : len(cmd)])
case cmd[1] == 'd': case cmd[1] == 'd':
fmt.Printf(c.String()); fmt.Printf(c.String())
} }
} else { } else {
c.Raw(cmd) c.Raw(cmd)
} }
} }
}(); }()
// stall here waiting for asplode on error channel // stall here waiting for asplode on error channel
for { for {
@ -76,17 +73,17 @@ func main() {
// server for some reason (e.g. quit, kill or ping timeout) // server for some reason (e.g. quit, kill or ping timeout)
// if we don't really want to quit, reconnect! // if we don't really want to quit, reconnect!
if !reallyquit { if !reallyquit {
fmt.Println("Reconnecting..."); fmt.Println("Reconnecting...")
if err := c.Connect("irc.freenode.net", ""); err != nil { if err := c.Connect("irc.freenode.net", ""); err != nil {
fmt.Printf("Connection error: %s\n", err); fmt.Printf("Connection error: %s\n", err)
break; break
} }
continue; continue
} }
break; break
} }
if err := <-c.Err; err != nil { if err := <-c.Err; err != nil {
fmt.Printf("goirc error: %s\n", err); fmt.Printf("goirc error: %s\n", err)
} }
} }
} }

View File

@ -4,123 +4,101 @@ package irc
// send to the server using an Conn connection // send to the server using an Conn connection
import ( import (
// "fmt"; // "fmt";
"reflect"; "reflect"
) )
// This could be a lot less ugly with the ability to manipulate // This could be a lot less ugly with the ability to manipulate
// the symbol table and add methods/functions on the fly // the symbol table and add methods/functions on the fly
// [ CMD, FMT, FMTARGS ] etc. // [ CMD, FMT, FMTARGS ] etc.
// send a raw line to the server for debugging etc // send a raw line to the server for debugging etc
func (conn *Conn) Raw(s string) { func (conn *Conn) Raw(s string) { conn.out <- s }
conn.out <- s
}
// send a PASS command to the server // send a PASS command to the server
func (conn *Conn) Pass(p string) { func (conn *Conn) Pass(p string) { conn.out <- "PASS "+p }
conn.out <- "PASS " + p
}
// send a NICK command to the server // send a NICK command to the server
func (conn *Conn) Nick(n string) { func (conn *Conn) Nick(n string) { conn.out <- "NICK "+n }
conn.out <- "NICK " + n
}
// send a USER command to the server // send a USER command to the server
func (conn *Conn) User(u, n string) { func (conn *Conn) User(u, n string) { conn.out <- "USER "+u+" 12 * :"+n }
conn.out <- "USER " + u + " 12 * :" + n
}
// send a JOIN command to the server // send a JOIN command to the server
func (conn *Conn) Join(c string) { func (conn *Conn) Join(c string) { conn.out <- "JOIN "+c }
conn.out <- "JOIN " + c
}
// send a PART command to the server // send a PART command to the server
func (conn *Conn) Part(c string, a ...) { func (conn *Conn) Part(c string, a ...) {
msg := getStringMsg(a); msg := getStringMsg(a)
if msg != "" { if msg != "" {
msg = " :" + msg msg = " :" + msg
} }
conn.out <- "PART " + c + msg conn.out <- "PART "+c+msg
} }
// send a QUIT command to the server // send a QUIT command to the server
func (conn *Conn) Quit(a ...) { func (conn *Conn) Quit(a ...) {
msg := getStringMsg(a); msg := getStringMsg(a)
if msg == "" { if msg == "" {
msg = "GoBye!" msg = "GoBye!"
} }
conn.out <- "QUIT :" + msg conn.out <- "QUIT :"+msg
} }
// send a WHOIS command to the server // send a WHOIS command to the server
func (conn *Conn) Whois(t string) { func (conn *Conn) Whois(t string) { conn.out <- "WHOIS "+t }
conn.out <- "WHOIS " + t
}
// send a WHO command to the server // send a WHO command to the server
func (conn *Conn) Who(t string) { func (conn *Conn) Who(t string) { conn.out <- "WHO "+t }
conn.out <- "WHO " + t
}
// send a PRIVMSG to the target t // send a PRIVMSG to the target t
func (conn *Conn) Privmsg(t, msg string) { func (conn *Conn) Privmsg(t, msg string) { conn.out <- "PRIVMSG "+t+" :"+msg }
conn.out <- "PRIVMSG " + t + " :" + msg
}
// send a NOTICE to the target t // send a NOTICE to the target t
func (conn *Conn) Notice(t, msg string) { func (conn *Conn) Notice(t, msg string) { conn.out <- "NOTICE "+t+" :"+msg }
conn.out <- "NOTICE " + t + " :" +msg
}
// send a (generic) CTCP to the target t // send a (generic) CTCP to the target t
func (conn *Conn) Ctcp(t, ctcp string, a ...) { func (conn *Conn) Ctcp(t, ctcp string, a ...) {
msg := getStringMsg(a); msg := getStringMsg(a)
if msg != "" { if msg != "" {
msg = " " + msg msg = " " + msg
} }
conn.Privmsg(t, "\001" + ctcp + msg + "\001") conn.Privmsg(t, "\001"+ctcp+msg+"\001")
} }
// send a generic CTCP reply to the target t // send a generic CTCP reply to the target t
func (conn *Conn) CtcpReply(t, ctcp string, a ...) { func (conn *Conn) CtcpReply(t, ctcp string, a ...) {
msg := getStringMsg(a); msg := getStringMsg(a)
if msg != "" { if msg != "" {
msg = " " + msg msg = " " + msg
} }
conn.Notice(t, "\001" + ctcp + msg + "\001") conn.Notice(t, "\001"+ctcp+msg+"\001")
} }
// send a CTCP "VERSION" to the target t // send a CTCP "VERSION" to the target t
func (conn *Conn) Version(t string) { func (conn *Conn) Version(t string) { conn.Ctcp(t, "VERSION") }
conn.Ctcp(t, "VERSION")
}
// send a CTCP "ACTION" to the target t -- /me does stuff! // send a CTCP "ACTION" to the target t -- /me does stuff!
func (conn *Conn) Action(t, msg string) { func (conn *Conn) Action(t, msg string) { conn.Ctcp(t, "ACTION", msg) }
conn.Ctcp(t, "ACTION", msg)
}
// send a TOPIC command to the channel c // send a TOPIC command to the channel c
func (conn *Conn) Topic(c string, a ...) { func (conn *Conn) Topic(c string, a ...) {
topic := getStringMsg(a); topic := getStringMsg(a)
if topic != "" { if topic != "" {
topic = " :" + topic topic = " :" + topic
} }
conn.out <- "TOPIC " + c + topic conn.out <- "TOPIC "+c+topic
} }
// send a MODE command (this one gets complicated) // send a MODE command (this one gets complicated)
// Mode(t) retrieves the user or channel modes for target t // Mode(t) retrieves the user or channel modes for target t
// Mode(t, "string" // Mode(t, "string"
func (conn *Conn) Mode(t string, a ...) { func (conn *Conn) Mode(t string, a ...) {
mode := getStringMsg(a); mode := getStringMsg(a)
if mode != "" { if mode != "" {
mode = " " + mode mode = " " + mode
} }
conn.out <- "MODE " + t + mode conn.out <- "MODE "+t+mode
} }
func getStringMsg(a ...) (msg string) { func getStringMsg(a ...) (msg string) {
@ -128,7 +106,7 @@ func getStringMsg(a ...) (msg string) {
// the below stolen and munged from fmt/print.go func getString() // the below stolen and munged from fmt/print.go func getString()
if v := reflect.NewValue(a).(*reflect.StructValue); v.NumField() > 0 { if v := reflect.NewValue(a).(*reflect.StructValue); v.NumField() > 0 {
if s, ok := v.Field(0).(*reflect.StringValue); ok { if s, ok := v.Field(0).(*reflect.StringValue); ok {
return s.Get(); return s.Get()
} }
if b, ok := v.Interface().([]byte); ok { if b, ok := v.Interface().([]byte); ok {
return string(b) return string(b)

View File

@ -1,76 +1,76 @@
package irc package irc
import ( import (
"bufio"; "bufio"
"os"; "os"
"net"; "net"
"fmt"; "fmt"
"strings"; "strings"
) )
// the IRC connection object // the IRC connection object
type Conn struct { type Conn struct {
// Hostname, Nickname, etc. // Hostname, Nickname, etc.
Host string; Host string
Me *Nick; Me *Nick
// I/O stuff to server // I/O stuff to server
sock *net.TCPConn; sock *net.TCPConn
io *bufio.ReadWriter; io *bufio.ReadWriter
in chan *Line; in chan *Line
out chan string; out chan string
connected bool; connected bool
// Error channel to transmit any fail back to the user // Error channel to transmit any fail back to the user
Err chan os.Error; Err chan os.Error
// Event handler mapping // Event handler mapping
events map[string] []func (*Conn, *Line); events map[string][]func(*Conn, *Line)
// Map of channels we're on // Map of channels we're on
chans map[string] *Channel; chans map[string]*Channel
// Map of nicks we know about // Map of nicks we know about
nicks map[string] *Nick; nicks map[string]*Nick
} }
// We'll parse an incoming line into this struct // We'll parse an incoming line into this struct
// raw =~ ":nick!user@host cmd args[] :text" // raw =~ ":nick!user@host cmd args[] :text"
// src == "nick!user@host" // src == "nick!user@host"
type Line struct { type Line struct {
Nick, Ident, Host, Src string; Nick, Ident, Host, Src string
Cmd, Text, Raw string; Cmd, Text, Raw string
Args []string; Args []string
} }
// construct a new IRC Connection object // construct a new IRC Connection object
func New(nick, user, name string) *Conn { func New(nick, user, name string) *Conn {
conn := new(Conn); conn := new(Conn)
conn.initialise(); conn.initialise()
conn.Me = conn.NewNick(nick, user, name, ""); conn.Me = conn.NewNick(nick, user, name, "")
conn.setupEvents(); conn.setupEvents()
return conn; return conn
} }
func (conn *Conn) initialise() { func (conn *Conn) initialise() {
// allocate meh some memoraaaahh // allocate meh some memoraaaahh
fmt.Println("irc.initialise(): initialising..."); fmt.Println("irc.initialise(): initialising...")
conn.nicks = make(map[string] *Nick); conn.nicks = make(map[string]*Nick)
conn.chans = make(map[string] *Channel); conn.chans = make(map[string]*Channel)
conn.in = make(chan *Line, 32); conn.in = make(chan *Line, 32)
conn.out = make(chan string, 32); conn.out = make(chan string, 32)
conn.Err = make(chan os.Error, 4); conn.Err = make(chan os.Error, 4)
conn.io = nil; conn.io = nil
conn.sock = nil; conn.sock = nil
} }
// connect the IRC connection object to a host // connect the IRC connection object to a host
func (conn *Conn) Connect(host, pass string) os.Error { func (conn *Conn) Connect(host, pass string) os.Error {
if conn.connected { if conn.connected {
return os.NewError(fmt.Sprintf("irc.Connect(): already connected to %s, cannot connect to %s", conn.Host, host)); return os.NewError(fmt.Sprintf("irc.Connect(): already connected to %s, cannot connect to %s", conn.Host, host))
} }
if !hasPort(host) { if !hasPort(host) {
host += ":6667"; host += ":6667"
} }
if addr, err := net.ResolveTCPAddr(host); err != nil { if addr, err := net.ResolveTCPAddr(host); err != nil {
@ -78,99 +78,94 @@ func (conn *Conn) Connect(host, pass string) os.Error {
} else if conn.sock, err = net.DialTCP("tcp", nil, addr); err != nil { } else if conn.sock, err = net.DialTCP("tcp", nil, addr); err != nil {
return err return err
} }
fmt.Println("irc.Connect(): connected happily..."); fmt.Println("irc.Connect(): connected happily...")
conn.Host = host; conn.Host = host
conn.io = bufio.NewReadWriter( conn.io = bufio.NewReadWriter(
bufio.NewReader(conn.sock), bufio.NewReader(conn.sock),
bufio.NewWriter(conn.sock), bufio.NewWriter(conn.sock))
); go conn.send()
go conn.send(); go conn.recv()
go conn.recv();
if pass != "" { if pass != "" {
conn.Pass(pass) conn.Pass(pass)
} }
conn.Nick(conn.Me.Nick); conn.Nick(conn.Me.Nick)
conn.User(conn.Me.Ident, conn.Me.Name); conn.User(conn.Me.Ident, conn.Me.Name)
go conn.runLoop(); go conn.runLoop()
fmt.Println("irc.Connect(): launched runLoop() goroutine."); fmt.Println("irc.Connect(): launched runLoop() goroutine.")
return nil; return nil
} }
// dispatch a nicely formatted os.Error to the error channel // dispatch a nicely formatted os.Error to the error channel
func (conn *Conn) error(s string, a ...) { func (conn *Conn) error(s string, a ...) { conn.Err <- os.NewError(fmt.Sprintf(s, a)) }
conn.Err <- os.NewError(fmt.Sprintf(s, a));
}
// copied from http.client for great justice // copied from http.client for great justice
func hasPort(s string) bool { func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
}
// dispatch input from channel as \r\n terminated line to peer // dispatch input from channel as \r\n terminated line to peer
func (conn *Conn) send() { func (conn *Conn) send() {
for { for {
line := <-conn.out; line := <-conn.out
if closed(conn.out) { if closed(conn.out) {
break; break
} }
if err := conn.io.WriteString(line + "\r\n"); err != nil { if err := conn.io.WriteString(line + "\r\n"); err != nil {
conn.error("irc.send(): %s", err.String()); conn.error("irc.send(): %s", err.String())
conn.shutdown(); conn.shutdown()
break; break
} }
conn.io.Flush(); conn.io.Flush()
fmt.Println("-> " + line); fmt.Println("-> " + line)
} }
} }
// receive one \r\n terminated line from peer, parse and dispatch it // receive one \r\n terminated line from peer, parse and dispatch it
func (conn *Conn) recv() { func (conn *Conn) recv() {
for { for {
s, err := conn.io.ReadString('\n'); s, err := conn.io.ReadString('\n')
if err != nil { if err != nil {
conn.error("irc.recv(): %s", err.String()); conn.error("irc.recv(): %s", err.String())
conn.shutdown(); conn.shutdown()
break; break
} }
// chop off \r\n // chop off \r\n
s = s[0:len(s)-2]; s = s[0 : len(s)-2]
fmt.Println("<- " + s); fmt.Println("<- " + s)
line := &Line{Raw: s}; line := &Line{Raw: s}
if s[0] == ':' { if s[0] == ':' {
// remove a source and parse it // remove a source and parse it
if idx := strings.Index(s, " "); idx != -1 { if idx := strings.Index(s, " "); idx != -1 {
line.Src, s = s[1:idx], s[idx+1:len(s)]; line.Src, s = s[1:idx], s[idx+1:len(s)]
} else { } else {
// pretty sure we shouldn't get here ... // pretty sure we shouldn't get here ...
line.Src = s[1:len(s)]; line.Src = s[1:len(s)]
conn.in <- line; conn.in <- line
continue; continue
} }
// src can be the hostname of the irc server or a nick!user@host // src can be the hostname of the irc server or a nick!user@host
line.Host = line.Src; line.Host = line.Src
nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@"); nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@")
if uidx != -1 && nidx != -1 { if uidx != -1 && nidx != -1 {
line.Nick = line.Src[0:nidx]; line.Nick = line.Src[0:nidx]
line.Ident = line.Src[nidx+1:uidx]; line.Ident = line.Src[nidx+1 : uidx]
line.Host = line.Src[uidx+1:len(line.Src)]; line.Host = line.Src[uidx+1 : len(line.Src)]
} }
} }
// now we're here, we've parsed a :nick!user@host or :server off // now we're here, we've parsed a :nick!user@host or :server off
// s should contain "cmd args[] :text" // s should contain "cmd args[] :text"
args := strings.Split(s, " :", 2); args := strings.Split(s, " :", 2)
if len(args) > 1 { if len(args) > 1 {
line.Text = args[1]; line.Text = args[1]
} }
args = strings.Split(args[0], " ", 0); args = strings.Split(args[0], " ", 0)
line.Cmd = strings.ToUpper(args[0]); line.Cmd = strings.ToUpper(args[0])
if len(args) > 1 { if len(args) > 1 {
line.Args = args[1:len(args)]; line.Args = args[1:len(args)]
} }
conn.in <- line conn.in <- line
} }
@ -179,50 +174,49 @@ func (conn *Conn) recv() {
func (conn *Conn) runLoop() { func (conn *Conn) runLoop() {
for { for {
if closed(conn.in) { if closed(conn.in) {
break; break
} }
select { select {
case line := <-conn.in: case line := <-conn.in:
conn.dispatchEvent(line); conn.dispatchEvent(line)
} }
} }
fmt.Println("irc.runLoop(): Exited runloop..."); fmt.Println("irc.runLoop(): Exited runloop...")
// if we fall off the end here due to shutdown, // if we fall off the end here due to shutdown,
// reinit everything once the runloop is done // reinit everything once the runloop is done
// so that Connect() can be called again. // so that Connect() can be called again.
conn.initialise(); conn.initialise()
} }
func (conn *Conn) shutdown() { func (conn *Conn) shutdown() {
close(conn.in); close(conn.in)
close(conn.out); close(conn.out)
close(conn.Err); close(conn.Err)
conn.connected = false; conn.connected = false
conn.sock.Close(); conn.sock.Close()
fmt.Println("irc.shutdown(): shut down sockets and channels!"); fmt.Println("irc.shutdown(): shut down sockets and channels!")
} }
func (conn *Conn) String() string { func (conn *Conn) String() string {
str := "GoIRC Connection\n"; str := "GoIRC Connection\n"
str += "----------------\n\n"; str += "----------------\n\n"
if conn.connected { if conn.connected {
str += "Connected to " + conn.Host + "\n\n" str += "Connected to " + conn.Host + "\n\n"
} else { } else {
str += "Not currently connected!\n\n"; str += "Not currently connected!\n\n"
} }
str += conn.Me.String() + "\n"; str += conn.Me.String() + "\n"
str += "GoIRC Channels\n"; str += "GoIRC Channels\n"
str += "--------------\n\n"; str += "--------------\n\n"
for _, ch := range conn.chans { for _, ch := range conn.chans {
str += ch.String() + "\n" str += ch.String() + "\n"
} }
str += "GoIRC NickNames\n"; str += "GoIRC NickNames\n"
str += "---------------\n\n"; str += "---------------\n\n"
for _, n := range conn.nicks { for _, n := range conn.nicks {
if n != conn.Me { if n != conn.Me {
str += n.String() + "\n" str += n.String() + "\n"
} }
} }
return str; return str
} }

View File

@ -4,28 +4,28 @@ package irc
// to manage tracking an irc connection etc. // to manage tracking an irc connection etc.
import ( import (
"strings"; "strings"
"strconv"; "strconv"
) )
// Add an event handler for a specific IRC command // Add an event handler for a specific IRC command
func (conn *Conn) AddHandler(name string, f func (*Conn, *Line)) { func (conn *Conn) AddHandler(name string, f func(*Conn, *Line)) {
n := strings.ToUpper(name); n := strings.ToUpper(name)
if e, ok := conn.events[n]; ok { if e, ok := conn.events[n]; ok {
if len(e) == cap(e) { if len(e) == cap(e) {
// crap, we're full. expand e by another 10 handler slots // crap, we're full. expand e by another 10 handler slots
ne := make([]func (*Conn, *Line), len(e), len(e)+10); ne := make([]func(*Conn, *Line), len(e), len(e)+10)
for i := 0; i<len(e); i++ { for i := 0; i < len(e); i++ {
ne[i] = e[i]; ne[i] = e[i]
} }
e = ne; e = ne
} }
e = e[0:len(e)+1]; e = e[0 : len(e)+1]
e[len(e)-1] = f; e[len(e)-1] = f
} else { } else {
e := make([]func (*Conn, *Line), 1, 10); e := make([]func(*Conn, *Line), 1, 10)
e[0] = f; e[0] = f
conn.events[n] = e; conn.events[n] = e
} }
} }
@ -34,7 +34,7 @@ func (conn *Conn) dispatchEvent(line *Line) {
// seems that we end up dispatching an event with a nil line when receiving // seems that we end up dispatching an event with a nil line when receiving
// EOF from the server. Until i've tracked down why.... // EOF from the server. Until i've tracked down why....
if line == nil { if line == nil {
conn.error("irc.dispatchEvent(): buh? line == nil :-("); conn.error("irc.dispatchEvent(): buh? line == nil :-(")
return return
} }
@ -44,25 +44,25 @@ func (conn *Conn) dispatchEvent(line *Line) {
if line.Cmd == "PRIVMSG" && len(line.Text) > 2 && if line.Cmd == "PRIVMSG" && len(line.Text) > 2 &&
line.Text[0] == '\001' && line.Text[len(line.Text)-1] == '\001' { line.Text[0] == '\001' && line.Text[len(line.Text)-1] == '\001' {
// WOO, it's a CTCP message // WOO, it's a CTCP message
t := strings.Split(line.Text[1:len(line.Text)-1], " ", 2); t := strings.Split(line.Text[1:len(line.Text)-1], " ", 2)
if c := strings.ToUpper(t[0]); c == "ACTION" { if c := strings.ToUpper(t[0]); c == "ACTION" {
// make a CTCP ACTION it's own event a-la PRIVMSG // make a CTCP ACTION it's own event a-la PRIVMSG
line.Cmd = c; line.Cmd = c
} else { } else {
// otherwise, dispatch a generic CTCP event that // otherwise, dispatch a generic CTCP event that
// contains the type of CTCP in line.Args[0] // contains the type of CTCP in line.Args[0]
line.Cmd = "CTCP"; line.Cmd = "CTCP"
a := make([]string, len(line.Args)+1); a := make([]string, len(line.Args)+1)
a[0] = c; a[0] = c
for i:=0; i<len(line.Args); i++ { for i := 0; i < len(line.Args); i++ {
a[i+1] = line.Args[i]; a[i+1] = line.Args[i]
} }
line.Args = a; line.Args = a
} }
if len(t) > 1 { if len(t) > 1 {
// for some CTCP messages this could make more sense // for some CTCP messages this could make more sense
// in line.Args[], but meh. MEH, I say. // in line.Args[], but meh. MEH, I say.
line.Text = t[1]; line.Text = t[1]
} }
} }
if funcs, ok := conn.events[line.Cmd]; ok { if funcs, ok := conn.events[line.Cmd]; ok {
@ -75,7 +75,7 @@ func (conn *Conn) dispatchEvent(line *Line) {
// sets up the internal event handlers to do useful things with lines // sets up the internal event handlers to do useful things with lines
// XXX: is there a better way of doing this? // XXX: is there a better way of doing this?
// Turns out there may be but it's not actually implemented in the language yet // Turns out there may be but it's not actually implemented in the language yet
// according to the people on freenode/#go-nuts ... :-( // according to the people on freenode/#go-nuts ... :-(
// see: http://golang.org/doc/go_spec.html#Method_expressions for details // see: http://golang.org/doc/go_spec.html#Method_expressions for details
// I think this means we should be able to do something along the lines of: // I think this means we should be able to do something along the lines of:
// conn.AddHandler("event", (*Conn).h_handler); // conn.AddHandler("event", (*Conn).h_handler);
@ -83,144 +83,150 @@ func (conn *Conn) dispatchEvent(line *Line) {
// func (conn *Conn) h_handler(line *Line) {} // func (conn *Conn) h_handler(line *Line) {}
// in the future, but for now the compiler throws a hissy fit. // in the future, but for now the compiler throws a hissy fit.
func (conn *Conn) setupEvents() { func (conn *Conn) setupEvents() {
conn.events = make(map[string] []func(*Conn, *Line)); conn.events = make(map[string][]func(*Conn, *Line))
// Basic ping/pong handler // Basic ping/pong handler
conn.AddHandler("PING", func(conn *Conn, line *Line) { conn.AddHandler("PING", func(conn *Conn, line *Line) { conn.Raw("PONG :" + line.Text) })
conn.Raw("PONG :" + line.Text);
});
// Handler to trigger a "CONNECTED" event on receipt of numeric 001 // Handler to trigger a "CONNECTED" event on receipt of numeric 001
conn.AddHandler("001", func(conn *Conn, line *Line) { conn.AddHandler("001", func(conn *Conn, line *Line) {
// we're connected! // we're connected!
conn.connected = true; conn.connected = true
conn.dispatchEvent(&Line{Cmd: "CONNECTED"}); conn.dispatchEvent(&Line{Cmd: "CONNECTED"})
// and we're being given our hostname (from the server's perspective) // and we're being given our hostname (from the server's perspective)
if ridx := strings.LastIndex(line.Text, " "); ridx != -1 { if ridx := strings.LastIndex(line.Text, " "); ridx != -1 {
h := line.Text[ridx+1:len(line.Text)]; h := line.Text[ridx+1 : len(line.Text)]
if idx := strings.Index(h, "@"); idx != -1 { if idx := strings.Index(h, "@"); idx != -1 {
conn.Me.Host = h[idx+1:len(h)]; conn.Me.Host = h[idx+1 : len(h)]
} }
} }
}); })
// XXX: do we need 005 protocol support message parsing here? // XXX: do we need 005 protocol support message parsing here?
// probably in the future, but I can't quite be arsed yet. // probably in the future, but I can't quite be arsed yet.
/* /*
:irc.pl0rt.org 005 GoTest CMDS=KNOCK,MAP,DCCALLOW,USERIP UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=20 CHANLIMIT=#:20 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 :are supported by this server :irc.pl0rt.org 005 GoTest CMDS=KNOCK,MAP,DCCALLOW,USERIP UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=20 CHANLIMIT=#:20 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 :are supported by this server
:irc.pl0rt.org 005 GoTest MAXTARGETS=20 WALLCHOPS WATCH=128 WATCHOPTS=A SILENCE=15 MODES=12 CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beI,kfL,lj,psmntirRcOAQKVCuzNSMT NETWORK=bb101.net CASEMAPPING=ascii EXTBAN=~,cqnr ELIST=MNUCT :are supported by this server :irc.pl0rt.org 005 GoTest MAXTARGETS=20 WALLCHOPS WATCH=128 WATCHOPTS=A SILENCE=15 MODES=12 CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beI,kfL,lj,psmntirRcOAQKVCuzNSMT NETWORK=bb101.net CASEMAPPING=ascii EXTBAN=~,cqnr ELIST=MNUCT :are supported by this server
:irc.pl0rt.org 005 GoTest STATUSMSG=~&@%+ EXCEPTS INVEX :are supported by this server :irc.pl0rt.org 005 GoTest STATUSMSG=~&@%+ EXCEPTS INVEX :are supported by this server
*/ */
// Handler to deal with "433 :Nickname already in use" // Handler to deal with "433 :Nickname already in use"
conn.AddHandler("433", func(conn *Conn, line *Line) { conn.AddHandler("433", func(conn *Conn, line *Line) {
// Args[1] is the new nick we were attempting to acquire // Args[1] is the new nick we were attempting to acquire
conn.Nick(line.Args[1] + "_"); conn.Nick(line.Args[1] + "_")
}); })
// Handler NICK messages to inform us about nick changes // Handler NICK messages to inform us about nick changes
conn.AddHandler("NICK", func(conn *Conn, line *Line) { conn.AddHandler("NICK", func(conn *Conn, line *Line) {
// all nicks should be handled the same way, our own included // all nicks should be handled the same way, our own included
if n := conn.GetNick(line.Nick); n != nil { if n := conn.GetNick(line.Nick); n != nil {
n.ReNick(line.Text); n.ReNick(line.Text)
} else { } else {
conn.error("irc.NICK(): buh? unknown nick %s.", line.Nick); conn.error("irc.NICK(): buh? unknown nick %s.", line.Nick)
} }
}); })
// Handle VERSION requests and CTCP PING // Handle VERSION requests and CTCP PING
conn.AddHandler("CTCP", func(conn *Conn, line *Line) { conn.AddHandler("CTCP", func(conn *Conn, line *Line) {
if line.Args[0] == "VERSION" { if line.Args[0] == "VERSION" {
conn.CtcpReply(line.Nick, "VERSION", "powered by goirc..."); conn.CtcpReply(line.Nick, "VERSION", "powered by goirc...")
} else if line.Args[0] == "PING" { } else if line.Args[0] == "PING" {
conn.CtcpReply(line.Nick, "PING", line.Text); conn.CtcpReply(line.Nick, "PING", line.Text)
} }
}); })
// Handle JOINs to channels to maintain state // Handle JOINs to channels to maintain state
conn.AddHandler("JOIN", func(conn *Conn, line *Line) { conn.AddHandler("JOIN", func(conn *Conn, line *Line) {
ch := conn.GetChannel(line.Text); ch := conn.GetChannel(line.Text)
n := conn.GetNick(line.Nick); n := conn.GetNick(line.Nick)
if ch == nil { if ch == nil {
// first we've seen of this channel, so should be us joining it // first we've seen of this channel, so should be us joining it
// NOTE this will also take care of n == nil && ch == nil // NOTE this will also take care of n == nil && ch == nil
if n != conn.Me { if n != conn.Me {
conn.error("irc.JOIN(): buh? JOIN to unknown channel %s recieved from (non-me) nick %s", line.Text, line.Nick); conn.error("irc.JOIN(): buh? JOIN to unknown channel %s recieved from (non-me) nick %s", line.Text, line.Nick)
return; return
} }
ch = conn.NewChannel(line.Text); ch = conn.NewChannel(line.Text)
// since we don't know much about this channel, ask server for info // since we don't know much about this channel, ask server for info
// we get the channel users automatically in 353 and the channel // we get the channel users automatically in 353 and the channel
// topic in 332 on join, so we just need to get the modes // topic in 332 on join, so we just need to get the modes
conn.Mode(ch.Name); conn.Mode(ch.Name)
} }
if n == nil { if n == nil {
// this is the first we've seen of this nick // this is the first we've seen of this nick
n = conn.NewNick(line.Nick, line.Ident, "", line.Host); n = conn.NewNick(line.Nick, line.Ident, "", line.Host)
// since we don't know much about this nick, ask server for info // since we don't know much about this nick, ask server for info
conn.Whois(n.Nick); conn.Whois(n.Nick)
} }
// this takes care of both nick and channel linking \o/ // this takes care of both nick and channel linking \o/
ch.AddNick(n); ch.AddNick(n)
}); })
// Handle PARTs from channels to maintain state // Handle PARTs from channels to maintain state
conn.AddHandler("PART", func(conn *Conn, line *Line) { conn.AddHandler("PART", func(conn *Conn, line *Line) {
ch := conn.GetChannel(line.Args[0]); ch := conn.GetChannel(line.Args[0])
n := conn.GetNick(line.Nick); n := conn.GetNick(line.Nick)
if ch != nil && n != nil { if ch != nil && n != nil {
ch.DelNick(n); ch.DelNick(n)
} else { } else {
conn.error("irc.PART(): buh? PART of channel %s by nick %s", line.Args[0], line.Nick); conn.error("irc.PART(): buh? PART of channel %s by nick %s", line.Args[0], line.Nick)
} }
}); })
// Handle KICKs from channels to maintain state // Handle KICKs from channels to maintain state
conn.AddHandler("KICK", func(conn *Conn, line *Line) { conn.AddHandler("KICK", func(conn *Conn, line *Line) {
// XXX: this won't handle autorejoining channels on KICK // XXX: this won't handle autorejoining channels on KICK
// it's trivial to do this in a seperate handler... // it's trivial to do this in a seperate handler...
ch := conn.GetChannel(line.Args[0]); ch := conn.GetChannel(line.Args[0])
n := conn.GetNick(line.Args[1]); n := conn.GetNick(line.Args[1])
if ch != nil && n != nil { if ch != nil && n != nil {
ch.DelNick(n); ch.DelNick(n)
} else { } else {
conn.error("irc.KICK(): buh? KICK from channel %s of nick %s", line.Args[0], line.Args[1]); conn.error("irc.KICK(): buh? KICK from channel %s of nick %s", line.Args[0], line.Args[1])
} }
}); })
// Handle other people's QUITs // Handle other people's QUITs
conn.AddHandler("QUIT", func(conn *Conn, line *Line) { conn.AddHandler("QUIT", func(conn *Conn, line *Line) {
if n := conn.GetNick(line.Nick); n != nil { if n := conn.GetNick(line.Nick); n != nil {
n.Delete(); n.Delete()
} else { } else {
conn.error("irc.QUIT(): buh? QUIT from unknown nick %s", line.Nick); conn.error("irc.QUIT(): buh? QUIT from unknown nick %s", line.Nick)
} }
}); })
// Handle MODE changes for channels we know about (and our nick personally) // Handle MODE changes for channels we know about (and our nick personally)
// this is moderately ugly. suggestions for improvement welcome // this is moderately ugly. suggestions for improvement welcome
conn.AddHandler("MODE", func(conn *Conn, line *Line) { conn.AddHandler("MODE", func(conn *Conn, line *Line) {
// channel modes first // channel modes first
if ch := conn.GetChannel(line.Args[0]); ch != nil { if ch := conn.GetChannel(line.Args[0]); ch != nil {
modeargs := line.Args[2:len(line.Args)]; modeargs := line.Args[2:len(line.Args)]
var modeop bool; // true => add mode, false => remove mode var modeop bool // true => add mode, false => remove mode
var modestr string; var modestr string
for i := 0; i < len(line.Args[1]); i++ { for i := 0; i < len(line.Args[1]); i++ {
switch m := line.Args[1][i]; m { switch m := line.Args[1][i]; m {
case '+': case '+':
modeop = true; modeop = true
modestr = string(m); modestr = string(m)
case '-': case '-':
modeop = false; modeop = false
modestr = string(m); modestr = string(m)
case 'i': ch.Modes.InviteOnly = modeop; case 'i':
case 'm': ch.Modes.Moderated = modeop; ch.Modes.InviteOnly = modeop
case 'n': ch.Modes.NoExternalMsg = modeop; case 'm':
case 'p': ch.Modes.Private = modeop; ch.Modes.Moderated = modeop
case 's': ch.Modes.Secret = modeop; case 'n':
case 't': ch.Modes.ProtectedTopic = modeop; ch.Modes.NoExternalMsg = modeop
case 'z': ch.Modes.SSLOnly = modeop; case 'p':
case 'O': ch.Modes.OperOnly = modeop; ch.Modes.Private = modeop
case 's':
ch.Modes.Secret = modeop
case 't':
ch.Modes.ProtectedTopic = modeop
case 'z':
ch.Modes.SSLOnly = modeop
case 'O':
ch.Modes.OperOnly = modeop
case 'k': case 'k':
if len(modeargs) != 0 { if len(modeargs) != 0 {
ch.Modes.Key, modeargs = modeargs[0], modeargs[1:len(modeargs)] ch.Modes.Key, modeargs = modeargs[0], modeargs[1:len(modeargs)]
@ -229,99 +235,117 @@ func (conn *Conn) setupEvents() {
} }
case 'l': case 'l':
if len(modeargs) != 0 { if len(modeargs) != 0 {
ch.Modes.Limit, _ = strconv.Atoi(modeargs[0]); ch.Modes.Limit, _ = strconv.Atoi(modeargs[0])
modeargs = modeargs[1:len(modeargs)]; modeargs = modeargs[1:len(modeargs)]
} else { } else {
conn.error("irc.MODE(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m) conn.error("irc.MODE(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m)
} }
case 'q', 'a', 'o', 'h', 'v': case 'q', 'a', 'o', 'h', 'v':
if len(modeargs) != 0 { if len(modeargs) != 0 {
n := conn.GetNick(modeargs[0]); n := conn.GetNick(modeargs[0])
if p, ok := ch.Nicks[n]; ok && n != nil { if p, ok := ch.Nicks[n]; ok && n != nil {
switch m { switch m {
case 'q': p.Owner = modeop; case 'q':
case 'a': p.Admin = modeop; p.Owner = modeop
case 'o': p.Op = modeop; case 'a':
case 'h': p.HalfOp = modeop; p.Admin = modeop
case 'v': p.Voice = modeop; case 'o':
p.Op = modeop
case 'h':
p.HalfOp = modeop
case 'v':
p.Voice = modeop
} }
modeargs = modeargs[1:len(modeargs)]; modeargs = modeargs[1:len(modeargs)]
} else { } else {
conn.error("irc.MODE(): MODE %s %s%s %s: buh? state tracking failure.", ch.Name, modestr, m, modeargs[0]); conn.error("irc.MODE(): MODE %s %s%s %s: buh? state tracking failure.", ch.Name, modestr, m, modeargs[0])
} }
} else { } else {
conn.error("irc.MODE(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m); conn.error("irc.MODE(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m)
} }
} }
} }
} else if n := conn.GetNick(line.Args[0]); n != nil { } else if n := conn.GetNick(line.Args[0]); n != nil {
// nick mode change, should be us // nick mode change, should be us
if n != conn.Me { if n != conn.Me {
conn.error("irc.MODE(): buh? recieved MODE %s for (non-me) nick %s", line.Args[1], n.Nick); conn.error("irc.MODE(): buh? recieved MODE %s for (non-me) nick %s", line.Args[1], n.Nick)
return; return
} }
var modeop bool; // true => add mode, false => remove mode var modeop bool // true => add mode, false => remove mode
for i := 0; i < len(line.Text); i++ { for i := 0; i < len(line.Text); i++ {
switch m := line.Text[i]; m { switch m := line.Text[i]; m {
case '+': case '+':
modeop = true; modeop = true
case '-': case '-':
modeop = false; modeop = false
case 'i': n.Modes.Invisible = modeop; case 'i':
case 'o': n.Modes.Oper = modeop; n.Modes.Invisible = modeop
case 'w': n.Modes.WallOps = modeop; case 'o':
case 'x': n.Modes.HiddenHost = modeop; n.Modes.Oper = modeop
case 'z': n.Modes.SSL = modeop; case 'w':
n.Modes.WallOps = modeop
case 'x':
n.Modes.HiddenHost = modeop
case 'z':
n.Modes.SSL = modeop
} }
} }
} else { } else {
conn.error("irc.MODE(): buh? not sure what to do with MODE %s %s", line.Args[0], line.Args[1]); conn.error("irc.MODE(): buh? not sure what to do with MODE %s %s", line.Args[0], line.Args[1])
} }
}); })
// Handle TOPIC changes for channels // Handle TOPIC changes for channels
conn.AddHandler("TOPIC", func(conn *Conn, line *Line) { conn.AddHandler("TOPIC", func(conn *Conn, line *Line) {
if ch := conn.GetChannel(line.Args[0]); ch != nil { if ch := conn.GetChannel(line.Args[0]); ch != nil {
ch.Topic = line.Text; ch.Topic = line.Text
} else { } else {
conn.error("irc.TOPIC(): buh? topic change on unknown channel %s", line.Args[0]); conn.error("irc.TOPIC(): buh? topic change on unknown channel %s", line.Args[0])
} }
}); })
// Handle 311 whois reply // Handle 311 whois reply
conn.AddHandler("311", func(conn *Conn, line *Line) { conn.AddHandler("311", func(conn *Conn, line *Line) {
if n := conn.GetNick(line.Args[1]); n != nil { if n := conn.GetNick(line.Args[1]); n != nil {
n.Ident = line.Args[2]; n.Ident = line.Args[2]
n.Host = line.Args[3]; n.Host = line.Args[3]
n.Name = line.Text; n.Name = line.Text
} else { } else {
conn.error("irc.311(): buh? received WHOIS info for unknown nick %s", line.Args[1]); conn.error("irc.311(): buh? received WHOIS info for unknown nick %s", line.Args[1])
} }
}); })
// Handle 324 mode reply // Handle 324 mode reply
conn.AddHandler("324", func(conn *Conn, line *Line) { conn.AddHandler("324", func(conn *Conn, line *Line) {
// XXX: copypasta from MODE, needs tidying. // XXX: copypasta from MODE, needs tidying.
if ch := conn.GetChannel(line.Args[1]); ch != nil { if ch := conn.GetChannel(line.Args[1]); ch != nil {
modeargs := line.Args[3:len(line.Args)]; modeargs := line.Args[3:len(line.Args)]
var modeop bool; // true => add mode, false => remove mode var modeop bool // true => add mode, false => remove mode
var modestr string; var modestr string
for i := 0; i < len(line.Args[2]); i++ { for i := 0; i < len(line.Args[2]); i++ {
switch m := line.Args[2][i]; m { switch m := line.Args[2][i]; m {
case '+': case '+':
modeop = true; modeop = true
modestr = string(m); modestr = string(m)
case '-': case '-':
modeop = false; modeop = false
modestr = string(m); modestr = string(m)
case 'i': ch.Modes.InviteOnly = modeop; case 'i':
case 'm': ch.Modes.Moderated = modeop; ch.Modes.InviteOnly = modeop
case 'n': ch.Modes.NoExternalMsg = modeop; case 'm':
case 'p': ch.Modes.Private = modeop; ch.Modes.Moderated = modeop
case 's': ch.Modes.Secret = modeop; case 'n':
case 't': ch.Modes.ProtectedTopic = modeop; ch.Modes.NoExternalMsg = modeop
case 'z': ch.Modes.SSLOnly = modeop; case 'p':
case 'O': ch.Modes.OperOnly = modeop; ch.Modes.Private = modeop
case 's':
ch.Modes.Secret = modeop
case 't':
ch.Modes.ProtectedTopic = modeop
case 'z':
ch.Modes.SSLOnly = modeop
case 'O':
ch.Modes.OperOnly = modeop
case 'k': case 'k':
if len(modeargs) != 0 { if len(modeargs) != 0 {
ch.Modes.Key, modeargs = modeargs[0], modeargs[1:len(modeargs)] ch.Modes.Key, modeargs = modeargs[0], modeargs[1:len(modeargs)]
@ -330,31 +354,31 @@ func (conn *Conn) setupEvents() {
} }
case 'l': case 'l':
if len(modeargs) != 0 { if len(modeargs) != 0 {
ch.Modes.Limit, _ = strconv.Atoi(modeargs[0]); ch.Modes.Limit, _ = strconv.Atoi(modeargs[0])
modeargs = modeargs[1:len(modeargs)]; modeargs = modeargs[1:len(modeargs)]
} else { } else {
conn.error("irc.324(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m) conn.error("irc.324(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m)
} }
} }
} }
} else { } else {
conn.error("irc.324(): buh? received MODE settings for unknown channel %s", line.Args[1]); conn.error("irc.324(): buh? received MODE settings for unknown channel %s", line.Args[1])
} }
}); })
// Handle 332 topic reply on join to channel // Handle 332 topic reply on join to channel
conn.AddHandler("332", func(conn *Conn, line *Line) { conn.AddHandler("332", func(conn *Conn, line *Line) {
if ch := conn.GetChannel(line.Args[1]); ch != nil { if ch := conn.GetChannel(line.Args[1]); ch != nil {
ch.Topic = line.Text; ch.Topic = line.Text
} else { } else {
conn.error("irc.332(): buh? received TOPIC value for unknown channel %s", line.Args[1]); conn.error("irc.332(): buh? received TOPIC value for unknown channel %s", line.Args[1])
} }
}); })
// Handle 353 names reply // Handle 353 names reply
conn.AddHandler("353", func(conn *Conn, line *Line) { conn.AddHandler("353", func(conn *Conn, line *Line) {
if ch := conn.GetChannel(line.Args[2]); ch != nil { if ch := conn.GetChannel(line.Args[2]); ch != nil {
nicks := strings.Split(line.Text, " ", 0); nicks := strings.Split(line.Text, " ", 0)
for _, nick := range nicks { for _, nick := range nicks {
// UnrealIRCd's coders are lazy and leave a trailing space // UnrealIRCd's coders are lazy and leave a trailing space
if nick == "" { if nick == "" {
@ -362,41 +386,46 @@ func (conn *Conn) setupEvents() {
} }
switch c := nick[0]; c { switch c := nick[0]; c {
case '~', '&', '@', '%', '+': case '~', '&', '@', '%', '+':
nick = nick[1:len(nick)]; nick = nick[1:len(nick)]
fallthrough; fallthrough
default: default:
n := conn.GetNick(nick); n := conn.GetNick(nick)
if n == nil { if n == nil {
// we don't know this nick yet! // we don't know this nick yet!
n = conn.NewNick(nick, "", "", ""); n = conn.NewNick(nick, "", "", "")
conn.Whois(nick); conn.Whois(nick)
} }
if n != conn.Me { if n != conn.Me {
// we will be in the names list, but should also be in // we will be in the names list, but should also be in
// the channel's nick list from the JOIN handler above // the channel's nick list from the JOIN handler above
ch.AddNick(n); ch.AddNick(n)
} }
p := ch.Nicks[n]; p := ch.Nicks[n]
switch c { switch c {
case '~': p.Owner = true; case '~':
case '&': p.Admin = true; p.Owner = true
case '@': p.Op = true; case '&':
case '%': p.HalfOp = true; p.Admin = true
case '+': p.Voice = true; case '@':
p.Op = true
case '%':
p.HalfOp = true
case '+':
p.Voice = true
} }
} }
} }
} else { } else {
conn.error("irc.353(): buh? received NAMES list for unknown channel %s", line.Args[2]); conn.error("irc.353(): buh? received NAMES list for unknown channel %s", line.Args[2])
} }
}); })
// Handle 671 whois reply (nick connected via SSL) // Handle 671 whois reply (nick connected via SSL)
conn.AddHandler("671", func(conn *Conn, line *Line) { conn.AddHandler("671", func(conn *Conn, line *Line) {
if n := conn.GetNick(line.Args[1]); n != nil { if n := conn.GetNick(line.Args[1]); n != nil {
n.Modes.SSL = true; n.Modes.SSL = true
} else { } else {
conn.error("irc.671(): buh? received WHOIS SSL info for unknown nick %s", line.Args[1]); conn.error("irc.671(): buh? received WHOIS SSL info for unknown nick %s", line.Args[1])
} }
}); })
} }

View File

@ -4,25 +4,24 @@ package irc
// as well as the internal state maintenance code for the handlers // as well as the internal state maintenance code for the handlers
import ( import (
"fmt"; "fmt"
"reflect"; "reflect"
) )
// A struct representing an IRC channel // A struct representing an IRC channel
type Channel struct { type Channel struct {
Name, Topic string; Name, Topic string
Modes *ChanMode; Modes *ChanMode
// MODE +q, +a, +o, +h, +v Nicks map[*Nick]*ChanPrivs
Nicks map[*Nick] *ChanPrivs; conn *Conn
conn *Conn;
} }
// A struct representing an IRC nick // A struct representing an IRC nick
type Nick struct { type Nick struct {
Nick, Ident, Host, Name string; Nick, Ident, Host, Name string
Modes *NickMode; Modes *NickMode
Channels map[*Channel] *ChanPrivs; Channels map[*Channel]*ChanPrivs
conn *Conn; conn *Conn
} }
// A struct representing the modes of an IRC Channel // A struct representing the modes of an IRC Channel
@ -30,13 +29,13 @@ type Nick struct {
// see the MODE handler in setupEvents() for details // see the MODE handler in setupEvents() for details
type ChanMode struct { type ChanMode struct {
// MODE +p, +s, +t, +n, +m // MODE +p, +s, +t, +n, +m
Private, Secret, ProtectedTopic, NoExternalMsg, Moderated bool; Private, Secret, ProtectedTopic, NoExternalMsg, Moderated bool
// MODE +i, +O, +z // MODE +i, +O, +z
InviteOnly, OperOnly, SSLOnly bool; InviteOnly, OperOnly, SSLOnly bool
// MODE +k // MODE +k
Key string; Key string
// MODE +l // MODE +l
Limit int; Limit int
} }
// A struct representing the modes of an IRC Nick (User Modes) // A struct representing the modes of an IRC Nick (User Modes)
@ -45,23 +44,23 @@ type ChanMode struct {
// without IRC operator privileges (and even then only on some IRCd's). // without IRC operator privileges (and even then only on some IRCd's).
type NickMode struct { type NickMode struct {
// MODE +i, +o, +w, +x, +z // MODE +i, +o, +w, +x, +z
Invisible, Oper, WallOps, HiddenHost, SSL bool; Invisible, Oper, WallOps, HiddenHost, SSL bool
} }
// A struct representing the modes a Nick can have on a Channel // A struct representing the modes a Nick can have on a Channel
type ChanPrivs struct { type ChanPrivs struct {
// MODE +q, +a, +o, +h, +v // MODE +q, +a, +o, +h, +v
Owner, Admin, Op, HalfOp, Voice bool; Owner, Admin, Op, HalfOp, Voice bool
} }
/******************************************************************************\ /******************************************************************************\
* Conn methods to create/look up nicks/channels * Conn methods to create/look up nicks/channels
\******************************************************************************/ \******************************************************************************/
func (conn *Conn) NewNick(nick, ident, name, host string) *Nick { func (conn *Conn) NewNick(nick, ident, name, host string) *Nick {
n := &Nick{Nick: nick, Ident: ident, Name: name, Host: host, conn: conn}; n := &Nick{Nick: nick, Ident: ident, Name: name, Host: host, conn: conn}
n.initialise(); n.initialise()
conn.nicks[n.Nick] = n; conn.nicks[n.Nick] = n
return n; return n
} }
func (conn *Conn) GetNick(n string) *Nick { func (conn *Conn) GetNick(n string) *Nick {
@ -72,9 +71,9 @@ func (conn *Conn) GetNick(n string) *Nick {
} }
func (conn *Conn) NewChannel(c string) *Channel { func (conn *Conn) NewChannel(c string) *Channel {
ch := &Channel{Name: c, conn: conn}; ch := &Channel{Name: c, conn: conn}
ch.initialise(); ch.initialise()
conn.chans[ch.Name] = ch; conn.chans[ch.Name] = ch
return ch return ch
} }
@ -89,28 +88,28 @@ func (conn *Conn) GetChannel(c string) *Channel {
* Channel methods for state management * Channel methods for state management
\******************************************************************************/ \******************************************************************************/
func (ch *Channel) initialise() { func (ch *Channel) initialise() {
ch.Modes = new(ChanMode); ch.Modes = new(ChanMode)
ch.Nicks = make(map[*Nick] *ChanPrivs); ch.Nicks = make(map[*Nick]*ChanPrivs)
} }
func (ch *Channel) AddNick(n *Nick) { func (ch *Channel) AddNick(n *Nick) {
if _, ok := ch.Nicks[n]; !ok { if _, ok := ch.Nicks[n]; !ok {
ch.Nicks[n] = new(ChanPrivs); ch.Nicks[n] = new(ChanPrivs)
n.Channels[ch] = ch.Nicks[n]; n.Channels[ch] = ch.Nicks[n]
} else { } else {
ch.conn.error("irc.Channel.AddNick() warning: trying to add already-present nick %s to channel %s", n.Nick, ch.Name); ch.conn.error("irc.Channel.AddNick() warning: trying to add already-present nick %s to channel %s", n.Nick, ch.Name)
} }
} }
func (ch *Channel) DelNick(n *Nick) { func (ch *Channel) DelNick(n *Nick) {
if _, ok := ch.Nicks[n]; ok { if _, ok := ch.Nicks[n]; ok {
fmt.Printf("irc.Channel.DelNick(): deleting %s from %s\n", n.Nick, ch.Name); fmt.Printf("irc.Channel.DelNick(): deleting %s from %s\n", n.Nick, ch.Name)
if n == n.conn.Me { if n == n.conn.Me {
// we're leaving the channel, so remove all state we have about it // we're leaving the channel, so remove all state we have about it
ch.Delete(); ch.Delete()
} else { } else {
ch.Nicks[n] = nil, false; ch.Nicks[n] = nil, false
n.DelChannel(ch); n.DelChannel(ch)
} }
} // no else here ... } // no else here ...
// we call Channel.DelNick() and Nick.DelChan() from each other to ensure // we call Channel.DelNick() and Nick.DelChan() from each other to ensure
@ -118,57 +117,57 @@ func (ch *Channel) DelNick(n *Nick) {
} }
func (ch *Channel) Delete() { func (ch *Channel) Delete() {
fmt.Printf("irc.Channel.Delete(): deleting %s\n", ch.Name); fmt.Printf("irc.Channel.Delete(): deleting %s\n", ch.Name)
for n, _ := range ch.Nicks { for n, _ := range ch.Nicks {
n.DelChannel(ch); n.DelChannel(ch)
} }
ch.conn.chans[ch.Name] = nil, false; ch.conn.chans[ch.Name] = nil, false
} }
/******************************************************************************\ /******************************************************************************\
* Nick methods for state management * Nick methods for state management
\******************************************************************************/ \******************************************************************************/
func (n *Nick) initialise() { func (n *Nick) initialise() {
n.Modes = new(NickMode); n.Modes = new(NickMode)
n.Channels = make(map[*Channel] *ChanPrivs); n.Channels = make(map[*Channel]*ChanPrivs)
} }
// very slightly different to Channel.AddNick() ... // very slightly different to Channel.AddNick() ...
func (n *Nick) AddChannel(ch *Channel) { func (n *Nick) AddChannel(ch *Channel) {
if _, ok := n.Channels[ch]; !ok { if _, ok := n.Channels[ch]; !ok {
ch.Nicks[n] = new(ChanPrivs); ch.Nicks[n] = new(ChanPrivs)
n.Channels[ch] = ch.Nicks[n]; n.Channels[ch] = ch.Nicks[n]
} else { } else {
n.conn.error("irc.Nick.AddChannel() warning: trying to add already-present channel %s to nick %s", ch.Name, n.Nick); n.conn.error("irc.Nick.AddChannel() warning: trying to add already-present channel %s to nick %s", ch.Name, n.Nick)
} }
} }
func (n *Nick) DelChannel(ch *Channel) { func (n *Nick) DelChannel(ch *Channel) {
if _, ok := n.Channels[ch]; ok { if _, ok := n.Channels[ch]; ok {
fmt.Printf("irc.Nick.DelChannel(): deleting %s from %s\n", n.Nick, ch.Name); fmt.Printf("irc.Nick.DelChannel(): deleting %s from %s\n", n.Nick, ch.Name)
n.Channels[ch] = nil, false; n.Channels[ch] = nil, false
ch.DelNick(n); ch.DelNick(n)
if len(n.Channels) == 0 { if len(n.Channels) == 0 {
// nick is no longer in any channels we inhabit, stop tracking it // nick is no longer in any channels we inhabit, stop tracking it
n.Delete(); n.Delete()
} }
} }
} }
func (n *Nick) ReNick(neu string) { func (n *Nick) ReNick(neu string) {
n.conn.nicks[n.Nick] = nil, false; n.conn.nicks[n.Nick] = nil, false
n.Nick = neu; n.Nick = neu
n.conn.nicks[n.Nick] = n; n.conn.nicks[n.Nick] = n
} }
func (n *Nick) Delete() { func (n *Nick) Delete() {
// we don't ever want to remove *our* nick from conn.nicks... // we don't ever want to remove *our* nick from conn.nicks...
if n != n.conn.Me { if n != n.conn.Me {
fmt.Printf("irc.Nick.Delete(): deleting %s\n", n.Nick); fmt.Printf("irc.Nick.Delete(): deleting %s\n", n.Nick)
for ch, _ := range n.Channels { for ch, _ := range n.Channels {
ch.DelNick(n); ch.DelNick(n)
} }
n.conn.nicks[n.Nick] = nil, false; n.conn.nicks[n.Nick] = nil, false
} }
} }
@ -176,152 +175,152 @@ func (n *Nick) Delete() {
* String() methods for all structs in this file for ease of debugging. * String() methods for all structs in this file for ease of debugging.
\******************************************************************************/ \******************************************************************************/
var ChanModeToString = map[string]string{ var ChanModeToString = map[string]string{
"Private":"p", "Private": "p",
"Secret":"s", "Secret": "s",
"ProtectedTopic":"t", "ProtectedTopic": "t",
"NoExternalMsg":"n", "NoExternalMsg": "n",
"Moderated":"m", "Moderated": "m",
"InviteOnly":"i", "InviteOnly": "i",
"OperOnly":"O", "OperOnly": "O",
"SSLOnly":"z", "SSLOnly": "z",
"Key":"k", "Key": "k",
"Limit":"l", "Limit": "l",
}; }
var NickModeToString = map[string]string{ var NickModeToString = map[string]string{
"Invisible":"i", "Invisible": "i",
"Oper":"o", "Oper": "o",
"WallOps":"w", "WallOps": "w",
"HiddenHost":"x", "HiddenHost": "x",
"SSL":"z", "SSL": "z",
}; }
var ChanPrivToString = map[string]string{ var ChanPrivToString = map[string]string{
"Owner":"q", "Owner": "q",
"Admin":"a", "Admin": "a",
"Op":"o", "Op": "o",
"HalfOp":"h", "HalfOp": "h",
"Voice":"v", "Voice": "v",
}; }
var ChanPrivToModeChar = map[string]byte{ var ChanPrivToModeChar = map[string]byte{
"Owner":'~', "Owner": '~',
"Admin":'&', "Admin": '&',
"Op":'@', "Op": '@',
"HalfOp":'%', "HalfOp": '%',
"Voice":'+', "Voice": '+',
}; }
var StringToChanMode, StringToNickMode, StringToChanPriv map[string]string; var StringToChanMode, StringToNickMode, StringToChanPriv map[string]string
var ModeCharToChanPriv map[byte]string; var ModeCharToChanPriv map[byte]string
// Init function to fill in reverse mappings for *toString constants etc. // Init function to fill in reverse mappings for *toString constants etc.
func init() { func init() {
StringToChanMode = make(map[string]string); StringToChanMode = make(map[string]string)
for k,v := range ChanModeToString { for k, v := range ChanModeToString {
StringToChanMode[v] = k; StringToChanMode[v] = k
} }
StringToNickMode = make(map[string]string); StringToNickMode = make(map[string]string)
for k,v := range NickModeToString { for k, v := range NickModeToString {
StringToNickMode[v] = k; StringToNickMode[v] = k
} }
StringToChanPriv = make(map[string]string); StringToChanPriv = make(map[string]string)
for k,v := range ChanPrivToString { for k, v := range ChanPrivToString {
StringToChanPriv[v] = k; StringToChanPriv[v] = k
} }
ModeCharToChanPriv = make(map[byte]string); ModeCharToChanPriv = make(map[byte]string)
for k,v := range ChanPrivToModeChar { for k, v := range ChanPrivToModeChar {
ModeCharToChanPriv[v] = k; ModeCharToChanPriv[v] = k
} }
} }
func (ch *Channel) String() string { func (ch *Channel) String() string {
str := "Channel: " + ch.Name + "\n\t"; str := "Channel: " + ch.Name + "\n\t"
str += "Topic: " + ch.Topic + "\n\t"; str += "Topic: " + ch.Topic + "\n\t"
str += "Modes: " + ch.Modes.String() + "\n\t"; str += "Modes: " + ch.Modes.String() + "\n\t"
str += "Nicks: \n"; str += "Nicks: \n"
for n, p := range ch.Nicks { for n, p := range ch.Nicks {
str += "\t\t" + n.Nick + ": " + p.String() + "\n"; str += "\t\t" + n.Nick + ": " + p.String() + "\n"
} }
return str; return str
} }
func (n *Nick) String() string { func (n *Nick) String() string {
str := "Nick: "+ n.Nick + "\n\t"; str := "Nick: " + n.Nick + "\n\t"
str += "Hostmask: " + n.Ident + "@" + n.Host + "\n\t"; str += "Hostmask: " + n.Ident + "@" + n.Host + "\n\t"
str += "Real Name: " + n.Name + "\n\t"; str += "Real Name: " + n.Name + "\n\t"
str += "Modes: " + n.Modes.String() + "\n\t"; str += "Modes: " + n.Modes.String() + "\n\t"
str += "Channels: \n"; str += "Channels: \n"
for ch, p := range n.Channels { for ch, p := range n.Channels {
str += "\t\t" + ch.Name + ": " + p.String() + "\n"; str += "\t\t" + ch.Name + ": " + p.String() + "\n"
} }
return str; return str
} }
func (cm *ChanMode) String() string { func (cm *ChanMode) String() string {
str := "+"; str := "+"
a := make([]string, 2); a := make([]string, 2)
v := reflect.Indirect(reflect.NewValue(cm)).(*reflect.StructValue); v := reflect.Indirect(reflect.NewValue(cm)).(*reflect.StructValue)
t := v.Type().(*reflect.StructType); t := v.Type().(*reflect.StructType)
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
switch f := v.Field(i).(type) { switch f := v.Field(i).(type) {
case *reflect.BoolValue: case *reflect.BoolValue:
if f.Get() { if f.Get() {
str += ChanModeToString[t.Field(i).Name]; str += ChanModeToString[t.Field(i).Name]
} }
case *reflect.StringValue: case *reflect.StringValue:
if f.Get() != "" { if f.Get() != "" {
str += ChanModeToString[t.Field(i).Name]; str += ChanModeToString[t.Field(i).Name]
a[0] = f.Get(); a[0] = f.Get()
} }
case *reflect.IntValue: case *reflect.IntValue:
if f.Get() != 0 { if f.Get() != 0 {
str += ChanModeToString[t.Field(i).Name]; str += ChanModeToString[t.Field(i).Name]
a[1] = fmt.Sprintf("%d", cm.Limit); a[1] = fmt.Sprintf("%d", cm.Limit)
} }
} }
} }
for _, s := range a { for _, s := range a {
if s != "" { if s != "" {
str += " " + s; str += " " + s
} }
} }
if str == "+" { if str == "+" {
str = "No modes set"; str = "No modes set"
} }
return str; return str
} }
func (nm *NickMode) String() string { func (nm *NickMode) String() string {
str := "+"; str := "+"
v := reflect.Indirect(reflect.NewValue(nm)).(*reflect.StructValue); v := reflect.Indirect(reflect.NewValue(nm)).(*reflect.StructValue)
t := v.Type().(*reflect.StructType); t := v.Type().(*reflect.StructType)
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
switch f := v.Field(i).(type) { switch f := v.Field(i).(type) {
// only bools here at the mo! // only bools here at the mo!
case *reflect.BoolValue: case *reflect.BoolValue:
if f.Get() { if f.Get() {
str += NickModeToString[t.Field(i).Name]; str += NickModeToString[t.Field(i).Name]
} }
} }
} }
if str == "+" { if str == "+" {
str = "No modes set"; str = "No modes set"
} }
return str; return str
} }
func (p *ChanPrivs) String() string { func (p *ChanPrivs) String() string {
str := "+"; str := "+"
v := reflect.Indirect(reflect.NewValue(p)).(*reflect.StructValue); v := reflect.Indirect(reflect.NewValue(p)).(*reflect.StructValue)
t := v.Type().(*reflect.StructType); t := v.Type().(*reflect.StructType)
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
switch f := v.Field(i).(type) { switch f := v.Field(i).(type) {
// only bools here at the mo too! // only bools here at the mo too!
case *reflect.BoolValue: case *reflect.BoolValue:
if f.Get() { if f.Get() {
str += ChanPrivToString[t.Field(i).Name]; str += ChanPrivToString[t.Field(i).Name]
} }
} }
} }
if str == "+" { if str == "+" {
str = "No modes set"; str = "No modes set"
} }
return str; return str
} }