diff --git a/mods/main.go b/mods/main.go index dfbb389..fd5f996 100644 --- a/mods/main.go +++ b/mods/main.go @@ -52,6 +52,10 @@ var mods = map[string]Mod{ OnInit: initstt, OnMessage: stt, }, + "remind": { + OnInit: initremind, + OnMessage: remind, + }, } func initmods() { @@ -102,6 +106,7 @@ func main() { gillmt = flag.String("gillmt", "gill.ttf", "Path to gill.ttf (Gill Sans MT font)") sourcesans = flag.String("sourcesans", "source.ttf", "Path to source.ttf (Source Sans Pro font)") macropath = flag.String("macropath", "macros.json", "Path to macros db (JSON)") + remindpath = flag.String("remindpath", "reminders.json", "Path to reminder db (JSON)") proverbi = flag.String("proverbi", "proverbi.txt", "Path to proverbi pairs (separated by /)") wittoken = flag.String("wit", "", "Wit.ai token") gapifile = flag.String("gapi", "gapi.json", "Google API Service Credentials file") @@ -127,10 +132,11 @@ func main() { initmods() var err error - broker, err = tg.CreateBrokerClient(*brokerAddr, dispatch) - if err != nil { - panic(err) - } + broker, err = tg.ConnectToBroker(*brokerAddr) + assert(err) + defer broker.Close() + + assert(tg.RunBrokerClient(broker, dispatch)) } func assert(err error) { diff --git a/mods/remind.go b/mods/remind.go new file mode 100644 index 0000000..a1c6377 --- /dev/null +++ b/mods/remind.go @@ -0,0 +1,175 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "strconv" + "strings" + "time" + "unicode" + + "github.com/hamcha/clessy/tg" +) + +var remindpath *string + +type Reminder struct { + TargetID int64 + When int64 + Text string +} + +var reminders map[string]Reminder + +const ReminderMaxDuration = time.Hour * 24 * 30 * 3 + +func initremind() { + 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(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) + if len(parts) < 3 { + broker.SendTextMessage(update.Chat, "Sintassi\n/ricordami [quando] Messaggio\n\nFormati supportati per [quando]:\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])", &update.MessageID) + return + } + + format := parts[1] + message := parts[2] + + timestamp, err := parseDuration(format) + if err != nil { + broker.SendTextMessage(update.Chat, err.Error(), &update.MessageID) + return + } + + id := strconv.FormatInt(update.Chat.ChatID, 36) + "-" + strconv.FormatInt(update.MessageID, 36) + reminders[id] = Reminder{ + TargetID: update.User.UserID, + When: timestamp.Unix(), + Text: message, + } + savereminder() + go runreminder(id) + + whenday := "più tardi" + _, todaym, todayd := time.Now().Date() + _, targetm, targetd := timestamp.Date() + if todaym != targetm || todayd != targetd { + whenday = "il " + timestamp.Format("2/1") + } + whentime := "alle " + timestamp.Format("15:04:05") + broker.SendTextMessage(update.Chat, "Ok, vedrò di avvisarti "+whenday+" "+whentime, &update.MessageID) + } +} + +func runreminder(id string) { + // 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}, "Heyla! Mi avevi chiesto di ricordarti questo:\n"+r.Text, nil) + // 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 parseDuration(date string) (time.Time, error) { + now := time.Now() + num := 0 + sep := ' ' + hour := now.Hour() + min := now.Minute() + sec := now.Second() + day := now.Day() + month := now.Month() + year := now.Year() + 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 = now.Second() + 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() + case isSscanfValid(fmt.Sscanf(date, "%d:%d", &hour, &min)): + day = now.Day() + month = now.Month() + year = now.Year() + sec = now.Second() + case isSscanfValid(fmt.Sscanf(date, "%d%c", &num, &sep)): + 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 now, fmt.Errorf("La durata ha una unità che non conosco, usa una di queste: s (secondi) m (minuti) h (ore) d (giorni)") + } + return now.Add(dur), nil + default: + return now, fmt.Errorf("Non capisco quando dovrei ricordartelo!") + } + targetDate := time.Date(year, month, day, hour, min, sec, 0, now.Location()) + if targetDate.Before(now) { + 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 +}