diff mod_muc_mav/mod_muc_mav.lua @ 6543:19bee2a6436a

mod_muc_mav: allow to work even in very large rooms Tested on a MUC with almost 5000 affiliations
author Stephen Paul Weber <singpolyma@singpolyma.net>
date Tue, 19 May 2026 13:31:35 -0500
parents d7162714f8b2
children
line wrap: on
line diff
--- a/mod_muc_mav/mod_muc_mav.lua	Tue May 19 09:35:53 2026 -0500
+++ b/mod_muc_mav/mod_muc_mav.lua	Tue May 19 13:31:35 2026 -0500
@@ -12,22 +12,41 @@
 
 local mav_versions = {}
 
-local function get_affiliations(from, room)
+local function get_affiliations(from, room, hash_only)
+	local mav_since = nil
 	local jid_affiliations = {}
+	local xs = {}
 	local x = st.stanza("x", {xmlns = xmlns_muc_user;});
+	local count = 0
 	for jid, affiliation, affiliation_data in room:each_affiliation() do
 		table.insert(jid_affiliations, jid.."\0"..affiliation);
-		local occupant_id = room:get_occupant_id_from_jid(jid);
-		local nick = affiliation_data and affiliation_data.reserved_nickname or nil;
-		local show_jid = (not room:is_anonymous_for(from) or util_jid.bare(from) == jid) and jid or nil
-		x:tag("item", {affiliation = affiliation or "none"; jid = show_jid; nick = nick;})
-			:tag("occupant-id", { xmlns = xmlns_occupant_id, id = occupant_id }):up():up();
+		if not hash_only then
+			local occupant_id = room:get_occupant_id_from_jid(jid);
+			local nick = affiliation_data and affiliation_data.reserved_nickname or nil;
+			local show_jid = (not room:is_anonymous_for(from) or util_jid.bare(from) == jid) and jid or nil
+			x:tag("item", {affiliation = affiliation or "none"; jid = show_jid; nick = nick;})
+				:tag("occupant-id", { xmlns = xmlns_occupant_id, id = occupant_id }):up():up();
+			count = count + 1
+			if count > 400 then
+				table.sort(jid_affiliations)
+				local hash_input = table.concat(jid_affiliations, "\0")
+				local hash = base64.encode(hashes.sha256(hash_input))
+				x:tag("mav", {xmlns = xmlns_mav; since = mav_since; ["until"] = hash;}):up()
+				table.insert(xs, x)
+				x = st.stanza("x", {xmlns = xmlns_muc_user;})
+				count = 0
+				mav_since = hash
+			end
+		end
 	end
 	table.sort(jid_affiliations);
 	local hash_input = table.concat(jid_affiliations, "\0");
 	local hash = base64.encode(hashes.sha256(hash_input))
-	x:tag("mav", {xmlns = xmlns_mav; ["until"] = hash;}):up();
-	return hash, x;
+	if not hash_only and (count > 0 or #xs < 1) then
+		x:tag("mav", {xmlns = xmlns_mav; since = mav_since; ["until"] = hash;}):up();
+		table.insert(xs, x);
+	end
+	return hash, xs;
 end
 
 local function handle_stanza_with_x(room, stanza, mav_current)
@@ -39,7 +58,8 @@
 	if is_initial then
 		muc_x:tag("mav", {xmlns = xmlns_mav; ["until"] = mav_current;}):up();
 	else
-		local mav_new = get_affiliations(stanza.attr.from, room);
+		-- TODO: stop recomputing the hash on every stanza in the presence flood
+		local mav_new = get_affiliations(stanza.attr.from, room, true);
 		if mav_current ~= mav_new then
 			module:log("info", "Update version of %s from %s to %s in affiliation update", stanza.attr.to, mav_current, mav_new);
 			muc_x:tag("mav", {xmlns = xmlns_mav; ["since"] = mav_current; ["until"] = mav_new; }):up();
@@ -81,13 +101,15 @@
 	if not mav_child then return; end
 	local mav_since = mav_child.attr["since"];
 	mav_versions[room.jid.."\0"..stanza.attr.from] = mav_since;
-	local mav_current, x = get_affiliations(stanza.attr.from, room);
+	local mav_current, xs = get_affiliations(stanza.attr.from, room);
 	if mav_current ~= mav_since then
 		-- TODO: Support for diffs
 		-- Send full response
 		module:log("info", "Old version of %s is %s, sending full response for %s", stanza.attr.from, mav_since, mav_current);
-		local announce_msg = st.message({ from = room.jid, to = stanza.attr.from }):add_child(x);
-		room:route_stanza(announce_msg);
+		for _, x in ipairs(xs) do
+			local announce_msg = st.message({ from = room.jid, to = stanza.attr.from }):add_child(x);
+			room:route_stanza(announce_msg);
+		end
 		mav_versions[room.jid.."\0"..stanza.attr.from] = mav_current;
 	else
 		module:log("info", "Old version of %s is %s, still current", stanza.attr.from, mav_since);