3319
+ − 1 -- XEP-XXX: MUC Push Notifications
+ − 2 -- Copyright (C) 2015-2016 Kim Alvefur
+ − 3 -- Copyright (C) 2017-2018 Thilo Molitor
+ − 4 --
+ − 5 -- This file is MIT/X11 licensed.
+ − 6
+ − 7 local s_match = string.match ;
+ − 8 local s_sub = string.sub ;
+ − 9 local os_time = os.time ;
+ − 10 local next = next ;
+ − 11 local st = require "util.stanza" ;
+ − 12 local jid = require "util.jid" ;
+ − 13 local dataform = require "util.dataforms" . new ;
+ − 14 local hashes = require "util.hashes" ;
+ − 15
+ − 16 local xmlns_push = "urn:xmpp:push:0" ;
+ − 17
+ − 18 -- configuration
+ − 19 local include_body = module : get_option_boolean ( "push_notification_with_body" , false );
+ − 20 local include_sender = module : get_option_boolean ( "push_notification_with_sender" , false );
+ − 21 local max_push_errors = module : get_option_number ( "push_max_errors" , 16 );
+ − 22 local max_push_devices = module : get_option_number ( "push_max_devices" , 5 );
+ − 23 local dummy_body = module : get_option_string ( "push_notification_important_body" , "New Message!" );
+ − 24
+ − 25 local host_sessions = prosody . hosts [ module . host ]. sessions ;
+ − 26 local push_errors = {};
+ − 27 local id2node = {};
+ − 28
+ − 29 module : depends ( "muc" );
+ − 30
+ − 31 -- ordered table iterator, allow to iterate on the natural order of the keys of a table,
+ − 32 -- see http://lua-users.org/wiki/SortedIteration
+ − 33 local function __genOrderedIndex ( t )
+ − 34 local orderedIndex = {}
+ − 35 for key in pairs ( t ) do
+ − 36 table.insert ( orderedIndex , key )
+ − 37 end
+ − 38 -- sort in reverse order (newest one first)
+ − 39 table.sort ( orderedIndex , function ( a , b )
+ − 40 if a == nil or t [ a ] == nil or b == nil or t [ b ] == nil then return false end
+ − 41 -- only one timestamp given, this is the newer one
+ − 42 if t [ a ]. timestamp ~= nil and t [ b ]. timestamp == nil then return true end
+ − 43 if t [ a ]. timestamp == nil and t [ b ]. timestamp ~= nil then return false end
+ − 44 -- both timestamps given, sort normally
+ − 45 if t [ a ]. timestamp ~= nil and t [ b ]. timestamp ~= nil then return t [ a ]. timestamp > t [ b ]. timestamp end
+ − 46 return false -- normally not reached
+ − 47 end )
+ − 48 return orderedIndex
+ − 49 end
+ − 50 local function orderedNext ( t , state )
+ − 51 -- Equivalent of the next function, but returns the keys in timestamp
+ − 52 -- order. We use a temporary ordered key table that is stored in the
+ − 53 -- table being iterated.
+ − 54
+ − 55 local key = nil
+ − 56 --print("orderedNext: state = "..tostring(state) )
+ − 57 if state == nil then
+ − 58 -- the first time, generate the index
+ − 59 t . __orderedIndex = __genOrderedIndex ( t )
+ − 60 key = t . __orderedIndex [ 1 ]
+ − 61 else
+ − 62 -- fetch the next value
+ − 63 for i = 1 , # t . __orderedIndex do
+ − 64 if t . __orderedIndex [ i ] == state then
+ − 65 key = t . __orderedIndex [ i + 1 ]
+ − 66 end
+ − 67 end
+ − 68 end
+ − 69
+ − 70 if key then
+ − 71 return key , t [ key ]
+ − 72 end
+ − 73
+ − 74 -- no more value to return, cleanup
+ − 75 t . __orderedIndex = nil
+ − 76 return
+ − 77 end
+ − 78 local function orderedPairs ( t )
+ − 79 -- Equivalent of the pairs() function on tables. Allows to iterate
+ − 80 -- in order
+ − 81 return orderedNext , t , nil
+ − 82 end
+ − 83
+ − 84 -- small helper function to return new table with only "maximum" elements containing only the newest entries
+ − 85 local function reduce_table ( table , maximum )
+ − 86 local count = 0 ;
+ − 87 local result = {};
+ − 88 for key , value in orderedPairs ( table ) do
+ − 89 count = count + 1 ;
+ − 90 if count > maximum then break end
+ − 91 result [ key ] = value ;
+ − 92 end
+ − 93 return result ;
+ − 94 end
+ − 95
+ − 96 -- For keeping state across reloads while caching reads
+ − 97 local push_store = ( function ()
+ − 98 local store = module : open_store ();
+ − 99 local push_services = {};
+ − 100 local api = {};
+ − 101 function api : get ( user )
+ − 102 if not push_services [ user ] then
+ − 103 local err ;
+ − 104 push_services [ user ], err = store : get ( user );
+ − 105 if not push_services [ user ] and err then
+ − 106 module : log ( "warn" , "Error reading push notification storage for user '%s': %s" , user , tostring ( err ));
+ − 107 push_services [ user ] = {};
+ − 108 return push_services [ user ], false ;
+ − 109 end
+ − 110 end
+ − 111 if not push_services [ user ] then push_services [ user ] = {} end
+ − 112 return push_services [ user ], true ;
+ − 113 end
+ − 114 function api : set ( user , data )
+ − 115 push_services [ user ] = reduce_table ( data , max_push_devices );
+ − 116 local ok , err = store : set ( user , push_services [ user ]);
+ − 117 if not ok then
+ − 118 module : log ( "error" , "Error writing push notification storage for user '%s': %s" , user , tostring ( err ));
+ − 119 return false ;
+ − 120 end
+ − 121 return true ;
+ − 122 end
+ − 123 function api : set_identifier ( user , push_identifier , data )
+ − 124 local services = self : get ( user );
+ − 125 services [ push_identifier ] = data ;
+ − 126 return self : set ( user , services );
+ − 127 end
+ − 128 return api ;
+ − 129 end )();
+ − 130
+ − 131
+ − 132 -- Forward declarations, as both functions need to reference each other
+ − 133 local handle_push_success , handle_push_error ;
+ − 134
+ − 135 function handle_push_error ( event )
+ − 136 local stanza = event . stanza ;
+ − 137 local error_type , condition = stanza : get_error ();
+ − 138 local node = id2node [ stanza . attr . id ];
+ − 139 if node == nil then return false ; end -- unknown stanza? Ignore for now!
+ − 140 local from = stanza . attr . from ;
+ − 141 local user_push_services = push_store : get ( node );
+ − 142 local changed = false ;
+ − 143
+ − 144 for push_identifier , _ in pairs ( user_push_services ) do
+ − 145 local stanza_id = hashes . sha256 ( push_identifier , true );
+ − 146 if stanza_id == stanza . attr . id then
+ − 147 if user_push_services [ push_identifier ] and user_push_services [ push_identifier ]. jid == from and error_type ~= "wait" then
+ − 148 push_errors [ push_identifier ] = push_errors [ push_identifier ] + 1 ;
+ − 149 module : log ( "info" , "Got error of type '%s' (%s) for identifier '%s': "
+ − 150 .. "error count for this identifier is now at %s" , error_type , condition , push_identifier ,
+ − 151 tostring ( push_errors [ push_identifier ]));
+ − 152 if push_errors [ push_identifier ] >= max_push_errors then
+ − 153 module : log ( "warn" , "Disabling push notifications for identifier '%s'" , push_identifier );
+ − 154 -- remove push settings from sessions
+ − 155 if host_sessions [ node ] then
+ − 156 for _ , session in pairs ( host_sessions [ node ]. sessions ) do
+ − 157 if session . push_identifier == push_identifier then
+ − 158 session . push_identifier = nil ;
+ − 159 session . push_settings = nil ;
+ − 160 session . first_hibernated_push = nil ;
+ − 161 end
+ − 162 end
+ − 163 end
+ − 164 -- save changed global config
+ − 165 changed = true ;
+ − 166 user_push_services [ push_identifier ] = nil
+ − 167 push_errors [ push_identifier ] = nil ;
+ − 168 -- unhook iq handlers for this identifier (if possible)
+ − 169 if module . unhook then
+ − 170 module : unhook ( "iq-error/host/" .. stanza_id , handle_push_error );
+ − 171 module : unhook ( "iq-result/host/" .. stanza_id , handle_push_success );
+ − 172 id2node [ stanza_id ] = nil ;
+ − 173 end
+ − 174 end
+ − 175 elseif user_push_services [ push_identifier ] and user_push_services [ push_identifier ]. jid == from and error_type == "wait" then
+ − 176 module : log ( "debug" , "Got error of type '%s' (%s) for identifier '%s': "
+ − 177 .. "NOT increasing error count for this identifier" , error_type , condition , push_identifier );
+ − 178 end
+ − 179 end
+ − 180 end
+ − 181 if changed then
+ − 182 push_store : set ( node , user_push_services );
+ − 183 end
+ − 184 return true ;
+ − 185 end
+ − 186
+ − 187 function handle_push_success ( event )
+ − 188 local stanza = event . stanza ;
+ − 189 local node = id2node [ stanza . attr . id ];
+ − 190 if node == nil then return false ; end -- unknown stanza? Ignore for now!
+ − 191 local from = stanza . attr . from ;
+ − 192 local user_push_services = push_store : get ( node );
+ − 193
+ − 194 for push_identifier , _ in pairs ( user_push_services ) do
+ − 195 if hashes . sha256 ( push_identifier , true ) == stanza . attr . id then
+ − 196 if user_push_services [ push_identifier ] and user_push_services [ push_identifier ]. jid == from and push_errors [ push_identifier ] > 0 then
+ − 197 push_errors [ push_identifier ] = 0 ;
+ − 198 module : log ( "debug" , "Push succeeded, error count for identifier '%s' is now at %s again" , push_identifier , tostring ( push_errors [ push_identifier ]));
+ − 199 end
+ − 200 end
+ − 201 end
+ − 202 return true ;
+ − 203 end
+ − 204
+ − 205 -- http://xmpp.org/extensions/xep-0357.html#disco
+ − 206 local function account_dico_info ( event )
+ − 207 ( event . reply or event . stanza ): tag ( "feature" , { var = xmlns_push }): up ();
+ − 208 end
+ − 209 module : hook ( "account-disco-info" , account_dico_info );
+ − 210
+ − 211 -- http://xmpp.org/extensions/xep-0357.html#enabling
+ − 212 local function push_enable ( event )
+ − 213 local origin , stanza = event . origin , event . stanza ;
+ − 214 local enable = stanza . tags [ 1 ];
+ − 215 origin . log ( "debug" , "Attempting to enable push notifications" );
+ − 216 -- MUST contain a 'jid' attribute of the XMPP Push Service being enabled
+ − 217 local push_jid = enable . attr . jid ;
+ − 218 -- SHOULD contain a 'node' attribute
+ − 219 local push_node = enable . attr . node ;
+ − 220 -- CAN contain a 'include_payload' attribute
+ − 221 local include_payload = enable . attr . include_payload ;
+ − 222 if not push_jid then
+ − 223 origin . log ( "debug" , "MUC Push notification enable request missing the 'jid' field" );
+ − 224 origin . send ( st . error_reply ( stanza , "modify" , "bad-request" , "Missing jid" ));
+ − 225 return true ;
+ − 226 end
+ − 227 local publish_options = enable : get_child ( "x" , "jabber:x:data" );
+ − 228 if not publish_options then
+ − 229 -- Could be intentional
+ − 230 origin . log ( "debug" , "No publish options in request" );
+ − 231 end
+ − 232 local push_identifier = push_jid .. "<" .. ( push_node or "" );
+ − 233 local push_service = {
+ − 234 jid = push_jid ;
+ − 235 node = push_node ;
+ − 236 include_payload = include_payload ;
+ − 237 options = publish_options and st . preserialize ( publish_options );
+ − 238 timestamp = os_time ();
+ − 239 };
+ − 240 local ok = push_store : set_identifier ( origin . username .. "@" .. origin . host , push_identifier , push_service );
+ − 241 if not ok then
+ − 242 origin . send ( st . error_reply ( stanza , "wait" , "internal-server-error" ));
+ − 243 else
+ − 244 origin . push_identifier = push_identifier ;
+ − 245 origin . push_settings = push_service ;
+ − 246 origin . first_hibernated_push = nil ;
+ − 247 origin . log ( "info" , "MUC Push notifications enabled for %s by %s (%s)" ,
+ − 248 tostring ( stanza . attr . to ),
+ − 249 tostring ( stanza . attr . from ),
+ − 250 tostring ( origin . push_identifier )
+ − 251 );
+ − 252 origin . send ( st . reply ( stanza ));
+ − 253 end
+ − 254 return true ;
+ − 255 end
+ − 256 module : hook ( "iq-set/host/" .. xmlns_push .. ":enable" , push_enable );
+ − 257
+ − 258
+ − 259 -- http://xmpp.org/extensions/xep-0357.html#disabling
+ − 260 local function push_disable ( event )
+ − 261 local origin , stanza = event . origin , event . stanza ;
+ − 262 local push_jid = stanza . tags [ 1 ]. attr . jid ; -- MUST include a 'jid' attribute
+ − 263 local push_node = stanza . tags [ 1 ]. attr . node ; -- A 'node' attribute MAY be included
+ − 264 if not push_jid then
+ − 265 origin . send ( st . error_reply ( stanza , "modify" , "bad-request" , "Missing jid" ));
+ − 266 return true ;
+ − 267 end
+ − 268 local user_push_services = push_store : get ( origin . username );
+ − 269 for key , push_info in pairs ( user_push_services ) do
+ − 270 if push_info . jid == push_jid and ( not push_node or push_info . node == push_node ) then
+ − 271 origin . log ( "info" , "Push notifications disabled (%s)" , tostring ( key ));
+ − 272 if origin . push_identifier == key then
+ − 273 origin . push_identifier = nil ;
+ − 274 origin . push_settings = nil ;
+ − 275 origin . first_hibernated_push = nil ;
+ − 276 end
+ − 277 user_push_services [ key ] = nil ;
+ − 278 push_errors [ key ] = nil ;
+ − 279 if module . unhook then
+ − 280 module : unhook ( "iq-error/host/" .. key , handle_push_error );
+ − 281 module : unhook ( "iq-result/host/" .. key , handle_push_success );
+ − 282 id2node [ key ] = nil ;
+ − 283 end
+ − 284 end
+ − 285 end
+ − 286 local ok = push_store : set ( origin . username , user_push_services );
+ − 287 if not ok then
+ − 288 origin . send ( st . error_reply ( stanza , "wait" , "internal-server-error" ));
+ − 289 else
+ − 290 origin . send ( st . reply ( stanza ));
+ − 291 end
+ − 292 return true ;
+ − 293 end
+ − 294 module : hook ( "iq-set/host/" .. xmlns_push .. ":disable" , push_disable );
+ − 295
+ − 296 -- Patched version of util.stanza:find() that supports giving stanza names
+ − 297 -- without their namespace, allowing for every namespace.
+ − 298 local function find ( self , path )
+ − 299 local pos = 1 ;
+ − 300 local len = # path + 1 ;
+ − 301
+ − 302 repeat
+ − 303 local xmlns , name , text ;
+ − 304 local char = s_sub ( path , pos , pos );
+ − 305 if char == "@" then
+ − 306 return self . attr [ s_sub ( path , pos + 1 )];
+ − 307 elseif char == "{" then
+ − 308 xmlns , pos = s_match ( path , "^([^}]+)}()" , pos + 1 );
+ − 309 end
+ − 310 name , text , pos = s_match ( path , "^([^@/#]*)([/#]?)()" , pos );
+ − 311 name = name ~= "" and name or nil ;
+ − 312 if pos == len then
+ − 313 if text == "#" then
+ − 314 local child = xmlns ~= nil and self : get_child ( name , xmlns ) or self : child_with_name ( name );
+ − 315 return child and child : get_text () or nil ;
+ − 316 end
+ − 317 return xmlns ~= nil and self : get_child ( name , xmlns ) or self : child_with_name ( name );
+ − 318 end
+ − 319 self = xmlns ~= nil and self : get_child ( name , xmlns ) or self : child_with_name ( name );
+ − 320 until not self
+ − 321 return nil ;
+ − 322 end
+ − 323
+ − 324 -- is this push a high priority one (this is needed for ios apps not using voip pushes)
+ − 325 local function is_important ( stanza )
+ − 326 local st_name = stanza and stanza . name or nil ;
+ − 327 if not st_name then return false ; end -- nonzas are never important here
+ − 328 if st_name == "presence" then
+ − 329 return false ; -- same for presences
+ − 330 elseif st_name == "message" then
+ − 331 -- unpack carbon copies
+ − 332 local stanza_direction = "in" ;
+ − 333 local carbon ;
+ − 334 local st_type ;
+ − 335 -- support carbon copied message stanzas having an arbitrary message-namespace or no message-namespace at all
+ − 336 if not carbon then carbon = find ( stanza , "{urn:xmpp:carbons:2}/forwarded/message" ); end
+ − 337 if not carbon then carbon = find ( stanza , "{urn:xmpp:carbons:1}/forwarded/message" ); end
+ − 338 stanza_direction = carbon and stanza : child_with_name ( "sent" ) and "out" or "in" ;
+ − 339 if carbon then stanza = carbon ; end
+ − 340 st_type = stanza . attr . type ;
+ − 341
+ − 342 -- headline message are always not important
+ − 343 if st_type == "headline" then return false ; end
+ − 344
+ − 345 -- carbon copied outgoing messages are not important
+ − 346 if carbon and stanza_direction == "out" then return false ; end
+ − 347
+ − 348 -- We can't check for body contents in encrypted messages, so let's treat them as important
+ − 349 -- Some clients don't even set a body or an empty body for encrypted messages
+ − 350
+ − 351 -- check omemo https://xmpp.org/extensions/inbox/omemo.html
+ − 352 if stanza : get_child ( "encrypted" , "eu.siacs.conversations.axolotl" ) or stanza : get_child ( "encrypted" , "urn:xmpp:omemo:0" ) then return true ; end
+ − 353
+ − 354 -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html
+ − 355 if stanza : get_child ( "x" , "jabber:x:encrypted" ) then return true ; end
+ − 356
+ − 357 -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html
+ − 358 if stanza : get_child ( "openpgp" , "urn:xmpp:openpgp:0" ) then return true ; end
+ − 359
+ − 360 local body = stanza : get_child_text ( "body" );
+ − 361 if st_type == "groupchat" and stanza : get_child_text ( "subject" ) then return false ; end -- groupchat subjects are not important here
+ − 362 return body ~= nil and body ~= "" ; -- empty bodies are not important
+ − 363 end
+ − 364 return false ; -- this stanza wasn't one of the above cases --> it is not important, too
+ − 365 end
+ − 366
+ − 367 local push_form = dataform {
+ − 368 { name = "FORM_TYPE" ; type = "hidden" ; value = "urn:xmpp:push:summary" ; };
+ − 369 { name = "message-count" ; type = "text-single" ; };
+ − 370 { name = "pending-subscription-count" ; type = "text-single" ; };
+ − 371 { name = "last-message-sender" ; type = "jid-single" ; };
+ − 372 { name = "last-message-body" ; type = "text-single" ; };
+ − 373 };
+ − 374
+ − 375 -- http://xmpp.org/extensions/xep-0357.html#publishing
+ − 376 local function handle_notify_request ( stanza , node , user_push_services , log_push_decline )
+ − 377 local pushes = 0 ;
+ − 378 if not user_push_services or next ( user_push_services ) == nil then return pushes end
+ − 379
+ − 380 -- XXX: customized
+ − 381 local body = stanza : get_child_text ( "body" );
+ − 382 if not body then
+ − 383 return pushes ;
+ − 384 end
+ − 385
+ − 386 for push_identifier , push_info in pairs ( user_push_services ) do
+ − 387 local send_push = true ; -- only send push to this node when not already done for this stanza or if no stanza is given at all
+ − 388 if stanza then
+ − 389 if not stanza . _push_notify then stanza . _push_notify = {}; end
+ − 390 if stanza . _push_notify [ push_identifier ] then
+ − 391 if log_push_decline then
+ − 392 module : log ( "debug" , "Already sent push notification for %s@%s to %s (%s)" , node , module . host , push_info . jid , tostring ( push_info . node ));
+ − 393 end
+ − 394 send_push = false ;
+ − 395 end
+ − 396 stanza . _push_notify [ push_identifier ] = true ;
+ − 397 end
+ − 398
+ − 399 if send_push then
+ − 400 -- construct push stanza
+ − 401 local stanza_id = hashes . sha256 ( push_identifier , true );
+ − 402 local push_publish = st . iq ({ to = push_info . jid , from = module . host , type = "set" , id = stanza_id })
+ − 403 : tag ( "pubsub" , { xmlns = "http://jabber.org/protocol/pubsub" })
+ − 404 : tag ( "publish" , { node = push_info . node })
+ − 405 : tag ( "item" )
+ − 406 : tag ( "notification" , { xmlns = xmlns_push });
+ − 407 local form_data = {
+ − 408 -- hardcode to 1 because other numbers are just meaningless (the XEP does not specify *what exactly* to count)
+ − 409 [ "message-count" ] = "1" ;
+ − 410 };
+ − 411 if stanza and include_sender then
+ − 412 form_data [ "last-message-sender" ] = stanza . attr . from ;
+ − 413 end
+ − 414 if stanza and include_body then
+ − 415 form_data [ "last-message-body" ] = stanza : get_child_text ( "body" );
+ − 416 elseif stanza and dummy_body and is_important ( stanza ) then
+ − 417 form_data [ "last-message-body" ] = tostring ( dummy_body );
+ − 418 end
+ − 419 push_publish : add_child ( push_form : form ( form_data ));
+ − 420 push_publish : up (); -- / notification
+ − 421 push_publish : up (); -- / publish
+ − 422 push_publish : up (); -- / pubsub
+ − 423 if push_info . options then
+ − 424 push_publish : tag ( "publish-options" ): add_child ( st . deserialize ( push_info . options ));
+ − 425 end
+ − 426 -- send out push
+ − 427 module : log ( "debug" , "Sending%s push notification for %s@%s to %s (%s)" , form_data [ "last-message-body" ] and " important" or "" , node , module . host , push_info . jid , tostring ( push_info . node ));
+ − 428 -- module:log("debug", "PUSH STANZA: %s", tostring(push_publish));
+ − 429 -- handle push errors for this node
+ − 430 if push_errors [ push_identifier ] == nil then
+ − 431 push_errors [ push_identifier ] = 0 ;
+ − 432 module : hook ( "iq-error/host/" .. stanza_id , handle_push_error );
+ − 433 module : hook ( "iq-result/host/" .. stanza_id , handle_push_success );
+ − 434 id2node [ stanza_id ] = node ;
+ − 435 end
+ − 436 module : send ( push_publish );
+ − 437 pushes = pushes + 1 ;
+ − 438 end
+ − 439 end
+ − 440 return pushes ;
+ − 441 end
+ − 442
+ − 443
+ − 444 -- archive message added
+ − 445 local function archive_message_added ( event )
+ − 446 -- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id }
+ − 447 -- only notify for new mam messages when at least one device is online
+ − 448 local room = event . room ;
+ − 449 local stanza = event . stanza ;
+ − 450 local body = stanza : get_child_text ( 'body' );
+ − 451
+ − 452 for reference in stanza : childtags ( "reference" , "urn:xmpp:reference:0" ) do
+ − 453 if reference . attr [ 'type' ] == 'mention' and reference . attr [ 'begin' ] and reference . attr [ 'end' ] then
+ − 454 local nick = body : sub ( tonumber ( reference . attr [ 'begin' ]) + 1 , tonumber ( reference . attr [ 'end' ]));
+ − 455 local jid = room : get_registered_jid ( nick );
+ − 456
+ − 457 if room . _occupants [ room . jid .. '/' .. nick ] then
+ − 458 -- We only notify for members not currently in the room
+ − 459 module : log ( "debug" , "Not notifying %s, because he's currently in the room" , jid );
+ − 460 else
+ − 461 -- We only need to notify once, even when there are multiple mentions.
+ − 462 local user_push_services = push_store : get ( jid );
+ − 463 handle_notify_request ( event . stanza , jid , user_push_services , true );
+ − 464 return
+ − 465 end
+ − 466 end
+ − 467 end
+ − 468 end
+ − 469
+ − 470 module : hook ( "muc-add-history" , archive_message_added );
+ − 471
+ − 472 local function send_ping ( event )
+ − 473 local user = event . user ;
+ − 474 local user_push_services = push_store : get ( user );
+ − 475 local push_services = event . push_services or user_push_services ;
+ − 476 handle_notify_request ( nil , user , push_services , true );
+ − 477 end
+ − 478 -- can be used by other modules to ping one or more (or all) push endpoints
+ − 479 module : hook ( "cloud-notify-ping" , send_ping );
+ − 480
+ − 481 module : log ( "info" , "Module loaded" );
+ − 482 function module . unload ()
+ − 483 if module . unhook then
+ − 484 module : unhook ( "account-disco-info" , account_dico_info );
+ − 485 module : unhook ( "iq-set/host/" .. xmlns_push .. ":enable" , push_enable );
+ − 486 module : unhook ( "iq-set/host/" .. xmlns_push .. ":disable" , push_disable );
+ − 487
+ − 488 module : unhook ( "muc-add-history" , archive_message_added );
+ − 489 module : unhook ( "cloud-notify-ping" , send_ping );
+ − 490
+ − 491 for push_identifier , _ in pairs ( push_errors ) do
+ − 492 local stanza_id = hashes . sha256 ( push_identifier , true );
+ − 493 module : unhook ( "iq-error/host/" .. stanza_id , handle_push_error );
+ − 494 module : unhook ( "iq-result/host/" .. stanza_id , handle_push_success );
+ − 495 id2node [ stanza_id ] = nil ;
+ − 496 end
+ − 497 end
+ − 498
+ − 499 module : log ( "info" , "Module unloaded" );
+ − 500 end