mirror of
				https://github.com/fluffle/goirc
				synced 2025-10-31 10:18:04 +00:00 
			
		
		
		
	vastly updated bot framework, now with state tracking etc.
This commit is contained in:
		
							parent
							
								
									4d610d1718
								
							
						
					
					
						commit
						79822340b5
					
				
					 8 changed files with 977 additions and 175 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -3,3 +3,4 @@ | ||||||
| _obj/ | _obj/ | ||||||
| *.swp | *.swp | ||||||
| *~ | *~ | ||||||
|  | *.out | ||||||
|  |  | ||||||
							
								
								
									
										87
									
								
								client.go
									
										
									
									
									
								
							
							
						
						
									
										87
									
								
								client.go
									
										
									
									
									
								
							|  | @ -4,32 +4,89 @@ import ( | ||||||
| 	"./irc/_obj/irc"; | 	"./irc/_obj/irc"; | ||||||
| 	"fmt"; | 	"fmt"; | ||||||
| 	"os"; | 	"os"; | ||||||
|  | 	"bufio"; | ||||||
|  | 	"strings"; | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
|  | 	// 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.IRCConn, line *irc.IRCLine) { | 		func(conn *irc.Conn, line *irc.Line) { | ||||||
| 			conn.Join("#"); | 			conn.Join("#"); | ||||||
| 		} | 		} | ||||||
| 	); | 	); | ||||||
| 	c.AddHandler("join", | 
 | ||||||
| 		func(conn *irc.IRCConn, line *irc.IRCLine) { | 	// connect to server | ||||||
| 			if line.Nick == conn.Me { |  | ||||||
| 				conn.Privmsg(line.Text, "I LIVE, BITCHES"); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	); |  | ||||||
| 	if err := c.Connect("irc.pl0rt.org", ""); err != nil { | 	if err := c.Connect("irc.pl0rt.org", ""); err != nil { | ||||||
| 		fmt.Printf("Connection error: %v\n", err); | 		fmt.Printf("Connection error: %s\n", err); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// if we get here, we're successfully connected and should have just | 	// set up a goroutine to read commands from stdin | ||||||
| 	// dispatched the "CONNECTED" event to it's handlers \o/ | 	in := make(chan string, 4); | ||||||
| 	control := make(chan os.Error, 1); | 	reallyquit := false; | ||||||
| 	go c.RunLoop(control); | 	go func() { | ||||||
| 	if err := <-control; err != nil { | 		con := bufio.NewReader(os.Stdin); | ||||||
| 		fmt.Printf("IRCConn.RunLoop terminated: %v\n", err); | 		for { | ||||||
|  | 			s, err := con.ReadString('\n'); | ||||||
|  | 			if err != nil { | ||||||
|  | 				// wha?, maybe ctrl-D... | ||||||
|  | 				close(in); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			// no point in sending empty lines down the channel | ||||||
|  | 			if len(s) > 2 { | ||||||
|  | 				in <- s[0:len(s)-1] | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}(); | ||||||
|  | 
 | ||||||
|  | 	// set up a goroutine to do parsey things with the stuff from stdin | ||||||
|  | 	go func() { | ||||||
|  | 		for { | ||||||
|  | 			if closed(in) { | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			cmd := <-in; | ||||||
|  | 			if cmd[0] == ':' { | ||||||
|  | 				switch idx := strings.Index(cmd, " "); { | ||||||
|  | 					case idx == -1: | ||||||
|  | 						continue; | ||||||
|  | 					case cmd[1] == 'q': | ||||||
|  | 						reallyquit = true; | ||||||
|  | 						c.Quit(cmd[idx+1:len(cmd)]); | ||||||
|  | 					case cmd[1] == 'j': | ||||||
|  | 						c.Join(cmd[idx+1:len(cmd)]); | ||||||
|  | 					case cmd[1] == 'p': | ||||||
|  | 						c.Part(cmd[idx+1:len(cmd)]); | ||||||
|  | 					case cmd[1] == 'd': | ||||||
|  | 						fmt.Printf(c.String()); | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				c.Raw(cmd) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}(); | ||||||
|  | 
 | ||||||
|  | 	// stall here waiting for asplode on error channel | ||||||
|  | 	for { | ||||||
|  | 		if closed(c.Err) { | ||||||
|  | 			// c.Err being closed indicates we've been disconnected from the | ||||||
|  | 			// server for some reason (e.g. quit, kill or ping timeout) | ||||||
|  | 			// if we don't really want to quit, reconnect! | ||||||
|  | 			if !reallyquit { | ||||||
|  | 				fmt.Println("Reconnecting..."); | ||||||
|  | 				if err := c.Connect("irc.pl0rt.org", ""); err != nil { | ||||||
|  | 					fmt.Printf("Connection error: %s\n", err); | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 		if err := <-c.Err; err != nil { | ||||||
|  | 			fmt.Printf("goirc error: %s\n", err); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,5 +9,6 @@ GOFILES=\ | ||||||
| 	connection.go\
 | 	connection.go\
 | ||||||
|     commands.go\
 |     commands.go\
 | ||||||
|     handlers.go\
 |     handlers.go\
 | ||||||
|  |     nickchan.go | ||||||
| 
 | 
 | ||||||
| include $(GOROOT)/src/Make.pkg | include $(GOROOT)/src/Make.pkg | ||||||
|  |  | ||||||
							
								
								
									
										104
									
								
								irc/commands.go
									
										
									
									
									
								
							
							
						
						
									
										104
									
								
								irc/commands.go
									
										
									
									
									
								
							|  | @ -1,10 +1,10 @@ | ||||||
| package irc | package irc | ||||||
| 
 | 
 | ||||||
| // this file contains the various commands you can | // this file contains the various commands you can | ||||||
| // send to the server using an IRCConn connection | // send to the server using an Conn connection | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt"; | //	"fmt"; | ||||||
| 	"reflect"; | 	"reflect"; | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -12,91 +12,127 @@ import ( | ||||||
| // 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 | ||||||
|  | func (conn *Conn) Raw(s string) { | ||||||
|  | 	conn.out <- s | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // send a PASS command to the server | // send a PASS command to the server | ||||||
| func (conn *IRCConn) Pass(p string) { | func (conn *Conn) Pass(p string) { | ||||||
| 	conn.send(fmt.Sprintf("PASS %s", p)); | 	conn.out <- "PASS " + p | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // send a NICK command to the server | // send a NICK command to the server | ||||||
| func (conn *IRCConn) Nick(n string) { | func (conn *Conn) Nick(n string) { | ||||||
| 	conn.send(fmt.Sprintf("NICK %s", n)); | 	conn.out <- "NICK " + n | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // send a USER command to the server | // send a USER command to the server | ||||||
| func (conn *IRCConn) User(u, n string) { | func (conn *Conn) User(u, n string) { | ||||||
| 	conn.send(fmt.Sprintf("USER %s 12 * :%s", u, n)); | 	conn.out <- "USER " + u + " 12 * :" + n | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // send a JOIN command to the server | // send a JOIN command to the server | ||||||
| func (conn *IRCConn) Join(c string) { | func (conn *Conn) Join(c string) { | ||||||
| 	conn.send(fmt.Sprintf("JOIN %s", c)); | 	conn.out <- "JOIN " + c | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // send a PART command to the server | // send a PART command to the server | ||||||
| func (conn *IRCConn) 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.send(fmt.Sprintf("PART %s%s", c, msg)); | 	conn.out <- "PART " + c + msg | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // send a QUIT command to the server | // send a QUIT command to the server | ||||||
| func (conn *IRCConn) Quit(a ...) { | func (conn *Conn) Quit(a ...) { | ||||||
| 	msg := getStringMsg(a); | 	msg := getStringMsg(a); | ||||||
| 	if msg == "" { | 	if msg == "" { | ||||||
| 		msg = "GoBye!" | 		msg = "GoBye!" | ||||||
| 	} | 	} | ||||||
| 	conn.send(fmt.Sprintf("QUIT :%s", msg)); | 	conn.out <- "QUIT :" + msg | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // send a WHOIS command to the server | ||||||
|  | func (conn *Conn) Whois(t string) { | ||||||
|  | 	conn.out <- "WHOIS " + t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // send a WHO command to the server | ||||||
|  | func (conn *Conn) Who(t string) { | ||||||
|  | 	conn.out <- "WHO " + t | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // send a PRIVMSG to the target t | // send a PRIVMSG to the target t | ||||||
| func (conn *IRCConn) Privmsg(t, msg string) { | func (conn *Conn) Privmsg(t, msg string) { | ||||||
| 	conn.send(fmt.Sprintf("PRIVMSG %s :%s", t, msg)); | 	conn.out <- "PRIVMSG " + t + " :" + msg | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // send a NOTICE to the target t | // send a NOTICE to the target t | ||||||
| func (conn *IRCConn) Notice(t, msg string) { | func (conn *Conn) Notice(t, msg string) { | ||||||
| 	conn.send(fmt.Sprintf("NOTICE %s :%s", 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 *IRCConn) 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, fmt.Sprintf("\001%s%s\001", ctcp, msg)); | 	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 *IRCConn) 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, fmt.Sprintf("\001%s%s\001", ctcp, msg)); | 	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 *IRCConn) 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 *IRCConn) 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 | ||||||
|  | func (conn *Conn) Topic(c string, a ...) { | ||||||
|  | 	topic := getStringMsg(a); | ||||||
|  | 	if topic != "" { | ||||||
|  | 		topic = " :" + topic | ||||||
|  | 	} | ||||||
|  | 	conn.out <- "TOPIC " + c + topic | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // send a MODE command (this one gets complicated) | ||||||
|  | // Mode(t) retrieves the user or channel modes for target t | ||||||
|  | // Mode(t, "string" | ||||||
|  | func (conn *Conn) Mode(t string, a ...) { | ||||||
|  | 	mode := getStringMsg(a); | ||||||
|  | 	if mode != "" { | ||||||
|  | 		mode = " " + mode | ||||||
|  | 	} | ||||||
|  | 	conn.out <- "MODE " + t + mode | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getStringMsg(a ...) (msg string) { | func getStringMsg(a ...) (msg string) { | ||||||
| 	// dealing with functions with a variable parameter list is nasteeh :-( | 	// dealing with functions with a variable parameter list is nasteeh :-( | ||||||
| 	// the below stolen and munged from fmt/print.go | 	// the below stolen and munged from fmt/print.go func getString() | ||||||
| 	if v := reflect.NewValue(a).(*reflect.StructValue); v.NumField() == 1 { | 	if v := reflect.NewValue(a).(*reflect.StructValue); v.NumField() > 0 { | ||||||
| 		// XXX: should we check that this looks at least vaguely stringy first? | 		if s, ok := v.Field(0).(*reflect.StringValue); ok { | ||||||
| 		msg = fmt.Sprintf("%v", v.Field(1)); | 			return s.Get(); | ||||||
| 	} else { | 		} | ||||||
| 		msg = "" | 		if b, ok := v.Interface().([]byte); ok { | ||||||
|  | 			return string(b) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return | 	return "" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,3 @@ | ||||||
| // Some IRC testing code! |  | ||||||
| 
 |  | ||||||
| package irc | package irc | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | @ -11,96 +9,99 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // the IRC connection object | // the IRC connection object | ||||||
| type IRCConn struct { | type Conn struct { | ||||||
| 	sock	*bufio.ReadWriter; | 	// Hostname, Nickname, etc. | ||||||
| 	Host	string; | 	Host string; | ||||||
| 	Me	string; | 	Me *Nick; | ||||||
| 	Ident	string; | 
 | ||||||
| 	Name	string; | 	// I/O stuff to server | ||||||
| 	con bool; | 	sock	*net.TCPConn; | ||||||
| 	reg	bool; | 	io	*bufio.ReadWriter; | ||||||
| 	events	map[string] []func (*IRCConn, *IRCLine); | 	in	chan *Line; | ||||||
| 	chans	map[string] *IRCChan; | 	out	chan string; | ||||||
| 	nicks	map[string] *IRCNick; | 	connected bool; | ||||||
|  | 
 | ||||||
|  | 	// Error channel to transmit any fail back to the user | ||||||
|  | 	Err	chan os.Error; | ||||||
|  | 
 | ||||||
|  | 	// Event handler mapping | ||||||
|  | 	events	map[string] []func (*Conn, *Line); | ||||||
|  | 
 | ||||||
|  | 	// Map of channels we're on | ||||||
|  | 	chans	map[string] *Channel; | ||||||
|  | 
 | ||||||
|  | 	// Map of nicks we know about | ||||||
|  | 	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 IRCLine struct { | type Line struct { | ||||||
| 	Nick	string; | 	Nick, Ident, Host, Src	string; | ||||||
| 	User	string; | 	Cmd, Text, Raw	string; | ||||||
| 	Host	string; |  | ||||||
| 	Src	string; |  | ||||||
| 	Cmd	string; |  | ||||||
| 	Args	[]string; | 	Args	[]string; | ||||||
| 	Text	string; |  | ||||||
| 	Raw		string; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // A struct representing an IRC channel |  | ||||||
| type IRCChan struct { |  | ||||||
| 	Name	string; |  | ||||||
| 	Topic	string; |  | ||||||
| 	Modes	map[string] string; |  | ||||||
| 	Nicks	map[string] *IRCNick; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // A struct representing an IRC nick |  | ||||||
| type IRCNick struct { |  | ||||||
| 	Name	string; |  | ||||||
| 	Chans	map[string] *IRCChan; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // construct a new IRC Connection object | // construct a new IRC Connection object | ||||||
| func New(nick, user, name string) (conn *IRCConn) { | func New(nick, user, name string) *Conn { | ||||||
| 	conn = &IRCConn{Me: nick, Ident: user, Name: name}; | 	conn := new(Conn); | ||||||
| 	// allocate meh some memoraaaahh | 	conn.initialise(); | ||||||
| 	conn.nicks = make(map[string] *IRCNick); | 	conn.Me = conn.NewNick(nick, user, name, ""); | ||||||
| 	conn.chans = make(map[string] *IRCChan); |  | ||||||
| 	conn.events = make(map[string] []func(*IRCConn, *IRCLine)); |  | ||||||
| 	conn.setupEvents(); | 	conn.setupEvents(); | ||||||
| 	return conn | 	return conn; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (conn *Conn) initialise() { | ||||||
|  | 	// allocate meh some memoraaaahh | ||||||
|  | 	fmt.Println("irc.initialise(): initialising..."); | ||||||
|  | 	conn.nicks = make(map[string] *Nick); | ||||||
|  | 	conn.chans = make(map[string] *Channel); | ||||||
|  | 	conn.in = make(chan *Line, 32); | ||||||
|  | 	conn.out = make(chan string, 32); | ||||||
|  | 	conn.Err = make(chan os.Error, 4); | ||||||
|  | 	conn.io = nil; | ||||||
|  | 	conn.sock = nil; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // connect the IRC connection object to a host | // connect the IRC connection object to a host | ||||||
| func (conn *IRCConn) Connect(host, pass string) (err os.Error) { | func (conn *Conn) Connect(host, pass string) os.Error { | ||||||
|  | 	if conn.connected { | ||||||
|  | 		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"; | ||||||
| 	} | 	} | ||||||
| 	sock, err := net.Dial("tcp", "", host); | 
 | ||||||
| 	if err != nil { | 	if addr, err := net.ResolveTCPAddr(host); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} else if conn.sock, err = net.DialTCP("tcp", nil, addr); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	conn.sock = bufio.NewReadWriter(bufio.NewReader(sock), bufio.NewWriter(sock)); | 	fmt.Println("irc.Connect(): connected happily..."); | ||||||
| 	conn.con = true; |  | ||||||
| 	conn.Host = host; | 	conn.Host = host; | ||||||
| 
 | 
 | ||||||
| 	// initial connection set-up | 	conn.io = bufio.NewReadWriter( | ||||||
| 	// verify valid nick/user/name here? | 		bufio.NewReader(conn.sock), | ||||||
|  | 		bufio.NewWriter(conn.sock) | ||||||
|  | 	); | ||||||
|  | 	go conn.send(); | ||||||
|  | 	go conn.recv(); | ||||||
|  | 
 | ||||||
| 	if pass != "" { | 	if pass != "" { | ||||||
| 		conn.Pass(pass) | 		conn.Pass(pass) | ||||||
| 	} | 	} | ||||||
| 	conn.Nick(conn.Me); | 	conn.Nick(conn.Me.Nick); | ||||||
| 	conn.User(conn.Ident, conn.Name); | 	conn.User(conn.Me.Ident, conn.Me.Name); | ||||||
| 
 | 
 | ||||||
| 	for line, err := conn.recv(); err == nil; line, err = conn.recv() { | 	go conn.runLoop(); | ||||||
| 		// initial loop to get us to the point where we're connected | 	fmt.Println("irc.Connect(): launched runLoop() goroutine."); | ||||||
| 		conn.dispatchEvent(line); | 	return nil; | ||||||
| 		if line.Cmd == "001" { |  | ||||||
| 			break; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return err; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (conn *IRCConn) RunLoop(c chan os.Error) { | // dispatch a nicely formatted os.Error to the error channel | ||||||
| 	var err os.Error; | func (conn *Conn) error(s string, a ...) { | ||||||
| 	for line, err := conn.recv(); err == nil; line, err = conn.recv() { | 	conn.Err <- os.NewError(fmt.Sprintf(s, a)); | ||||||
| 		conn.dispatchEvent(line); |  | ||||||
| 	} |  | ||||||
| 	c <- err; |  | ||||||
| 	return; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // copied from http.client for great justice | // copied from http.client for great justice | ||||||
|  | @ -108,56 +109,120 @@ func hasPort(s string) bool { | ||||||
| 	return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") | 	return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // send \r\n terminated line to peer, propagate errors | // dispatch input from channel as \r\n terminated line to peer | ||||||
| func (conn *IRCConn) send(line string) (err os.Error) { | func (conn *Conn) send() { | ||||||
| 	err = conn.sock.WriteString(line + "\r\n"); | 	for { | ||||||
| 	conn.sock.Flush(); | 		line := <-conn.out; | ||||||
| 	fmt.Println("-> " + line); | 		if closed(conn.out) { | ||||||
| 	return err | 			break; | ||||||
|  | 		} | ||||||
|  | 		if err := conn.io.WriteString(line + "\r\n"); err != nil { | ||||||
|  | 			conn.error("irc.send(): %s", err.String()); | ||||||
|  | 			conn.shutdown(); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 		conn.io.Flush(); | ||||||
|  | 		fmt.Println("-> " + line); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // receive one \r\n terminated line from peer and parse it, propagate errors | // receive one \r\n terminated line from peer, parse and dispatch it | ||||||
| func (conn *IRCConn) recv() (line *IRCLine, err os.Error) { | func (conn *Conn) recv() { | ||||||
| 	s, err := conn.sock.ReadString('\n'); | 	for { | ||||||
| 	if err != nil { | 		s, err := conn.io.ReadString('\n'); | ||||||
| 		return line, err | 		if err != nil { | ||||||
| 	} | 			conn.error("irc.recv(): %s", err.String()); | ||||||
| 	// chop off \r\n | 			conn.shutdown(); | ||||||
| 	s = s[0:len(s)-2]; | 			break; | ||||||
| 	fmt.Println("<- " + s); | 		} | ||||||
|  | 		// chop off \r\n | ||||||
|  | 		s = s[0:len(s)-2]; | ||||||
|  | 		fmt.Println("<- " + s); | ||||||
| 
 | 
 | ||||||
| 	line = &IRCLine{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)]; | ||||||
| 			return line, nil; | 				conn.in <- line; | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// src can be the hostname of the irc server or a nick!user@host | ||||||
|  | 			line.Host = line.Src; | ||||||
|  | 			nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@"); | ||||||
|  | 			if uidx != -1 && nidx != -1 { | ||||||
|  | 				line.Nick  = line.Src[0:nidx]; | ||||||
|  | 				line.Ident = line.Src[nidx+1:uidx]; | ||||||
|  | 				line.Host  = line.Src[uidx+1:len(line.Src)]; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// src can be the hostname of the irc server or a nick!user@host | 		// now we're here, we've parsed a :nick!user@host or :server off | ||||||
| 		line.Host = line.Src; | 		// s should contain "cmd args[] :text" | ||||||
| 		nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@"); | 		args := strings.Split(s, " :", 2); | ||||||
| 		if uidx != -1 && nidx != -1 { | 		if len(args) > 1 { | ||||||
| 			line.Nick = line.Src[0:nidx]; | 			line.Text = args[1]; | ||||||
| 			line.User = line.Src[nidx+1:uidx]; |  | ||||||
| 			line.Host = line.Src[uidx+1:len(line.Src)]; |  | ||||||
| 		} | 		} | ||||||
|  | 		args = strings.Split(args[0], " ", 0); | ||||||
|  | 		line.Cmd = strings.ToUpper(args[0]); | ||||||
|  | 		if len(args) > 1 { | ||||||
|  | 			line.Args = args[1:len(args)]; | ||||||
|  | 		} | ||||||
|  | 		conn.in <- line | ||||||
| 	} | 	} | ||||||
| 
 | } | ||||||
| 	// now we're here, we've parsed a :nick!user@host or :server off | 
 | ||||||
| 	// s should contain "cmd args[] :text" | func (conn *Conn) runLoop() { | ||||||
| 	args := strings.Split(s, " :", 2); | 	for { | ||||||
| 	if len(args) > 1 { | 		if closed(conn.in) { | ||||||
| 		line.Text = args[1]; | 			break; | ||||||
| 	} | 		} | ||||||
| 	args = strings.Split(args[0], " ", 0); | 		select { | ||||||
| 	line.Cmd = strings.ToUpper(args[0]); | 			case line := <-conn.in: | ||||||
| 	if len(args) > 1 { | 				conn.dispatchEvent(line); | ||||||
| 		line.Args = args[1:len(args)]; | 		} | ||||||
| 	} | 	} | ||||||
| 	return line, nil | 	fmt.Println("irc.runLoop(): Exited runloop..."); | ||||||
|  | 	// if we fall off the end here due to shutdown, | ||||||
|  | 	// reinit everything once the runloop is done | ||||||
|  | 	// so that Connect() can be called again. | ||||||
|  | 	conn.initialise(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (conn *Conn) shutdown() { | ||||||
|  | 	close(conn.in); | ||||||
|  | 	close(conn.out); | ||||||
|  | 	close(conn.Err); | ||||||
|  | 	conn.connected = false; | ||||||
|  | 	conn.sock.Close(); | ||||||
|  | 	fmt.Println("irc.shutdown(): shut down sockets and channels!"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (conn *Conn) String() string { | ||||||
|  | 	str := "GoIRC Connection\n"; | ||||||
|  | 	str += "----------------\n\n"; | ||||||
|  | 	if conn.connected { | ||||||
|  | 		str += "Connected to " + conn.Host + "\n\n" | ||||||
|  | 	} else { | ||||||
|  | 		str += "Not currently connected!\n\n"; | ||||||
|  | 	} | ||||||
|  | 	str += conn.Me.String() + "\n"; | ||||||
|  | 	str += "GoIRC Channels\n"; | ||||||
|  | 	str += "--------------\n\n"; | ||||||
|  | 	for _, ch := range conn.chans { | ||||||
|  | 		str += ch.String() + "\n" | ||||||
|  | 	} | ||||||
|  | 	str += "GoIRC NickNames\n"; | ||||||
|  | 	str += "---------------\n\n"; | ||||||
|  | 	for _, n := range conn.nicks { | ||||||
|  | 		if n != conn.Me { | ||||||
|  | 			str += n.String() + "\n" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return str; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										346
									
								
								irc/handlers.go
									
										
									
									
									
								
							
							
						
						
									
										346
									
								
								irc/handlers.go
									
										
									
									
									
								
							|  | @ -4,17 +4,17 @@ package irc | ||||||
| // to manage tracking an irc connection etc. | // to manage tracking an irc connection etc. | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt"; |  | ||||||
| 	"strings"; | 	"strings"; | ||||||
|  | 	"strconv"; | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Add an event handler for a specific IRC command | // Add an event handler for a specific IRC command | ||||||
| func (conn *IRCConn) AddHandler(name string, f func (*IRCConn, *IRCLine)) { | 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 (*IRCConn, *IRCLine), 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]; | ||||||
| 			} | 			} | ||||||
|  | @ -23,14 +23,21 @@ func (conn *IRCConn) AddHandler(name string, f func (*IRCConn, *IRCLine)) { | ||||||
| 		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 (*IRCConn, *IRCLine), 1, 10); | 		e := make([]func (*Conn, *Line), 1, 10); | ||||||
| 		e[0] = f; | 		e[0] = f; | ||||||
| 		conn.events[n] = e; | 		conn.events[n] = e; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // loops through all event handlers for line.Cmd, running each in a goroutine | // loops through all event handlers for line.Cmd, running each in a goroutine | ||||||
| func (conn *IRCConn) dispatchEvent(line *IRCLine) { | func (conn *Conn) dispatchEvent(line *Line) { | ||||||
|  | 	// seems that we end up dispatching an event with a nil line when receiving | ||||||
|  | 	// EOF from the server. Until i've tracked down why.... | ||||||
|  | 	if line == nil { | ||||||
|  | 		conn.error("irc.dispatchEvent(): buh? line == nil :-("); | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// So, I think CTCP and (in particular) CTCP ACTION are better handled as | 	// So, I think CTCP and (in particular) CTCP ACTION are better handled as | ||||||
| 	// separate events as opposed to forcing people to have gargantuan PRIVMSG | 	// separate events as opposed to forcing people to have gargantuan PRIVMSG | ||||||
| 	// handlers to cope with the possibilities. | 	// handlers to cope with the possibilities. | ||||||
|  | @ -67,22 +74,329 @@ func (conn *IRCConn) dispatchEvent(line *IRCLine) { | ||||||
| 
 | 
 | ||||||
| // 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? | ||||||
| func (conn *IRCConn) setupEvents() { | // Turns out there may be but it's not actually implemented in the language yet | ||||||
|  | // according to the people on freenode/#go-nuts ... :-(  | ||||||
|  | // 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: | ||||||
|  | //   conn.AddHandler("event", (*Conn).h_handler); | ||||||
|  | // where h_handler is declared in the irc package as: | ||||||
|  | //   func (conn *Conn) h_handler(line *Line) {} | ||||||
|  | // in the future, but for now the compiler throws a hissy fit. | ||||||
|  | func (conn *Conn) setupEvents() { | ||||||
|  | 	conn.events = make(map[string] []func(*Conn, *Line)); | ||||||
|  | 
 | ||||||
| 	// Basic ping/pong handler | 	// Basic ping/pong handler | ||||||
| 	conn.AddHandler("PING",	func(conn *IRCConn, line *IRCLine) { | 	conn.AddHandler("PING",	func(conn *Conn, line *Line) { | ||||||
| 		conn.send(fmt.Sprintf("PONG :%s", 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 *IRCConn, line *IRCLine) { | 	conn.AddHandler("001", func(conn *Conn, line *Line) { | ||||||
| 		l := new(IRCLine); | 		// we're connected! | ||||||
| 		l.Cmd = "CONNECTED"; | 		conn.connected = true; | ||||||
| 		conn.dispatchEvent(l); | 		conn.dispatchEvent(&Line{Cmd: "CONNECTED"}); | ||||||
|  | 		// and we're being given our hostname (from the server's perspective) | ||||||
|  | 		if ridx := strings.LastIndex(line.Text, " "); ridx != -1 { | ||||||
|  | 			h := line.Text[ridx+1:len(line.Text)]; | ||||||
|  | 			if idx := strings.Index(h, "@"); idx != -1 { | ||||||
|  | 				conn.Me.Host = h[idx+1:len(h)]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	// Handler to deal with "433 :Nickname already in use" on connection | 	// XXX: do we need 005 protocol support message parsing here? | ||||||
| 	conn.AddHandler("433", func(conn *IRCConn, line *IRCLine) { | 	// probably in the future, but I can't quite be arsed yet. | ||||||
| 		conn.Nick(conn.Me + "_"); | 	/* | ||||||
|  | :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 STATUSMSG=~&@%+ EXCEPTS INVEX :are supported by this server | ||||||
|  | 	*/ | ||||||
|  | 
 | ||||||
|  | 	// Handler to deal with "433 :Nickname already in use" | ||||||
|  | 	conn.AddHandler("433", func(conn *Conn, line *Line) { | ||||||
|  | 		// Args[1] is the new nick we were attempting to acquire | ||||||
|  | 		conn.Nick(line.Args[1] + "_"); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handler NICK messages to inform us about nick changes | ||||||
|  | 	conn.AddHandler("NICK", func(conn *Conn, line *Line) { | ||||||
|  | 		// all nicks should be handled the same way, our own included | ||||||
|  | 		if n := conn.GetNick(line.Nick); n != nil { | ||||||
|  | 			n.ReNick(line.Text); | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.NICK(): buh? unknown nick %s.", line.Nick); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle VERSION requests and CTCP PING | ||||||
|  | 	conn.AddHandler("CTCP", func(conn *Conn, line *Line) { | ||||||
|  | 		if line.Args[0] == "VERSION" { | ||||||
|  | 			conn.CtcpReply(line.Nick, "VERSION", "powered by goirc..."); | ||||||
|  | 		} else if line.Args[0] == "PING" { | ||||||
|  | 			conn.CtcpReply(line.Nick, "PING", line.Text); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle JOINs to channels to maintain state | ||||||
|  | 	conn.AddHandler("JOIN", func(conn *Conn, line *Line) { | ||||||
|  | 		ch := conn.GetChannel(line.Text); | ||||||
|  | 		n := conn.GetNick(line.Nick); | ||||||
|  | 		if ch == nil { | ||||||
|  | 			// first we've seen of this channel, so should be us joining it | ||||||
|  | 			// NOTE this will also take care of n == nil && ch == nil | ||||||
|  | 			if n != conn.Me { | ||||||
|  | 				conn.error("irc.JOIN(): buh? JOIN to unknown channel %s recieved from (non-me) nick %s", line.Text, line.Nick); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			ch = conn.NewChannel(line.Text); | ||||||
|  | 			// since we don't know much about this channel, ask server for info | ||||||
|  | 			// we get the channel users automatically in 353 and the channel | ||||||
|  | 			// topic in 332 on join, so we just need to get the modes | ||||||
|  | 			conn.Mode(ch.Name); | ||||||
|  | 		} | ||||||
|  | 		if n == nil { | ||||||
|  | 			// this is the first we've seen of this nick | ||||||
|  | 			n = conn.NewNick(line.Nick, line.Ident, "", line.Host); | ||||||
|  | 			// since we don't know much about this nick, ask server for info | ||||||
|  | 			conn.Whois(n.Nick); | ||||||
|  | 		} | ||||||
|  | 		// this takes care of both nick and channel linking \o/ | ||||||
|  | 		ch.AddNick(n); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle PARTs from channels to maintain state | ||||||
|  | 	conn.AddHandler("PART", func(conn *Conn, line *Line) { | ||||||
|  | 		ch := conn.GetChannel(line.Args[0]); | ||||||
|  | 		n := conn.GetNick(line.Nick); | ||||||
|  | 		if ch != nil && n != nil { | ||||||
|  | 			ch.DelNick(n); | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.PART(): buh? PART of channel %s by nick %s", line.Args[0], line.Nick); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle KICKs from channels to maintain state | ||||||
|  | 	conn.AddHandler("KICK", func(conn *Conn, line *Line) { | ||||||
|  | 		// XXX: this won't handle autorejoining channels on KICK | ||||||
|  | 		// it's trivial to do this in a seperate handler... | ||||||
|  | 		ch := conn.GetChannel(line.Args[0]); | ||||||
|  | 		n := conn.GetNick(line.Args[1]); | ||||||
|  | 		if ch != nil && n != nil { | ||||||
|  | 			ch.DelNick(n); | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.KICK(): buh? KICK from channel %s of nick %s", line.Args[0], line.Args[1]); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle other people's QUITs | ||||||
|  | 	conn.AddHandler("QUIT", func(conn *Conn, line *Line) { | ||||||
|  | 		if n := conn.GetNick(line.Nick); n != nil { | ||||||
|  | 			n.Delete(); | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.QUIT(): buh? QUIT from unknown nick %s", line.Nick); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle MODE changes for channels we know about (and our nick personally) | ||||||
|  | 	// this is moderately ugly. suggestions for improvement welcome | ||||||
|  | 	conn.AddHandler("MODE", func(conn *Conn, line *Line) { | ||||||
|  | 		// channel modes first | ||||||
|  | 		if ch := conn.GetChannel(line.Args[0]); ch != nil { | ||||||
|  | 			modeargs := line.Args[2:len(line.Args)]; | ||||||
|  | 			var modeop bool; // true => add mode, false => remove mode | ||||||
|  | 			var modestr string; | ||||||
|  | 			for i := 0; i < len(line.Args[1]); i++ { | ||||||
|  | 				switch m := line.Args[1][i]; m { | ||||||
|  | 				case '+': | ||||||
|  | 					modeop = true; | ||||||
|  | 					modestr = string(m); | ||||||
|  | 				case '-': | ||||||
|  | 					modeop = false; | ||||||
|  | 					modestr = string(m); | ||||||
|  | 				case 'i': ch.Modes.InviteOnly = modeop; | ||||||
|  | 				case 'm': ch.Modes.Moderated = modeop; | ||||||
|  | 				case 'n': ch.Modes.NoExternalMsg = modeop; | ||||||
|  | 				case 'p': 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': | ||||||
|  | 					if len(modeargs) != 0 { | ||||||
|  | 						ch.Modes.Key, modeargs = modeargs[0], modeargs[1:len(modeargs)] | ||||||
|  | 					} else { | ||||||
|  | 						conn.error("irc.MODE(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m) | ||||||
|  | 					} | ||||||
|  | 				case 'l': | ||||||
|  | 					if len(modeargs) != 0 { | ||||||
|  | 						ch.Modes.Limit, _ = strconv.Atoi(modeargs[0]); | ||||||
|  | 						modeargs = modeargs[1:len(modeargs)]; | ||||||
|  | 					} else { | ||||||
|  | 						conn.error("irc.MODE(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m) | ||||||
|  | 					} | ||||||
|  | 				case 'q', 'a', 'o', 'h', 'v': | ||||||
|  | 					if len(modeargs) != 0 { | ||||||
|  | 						n := conn.GetNick(modeargs[0]); | ||||||
|  | 						if p, ok := ch.Nicks[n]; ok && n != nil { | ||||||
|  | 							switch m { | ||||||
|  | 							case 'q': p.Owner = modeop; | ||||||
|  | 							case 'a': p.Admin = modeop; | ||||||
|  | 							case 'o': p.Op = modeop; | ||||||
|  | 							case 'h': p.HalfOp = modeop; | ||||||
|  | 							case 'v': p.Voice = modeop; | ||||||
|  | 							} | ||||||
|  | 							modeargs = modeargs[1:len(modeargs)]; | ||||||
|  | 						} else { | ||||||
|  | 							conn.error("irc.MODE(): MODE %s %s%s %s: buh? state tracking failure.", ch.Name, modestr, m, modeargs[0]); | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						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 { | ||||||
|  | 			// nick mode change, should be us | ||||||
|  | 			if n != conn.Me { | ||||||
|  | 				conn.error("irc.MODE(): buh? recieved MODE %s for (non-me) nick %s", line.Args[1], n.Nick); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			var modeop bool; // true => add mode, false => remove mode | ||||||
|  | 			for i := 0; i < len(line.Text); i++ { | ||||||
|  | 				switch m := line.Text[i]; m { | ||||||
|  | 				case '+': | ||||||
|  | 					modeop = true; | ||||||
|  | 				case '-': | ||||||
|  | 					modeop = false; | ||||||
|  | 				case 'i': n.Modes.Invisible = modeop; | ||||||
|  | 				case 'o': n.Modes.Oper = modeop; | ||||||
|  | 				case 'w': n.Modes.WallOps = modeop; | ||||||
|  | 				case 'x': n.Modes.HiddenHost = modeop; | ||||||
|  | 				case 'z': n.Modes.SSL = modeop; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			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 | ||||||
|  | 	conn.AddHandler("TOPIC", func(conn *Conn, line *Line) { | ||||||
|  | 		if ch := conn.GetChannel(line.Args[0]); ch != nil { | ||||||
|  | 			ch.Topic = line.Text; | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.TOPIC(): buh? topic change on unknown channel %s", line.Args[0]); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle 311 whois reply | ||||||
|  | 	conn.AddHandler("311", func(conn *Conn, line *Line) { | ||||||
|  | 		if n := conn.GetNick(line.Args[1]); n != nil { | ||||||
|  | 			n.Ident = line.Args[2]; | ||||||
|  | 			n.Host = line.Args[3]; | ||||||
|  | 			n.Name = line.Text; | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.311(): buh? received WHOIS info for unknown nick %s", line.Args[1]); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle 324 mode reply | ||||||
|  | 	conn.AddHandler("324", func(conn *Conn, line *Line) { | ||||||
|  | 		// XXX: copypasta from MODE, needs tidying. | ||||||
|  | 		if ch := conn.GetChannel(line.Args[1]); ch != nil { | ||||||
|  | 			modeargs := line.Args[3:len(line.Args)]; | ||||||
|  | 			var modeop bool; // true => add mode, false => remove mode | ||||||
|  | 			var modestr string; | ||||||
|  | 			for i := 0; i < len(line.Args[2]); i++ { | ||||||
|  | 				switch m := line.Args[2][i]; m { | ||||||
|  | 				case '+': | ||||||
|  | 					modeop = true; | ||||||
|  | 					modestr = string(m); | ||||||
|  | 				case '-': | ||||||
|  | 					modeop = false; | ||||||
|  | 					modestr = string(m); | ||||||
|  | 				case 'i': ch.Modes.InviteOnly = modeop; | ||||||
|  | 				case 'm': ch.Modes.Moderated = modeop; | ||||||
|  | 				case 'n': ch.Modes.NoExternalMsg = modeop; | ||||||
|  | 				case 'p': 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': | ||||||
|  | 					if len(modeargs) != 0 { | ||||||
|  | 						ch.Modes.Key, modeargs = modeargs[0], modeargs[1:len(modeargs)] | ||||||
|  | 					} else { | ||||||
|  | 						conn.error("irc.324(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m) | ||||||
|  | 					} | ||||||
|  | 				case 'l': | ||||||
|  | 					if len(modeargs) != 0 { | ||||||
|  | 						ch.Modes.Limit, _ = strconv.Atoi(modeargs[0]); | ||||||
|  | 						modeargs = modeargs[1:len(modeargs)]; | ||||||
|  | 					} else { | ||||||
|  | 						conn.error("irc.324(): buh? not enough arguments to process MODE %s %s%s", ch.Name, modestr, m) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.324(): buh? received MODE settings for unknown channel %s", line.Args[1]); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle 332 topic reply on join to channel | ||||||
|  | 	conn.AddHandler("332", func(conn *Conn, line *Line) { | ||||||
|  | 		if ch := conn.GetChannel(line.Args[1]); ch != nil { | ||||||
|  | 			ch.Topic = line.Text; | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.332(): buh? received TOPIC value for unknown channel %s", line.Args[1]); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle 353 names reply | ||||||
|  | 	conn.AddHandler("353", func(conn *Conn, line *Line) { | ||||||
|  | 		if ch := conn.GetChannel(line.Args[2]); ch != nil { | ||||||
|  | 			nicks := strings.Split(line.Text, " ", 0); | ||||||
|  | 			for _, nick := range nicks { | ||||||
|  | 				// UnrealIRCd's coders are lazy and leave a trailing space | ||||||
|  | 				if nick == "" { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				switch c := nick[0]; c { | ||||||
|  | 				case '~', '&', '@', '%', '+': | ||||||
|  | 					nick = nick[1:len(nick)]; | ||||||
|  | 					fallthrough; | ||||||
|  | 				default: | ||||||
|  | 					n := conn.GetNick(nick); | ||||||
|  | 					if n == nil { | ||||||
|  | 						// we don't know this nick yet! | ||||||
|  | 						n = conn.NewNick(nick, "", "", ""); | ||||||
|  | 						conn.Whois(nick); | ||||||
|  | 					} | ||||||
|  | 					if n != conn.Me { | ||||||
|  | 						// we will be in the names list, but should also be in | ||||||
|  | 						// the channel's nick list from the JOIN handler above | ||||||
|  | 						ch.AddNick(n); | ||||||
|  | 					} | ||||||
|  | 					p := ch.Nicks[n]; | ||||||
|  | 					switch c { | ||||||
|  | 					case '~': p.Owner = true; | ||||||
|  | 					case '&': p.Admin = true; | ||||||
|  | 					case '@': p.Op = true; | ||||||
|  | 					case '%': p.HalfOp = true; | ||||||
|  | 					case '+': p.Voice = true; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.353(): buh? received NAMES list for unknown channel %s", line.Args[2]); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Handle 671 whois reply (nick connected via SSL) | ||||||
|  | 	conn.AddHandler("671", func(conn *Conn, line *Line) { | ||||||
|  | 		if n := conn.GetNick(line.Args[1]); n != nil { | ||||||
|  | 			n.Modes.SSL = true; | ||||||
|  | 		} else { | ||||||
|  | 			conn.error("irc.671(): buh? received WHOIS SSL info for unknown nick %s", line.Args[1]); | ||||||
|  | 		} | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 |  | ||||||
|  |  | ||||||
							
								
								
									
										327
									
								
								irc/nickchan.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								irc/nickchan.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,327 @@ | ||||||
|  | package irc | ||||||
|  | 
 | ||||||
|  | // Here you'll find the Channel and Nick structs | ||||||
|  | // as well as the internal state maintenance code for the handlers | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt"; | ||||||
|  | 	"reflect"; | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A struct representing an IRC channel | ||||||
|  | type Channel struct { | ||||||
|  | 	Name, Topic	string; | ||||||
|  | 	Modes	*ChanMode; | ||||||
|  | 	// MODE +q, +a, +o, +h, +v | ||||||
|  | 	Nicks	map[*Nick] *ChanPrivs; | ||||||
|  | 	conn	*Conn; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A struct representing an IRC nick | ||||||
|  | type Nick struct { | ||||||
|  | 	Nick, Ident, Host, Name	string; | ||||||
|  | 	Modes	*NickMode; | ||||||
|  | 	Channels	map[*Channel] *ChanPrivs; | ||||||
|  | 	conn	*Conn; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A struct representing the modes of an IRC Channel | ||||||
|  | // (the ones we care about, at least) | ||||||
|  | // see the MODE handler in setupEvents() for details | ||||||
|  | type ChanMode struct { | ||||||
|  | 	// MODE +p, +s, +t, +n, +m | ||||||
|  | 	Private, Secret, ProtectedTopic, NoExternalMsg, Moderated	bool; | ||||||
|  | 	// MODE +i, +O, +z | ||||||
|  | 	InviteOnly, OperOnly, SSLOnly	bool; | ||||||
|  | 	// MODE +k | ||||||
|  | 	Key	string; | ||||||
|  | 	// MODE +l | ||||||
|  | 	Limit int; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A struct representing the modes of an IRC Nick (User Modes) | ||||||
|  | // (again, only the ones we care about) | ||||||
|  | // This is only really useful for conn.Me, as we can't see other people's modes | ||||||
|  | // without IRC operator privileges (and even then only on some IRCd's). | ||||||
|  | type NickMode struct { | ||||||
|  | 	// MODE +i, +o, +w, +x, +z | ||||||
|  | 	Invisible, Oper, WallOps, HiddenHost, SSL	bool; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A struct representing the modes a Nick can have on a Channel | ||||||
|  | type ChanPrivs struct { | ||||||
|  | 	// MODE +q, +a, +o, +h, +v | ||||||
|  | 	Owner, Admin, Op, HalfOp, Voice bool; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************************************************************************\ | ||||||
|  |  * Conn methods to create/look up nicks/channels | ||||||
|  | \******************************************************************************/ | ||||||
|  | func (conn *Conn) NewNick(nick, ident, name, host string) *Nick { | ||||||
|  | 	n := &Nick{Nick: nick, Ident: ident, Name: name, Host: host, conn: conn}; | ||||||
|  | 	n.initialise(); | ||||||
|  | 	conn.nicks[n.Nick] = n; | ||||||
|  | 	return n; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (conn *Conn) GetNick(n string) *Nick { | ||||||
|  | 	if nick, ok := conn.nicks[n]; ok { | ||||||
|  | 		return nick | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (conn *Conn) NewChannel(c string) *Channel { | ||||||
|  | 	ch := &Channel{Name: c, conn: conn}; | ||||||
|  | 	ch.initialise(); | ||||||
|  | 	conn.chans[ch.Name] = ch; | ||||||
|  | 	return ch | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (conn *Conn) GetChannel(c string) *Channel { | ||||||
|  | 	if ch, ok := conn.chans[c]; ok { | ||||||
|  | 		return ch | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************************************************************************\ | ||||||
|  |  * Channel methods for state management | ||||||
|  | \******************************************************************************/ | ||||||
|  | func (ch *Channel) initialise() { | ||||||
|  | 	ch.Modes = new(ChanMode); | ||||||
|  | 	ch.Nicks = make(map[*Nick] *ChanPrivs); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ch *Channel) AddNick(n *Nick) { | ||||||
|  | 	if _, ok := ch.Nicks[n]; !ok { | ||||||
|  | 		ch.Nicks[n] = new(ChanPrivs); | ||||||
|  | 		n.Channels[ch] = ch.Nicks[n]; | ||||||
|  | 	} else { | ||||||
|  | 		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) { | ||||||
|  | 	if _, ok := ch.Nicks[n]; ok { | ||||||
|  | 		fmt.Printf("irc.Channel.DelNick(): deleting %s from %s\n", n.Nick, ch.Name); | ||||||
|  | 		if n == n.conn.Me { | ||||||
|  | 			// we're leaving the channel, so remove all state we have about it | ||||||
|  | 			ch.Delete(); | ||||||
|  | 		} else { | ||||||
|  | 			ch.Nicks[n] = nil, false; | ||||||
|  | 			n.DelChannel(ch); | ||||||
|  | 		} | ||||||
|  | 	} // no else here ... | ||||||
|  | 	// we call Channel.DelNick() and Nick.DelChan() from each other to ensure | ||||||
|  | 	// consistency, and this would mean spewing an error message every delete | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ch *Channel) Delete() { | ||||||
|  | 	fmt.Printf("irc.Channel.Delete(): deleting %s\n", ch.Name); | ||||||
|  | 	for n, _ := range ch.Nicks { | ||||||
|  | 		n.DelChannel(ch); | ||||||
|  | 	} | ||||||
|  | 	ch.conn.chans[ch.Name] = nil, false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************************************************************************\ | ||||||
|  |  * Nick methods for state management | ||||||
|  | \******************************************************************************/ | ||||||
|  | func (n *Nick) initialise() { | ||||||
|  | 	n.Modes = new(NickMode); | ||||||
|  | 	n.Channels = make(map[*Channel] *ChanPrivs); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // very slightly different to Channel.AddNick() ... | ||||||
|  | func (n *Nick) AddChannel(ch *Channel) { | ||||||
|  | 	if _, ok := n.Channels[ch]; !ok { | ||||||
|  | 		ch.Nicks[n] = new(ChanPrivs); | ||||||
|  | 		n.Channels[ch] = ch.Nicks[n]; | ||||||
|  | 	} else { | ||||||
|  | 		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) { | ||||||
|  | 	if _, ok := n.Channels[ch]; ok { | ||||||
|  | 		fmt.Printf("irc.Nick.DelChannel(): deleting %s from %s\n", n.Nick, ch.Name); | ||||||
|  | 		n.Channels[ch] = nil, false; | ||||||
|  | 		ch.DelNick(n); | ||||||
|  | 		if len(n.Channels) == 0 { | ||||||
|  | 			// nick is no longer in any channels we inhabit, stop tracking it | ||||||
|  | 			n.Delete(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *Nick) ReNick(neu string) { | ||||||
|  | 	n.conn.nicks[n.Nick] = nil, false; | ||||||
|  | 	n.Nick = neu; | ||||||
|  | 	n.conn.nicks[n.Nick] = n; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *Nick) Delete() { | ||||||
|  | 	// we don't ever want to remove *our* nick from conn.nicks... | ||||||
|  | 	if n != n.conn.Me { | ||||||
|  | 		fmt.Printf("irc.Nick.Delete(): deleting %s\n", n.Nick); | ||||||
|  | 		for ch, _ := range n.Channels { | ||||||
|  | 			ch.DelNick(n); | ||||||
|  | 		} | ||||||
|  | 		n.conn.nicks[n.Nick] = nil, false; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************************************************************************\ | ||||||
|  |  * String() methods for all structs in this file for ease of debugging. | ||||||
|  | \******************************************************************************/ | ||||||
|  | var ChanModeToString = map[string]string{ | ||||||
|  | 	"Private":"p", | ||||||
|  | 	"Secret":"s", | ||||||
|  | 	"ProtectedTopic":"t", | ||||||
|  | 	"NoExternalMsg":"n", | ||||||
|  | 	"Moderated":"m", | ||||||
|  | 	"InviteOnly":"i", | ||||||
|  | 	"OperOnly":"O", | ||||||
|  | 	"SSLOnly":"z", | ||||||
|  | 	"Key":"k", | ||||||
|  | 	"Limit":"l", | ||||||
|  | }; | ||||||
|  | var NickModeToString = map[string]string{ | ||||||
|  | 	"Invisible":"i", | ||||||
|  | 	"Oper":"o", | ||||||
|  | 	"WallOps":"w", | ||||||
|  | 	"HiddenHost":"x", | ||||||
|  | 	"SSL":"z", | ||||||
|  | }; | ||||||
|  | var ChanPrivToString = map[string]string{ | ||||||
|  | 	"Owner":"q", | ||||||
|  | 	"Admin":"a", | ||||||
|  | 	"Op":"o", | ||||||
|  | 	"HalfOp":"h", | ||||||
|  | 	"Voice":"v", | ||||||
|  | }; | ||||||
|  | var ChanPrivToModeChar = map[string]byte{ | ||||||
|  | 	"Owner":'~', | ||||||
|  | 	"Admin":'&', | ||||||
|  | 	"Op":'@', | ||||||
|  | 	"HalfOp":'%', | ||||||
|  | 	"Voice":'+', | ||||||
|  | }; | ||||||
|  | var StringToChanMode, StringToNickMode, StringToChanPriv map[string]string; | ||||||
|  | var ModeCharToChanPriv map[byte]string; | ||||||
|  | 
 | ||||||
|  | // Init function to fill in reverse mappings for *toString constants etc. | ||||||
|  | func init() { | ||||||
|  | 	StringToChanMode = make(map[string]string); | ||||||
|  | 	for k,v := range ChanModeToString { | ||||||
|  | 		StringToChanMode[v] = k; | ||||||
|  | 	} | ||||||
|  | 	StringToNickMode = make(map[string]string); | ||||||
|  | 	for k,v := range NickModeToString { | ||||||
|  | 		StringToNickMode[v] = k; | ||||||
|  | 	} | ||||||
|  | 	StringToChanPriv = make(map[string]string); | ||||||
|  | 	for k,v := range ChanPrivToString { | ||||||
|  | 		StringToChanPriv[v] = k; | ||||||
|  | 	} | ||||||
|  | 	ModeCharToChanPriv = make(map[byte]string); | ||||||
|  | 	for k,v := range ChanPrivToModeChar { | ||||||
|  | 		ModeCharToChanPriv[v] = k; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ch *Channel) String() string { | ||||||
|  | 	str := "Channel: " + ch.Name + "\n\t"; | ||||||
|  | 	str += "Topic: " + ch.Topic + "\n\t"; | ||||||
|  | 	str += "Modes: " + ch.Modes.String() + "\n\t"; | ||||||
|  | 	str += "Nicks: \n"; | ||||||
|  | 	for n, p := range ch.Nicks { | ||||||
|  | 		str += "\t\t" + n.Nick + ": " + p.String() + "\n"; | ||||||
|  | 	} | ||||||
|  | 	return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *Nick) String() string { | ||||||
|  | 	str := "Nick: "+ n.Nick + "\n\t"; | ||||||
|  | 	str += "Hostmask: " + n.Ident + "@" + n.Host + "\n\t"; | ||||||
|  | 	str += "Real Name: " + n.Name + "\n\t"; | ||||||
|  | 	str += "Modes: " + n.Modes.String() + "\n\t"; | ||||||
|  | 	str += "Channels: \n"; | ||||||
|  | 	for ch, p := range n.Channels { | ||||||
|  | 		str += "\t\t" + ch.Name + ": " + p.String() + "\n"; | ||||||
|  | 	} | ||||||
|  | 	return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (cm *ChanMode) String() string { | ||||||
|  | 	str := "+"; | ||||||
|  | 	a := make([]string, 2); | ||||||
|  | 	v := reflect.Indirect(reflect.NewValue(cm)).(*reflect.StructValue); | ||||||
|  | 	t := v.Type().(*reflect.StructType); | ||||||
|  | 	for i := 0; i < v.NumField(); i++ { | ||||||
|  | 		switch f := v.Field(i).(type) { | ||||||
|  | 		case *reflect.BoolValue: | ||||||
|  | 			if f.Get() { | ||||||
|  | 				str += ChanModeToString[t.Field(i).Name]; | ||||||
|  | 			} | ||||||
|  | 		case *reflect.StringValue: | ||||||
|  | 			if f.Get() != "" { | ||||||
|  | 				str += ChanModeToString[t.Field(i).Name]; | ||||||
|  | 				a[0] = f.Get(); | ||||||
|  | 			} | ||||||
|  | 		case *reflect.IntValue: | ||||||
|  | 			if f.Get() != 0 { | ||||||
|  | 				str += ChanModeToString[t.Field(i).Name]; | ||||||
|  | 				a[1] = fmt.Sprintf("%d", cm.Limit); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, s := range a { | ||||||
|  | 		if s != "" { | ||||||
|  | 			str += " " + s; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if str == "+" { | ||||||
|  | 		str = "No modes set"; | ||||||
|  | 	} | ||||||
|  | 	return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (nm *NickMode) String() string { | ||||||
|  | 	str := "+"; | ||||||
|  | 	v := reflect.Indirect(reflect.NewValue(nm)).(*reflect.StructValue); | ||||||
|  | 	t := v.Type().(*reflect.StructType); | ||||||
|  | 	for i := 0; i < v.NumField(); i++ { | ||||||
|  | 		switch f := v.Field(i).(type) { | ||||||
|  | 		// only bools here at the mo! | ||||||
|  | 		case *reflect.BoolValue: | ||||||
|  | 			if f.Get() { | ||||||
|  | 				str += NickModeToString[t.Field(i).Name]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if str == "+" { | ||||||
|  | 		str = "No modes set"; | ||||||
|  | 	} | ||||||
|  | 	return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *ChanPrivs) String() string { | ||||||
|  | 	str := "+"; | ||||||
|  | 	v := reflect.Indirect(reflect.NewValue(p)).(*reflect.StructValue); | ||||||
|  | 	t := v.Type().(*reflect.StructType); | ||||||
|  | 	for i := 0; i < v.NumField(); i++ { | ||||||
|  | 		switch f := v.Field(i).(type) { | ||||||
|  | 		// only bools here at the mo too! | ||||||
|  | 		case *reflect.BoolValue: | ||||||
|  | 			if f.Get() { | ||||||
|  | 				str += ChanPrivToString[t.Field(i).Name]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if str == "+" { | ||||||
|  | 		str = "No modes set"; | ||||||
|  | 	} | ||||||
|  | 	return str; | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								vims
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vims
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | find . -name \*.go | xargs gvim -p | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue