catbase/plugins/sms/sms.go

229 lines
6.0 KiB
Go

package sms
import (
"fmt"
"net/http"
"regexp"
"strings"
twilio "github.com/kevinburke/twilio-go"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/config"
)
var plugin *SMSPlugin
type SMSPlugin struct {
b bot.Bot
c *config.Config
}
func New(b bot.Bot) *SMSPlugin {
if plugin != nil {
return plugin
}
plugin = &SMSPlugin{
b: b,
c: b.Config(),
}
plugin.setup()
plugin.registerWeb()
b.Register(plugin, bot.Message, plugin.message)
b.Register(plugin, bot.Help, plugin.help)
return plugin
}
func (p *SMSPlugin) checkNumber(num string) (string, error) {
expectedLen := 10
if len(num) != expectedLen {
return num, fmt.Errorf("invalid number length: %d, expected %d", len(num), expectedLen)
}
return num, nil
}
var regRegex = regexp.MustCompile(`(?i)my sms number is (\d+)`)
var sendRegex = regexp.MustCompile(`(?i)send sms to\s(?P<name>\S+)\s(?P<body>.+)`)
// Send will send a text to a registered user, who
func (p *SMSPlugin) Send(who, message string) error {
num, err := p.get(who)
if err != nil {
return fmt.Errorf("unknown user: %s: %w", who, err)
}
sid := p.c.Get("TWILIOSID", "")
token := p.c.Get("TWILIOTOKEN", "")
myNum := p.c.Get("TWILIONUMBER", "")
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 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
}
num, err := p.checkNumber(subs[1])
if err != nil {
p.b.Send(c, bot.Message, ch, fmt.Sprintf("That number didn't make sense to me: %s", err))
return true
}
me := p.b.WhoAmI()
m := fmt.Sprintf("Hello from %s. You are registered for reminders and you can chat with the channel "+
"by talking to this number", me)
err = p.add(who, num)
if err != nil {
log.Error().Err(err).Msgf("error adding user")
p.b.Send(c, bot.Message, ch, fmt.Sprintf("I didn't send the message, %s", err))
return true
}
err = p.Send(who, m)
if err != nil {
log.Error().Err(err).Msgf("error sending to user")
if err := p.delete(who); err != nil {
log.Error().Err(err).Msgf("error deleting user")
}
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
}
return false
}
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 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.",
p.c.Get("TWILIONUMBER", "unknown"))
m += "\nAll reminders with registered users will be sent to their SMS number."
p.b.Send(c, bot.Message, ch, m)
return true
}
func (p *SMSPlugin) registerWeb() {
http.HandleFunc("/sms/new", p.receive)
}
func (p *SMSPlugin) receive(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(405)
fmt.Fprintf(w, "Incorrect HTTP method")
return
}
if err := r.ParseForm(); err != nil {
log.Error().Err(err).Msgf("could not parse incoming SMS")
return
}
body := r.PostFormValue("Body")
number := strings.TrimPrefix(r.PostFormValue("From"), "+1")
from, err := p.getByNumber(number)
if err != nil {
log.Debug().Err(err).Msgf("could not find user")
from = number
}
m := fmt.Sprintf("SMS From %s: %s", from, body)
chs := p.c.GetArray("channels", []string{})
log.Debug().Msgf("%s to %v", m, chs)
for _, ch := range chs {
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, m)
}
}
func (p *SMSPlugin) add(who, num string) error {
tx, err := p.b.DB().Beginx()
if err != nil {
return err
}
_, err = tx.Exec(`insert or replace into sms (who, num) values (?, ?)`, who, num)
if err != nil {
return err
}
err = tx.Commit()
return err
}
func (p *SMSPlugin) delete(who string) error {
tx, err := p.b.DB().Beginx()
if err != nil {
return err
}
_, err = tx.Exec(`delete from sms where who=?`, who)
if err != nil {
return err
}
err = tx.Commit()
return err
}
func (p *SMSPlugin) get(who string) (string, error) {
num := ""
err := p.b.DB().Get(&num, `select num from sms where who=?`, who)
return num, err
}
func (p *SMSPlugin) getByNumber(num string) (string, error) {
who := ""
err := p.b.DB().Get(&who, `select who from sms where num=?`, num)
return who, err
}
func (p *SMSPlugin) setup() {
_, err := p.b.DB().Exec(`create table if not exists sms (
who string primary key,
num string
)`)
if err != nil {
log.Fatal().Err(err).Msgf("could not create sms table")
}
}