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 (
2017-05-11 16:40:59 +00:00
"errors"
2016-05-09 17:09:17 +00:00
"fmt"
2017-05-11 16:40:59 +00:00
"log"
2017-05-09 14:12:24 +00:00
"strconv"
2016-05-09 17:09:17 +00:00
"strings"
"sync"
"time"
2017-05-11 16:40:59 +00:00
"github.com/jmoiron/sqlx"
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"
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 {
2018-02-05 21:04:40 +00:00
Bot bot . Bot
db * sqlx . DB
mutex * sync . Mutex
timer * time . Timer
config * config . Config
2016-05-09 17:09:17 +00:00
}
type Reminder struct {
2017-05-11 16:40:59 +00:00
id int64
2016-05-09 17:09:17 +00:00
from string
who string
what string
when time . Time
channel string
}
2019-02-05 19:41:38 +00:00
func New ( b bot . Bot ) * ReminderPlugin {
2017-05-11 16:40:59 +00:00
log . SetFlags ( log . LstdFlags | log . Lshortfile )
2019-02-05 19:41:38 +00:00
if _ , err := b . DB ( ) . Exec ( ` create table if not exists reminders (
2017-05-11 16:40:59 +00:00
id integer primary key ,
fromWho string ,
toWho string ,
what string ,
remindWhen string ,
channel string
) ; ` ) ; err != nil {
2019-01-20 20:21:26 +00:00
log . Fatal ( err )
2017-05-11 16:40:59 +00:00
}
2016-05-09 17:09:17 +00:00
dur , _ := time . ParseDuration ( "1h" )
timer := time . NewTimer ( dur )
timer . Stop ( )
plugin := & ReminderPlugin {
2019-02-05 19:41:38 +00:00
Bot : b ,
db : b . DB ( ) ,
2018-02-05 21:04:40 +00:00
mutex : & sync . Mutex { } ,
timer : timer ,
2019-02-05 19:41:38 +00:00
config : b . Config ( ) ,
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
2017-05-11 16:40:59 +00:00
go reminderer ( 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-02-05 19:41:38 +00:00
func ( p * ReminderPlugin ) message ( kind bot . Kind , message msg . Message , args ... interface { } ) bool {
2016-05-09 17:09:17 +00:00
channel := message . Channel
from := message . User . Name
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
2016-05-09 17:09:17 +00:00
dur , err := time . ParseDuration ( parts [ 3 ] )
if err != nil {
2019-02-05 15:54:13 +00:00
p . Bot . Send ( bot . Message , channel , "Easy cowboy, not sure I can parse that duration." )
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" {
//one off reminder
//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 : ] , " " )
2017-05-11 16:40:59 +00:00
p . addReminder ( & Reminder {
id : - 1 ,
2017-05-01 15:54:44 +00:00
from : from ,
who : who ,
what : what ,
when : when ,
channel : channel ,
} )
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
//remind who every dur for dur2 blah
dur2 , err := time . ParseDuration ( parts [ 5 ] )
if err != nil {
2019-02-05 15:54:13 +00:00
p . Bot . Send ( bot . Message , channel , "Easy cowboy, not sure I can parse that duration." )
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-02-05 15:54:13 +00:00
p . Bot . Send ( 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 {
id : int64 ( - 1 ) ,
2017-05-01 15:54:44 +00:00
from : from ,
who : who ,
what : what ,
when : when ,
channel : channel ,
} )
when = when . Add ( dur )
}
} else {
2019-02-05 15:54:13 +00:00
p . Bot . Send ( 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-02-05 15:54:13 +00:00
p . Bot . Send ( bot . Message , channel , fmt . Sprintf ( "Okay. I'll remind you." ) )
2018-10-26 15:01:01 +00:00
} else if doConfirm {
2019-02-05 15:54:13 +00:00
p . Bot . Send ( 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 ] ) )
} else if strings . ToLower ( parts [ 2 ] ) == "from" {
response , err = p . getAllRemindersFromMeFormatted ( channel , strings . ToLower ( parts [ 3 ] ) )
}
}
2017-05-11 16:40:59 +00:00
if err != nil {
2019-02-05 15:54:13 +00:00
p . Bot . Send ( bot . Message , channel , "listing failed." )
2017-04-27 16:47:18 +00:00
} else {
2019-02-05 15:54:13 +00:00
p . Bot . Send ( 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" {
2017-05-11 16:40:59 +00:00
id , err := strconv . ParseInt ( parts [ 2 ] , 10 , 64 )
2017-05-09 14:12:24 +00:00
if err != nil {
2019-02-05 15:54:13 +00:00
p . Bot . Send ( 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-02-05 15:54:13 +00:00
p . Bot . Send ( bot . Message , channel , fmt . Sprintf ( "successfully canceled reminder: %s" , parts [ 2 ] ) )
2017-05-09 14:12:24 +00:00
} else {
2019-02-05 15:54:13 +00:00
p . Bot . Send ( 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-02-05 19:41:38 +00:00
func ( p * ReminderPlugin ) help ( kind bot . Kind , message msg . Message , args ... interface { } ) bool {
p . Bot . Send ( 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 ) RegisterWeb ( ) * string {
return nil
}
2017-05-11 16:40:59 +00:00
func ( p * ReminderPlugin ) getNextReminder ( ) * Reminder {
p . mutex . Lock ( )
defer p . mutex . Unlock ( )
rows , err := p . db . Query ( "select id, fromWho, toWho, what, remindWhen, channel from reminders order by remindWhen asc limit 1;" )
if err != nil {
log . Print ( err )
return nil
}
defer rows . Close ( )
once := false
var reminder * Reminder
for rows . Next ( ) {
if once {
log . Print ( "somehow got multiple rows" )
}
reminder = & Reminder { }
var when string
err := rows . Scan ( & reminder . id , & reminder . from , & reminder . who , & reminder . what , & when , & reminder . channel )
if err != nil {
log . Print ( err )
return nil
}
reminder . when , err = time . Parse ( TIMESTAMP , when )
if err != nil {
log . Print ( err )
return nil
}
once = true
}
return reminder
}
func ( p * ReminderPlugin ) addReminder ( reminder * Reminder ) error {
p . mutex . Lock ( )
defer p . mutex . Unlock ( )
_ , err := p . db . Exec ( ` insert into reminders (fromWho, toWho, what, remindWhen, channel) values (?, ?, ?, ?, ?); ` ,
2018-02-05 21:04:40 +00:00
reminder . from , reminder . who , reminder . what , reminder . when . Format ( TIMESTAMP ) , reminder . channel )
2017-05-11 16:40:59 +00:00
if err != nil {
log . Print ( err )
}
return err
}
func ( p * ReminderPlugin ) deleteReminder ( id int64 ) error {
p . mutex . Lock ( )
defer p . mutex . Unlock ( )
res , err := p . db . Exec ( ` delete from reminders where id = ?; ` , id )
if err != nil {
log . Print ( err )
} else {
if affected , err := res . RowsAffected ( ) ; err != nil {
return err
} else if affected != 1 {
return errors . New ( "didn't delete any rows" )
}
}
return err
}
2019-01-27 21:18:16 +00:00
func ( p * ReminderPlugin ) getRemindersFormatted ( filter string ) ( string , error ) {
max := p . config . GetInt ( "Reminder.MaxList" , 25 )
queryString := fmt . Sprintf ( "select id, fromWho, toWho, what, remindWhen from reminders %s order by remindWhen asc limit %d;" , filter , max )
countString := fmt . Sprintf ( "select COUNT(*) from reminders %s;" , filter )
2017-05-11 16:40:59 +00:00
p . mutex . Lock ( )
defer p . mutex . Unlock ( )
2019-01-27 21:18:16 +00:00
var total int
err := p . db . Get ( & total , countString )
if err != nil {
log . Print ( err )
return "" , nil
}
if total == 0 {
return "no pending reminders" , nil
}
2018-02-05 21:04:40 +00:00
rows , err := p . db . Query ( queryString )
2017-05-11 16:40:59 +00:00
if err != nil {
log . Print ( err )
return "" , nil
}
defer rows . Close ( )
reminders := ""
counter := 1
reminder := & Reminder { }
for rows . Next ( ) {
var when string
err := rows . Scan ( & reminder . id , & reminder . from , & reminder . who , & reminder . what , & when )
if err != nil {
return "" , err
}
reminders += fmt . Sprintf ( "%d) %s -> %s :: %s @ %s (%d)\n" , counter , reminder . from , reminder . who , reminder . what , when , reminder . id )
counter ++
}
2019-01-27 21:18:16 +00:00
remaining := total - max
if remaining > 0 {
reminders += fmt . Sprintf ( "...%d more...\n" , remaining )
2017-05-11 16:40:59 +00:00
}
return reminders , nil
}
2018-02-05 21:04:40 +00:00
func ( p * ReminderPlugin ) getAllRemindersFormatted ( channel string ) ( string , error ) {
2019-01-27 21:18:16 +00:00
return p . getRemindersFormatted ( "" )
2018-02-05 21:04:40 +00:00
}
func ( p * ReminderPlugin ) getAllRemindersFromMeFormatted ( channel , me string ) ( string , error ) {
2019-01-27 21:18:16 +00:00
return p . getRemindersFormatted ( fmt . Sprintf ( "where fromWho = '%s'" , me ) )
2018-02-05 21:04:40 +00:00
}
func ( p * ReminderPlugin ) getAllRemindersToMeFormatted ( channel , me string ) ( string , error ) {
2019-01-27 21:18:16 +00:00
return p . getRemindersFormatted ( fmt . Sprintf ( "where toWho = '%s'" , 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 {
p . timer . Reset ( nextReminder . when . Sub ( time . Now ( ) . UTC ( ) ) )
}
}
func reminderer ( p * ReminderPlugin ) {
for {
<- p . timer . C
reminder := p . getNextReminder ( )
if reminder != nil && time . Now ( ) . UTC ( ) . After ( reminder . when ) {
2018-10-26 17:38:12 +00:00
var message string
2017-05-11 16:40:59 +00:00
if reminder . from == reminder . who {
reminder . from = "you"
2018-10-26 17:38:12 +00:00
message = fmt . Sprintf ( "Hey %s, you wanted to be reminded: %s" , reminder . who , reminder . what )
} else {
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
}
2019-02-05 15:54:13 +00:00
p . Bot . Send ( bot . Message , reminder . channel , message )
2017-05-11 16:40:59 +00:00
2018-02-05 21:04:40 +00:00
if err := p . deleteReminder ( reminder . id ) ; err != nil {
2017-05-11 16:40:59 +00:00
log . Print ( reminder . id )
log . Print ( err )
log . Fatal ( "this will cause problems, we need to stop now." )
}
}
p . queueUpNextReminder ( )
}
}