update README and comments so godoc creates nice helpful html documentation

This commit is contained in:
Alex Bramley 2009-12-18 22:39:22 +00:00
parent 07ff350dd0
commit e5131515b8
5 changed files with 158 additions and 59 deletions

View File

@ -52,5 +52,11 @@ likely that this state tracking will become optional in the near future.
Sorry the documentation is crap. Use the source, Luke. Sorry the documentation is crap. Use the source, Luke.
[Feedback](mailto:a.bramley@gmail.com) on design decisions is welcome. I am
indebted to Matt Gruen for his work on
[go-bot](http://code.google.com/p/go-bot/source/browse/irc.go) which inspired
the re-organisation and channel-based communication structure of `*Conn.send()`
and `*Conn.recv()`. I'm sure things could be more asynchronous, still.
This code is (c) 2009 Alex Bramley, and released under the same licence terms This code is (c) 2009 Alex Bramley, and released under the same licence terms
as Go itself. as Go itself.

View File

@ -4,7 +4,6 @@ package irc
// send to the server using an Conn connection // send to the server using an Conn connection
import ( import (
// "fmt";
"reflect" "reflect"
) )
@ -12,89 +11,100 @@ 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 // Raw() sends a raw line to the server, should really only be used for
func (conn *Conn) Raw(s string) { conn.out <- s } // debugging purposes but may well come in handy.
func (conn *Conn) Raw(rawline string) { conn.out <- rawline }
// send a PASS command to the server // Pass() sends a PASS command to the server
func (conn *Conn) Pass(p string) { conn.out <- "PASS "+p } func (conn *Conn) Pass(password string) { conn.out <- "PASS "+password }
// send a NICK command to the server // Nick() sends a NICK command to the server
func (conn *Conn) Nick(n string) { conn.out <- "NICK "+n } func (conn *Conn) Nick(nick string) { conn.out <- "NICK "+nick }
// send a USER command to the server // User() sends a USER command to the server
func (conn *Conn) User(u, n string) { conn.out <- "USER "+u+" 12 * :"+n } func (conn *Conn) User(ident, name string) {
conn.out <- "USER "+ident+" 12 * :"+name
}
// send a JOIN command to the server // Join() sends a JOIN command to the server
func (conn *Conn) Join(c string) { conn.out <- "JOIN "+c } func (conn *Conn) Join(channel string) { conn.out <- "JOIN "+channel }
// send a PART command to the server // Part() sends a PART command to the server with an optional part message
func (conn *Conn) Part(c string, a ...) { func (conn *Conn) Part(channel string, message ...) {
msg := getStringMsg(a) msg := getStringMsg(message)
if msg != "" { if msg != "" {
msg = " :" + msg msg = " :" + msg
} }
conn.out <- "PART "+c+msg conn.out <- "PART "+channel+msg
} }
// send a QUIT command to the server // Quit() sends a QUIT command to the server with an optional quit message
func (conn *Conn) Quit(a ...) { func (conn *Conn) Quit(message ...) {
msg := getStringMsg(a) msg := getStringMsg(message)
if msg == "" { if msg == "" {
msg = "GoBye!" msg = "GoBye!"
} }
conn.out <- "QUIT :"+msg conn.out <- "QUIT :"+msg
} }
// send a WHOIS command to the server // Whois() sends a WHOIS command to the server
func (conn *Conn) Whois(t string) { conn.out <- "WHOIS "+t } func (conn *Conn) Whois(nick string) { conn.out <- "WHOIS "+nick }
// send a WHO command to the server //Who() sends a WHO command to the server
func (conn *Conn) Who(t string) { conn.out <- "WHO "+t } func (conn *Conn) Who(nick string) { conn.out <- "WHO "+nick }
// send a PRIVMSG to the target t // Privmsg() sends a PRIVMSG to the target t
func (conn *Conn) Privmsg(t, msg string) { conn.out <- "PRIVMSG "+t+" :"+msg } func (conn *Conn) Privmsg(t, msg string) { conn.out <- "PRIVMSG "+t+" :"+msg }
// send a NOTICE to the target t // Notice() sends a NOTICE to the target t
func (conn *Conn) Notice(t, msg string) { conn.out <- "NOTICE "+t+" :"+msg } func (conn *Conn) Notice(t, msg string) { conn.out <- "NOTICE "+t+" :"+msg }
// send a (generic) CTCP to the target t // Ctcp() sends a (generic) CTCP message to the target t
func (conn *Conn) Ctcp(t, ctcp string, a ...) { // with an optional argument
msg := getStringMsg(a) func (conn *Conn) Ctcp(t, ctcp string, arg ...) {
msg := getStringMsg(arg)
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 // CtcpReply() sends a generic CTCP reply to the target t
func (conn *Conn) CtcpReply(t, ctcp string, a ...) { // with an optional argument
msg := getStringMsg(a) func (conn *Conn) CtcpReply(t, ctcp string, arg ...) {
msg := getStringMsg(arg)
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 // Version() sends a CTCP "VERSION" to the target t
func (conn *Conn) Version(t string) { conn.Ctcp(t, "VERSION") } func (conn *Conn) Version(t string) { conn.Ctcp(t, "VERSION") }
// send a CTCP "ACTION" to the target t -- /me does stuff! // Action() sends a CTCP "ACTION" to the target t
func (conn *Conn) Action(t, msg string) { conn.Ctcp(t, "ACTION", msg) } func (conn *Conn) Action(t, msg string) { conn.Ctcp(t, "ACTION", msg) }
// send a TOPIC command to the channel c // Topic() sends a TOPIC command to the channel
func (conn *Conn) Topic(c string, a ...) { // Topic(channel) retrieves the current channel topic (see "332" handler)
topic := getStringMsg(a) // Topic(channel, topic) sets the topic for the channel
if topic != "" { func (conn *Conn) Topic(channel string, topic ...) {
topic = " :" + topic t := getStringMsg(topic)
if t != "" {
t = " :" + t
} }
conn.out <- "TOPIC "+c+topic conn.out <- "TOPIC "+channel+t
} }
// send a MODE command (this one gets complicated) // send a MODE command to the server. This one can get complicated if we try
// Mode(t) retrieves the user or channel modes for target t // to be too clever, so it's deliberately simple:
// Mode(t, "string" // Mode(t) retrieves the user or channel modes for target t
func (conn *Conn) Mode(t string, a ...) { // Mode(t, "modestring") sets user or channel modes for target t, where...
mode := getStringMsg(a) // modestring == e.g. "+o <nick>" or "+ntk <key>" or "-is"
// This means you'll need to do your own mode work. It may be linked in with
// the state tracking and ChanMode/NickMode/ChanPrivs objects later...
func (conn *Conn) Mode(t string, modestring ...) {
mode := getStringMsg(modestring)
if mode != "" { if mode != "" {
mode = " " + mode mode = " " + mode
} }

View File

@ -8,9 +8,10 @@ import (
"strings" "strings"
) )
// the IRC connection object // An IRC connection is represented by this struct. Once connected, any errors
// encountered are piped down *Conn.Err; this channel is closed on disconnect.
type Conn struct { type Conn struct {
// Hostname, Nickname, etc. // Connection Hostname and Nickname
Host string Host string
Me *Nick Me *Nick
@ -34,16 +35,19 @@ type Conn struct {
nicks map[string]*Nick nicks map[string]*Nick
} }
// We'll parse an incoming line into this struct // We parse an incoming line into this struct. Line.Cmd is used as the trigger
// raw =~ ":nick!user@host cmd args[] :text" // name for incoming event handlers, see *Conn.recv() for details.
// src == "nick!user@host" // Raw =~ ":nick!user@host cmd args[] :text"
// Src == "nick!user@host"
// Cmd == e.g. PRIVMSG, 332
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 // Creates a new IRC connection object, but doesn't connect to anything so
// that you can add event handlers to it. See AddHandler() for details.
func New(nick, user, name string) *Conn { func New(nick, user, name string) *Conn {
conn := new(Conn) conn := new(Conn)
conn.initialise() conn.initialise()
@ -64,8 +68,10 @@ func (conn *Conn) initialise() {
conn.sock = nil conn.sock = nil
} }
// connect the IRC connection object to a host // Connect the IRC connection object to "host[:port]" which should be either
func (conn *Conn) Connect(host, pass string) os.Error { // a hostname or an IP address, with an optional port defaulting to 6667.
// You can also provide an optional connect password.
func (conn *Conn) Connect(host string, pass ...) 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))
} }
@ -87,8 +93,9 @@ func (conn *Conn) Connect(host, pass string) os.Error {
go conn.send() go conn.send()
go conn.recv() go conn.recv()
if pass != "" { // see getStringMsg() in commands.go for what this does
conn.Pass(pass) if p := getStringMsg(pass); p != "" {
conn.Pass(p)
} }
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)
@ -197,6 +204,8 @@ func (conn *Conn) shutdown() {
fmt.Println("irc.shutdown(): shut down sockets and channels!") fmt.Println("irc.shutdown(): shut down sockets and channels!")
} }
// Dumps a load of information about the current state of the connection to a
// string for debugging state tracking and other such things.
func (conn *Conn) String() string { func (conn *Conn) String() string {
str := "GoIRC Connection\n" str := "GoIRC Connection\n"
str += "----------------\n\n" str += "----------------\n\n"

View File

@ -8,7 +8,19 @@ import (
"strconv" "strconv"
) )
// Add an event handler for a specific IRC command // AddHandler() adds an event handler for a specific IRC command.
//
// Handlers take the form of an anonymous function (currently):
// func(conn *irc.Conn, line *irc.Line) {
// // handler code here
// }
//
// Handlers are triggered on incoming Lines from the server, with the handler
// "name" being equivalent to Line.Cmd. Read the RFCs for details on what
// replies could come from the server. They'll generally be things like
// "PRIVMSG", "JOIN", etc. but all the numeric replies are left as ascii
// strings of digits like "332" (mainly because I really didn't feel like
// putting massive constant tables in).
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 {

View File

@ -12,8 +12,8 @@ import (
type Channel struct { type Channel struct {
Name, Topic string Name, Topic string
Modes *ChanMode Modes *ChanMode
Nicks map[*Nick]*ChanPrivs Nicks map[*Nick]*ChanPrivs
conn *Conn conn *Conn
} }
// A struct representing an IRC nick // A struct representing an IRC nick
@ -25,21 +25,26 @@ type Nick struct {
} }
// A struct representing the modes of an IRC Channel // A struct representing the modes of an IRC Channel
// (the ones we care about, at least) // (the ones we care about, at least).
// see the MODE handler in setupEvents() for details //
// See the MODE handler in setupEvents() for details of how this is maintained.
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)
// (again, only the ones we care about) // (again, only the ones we care about)
//
// This is only really useful for conn.Me, as we can't see other people's modes // 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). // without IRC operator privileges (and even then only on some IRCd's).
type NickMode struct { type NickMode struct {
@ -56,6 +61,9 @@ type ChanPrivs struct {
/******************************************************************************\ /******************************************************************************\
* Conn methods to create/look up nicks/channels * Conn methods to create/look up nicks/channels
\******************************************************************************/ \******************************************************************************/
// Creates a new *irc.Nick, initialises it, and stores it in *irc.Conn so it
// can be properly tracked for state management purposes.
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()
@ -63,6 +71,7 @@ func (conn *Conn) NewNick(nick, ident, name, host string) *Nick {
return n return n
} }
// Returns an *irc.Nick for the nick n, if we're tracking it.
func (conn *Conn) GetNick(n string) *Nick { func (conn *Conn) GetNick(n string) *Nick {
if nick, ok := conn.nicks[n]; ok { if nick, ok := conn.nicks[n]; ok {
return nick return nick
@ -70,6 +79,8 @@ func (conn *Conn) GetNick(n string) *Nick {
return nil return nil
} }
// Creates a new *irc.Channel, initialises it, and stores it in *irc.Conn so it
// can be properly tracked for state management purposes.
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()
@ -77,6 +88,7 @@ func (conn *Conn) NewChannel(c string) *Channel {
return ch return ch
} }
// Returns an *irc.Channel for the channel c, if we're tracking it.
func (conn *Conn) GetChannel(c string) *Channel { func (conn *Conn) GetChannel(c string) *Channel {
if ch, ok := conn.chans[c]; ok { if ch, ok := conn.chans[c]; ok {
return ch return ch
@ -87,11 +99,13 @@ 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)
} }
// Associates an *irc.Nick with an *irc.Channel using a shared *irc.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)
@ -101,6 +115,9 @@ func (ch *Channel) AddNick(n *Nick) {
} }
} }
// Disassociates an *irc.Nick from an *irc.Channel. Will call ch.Delete() if
// the *irc.Nick being removed is the connection's nick. Will also call
// n.DelChannel(ch) to remove the association from the perspective of *irc.Nick.
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)
@ -116,6 +133,8 @@ func (ch *Channel) DelNick(n *Nick) {
// consistency, and this would mean spewing an error message every delete // consistency, and this would mean spewing an error message every delete
} }
// Stops the channel from being tracked by state tracking handlers. Also calls
// n.DelChannel(ch) for all nicks that are associated with the channel.
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 {
@ -132,7 +151,11 @@ func (n *Nick) initialise() {
n.Channels = make(map[*Channel]*ChanPrivs) n.Channels = make(map[*Channel]*ChanPrivs)
} }
// very slightly different to Channel.AddNick() ... // Associates an *irc.Channel with an *irc.Nick using a shared *irc.ChanPrivs
//
// Very slightly different to irc.Channel.AddNick() in that it tests for a
// pre-existing association within the *irc.Nick object rather than the
// *irc.Channel object before associating the two.
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)
@ -142,6 +165,9 @@ func (n *Nick) AddChannel(ch *Channel) {
} }
} }
// Disassociates an *irc.Channel from an *irc.Nick. Will call n.Delete() if
// the *irc.Nick is no longer on any channels we are tracking. Will also call
// ch.DelNick(n) to remove the association from the perspective of *irc.Channel.
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)
@ -154,12 +180,16 @@ func (n *Nick) DelChannel(ch *Channel) {
} }
} }
// Signals to the tracking code that the *irc.Nick object should be tracked
// under a "neu" nick rather than the old one.
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
} }
// Stops the nick from being tracked by state tracking handlers. Also calls
// ch.DelNick(n) for all nicks that are associated with the channel.
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 {
@ -174,6 +204,8 @@ 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.
\******************************************************************************/ \******************************************************************************/
// Map *irc.ChanMode fields to IRC mode characters
var ChanModeToString = map[string]string{ var ChanModeToString = map[string]string{
"Private": "p", "Private": "p",
"Secret": "s", "Secret": "s",
@ -186,6 +218,8 @@ var ChanModeToString = map[string]string{
"Key": "k", "Key": "k",
"Limit": "l", "Limit": "l",
} }
// Map *irc.NickMode fields to IRC mode characters
var NickModeToString = map[string]string{ var NickModeToString = map[string]string{
"Invisible": "i", "Invisible": "i",
"Oper": "o", "Oper": "o",
@ -193,6 +227,8 @@ var NickModeToString = map[string]string{
"HiddenHost": "x", "HiddenHost": "x",
"SSL": "z", "SSL": "z",
} }
// Map *irc.ChanPrivs fields to IRC mode characters
var ChanPrivToString = map[string]string{ var ChanPrivToString = map[string]string{
"Owner": "q", "Owner": "q",
"Admin": "a", "Admin": "a",
@ -200,6 +236,9 @@ var ChanPrivToString = map[string]string{
"HalfOp": "h", "HalfOp": "h",
"Voice": "v", "Voice": "v",
} }
// Map *irc.ChanPrivs fields to the symbols used to represent these modes
// in NAMES and WHOIS responses
var ChanPrivToModeChar = map[string]byte{ var ChanPrivToModeChar = map[string]byte{
"Owner": '~', "Owner": '~',
"Admin": '&', "Admin": '&',
@ -207,6 +246,8 @@ var ChanPrivToModeChar = map[string]byte{
"HalfOp": '%', "HalfOp": '%',
"Voice": '+', "Voice": '+',
} }
// Reverse mappings of the above datastructures
var StringToChanMode, StringToNickMode, StringToChanPriv map[string]string var StringToChanMode, StringToNickMode, StringToChanPriv map[string]string
var ModeCharToChanPriv map[byte]string var ModeCharToChanPriv map[byte]string
@ -230,6 +271,13 @@ func init() {
} }
} }
// Returns a string representing the channel. Looks like:
// Channel: <channel name> e.g. #moo
// Topic: <channel topic> e.g. Discussing the merits of cows!
// Mode: <channel modes> e.g. +nsti
// Nicks:
// <nick>: <privs> e.g. CowMaster: +o
// ...
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"
@ -241,6 +289,14 @@ func (ch *Channel) String() string {
return str return str
} }
// Returns a string representing the nick. Looks like:
// Nick: <nick name> e.g. CowMaster
// Hostmask: <ident@host> e.g. moo@cows.org
// Real Name: <real name> e.g. Steve "CowMaster" Bush
// Modes: <nick modes> e.g. +z
// Channels:
// <channel>: <privs> e.g. #moo: +o
// ...
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"
@ -253,6 +309,8 @@ func (n *Nick) String() string {
return str return str
} }
// Returns a string representing the channel modes. Looks like:
// +npk key
func (cm *ChanMode) String() string { func (cm *ChanMode) String() string {
str := "+" str := "+"
a := make([]string, 2) a := make([]string, 2)
@ -287,6 +345,8 @@ func (cm *ChanMode) String() string {
return str return str
} }
// Returns a string representing the nick modes. Looks like:
// +iwx
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)
@ -306,6 +366,8 @@ func (nm *NickMode) String() string {
return str return str
} }
// Returns a string representing the channel privileges. Looks like:
// +o
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)