2017-05-04 14:54:38 +00:00
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
"unicode"
2018-04-03 10:07:06 +00:00
"github.com/hamcha/tg"
2017-05-04 14:54:38 +00:00
)
var remindpath * string
type Reminder struct {
2017-05-04 15:11:41 +00:00
TargetID int64
When int64
Text string
Reference * ReminderReference
}
type ReminderReference struct {
Chat int64
Message int64
2017-05-04 14:54:38 +00:00
}
var reminders map [ string ] Reminder
const ReminderMaxDuration = time . Hour * 24 * 30 * 3
2018-05-24 15:48:35 +00:00
func remind_init ( ) {
2017-05-04 14:54:38 +00:00
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
}
2017-05-31 17:08:47 +00:00
for id := range reminders {
2017-05-04 14:54:38 +00:00
go runreminder ( id )
}
log . Printf ( "[remind] Loaded %d pending reminders from %s\n" , len ( reminders ) , * remindpath )
}
2018-05-24 15:48:35 +00:00
func remind_message ( broker * tg . Broker , update tg . APIMessage ) {
2017-05-04 14:54:38 +00:00
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 )
2017-05-04 15:15:04 +00:00
// Little hack to allow text-less reminders with replies
if len ( parts ) == 2 && update . ReplyTo != nil {
parts = append ( parts , "" )
}
2017-05-04 14:54:38 +00:00
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])" , & 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 )
2017-05-04 15:11:41 +00:00
reminder := Reminder {
2017-05-04 14:54:38 +00:00
TargetID : update . User . UserID ,
When : timestamp . Unix ( ) ,
Text : message ,
}
2017-05-04 15:11:41 +00:00
if update . ReplyTo != nil {
reminder . Reference = & ReminderReference {
Chat : update . Chat . ChatID ,
Message : update . ReplyTo . MessageID ,
}
}
reminders [ id ] = reminder
2017-05-04 14:54:38 +00:00
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 ) {
2017-05-31 17:03:48 +00:00
// In case this is called in the init, wait until the broker is available
for broker == nil {
// Wait
2017-05-31 17:08:47 +00:00
time . Sleep ( time . Second )
2017-05-31 17:03:48 +00:00
}
2017-05-04 14:54:38 +00:00
// 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 )
2017-05-04 15:11:41 +00:00
if r . Reference != nil {
broker . ForwardMessage ( & tg . APIChat { ChatID : r . TargetID } , tg . APIMessage { MessageID : r . Reference . Message , Chat : & tg . APIChat { ChatID : r . Reference . Chat } } )
}
2017-05-04 14:54:38 +00:00
// 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
}
2018-11-02 13:17:38 +00:00
func scanMixedDelay ( str string ) ( bool , time . Time , error ) {
remaining := str
2017-05-04 14:54:38 +00:00
now := time . Now ( )
num := 0
sep := ' '
2018-11-02 13:17:38 +00:00
for len ( remaining ) > 1 {
scanned , 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)" )
}
now = now . Add ( dur )
remaining = remaining [ scanned : ]
}
return true , now , nil
}
func parseDuration ( date string ) ( time . Time , error ) {
now := time . Now ( )
2017-05-04 14:54:38 +00:00
hour := now . Hour ( )
min := now . Minute ( )
sec := now . Second ( )
day := now . Day ( )
month := now . Month ( )
year := now . Year ( )
2018-11-02 13:17:38 +00:00
dayunspecified := false
isDurationFmt , duration , err := scanMixedDelay ( date )
2017-05-04 14:54:38 +00:00
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 ) ) :
2017-05-04 15:16:23 +00:00
sec = 0
2017-05-04 14:54:38 +00:00
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 ( )
2018-11-02 13:17:38 +00:00
dayunspecified = true
2017-05-04 14:54:38 +00:00
case isSscanfValid ( fmt . Sscanf ( date , "%d:%d" , & hour , & min ) ) :
day = now . Day ( )
month = now . Month ( )
year = now . Year ( )
2017-05-04 15:16:23 +00:00
sec = 0
2018-11-02 13:17:38 +00:00
dayunspecified = true
case isDurationFmt :
return duration , err
2017-05-04 14:54:38 +00:00
default :
return now , fmt . Errorf ( "Non capisco quando dovrei ricordartelo!" )
}
2017-05-04 19:04:47 +00:00
loc , _ := time . LoadLocation ( "Local" )
targetDate := time . Date ( year , month , day , hour , min , sec , 0 , loc )
2017-05-04 14:54:38 +00:00
if targetDate . Before ( now ) {
2018-11-02 13:17:38 +00:00
// 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!" )
}
2017-05-04 14:54:38 +00:00
}
if targetDate . After ( now . Add ( ReminderMaxDuration ) ) {
return now , fmt . Errorf ( "Non credo riuscirei a ricordarmi qualcosa per così tanto" )
}
return targetDate , nil
}