From 4b9b8fa682441da7e8654c38de21dba2382e3b13 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 17 Aug 2012 16:38:15 -0400 Subject: [PATCH] Initial revision of the godeepintir bot. This is a revised version of deepintir, a python bot which used ii to connect. This version houses its own IRC connection and therefore is most useful with a bouncer such as ZNC. This initial version does not exhibit much functionality. It can only show off how plugins might be written in later versions and currently mimics anything said to it. There may be bugs, and it will most certainly be altered in signifigant ways before the API is stable. --- .hgignore | 1 + bot/bot.go | 41 +++++++++++++++++++++++++++ bot/handlers.go | 61 ++++++++++++++++++++++++++++++++++++++++ config.json | 62 ++++++++++++++++++++++++++++++++++++++++ config/config.go | 31 ++++++++++++++++++++ main.go | 58 ++++++++++++++++++++++++++++++++++++++ plugins/plugins.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 324 insertions(+) create mode 100644 .hgignore create mode 100644 bot/bot.go create mode 100644 bot/handlers.go create mode 100644 config.json create mode 100644 config/config.go create mode 100644 main.go create mode 100644 plugins/plugins.go diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..9b83119 --- /dev/null +++ b/.hgignore @@ -0,0 +1 @@ +godeepintir diff --git a/bot/bot.go b/bot/bot.go new file mode 100644 index 0000000..5ae2bd8 --- /dev/null +++ b/bot/bot.go @@ -0,0 +1,41 @@ +package bot + +import irc "github.com/fluffle/goirc/client" + +// Bot type provides storage for bot-wide information, configs, and database connections +type Bot struct { + Plugins []Handler + Users []User + Conn *irc.Conn + // mongodb connection will go here +} + +// User type stores user history. This is a vehicle that will follow the user for the active +// session +type User struct { + // Current nickname known + Name string + + // LastSeen DateTime + + // Alternative nicknames seen + Alts []string + + // Last N messages sent to the user + MessageLog []string +} + +// NewBot creates a Bot for a given connection and set of handlers. The handlers must not +// require the bot as input for their creation (so use AddHandler instead to add handlers) +func NewBot(c *irc.Conn, p ...Handler) *Bot { + return &Bot{ + Plugins: p, + Users: make([]User, 10), + Conn: c, + } +} + +// Adds a constructed handler to the bots handlers list +func (b *Bot) AddHandler(h Handler) { + b.Plugins = append(b.Plugins, h) +} \ No newline at end of file diff --git a/bot/handlers.go b/bot/handlers.go new file mode 100644 index 0000000..7d9df30 --- /dev/null +++ b/bot/handlers.go @@ -0,0 +1,61 @@ +package bot + +import ( + "fmt" +) +import irc "github.com/fluffle/goirc/client" + +// Interface used for compatibility with the Plugin interface +type Handler interface { + Message(user *User, channel, message string) bool +} + +// Checks to see if our user exists and if any changes have occured to it +// This uses a linear scan for now, oh well. +func (b *Bot) checkuser(nick string) *User { + var user *User = nil + for _, usr := range b.Users { + if usr.Name == nick { + user = &usr + } + } + if user == nil { + fmt.Println("Making a new user") + user = &User{ + Name: nick, + Alts: make([]string, 1), + MessageLog: make([]string, 50), + } + b.Users = append(b.Users, *user) + } + + return user +} + +// Handles incomming PRIVMSG requests +func (b *Bot) Msg_recieved(conn *irc.Conn, line *irc.Line) { + // Check for the user + user := b.checkuser(line.Nick) + + channel := line.Args[0] + message := line.Args[1] + + user.MessageLog = append(user.MessageLog, message) + + fmt.Printf("In %s, %s said: '%s'\n", channel, line.Nick, message) + for _, p := range b.Plugins { + if p.Message(user, channel, message) { + break + } + } +} + +// Take an input string and mutate it based on $vars in the string +func (b *Bot) filter(input string) string { + return input +} + +// Sends message to channel +func (b *Bot) SendMessage(channel, message string) { + b.Conn.Privmsg(channel, message) +} \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..a433788 --- /dev/null +++ b/config.json @@ -0,0 +1,62 @@ +{ + "Dbname": "deepintir", + "Dbserver": "127.0.0.1", + "Channels": ["#AlePaleTest"], + "Plugins": [], + "Server": "127.0.0.1:6666", + "Nick": "AlePaleTest", + "Pass": "AlePaleTest:test", + + "comment": "Follows is the old bot", + + "bot": { + "dbname": "deepintir", + "inchan": "out", + "outchan": "in", + "logchan": "logs", + "nick": "|AlePale|", + "channel": "#flagonslayers", + "command_char": "!", + "plugins": { + "help": "Help", + "talker": "Talker", + "remember": "Remember", + "beers": "Beers", + "first": "First", + "sudo": "Sudo" + }, + "_plugins": { + "tama": "Tama" + }, + "datastore": "datastore.dat" + }, + "Talker": { + "names": ["fredfelps", "felps"], + "slogan": "GOD HATES %NICK%" + }, + "Remember": { + "quotefile": "quotes.txt", + "loglength": 50, + "quotechance": 0.10, + "quotetimer": 1800 + }, + "Beers": { + "responses": [ + "Stay thirsty, my friend!", + "HIC!", + "ZIGGY! ZAGGY!" + ], + "countfile": "beers.txt", + "drinktriggers": [ + "drink", + "imbibe" + ], + "puketriggers": [ + "puke", + "yack" + ] + }, + "Sudo": { + "chance": 0.01 + } +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..f1732c3 --- /dev/null +++ b/config/config.go @@ -0,0 +1,31 @@ +package config + +import "encoding/json" +import "fmt" +import "io/ioutil" + +// Config stores any system-wide startup information that cannot be easily configured via +// the database +type Config struct { + Dbname string + Dbserver string + Channels []string + Plugins []string + Nick, Server, Pass string + } + + // Readconfig loads the config data out of a JSON file located in cfile +func Readconfig(cfile string) *Config { + fmt.Printf("Using %s as config file.\n", cfile) + file, e := ioutil.ReadFile(cfile) + if e != nil { + panic("Couldn't read config file!") + } + + var c Config + err := json.Unmarshal(file, &c) + if err != nil { + panic(err) + } + return &c +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..55839df --- /dev/null +++ b/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "flag" + "fmt" + "godeepintir/config" + "godeepintir/bot" + "godeepintir/plugins" +) +import irc "github.com/fluffle/goirc/client" + +func main() { + + // These belong in the JSON file + // var server = flag.String("server", "irc.freenode.net", "Server to connect to.") + // var nick = flag.String("nick", "CrappyBot", "Nick to set upon connection.") + // var pass = flag.String("pass", "", "IRC server password to use") + // var channel = flag.String("channel", "#AlePaleTest", "Channel to connet to.") + + var cfile = flag.String("config", "config.json", "Config file to load. (Defaults to config.json)") + flag.Parse() // parses the logging flags. + + config := config.Readconfig(*cfile) + fmt.Println(config) + + c := irc.SimpleClient(config.Nick) + // Optionally, enable SSL + c.SSL = false + + // Add handlers to do things here! + // e.g. join a channel on connect. + c.AddHandler("connected", + func(conn *irc.Conn, line *irc.Line) { + for _, channel := range config.Channels { + conn.Join(channel) + } + }) + // And a signal on disconnect + quit := make(chan bool) + + c.AddHandler("disconnected", + func(conn *irc.Conn, line *irc.Line) { quit <- true }) + + b := bot.NewBot(c) + b.AddHandler(plugins.NewTestPlugin(b)) + + c.AddHandler("PRIVMSG", func(conn *irc.Conn, line *irc.Line) { + b.Msg_recieved(conn, line) + }) + + // Tell client to connect + if err := c.Connect(config.Server, config.Pass); err != nil { + fmt.Printf("Connection error: %s\n", err) + } + + // Wait for disconnect + <-quit +} diff --git a/plugins/plugins.go b/plugins/plugins.go new file mode 100644 index 0000000..3a6ca4e --- /dev/null +++ b/plugins/plugins.go @@ -0,0 +1,70 @@ +package plugins + +import "fmt" +import "godeepintir/bot" + +// Plugin interface defines the methods needed to accept a plugin +type Plugin interface { + Message(user *bot.User, channel, message string) bool + LoadData() +} + +// ---- Below are some example plugins + +// Creates a new TestPlugin with the Plugin interface +func NewTestPlugin(bot *bot.Bot) *TestPlugin { + tp := TestPlugin{} + tp.LoadData() + tp.Bot = bot + return &tp +} + +// TestPlugin type allows our plugin to store persistent state information +type TestPlugin struct { + Bot *bot.Bot + Responds []string + Name string + Feces string +} + +func (p *TestPlugin) LoadData() { + config := GetPluginConfig("TestPlugin") + p.Name = config.Name + p.Feces = config.Values["Feces"].(string) +} + +func (p *TestPlugin) Message(user *bot.User, channel, message string) bool { + fmt.Println(user, message) + fmt.Println("My plugin name is:", p.Name, " My feces are:", p.Feces) + p.Bot.SendMessage(channel, message) + return true +} + +type PluginConfig struct { + Name string + Values map[string]interface{} +} + +// Loads plugin config out of the DB +// Stored in db.plugins.find("name": name) +func GetPluginConfig(name string) PluginConfig { + return PluginConfig{ + Name: "TestPlugin", + Values: map[string]interface{}{ + "Feces": "test", + "Responds": "fucker", + }, + } +} + +// FalsePlugin shows how plugin fallthrough works for handling messages +type FalsePlugin struct{} + +func (fp FalsePlugin) Message(user, message string) bool { + fmt.Println("FalsePlugin returning false.") + return false +} + +func (fp FalsePlugin) LoadData() { + +}