# HG changeset patch # User Matthew Wild # Date 1586981985 -3600 # Node ID f14c862598a95db2b322e5549d913748ba18d332 # Parent df6227e288e57c7da6f178be25f92cd520caf9bb mod_muc_rai: New module to implement Room Activity Indicators diff -r df6227e288e5 -r f14c862598a9 mod_muc_rai/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_rai/README.markdown Wed Apr 15 21:19:45 2020 +0100 @@ -0,0 +1,30 @@ +# Introduction + +This module implements [XEP-xxxx: Room Activity Indicators](https://xmpp.org/extensions/inbox/room-activity-indicators.html). + +## Requirements + +This module currently depends on mod_muc_markers, so review the requirements for that module. + +# Configuring + +## Enabling + +``` {.lua} +Component "rooms.example.net" "muc" +modules_enabled = { + "muc_rai"; + "muc_markers"; + "muc_mam"; +} +``` + +## Settings + +|Name |Description |Default | +|-----|------------|--------| +|muc_rai_max_subscribers| Maximum number of active subscriptions allowed | 1024 | + +# Compatibility + +Requires Prosody trunk (2020-04-15+). \ No newline at end of file diff -r df6227e288e5 -r f14c862598a9 mod_muc_rai/mod_muc_rai.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_rai/mod_muc_rai.lua Wed Apr 15 21:19:45 2020 +0100 @@ -0,0 +1,215 @@ +local cache = require "util.cache"; +local jid = require "util.jid"; +local st = require "util.stanza"; + +local max_subscribers = module:get_option_number("muc_rai_max_subscribers", 1024); + +local muc_affiliation_store = module:open_store("config", "map"); +local muc_archive = module:open_store("muc_log", "archive"); + +local xmlns_rai = "xmpp:prosody.im/protocol/rai"; + +local muc_markers = module:depends("muc_markers"); + +-- subscriber_jid -> { [room_jid] = interested } +local subscribed_users = cache.new(max_subscribers, false); +-- room_jid -> { [user_jid] = interested } +local interested_users = {}; +-- room_jid -> last_id +local room_activity_cache = cache.new(1024); + +-- Send a single notification for a room, updating data structures as needed +local function send_single_notification(user_jid, room_jid) + local notification = st.message({ to = user_jid, from = module.host }) + :tag("rai", { xmlns = xmlns_rai }) + :text_tag("activity", room_jid) + :up(); + local interested_room_users = interested_users[room_jid]; + if interested_room_users then + interested_room_users[user_jid] = nil; + end + local interested_rooms = subscribed_users:get(user_jid); + if interested_rooms then + interested_rooms[room_jid] = nil; + end + module:log("debug", "Sending notification from %s to %s", room_jid, user_jid); + return module:send(notification); +end + +local function subscribe_room(user_jid, room_jid) + local interested_rooms = subscribed_users:get(user_jid); + if not interested_rooms then + return nil, "not-subscribed"; + end + module:log("debug", "Subscribed %s to %s", user_jid, room_jid); + interested_rooms[room_jid] = true; + + local interested_room_users = interested_users[room_jid]; + if not interested_room_users then + interested_room_users = {}; + interested_users[room_jid] = interested_room_users; + end + interested_room_users[user_jid] = true; + return true; +end + +local function unsubscribe_room(user_jid, room_jid) + local interested_rooms = subscribed_users:get(user_jid); + if not interested_rooms then + return nil, "not-subscribed"; + end + interested_rooms[room_jid] = nil; + + local interested_room_users = interested_users[room_jid]; + if not interested_room_users then + return true; + end + interested_room_users[user_jid] = nil; + return true; +end + +local function notify_interested_users(room_jid) + module:log("warn", "NOTIFYING FOR %s", room_jid) + local interested_room_users = interested_users[room_jid]; + if not interested_room_users then + module:log("debug", "Nobody interested in %s", room_jid); + return; + end + for user_jid in pairs(interested_room_users) do + send_single_notification(user_jid, room_jid); + end + return true; +end + +local function unsubscribe_user_from_all_rooms(user_jid) + local interested_rooms = subscribed_users:get(user_jid); + if not interested_rooms then + return nil, "not-subscribed"; + end + for room_jid in pairs(interested_rooms) do + unsubscribe_room(user_jid, room_jid); + end + return true; +end + +local function get_last_room_message_id(room_jid) + local last_room_message_id = room_activity_cache:get(room_jid); + if last_room_message_id then + return last_room_message_id; + end + + -- Load all the data! + local query = { + limit = 1; + reverse = true; + with = "message