changeset 6500:36b9d79e04b7

mod_muc_auto_hats: New module to dynamically assign hats based on config
author Matthew Wild <mwild1@gmail.com>
date Wed, 25 Mar 2026 18:47:51 +0000
parents 1213145e27fc
children f7158c0e2202
files mod_muc_auto_hats/README.md mod_muc_auto_hats/mod_muc_auto_hats.lua
diffstat 2 files changed, 86 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_muc_auto_hats/README.md	Wed Mar 25 18:47:51 2026 +0000
@@ -0,0 +1,51 @@
+---
+labels:
+- 'Stage-Alpha'
+summary: 'Dynamically set hats based on configurable policies'
+...
+
+Introduction
+============
+
+This module lets you dynamically assign hats to MUC occupants based on
+configuration rules.
+
+Currently the only supported rule type is based on the host that the user is
+joining from. More rule types may be added in the future.
+
+Configuration
+=============
+
+Add the module to the MUC host (not the global modules\_enabled):
+
+
+``` {.lua}
+        Component "conference.example.com" "muc"
+            modules_enabled = { "muc_auto_hats" }
+```
+
+Then add configuration rules.
+
+Hats are identified by URIs, which must be unique (they are not displayed to
+the user). If you don't specify one, Prosody will generate a unique one for
+you. Each hat also has a human-readable 'title'. Again, Prosody will generate
+a default if you don't specify one.
+
+## By user host
+
+``` {.lua}
+muc_auto_hats_by_host = {
+	["anon.example.com"] = {
+		title = "Web chat";
+	};
+	["staff.example.com"] = {
+		title = "Staff";
+		uri = "https://example.com/roles/staff";
+	};
+}
+```
+
+Compatibility
+=============
+
+Requires Prosody 13.0+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_muc_auto_hats/mod_muc_auto_hats.lua	Wed Mar 25 18:47:51 2026 +0000
@@ -0,0 +1,35 @@
+local jid = require "prosody.util.jid";
+local sha256 = require "prosody.util.hashes".sha256;
+local st = require "prosody.util.stanza";
+
+local host_hats = module:get_option("muc_auto_hats_by_host");
+
+for hat_host, hat_config in pairs(host_hats) do
+	if not hat_config.uri then
+		hat_config.uri = ("xmpp:%s/hats/%s"):format(module.host, sha256(hat_host..","..module.host, true));
+	end
+	if not hat_config.title then
+		hat_config.title = hat_host;
+	end
+end
+
+module:hook("muc-build-occupant-presence", function (event)
+	local pres = event.stanza;
+
+	local bare_jid = event.occupant and event.occupant.bare_jid or event.bare_jid;
+	local occ_host = jid.host(bare_jid);
+
+	local host_hat = host_hats[occ_host];
+
+	if not host_hat then
+		return;
+	end
+
+	local hats_el = pres:get_child("hats", "urn:xmpp:hats:0");
+	if not hats_el then
+		hats_el = st.stanza("hats", "urn:xmpp:hats:0");
+		pres:add_direct_child(hats_el);
+	end
+
+	hats_el:text_tag("hat", nil, { uri = host_hat.uri, title = host_hat.title });
+end);