Software / code / prosody-modules
Comparison
mod_reminders/mod_reminders.lua @ 3917:3e19c25ff93e
mod_reminders: Initial commit for supporting Reminders ProtoXEP
See https://tenak.net/marcos/xeps/reminders.html
| author | marc0s <marcos.devera@quobis.com> |
|---|---|
| date | Thu, 27 Feb 2020 00:59:17 +0100 |
| child | 3921:9eabd68b8e48 |
comparison
equal
deleted
inserted
replaced
| 3916:f1e28dcb3791 | 3917:3e19c25ff93e |
|---|---|
| 1 -- mod_reminders | |
| 2 -- | |
| 3 -- Copyright (C) 2020 Marcos de Vera Piquero <marcos@tenak.net> | |
| 4 -- | |
| 5 -- This file is MIT/X11 licensed. | |
| 6 -- | |
| 7 -- A module to support ProtoXEP: Reminders | |
| 8 -- | |
| 9 | |
| 10 local id = require "util.id" | |
| 11 local datetime = require"util.datetime"; | |
| 12 local errors = require"util.error"; | |
| 13 local jid = require"util.jid"; | |
| 14 local st = require"util.stanza"; | |
| 15 local os_time = os.time; | |
| 16 | |
| 17 local xmlns_reminders = "urn:xmpp:reminders:0"; | |
| 18 | |
| 19 local reminders_store = module:open_store(xmlns_reminders, "keyval"); | |
| 20 | |
| 21 local reminders_errors = { | |
| 22 missing_fields = { | |
| 23 type = "modify"; | |
| 24 condition = "bad-request"; | |
| 25 text = "Missing required value for date or text"; | |
| 26 }; | |
| 27 invalid_dateformat = { | |
| 28 type = "modify"; | |
| 29 condition = "bad-request"; | |
| 30 text = "Invalid date format"; | |
| 31 }; | |
| 32 past_date = { | |
| 33 type = "modify"; | |
| 34 condition = "gone"; | |
| 35 text = "Reminder date is in the past"; | |
| 36 }; | |
| 37 store_error = { | |
| 38 type = "cancel"; | |
| 39 condition = "internal-server-error"; | |
| 40 text = "Unable to persist data"; | |
| 41 }; | |
| 42 }; | |
| 43 | |
| 44 local function reminder_error (name) | |
| 45 return errors.new(name, nil, reminders_errors); | |
| 46 end | |
| 47 | |
| 48 local function store_reminder (reminder) | |
| 49 -- pushes the reminder to the store, and nothing else | |
| 50 return reminders_store:set(reminder.id, reminder); | |
| 51 end | |
| 52 | |
| 53 local function delete_reminder (reminder_id) | |
| 54 -- empties the store for the given reminder_id | |
| 55 return reminders_store:set(reminder_id, nil); | |
| 56 end | |
| 57 | |
| 58 local function get_reminder (reminder_id) | |
| 59 return reminders_store:get(reminder_id); | |
| 60 end | |
| 61 | |
| 62 local function send_reminder (reminder) | |
| 63 -- actually delivers the <message /> with the reminder to the user | |
| 64 local bare = jid.bare(reminder.jid); | |
| 65 module:log("debug", "Sending reminder %s to %s", reminder.id, bare); | |
| 66 local message = st.message({ from = "localhost"; to = bare; id = id.short() }) | |
| 67 :tag("reminder", {xmlns = xmlns_reminders}) | |
| 68 :add_child(reminder.text) | |
| 69 :tag("date"):text(datetime.datetime(reminder.date)):up(); | |
| 70 module:send(message); | |
| 71 return delete_reminder(reminder.id) | |
| 72 end | |
| 73 | |
| 74 local function schedule_reminder (reminder) | |
| 75 -- schedule a module:add_timer for the given reminder | |
| 76 module:log("debug", "Scheduling reminder to datetime %s", reminder.date); | |
| 77 local now = os_time(); | |
| 78 local when = reminder.date; | |
| 79 local delay = when - now; | |
| 80 module:log("debug", "Reminder text: %s", reminder.text); | |
| 81 local function callback () | |
| 82 send_reminder(reminder) | |
| 83 end | |
| 84 module:add_timer(delay, callback); | |
| 85 end | |
| 86 | |
| 87 local function process_reminders_store () | |
| 88 -- retrieve all reminders in the store and schedule them | |
| 89 for reminder_id in reminders_store:users() do | |
| 90 module:log("debug", "Found stored reminder %s", reminder_id); | |
| 91 local reminder = get_reminder(reminder_id); | |
| 92 if reminder.date and reminder.text then | |
| 93 local text = st.deserialize(reminder.text) | |
| 94 module:log("debug", "Read reminder %s", reminder.id); | |
| 95 -- cleanup missed reminders | |
| 96 if reminder.date < os_time() then | |
| 97 module:log("debug", "Deleting outdated reminder %s", reminder.id) | |
| 98 delete_reminder(reminder.id) | |
| 99 end | |
| 100 schedule_reminder({ | |
| 101 date = reminder.date; | |
| 102 id = reminder.id; | |
| 103 jid = reminder.jid; | |
| 104 text = text; | |
| 105 }) | |
| 106 else | |
| 107 delete_reminder(reminder_id); | |
| 108 end | |
| 109 end | |
| 110 end | |
| 111 | |
| 112 local function create_reminder (jid, reminder) | |
| 113 local rem = st.clone(reminder); | |
| 114 local date = reminder:get_child("date"); | |
| 115 local text = reminder:get_child("text"); | |
| 116 if date == nil or text == nil then | |
| 117 return nil, reminder_error("missing_fields") | |
| 118 end | |
| 119 local now = os_time(); | |
| 120 local _, parsed_date = pcall(datetime.parse, date:get_text()); | |
| 121 if parsed_date == nil then | |
| 122 return nil, reminder_error("invalid_dateformat") | |
| 123 end | |
| 124 if parsed_date < now then | |
| 125 return nil, reminder_error("past_date"), nil | |
| 126 end | |
| 127 rem.attr.id = id.medium(); | |
| 128 local data = { | |
| 129 id = rem.attr.id; | |
| 130 jid = jid; | |
| 131 text = text; | |
| 132 date = parsed_date; | |
| 133 } | |
| 134 local stored = store_reminder(data); | |
| 135 if not stored then | |
| 136 return nil, reminder_error("store_error") | |
| 137 end | |
| 138 schedule_reminder(data); | |
| 139 return rem | |
| 140 end | |
| 141 | |
| 142 local function handle_set (event) | |
| 143 local origin, stanza = event.origin, event.stanza | |
| 144 local reminder = stanza:get_child("reminder", xmlns_reminders); | |
| 145 if reminder.attr.id ~= nil and reminder:get_child("date") == nil then | |
| 146 -- delete existing reminder | |
| 147 local ok = delete_reminder(reminder.attr.id); | |
| 148 if ok then | |
| 149 module:log("debug", "reminder %s deleted", reminder.attr.id); | |
| 150 origin.send(st.reply(stanza):add_child(reminder)); | |
| 151 else | |
| 152 module:log("debug", "failed to delete reminder %s", reminder.attr.id); | |
| 153 origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); | |
| 154 end | |
| 155 return true; | |
| 156 else | |
| 157 -- create new reminder | |
| 158 local jid = stanza.attr.from | |
| 159 local created, err = create_reminder(jid, reminder); | |
| 160 if err ~= nil then | |
| 161 origin.send(st.error_reply(stanza, err)) | |
| 162 return true; | |
| 163 else | |
| 164 origin.send(st.reply(stanza):add_child(created)) | |
| 165 return true; | |
| 166 end | |
| 167 end | |
| 168 origin.send(st.error_reply(stanza, "modify", "bad-request")); | |
| 169 return true; | |
| 170 end | |
| 171 | |
| 172 | |
| 173 -- load saved reminders and set timers | |
| 174 process_reminders_store(); | |
| 175 | |
| 176 module:hook("iq-set/host/"..xmlns_reminders..":reminder", handle_set) | |
| 177 module:add_feature(xmlns_reminders); | |
| 178 module:log("debug", "Module loaded"); |