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 (
2019-03-10 02:26:11 +00:00
"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
)
2017-05-11 16:40:59 +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
2019-03-10 02:26:11 +00:00
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" `
2021-12-22 03:36:08 +00:00
From string ` db:"fromWho" `
Who string ` db:"toWho" `
2021-12-21 04:31:19 +00:00
What string
2021-12-22 03:36:08 +00:00
When time . Time ` db:"remindWhen" `
2021-12-21 04:31:19 +00:00
Channel string
2016-05-09 17:09:17 +00:00
}
2019-02-05 19:41:38 +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 ( )
2019-03-10 02:26:11 +00:00
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 ,
2019-02-05 19:41:38 +00:00
config : b . Config ( ) ,
2019-03-10 02:26:11 +00:00
when : w ,
2016-05-09 17:09:17 +00:00
}
2017-01-05 14:49:27 +00:00
2017-05-11 16:40:59 +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
2019-02-05 19:41:38 +00:00
b . Register ( plugin , bot . Message , plugin . message )
b . Register ( plugin , bot . Help , plugin . help )
2017-05-11 16:40:59 +00:00
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
2019-03-10 02:26:11 +00:00
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 ( )
2019-03-10 02:26:11 +00:00
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" )
2019-03-10 02:26:11 +00:00
}
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
2019-03-10 02:26:11 +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
2019-03-10 02:26:11 +00:00
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
2017-05-11 16:40:59 +00:00
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-11 16:40:59 +00:00
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
2019-03-10 02:26:11 +00:00
dur2 , err = time . ParseDuration ( parts [ 5 ] )
2017-05-01 15:54:44 +00:00
if err != nil {
2019-03-10 02:26:11 +00:00
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
}
2017-05-11 16:40:59 +00:00
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 : ] , " " )
2019-01-27 21:18:16 +00:00
max := p . config . GetInt ( "Reminder.MaxBatchAdd" , 10 )
2017-05-01 15:54:44 +00:00
for i := 0 ; when . Before ( endTime ) ; i ++ {
2019-01-21 19:24:03 +00:00
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
}
2017-05-11 16:40:59 +00:00
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
2018-10-26 15:01:01 +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." ) )
2018-10-26 15:01:01 +00:00
} 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
2017-05-11 16:40:59 +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 ] ) )
}
}
2017-05-11 16:40:59 +00:00
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 {
2017-05-11 16:40:59 +00:00
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" )
2019-02-05 19:41:38 +00:00
return true
2016-05-09 17:09:17 +00:00
}
2017-05-11 16:40:59 +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" )
2017-05-11 16:40:59 +00:00
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
}
2017-05-11 16:40:59 +00:00
2021-12-20 17:40:10 +00:00
return & reminder
2017-05-11 16:40:59 +00:00
}
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 )
2017-05-11 16:40:59 +00:00
if err != nil {
2021-12-21 04:31:19 +00:00
log . Error ( ) . Err ( err ) . Msgf ( "error creating reminder" )
return err
2017-05-11 16:40:59 +00:00
}
2021-12-21 04:31:19 +00:00
return nil
2017-05-11 16:40:59 +00:00
}
2021-12-21 04:31:19 +00:00
func ( p * ReminderPlugin ) deleteReminder ( id uint64 ) error {
2017-05-11 16:40:59 +00:00
p . mutex . Lock ( )
defer p . mutex . Unlock ( )
2021-12-20 17:40:10 +00:00
err := p . store . Delete ( id , Reminder { } )
2017-05-11 16:40:59 +00:00
return err
}
2021-12-20 17:40:10 +00:00
func ( p * ReminderPlugin ) getRemindersFormatted ( filter , who string ) ( string , error ) {
2019-01-27 21:18:16 +00:00
max := p . config . GetInt ( "Reminder.MaxList" , 25 )
2017-05-11 16:40:59 +00:00
p . mutex . Lock ( )
defer p . mutex . Unlock ( )
2019-01-27 21:18:16 +00:00
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 { } )
2021-12-22 07:53:02 +00:00
err = p . store . Find ( & reminders , ( & bh . Query { } ) . SortBy ( "When" ) . Limit ( max ) )
2021-12-21 19:08:20 +00:00
} else {
log . Debug ( ) . Msgf ( "Looking for reminders where %s eq %s" , filter , who )
total , _ = p . store . Count ( Reminder { } , bh . Where ( filter ) . Eq ( who ) )
2021-12-22 07:53:02 +00:00
err = p . store . Find ( & reminders , bh . Where ( filter ) . Eq ( who ) . SortBy ( "When" ) . Limit ( max ) )
2021-12-20 17:40:10 +00:00
}
2019-01-27 21:18:16 +00:00
if err != nil {
2021-12-21 19:08:20 +00:00
log . Error ( ) . Err ( err ) . Msgf ( "error finding reminders" )
2019-01-27 21:18:16 +00:00
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-22 07:53:02 +00:00
txt += fmt . Sprintf ( "%d) %s -> %s :: %s @ %s\n" , reminder . ID , reminder . From , reminder . Who , reminder . What , reminder . When . Format ( time . RFC3339 ) )
2017-05-11 16:40:59 +00:00
counter ++
}
2019-01-27 21:18:16 +00:00
remaining := total - max
if remaining > 0 {
2021-12-20 17:40:10 +00:00
txt += fmt . Sprintf ( "...%d more...\n" , remaining )
2017-05-11 16:40:59 +00:00
}
2021-12-20 17:40:10 +00:00
return txt , nil
2017-05-11 16:40:59 +00:00
}
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
}
2017-05-11 16:40:59 +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 ( ) ) )
2017-05-11 16:40:59 +00:00
}
}
2019-05-27 23:21:53 +00:00
func reminderer ( c bot . Connector , p * ReminderPlugin ) {
2017-05-11 16:40:59 +00:00
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 )
2017-05-11 16:40:59 +00:00
}
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" )
}
2017-05-11 16:40:59 +00:00
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." )
2017-05-11 16:40:59 +00:00
}
}
p . queueUpNextReminder ( )
}
}