bot: hook connectors up to events

This includes a full test of `admin`
This commit is contained in:
Chris Sexton 2019-02-05 13:33:18 -05:00
parent d85c855d47
commit 82dcf410f2
11 changed files with 149 additions and 199 deletions

View File

@ -83,9 +83,7 @@ func New(config *config.Config, connector Connector) Bot {
addr := config.Get("HttpAddr", "127.0.0.1:1337") addr := config.Get("HttpAddr", "127.0.0.1:1337")
go http.ListenAndServe(addr, nil) go http.ListenAndServe(addr, nil)
connector.RegisterMessageReceived(bot.MsgReceived) connector.RegisterEvent(bot.Receive)
connector.RegisterEventReceived(bot.EventReceived)
connector.RegisterReplyMessageReceived(bot.ReplyMsgReceived)
return bot return bot
} }
@ -249,10 +247,14 @@ func (b *bot) RegisterFilter(name string, f func(string) string) {
b.filters[name] = f b.filters[name] = f
} }
// Send a message to the connection
func (b *bot) Send(kind Kind, args ...interface{}) (error, string) { return nil, "" }
// Register a callback // Register a callback
func (b *bot) Register(name string, kind Kind, cb Callback) { func (b *bot) Register(name string, kind Kind, cb Callback) {
name = strings.ToLower(name)
if _, ok := b.callbacks[name]; !ok {
b.callbacks[name] = make(map[Kind][]Callback)
}
if _, ok := b.callbacks[name][kind]; !ok {
b.callbacks[name][kind] = []Callback{}
}
b.callbacks[name][kind] = append(b.callbacks[name][kind], cb) b.callbacks[name][kind] = append(b.callbacks[name][kind], cb)
} }

View File

