Mercurial > lbo > hg > goe_bot
changeset 45:7a18eb2dd075
Implement reminder handler
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Sat, 10 Dec 2016 17:33:15 +0100 |
parents | 88245ca6ed0e |
children | 52ec77607624 |
files | handler_remind.go handlers.go main.go remind.go sql/remind.go |
diffstat | 5 files changed, 152 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/handler_remind.go Sat Dec 10 17:33:15 2016 +0100 @@ -0,0 +1,115 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "regexp" + "strconv" + "strings" + "time" + + "bitbucket.org/dermesser/goe_bot/sql" +) + +var ( + hhmmRE = regexp.MustCompile(`(\d{1,2}):(\d\d)`) + durRE = regexp.MustCompile(`\+?(\d+)([smhd])`) + dateRE = regexp.MustCompile(`(\d{4})-(\d\d)-(\d\d) (\d{1,2}):(\d\d)`) + dateOfWeekRE = regexp.MustCompile(`(Mo|Di|Mi|Do|Fr|Sa|So) (\d{1,2}):(\d\d)`) +) + +func parseReminderString(s string) time.Time { + now := time.Now().In(time.FixedZone("local", *flagLocalOffset)) + log.Println(s) + + if m := hhmmRE.FindStringSubmatch(s); len(m) >= 3 { + log.Println(m) + h, err1 := strconv.ParseInt(m[1], 10, 32) + m, err2 := strconv.ParseInt(m[2], 10, 32) + + if err1 == nil && err2 == nil { + nowH, nowM, nowS := now.Clock() + timeOfDay := time.Duration(nowH)*time.Hour + time.Duration(nowM)*time.Minute + time.Duration(nowS)*time.Second + midnight := now.Add(-timeOfDay) + // duration since midnight of alert time + alert := time.Duration(h)*time.Hour + time.Duration(m)*time.Minute + + if alert > timeOfDay { // later today + return midnight.Add(alert) + } else { // that time tomorrow + return midnight.Add(24*time.Hour + alert) + } + } else { + return time.Unix(0, 0) + } + } else if m := durRE.FindStringSubmatch(s); len(m) >= 3 { + log.Println(m) + dur, err := strconv.ParseInt(m[1], 10, 32) + + if err != nil { + return time.Unix(0, 0) + } + + if m[2] == "s" { + return now.Add(time.Duration(dur) * time.Second) + } else if m[2] == "m" { + return now.Add(time.Duration(dur) * time.Minute) + } else if m[2] == "h" { + return now.Add(time.Duration(dur) * time.Hour) + } else if m[2] == "d" { + return now.Add(time.Duration(dur) * 24 * time.Hour) + } else { + return time.Unix(0, 0) + } + } + + // rest is not implemented yet + + return time.Unix(0, 0) +} + +// Handler for /remind messages +// Allowed formats: hh:mm (today), +XXs, +XXm, +XXh, +XXd, "yyyy-mm-dd hh:mm", +// {Mo,Di,Mi,Do,Fr,Sa,So} hh:mm, +func reminderHandler(ctx context.Context, msg message) (replyContent, error) { + // TODO: This doesn't work with multi-part formats like dateRE or dateOfWeekRE! + // first part is time + parts := strings.SplitN(msg.Text, " ", 2) + + if len(parts) < 2 { + log.Println("Not enough parts in message:", msg.Text) + return replyContent{text: "_Hilfe (z.B.): /remind 25m Irgendwas_"}, errors.New("bad message format") + } + + alertTime := parseReminderString(parts[0]) + + if alertTime.IsZero() { + return replyContent{text: "Tut mir leid, ich verstehe das Format nicht. Bitte benutze +XX{s,m,h,d} oder hh:mm"}, + errors.New("didn't understand time format") + } + + db, err := backend.Reminders() + + if err != nil { + log.Println("Couldn't get Reminders object:", err) + return replyContent{text: "_Ich konnte mich nicht mit der Datenbank verbinden :(_"}, errors.New("couldn't get Reminders") + } + + r := reminder{ + Text: parts[1], + Owner: msg.From.First_Name + " " + msg.From.Last_Name, + Due: alertTime, + ChatID: msg.Chat.ID, + ReplyTo: msg.Message_ID} + + id, err := db.InsertReminder(sql.Reminder(r)) + + if err != nil { + log.Println("Couldn't insert reminder:", err) + return replyContent{text: "_Ich konnte leider keine Erinnerung setzen._"}, errors.New("couldn't set reminder") + } + + return replyContent{text: fmt.Sprintf("*✓* Erinnerung #%d in %v", id, alertTime.Sub(time.Now()))}, nil +}
--- a/handlers.go Sat Dec 10 17:22:54 2016 +0100 +++ b/handlers.go Sat Dec 10 17:33:15 2016 +0100 @@ -32,7 +32,7 @@ handlers = map[string]handler{ echoCmd: {echoHandler, "Anfrage zurücksenden"}, fortuneCmd: {fortuneHandler, "Glückskeks"}, - remindCmd: {missingHandler, "Wecker"}, + remindCmd: {reminderHandler, "Wecker"}, statusCmd: {statusHandler, "Status anfragen"}, todoCmd: {todoHandler, "Aufgabenliste"}, }
--- a/main.go Sat Dec 10 17:22:54 2016 +0100 +++ b/main.go Sat Dec 10 17:33:15 2016 +0100 @@ -29,6 +29,8 @@ flagDBPort = flag.Uint("dbport", 5432, "Database port") flagDBHost = flag.String("dbhost", "/var/run/postgresql/", "Socket path or address for database") + flagLocalOffset = flag.Int("tz_off", 3600, "Seconds offset from UTC") + // SHARED VARIABLES backend *sql.Storage ) @@ -67,6 +69,9 @@ return } + // Start background reminder thread + go (&reminders{}).reminderChecker() + mux := http.NewServeMux() mux.HandleFunc("/debug", debugHandler) mux.HandleFunc("/hook", updatesHandler)
--- a/remind.go Sat Dec 10 17:22:54 2016 +0100 +++ b/remind.go Sat Dec 10 17:33:15 2016 +0100 @@ -22,8 +22,12 @@ return rm.removeFromDB() } - msg := sendMessage{Chat_ID: rm.ChatID, Parse_Mode: "Markdown", - Reply_To_Message_Id: rm.ReplyTo, Text: rm.Text} + msg := sendMessage{ + Chat_ID: rm.ChatID, + Parse_Mode: "Markdown", + Reply_To_Message_Id: rm.ReplyTo, + Text: "*BEEP BEEP BEEP*", + } rm.Attempts++ err := sendChatMessage(msg) @@ -65,7 +69,6 @@ inner: for { time.Sleep(2 * time.Second) - now := time.Now() events, err := db.SelectDueReminders() @@ -76,10 +79,6 @@ } for _, rm := range events { - if !now.After(rm.Due) { - continue - } - err := reminder(rm).fire() if err != nil {
--- a/sql/remind.go Sat Dec 10 17:22:54 2016 +0100 +++ b/sql/remind.go Sat Dec 10 17:33:15 2016 +0100 @@ -5,14 +5,16 @@ "errors" "log" "time" + + "github.com/lib/pq" ) // prepared statements const ( - // Parameters: 1 = due time, 2 = text, 3 = chat ID, 4 = command message ID + // Parameters: 1 = due time, 2 = description, 3 = owner, 4 = chat ID, 5 = command message ID // Returns: id INTEGER - insertReminder = `INSERT INTO reminders (created, due, description, chat_id, orig_msg_id) - VALUES (now(), $1, $2, $3, $4) RETURNING id` + insertReminder = `INSERT INTO reminders (created, due, description, owner, chat_id, orig_msg_id) + VALUES (now(), $1, $2, $3, $4, $5) RETURNING id` // Parameters: n/a // Returns: id INTEGER, due TIMESTAMP, owner TEXT, description TEXT, chat_id INTEGER, orig_msg_id INTEGER selectDueReminders = `SELECT id, due, owner, description, chat_id, orig_msg_id FROM reminders WHERE NOT done AND now() > due ORDER BY due ASC` @@ -64,6 +66,26 @@ } } +func (r Reminders) InsertReminder(rm Reminder) (uint, error) { + if stmt, ok := r.db.prepared[insertReminder]; ok { + row := stmt.QueryRow(pq.FormatTimestamp(rm.Due), rm.Text, rm.Owner, rm.ChatID, rm.ReplyTo) + + var id uint + + err := row.Scan(&id) + + if err != nil { + log.Println("Couldn't insert reminder:", err) + return 0, err + } + + return id, nil + } else { + log.Println("Couldn't find prepared statement for", insertReminder) + return 0, errors.New("couldn't find prepared statement") + } +} + func (r Reminders) SelectDueReminders() ([]Reminder, error) { if stmt, ok := r.db.prepared[selectDueReminders]; ok { rows, err := stmt.Query()