This repository has been archived on 2023-07-05. You can view files and clone it, but cannot push or open issues or pull requests.
clessy/mods/remind.go

278 lines
7.4 KiB
Go

package main
import (
"encoding/json"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
"unicode"
"git.fromouter.space/hamcha/tg"
)
var remindpath *string
type Reminder struct {
TargetID int64
When int64
Text string
Reference *ReminderReference
}
type ReminderReference struct {
Chat int64
Message int64
}
var reminders map[string]Reminder
const reminderMaxDuration = time.Hour * 24 * 30 * 3
var defaultLocation *time.Location = nil
func remind_init() {
var err error
defaultLocation, err = time.LoadLocation("Europe/Rome")
if err != nil {
log.Fatalf("[remind] Something is really wrong: %s\n", err.Error())
return
}
reminders = make(map[string]Reminder)
file, err := os.Open(*remindpath)
if err != nil {
return
}
defer file.Close()
err = json.NewDecoder(file).Decode(&reminders)
if err != nil {
log.Println("[remind] WARN: Could not load pending reminders (malformed or unreadable file): " + err.Error())
return
}
for id := range reminders {
go runreminder(id)
}
log.Printf("[remind] Loaded %d pending reminders from %s\n", len(reminders), *remindpath)
}
func remind_message(broker *tg.Broker, update tg.APIMessage) {
if isCommand(update, "ricordami") {
// Supported formats:
// Xs/m/h/d => in X seconds/minutes/hours/days
// HH:MM => at HH:MM (24 hour format)
// HH:MM:SS => at HH:MM:SS (24 hour format)
// dd/mm/yyyy => same hour, specific date
// dd/mm/yyyy-HH:MM => specific hour, specific dat
// dd/mm/yyyy-HH:MM:SS => specific hour, specific date
parts := strings.SplitN(*(update.Text), " ", 3)
// Little hack to allow text-less reminders with replies
if len(parts) == 2 && update.ReplyTo != nil {
parts = append(parts, "")
}
if len(parts) < 3 {
broker.SendTextMessage(update.Chat, "<b>Sintassi</b>\n/ricordami <i>[quando]</i> Messaggio\n\n<b>Formati supportati per [quando]</b>:\n 10s 10m 10h 10d (secondi/minuti/ore/giorni)\n 13:20 15:55:01 (ora dello stesso giorno, formato 24h)\n 11/02/2099 11/02/2099-11:20:01 (giorno diverso, stessa ora [1] o specifica [2])", &tg.MessageOptions{
ReplyID: &update.MessageID,
})
return
}
format := parts[1]
message := parts[2]
loc := defaultLocation
if uloc, ok := tzlocs[update.User.UserID]; ok {
loc = uloc
}
timestamp, err := parseDuration(format, loc)
if err != nil {
broker.SendTextMessage(update.Chat, err.Error(), &tg.MessageOptions{
ReplyID: &update.MessageID,
})
return
}
id := strconv.FormatInt(update.Chat.ChatID, 36) + "-" + strconv.FormatInt(update.MessageID, 36)
reminder := Reminder{
TargetID: update.User.UserID,
When: timestamp.Unix(),
Text: message,
}
if update.ReplyTo != nil {
reminder.Reference = &ReminderReference{
Chat: update.Chat.ChatID,
Message: update.ReplyTo.MessageID,
}
}
reminders[id] = reminder
savereminder()
go runreminder(id)
whenday := "più tardi"
_, todaym, todayd := time.Now().Date()
_, targetm, targetd := timestamp.Date()
if todaym != targetm || todayd != targetd {
whenday = "il " + timestamp.In(loc).Format("2/1")
}
whentime := "alle " + timestamp.In(loc).Format("15:04:05")
broker.SendTextMessage(update.Chat, "Ok, vedrò di avvisarti "+whenday+" "+whentime, &tg.MessageOptions{
ReplyID: &update.MessageID,
})
}
if isCommand(update, "reminders") {
// Should only work in private chats
if update.Chat.Type != tg.ChatTypePrivate {
broker.SendTextMessage(update.Chat, "Per favore chiedimi in privato dei reminder", &tg.MessageOptions{
ReplyID: &update.MessageID,
})
return
}
useritems := []Reminder{}
for _, reminder := range reminders {
if reminder.TargetID == update.User.UserID {
useritems = append(useritems, reminder)
}
}
if len(useritems) == 0 {
broker.SendTextMessage(update.Chat, "Non ci sono reminder in coda per te", &tg.MessageOptions{
ReplyID: &update.MessageID,
})
} else {
broker.SendTextMessage(update.Chat, fmt.Sprintf("Ci sono <b>%d</b> reminder in coda per te", len(useritems)), &tg.MessageOptions{
ReplyID: &update.MessageID,
})
}
}
}
func runreminder(id string) {
// In case this is called in the init, wait until the broker is available
for broker == nil {
// Wait
time.Sleep(time.Second)
}
// Get reminder
r := reminders[id]
remaining := r.When - time.Now().Unix()
if remaining > 0 {
// Wait remaining time
time.Sleep(time.Second * time.Duration(remaining))
}
// Remind!
broker.SendTextMessage(&tg.APIChat{ChatID: r.TargetID}, "<b>Heyla! Mi avevi chiesto di ricordarti questo:</b>\n"+r.Text, nil)
if r.Reference != nil {
broker.ForwardMessage(&tg.APIChat{ChatID: r.TargetID}, tg.APIMessage{MessageID: r.Reference.Message, Chat: &tg.APIChat{ChatID: r.Reference.Chat}})
}
// Delete reminder from pending list and save list to disk
delete(reminders, id)
savereminder()
}
func savereminder() {
file, err := os.Create(*remindpath)
if err != nil {
log.Println("[remind] WARN: Could not open pending reminders file: " + err.Error())
return
}
err = json.NewEncoder(file).Encode(reminders)
if err != nil {
log.Println("[macro] WARN: Could not save pending reminders into file: " + err.Error())
}
}
func isSscanfValid(n int, err error) bool {
return err == nil
}
func scanMixedDelay(str string) (bool, time.Time, error) {
remaining := str
now := time.Now()
num := 0
sep := ' '
for len(remaining) > 1 {
_, err := fmt.Sscanf(remaining, "%d%c", &num, &sep)
if err != nil {
return false, now, err
}
dur := time.Duration(num)
switch unicode.ToLower(sep) {
case 's':
dur *= time.Second
case 'm':
dur *= time.Minute
case 'h':
dur *= time.Hour
case 'd':
dur *= time.Hour * 24
default:
return true, now, fmt.Errorf("La durata ha una unità che non conosco, usa una di queste: s (secondi) m (minuti) h (ore) d (giorni)")
}
return true, now.Add(dur), nil
}
/* TO FIX
now = now.Add(dur)
remaining = remaining[scanned:]
}
return true, now, nil
*/
return false, now, nil
}
func parseDuration(date string, loc *time.Location) (time.Time, error) {
now := time.Now().In(loc)
hour := now.Hour()
min := now.Minute()
sec := now.Second()
day := now.Day()
month := now.Month()
year := now.Year()
dayunspecified := false
isDurationFmt, duration, err := scanMixedDelay(date)
switch {
case isSscanfValid(fmt.Sscanf(date, "%d/%d/%d-%d:%d:%d", &day, &month, &year, &hour, &min, &sec)):
case isSscanfValid(fmt.Sscanf(date, "%d/%d/%d-%d:%d", &day, &month, &year, &hour, &min)):
sec = 0
case isSscanfValid(fmt.Sscanf(date, "%d/%d/%d", &day, &month, &year)):
hour = now.Hour()
min = now.Minute()
sec = now.Second()
case isSscanfValid(fmt.Sscanf(date, "%d:%d:%d", &hour, &min, &sec)):
day = now.Day()
month = now.Month()
year = now.Year()
dayunspecified = true
case isSscanfValid(fmt.Sscanf(date, "%d:%d", &hour, &min)):
day = now.Day()
month = now.Month()
year = now.Year()
sec = 0
dayunspecified = true
case isDurationFmt:
return duration, err
default:
return now, fmt.Errorf("Non capisco quando dovrei ricordartelo!")
}
targetDate := time.Date(year, month, day, hour, min, sec, 0, loc)
if targetDate.Before(now) {
// If day was not specified assume tomorrow
if dayunspecified {
targetDate = targetDate.Add(time.Hour * 24)
} else {
return now, fmt.Errorf("Non posso ricordarti cose nel passato!")
}
}
if targetDate.After(now.Add(reminderMaxDuration)) {
return now, fmt.Errorf("Non credo riuscirei a ricordarmi qualcosa per così tanto")
}
return targetDate, nil
}