Skip to content

Instantly share code, notes, and snippets.

@4poc
Last active August 29, 2015 14:03
Show Gist options
  • Save 4poc/5d90091db803f52b618d to your computer and use it in GitHub Desktop.
Save 4poc/5d90091db803f52b618d to your computer and use it in GitHub Desktop.
GO IRC Bot
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