catbase/plugins/reminder/reminder.go

349 lines
9.2 KiB
Go
Raw Normal View History

2016-05-09 17:09:17 +00:00
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
package reminder
import (
"github.com/olebedev/when"
"github.com/olebedev/when/rules/common"
"github.com/olebedev/when/rules/en"
2021-12-20 17:40:10 +00:00
bh "github.com/timshannon/bolthold"
2016-05-09 17:09:17 +00:00
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
2017-05-01 15:54:44 +00:00
"github.com/velour/catbase/config"
2021-12-20 17:40:10 +00:00
"sync"
"time"
)
import (
"fmt"
"github.com/rs/zerolog/log"
"strconv"
"strings"
2020-05-17 14:49:38 +00:00
"github.com/velour/catbase/plugins/sms"
2016-05-09 17:09:17 +00:00
)
const (
TIMESTAMP = "2006-01-02 15:04:05"
)
2016-05-09 17:09:17 +00:00
type ReminderPlugin struct {
2019-05-27 23:21:53 +00:00
bot bot.Bot
2021-12-20 17:40:10 +00:00
store *bh.Store
2018-02-05 21:04:40 +00:00
mutex *sync.Mutex
timer *time.Timer
config *config.Config
when *when.Parser
2016-05-09 17:09:17 +00:00
}
type Reminder struct {
2021-12-21 04:31:19 +00:00
ID uint64 `boltholdKey:"ID"`
From string
Who string
What string
When time.Time
Channel string
2016-05-09 17:09:17 +00:00
}
func New(b bot.Bot) *ReminderPlugin {
2016-05-09 17:09:17 +00:00
dur, _ := time.ParseDuration("1h")
timer := time.NewTimer(dur)
timer.Stop()
w := when.New(nil)
w.Add(en.All...)
w.Add(common.All...)
2016-05-09 17:09:17 +00:00
plugin := &ReminderPlugin{
2019-05-27 23:21:53 +00:00
bot: b,
2021-12-20 17:40:10 +00:00
store: b.Store(),
2018-02-05 21:04:40 +00:00
mutex: &sync.Mutex{},
timer: timer,
config: b.Config(),
when: w,
2016-05-09 17:09:17 +00:00
}
2017-01-05 14:49:27 +00:00
plugin.queueUpNextReminder()
2016-05-09 17:09:17 +00:00
2019-05-27 23:21:53 +00:00
go reminderer(b.DefaultConnector(), plugin)
2016-05-09 17:09:17 +00:00
b.Register(plugin, bot.Message, plugin.message)
b.Register(plugin, bot.Help, plugin.help)
return plugin
2016-05-09 17:09:17 +00:00
}
2019-05-27 23:21:53 +00:00
func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
2016-05-09 17:09:17 +00:00
channel := message.Channel
from := message.User.Name
var dur, dur2 time.Duration
t, err := p.when.Parse(message.Body, time.Now())
// Allowing err to fallthrough for other parsing
if t != nil && err == nil {
2019-03-10 02:55:01 +00:00
t2 := t.Time.Sub(time.Now()).String()
message.Body = string(message.Body[0:t.Index]) + t2 + string(message.Body[t.Index+len(t.Text):])
2019-03-10 02:55:01 +00:00
log.Debug().
Str("body", message.Body).
Str("text", t.Text).
Msg("Got time request")
}
2016-05-09 17:09:17 +00:00
parts := strings.Fields(message.Body)
2016-05-09 17:27:28 +00:00
if len(parts) >= 5 {
2016-05-09 17:09:17 +00:00
if strings.ToLower(parts[0]) == "remind" {
who := parts[1]
2016-08-07 01:22:03 +00:00
if who == "me" {
who = from
}
2017-05-01 15:54:44 +00:00
dur, err = time.ParseDuration(parts[3])
2016-05-09 17:09:17 +00:00
if err != nil {
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.")
2016-05-09 17:09:17 +00:00
return true
}
2017-05-01 15:54:44 +00:00
operator := strings.ToLower(parts[2])
doConfirm := true
if operator == "in" || operator == "at" || operator == "on" {
2017-05-01 15:54:44 +00:00
//one off reminder
2021-12-21 04:31:19 +00:00
//remind Who in dur blah
when := time.Now().UTC().Add(dur)
2017-05-01 15:54:44 +00:00
what := strings.Join(parts[4:], " ")
2021-12-21 19:08:20 +00:00
rem := &Reminder{
2021-12-21 04:31:19 +00:00
From: from,
Who: who,
What: what,
When: when,
Channel: channel,
2021-12-21 19:08:20 +00:00
}
log.Debug().Msgf("Adding reminder: %v", rem)
p.addReminder(rem)
all := []Reminder{}
p.store.Find(&all, &bh.Query{})
log.Debug().Msgf("All reminders: %v", all)
2017-05-01 15:54:44 +00:00
} else if operator == "every" && strings.ToLower(parts[4]) == "for" {
//batch add, especially for reminding msherms to buy a kit
2021-12-21 04:31:19 +00:00
//remind Who every dur for dur2 blah
dur2, err = time.ParseDuration(parts[5])
2017-05-01 15:54:44 +00:00
if err != nil {
log.Error().Err(err)
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.")
2017-05-01 15:54:44 +00:00
return true
}
when := time.Now().UTC().Add(dur)
endTime := time.Now().UTC().Add(dur2)
2017-05-01 15:54:44 +00:00
what := strings.Join(parts[6:], " ")
max := p.config.GetInt("Reminder.MaxBatchAdd", 10)
2017-05-01 15:54:44 +00:00
for i := 0; when.Before(endTime); i++ {
if i >= max {
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.")
2017-05-01 15:54:44 +00:00
doConfirm = false
break
}
p.addReminder(&Reminder{
2021-12-21 04:31:19 +00:00
From: from,
Who: who,
What: what,
When: when,
Channel: channel,
2017-05-01 15:54:44 +00:00
})
when = when.Add(dur)
}
} else {
2021-12-21 04:31:19 +00:00
p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I comprehend What you're asking.")
2017-05-01 15:54:44 +00:00
return true
}
2016-05-09 17:09:17 +00:00
if doConfirm && from == who {
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("Okay. I'll remind you."))
} else if doConfirm {
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("Sure %s, I'll remind %s.", from, who))
2017-05-01 15:54:44 +00:00
}
2016-05-09 17:09:17 +00:00
p.queueUpNextReminder()
2016-05-09 17:09:17 +00:00
return true
}
2018-02-05 21:04:40 +00:00
} else if len(parts) >= 2 && strings.ToLower(parts[0]) == "list" && strings.ToLower(parts[1]) == "reminders" {
var response string
var err error
if len(parts) == 2 {
response, err = p.getAllRemindersFormatted(channel)
} else if len(parts) == 4 {
if strings.ToLower(parts[2]) == "to" {
response, err = p.getAllRemindersToMeFormatted(channel, strings.ToLower(parts[3]))
2021-12-21 19:08:20 +00:00
} else if strings.ToLower(parts[2]) == "from" {
2018-02-05 21:04:40 +00:00
response, err = p.getAllRemindersFromMeFormatted(channel, strings.ToLower(parts[3]))
}
}
if err != nil {
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, "listing failed.")
2017-04-27 16:47:18 +00:00
} else {
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, response)
2017-04-27 16:47:18 +00:00
}
return true
2017-05-09 14:12:24 +00:00
} else if len(parts) == 3 && strings.ToLower(parts[0]) == "cancel" && strings.ToLower(parts[1]) == "reminder" {
2021-12-21 04:31:19 +00:00
id, err := strconv.ParseUint(parts[2], 10, 64)
2017-05-09 14:12:24 +00:00
if err != nil {
2021-12-21 04:31:19 +00:00
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("couldn't parse ID: %s", parts[2]))
2017-05-09 14:12:24 +00:00
} else {
err := p.deleteReminder(id)
if err == nil {
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("successfully canceled reminder: %s", parts[2]))
2017-05-09 14:12:24 +00:00
} else {
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("failed to find and cancel reminder: %s", parts[2]))
2017-05-09 14:12:24 +00:00
}
}
return true
2016-05-09 17:09:17 +00:00
}
return false
}
2019-05-27 23:21:53 +00:00
func (p *ReminderPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
p.bot.Send(c, bot.Message, message.Channel, "Pester someone with a reminder. Try \"remind <user> in <duration> message\".\n\nUnsure about duration syntax? Check https://golang.org/pkg/time/#ParseDuration")
return true
2016-05-09 17:09:17 +00:00
}
func (p *ReminderPlugin) getNextReminder() *Reminder {
p.mutex.Lock()
defer p.mutex.Unlock()
2021-12-20 17:40:10 +00:00
reminder := Reminder{}
2021-12-21 19:08:20 +00:00
err := p.store.FindOne(&reminder, (&bh.Query{}).SortBy("When"))
if err != nil && err != bh.ErrNotFound {
log.Error().Err(err).Msgf("aggregate reminders failed")
return nil
2021-12-21 19:08:20 +00:00
} else if err == bh.ErrNotFound {
2021-12-21 04:31:19 +00:00
log.Error().Msg("No next reminder in system.")
return nil
}
2021-12-20 17:40:10 +00:00
return &reminder
}
func (p *ReminderPlugin) addReminder(reminder *Reminder) error {
p.mutex.Lock()
defer p.mutex.Unlock()
2021-12-20 17:40:10 +00:00
err := p.store.Insert(bh.NextSequence(), reminder)
if err != nil {
2021-12-21 04:31:19 +00:00
log.Error().Err(err).Msgf("error creating reminder")
return err
}
2021-12-21 04:31:19 +00:00
return nil
}
2021-12-21 04:31:19 +00:00
func (p *ReminderPlugin) deleteReminder(id uint64) error {
p.mutex.Lock()
defer p.mutex.Unlock()
2021-12-20 17:40:10 +00:00
err := p.store.Delete(id, Reminder{})
return err
}
2021-12-20 17:40:10 +00:00
func (p *ReminderPlugin) getRemindersFormatted(filter, who string) (string, error) {
max := p.config.GetInt("Reminder.MaxList", 25)
p.mutex.Lock()
defer p.mutex.Unlock()
2021-12-21 19:08:20 +00:00
var total int
var err error
reminders := []Reminder{}
2021-12-20 17:40:10 +00:00
if filter == "" || who == "" {
2021-12-21 19:08:20 +00:00
total, _ = p.store.Count(Reminder{}, &bh.Query{})
err = p.store.Find(&reminders, (&bh.Query{}).SortBy("ID").Limit(max))
} else {
log.Debug().Msgf("Looking for reminders where %s eq %s", filter, who)
total, _ = p.store.Count(Reminder{}, bh.Where(filter).Eq(who))
err = p.store.Find(&reminders, bh.Where(filter).Eq(who).SortBy("ID").Limit(max))
2021-12-20 17:40:10 +00:00
}
if err != nil {
2021-12-21 19:08:20 +00:00
log.Error().Err(err).Msgf("error finding reminders")
return "", nil
}
if total == 0 {
return "no pending reminders", nil
}
2021-12-20 17:40:10 +00:00
txt := ""
for counter, reminder := range reminders {
2021-12-21 19:08:20 +00:00
txt += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", reminder.ID, reminder.From, reminder.Who, reminder.What, reminder.When, reminder.ID)
counter++
}
remaining := total - max
if remaining > 0 {
2021-12-20 17:40:10 +00:00
txt += fmt.Sprintf("...%d more...\n", remaining)
}
2021-12-20 17:40:10 +00:00
return txt, nil
}
2018-02-05 21:04:40 +00:00
func (p *ReminderPlugin) getAllRemindersFormatted(channel string) (string, error) {
2021-12-20 17:40:10 +00:00
return p.getRemindersFormatted("", "")
2018-02-05 21:04:40 +00:00
}
func (p *ReminderPlugin) getAllRemindersFromMeFormatted(channel, me string) (string, error) {
2021-12-21 19:08:20 +00:00
return p.getRemindersFormatted("From", me)
2018-02-05 21:04:40 +00:00
}
func (p *ReminderPlugin) getAllRemindersToMeFormatted(channel, me string) (string, error) {
2021-12-21 19:08:20 +00:00
return p.getRemindersFormatted("Who", me)
2018-02-05 21:04:40 +00:00
}
func (p *ReminderPlugin) queueUpNextReminder() {
nextReminder := p.getNextReminder()
if nextReminder != nil {
2021-12-21 04:31:19 +00:00
p.timer.Reset(nextReminder.When.Sub(time.Now().UTC()))
}
}
2019-05-27 23:21:53 +00:00
func reminderer(c bot.Connector, p *ReminderPlugin) {
for {
<-p.timer.C
reminder := p.getNextReminder()
2021-12-21 04:31:19 +00:00
if reminder != nil && time.Now().UTC().After(reminder.When) {
2018-10-26 17:38:12 +00:00
var message string
2021-12-21 04:31:19 +00:00
if reminder.From == reminder.Who {
reminder.From = "you"
message = fmt.Sprintf("Hey %s, you wanted to be reminded: %s", reminder.Who, reminder.What)
2018-10-26 17:38:12 +00:00
} else {
2021-12-21 04:31:19 +00:00
message = fmt.Sprintf("Hey %s, %s wanted you to be reminded: %s", reminder.Who, reminder.From, reminder.What)
}
2021-12-21 04:31:19 +00:00
p.bot.Send(c, bot.Message, reminder.Channel, message)
2020-05-17 14:49:38 +00:00
smsPlugin := sms.New(p.bot)
2021-12-21 04:31:19 +00:00
if err := smsPlugin.Send(reminder.Who, message); err != nil {
2020-05-17 14:49:38 +00:00
log.Error().Err(err).Msgf("could not send reminder")
}
2021-12-21 19:08:20 +00:00
log.Debug().Msgf("trying to delete: %v", reminder)
2021-12-21 04:31:19 +00:00
if err := p.deleteReminder(reminder.ID); err != nil {
log.Fatal().
Uint64("ID", reminder.ID).
2019-03-07 16:35:42 +00:00
Err(err).
Msg("this will cause problems, we need to stop now.")
}
}
p.queueUpNextReminder()
}
}