comparison mod_muc_mav/mod_muc_mav.lua @ 6530:8e87fbbb6cd3

mod_muc_mav: Add module for XEP-0463: MUC Affiliations Versioning
author Marvin W <hg@larma.de>
date Mon, 04 May 2026 20:55:39 +0200
parents
children 4d7e2d0db2ab
comparison
equal deleted inserted replaced
6529:971a531654dc 6530:8e87fbbb6cd3
1 local st = require "util.stanza";
2 local hashes = require "util.hashes";
3 local base64 = require "util.encodings".base64;
4
5 local mod_muc = module:depends"muc";
6
7 local xmlns_mav = "urn:xmpp:muc:affiliations:1";
8 local xmlns_muc = "http://jabber.org/protocol/muc";
9 local xmlns_muc_user = "http://jabber.org/protocol/muc#user";
10
11 local mav_versions = {}
12
13 local function get_affiliations(room)
14 local jid_affiliations = {}
15 local x = st.stanza("x", {xmlns = xmlns_muc_user;});
16 for jid, affiliation, affiliation_data in room:each_affiliation() do
17 table.insert(jid_affiliations, jid.."\0"..affiliation);
18 local nick = affiliation_data and affiliation_data.reserved_nickname;
19 x:tag("item", {affiliation = affiliation or "none"; jid = jid; nick = nick;}):up();
20 end
21 table.sort(jid_affiliations);
22 local hash_input = table.concat(jid_affiliations, "\0");
23 local hash = base64.encode(hashes.sha256(hash_input))
24 x:tag("mav", {xmlns = xmlns_mav; ["until"] = hash;}):up();
25 return hash, x;
26 end
27
28 local function handle_stanza_with_x(room, stanza, mav_current)
29 local muc_x = stanza:get_child("x", xmlns_muc_user);
30 local is_initial = false;
31 for child in muc_x:childtags("status") do
32 if child.attr.code == "110" then is_initial = true; end;
33 end
34 if is_initial then
35 muc_x:tag("mav", {xmlns = xmlns_mav; ["until"] = mav_current;}):up();
36 else
37 local mav_new = get_affiliations(room);
38 if mav_current ~= mav_new then
39 module:log("info", "Update version of %s from %s to %s in affiliation update", stanza.attr.to, mav_current, mav_new);
40 muc_x:tag("mav", {xmlns = xmlns_mav; ["since"] = mav_current; ["until"] = mav_new; }):up();
41 mav_versions[room.jid.."\0"..stanza.attr.to] = mav_new;
42 end
43 end
44 end
45
46 local function room_route_stanza(room, stanza)
47 local muc_x = stanza:get_child("x", xmlns_muc_user);
48 if muc_x then
49 local mav_current = mav_versions[room.jid.."\0"..stanza.attr.to];
50 if mav_current then
51 if stanza.name == "presence" or stanza.name == "message" then
52 handle_stanza_with_x(room, stanza, mav_current);
53 end
54 end
55 end
56 module:send(stanza);
57 end
58
59 -- TODO: Stop this hack
60 local original_room_route_stanza = mod_muc.room_mt.route_stanza;
61 mod_muc.room_mt.route_stanza = room_route_stanza;
62 function module.unload()
63 mod_muc.room_mt.route_stanza = original_room_route_stanza;
64 end
65
66
67 module:hook("muc-disco#info", function(event)
68 event.reply:tag("feature", {var = xmlns_mav}):up();
69 end);
70
71 module:hook("muc-occupant-pre-join", function(event)
72 local room, stanza = event.room, event.stanza;
73 local muc_x = stanza:get_child("x", xmlns_muc);
74 if not muc_x then return; end
75 local mav_child = muc_x:get_child("mav", xmlns_mav);
76 if not mav_child then return; end
77 local mav_since = mav_child.attr["since"];
78 mav_versions[room.jid.."\0"..stanza.attr.from] = mav_since;
79 local mav_current, x = get_affiliations(room);
80 if mav_current ~= mav_since then
81 -- TODO: Support for diffs
82 -- Send full response
83 module:log("info", "Old version of %s is %s, sending full response for %s", stanza.attr.from, mav_since, mav_current);
84 local announce_msg = st.message({ from = room.jid, to = stanza.attr.from }):add_child(x);
85 room:route_stanza(announce_msg);
86 mav_versions[room.jid.."\0"..stanza.attr.from] = mav_current;
87 else
88 module:log("info", "Old version of %s is %s, still current", stanza.attr.from, mav_since);
89 end
90 end, -100);
91
92 module:hook("muc-occupant-left", function(event)
93 local room, occupant = event.room, event.occupant;
94 for jid in occupant:each_session() do
95 mav_versions[room.jid.."\0"..jid] = nil;
96 end
97 end);
98
99 module:hook("muc-occupant-pre-leave", function(event)
100 local room, stanza = event.room, event.stanza;
101 mav_versions[room.jid.."\0"..stanza.attr.from] = nil;
102 end);