comparison mod_rtbl/mod_rtbl.lua @ 6301:77b5dd33a8f3

mod_rtbl: New generic API module for subscribing to RTBLs via XEP-0060
author Matthew Wild <mwild1@gmail.com>
date Wed, 30 Jul 2025 14:13:44 +0100
parents
children 13f674a9fb22
comparison
equal deleted inserted replaced
6300:8689f49d21ef 6301:77b5dd33a8f3
1 local array = require "util.array";
2 local id = require "util.id";
3 local it = require "util.iterators";
4 local set = require "util.set";
5 local st = require "util.stanza";
6
7 module:depends("pubsub_subscription");
8
9 local function new_rtbl_subscription(rtbl_service_jid, rtbl_node, handlers)
10 local rtbl = {};
11 local items = {};
12 rtbl.items = items;
13
14 local function notify(event_type, hash)
15 local handler = handlers[event_type];
16 if not handler then return; end
17 handler(hash);
18 end
19
20 module:add_item("pubsub-subscription", {
21 service = rtbl_service_jid;
22 node = rtbl_node;
23
24 -- Callbacks:
25 on_subscribed = function()
26 module:log("info", "RTBL active: %s:%s", rtbl_service_jid, rtbl_node);
27 rtbl.subscribed = true;
28 end;
29
30 on_error = function(err)
31 module:log(
32 "error",
33 "Failed to subscribe to RTBL: %s:%s %s::%s: %s",
34 rtbl_service_jid,
35 rtbl_node,
36 err.type,
37 err.condition,
38 err.text
39 );
40 rtbl.subscribed = false;
41 rtbl.error = err;
42 end;
43
44 on_item = function(event)
45 local hash = event.item.attr.id;
46 if not hash then return; end
47 module:log("debug", "Received new hash from %s:%s: %s", rtbl_service_jid, rtbl_node, hash);
48 items[hash] = true;
49 notify("added", hash);
50 end;
51
52 on_retract = function (event)
53 local hash = event.item.attr.id;
54 if not hash then return; end
55 module:log("debug", "Retracted hash from %s:%s: %s", rtbl_service_jid, rtbl_node, hash);
56 items[hash] = nil;
57 notify("removed", hash);
58 end;
59
60 purge = function()
61 module:log("debug", "Purge all hashes from %s:%s", rtbl_service_jid, rtbl_node);
62 for hash in pairs(items) do
63 items[hash] = nil;
64 notify("removed", hash);
65 end
66 end;
67 });
68
69 local request_id = "rtbl-request-"..id.short();
70
71 local function request_list()
72 local items_request = st.iq({ to = rtbl_service_jid, from = module.host, type = "get", id = request_id })
73 :tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" })
74 :tag("items", { node = rtbl_node }):up()
75 :up();
76 module:send(items_request);
77 end
78
79 local function update_list(event)
80 local from_jid = event.stanza.attr.from;
81 if from_jid ~= rtbl_service_jid then
82 module:log("debug", "Ignoring RTBL response from unknown sender: %s", from_jid);
83 return;
84 end
85 local items_el = event.stanza:find("{http://jabber.org/protocol/pubsub}pubsub/items");
86 if not items_el then
87 module:log("warn", "Invalid items response from RTBL service %s:%s", rtbl_service_jid, rtbl_node);
88 return;
89 end
90
91 local old_entries = set.new(array.collect(it.keys(items)));
92
93 local n_added, n_removed, n_total = 0, 0, 0;
94 for item in items_el:childtags("item") do
95 local hash = item.attr.id;
96 if hash then
97 n_total = n_total + 1;
98 if not old_entries:contains(hash) then
99 -- New entry
100 n_added = n_added + 1;
101 items[hash] = true;
102 notify("added", hash);
103 else
104 -- Entry already existed
105 old_entries:remove(hash);
106 end
107 end
108 end
109
110 -- Remove old entries that weren't in the received list
111 for hash in old_entries do
112 n_removed = n_removed + 1;
113 items[hash] = nil;
114 notify("removed", hash);
115 end
116
117 module:log("info", "%d RTBL entries received from %s:%s (%d added, %d removed)", n_total, from_jid, rtbl_node, n_added, n_removed);
118 return true;
119 end
120
121 module:hook("iq-result/host/"..request_id, update_list);
122 module:add_timer(0, request_list);
123
124 return rtbl;
125 end
126
127 return {
128 new_rtbl_subscription = new_rtbl_subscription;
129 };