Skip to content

Commit dab29a8

Browse files
Implement addreminder command
1 parent 8c36acb commit dab29a8

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed

cmd/gatekeeper/command.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ var (
2424
CommandDef = "([a-zA-Z0-9\\-_]+)( +(.*))?"
2525
CommandRegexp = regexp.MustCompile("^ *("+CommandPrefix+") *"+CommandDef+"$")
2626
CommandNoPrefixRegexp = regexp.MustCompile("^ *"+CommandDef+"$")
27+
ReminderDurationRegexpStr = `(\d+)(s|m|h|d|y)`
28+
ReminderArgsRegexpStr = `^((\d+(s|m|h|d|y))+) +(.+)$`
29+
ReminderDurationRegexp = regexp.MustCompile(ReminderDurationRegexpStr)
30+
ReminderArgsRegexp = regexp.MustCompile(ReminderArgsRegexpStr)
2731
Commit = func() string {
2832
if info, ok := debug.ReadBuildInfo(); ok {
2933
for _, setting := range info.Settings {
@@ -752,6 +756,43 @@ func EvalBuiltinCommand(db *sql.DB, command Command, env CommandEnvironment, con
752756
}
753757
// TODO: report "added" instead of "updated" when the command didn't exist but was newly created
754758
env.SendMessage(fmt.Sprintf("%s command %s is updated", env.AtAuthor(), name))
759+
case "addreminder":
760+
args := ReminderArgsRegexp.FindStringSubmatch(command.Args)
761+
if (args == nil) {
762+
env.SendMessage(env.AtAuthor() + " Coudn't parse the reminder arguments, expected `" + ReminderArgsRegexpStr + "`")
763+
return
764+
}
765+
766+
durationStr := args[1]
767+
message := args[4]
768+
769+
delay := time.Duration(0)
770+
for _, match := range ReminderDurationRegexp.FindAllStringSubmatch(durationStr, -1) {
771+
ammount, err := strconv.ParseInt(match[1], 10, 64)
772+
if err != nil {
773+
log.Println("Reminder duration parsing: ", err)
774+
env.SendMessage(env.AtAuthor() + " Delay ammount overflows." + "\n")
775+
return
776+
}
777+
unit := match[2]
778+
779+
d, ok := MulDurationSafe(ammount, UnitDurations[unit])
780+
if !ok {
781+
env.SendMessage(env.AtAuthor() + "Duration specified caused an overflow.")
782+
return
783+
}
784+
785+
delay, ok = AddDurationSafe(delay, d)
786+
if !ok {
787+
env.SendMessage(env.AtAuthor() + "Duration specified caused an overflow.")
788+
return
789+
}
790+
}
791+
792+
SetReminder(env, Reminder{
793+
Delay: delay,
794+
Message: message,
795+
})
755796
case "delcmd":
756797
if !env.IsAuthorAdmin() {
757798
env.SendMessage(env.AtAuthor() + " only for " + env.AtAdmin())

cmd/gatekeeper/reminders.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"sync"
6+
"time"
7+
"log"
8+
"math"
9+
)
10+
11+
type Reminder struct {
12+
Message string
13+
Delay time.Duration
14+
}
15+
16+
var UnitDurations = map[string]time.Duration{
17+
"s": time.Second,
18+
"m": time.Minute,
19+
"h": time.Hour,
20+
"d": 24 * time.Hour,
21+
"y": time.Duration(float64(365.2425) * float64(24 * time.Hour)),
22+
}
23+
24+
type UserId = string
25+
26+
var mutex = &sync.Mutex{}
27+
var reminders = map[UserId]context.CancelFunc{}
28+
29+
func SetReminder(env CommandEnvironment, r Reminder) {
30+
if !validateReminder(env, r) {
31+
return
32+
}
33+
34+
mutex.Lock()
35+
defer mutex.Unlock()
36+
37+
ctx, cancel := context.WithCancel(context.Background())
38+
39+
cancelOldReminder, ok := reminders[env.AuthorUserId()]
40+
if ok {
41+
cancelOldReminder()
42+
}
43+
44+
reminders[env.AuthorUserId()] = cancel
45+
46+
go func() {
47+
timer := time.NewTimer(r.Delay)
48+
defer timer.Stop()
49+
50+
select {
51+
case <-timer.C:
52+
env.SendMessage(env.AtAuthor() + " " + r.Message + "\n")
53+
case <-ctx.Done():
54+
env.SendMessage(env.AtAuthor() + " Old reminder has been canceled: '" + r.Message + "'")
55+
}
56+
}()
57+
58+
env.SendMessage(env.AtAuthor() + " Reminder has been successfully set to fire in " + r.Delay.String() + ".")
59+
}
60+
61+
func validateReminder(env CommandEnvironment, r Reminder) bool {
62+
if (r.Delay < 1 * time.Second) {
63+
log.Println("Smol reminder delay: " + r.Delay.String())
64+
env.SendMessage(env.AtAuthor() + " Delay specified has an unexpected duration, check logs." + "\n")
65+
return false
66+
}
67+
68+
if (env.IsAuthorAdmin()) {
69+
return true
70+
}
71+
72+
if (len(r.Message) > 255) {
73+
env.SendMessage(env.AtAuthor() + " Message body must be under 255 characters long" + "\n")
74+
return false
75+
}
76+
77+
return true
78+
}
79+
80+
func AddDurationSafe(a, b time.Duration) (time.Duration, bool) {
81+
if b > 0 && a > time.Duration(math.MaxInt64)-b {
82+
return 0, false
83+
}
84+
if b < 0 && a < time.Duration(math.MinInt64)-b {
85+
return 0, false
86+
}
87+
return a + b, true
88+
}
89+
90+
func MulDurationSafe(ammount int64, d time.Duration) (time.Duration, bool) {
91+
if d == 0 || ammount == 0 {
92+
return 0, true
93+
}
94+
if ammount > 0 {
95+
if d > time.Duration(math.MaxInt64)/time.Duration(ammount) {
96+
return 0, false
97+
}
98+
} else {
99+
if d < time.Duration(math.MinInt64)/time.Duration(ammount) {
100+
return 0, false
101+
}
102+
}
103+
return d * time.Duration(ammount), true
104+
}

0 commit comments

Comments
 (0)