@ -17,23 +17,18 @@ import (
) )
func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) { func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) {
panic("I don't know what to do here yet") log.Println("Received event: ", msg)
}
// Handles incomming PRIVMSG requests
func (b *bot) MsgReceived(msg msg.Message) {
log.Println("Received message: ", msg)
// msg := b.buildMessage(client, inMsg) // msg := b.buildMessage(client, inMsg)
// do need to look up user and fix it // do need to look up user and fix it
if strings.HasPrefix(msg.Body, "help ") && msg.Command { if kind == Message && strings.HasPrefix(msg.Body, "help ") && msg.Command {
parts := strings.Fields(strings.ToLower(msg.Body)) parts := strings.Fields(strings.ToLower(msg.Body))
b.checkHelp(msg.Channel, parts) b.checkHelp(msg.Channel, parts)
goto RET goto RET
} }
for _, name := range b.pluginOrdering { for _, name := range b.pluginOrdering {
if b.runCallback(name, Message, msg) { if b.runCallback(name, kind, msg, args) {
goto RET goto RET
} }
} }
@ -43,59 +38,18 @@ RET:
return return
} }
// Handle incoming events
func (b *bot) EventReceived(msg msg.Message) {
log.Println("Received event: ", msg)
//msg := b.buildMessage(conn, inMsg)
for _, name := range b.pluginOrdering {
if b.runCallback(name, Event, msg) {
return
}
}
}
func (b *bot) runCallback(plugin string, evt Kind, message msg.Message, args ...interface{}) bool { func (b *bot) runCallback(plugin string, evt Kind, message msg.Message, args ...interface{}) bool {
for _, cb := range b.callbacks[plugin][evt] { for _, cb := range b.callbacks[plugin][evt] {
if cb(evt, message) { if cb(evt, message, args) {
return true return true
} }
} }
return false return false
} }
// Handle incoming replys // Send a message to the connection
func (b *bot) ReplyMsgReceived(msg msg.Message, identifier string) { func (b *bot) Send(kind Kind, args ...interface{}) (string, error) {
log.Println("Received message: ", msg) return b.conn.Send(kind, args...)
for _, name := range b.pluginOrdering {
if b.runCallback(name, Reply, msg, identifier) {
break
}
}
}
func (b *bot) SendMessage(channel, message string) (error, string) {
return b.conn.Send(Message, channel, message)
}
func (b *bot) SendAction(channel, message string) (error, string) {
return b.conn.Send(Action, channel, message)
}
func (b *bot) ReplyToMessageIdentifier(channel, message, identifier string) (error, string) {
return b.conn.Send(Reply, channel, message, identifier)
}
func (b *bot) ReplyToMessage(channel, message string, replyTo msg.Message) (error, string) {
return b.conn.Send(Reply, channel, message, replyTo)
}
func (b *bot) React(channel, reaction string, message msg.Message) (error, string) {
return b.conn.Send(Reaction, channel, reaction, message)
}
func (b *bot) Edit(channel, newMessage, identifier string) (error, string) {
return b.conn.Send(Edit, channel, newMessage, identifier)
} }
func (b *bot) GetEmojiList() map[string]string { func (b *bot) GetEmojiList() map[string]string {
@ -110,7 +64,7 @@ func (b *bot) checkHelp(channel string, parts []string) {
for name, _ := range b.plugins { for name, _ := range b.plugins {
topics = fmt.Sprintf("%s, %s", topics, name) topics = fmt.Sprintf("%s, %s", topics, name)
} }
b.SendMessage(channel, topics) b.Send(Message, channel, topics)
} else { } else {
// trigger the proper plugin's help response // trigger the proper plugin's help response
if parts[1] == "about" { if parts[1] == "about" {
@ -127,7 +81,7 @@ func (b *bot) checkHelp(channel string, parts []string) {
b.runCallback(parts[1], Help, msg.Message{Channel: channel}, channel, parts) b.runCallback(parts[1], Help, msg.Message{Channel: channel}, channel, parts)
} else { } else {
msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", parts[1]) msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", parts[1])
b.SendMessage(channel, msg) b.Send(Message, channel, msg)
} }
} }
} }
@ -223,14 +177,14 @@ func (b *bot) listVars(channel string, parts []string) {
if len(variables) > 0 { if len(variables) > 0 {
msg += ", " + strings.Join(variables, ", ") msg += ", " + strings.Join(variables, ", ")
} }
b.SendMessage(channel, msg) b.Send(Message, channel, msg)
} }
func (b *bot) Help(channel string, parts []string) { func (b *bot) Help(channel string, parts []string) {
msg := fmt.Sprintf("Hi, I'm based on godeepintir version %s. I'm written in Go, and you "+ msg := fmt.Sprintf("Hi, I'm based on godeepintir version %s. I'm written in Go, and you "+
"can find my source code on the internet here: "+ "can find my source code on the internet here: "+
"http://github.com/velour/catbase", b.version) "http://github.com/velour/catbase", b.version)
b.SendMessage(channel, msg) b.Send(Message, channel, msg)
} }
// Send our own musings to the plugins // Send our own musings to the plugins

View File

