# HG changeset patch # User Matthew Wild # Date 1772721343 0 # Node ID c41615686ce484d25dc07ce2027e733e107586fc # Parent 653ac8702509d2f531f9bb03bd95c407c391f442 mod_muc_auto_role: Set default role for MUC occupants based on policies diff -r 653ac8702509 -r c41615686ce4 mod_muc_auto_role/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_auto_role/README.md Thu Mar 05 14:35:43 2026 +0000 @@ -0,0 +1,55 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Set default role for MUC occupants based on policies' +... + +Introduction +============ + +This module allows you to apply policies to occupants joining a MUC room, and +choose an appropriate initial role for them. + +Details +======= + +Depending on configuration, someone who joins a MUC will usually be assigned +one of two roles: "visitor", or "participant". + +The "visitor" role does not have permission to speak in moderated rooms. The +"participant" role may speak in moderated rooms. + +By default, Prosody assigns "participant" when a MUC is not moderated, and +"visitor" when it is moderated. This allows existing occupants to continue +speaking when a MUC is switched from unmoderated to moderated (e.g. in +response to abuse). + +This module overrides the default behaviour, using other signals such as +[XEP-0489: Reporting Account Affiliations](https://xmpp.org/extensions/xep-0489.html), +(which is implemented in Prosody in [mod_report_affiliations](https://modules.prosody.im/mod_report_affiliations)) +to decide which role to assign by default. + +This allows a smarter approach than simply assigning everyone "visitor" or +"participant". + +Configuration +============= + +Add the module to the MUC host (not the global modules\_enabled): + + Component "conference.example.com" "muc" + modules_enabled = { "muc_auto_role" } + +You can define (globally or per-MUC component) the following options: + + Name Default Description + ------------------------------- ----------- ---------------------------------------------------- + muc_auto_role_trusted_servers `{}` A list of trusted servers to use RAA info from + muc_auto_role_trust_threshold `51` The minimum RAA trust score which will be considered trusted (for `"participant"` role) + muc_auto_role_untrusted_servers `{}` A list of servers which will always use the `"visitor"` role. + muc_default_role `"visitor"` The default role to assign when no other policy fits + +Compatibility +============= + +Requires Prosody 13.0+ diff -r 653ac8702509 -r c41615686ce4 mod_muc_auto_role/mod_muc_auto_role.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_auto_role/mod_muc_auto_role.lua Thu Mar 05 14:35:43 2026 +0000 @@ -0,0 +1,48 @@ +local jid = require "prosody.util.jid"; + +local xmlns_aff = "urn:xmpp:raa:0"; + +local raa_servers = module:get_option_set("muc_auto_role_trusted_servers", {}); +local trust_threshold = module:get_option_integer("muc_auto_role_trust_threshold", 51, 0, 100); + +local untrusted_servers = module:get_option_set("muc_auto_role_untrusted_servers", {}); + +local default_role = module:get_option_enum("muc_default_role", "visitor", "participant"); + +-- TODO: This should be a config option in mod_muc +module:hook("muc-get-default-role", function () + return default_role; +end, 5); + +module:hook("muc-occupant-pre-join", function (event) + local stanza = event.stanza; + + local from_host = jid.host(stanza.attr.from); + + if untrusted_servers:contains(from_host) then + event.occupant.role = "visitor"; + end + + if not raa_servers:contains(from_host) then + return; + end + + local raa = stanza:get_child("info", xmlns_aff); + if not raa then + return; + end + + local aff = raa.attr.affiliation; + local trust = tonumber(raa.attr.trust) or 50; + + if aff == "member" or aff == "admin" or trust >= trust_threshold then + event.occupant.role = "participant"; + else + event.occupant.role = "visitor"; + end + + module:log("debug", "Assigning <%s> role '%s' due to affiliation %s and trust score %d of <%s>", + event.occupant.nick, event.occupant.role, aff, trust, stanza.attr.from + ); + +end);