# HG changeset patch # User Stephen Paul Weber # Date 1779215495 18000 # Node ID 19bee2a6436a18a0af40a55d8b0ec4d052351a2e # Parent d7162714f8b203d37241e1a8a565955e8749eada mod_muc_mav: allow to work even in very large rooms Tested on a MUC with almost 5000 affiliations diff -r d7162714f8b2 -r 19bee2a6436a mod_muc_mav/mod_muc_mav.lua --- 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);