@ -45,7 +45,7 @@ type Bot interface {
// AddPlugin registers a new plugin handler // AddPlugin registers a new plugin handler
AddPlugin(string, Plugin) AddPlugin(string, Plugin)
// First arg should be one of bot.Message/Reply/Action/etc // First arg should be one of bot.Message/Reply/Action/etc
Send(Kind, ...interface{}) (error, string) Send(Kind, ...interface{}) (string, error)
// First arg should be one of bot.Message/Reply/Action/etc // First arg should be one of bot.Message/Reply/Action/etc
Receive(Kind, msg.Message, ...interface{}) Receive(Kind, msg.Message, ...interface{})
// Register a callback // Register a callback
@ -61,11 +61,9 @@ type Bot interface {
// Connector represents a server connection to a chat service // Connector represents a server connection to a chat service
type Connector interface { type Connector interface {
RegisterEventReceived(func(message msg.Message)) RegisterEvent(func(Kind, msg.Message, ...interface{}))
RegisterMessageReceived(func(message msg.Message))
RegisterReplyMessageReceived(func(msg.Message, string))
Send(Kind, ...interface{}) (error, string) Send(Kind, ...interface{}) (string, error)
GetEmojiList() map[string]string GetEmojiList() map[string]string
Serve() error Serve() error

View File

@ -29,14 +29,14 @@ type MockBot struct {
func (mb *MockBot) Config() *config.Config { return mb.Cfg } func (mb *MockBot) Config() *config.Config { return mb.Cfg }
func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB } func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB }
func (mb *MockBot) Who(string) []user.User { return []user.User{} } func (mb *MockBot) Who(string) []user.User { return []user.User{} }
func (mb *MockBot) Send(kind Kind, args ...interface{}) (error, string) { func (mb *MockBot) Send(kind Kind, args ...interface{}) (string, error) {
switch kind { switch kind {
case Message: case Message:
mb.Messages = append(mb.Messages, args[1].(string)) mb.Messages = append(mb.Messages, args[1].(string))
return nil, fmt.Sprintf("m-%d", len(mb.Actions)-1) return fmt.Sprintf("m-%d", len(mb.Actions)-1), nil
case Action: case Action:
mb.Actions = append(mb.Actions, args[1].(string)) mb.Actions = append(mb.Actions, args[1].(string))
return nil, fmt.Sprintf("a-%d", len(mb.Actions)-1) return fmt.Sprintf("a-%d", len(mb.Actions)-1), nil
case Edit: case Edit:
ch, m, id := args[0].(string), args[1].(string), args[2].(string) ch, m, id := args[0].(string), args[1].(string), args[2].(string)
return mb.edit(ch, m, id) return mb.edit(ch, m, id)
@ -44,7 +44,7 @@ func (mb *MockBot) Send(kind Kind, args ...interface{}) (error, string) {
ch, re, msg := args[0].(string), args[1].(string), args[2].(msg.Message) ch, re, msg := args[0].(string), args[1].(string), args[2].(msg.Message)
return mb.react(ch, re, msg) return mb.react(ch, re, msg)
} }
return fmt.Errorf("Mesasge type unhandled"), "ERROR" return "ERR", fmt.Errorf("Mesasge type unhandled")
} }
func (mb *MockBot) AddPlugin(name string, f Plugin) {} func (mb *MockBot) AddPlugin(name string, f Plugin) {}
func (mb *MockBot) Register(name string, kind Kind, cb Callback) {} func (mb *MockBot) Register(name string, kind Kind, cb Callback) {}
@ -53,40 +53,40 @@ func (mb *MockBot) Filter(msg msg.Message, s string) string { re
func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil }
func (mb *MockBot) CheckAdmin(nick string) bool { return false } func (mb *MockBot) CheckAdmin(nick string) bool { return false }
func (mb *MockBot) react(channel, reaction string, message msg.Message) (error, string) { func (mb *MockBot) react(channel, reaction string, message msg.Message) (string, error) {
mb.Reactions = append(mb.Reactions, reaction) mb.Reactions = append(mb.Reactions, reaction)
return nil, "" return "", nil
} }
func (mb *MockBot) edit(channel, newMessage, identifier string) (error, string) { func (mb *MockBot) edit(channel, newMessage, identifier string) (string, error) {
isMessage := identifier[0] == 'm' isMessage := identifier[0] == 'm'
if !isMessage && identifier[0] != 'a' { if !isMessage && identifier[0] != 'a' {
err := fmt.Errorf("failed to parse identifier: %s", identifier) err := fmt.Errorf("failed to parse identifier: %s", identifier)
log.Println(err) log.Println(err)
return err, "" return "", err
} }
index, err := strconv.Atoi(strings.Split(identifier, "-")[1]) index, err := strconv.Atoi(strings.Split(identifier, "-")[1])
if err != nil { if err != nil {
err := fmt.Errorf("failed to parse identifier: %s", identifier) err := fmt.Errorf("failed to parse identifier: %s", identifier)
log.Println(err) log.Println(err)
return err, "" return "", err
} }
if isMessage { if isMessage {
if index < len(mb.Messages) { if index < len(mb.Messages) {
mb.Messages[index] = newMessage mb.Messages[index] = newMessage
} else { } else {
return fmt.Errorf("No message"), "" return "", fmt.Errorf("No message")
} }
} else { } else {
if index < len(mb.Actions) { if index < len(mb.Actions) {
mb.Actions[index] = newMessage mb.Actions[index] = newMessage
} else { } else {
return fmt.Errorf("No action"), "" return "", fmt.Errorf("No action")
} }
} }
return nil, "" return "", nil
} }
func (mb *MockBot) GetEmojiList() map[string]string { return make(map[string]string) } func (mb *MockBot) GetEmojiList() map[string]string { return make(map[string]string) }

View File

@ -42,9 +42,7 @@ type Irc struct {
config *config.Config config *config.Config
quit chan bool quit chan bool
eventReceived func(msg.Message) event func(bot.Kind, msg.Message, ...interface{})
messageReceived func(msg.Message)
replyMessageReceived func(msg.Message, string)
} }
func New(c *config.Config) *Irc { func New(c *config.Config) *Irc {
@ -54,19 +52,11 @@ func New(c *config.Config) *Irc {
return &i return &i
} }
func (i *Irc) RegisterEventReceived(f func(msg.Message)) { func (i *Irc) RegisterEvent(f func(bot.Kind, msg.Message, ...interface{})) {
i.eventReceived = f i.event = f
} }
func (i *Irc) RegisterMessageReceived(f func(msg.Message)) { func (i *Irc) Send(kind bot.Kind, args ...interface{}) (string, error) {
i.messageReceived = f
}
func (i *Irc) RegisterReplyMessageReceived(f func(msg.Message, string)) {
i.replyMessageReceived = f
}
func (i *Irc) Send(kind bot.Kind, args ...interface{}) (error, string) {
switch kind { switch kind {
case bot.Reply: case bot.Reply:
case bot.Message: case bot.Message:
@ -75,7 +65,7 @@ func (i *Irc) Send(kind bot.Kind, args ...interface{}) (error, string) {
return i.sendAction(args[0].(string), args[1].(string)) return i.sendAction(args[0].(string), args[1].(string))
default: default:
} }
return nil, "" return "", nil
} }
func (i *Irc) JoinChannel(channel string) { func (i *Irc) JoinChannel(channel string) {
@ -83,7 +73,7 @@ func (i *Irc) JoinChannel(channel string) {
i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}} i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}}
} }
func (i *Irc) sendMessage(channel, message string) (error, string) { func (i *Irc) sendMessage(channel, message string) (string, error) {
for len(message) > 0 { for len(message) > 0 {
m := irc.Msg{ m := irc.Msg{
Cmd: "PRIVMSG", Cmd: "PRIVMSG",
@ -107,11 +97,11 @@ func (i *Irc) sendMessage(channel, message string) (error, string) {
i.Client.Out <- m i.Client.Out <- m
} }
return nil, "NO_IRC_IDENTIFIERS" return "NO_IRC_IDENTIFIERS", nil
} }
// Sends action to channel // Sends action to channel
func (i *Irc) sendAction(channel, message string) (error, string) { func (i *Irc) sendAction(channel, message string) (string, error) {
message = actionPrefix + " " + message + "\x01" message = actionPrefix + " " + message + "\x01"
return i.sendMessage(channel, message) return i.sendMessage(channel, message)
@ -123,7 +113,7 @@ func (i *Irc) GetEmojiList() map[string]string {
} }
func (i *Irc) Serve() error { func (i *Irc) Serve() error {
if i.eventReceived == nil || i.messageReceived == nil { if i.event == nil {
return fmt.Errorf("Missing an event handler") return fmt.Errorf("Missing an event handler")
} }
@ -202,53 +192,53 @@ func (i *Irc) handleMsg(msg irc.Msg) {
// OK, ignore // OK, ignore
case irc.ERR_NOSUCHNICK: case irc.ERR_NOSUCHNICK:
i.eventReceived(botMsg) fallthrough
case irc.ERR_NOSUCHCHANNEL: case irc.ERR_NOSUCHCHANNEL:
i.eventReceived(botMsg) fallthrough
case irc.RPL_MOTD: case irc.RPL_MOTD:
i.eventReceived(botMsg) fallthrough
case irc.RPL_NAMREPLY: case irc.RPL_NAMREPLY:
i.eventReceived(botMsg) fallthrough
case irc.RPL_TOPIC: case irc.RPL_TOPIC:
i.eventReceived(botMsg) fallthrough
case irc.KICK: case irc.KICK:
i.eventReceived(botMsg) fallthrough
case irc.TOPIC: case irc.TOPIC:
i.eventReceived(botMsg) fallthrough
case irc.MODE: case irc.MODE:
i.eventReceived(botMsg) fallthrough
case irc.JOIN: case irc.JOIN:
i.eventReceived(botMsg) fallthrough
case irc.PART: case irc.PART:
i.eventReceived(botMsg) fallthrough
case irc.NOTICE:
fallthrough
case irc.NICK:
fallthrough
case irc.RPL_WHOREPLY:
fallthrough
case irc.RPL_ENDOFWHO:
i.event(bot.Event, botMsg)
case irc.PRIVMSG:
i.event(bot.Message, botMsg)
case irc.QUIT: case irc.QUIT:
os.Exit(1) os.Exit(1)
case irc.NOTICE:
i.eventReceived(botMsg)
case irc.PRIVMSG:
i.messageReceived(botMsg)
case irc.NICK:
i.eventReceived(botMsg)
case irc.RPL_WHOREPLY:
i.eventReceived(botMsg)
case irc.RPL_ENDOFWHO:
i.eventReceived(botMsg)
default: default:
cmd := irc.CmdNames[msg.Cmd] cmd := irc.CmdNames[msg.Cmd]
log.Println("(" + cmd + ") " + msg.Raw) log.Println("(" + cmd + ") " + msg.Raw)

View File

@ -35,6 +35,7 @@ import (
"github.com/velour/catbase/plugins/twitch" "github.com/velour/catbase/plugins/twitch"
"github.com/velour/catbase/plugins/your" "github.com/velour/catbase/plugins/your"
"github.com/velour/catbase/plugins/zork" "github.com/velour/catbase/plugins/zork"
"github.com/velour/catbase/slack"
) )
var ( var (
@ -70,7 +71,7 @@ func main() {
case "irc": case "irc":
client = irc.New(c) client = irc.New(c)
case "slack": case "slack":
//client = slack.New(c) client = slack.New(c)
default: default:
log.Fatalf("Unknown connection type: %s", c.Get("type", "UNSET")) log.Fatalf("Unknown connection type: %s", c.Get("type", "UNSET"))
} }

View File

@ -25,12 +25,14 @@ type AdminPlugin struct {
} }
// NewAdminPlugin creates a new AdminPlugin with the Plugin interface // NewAdminPlugin creates a new AdminPlugin with the Plugin interface
func New(bot bot.Bot) *AdminPlugin { func New(b bot.Bot) *AdminPlugin {
p := &AdminPlugin{ p := &AdminPlugin{
Bot: bot, Bot: b,
db: bot.DB(), db: b.DB(),
cfg: bot.Config(), cfg: b.Config(),
} }
b.Register("admin", bot.Message, p.message)
b.Register("admin", bot.Help, p.help)
return p return p
} }
@ -44,7 +46,7 @@ var forbiddenKeys = map[string]bool{
// Message responds to the bot hook on recieving messages. // Message responds to the bot hook on recieving messages.
// This function returns true if the plugin responds in a meaningful way to the users message. // This function returns true if the plugin responds in a meaningful way to the users message.
// Otherwise, the function returns false and the bot continues execution of other plugins. // Otherwise, the function returns false and the bot continues execution of other plugins.
func (p *AdminPlugin) Message(message msg.Message) bool { func (p *AdminPlugin) message(k bot.Kind, message msg.Message, args ...interface{}) bool {
body := message.Body body := message.Body
if p.quiet { if p.quiet {
@ -143,23 +145,12 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool {
} }
// Help responds to help requests. Every plugin must implement a help function. // Help responds to help requests. Every plugin must implement a help function.
func (p *AdminPlugin) Help(channel string, parts []string) { func (p *AdminPlugin) help(kind bot.Kind, m msg.Message, args ...interface{}) bool {
p.Bot.Send(bot.Message, channel, "This does super secret things that you're not allowed to know about.") p.Bot.Send(bot.Message, m.Channel, "This does super secret things that you're not allowed to know about.")
} return true
// Empty event handler because this plugin does not do anything on event recv
func (p *AdminPlugin) Event(kind string, message msg.Message) bool {
return false
}
// Handler for bot's own messages
func (p *AdminPlugin) BotMessage(message msg.Message) bool {
return false
} }
// Register any web URLs desired // Register any web URLs desired
func (p *AdminPlugin) RegisterWeb() *string { func (p *AdminPlugin) RegisterWeb() *string {
return nil return nil
} }
func (p *AdminPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }

View File

@ -22,12 +22,12 @@ func setup(t *testing.T) (*AdminPlugin, *bot.MockBot) {
return a, mb return a, mb
} }
func makeMessage(payload string) msg.Message { func makeMessage(payload string) (bot.Kind, msg.Message) {
isCmd := strings.HasPrefix(payload, "!") isCmd := strings.HasPrefix(payload, "!")
if isCmd { if isCmd {
payload = payload[1:] payload = payload[1:]
} }
return msg.Message{ return bot.Message, msg.Message{
User: &user.User{Name: "tester"}, User: &user.User{Name: "tester"},
Channel: "test", Channel: "test",
Body: payload, Body: payload,
@ -38,7 +38,7 @@ func makeMessage(payload string) msg.Message {
func TestSet(t *testing.T) { func TestSet(t *testing.T) {
a, mb := setup(t) a, mb := setup(t)
expected := "test value" expected := "test value"
a.Message(makeMessage("!set test.key " + expected)) a.message(makeMessage("!set test.key " + expected))
actual := mb.Config().Get("test.key", "ERR") actual := mb.Config().Get("test.key", "ERR")
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
@ -47,7 +47,7 @@ func TestGetValue(t *testing.T) {
a, mb := setup(t) a, mb := setup(t)
expected := "value" expected := "value"
mb.Config().Set("test.key", "value") mb.Config().Set("test.key", "value")
a.Message(makeMessage("!get test.key")) a.message(makeMessage("!get test.key"))
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], expected) assert.Contains(t, mb.Messages[0], expected)
} }
@ -55,7 +55,7 @@ func TestGetValue(t *testing.T) {
func TestGetEmpty(t *testing.T) { func TestGetEmpty(t *testing.T) {
a, mb := setup(t) a, mb := setup(t)
expected := "test.key: <unknown>" expected := "test.key: <unknown>"
a.Message(makeMessage("!get test.key")) a.message(makeMessage("!get test.key"))
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
assert.Equal(t, expected, mb.Messages[0]) assert.Equal(t, expected, mb.Messages[0])
} }
@ -63,7 +63,7 @@ func TestGetEmpty(t *testing.T) {
func TestGetForbidden(t *testing.T) { func TestGetForbidden(t *testing.T) {
a, mb := setup(t) a, mb := setup(t)
expected := "cannot access" expected := "cannot access"
a.Message(makeMessage("!get slack.token")) a.message(makeMessage("!get slack.token"))
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], expected) assert.Contains(t, mb.Messages[0], expected)
} }

View File

@ -107,7 +107,7 @@ func New(b bot.Bot) *RPGPlugin {
func (p *RPGPlugin) Message(message msg.Message) bool { func (p *RPGPlugin) Message(message msg.Message) bool {
if strings.ToLower(message.Body) == "start rpg" { if strings.ToLower(message.Body) == "start rpg" {
b := NewRandomBoard() b := NewRandomBoard()
_, ts := p.Bot.Send(bot.Message, message.Channel, b.toMessageString()) ts, _ := p.Bot.Send(bot.Message, message.Channel, b.toMessageString())
p.listenFor[ts] = b p.listenFor[ts] = b
p.Bot.Send(bot.Reply, message.Channel, "Over here.", ts) p.Bot.Send(bot.Reply, message.Channel, "Over here.", ts)
return true return true

View File

@ -47,7 +47,7 @@ func NewRandomGame(b bot.Bot, channel, who string) *game {
size: size, size: size,
current: size / 2, current: size / 2,
} }
_, g.id = b.Send(bot.Message, channel, g.toMessageString()) g.id, _ = b.Send(bot.Message, channel, g.toMessageString())
g.schedulePush() g.schedulePush()
g.scheduleDecrement() g.scheduleDecrement()

View File

@ -44,9 +44,7 @@ type Slack struct {
emoji map[string]string emoji map[string]string
eventReceived func(msg.Message) event func(bot.Kind, msg.Message, ...interface{})
messageReceived func(msg.Message)
replyMessageReceived func(msg.Message, string)
} }
var idCounter uint64 var idCounter uint64
@ -177,7 +175,31 @@ func New(c *config.Config) *Slack {
} }
} }
func checkReturnStatus(response *http.Response) bool { func (s *Slack) Send(kind bot.Kind, args ...interface{}) (string, error) {
switch kind {
case bot.Message:
return s.sendMessage(args[0].(string), args[1].(string))
case bot.Action:
return s.sendAction(args[0].(string), args[1].(string))
case bot.Edit:
return s.edit(args[0].(string), args[1].(string), args[2].(string))
case bot.Reply:
switch args[2].(type) {
case msg.Message:
return s.replyToMessage(args[0].(string), args[1].(string), args[2].(msg.Message))
case string:
return s.replyToMessageIdentifier(args[0].(string), args[1].(string), args[2].(string))
default:
return "", fmt.Errorf("Invalid types given to Reply")
}
case bot.Reaction:
return s.react(args[0].(string), args[1].(string), args[2].(msg.Message))
default:
}
return "", fmt.Errorf("No handler for message type %d", kind)
}
func checkReturnStatus(response *http.Response) error {
type Response struct { type Response struct {
OK bool `json:"ok"` OK bool `json:"ok"`
} }
@ -185,32 +207,24 @@ func checkReturnStatus(response *http.Response) bool {
body, err := ioutil.ReadAll(response.Body) body, err := ioutil.ReadAll(response.Body)
response.Body.Close() response.Body.Close()
if err != nil { if err != nil {
log.Printf("Error reading Slack API body: %s", err) err := fmt.Errorf("Error reading Slack API body: %s", err)
return false return err
} }
var resp Response var resp Response
err = json.Unmarshal(body, &resp) err = json.Unmarshal(body, &resp)
if err != nil { if err != nil {
log.Printf("Error parsing message response: %s", err) err := fmt.Errorf("Error parsing message response: %s", err)
return false return err
} }
return resp.OK return nil
} }
func (s *Slack) RegisterEventReceived(f func(msg.Message)) { func (s *Slack) RegisterEvent(f func(bot.Kind, msg.Message, ...interface{})) {
s.eventReceived = f s.event = f
} }
func (s *Slack) RegisterMessageReceived(f func(msg.Message)) { func (s *Slack) sendMessageType(channel, message string, meMessage bool) (string, error) {
s.messageReceived = f
}
func (s *Slack) RegisterReplyMessageReceived(f func(msg.Message, string)) {
s.replyMessageReceived = f
}
func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string, error) {
postUrl := "https://slack.com/api/chat.postMessage" postUrl := "https://slack.com/api/chat.postMessage"
if meMessage { if meMessage {
postUrl = "https://slack.com/api/chat.meMessage" postUrl = "https://slack.com/api/chat.meMessage"
@ -262,19 +276,19 @@ func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string
return mr.Timestamp, err return mr.Timestamp, err
} }
func (s *Slack) SendMessage(channel, message string) string { func (s *Slack) sendMessage(channel, message string) (string, error) {
log.Printf("Sending message to %s: %s", channel, message) log.Printf("Sending message to %s: %s", channel, message)
identifier, _ := s.SendMessageType(channel, message, false) identifier, err := s.sendMessageType(channel, message, false)
return identifier return identifier, err
} }
func (s *Slack) SendAction(channel, message string) string { func (s *Slack) sendAction(channel, message string) (string, error) {
log.Printf("Sending action to %s: %s", channel, message) log.Printf("Sending action to %s: %s", channel, message)
identifier, _ := s.SendMessageType(channel, "_"+message+"_", true) identifier, err := s.sendMessageType(channel, "_"+message+"_", true)
return identifier return identifier, err
} }
func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) { func (s *Slack) replyToMessageIdentifier(channel, message, identifier string) (string, error) {
nick := s.config.Get("Nick", "bot") nick := s.config.Get("Nick", "bot")
icon := s.config.Get("IconURL", "https://placekitten.com/128/128") icon := s.config.Get("IconURL", "https://placekitten.com/128/128")
@ -288,15 +302,15 @@ func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (s
}) })
if err != nil { if err != nil {
log.Printf("Error sending Slack reply: %s", err) err := fmt.Errorf("Error sending Slack reply: %s", err)
return "", false return "", err
} }
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close() resp.Body.Close()
if err != nil { if err != nil {
log.Printf("Error reading Slack API body: %s", err) err := fmt.Errorf("Error reading Slack API body: %s", err)
return "", false return "", err
} }
log.Println(string(body)) log.Println(string(body))
@ -309,22 +323,22 @@ func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (s
var mr MessageResponse var mr MessageResponse
err = json.Unmarshal(body, &mr) err = json.Unmarshal(body, &mr)
if err != nil { if err != nil {
log.Printf("Error parsing message response: %s", err) err := fmt.Errorf("Error parsing message response: %s", err)
return "", false return "", err
} }
if !mr.OK { if !mr.OK {
return "", false return "", fmt.Errorf("Got !OK from slack message response")
} }
return mr.Timestamp, err == nil return mr.Timestamp, err
} }
func (s *Slack) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) { func (s *Slack) replyToMessage(channel, message string, replyTo msg.Message) (string, error) {
return s.ReplyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"]) return s.replyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"])
} }
func (s *Slack) React(channel, reaction string, message msg.Message) bool { func (s *Slack) react(channel, reaction string, message msg.Message) (string, error) {
log.Printf("Reacting in %s: %s", channel, reaction) log.Printf("Reacting in %s: %s", channel, reaction)
resp, err := http.PostForm("https://slack.com/api/reactions.add", resp, err := http.PostForm("https://slack.com/api/reactions.add",
url.Values{"token": {s.token}, url.Values{"token": {s.token},
@ -332,13 +346,13 @@ func (s *Slack) React(channel, reaction string, message msg.Message) bool {
"channel": {channel}, "channel": {channel},
"timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}}) "timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}})
if err != nil { if err != nil {
log.Printf("reaction failed: %s", err) err := fmt.Errorf("reaction failed: %s", err)
return false return "", err
} }
return checkReturnStatus(resp) return "", checkReturnStatus(resp)
} }
func (s *Slack) Edit(channel, newMessage, identifier string) bool { func (s *Slack) edit(channel, newMessage, identifier string) (string, error) {
log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage)
resp, err := http.PostForm("https://slack.com/api/chat.update", resp, err := http.PostForm("https://slack.com/api/chat.update",
url.Values{"token": {s.token}, url.Values{"token": {s.token},
@ -346,10 +360,10 @@ func (s *Slack) Edit(channel, newMessage, identifier string) bool {
"text": {newMessage}, "text": {newMessage},
"ts": {identifier}}) "ts": {identifier}})
if err != nil { if err != nil {
log.Printf("edit failed: %s", err) err := fmt.Errorf("edit failed: %s", err)
return false return "", err
} }
return checkReturnStatus(resp) return "", checkReturnStatus(resp)
} }
func (s *Slack) GetEmojiList() map[string]string { func (s *Slack) GetEmojiList() map[string]string {
@ -441,11 +455,11 @@ func (s *Slack) Serve() error {
log.Printf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time) log.Printf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time)
} else { } else {
s.lastRecieved = m.Time s.lastRecieved = m.Time
s.messageReceived(m) s.event(bot.Message, m)
} }
} else if msg.ThreadTs != "" { } else if msg.ThreadTs != "" {
//we're throwing away some information here by not parsing the correct reply object type, but that's okay //we're throwing away some information here by not parsing the correct reply object type, but that's okay
s.replyMessageReceived(s.buildLightReplyMessage(msg), msg.ThreadTs) s.event(bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTs)
} else { } else {
log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID) log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID)
} }