| 1 | -- XEP-0410: MUC Self-Ping |
| 2 | -- |
| 3 | -- Detects silent MUC disconnects (e.g. a remote MUC service restarting |
| 4 | -- without us receiving an unavailable presence) by pinging our own |
| 5 | -- occupant JID in rooms we have received no traffic from for a while, |
| 6 | -- and rejoining when the ping shows we are no longer joined. |
| 7 | local verse = require "verse"; |
| 8 | local jid = require "prosody.util.jid"; |
| 9 | |
| 10 | local xmlns_ping = "urn:xmpp:ping"; |
| 11 | local xmlns_muc = "http://jabber.org/protocol/muc"; |
| 12 | |
| 13 | -- Ping error conditions that still mean we are joined (XEP-0410 ยง3.2) |
| 14 | local still_joined = { |
| 15 | ["service-unavailable"] = true; |
| 16 | ["feature-not-implemented"] = true; |
| 17 | }; |
| 18 | |
| 19 | -- Conditions meaning the MUC was unreachable, so joined-ness is unknown |
| 20 | local unreachable = { |
| 21 | ["remote-server-not-found"] = true; |
| 22 | ["remote-server-timeout"] = true; |
| 23 | ["timeout"] = true; |
| 24 | }; |
| 25 | |
| 26 | function riddim.plugins.muc_ping(bot) |
| 27 | local stream = bot.stream; |
| 28 | local interval = bot.config.muc_ping_interval or 300; |
| 29 | local timeout = bot.config.muc_ping_timeout or 60; |
| 30 | local serial = 0; |
| 31 | |
| 32 | -- Any stanza from a room is proof that we are still connected to it |
| 33 | stream:hook("stanza", function (stanza) |
| 34 | local room_jid = jid.bare(stanza.attr.from); |
| 35 | local room = room_jid and stream.rooms[room_jid]; |
| 36 | if room then |
| 37 | room.last_activity = os.time(); |
| 38 | end |
| 39 | end, 1000); |
| 40 | |
| 41 | stream:hook("groupchat/joining", function (room) |
| 42 | room.last_activity = os.time(); |
| 43 | end, 1000); |
| 44 | |
| 45 | local function rejoin(room, forget_occupants) |
| 46 | if forget_occupants then |
| 47 | -- The room's presence handler keeps a reference to this table, |
| 48 | -- so it must be cleared in place, not replaced |
| 49 | for nick in pairs(room.occupants) do |
| 50 | room.occupants[nick] = nil; |
| 51 | end |
| 52 | end |
| 53 | local join_st = verse.presence():tag("x", { xmlns = xmlns_muc }):reset(); |
| 54 | stream:event("pre-groupchat/joining", join_st); |
| 55 | room:send(join_st); |
| 56 | end |
| 57 | |
| 58 | local function ping_room(room) |
| 59 | serial = serial + 1; |
| 60 | local this_ping = serial; |
| 61 | room.pending_ping = this_ping; |
| 62 | local function result(ok, condition) |
| 63 | if room.pending_ping ~= this_ping then return; end |
| 64 | room.pending_ping = nil; |
| 65 | room.last_activity = os.time(); |
| 66 | if stream.rooms[room.jid] ~= room then return; end -- room was left |
| 67 | if ok or still_joined[condition] then |
| 68 | return; |
| 69 | elseif unreachable[condition] then |
| 70 | -- Try joining in case we were dropped (harmless if we are |
| 71 | -- still an occupant), and keep pinging |
| 72 | stream:warn("muc_ping: no response from %s (%s), rejoining", room.jid, condition); |
| 73 | rejoin(room, false); |
| 74 | else -- e.g. not-acceptable, item-not-found: we are not joined |
| 75 | stream:warn("muc_ping: not in %s (%s), rejoining", room.jid, condition); |
| 76 | rejoin(room, true); |
| 77 | end |
| 78 | end |
| 79 | stream:debug("muc_ping: pinging %s", room.jid); |
| 80 | stream:send_iq(verse.iq({ to = room.jid.."/"..room.nick, type = "get" }) |
| 81 | :tag("ping", { xmlns = xmlns_ping }), function (reply) |
| 82 | if reply.attr.type == "error" then |
| 83 | local _, condition = reply:get_error(); |
| 84 | result(false, condition or "undefined-condition"); |
| 85 | else |
| 86 | result(true); |
| 87 | end |
| 88 | end); |
| 89 | verse.add_task(timeout, function () |
| 90 | result(false, "timeout"); |
| 91 | end); |
| 92 | end |
| 93 | |
| 94 | verse.add_task(interval, function () |
| 95 | local now = os.time(); |
| 96 | for _, room in pairs(stream.rooms) do |
| 97 | if not room.pending_ping and now - (room.last_activity or now) >= interval then |
| 98 | ping_room(room); |
| 99 | end |
| 100 | if not room.last_activity then |
| 101 | room.last_activity = now; |
| 102 | end |
| 103 | end |
| 104 | return interval; |
| 105 | end); |
| 106 | end |