Last active
August 29, 2015 14:03
-
-
Save 4poc/5d90091db803f52b618d to your computer and use it in GitHub Desktop.
GO IRC Bot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"crypto/tls" | |
"fmt" | |
"io" | |
"net" | |
"reflect" | |
"regexp" | |
"strconv" | |
"strings" | |
) | |
var config = map[string]interface{}{ | |
"host": "chat.freenode.net", | |
"port": 7070, | |
"ssl": true, | |
"nick": "gobot8-", | |
"username": "apoc", | |
"realname": "GO IRC Bot", | |
"prefix": "!", | |
"autojoin": "##test", | |
} | |
type Bot struct { | |
wc chan<- string | |
} | |
func (bot *Bot) ActionGo(m *PrivMessage) { | |
if !m.query { | |
bot.Say(m.to, "GO!") | |
} else { | |
bot.Say(m.from.nick, "GO!") | |
} | |
} | |
func (bot *Bot) ActionQuit(m *PrivMessage) { | |
bot.Quit() | |
} | |
func (bot *Bot) Pong(param string) { | |
bot.wc <- fmt.Sprintf("PONG :%s\r\n", param) | |
} | |
func (bot *Bot) Join(channel string) { | |
bot.wc <- fmt.Sprintf("JOIN %s\r\n", channel) | |
} | |
func (bot *Bot) Say(dest, message string) { | |
bot.wc <- fmt.Sprintf("PRIVMSG %s %s\r\n", dest, message) | |
} | |
func (bot *Bot) Quit() { | |
bot.wc <- fmt.Sprintf("QUIT :\r\n") | |
} | |
func (bot *Bot) RecvPrivmsg(m *PrivMessage) { | |
//fmt.Printf("Privmsg Received: from:%s to:%s message:'%s'\n", m.from.nick, m.to, m.message) | |
// dynamic dispatch to Action<ACTION> methods from messages like !quit | |
if m.addressed { | |
name := "Action" + strings.ToUpper(string(m.action[0])) + strings.ToLower(m.action[1:]) | |
fmt.Printf("[PrivmsgDispatch] %s\n", name) | |
method := reflect.ValueOf(bot).MethodByName(name) | |
if method.IsValid() { | |
method.Call([]reflect.Value{reflect.ValueOf(m)}) | |
} | |
} | |
} | |
func (bot *Bot) RawRecv376(*RawMessage) { | |
bot.Join(config["autojoin"].(string)) | |
} | |
func (bot *Bot) RawRecv422(*RawMessage) { | |
bot.Join(config["autojoin"].(string)) | |
} | |
func (bot *Bot) RawRecvPing(raw *RawMessage) { | |
bot.Pong(raw.params[0]) | |
} | |
func (bot *Bot) RawRecvPrivmsg(raw *RawMessage) { | |
bot.RecvPrivmsg(ParsePrivMessage(raw)) | |
} | |
func (bot *Bot) Dispatch(raw *RawMessage) { | |
// dynamically dispatches irc commands received to bot methods prefixed with Recv | |
fmt.Printf("Prefix:%s [Command::%s] Params:%s\n", raw.prefix, raw.command, strings.Join(raw.params, ", ")) | |
name := "" | |
if _, err := strconv.Atoi(raw.command); err == nil { | |
name = "RawRecv" + raw.command | |
} else { | |
name = "RawRecv" + string(raw.command[0]) + strings.ToLower(raw.command[1:]) | |
} | |
method := reflect.ValueOf(bot).MethodByName(name) | |
if method.IsValid() { | |
method.Call([]reflect.Value{reflect.ValueOf(raw)}) | |
} | |
} | |
type Hostmask struct { | |
nick string | |
user string | |
host string | |
} | |
func ParseHostmask(raw string) *Hostmask { | |
hostmask := new(Hostmask) | |
// apoc!~apoc@april-fools/2014/ninth/apoc [PRIVMSG] [##test foo] | |
if i := strings.Index(raw, "!"); i != -1 { | |
hostmask.nick = raw[0:i] | |
} | |
userLastIndex := 0 | |
if i := strings.Index(raw, "@"); i != -1 { | |
hostmask.user = raw[len(hostmask.nick)+1 : i] | |
userLastIndex = i + 1 | |
} | |
hostmask.host = raw[userLastIndex:] | |
//fmt.Printf("hostmask: %s\n", raw) | |
//fmt.Printf("nick: '%s'\n", hostmask.nick) | |
//fmt.Printf("user: '%s'\n", hostmask.user) | |
//fmt.Printf("host: '%s'\n", hostmask.host) | |
return hostmask | |
} | |
type PrivMessage struct { | |
from *Hostmask | |
to string | |
message string | |
query bool | |
addressed bool | |
action string | |
params []string | |
} | |
func ParsePrivMessage(raw *RawMessage) *PrivMessage { | |
msg := new(PrivMessage) | |
msg.from = ParseHostmask(raw.prefix) | |
msg.to = raw.params[0] | |
msg.message = raw.params[1] | |
if msg.to == config["nick"] { | |
msg.query = true | |
msg.addressed = true | |
} | |
actionStartIndex := 0 | |
if strings.Index(msg.message, config["prefix"].(string)) == 0 { | |
msg.addressed = true | |
actionStartIndex = len(config["prefix"].(string)) | |
} | |
//fmt.Printf("actionStartIndex = %d\n", actionStartIndex) | |
if strings.Index(msg.message, config["nick"].(string)+": ") == 0 || | |
strings.Index(msg.message, config["nick"].(string)+", ") == 0 { | |
msg.addressed = true | |
actionStartIndex = len(config["nick"].(string)) + 2 | |
} | |
if msg.addressed { | |
msg.action = msg.message[actionStartIndex:] | |
if i := strings.Index(msg.action, " "); i != -1 { | |
msg.params = strings.Split(msg.action[i+1:], " ") | |
msg.action = msg.action[0:i] | |
} | |
} | |
//fmt.Printf("msg.action = '%s'\n", msg.action) | |
//fmt.Printf("msg.params = '%s'\n", strings.Join(msg.params, "', '")) | |
return msg | |
} | |
type RawMessage struct { | |
prefix string | |
command string | |
params []string | |
} | |
func ParseRawMessage(raw string) *RawMessage { | |
raw = strings.Trim(raw, "\r\n") | |
//fmt.Printf("[raw] %s\n", raw) | |
tokens := []string{} | |
if strings.Index(raw, ":") == 0 { | |
raw = raw[1:] | |
} else { | |
tokens = append(tokens, "") | |
} | |
re := regexp.MustCompile(`(?::(.*$)|(\S+))(.*)$`) | |
for { | |
m := re.FindStringSubmatch(raw) | |
if m == nil || len(m) != 4 { | |
break | |
} | |
tokens = append(tokens, m[1]+m[2]) | |
raw = m[3] | |
} | |
//fmt.Println("'" + strings.Join(tokens, "', '") + "'") | |
return &RawMessage{tokens[0], tokens[1], tokens[2:]} | |
//msg := &RawMessage{tokens[0], tokens[1], tokens[2:]} | |
//fmt.Printf("Prefix:%s [Command::%s] Params:%s\n", msg.prefix, msg.command, strings.Join(msg.params, ", ")) | |
//return msg | |
} | |
func client(wc chan<- string) chan<- string { | |
rc := make(chan string, 32) | |
// write nick/user just after connecting | |
wc <- fmt.Sprintf("NICK %s\r\n", config["nick"]) | |
wc <- fmt.Sprintf("USER %s 0 * :%s\r\n", config["username"], config["realname"]) | |
bot := Bot{wc} | |
// this loop reads raw irc messages and processes each one concurrently | |
go func() { | |
for { | |
go bot.Dispatch(ParseRawMessage(<-rc)) | |
} | |
}() | |
return rc | |
} | |
func main() { | |
connstr := fmt.Sprintf("%s:%d", config["host"], config["port"]) | |
fmt.Printf("Connect to IRC server at %s (ssl: %v)\n", connstr, config["ssl"].(bool)) | |
var conn io.ReadWriter | |
if config["ssl"].(bool) { | |
config := tls.Config{InsecureSkipVerify: true} | |
tlsconn, err := tls.Dial("tcp", connstr, &config) | |
if err != nil { | |
fmt.Println("Unable to connect: " + err.Error()) | |
return | |
} | |
/* | |
state := tlsconn.ConnectionState() | |
for _, v := range state.PeerCertificates { | |
fmt.Println(x509.MarshalPKIXPublicKey(v.PublicKey)) | |
fmt.Println(v.Subject) | |
} | |
log.Println("client: handshake: ", state.HandshakeComplete) | |
log.Println("client: mutual: ", state.NegotiatedProtocolIsMutual) | |
*/ | |
conn = tlsconn | |
} else { | |
tcpconn, err := net.Dial("tcp", connstr) | |
if err != nil { | |
fmt.Println("Unable to connect: " + err.Error()) | |
return | |
} | |
conn = tcpconn | |
} | |
// create buffered readers and writers (also gives us string based read/write functions) | |
reader := bufio.NewReader(conn) // ReadString("\r\n") read to newline | |
writer := bufio.NewWriter(conn) // WriteString("...\r\n") | |
// NOTE: i think i could also just use the fmt fprint/fscan functions | |
// writer channel-loop | |
wc := make(chan string, 32) | |
go func() { | |
for { | |
writer.WriteString(<-wc) | |
writer.Flush() | |
} | |
}() | |
// reader channel-loop | |
rc := client(wc) // channel is a generator function | |
for { | |
if raw, err := reader.ReadString('\n'); err != io.EOF { | |
rc <- raw | |
} else { | |
break | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment