Changeset

6133:a0429c322454

mod_muc_activity: create module
author Jonas Schäfer <jonas@wielicki.name>
date Sat, 04 Jan 2025 19:04:26 +0100
parents 6132:ffec70ddbffc
children 6134:00b55c7ef393
files mod_muc_activity/README.md mod_muc_activity/mod_muc_activity.lua
diffstat 2 files changed, 104 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_muc_activity/README.md	Sat Jan 04 19:04:26 2025 +0100
@@ -0,0 +1,17 @@
+---
+summary: 'XEP-0502 (MUC Activity Indicator) implementation'
+...
+
+This module provides an implementation of [XEP-0502 (MUC Activity Indicator)](https://xmpp.org/extensions/xep-0502.html) for Prosody.
+
+To enable it, load it on a MUC host, for example:
+
+```lua
+Component "chat.domain.example" "muc"
+    modules_enabled = { "muc_activity" }
+```
+
+When this module is loaded, it will expose the average number of messages per hour for all public MUCs.
+The number is calculated over a 24 hour window.
+
+Note that this module may impact server performance on servers with many MUCs.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_muc_activity/mod_muc_activity.lua	Sat Jan 04 19:04:26 2025 +0100
@@ -0,0 +1,87 @@
+local os_time = os.time;
+local math_floor = math.floor;
+local store = module:open_store("muc_activity", "keyval");
+
+local accumulator = {};
+
+local field_var = "{urn:xmpp:muc-activity}message-activity";
+
+module:hook("muc-room-destroyed", function (event)
+	local jid = event.room.jid;
+	module:log("debug", "deleting activity data for destroyed muc %s", jid);
+	store:set_keys(jid, {});
+	accumulator[jid] = nil;
+end)
+
+module:hook("muc-occupant-groupchat", function (event)
+	local jid = event.room.jid;
+	if not event.room:get_persistent() then
+		-- we do not count stanzas in non-persistent rooms
+		if accumulator[jid] then
+			-- if we have state for the room, drop it.
+			store:set_keys(jid, {});
+			accumulator[jid] = nil;
+		end
+
+		return
+	end
+
+	if event.stanza:get_child("body") == nil then
+		-- we do not count stanzas without body.
+		return
+	end
+
+	module:log("debug", "counting stanza for MUC activity in %s", jid);
+	accumulator[jid] = (accumulator[jid] or 0) + 1;
+end)
+
+local function shift(data)
+	for i = 1, 23 do
+		data[i] = data[i+1]
+	end
+end
+
+local function accumulate(data)
+	if data == nil then
+		return 0;
+	end
+	local accum = 0;
+	for i = 1, 24 do
+		local v = data[i];
+		if v ~= nil then
+			accum = accum + v
+		end
+	end
+	return accum;
+end
+
+module:hourly("muc-activity-shift", function ()
+	module:log("info", "shifting MUC activity store forward by one hour");
+	for jid in store:users() do
+		local data = store:get(jid);
+		local new = accumulator[jid] or 0;
+		shift(data);
+		data[24] = new;
+		accumulator[jid] = nil;
+		store:set(jid, data);
+	end
+
+	-- All remaining entries in the accumulator are non-existent in the store,
+	-- otherwise they would have been removed earlier.
+	for jid, count in pairs(accumulator) do
+		store:set(jid, { [24] = count });
+	end
+	accumulator = {};
+end)
+
+module:hook("muc-disco#info", function(event)
+	local room = event.room;
+	local jid = room.jid;
+	if not room:get_persistent() or not room:get_public() or room:get_members_only() or room:get_password() ~= nil then
+		module:log("debug", "%s is not persistent or not public, not injecting message activity", jid);
+		return;
+	end
+	local count = accumulate(store:get(jid)) / 24.0;
+	table.insert(event.form, { name = field_var, label = "Message activity" });
+	event.formdata[field_var] = tostring(count);
+end);