mirror of https://github.com/velour/catbase.git
bot: refactor callback handlers
New system: * Each callback can filter for a regex * Backwards compatability using a `.*` generic regex * Handlers now accept a request object instead of bare arguments All new plugins should use this new system.
This commit is contained in:
parent
ff4b541c01
commit
aad4ecf143
23
bot/bot.go
23
bot/bot.go
|
@ -230,16 +230,29 @@ func (b *bot) RegisterFilter(name string, f func(string) string) {
|
|||
b.filters[name] = f
|
||||
}
|
||||
|
||||
// Register a callback
|
||||
func (b *bot) Register(p Plugin, kind Kind, cb Callback) {
|
||||
// RegisterRegex does what register does, but with a matcher
|
||||
func (b *bot) RegisterRegex(p Plugin, kind Kind, r *regexp.Regexp, resp ResponseHandler) {
|
||||
t := reflect.TypeOf(p).String()
|
||||
if _, ok := b.callbacks[t]; !ok {
|
||||
b.callbacks[t] = make(map[Kind][]Callback)
|
||||
b.callbacks[t] = make(map[Kind]map[*regexp.Regexp][]ResponseHandler)
|
||||
}
|
||||
if _, ok := b.callbacks[t][kind]; !ok {
|
||||
b.callbacks[t][kind] = []Callback{}
|
||||
b.callbacks[t][kind] = map[*regexp.Regexp][]ResponseHandler{}
|
||||
}
|
||||
b.callbacks[t][kind] = append(b.callbacks[t][kind], cb)
|
||||
if _, ok := b.callbacks[t][kind][r]; !ok {
|
||||
b.callbacks[t][kind][r] = []ResponseHandler{}
|
||||
}
|
||||
b.callbacks[t][kind][r] = append(b.callbacks[t][kind][r], resp)
|
||||
}
|
||||
|
||||
// Register a callback
|
||||
// This function should be considered deprecated.
|
||||
func (b *bot) Register(p Plugin, kind Kind, cb Callback) {
|
||||
r := regexp.MustCompile(`.*`)
|
||||
resp := func(r Request) bool {
|
||||
return cb(r.Conn, r.Kind, r.Msg, r.Args...)
|
||||
}
|
||||
b.RegisterRegex(p, kind, r, resp)
|
||||
}
|
||||
|
||||
func (b *bot) RegisterWeb(root, name string) {
|
||||
|
|
|
@ -41,11 +41,34 @@ RET:
|
|||
return true
|
||||
}
|
||||
|
||||
func parseValues(r *regexp.Regexp, body string) RegexValues {
|
||||
out := RegexValues{}
|
||||
subs := r.FindStringSubmatch(body)
|
||||
if len(subs) == 0 {
|
||||
return out
|
||||
}
|
||||
for i, n := range r.SubexpNames() {
|
||||
out[n] = subs[i]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (b *bot) runCallback(conn Connector, plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool {
|
||||
t := reflect.TypeOf(plugin).String()
|
||||
for _, cb := range b.callbacks[t][evt] {
|
||||
if cb(conn, evt, message, args...) {
|
||||
return true
|
||||
for r, cbs := range b.callbacks[t][evt] {
|
||||
if r.MatchString(message.Body) {
|
||||
for _, cb := range cbs {
|
||||
resp := Request{
|
||||
Conn: conn,
|
||||
Kind: evt,
|
||||
Msg: message,
|
||||
Values: parseValues(r, message.Body),
|
||||
Args: args,
|
||||
}
|
||||
if cb(resp) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
package bot
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"github.com/velour/catbase/bot/msg"
|
||||
|
@ -46,9 +48,20 @@ type ImageAttachment struct {
|
|||
Height int
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Conn Connector
|
||||
Kind Kind
|
||||
Msg msg.Message
|
||||
Values RegexValues
|
||||
Args []interface{}
|
||||
}
|
||||
|
||||
type Kind int
|
||||
type Callback func(Connector, Kind, msg.Message, ...interface{}) bool
|
||||
type CallbackMap map[string]map[Kind][]Callback
|
||||
type ResponseHandler func(Request) bool
|
||||
type CallbackMap map[string]map[Kind]map[*regexp.Regexp][]ResponseHandler
|
||||
|
||||
type RegexValues map[string]string
|
||||
|
||||
// b interface serves to allow mocking of the actual bot
|
||||
type Bot interface {
|
||||
|
@ -77,6 +90,10 @@ type Bot interface {
|
|||
// The Kind arg should be one of bot.Message/Reply/Action/etc
|
||||
Receive(Connector, Kind, msg.Message, ...interface{}) bool
|
||||
|
||||
// Register a plugin callback
|
||||
// Kind will be matched to the event for the callback
|
||||
RegisterRegex(Plugin, Kind, *regexp.Regexp, ResponseHandler)
|
||||
|
||||
// Register a plugin callback
|
||||
// Kind will be matched to the event for the callback
|
||||
Register(Plugin, Kind, Callback)
|
||||
|
|
10
bot/mock.go
10
bot/mock.go
|
@ -5,6 +5,7 @@ package bot
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -51,10 +52,11 @@ func (mb *MockBot) Send(c Connector, kind Kind, args ...interface{}) (string, er
|
|||
}
|
||||
return "ERR", fmt.Errorf("Mesasge type unhandled")
|
||||
}
|
||||
func (mb *MockBot) AddPlugin(f Plugin) {}
|
||||
func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {}
|
||||
func (mb *MockBot) RegisterWeb(_, _ string) {}
|
||||
func (mb *MockBot) GetWebNavigation() []EndPoint { return nil }
|
||||
func (mb *MockBot) AddPlugin(f Plugin) {}
|
||||
func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {}
|
||||
func (mb *MockBot) RegisterRegex(p Plugin, kind Kind, r *regexp.Regexp, h ResponseHandler) {}
|
||||
func (mb *MockBot) RegisterWeb(_, _ string) {}
|
||||
func (mb *MockBot) GetWebNavigation() []EndPoint { return nil }
|
||||
func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -31,7 +31,12 @@ func New(b bot.Bot) *SMSPlugin {
|
|||
}
|
||||
plugin.setup()
|
||||
plugin.registerWeb()
|
||||
b.Register(plugin, bot.Message, plugin.message)
|
||||
|
||||
b.RegisterRegex(plugin, bot.Message, deleteRegex, plugin.deleteCmd)
|
||||
b.RegisterRegex(plugin, bot.Message, sendRegex, plugin.sendCmd)
|
||||
b.RegisterRegex(plugin, bot.Message, regSomeoneRegex, plugin.registerOtherCmd)
|
||||
b.RegisterRegex(plugin, bot.Message, regSelfRegex, plugin.registerSelfCmd)
|
||||
|
||||
b.Register(plugin, bot.Help, plugin.help)
|
||||
return plugin
|
||||
}
|
||||
|
@ -46,9 +51,10 @@ func (p *SMSPlugin) checkNumber(num string) (string, error) {
|
|||
return num, nil
|
||||
}
|
||||
|
||||
var regRegex = regexp.MustCompile(`(?i)my sms number is (\d+)`)
|
||||
var reg2Regex = regexp.MustCompile(`(?i)register sms for (?P<name>\S+) (\d+)`)
|
||||
var regSelfRegex = regexp.MustCompile(`(?i)my sms number is (?P<number>\d+)`)
|
||||
var regSomeoneRegex = regexp.MustCompile(`(?i)register sms for (?P<name>\S+) (?P<number>\d+)`)
|
||||
var sendRegex = regexp.MustCompile(`(?i)send sms to (?P<name>\S+) (?P<body>.+)`)
|
||||
var deleteRegex = regexp.MustCompile(`(?i)delete my sms$`)
|
||||
|
||||
// Send will send a text to a registered user, who
|
||||
func (p *SMSPlugin) Send(who, message string) error {
|
||||
|
@ -59,69 +65,17 @@ func (p *SMSPlugin) Send(who, message string) error {
|
|||
sid := p.c.Get("TWILIOSID", "")
|
||||
token := p.c.Get("TWILIOTOKEN", "")
|
||||
myNum := p.c.Get("TWILIONUMBER", "")
|
||||
|
||||
if sid == "" || token == "" || myNum == "" {
|
||||
return fmt.Errorf("this bot is not configured for Twilio")
|
||||
}
|
||||
|
||||
client := twilio.NewClient(sid, token, nil)
|
||||
|
||||
_, err = client.Messages.SendMessage(myNum, num, message, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *SMSPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||
ch, chName := message.Channel, message.ChannelName
|
||||
body := strings.TrimSpace(message.Body)
|
||||
who := message.User.Name
|
||||
|
||||
log.Debug().Bytes("body", []byte(body)).Msgf("body: '%s', match: %v, %#v", body, sendRegex.MatchString(body), sendRegex.FindStringSubmatch(body))
|
||||
|
||||
if strings.ToLower(body) == "delete my sms" {
|
||||
if err := p.delete(who); err != nil {
|
||||
p.b.Send(c, bot.Message, ch, "Something went wrong.")
|
||||
return true
|
||||
}
|
||||
p.b.Send(c, bot.Message, ch, "Okay.")
|
||||
return true
|
||||
}
|
||||
|
||||
if sendRegex.MatchString(body) {
|
||||
subs := sendRegex.FindStringSubmatch(body)
|
||||
if len(subs) != 3 {
|
||||
p.b.Send(c, bot.Message, ch, fmt.Sprintf("I didn't send the message. Your request makes no sense."))
|
||||
return true
|
||||
}
|
||||
user, body := subs[1], fmt.Sprintf("#%s: %s", chName, subs[2])
|
||||
err := p.Send(user, body)
|
||||
if err != nil {
|
||||
p.b.Send(c, bot.Message, ch, fmt.Sprintf("I didn't send the message, %s", err))
|
||||
return true
|
||||
}
|
||||
p.b.Send(c, bot.Message, ch, "I sent a message to them.")
|
||||
return true
|
||||
}
|
||||
|
||||
if reg2Regex.MatchString(body) {
|
||||
subs := reg2Regex.FindStringSubmatch(body)
|
||||
if subs == nil || len(subs) != 3 {
|
||||
p.b.Send(c, bot.Message, ch, fmt.Sprintf("if you're trying to register somebody, give me a "+
|
||||
"message of the format: `%s`", reg2Regex))
|
||||
return true
|
||||
}
|
||||
|
||||
return p.reg(c, ch, subs[1], subs[2])
|
||||
}
|
||||
|
||||
if regRegex.MatchString(body) {
|
||||
subs := regRegex.FindStringSubmatch(body)
|
||||
if subs == nil || len(subs) != 2 {
|
||||
p.b.Send(c, bot.Message, ch, fmt.Sprintf("if you're trying to register a number, give me a "+
|
||||
"message of the format: `%s`", regRegex))
|
||||
return true
|
||||
}
|
||||
|
||||
return p.reg(c, ch, who, subs[1])
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *SMSPlugin) reg(c bot.Connector, ch, who, num string) bool {
|
||||
num, err := p.checkNumber(num)
|
||||
if err != nil {
|
||||
|
@ -152,8 +106,8 @@ func (p *SMSPlugin) reg(c bot.Connector, ch, who, num string) bool {
|
|||
|
||||
func (p *SMSPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||
ch := message.Channel
|
||||
m := fmt.Sprintf("You can register your number with: `%s`", regRegex)
|
||||
m += fmt.Sprintf("\nYou can register somebody else with: `%s`", reg2Regex)
|
||||
m := fmt.Sprintf("You can register your number with: `%s`", regSelfRegex)
|
||||
m += fmt.Sprintf("\nYou can register somebody else with: `%s`", regSomeoneRegex)
|
||||
m += fmt.Sprintf("\nYou can send a message to a user with: `%s`", sendRegex)
|
||||
m += "\nYou can deregister with: `delete my sms`"
|
||||
m += fmt.Sprintf("\nAll messages sent to %s will come to the channel.",
|
||||
|
@ -244,3 +198,55 @@ func (p *SMSPlugin) setup() {
|
|||
log.Fatal().Err(err).Msgf("could not create sms table")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *SMSPlugin) deleteCmd(r bot.Request) bool {
|
||||
ch := r.Msg.Channel
|
||||
if err := p.delete(r.Msg.User.Name); err != nil {
|
||||
p.b.Send(r.Conn, bot.Message, ch, "Something went wrong.")
|
||||
return true
|
||||
}
|
||||
p.b.Send(r.Conn, bot.Message, ch, "Okay.")
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *SMSPlugin) sendCmd(r bot.Request) bool {
|
||||
if r.Msg.User.Name == p.c.Get("nick", "") {
|
||||
return false
|
||||
}
|
||||
ch := r.Msg.Channel
|
||||
chName := r.Msg.ChannelName
|
||||
c := r.Conn
|
||||
if r.Values["name"] == "" || r.Values["body"] == "" {
|
||||
p.b.Send(c, bot.Message, ch, fmt.Sprintf("I didn't send the message. Your request makes no sense."))
|
||||
return true
|
||||
}
|
||||
user, body := r.Values["name"], fmt.Sprintf("#%s: %s", chName, r.Values["body"])
|
||||
err := p.Send(user, body)
|
||||
if err != nil {
|
||||
p.b.Send(c, bot.Message, ch, fmt.Sprintf("I didn't send the message, %s", err))
|
||||
return true
|
||||
}
|
||||
p.b.Send(c, bot.Message, ch, "I sent a message to them.")
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *SMSPlugin) registerOtherCmd(r bot.Request) bool {
|
||||
ch := r.Msg.Channel
|
||||
if r.Values["name"] == "" || r.Values["number"] == "" {
|
||||
p.b.Send(r.Conn, bot.Message, ch, fmt.Sprintf("if you're trying to register somebody, give me a "+
|
||||
"message of the format: `%s`", regSomeoneRegex))
|
||||
return true
|
||||
}
|
||||
|
||||
return p.reg(r.Conn, ch, r.Values["name"], r.Values["number"])
|
||||
}
|
||||
|
||||
func (p *SMSPlugin) registerSelfCmd(r bot.Request) bool {
|
||||
ch := r.Msg.Channel
|
||||
who := r.Msg.User.Name
|
||||
if r.Values["number"] == "" {
|
||||
p.b.Send(r.Conn, bot.Message, ch, fmt.Sprintf("if you're trying to register a number, give me a "+
|
||||
"message of the format: `%s`", regSelfRegex))
|
||||
}
|
||||
return p.reg(r.Conn, ch, who, r.Values["number"])
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue