Software /
code /
prosody-modules
Comparison
mod_cloud_notify/mod_cloud_notify.lua @ 2976:df86ce6bb0b4
Implement dummy body message to indicate high priority push
This adds a dummy body sent alongside the push when the original message
also contained a body to indicate a high priority push.
The body can be configured and its contents are generally meaningless
with most app servers because it will be stripped before pushed to the client.
author | tmolitor <thilo@eightysoft.de> |
---|---|
date | Sun, 01 Apr 2018 23:24:33 +0200 |
parent | 2792:7622ec165cdd |
child | 3010:7ee59f417c16 |
comparison
equal
deleted
inserted
replaced
2975:7eb6fa9b03fd | 2976:df86ce6bb0b4 |
---|---|
3 -- Copyright (C) 2017 Thilo Molitor | 3 -- Copyright (C) 2017 Thilo Molitor |
4 -- | 4 -- |
5 -- This file is MIT/X11 licensed. | 5 -- This file is MIT/X11 licensed. |
6 | 6 |
7 local t_insert = table.insert; | 7 local t_insert = table.insert; |
8 local s_match = string.match; | |
9 local s_sub = string.sub; | |
8 local st = require"util.stanza"; | 10 local st = require"util.stanza"; |
9 local jid = require"util.jid"; | 11 local jid = require"util.jid"; |
10 local dataform = require"util.dataforms".new; | 12 local dataform = require"util.dataforms".new; |
11 local filters = require"util.filters"; | 13 local filters = require"util.filters"; |
12 local hashes = require"util.hashes"; | 14 local hashes = require"util.hashes"; |
15 | 17 |
16 -- configuration | 18 -- configuration |
17 local include_body = module:get_option_boolean("push_notification_with_body", false); | 19 local include_body = module:get_option_boolean("push_notification_with_body", false); |
18 local include_sender = module:get_option_boolean("push_notification_with_sender", false); | 20 local include_sender = module:get_option_boolean("push_notification_with_sender", false); |
19 local max_push_errors = module:get_option_number("push_max_errors", 50); | 21 local max_push_errors = module:get_option_number("push_max_errors", 50); |
22 local dummy_body = module:get_option_string("push_notification_important_body", ""); | |
20 | 23 |
21 local host_sessions = prosody.hosts[module.host].sessions; | 24 local host_sessions = prosody.hosts[module.host].sessions; |
22 local push_errors = {}; | 25 local push_errors = {}; |
23 local id2node = {}; | 26 local id2node = {}; |
24 | 27 |
206 end | 209 end |
207 return true; | 210 return true; |
208 end | 211 end |
209 module:hook("iq-set/self/"..xmlns_push..":disable", push_disable); | 212 module:hook("iq-set/self/"..xmlns_push..":disable", push_disable); |
210 | 213 |
211 -- clone a stanza and strip it | 214 -- Patched version of util.stanza:find() that supports giving stanza names |
212 local function strip_stanza(stanza) | 215 -- without their namespace, allowing for every namespace. |
213 local tags = {}; | 216 local function find(self, path) |
214 local new = { name = stanza.name, attr = { xmlns = stanza.attr.xmlns, type = stanza.attr.type }, tags = tags }; | 217 local pos = 1; |
215 for i=1,#stanza do | 218 local len = #path + 1; |
216 local child = stanza[i]; | 219 |
217 if type(child) == "table" then -- don't add raw text nodes | 220 repeat |
218 if child.name then | 221 local xmlns, name, text; |
219 child = strip_stanza(child); | 222 local char = s_sub(path, pos, pos); |
220 t_insert(tags, child); | 223 if char == "@" then |
221 end | 224 return self.attr[s_sub(path, pos + 1)]; |
222 t_insert(new, child); | 225 elseif char == "{" then |
223 end | 226 xmlns, pos = s_match(path, "^([^}]+)}()", pos + 1); |
224 end | 227 end |
225 return setmetatable(new, st.stanza_mt); | 228 name, text, pos = s_match(path, "^([^@/#]*)([/#]?)()", pos); |
229 name = name ~= "" and name or nil; | |
230 if pos == len then | |
231 if text == "#" then | |
232 local child = xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name); | |
233 return child and child:get_text() or nil; | |
234 end | |
235 return xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name); | |
236 end | |
237 self = xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name); | |
238 until not self | |
239 return nil; | |
240 end | |
241 | |
242 -- is this push a high priority one (this is needed for ios apps not using voip pushes) | |
243 local function is_important(stanza) | |
244 local st_name = stanza and stanza.name or nil; | |
245 if not st_name then return false; end -- nonzas are never important here | |
246 if st_name == "presence" then | |
247 return false; -- same for presences | |
248 elseif st_name == "message" then | |
249 -- unpack carbon copies | |
250 local stanza_direction = "in"; | |
251 local carbon; | |
252 local st_type; | |
253 -- support carbon copied message stanzas having an arbitrary message-namespace or no message-namespace at all | |
254 if not carbon then carbon = find(stanza, "{urn:xmpp:carbons:2}/forwarded/message"); end | |
255 if not carbon then carbon = find(stanza, "{urn:xmpp:carbons:1}/forwarded/message"); end | |
256 stanza_direction = carbon and stanza:child_with_name("sent") and "out" or "in"; | |
257 if carbon then stanza = carbon; end | |
258 st_type = stanza.attr.type; | |
259 | |
260 -- headline message are always not important | |
261 if st_type == "headline" then return false; end | |
262 | |
263 -- carbon copied outgoing messages are not important | |
264 if carbon and stanza_direction == "out" then return false; end | |
265 | |
266 -- We can't check for body contents in encrypted messages, so let's treat them as important | |
267 -- Some clients don't even set a body or an empty body for encrypted messages | |
268 | |
269 -- check omemo https://xmpp.org/extensions/inbox/omemo.html | |
270 if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end | |
271 | |
272 -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html | |
273 if stanza:get_child("x", "jabber:x:encrypted") then return true; end | |
274 | |
275 -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html | |
276 if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end | |
277 | |
278 local body = stanza:get_child_text("body"); | |
279 if st_type == "groupchat" and stanza:get_child_text("subject") then return false; end -- groupchat subjects are not important here | |
280 return body ~= nil and body ~= ""; -- empty bodies are not important | |
281 end | |
282 return false; -- this stanza wasn't one of the above cases --> it is not important, too | |
226 end | 283 end |
227 | 284 |
228 local push_form = dataform { | 285 local push_form = dataform { |
229 { name = "FORM_TYPE"; type = "hidden"; value = "urn:xmpp:push:summary"; }; | 286 { name = "FORM_TYPE"; type = "hidden"; value = "urn:xmpp:push:summary"; }; |
230 { name = "message-count"; type = "text-single"; }; | 287 { name = "message-count"; type = "text-single"; }; |
231 { name = "pending-subscription-count"; type = "text-single"; }; | 288 { name = "pending-subscription-count"; type = "text-single"; }; |
232 { name = "last-message-sender"; type = "jid-single"; }; | 289 { name = "last-message-sender"; type = "jid-single"; }; |
233 { name = "last-message-body"; type = "text-single"; }; | 290 { name = "last-message-body"; type = "text-single"; }; |
234 { name = "last-message-priority"; type = "text-single"; }; | |
235 }; | 291 }; |
236 | 292 |
237 -- http://xmpp.org/extensions/xep-0357.html#publishing | 293 -- http://xmpp.org/extensions/xep-0357.html#publishing |
238 local function handle_notify_request(stanza, node, user_push_services) | 294 local function handle_notify_request(stanza, node, user_push_services) |
239 local pushes = 0; | 295 local pushes = 0; |
267 if stanza and include_sender then | 323 if stanza and include_sender then |
268 form_data["last-message-sender"] = stanza.attr.from; | 324 form_data["last-message-sender"] = stanza.attr.from; |
269 end | 325 end |
270 if stanza and include_body then | 326 if stanza and include_body then |
271 form_data["last-message-body"] = stanza:get_child_text("body"); | 327 form_data["last-message-body"] = stanza:get_child_text("body"); |
328 elseif stanza and dummy_body ~= "" and is_important(stanza) then | |
329 form_data["last-message-body"] = dummy_body; | |
272 end | 330 end |
273 push_publish:add_child(push_form:form(form_data)); | 331 push_publish:add_child(push_form:form(form_data)); |
274 if stanza and push_info.include_payload == "stripped" then | |
275 push_publish:tag("payload", { type = "stripped" }) | |
276 :add_child(strip_stanza(stanza)); | |
277 push_publish:up(); -- / payload | |
278 end | |
279 if stanza and push_info.include_payload == "full" then | |
280 push_publish:tag("payload", { type = "full" }) | |
281 :add_child(st.clone(stanza)); | |
282 push_publish:up(); -- / payload | |
283 end | |
284 push_publish:up(); -- / notification | 332 push_publish:up(); -- / notification |
285 push_publish:up(); -- / publish | 333 push_publish:up(); -- / publish |
286 push_publish:up(); -- / pubsub | 334 push_publish:up(); -- / pubsub |
287 if push_info.options then | 335 if push_info.options then |
288 push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options)); | 336 push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options)); |