Mercurial > prosody-modules
changeset 6431:459cb2c2789b
mod_muc_members_json: Support private MUCs, non-member affiliation grants, async storage, dry-run mode
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Tue, 10 Mar 2026 16:58:17 +0000 |
| parents | cf5f0a20e16a |
| children | af8577933ddf |
| files | mod_muc_members_json/README.md mod_muc_members_json/mod_muc_members_json.lua |
| diffstat | 2 files changed, 124 insertions(+), 24 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_muc_members_json/README.md Tue Mar 10 16:13:25 2026 +0100 +++ b/mod_muc_members_json/README.md Tue Mar 10 16:58:17 2026 +0000 @@ -42,6 +42,20 @@ muc_members_json_mucs = { -- This configures hats for the myroom@<this MUC host> MUC myroom = { + -- Set the MUC to private. In this case the MUC will be closed + -- to anyone who doesn't have one of the owner_roles, admin_roles + -- or member_roles specified below + private = true; + + -- Specify privileges based on roles specified in the JSON + owner_roles = { "staff" }; + admin_roles = { "moderator" }; + + -- If not specified, anyone listed in the JSON will be added + -- as a member. Otherwise, only the specified roles will + -- become members in this MUC. + member_roles = { "myteam" }; + -- The optional field 'member_hat' defines a hat that will be -- added to any user that is listed in the members JSON -- (regardless of what roles they have, if any) @@ -92,6 +106,6 @@ ------- ------------------ trunk Works - 0.12 Works + 13.0 Works ------- ------------------
--- a/mod_muc_members_json/mod_muc_members_json.lua Tue Mar 10 16:13:25 2026 +0100 +++ b/mod_muc_members_json/mod_muc_members_json.lua Tue Mar 10 16:58:17 2026 +0000 @@ -1,9 +1,14 @@ +local async = require "util.async"; local http = require "net.http"; local json = require "util.json"; +local promise = require "util.promise"; +local set = require "util.set"; local json_url = assert(module:get_option_string("muc_members_json_url"), "muc_members_json_url required"); local managed_mucs = module:get_option("muc_members_json_mucs"); +local dry_run_mode = module:get_option_boolean("muc_members_json_dry_run", false); + local mod_muc = module:depends("muc"); --[[ @@ -53,41 +58,122 @@ return hats; end -function module.load() - http.request(json_url) +local function async_as_promise(f, p) + return promise.new(function (resolve, reject) + local r = async.runner(f); + r:onready(function () + resolve(true); + end); + r:onerror(function (err) + reject(err); + end); + if p == nil then + p = true; + end + local ok, state = r:run(p); + if ok and state == "ready" then + resolve(true); + end + end); +end + +function update_members(dry_run) + module:log("debug", "Updating... (dry run: %s)", dry_run and "no" or "yes"); + return http.request(json_url) :next(function (result) return json.decode(result.body); end) :next(function (data) module:log("debug", "DATA: %s", require "util.serialization".serialize(data, "debug")); + return async_as_promise(function () + for name, muc_config in pairs(managed_mucs) do + local muc_jid = name.."@"..module.host; + local muc = mod_muc.get_room_from_jid(muc_jid); + if muc then + local owner_roles = set.new(muc_config.owner_roles); + local admin_roles = set.new(muc_config.admin_roles); + local member_roles = set.new(muc_config.member_roles); + local affiliated_roles = owner_roles + admin_roles + member_roles; + local is_private = muc_config.private; + local include_all_members = muc_config.member_roles == nil; - for name, muc_config in pairs(managed_mucs) do - local muc_jid = name.."@"..module.host; - local muc = mod_muc.get_room_from_jid(muc_jid); - module:log("warn", "%s -> %s -> %s", name, muc_jid, muc); - if muc then - local jids = {}; - for _, member_info in ipairs(data.members) do - for _, member_jid in ipairs(member_info.jids) do - jids[member_jid] = true; - local affiliation = muc:get_affiliation(member_jid); - if not affiliation then - muc:set_affiliation(true, member_jid, "member", "imported membership"); - muc:set_affiliation_data(member_jid, "source", module.name); + if is_private and not muc:get_members_only() then + if not dry_run then + module:log("info", "Setting %s to members-only...", muc_jid); + muc:set_members_only(true); + else + module:log("info", "DRY RUN: Would set %s to members-only...", muc_jid); end - muc:set_affiliation_data(member_jid, "hats", get_hats(member_info, muc_config)); end - end - -- Remove affiliation from folk who weren't in the source data but previously were - for jid, aff, data in muc:each_affiliation() do - if not jids[jid] and data and data.source == module.name then - muc:set_affiliation(true, jid, "none", "imported membership lost"); + + local jids = {}; + for _, member_info in ipairs(data.members) do + local roles = set.new(member_info.roles); + for _, member_jid in ipairs(member_info.jids) do + if include_all_members or not set.intersection(affiliated_roles, roles):empty() then + jids[member_jid] = true; + local is_owner = not set.intersection(owner_roles, roles):empty(); + local is_admin = not set.intersection(admin_roles, roles):empty(); + local expected_affiliation = (is_owner and "owner") or (is_admin and "admin") or "member"; + local affiliation = muc:get_affiliation(member_jid); + if affiliation ~= expected_affiliation then + if not dry_run then + muc:set_affiliation(true, member_jid, expected_affiliation, "imported membership"); + muc:set_affiliation_data(member_jid, "source", module.name); + module:log("info", "Set affiliation of %s in %s, %s -> %s", member_jid, muc_jid, affiliation, expected_affiliation); + else + module:log("info", "DRY RUN: Would set affiliation of %s in %s, %s -> %s", member_jid, muc_jid, affiliation, expected_affiliation); + end + end + if not dry_run then + muc:set_affiliation_data(member_jid, "hats", get_hats(member_info, muc_config)); + end + end + end + end + -- Remove affiliation from folk who weren't in the source data but previously were + for jid, aff, data in muc:each_affiliation() do + if not jids[jid] and data and data.source == module.name then + if not dry_run then + module:log("info", "Removing affiliation of %s in %s", jid, muc_jid); + muc:set_affiliation(true, jid, "none", "imported membership lost"); + else + module:log("info", "DRY RUN: Would remove affiliation of %s in %s", jid, muc_jid); + end + end end end end - end - + end); end):catch(function (err) module:log("error", "FAILED: %s", err); end); end + +function module.load() + update_members(dry_run_mode); +end + +module:hourly(function () + update_members(dry_run_mode); +end); + +module:add_item("shell-command", { + section = "members_json"; + section_desc = "Commands related to mod_muc_members_json"; + name = "update"; + desc = "Force an update of the members data"; + args = { + { name = "host", type = "string" }; + }; + host_selector = "host"; + flags = { + bool_params = { dry_run = true }; + }; + handler = function (shell, host, opts) + return update_members(opts.dry_run) + :next(function () + return opts.dry_run and "Updated (dry-run only, check logs)" or "Updated"; + end); + end; +});
