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"); |