2012-08-17 20:38:15 +00:00
|
|
|
package bot
|
|
|
|
|
2012-10-11 20:28:00 +00:00
|
|
|
import (
|
2013-06-02 01:59:55 +00:00
|
|
|
"code.google.com/p/velour/irc"
|
2013-05-06 04:29:13 +00:00
|
|
|
"github.com/chrissexton/alepale/config"
|
2013-06-01 17:39:17 +00:00
|
|
|
"html/template"
|
2012-10-11 20:28:00 +00:00
|
|
|
"labix.org/v2/mgo"
|
2013-06-01 17:39:17 +00:00
|
|
|
"log"
|
2013-06-01 17:10:15 +00:00
|
|
|
"net/http"
|
2012-10-11 20:28:00 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
2012-08-17 20:38:15 +00:00
|
|
|
|
2013-06-02 01:59:55 +00:00
|
|
|
const actionPrefix = "\x01ACTION"
|
|
|
|
|
2012-08-17 20:38:15 +00:00
|
|
|
// Bot type provides storage for bot-wide information, configs, and database connections
|
|
|
|
type Bot struct {
|
2012-08-17 22:09:29 +00:00
|
|
|
// Each plugin must be registered in our plugins handler. To come: a map so that this
|
|
|
|
// will allow plugins to respond to specific kinds of events
|
2012-08-26 16:15:26 +00:00
|
|
|
Plugins map[string]Handler
|
|
|
|
PluginOrdering []string
|
2012-08-17 22:09:29 +00:00
|
|
|
|
|
|
|
// Users holds information about all of our friends
|
|
|
|
Users []User
|
2013-05-08 00:08:18 +00:00
|
|
|
// Represents the bot
|
|
|
|
Me User
|
2012-08-17 22:09:29 +00:00
|
|
|
|
|
|
|
// Conn allows us to send messages and modify our connection state
|
2013-06-02 01:59:55 +00:00
|
|
|
Client *irc.Client
|
2012-08-17 22:09:29 +00:00
|
|
|
|
|
|
|
Config *config.Config
|
|
|
|
|
|
|
|
// Mongo connection and db allow botwide access to the database
|
2012-08-17 21:37:49 +00:00
|
|
|
DbSession *mgo.Session
|
|
|
|
Db *mgo.Database
|
2012-08-17 22:56:44 +00:00
|
|
|
|
2012-08-26 23:23:51 +00:00
|
|
|
varColl *mgo.Collection
|
|
|
|
|
2012-10-11 20:28:00 +00:00
|
|
|
logIn chan Message
|
|
|
|
logOut chan Messages
|
|
|
|
|
2012-08-17 22:56:44 +00:00
|
|
|
Version string
|
2013-06-01 17:10:15 +00:00
|
|
|
|
|
|
|
// The entries to the bot's HTTP interface
|
2013-06-01 17:39:17 +00:00
|
|
|
httpEndPoints map[string]string
|
2012-08-17 20:38:15 +00:00
|
|
|
}
|
|
|
|
|
2012-10-11 20:28:00 +00:00
|
|
|
// Log provides a slice of messages in order
|
|
|
|
type Log Messages
|
|
|
|
type Messages []Message
|
|
|
|
|
|
|
|
type Logger struct {
|
|
|
|
in <-chan Message
|
|
|
|
out chan<- Messages
|
|
|
|
entries Messages
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewLogger(in chan Message, out chan Messages) *Logger {
|
|
|
|
return &Logger{in, out, make(Messages, 0)}
|
|
|
|
}
|
|
|
|
|
|
|
|
func RunNewLogger(in chan Message, out chan Messages) {
|
|
|
|
logger := NewLogger(in, out)
|
|
|
|
go logger.Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Logger) sendEntries() {
|
|
|
|
l.out <- l.entries
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Logger) Run() {
|
|
|
|
var msg Message
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case msg = <-l.in:
|
|
|
|
l.entries = append(l.entries, msg)
|
|
|
|
case l.out <- l.entries:
|
|
|
|
go l.sendEntries()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-17 22:09:29 +00:00
|
|
|
type Message struct {
|
|
|
|
User *User
|
|
|
|
Channel, Body string
|
2012-08-25 04:49:48 +00:00
|
|
|
Raw string
|
2012-08-18 01:39:26 +00:00
|
|
|
Command bool
|
2012-08-25 04:49:48 +00:00
|
|
|
Action bool
|
2012-10-11 20:28:00 +00:00
|
|
|
Time time.Time
|
2013-04-21 20:49:00 +00:00
|
|
|
Host string
|
2012-08-17 22:09:29 +00:00
|
|
|
}
|
|
|
|
|
2012-08-26 23:23:51 +00:00
|
|
|
type Variable struct {
|
|
|
|
Variable, Value string
|
|
|
|
}
|
|
|
|
|
2012-08-17 22:56:44 +00:00
|
|
|
// NewBot creates a Bot for a given connection and set of handlers.
|
2013-06-02 01:59:55 +00:00
|
|
|
func NewBot(config *config.Config, c *irc.Client) *Bot {
|
2012-08-17 21:37:49 +00:00
|
|
|
session, err := mgo.Dial(config.DbServer)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
db := session.DB(config.DbName)
|
|
|
|
|
2012-10-11 20:28:00 +00:00
|
|
|
logIn := make(chan Message)
|
|
|
|
logOut := make(chan Messages)
|
|
|
|
|
|
|
|
RunNewLogger(logIn, logOut)
|
|
|
|
|
2013-05-07 23:05:40 +00:00
|
|
|
users := []User{
|
|
|
|
User{
|
2013-06-02 01:59:55 +00:00
|
|
|
Name: config.Nick,
|
2013-05-07 23:05:40 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:10:15 +00:00
|
|
|
bot := &Bot{
|
2012-08-26 16:15:26 +00:00
|
|
|
Config: config,
|
|
|
|
Plugins: make(map[string]Handler),
|
|
|
|
PluginOrdering: make([]string, 0),
|
2013-05-07 23:05:40 +00:00
|
|
|
Users: users,
|
2013-05-08 00:08:18 +00:00
|
|
|
Me: users[0],
|
2013-06-02 01:59:55 +00:00
|
|
|
Client: c,
|
2012-08-26 16:15:26 +00:00
|
|
|
DbSession: session,
|
|
|
|
Db: db,
|
2012-08-26 23:23:51 +00:00
|
|
|
varColl: db.C("variables"),
|
2012-10-11 20:28:00 +00:00
|
|
|
logIn: logIn,
|
|
|
|
logOut: logOut,
|
2012-08-26 16:15:26 +00:00
|
|
|
Version: config.Version,
|
2013-06-01 17:39:17 +00:00
|
|
|
httpEndPoints: make(map[string]string),
|
2012-08-17 20:38:15 +00:00
|
|
|
}
|
2013-06-01 17:10:15 +00:00
|
|
|
|
|
|
|
http.HandleFunc("/", bot.serveRoot)
|
2013-06-01 17:29:12 +00:00
|
|
|
if config.HttpAddr == "" {
|
|
|
|
config.HttpAddr = "127.0.0.1:1337"
|
|
|
|
}
|
|
|
|
go http.ListenAndServe(config.HttpAddr, nil)
|
2013-06-01 17:10:15 +00:00
|
|
|
|
|
|
|
return bot
|
2012-08-17 20:38:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Adds a constructed handler to the bots handlers list
|
2012-08-17 22:56:44 +00:00
|
|
|
func (b *Bot) AddHandler(name string, h Handler) {
|
|
|
|
b.Plugins[strings.ToLower(name)] = h
|
2012-08-26 16:15:26 +00:00
|
|
|
b.PluginOrdering = append(b.PluginOrdering, name)
|
2013-06-01 17:10:15 +00:00
|
|
|
if entry := h.RegisterWeb(); entry != nil {
|
2013-06-01 17:39:17 +00:00
|
|
|
b.httpEndPoints[name] = *entry
|
2013-06-01 17:10:15 +00:00
|
|
|
}
|
2012-08-17 21:37:49 +00:00
|
|
|
}
|
2012-08-26 20:15:50 +00:00
|
|
|
|
|
|
|
func (b *Bot) SendMessage(channel, message string) {
|
2013-06-02 01:59:55 +00:00
|
|
|
for len(message) > 0 {
|
|
|
|
m := irc.Msg{
|
|
|
|
Cmd: "PRIVMSG",
|
|
|
|
Args: []string{channel, message},
|
|
|
|
}
|
|
|
|
_, err := m.RawString()
|
|
|
|
if err != nil {
|
|
|
|
mtl := err.(irc.MsgTooLong)
|
|
|
|
m.Args[1] = message[:mtl.NTrunc]
|
|
|
|
message = message[mtl.NTrunc:]
|
|
|
|
} else {
|
|
|
|
message = ""
|
|
|
|
}
|
|
|
|
b.Client.Out <- m
|
|
|
|
}
|
2013-05-08 00:08:18 +00:00
|
|
|
|
|
|
|
b.selfSaid(channel, message)
|
2012-08-26 20:15:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sends action to channel
|
|
|
|
func (b *Bot) SendAction(channel, message string) {
|
2013-06-02 01:59:55 +00:00
|
|
|
// TODO: ADD CTCP ACTION
|
|
|
|
message = actionPrefix + " " + message + "\x01"
|
|
|
|
|
|
|
|
b.SendMessage(channel, message)
|
2013-05-08 00:08:18 +00:00
|
|
|
|
|
|
|
// Notify plugins that we've said something
|
|
|
|
b.selfSaid(channel, message)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handles incomming PRIVMSG requests
|
2013-06-02 01:59:55 +00:00
|
|
|
func (b *Bot) MsgRecieved(client *irc.Client, inMsg irc.Msg) {
|
|
|
|
if inMsg.User == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := b.buildMessage(client, inMsg)
|
2013-05-08 00:08:18 +00:00
|
|
|
|
|
|
|
if strings.HasPrefix(msg.Body, "help") && msg.Command {
|
|
|
|
parts := strings.Fields(strings.ToLower(msg.Body))
|
|
|
|
b.checkHelp(msg.Channel, parts)
|
|
|
|
goto RET
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, name := range b.PluginOrdering {
|
|
|
|
p := b.Plugins[name]
|
|
|
|
if p.Message(msg) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RET:
|
|
|
|
b.logIn <- msg
|
|
|
|
return
|
2012-08-26 20:15:50 +00:00
|
|
|
}
|
2013-06-01 17:10:15 +00:00
|
|
|
|
2013-06-02 01:59:55 +00:00
|
|
|
func (b *Bot) EventRecieved(conn *irc.Client, inMsg irc.Msg) {
|
|
|
|
if inMsg.User == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
msg := b.buildMessage(conn, inMsg)
|
|
|
|
for _, name := range b.PluginOrdering {
|
|
|
|
p := b.Plugins[name]
|
|
|
|
if p.Event(inMsg.Cmd, msg) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bot) Who(channel string) []User {
|
|
|
|
return b.Users
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:39:17 +00:00
|
|
|
var rootIndex string = `
|
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>Factoids</title>
|
|
|
|
<link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.1.0/pure-min.css">
|
|
|
|
</head>
|
|
|
|
{{if .EndPoints}}
|
|
|
|
<div style="padding-top: 1em;">
|
|
|
|
<table class="pure-table">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th>Plugin</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
|
|
|
|
<tbody>
|
|
|
|
{{range $key, $value := .EndPoints}}
|
|
|
|
<tr>
|
|
|
|
<td><a href="{{$value}}">{{$key}}</a></td>
|
|
|
|
</tr>
|
|
|
|
{{end}}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
{{end}}
|
|
|
|
</html>
|
|
|
|
`
|
|
|
|
|
2013-06-01 17:10:15 +00:00
|
|
|
func (b *Bot) serveRoot(w http.ResponseWriter, r *http.Request) {
|
2013-06-01 17:39:17 +00:00
|
|
|
context := make(map[string]interface{})
|
|
|
|
context["EndPoints"] = b.httpEndPoints
|
|
|
|
t, err := template.New("rootIndex").Parse(rootIndex)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
2013-06-01 17:10:15 +00:00
|
|
|
}
|
2013-06-01 17:39:17 +00:00
|
|
|
t.Execute(w, context)
|
2013-06-01 17:10:15 +00:00
|
|
|
}
|