mirror of https://github.com/fluffle/goirc
initial go IRC library, dirty hax abound :-)
This commit is contained in:
commit
545a88fea0
|
@ -0,0 +1,5 @@
|
||||||
|
/gobot
|
||||||
|
*.[568]
|
||||||
|
_obj/
|
||||||
|
*.swp
|
||||||
|
*~
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
# Use of this source code is governed by a BSD-style
|
||||||
|
# license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
include $(GOROOT)/src/Make.$(GOARCH)
|
||||||
|
|
||||||
|
TARG=gobot
|
||||||
|
GOFILES=\
|
||||||
|
client.go\
|
||||||
|
|
||||||
|
include $(GOROOT)/src/Make.cmd
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"./irc/_obj/irc";
|
||||||
|
"fmt";
|
||||||
|
"os";
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := irc.New("GoTest", "gotest", "GoBot");
|
||||||
|
c.AddHandler("connected",
|
||||||
|
func(conn *irc.IRCConn, line *irc.IRCLine) {
|
||||||
|
conn.Join("#");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
c.AddHandler("join",
|
||||||
|
func(conn *irc.IRCConn, line *irc.IRCLine) {
|
||||||
|
if line.Nick == conn.Me {
|
||||||
|
conn.Privmsg(line.Text, "I LIVE, BITCHES");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if err := c.Connect("irc.pl0rt.org", ""); err != nil {
|
||||||
|
fmt.Printf("Connection error: %v\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we get here, we're successfully connected and should have just
|
||||||
|
// dispatched the "CONNECTED" event to it's handlers \o/
|
||||||
|
control := make(chan os.Error, 1);
|
||||||
|
go c.RunLoop(control);
|
||||||
|
if err := <-control; err != nil {
|
||||||
|
fmt.Printf("IRCConn.RunLoop terminated: %v\n", err);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
# Use of this source code is governed by a BSD-style
|
||||||
|
# license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
include $(GOROOT)/src/Make.$(GOARCH)
|
||||||
|
|
||||||
|
TARG=irc
|
||||||
|
GOFILES=\
|
||||||
|
connection.go\
|
||||||
|
commands.go\
|
||||||
|
handlers.go\
|
||||||
|
|
||||||
|
include $(GOROOT)/src/Make.pkg
|
|
@ -0,0 +1,102 @@
|
||||||
|
package irc
|
||||||
|
|
||||||
|
// this file contains the various commands you can
|
||||||
|
// send to the server using an IRCConn connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt";
|
||||||
|
"reflect";
|
||||||
|
)
|
||||||
|
|
||||||
|
// This could be a lot less ugly with the ability to manipulate
|
||||||
|
// the symbol table and add methods/functions on the fly
|
||||||
|
// [ CMD, FMT, FMTARGS ] etc.
|
||||||
|
|
||||||
|
// send a PASS command to the server
|
||||||
|
func (conn *IRCConn) Pass(p string) {
|
||||||
|
conn.send(fmt.Sprintf("PASS %s", p));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a NICK command to the server
|
||||||
|
func (conn *IRCConn) Nick(n string) {
|
||||||
|
conn.send(fmt.Sprintf("NICK %s", n));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a USER command to the server
|
||||||
|
func (conn *IRCConn) User(u, n string) {
|
||||||
|
conn.send(fmt.Sprintf("USER %s 12 * :%s", u, n));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a JOIN command to the server
|
||||||
|
func (conn *IRCConn) Join(c string) {
|
||||||
|
conn.send(fmt.Sprintf("JOIN %s", c));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a PART command to the server
|
||||||
|
func (conn *IRCConn) Part(c string, a ...) {
|
||||||
|
msg := getStringMsg(a);
|
||||||
|
if msg != "" {
|
||||||
|
msg = " :" + msg
|
||||||
|
}
|
||||||
|
conn.send(fmt.Sprintf("PART %s%s", c, msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a QUIT command to the server
|
||||||
|
func (conn *IRCConn) Quit(a ...) {
|
||||||
|
msg := getStringMsg(a);
|
||||||
|
if msg == "" {
|
||||||
|
msg = "GoBye!"
|
||||||
|
}
|
||||||
|
conn.send(fmt.Sprintf("QUIT :%s", msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a PRIVMSG to the target t
|
||||||
|
func (conn *IRCConn) Privmsg(t, msg string) {
|
||||||
|
conn.send(fmt.Sprintf("PRIVMSG %s :%s", t, msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a NOTICE to the target t
|
||||||
|
func (conn *IRCConn) Notice(t, msg string) {
|
||||||
|
conn.send(fmt.Sprintf("NOTICE %s :%s", t, msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a (generic) CTCP to the target t
|
||||||
|
func (conn *IRCConn) Ctcp(t, ctcp string, a ...) {
|
||||||
|
msg := getStringMsg(a);
|
||||||
|
if msg != "" {
|
||||||
|
msg = " " + msg
|
||||||
|
}
|
||||||
|
conn.Privmsg(t, fmt.Sprintf("\001%s%s\001", ctcp, msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a generic CTCP reply to the target t
|
||||||
|
func (conn *IRCConn) CtcpReply(t, ctcp string, a ...) {
|
||||||
|
msg := getStringMsg(a);
|
||||||
|
if msg != "" {
|
||||||
|
msg = " " + msg
|
||||||
|
}
|
||||||
|
conn.Notice(t, fmt.Sprintf("\001%s%s\001", ctcp, msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// send a CTCP "VERSION" to the target t
|
||||||
|
func (conn *IRCConn) Version(t string) {
|
||||||
|
conn.Ctcp(t, "VERSION");
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a CTCP "ACTION" to the target t -- /me does stuff!
|
||||||
|
func (conn *IRCConn) Action(t, msg string) {
|
||||||
|
conn.Ctcp(t, "ACTION", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStringMsg(a ...) (msg string) {
|
||||||
|
// dealing with functions with a variable parameter list is nasteeh :-(
|
||||||
|
// the below stolen and munged from fmt/print.go
|
||||||
|
if v := reflect.NewValue(a).(*reflect.StructValue); v.NumField() == 1 {
|
||||||
|
// XXX: should we check that this looks at least vaguely stringy first?
|
||||||
|
msg = fmt.Sprintf("%v", v.Field(1));
|
||||||
|
} else {
|
||||||
|
msg = ""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
// Some IRC testing code!
|
||||||
|
|
||||||
|
package irc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio";
|
||||||
|
"os";
|
||||||
|
"net";
|
||||||
|
"fmt";
|
||||||
|
"strings";
|
||||||
|
)
|
||||||
|
|
||||||
|
// the IRC connection object
|
||||||
|
type IRCConn struct {
|
||||||
|
sock *bufio.ReadWriter;
|
||||||
|
Host string;
|
||||||
|
Me string;
|
||||||
|
Ident string;
|
||||||
|
Name string;
|
||||||
|
con bool;
|
||||||
|
reg bool;
|
||||||
|
events map[string] []func (*IRCConn, *IRCLine);
|
||||||
|
chans map[string] *IRCChan;
|
||||||
|
nicks map[string] *IRCNick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll parse an incoming line into this struct
|
||||||
|
// raw =~ ":nick!user@host cmd args[] :text"
|
||||||
|
// src == "nick!user@host"
|
||||||
|
type IRCLine struct {
|
||||||
|
Nick string;
|
||||||
|
User string;
|
||||||
|
Host string;
|
||||||
|
Src string;
|
||||||
|
Cmd 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
|
||||||
|
func New(nick, user, name string) (conn *IRCConn) {
|
||||||
|
conn = &IRCConn{Me: nick, Ident: user, Name: name};
|
||||||
|
// allocate meh some memoraaaahh
|
||||||
|
conn.nicks = make(map[string] *IRCNick);
|
||||||
|
conn.chans = make(map[string] *IRCChan);
|
||||||
|
conn.events = make(map[string] []func(*IRCConn, *IRCLine));
|
||||||
|
conn.setupEvents();
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect the IRC connection object to a host
|
||||||
|
func (conn *IRCConn) Connect(host, pass string) (err os.Error) {
|
||||||
|
if !hasPort(host) {
|
||||||
|
host += ":6667";
|
||||||
|
}
|
||||||
|
sock, err := net.Dial("tcp", "", host);
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn.sock = bufio.NewReadWriter(bufio.NewReader(sock), bufio.NewWriter(sock));
|
||||||
|
conn.con = true;
|
||||||
|
conn.Host = host;
|
||||||
|
|
||||||
|
// initial connection set-up
|
||||||
|
// verify valid nick/user/name here?
|
||||||
|
if pass != "" {
|
||||||
|
conn.Pass(pass)
|
||||||
|
}
|
||||||
|
conn.Nick(conn.Me);
|
||||||
|
conn.User(conn.Ident, conn.Name);
|
||||||
|
|
||||||
|
for line, err := conn.recv(); err == nil; line, err = conn.recv() {
|
||||||
|
// initial loop to get us to the point where we're connected
|
||||||
|
conn.dispatchEvent(line);
|
||||||
|
if line.Cmd == "001" {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *IRCConn) RunLoop(c chan os.Error) {
|
||||||
|
var err os.Error;
|
||||||
|
for line, err := conn.recv(); err == nil; line, err = conn.recv() {
|
||||||
|
conn.dispatchEvent(line);
|
||||||
|
}
|
||||||
|
c <- err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from http.client for great justice
|
||||||
|
func hasPort(s string) bool {
|
||||||
|
return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
|
||||||
|
}
|
||||||
|
|
||||||
|
// send \r\n terminated line to peer, propagate errors
|
||||||
|
func (conn *IRCConn) send(line string) (err os.Error) {
|
||||||
|
err = conn.sock.WriteString(line + "\r\n");
|
||||||
|
conn.sock.Flush();
|
||||||
|
fmt.Println("-> " + line);
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive one \r\n terminated line from peer and parse it, propagate errors
|
||||||
|
func (conn *IRCConn) recv() (line *IRCLine, err os.Error) {
|
||||||
|
s, err := conn.sock.ReadString('\n');
|
||||||
|
if err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
// chop off \r\n
|
||||||
|
s = s[0:len(s)-2];
|
||||||
|
fmt.Println("<- " + s);
|
||||||
|
|
||||||
|
line = &IRCLine{Raw: s};
|
||||||
|
if s[0] == ':' {
|
||||||
|
// remove a source and parse it
|
||||||
|
if idx := strings.Index(s, " "); idx != -1 {
|
||||||
|
line.Src, s = s[1:idx], s[idx+1:len(s)];
|
||||||
|
} else {
|
||||||
|
// pretty sure we shouldn't get here ...
|
||||||
|
line.Src = s[1:len(s)];
|
||||||
|
return line, nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.User = line.Src[nidx+1:uidx];
|
||||||
|
line.Host = line.Src[uidx+1:len(line.Src)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we're here, we've parsed a :nick!user@host or :server off
|
||||||
|
// s should contain "cmd args[] :text"
|
||||||
|
args := strings.Split(s, " :", 2);
|
||||||
|
if len(args) > 1 {
|
||||||
|
line.Text = args[1];
|
||||||
|
}
|
||||||
|
args = strings.Split(args[0], " ", 0);
|
||||||
|
line.Cmd = strings.ToUpper(args[0]);
|
||||||
|
if len(args) > 1 {
|
||||||
|
line.Args = args[1:len(args)];
|
||||||
|
}
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package irc
|
||||||
|
|
||||||
|
// this file contains the basic set of event handlers
|
||||||
|
// to manage tracking an irc connection etc.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt";
|
||||||
|
"strings";
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add an event handler for a specific IRC command
|
||||||
|
func (conn *IRCConn) AddHandler(name string, f func (*IRCConn, *IRCLine)) {
|
||||||
|
n := strings.ToUpper(name);
|
||||||
|
if e, ok := conn.events[n]; ok {
|
||||||
|
if len(e) == cap(e) {
|
||||||
|
// crap, we're full. expand e by another 10 handler slots
|
||||||
|
ne := make([]func (*IRCConn, *IRCLine), len(e), len(e)+10);
|
||||||
|
for i := 0; i<len(e); i++ {
|
||||||
|
ne[i] = e[i];
|
||||||
|
}
|
||||||
|
e = ne;
|
||||||
|
}
|
||||||
|
e = e[0:len(e)+1];
|
||||||
|
e[len(e)-1] = f;
|
||||||
|
} else {
|
||||||
|
e := make([]func (*IRCConn, *IRCLine), 1, 10);
|
||||||
|
e[0] = f;
|
||||||
|
conn.events[n] = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loops through all event handlers for line.Cmd, running each in a goroutine
|
||||||
|
func (conn *IRCConn) dispatchEvent(line *IRCLine) {
|
||||||
|
// So, I think CTCP and (in particular) CTCP ACTION are better handled as
|
||||||
|
// separate events as opposed to forcing people to have gargantuan PRIVMSG
|
||||||
|
// handlers to cope with the possibilities.
|
||||||
|
if line.Cmd == "PRIVMSG" && len(line.Text) > 2
|
||||||
|
&& line.Text[0] == '\001' && line.Text[len(line.Text)-1] == '\001' {
|
||||||
|
// WOO, it's a CTCP message
|
||||||
|
t := strings.Split(line.Text[1:len(line.Text)-1], " ", 2);
|
||||||
|
if c := strings.ToUpper(t[0]); c == "ACTION" {
|
||||||
|
// make a CTCP ACTION it's own event a-la PRIVMSG
|
||||||
|
line.Cmd = c;
|
||||||
|
} else {
|
||||||
|
// otherwise, dispatch a generic CTCP event that
|
||||||
|
// contains the type of CTCP in line.Args[0]
|
||||||
|
line.Cmd = "CTCP";
|
||||||
|
a := make([]string, len(line.Args)+1);
|
||||||
|
a[0] = c;
|
||||||
|
for i:=0; i<len(line.Args); i++ {
|
||||||
|
a[i+1] = line.Args[i];
|
||||||
|
}
|
||||||
|
line.Args = a;
|
||||||
|
}
|
||||||
|
if len(t) > 1 {
|
||||||
|
// for some CTCP messages this could make more sense
|
||||||
|
// in line.Args[], but meh. MEH, I say.
|
||||||
|
line.Text = t[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if funcs, ok := conn.events[line.Cmd]; ok {
|
||||||
|
for _, f := range funcs {
|
||||||
|
go f(conn, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets up the internal event handlers to do useful things with lines
|
||||||
|
// XXX: is there a better way of doing this?
|
||||||
|
func (conn *IRCConn) setupEvents() {
|
||||||
|
// Basic ping/pong handler
|
||||||
|
conn.AddHandler("PING", func(conn *IRCConn, line *IRCLine) {
|
||||||
|
conn.send(fmt.Sprintf("PONG :%s", line.Text));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handler to trigger a "CONNECTED" event on receipt of numeric 001
|
||||||
|
conn.AddHandler("001", func(conn *IRCConn, line *IRCLine) {
|
||||||
|
l := new(IRCLine);
|
||||||
|
l.Cmd = "CONNECTED";
|
||||||
|
conn.dispatchEvent(l);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handler to deal with "433 :Nickname already in use" on connection
|
||||||
|
conn.AddHandler("433", func(conn *IRCConn, line *IRCLine) {
|
||||||
|
conn.Nick(conn.Me + "_");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue