Files
upstream-jitsi-meet/resources/prosody-plugins/mod_muc_breakout_rooms.lua
upstream-mirror 1d2bfcac0c
Some checks failed
Prosody Plugin Tests / test (push) Failing after 10m11s
Close stale issues and PRs / stale (push) Failing after 1s
snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
2026-09-15 15:56:32 +05:00

814 lines
29 KiB
Lua

-- This module is added under the main virtual host domain
-- It needs a breakout rooms muc component
--
-- VirtualHost "jitmeet.example.com"
-- modules_enabled = {
-- "muc_breakout_rooms"
-- }
-- breakout_rooms_muc = "breakout.jitmeet.example.com"
-- main_muc = "muc.jitmeet.example.com"
--
-- Component "breakout.jitmeet.example.com" "muc"
-- restrict_room_creation = true
-- storage = "memory"
-- admins = { "focusUser@auth.jitmeet.example.com" }
-- muc_room_locking = false
-- muc_room_default_public_jids = true
--
module:depends('room_destroy');
-- we use async to detect Prosody 0.10 and earlier
local have_async = pcall(require, 'util.async');
if not have_async then
module:log('warn', 'Breakout rooms will not work with Prosody version 0.10 or less.');
return;
end
local jid_node = require 'util.jid'.node;
local jid_host = require 'util.jid'.host;
local jid_split = require 'util.jid'.split;
local jid_resource = require 'util.jid'.resource;
local jid_bare = require 'util.jid'.bare;
local json = require 'cjson.safe';
local st = require 'util.stanza';
local uuid_gen = require 'util.uuid'.generate;
local util = module:require 'util';
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
local is_healthcheck_room = util.is_healthcheck_room;
local process_host_module = util.process_host_module;
local is_admin = util.is_admin;
local get_occupant_by_real_jid = util.get_occupant_by_real_jid;
local BREAKOUT_ROOMS_IDENTITY_TYPE = 'breakout_rooms';
-- Available breakout room functionality
local RENAME_FEATURE = 'http://jitsi.org/protocol/breakout_rooms#rename';
-- only send at most this often updates on breakout rooms to avoid flooding.
local BROADCAST_ROOMS_INTERVAL = .3;
-- close conference after this amount of seconds if all leave.
local ROOMS_TTL_IF_ALL_LEFT = 5;
local JSON_TYPE_ADD_BREAKOUT_ROOM = 'features/breakout-rooms/add';
local JSON_TYPE_MOVE_TO_ROOM_REQUEST = 'features/breakout-rooms/move-to-room';
local JSON_TYPE_REMOVE_BREAKOUT_ROOM = 'features/breakout-rooms/remove';
local JSON_TYPE_RENAME_BREAKOUT_ROOM = 'features/breakout-rooms/rename';
local JSON_TYPE_UPDATE_BREAKOUT_ROOMS = 'features/breakout-rooms/update';
local main_muc_component_config = module:get_option_string('main_muc');
if main_muc_component_config == nil then
module:log('error', 'breakout rooms not enabled missing main_muc config');
return ;
end
local breakout_rooms_muc_component_config = module:get_option_string('breakout_rooms_muc', 'breakout.'..module.host);
module:depends('jitsi_session');
local breakout_rooms_muc_service;
local main_muc_service;
-- Maps a breakout room jid to the main room jid
local main_rooms_map = {};
-- Maps a full room JID to a bare connection jid for a participant that's changing rooms.
local cache = require 'util.cache';
local switching_room_cache = cache.new(1000);
-- Utility functions
function get_main_room_jid(room_jid)
local _, host = jid_split(room_jid);
return
host == main_muc_component_config
and room_jid
or main_rooms_map[room_jid];
end
function get_main_room(room_jid)
local main_room_jid = get_main_room_jid(room_jid);
return main_muc_service.get_room_from_jid(main_room_jid), main_room_jid;
end
function get_room_from_jid(room_jid)
local host = jid_host(room_jid);
return
host == main_muc_component_config
and main_muc_service.get_room_from_jid(room_jid)
or breakout_rooms_muc_service.get_room_from_jid(room_jid);
end
function send_json_msg(to_jid, json_msg)
local stanza = st.message({ from = breakout_rooms_muc_component_config; to = to_jid; })
:tag('json-message', { xmlns = 'http://jitsi.org/jitmeet' }):text(json_msg):up();
module:send(stanza);
end
function get_participants(room)
local participants = {};
if room then
for room_nick, occupant in room:each_occupant() do
-- Filter focus as we keep it as a hidden participant
if not is_admin(occupant.bare_jid) then
local display_name = occupant:get_presence():get_child_text(
'nick', 'http://jabber.org/protocol/nick');
local real_nick = internal_room_jid_match_rewrite(room_nick);
participants[real_nick] = {
jid = occupant.jid,
role = occupant.role,
displayName = display_name
};
end
end
end
return participants;
end
function broadcast_breakout_rooms(room_jid)
local main_room = get_main_room(room_jid);
if not main_room or main_room.broadcast_timer then
return;
end
-- Only send each BROADCAST_ROOMS_INTERVAL seconds to prevent flooding of messages.
main_room.broadcast_timer = module:add_timer(BROADCAST_ROOMS_INTERVAL, function()
local main_room, main_room_jid = get_main_room(room_jid);
if not main_room then
return;
end
main_room.broadcast_timer = nil;
local real_jid = internal_room_jid_match_rewrite(main_room_jid);
local real_node = jid_node(real_jid);
local rooms = {
[real_node] = {
isMainRoom = true,
id = real_node,
jid = real_jid,
name = main_room._data.subject,
participants = get_participants(main_room)
};
}
for breakout_room_jid, subject in pairs(main_room._data.breakout_rooms or {}) do
local breakout_room = breakout_rooms_muc_service.get_room_from_jid(breakout_room_jid);
local breakout_room_node = jid_node(breakout_room_jid)
rooms[breakout_room_node] = {
id = breakout_room_node,
jid = breakout_room_jid,
name = subject,
participants = {}
}
-- The room may not physically exist yet.
if breakout_room then
rooms[breakout_room_node].participants = get_participants(breakout_room);
end
end
local json_msg, error = json.encode({
type = BREAKOUT_ROOMS_IDENTITY_TYPE,
event = JSON_TYPE_UPDATE_BREAKOUT_ROOMS,
roomCounter = main_room._data.breakout_rooms_counter,
rooms = rooms
});
if not json_msg then
module:log('error', 'not broadcasting breakout room information room:%s error:%s', main_room_jid, error);
return;
end
for _, occupant in main_room:each_occupant() do
if not is_admin(occupant.bare_jid) then
send_json_msg(occupant.jid, json_msg)
end
end
for breakout_room_jid in pairs(main_room._data.breakout_rooms or {}) do
local room = breakout_rooms_muc_service.get_room_from_jid(breakout_room_jid);
if room then
for _, occupant in room:each_occupant() do
if not is_admin(occupant.bare_jid) then
send_json_msg(occupant.jid, json_msg)
end
end
end
end
end);
end
-- Managing breakout rooms
function create_breakout_room(orig_room, subject)
local main_room, main_room_jid = get_main_room(orig_room.jid);
if orig_room ~= main_room then
module:log('warn', 'Invalid create breakout room request for %s', orig_room.jid);
return;
end
local breakout_room_jid = uuid_gen() .. '@' .. breakout_rooms_muc_component_config;
if not main_room._data.breakout_rooms then
main_room._data.breakout_rooms = {};
main_room._data.breakout_rooms_counter = 0;
end
main_room._data.breakout_rooms_counter = main_room._data.breakout_rooms_counter + 1;
main_room._data.breakout_rooms[breakout_room_jid] = subject;
main_room._data.breakout_rooms_active = true;
-- Make room persistent - not to be destroyed - if all participants join breakout rooms.
main_room:set_persistent(true);
main_room:save(true);
main_rooms_map[breakout_room_jid] = main_room_jid;
broadcast_breakout_rooms(main_room_jid);
end
function destroy_breakout_room(orig_room, room_jid, message)
local main_room, main_room_jid = get_main_room(room_jid);
if room_jid == main_room_jid then
return;
end
if orig_room ~= main_room then
module:log('warn', 'Invalid destroy breakout room request for %s', orig_room.jid);
return;
end
local breakout_room = breakout_rooms_muc_service.get_room_from_jid(room_jid);
if breakout_room then
message = message or 'Breakout room removed.';
breakout_room:destroy(main_room and main_room_jid or nil, message);
end
if main_room then
if main_room._data.breakout_rooms then
main_room._data.breakout_rooms[room_jid] = nil;
end
main_room:save(true);
main_rooms_map[room_jid] = nil;
broadcast_breakout_rooms(main_room_jid);
end
end
function rename_breakout_room(orig_room, room_jid, name)
local main_room, main_room_jid = get_main_room(room_jid);
if room_jid == main_room_jid then
return;
end
if orig_room ~= main_room then
module:log('warn', 'Invalid rename breakout room request for %s', orig_room.jid);
return;
end
if main_room then
if main_room._data.breakout_rooms then
main_room._data.breakout_rooms[room_jid] = name;
local breakout_room = breakout_rooms_muc_service.get_room_from_jid(room_jid);
if breakout_room then
breakout_room:set_subject(breakout_room.jid, name);
end
end
main_room:save(true);
broadcast_breakout_rooms(main_room_jid);
end
end
-- Handling events
function on_message(event)
local session = event.origin;
-- Check the type of the incoming stanza to avoid loops:
if event.stanza.attr.type == 'error' then
return; -- We do not want to reply to these, so leave.
end
if not session or not session.jitsi_web_query_room then
return false;
end
local message = event.stanza:get_child(BREAKOUT_ROOMS_IDENTITY_TYPE);
if not message then
return false;
end
-- get room name with tenant and find room
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
module:log('warn', 'No room found for %s/%s',
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return false;
end
-- check that the participant requesting is a moderator and is an occupant in the room
local from = event.stanza.attr.from;
local occupant = get_occupant_by_real_jid(room, from);
if not occupant then
module:log('warn', 'No occupant %s found for %s', from, room.jid);
return false;
end
if occupant.role ~= 'moderator' then
module:log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid);
return false;
end
if message.attr.type == JSON_TYPE_ADD_BREAKOUT_ROOM then
create_breakout_room(room, message.attr.subject);
return true;
elseif message.attr.type == JSON_TYPE_REMOVE_BREAKOUT_ROOM then
destroy_breakout_room(room, message.attr.breakoutRoomJid);
return true;
elseif message.attr.type == JSON_TYPE_RENAME_BREAKOUT_ROOM then
rename_breakout_room(room, message.attr.breakoutRoomJid, message.attr.subject);
return true;
elseif message.attr.type == JSON_TYPE_MOVE_TO_ROOM_REQUEST then
local participant_jid = message.attr.participantJid;
local target_room_jid = message.attr.roomJid;
if not room._data.breakout_rooms or not (
room._data.breakout_rooms[target_room_jid] or target_room_jid == internal_room_jid_match_rewrite(room.jid))
then
module:log('warn', 'Invalid breakout room %s for %s', target_room_jid, room.jid);
return false
end
local json_msg, error = json.encode({
type = BREAKOUT_ROOMS_IDENTITY_TYPE,
event = JSON_TYPE_MOVE_TO_ROOM_REQUEST,
roomJid = target_room_jid
});
if not json_msg then
module:log('error', 'skip sending request room:%s error:%s', room.jid, error);
return false
end
send_json_msg(participant_jid, json_msg)
return true;
end
-- return error.
return false;
end
function on_breakout_room_pre_create(event)
local breakout_room = event.room;
local main_room, main_room_jid = get_main_room(breakout_room.jid);
-- Only allow existent breakout rooms to be started.
-- Authorisation of breakout rooms is done by their random uuid name
if main_room and main_room._data.breakout_rooms and main_room._data.breakout_rooms[breakout_room.jid] then
breakout_room:set_subject(breakout_room.jid, main_room._data.breakout_rooms[breakout_room.jid]);
else
module:log('debug', 'Invalid breakout room %s will not be created.', breakout_room.jid);
-- Tell the client that the join failed. Without this reply the join presence is
-- dropped without an answer, and the client waits until its own timeout.
-- Prosody sends an error for restrict_room_creation, but that handler and this one
-- both use the default priority, so the order between the two is not defined.
if event.origin and event.stanza then
event.origin.send(st.error_reply(
event.stanza, 'cancel', 'item-not-found', 'Breakout room is invalid.'));
end
breakout_room:destroy(main_room_jid, 'Breakout room is invalid.');
return true;
end
end
function on_occupant_joined(event)
local occupant, room, session = event.occupant, event.room, event.origin;
if is_healthcheck_room(room.jid) then
return;
end
local main_room, main_room_jid = get_main_room(room.jid);
local isFocus = is_admin(occupant.bare_jid);
if main_room and main_room._data.breakout_rooms_active then
if not isFocus then
broadcast_breakout_rooms(main_room_jid);
end
-- Prevent closing all rooms if a participant has joined (see on_occupant_left).
if main_room.close_timer then
main_room.close_timer:stop();
main_room.close_timer = nil;
end
-- clear any switching state for this occupant, we always store main room / resource
switching_room_cache:set(main_room_jid..'/'..jid_resource(occupant.nick), nil);
end
-- check if this is joining main room or breakout room
if main_room == room and not isFocus then
-- mark that this session was in the main room
session.jitsi_breakout_main_jid = main_room_jid;
end
end
function exist_occupants_in_room(room)
if not room then
return false;
end
for _, occupant in room:each_occupant() do
if not is_admin(occupant.bare_jid) then
return true;
end
end
return false;
end
function exist_occupants_in_rooms(main_room)
if exist_occupants_in_room(main_room) then
return true;
end
for breakout_room_jid in pairs(main_room._data.breakout_rooms or {}) do
local room = breakout_rooms_muc_service.get_room_from_jid(breakout_room_jid);
if exist_occupants_in_room(room) then
return true;
end
end
return false;
end
function on_occupant_pre_leave(event)
local room, occupant, session, stanza = event.room, event.occupant, event.origin, event.stanza;
local main_room = get_main_room(room.jid);
prosody.events.fire_event('jitsi-breakout-occupant-leaving', {
room = room; main_room = main_room; occupant = occupant; stanza = stanza; session = session;
});
local presence_status = stanza:get_child_text('status');
if presence_status == 'switch_room' then
switching_room_cache:set(main_room.jid..'/'..jid_resource(occupant.nick), jid_bare(occupant.jid));
end
end
function on_occupant_left(event)
local room_jid = event.room.jid;
if is_healthcheck_room(room_jid) then
return;
end
local main_room, main_room_jid = get_main_room(room_jid);
if not main_room then
return;
end
if main_room._data.breakout_rooms_active and not is_admin(event.occupant.bare_jid) then
broadcast_breakout_rooms(main_room_jid);
end
-- Close the conference if all left for good.
if main_room._data.breakout_rooms_active and not main_room.close_timer and not exist_occupants_in_rooms(main_room) then
main_room.close_timer = module:add_timer(ROOMS_TTL_IF_ALL_LEFT, function()
-- we need to look up again the room as till the timer is fired, the room maybe already destroyed/recreated
-- and we will have the old instance
local main_room, main_room_jid = get_main_room(room_jid);
if main_room and main_room.close_timer then
prosody.events.fire_event("maybe-destroy-room", {
room = main_room;
reason = 'All occupants left.';
caller = module:get_name();
});
end
end);
end
end
-- Stop other modules from destroying room if breakout rooms not empty
function handle_maybe_destroy_main_room(event)
local main_room = event.room;
local caller = event.caller;
if caller == module:get_name() then
-- we were the one that requested the deletion. Do not override.
return nil; -- stop room destruction
end
-- deletion was requested by another module. Check for break room occupants.
for breakout_room_jid, _ in pairs(main_room._data.breakout_rooms or {}) do
local breakout_room = breakout_rooms_muc_service.get_room_from_jid(breakout_room_jid);
if breakout_room and breakout_room:has_occupant() then
module:log('info', 'Suppressing room destroy. Breakout room still occupied %s', breakout_room_jid);
return true; -- stop room destruction
end
end
end
module:hook_global("maybe-destroy-room", handle_maybe_destroy_main_room)
function on_main_room_destroyed(event)
local main_room = event.room;
if is_healthcheck_room(main_room.jid) then
return;
end
for breakout_room_jid in pairs(main_room._data.breakout_rooms or {}) do
destroy_breakout_room(main_room, breakout_room_jid, event.reason)
end
end
-- Checks for a conflict with a JID in the switching_room_cache. In case of a conflict sends an error and returns true (the join is not allowed).
-- in switching_room_cache is the same as the jid that is sending the stanza, if that is the case we can allow
-- the join to proceed by returning false. If there is no match we send an error and return true
-- which should halt the join.
-- @param jid - The jid to check, this is the jid requested to join breakout or main room
-- @param from_bare_jid - The real jid of the occupant trying to join
-- @param room - The room being joined
-- @param stanza - The presence stanza
-- @param origin - The session origin to send error if needed
function check_switching_state(jid, from_bare_jid, room, stanza, origin)
local switching_session_jid = switching_room_cache:get(jid);
if switching_session_jid and switching_session_jid ~= from_bare_jid then
local reply = st.error_reply(stanza, "cancel", "conflict", nil, room.jid):up();
origin.send(reply);
return true;
end
return false;
end
function check_for_existing_occupant_in_room(room, requested_resource, bare_jid, stanza, origin)
local dest_occupant = room:get_occupant_by_nick(room.jid..'/'..requested_resource);
if dest_occupant ~= nil and bare_jid ~= jid_bare(dest_occupant.bare_jid) then
origin.send(st.error_reply(stanza, 'cancel', 'conflict', nil, room.jid):up());
return true;
end
end
-- This is a request to join or change jid in main or breakout room. We need to check whether the requested jid does not
-- conflict with a jid which is currently in switching state or already in another room.
function on_occupant_pre_join_or_change(e)
local room, stanza, origin = e.room, e.stanza, e.origin;
local occupant = e.dest_occupant or e.occupant;
local requested_jid = stanza.attr.to;
local main_room = get_main_room(room.jid);
-- case where the room can be destroyed while someone is switching to it
if not main_room then
origin.send(st.error_reply(stanza, 'cancel', 'service-unavailable'));
return true;
end
if is_admin(occupant.bare_jid) then
return;
end
local main_room_requested_jid = main_room.jid..'/'..jid_resource(requested_jid);
local bare_jid = jid_bare(stanza.attr.from);
-- we always store main room jid with resource in switching cache
if check_switching_state(main_room_requested_jid, bare_jid, room, stanza, origin) then
return true;
end
if main_room == room then
-- this is the main room we need to check all its breakout rooms
for breakout_room_jid in pairs(room._data.breakout_rooms or {}) do
local breakout_room = breakout_rooms_muc_service.get_room_from_jid(breakout_room_jid);
if breakout_room then
if check_for_existing_occupant_in_room(
breakout_room, jid_resource(requested_jid), bare_jid, stanza, origin) then
return true;
end
end
end
else
if origin.jitsi_breakout_main_jid ~= main_room.jid then
-- this session did not come from the main room
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed'));
return true;
end
-- this is a breakout room let's check the main room
if check_for_existing_occupant_in_room(main_room, jid_resource(requested_jid), bare_jid, stanza, origin) then
return true;
end
-- now let's check the rest of the breakout rooms
for breakout_room_jid in pairs(main_room._data.breakout_rooms or {}) do
local breakout_room = breakout_rooms_muc_service.get_room_from_jid(breakout_room_jid);
if breakout_room then
if check_for_existing_occupant_in_room(
breakout_room, jid_resource(requested_jid), bare_jid, stanza, origin) then
return true;
end
end
end
end
end
-- Module operations
-- operates on already loaded breakout rooms muc module
function process_breakout_rooms_muc_loaded(breakout_rooms_muc, host_module)
module:log('debug', 'Breakout rooms muc loaded');
-- Advertise the breakout rooms component so clients can pick up the address and use it
module:add_identity('component', BREAKOUT_ROOMS_IDENTITY_TYPE, breakout_rooms_muc_component_config);
-- Tag the disco#info response with available features of breakout rooms.
host_module:hook('host-disco-info-node', function (event)
local session, reply, node = event.origin, event.reply, event.node;
if node == BREAKOUT_ROOMS_IDENTITY_TYPE and session.jitsi_web_query_room then
reply:tag('feature', { var = RENAME_FEATURE }):up();
end
event.exists = true;
end);
breakout_rooms_muc_service = breakout_rooms_muc;
module:log("info", "Hook to muc events on %s", breakout_rooms_muc_component_config);
host_module:hook('message/host', on_message);
host_module:hook('muc-occupant-joined', on_occupant_joined);
host_module:hook('muc-occupant-left', on_occupant_left);
host_module:hook('muc-room-pre-create', on_breakout_room_pre_create);
host_module:hook('muc-occupant-pre-leave', on_occupant_pre_leave);
host_module:hook('muc-occupant-pre-join', on_occupant_pre_join_or_change);
host_module:hook('muc-occupant-pre-change', on_occupant_pre_join_or_change);
host_module:hook('muc-disco#info', function (event)
local room = event.room;
local main_room, main_room_jid = get_main_room(room.jid);
-- Breakout room metadata.
table.insert(event.form, {
name = 'muc#roominfo_isbreakout';
label = 'Is this a breakout room?';
type = "boolean";
});
event.formdata['muc#roominfo_isbreakout'] = true;
table.insert(event.form, {
name = 'muc#roominfo_breakout_main_room';
label = 'The main room associated with this breakout room';
});
event.formdata['muc#roominfo_breakout_main_room'] = internal_room_jid_match_rewrite(main_room_jid);
-- If the main room has a lobby, make it so this breakout room also uses it.
if (main_room and main_room._data.lobbyroom and main_room:get_members_only()) then
table.insert(event.form, {
name = 'muc#roominfo_lobbyroom';
label = 'Lobby room jid';
});
event.formdata['muc#roominfo_lobbyroom'] = main_room._data.lobbyroom;
end
end);
host_module:hook("muc-config-form", function(event)
local room = event.room;
local _, main_room_jid = get_main_room(room.jid);
-- Breakout room metadata.
table.insert(event.form, {
name = 'muc#roominfo_isbreakout';
label = 'Is this a breakout room?';
type = "boolean";
value = true;
});
table.insert(event.form, {
name = 'muc#roominfo_breakout_main_room';
label = 'The main room associated with this breakout room';
value = internal_room_jid_match_rewrite(main_room_jid);
});
end);
local room_mt = breakout_rooms_muc_service.room_mt;
room_mt.get_members_only = function(room)
local main_room = get_main_room(room.jid);
if not main_room then
module:log('error', 'No main room (%s)!', room.jid);
return false;
end
return main_room.get_members_only(main_room)
end
-- we base affiliations (roles) in breakout rooms muc component to be based on the roles in the main muc
room_mt.get_affiliation = function(room, jid)
local main_room, _ = get_main_room(room.jid);
if not main_room then
module:log('error', 'No main room(%s) for %s!', room.jid, jid);
return 'none';
end
-- moderators in main room are moderators here
local role = main_room.get_affiliation(main_room, jid);
if role then
return role;
end
return 'none';
end
end
-- process or waits to process the breakout rooms muc component
process_host_module(breakout_rooms_muc_component_config, function(host_module, host)
module:log('info', 'Breakout rooms component created %s', host);
local muc_module = prosody.hosts[host].modules.muc;
if muc_module then
process_breakout_rooms_muc_loaded(muc_module, host_module);
else
module:log('debug', 'Will wait for muc to be available');
prosody.hosts[host].events.add_handler('module-loaded', function(event)
if (event.module == 'muc') then
process_breakout_rooms_muc_loaded(prosody.hosts[host].modules.muc, host_module);
end
end);
end
end);
-- clears switching_room_cache on resource unbind (disconnect one way or another)
local function handle_pre_resource_unbind(event)
local participant_bare_jid = jid_bare(event.jid);
-- check switching_room_cache for anyone that is switching rooms but got disconnected so we can clean up the map
local keysToRemove = {};
for key, value in switching_room_cache:items() do
if value == participant_bare_jid then
table.insert(keysToRemove, key)
end
end
for _, key in ipairs(keysToRemove) do
switching_room_cache:set(key, nil);
end
end
module:hook_global('jitsi-pre-session-unbind', handle_pre_resource_unbind);
-- operates on already loaded main muc module
function process_main_muc_loaded(main_muc, host_module)
module:log('debug', 'Main muc loaded');
main_muc_service = main_muc;
module:log("info", "Hook to muc events on %s", main_muc_component_config);
host_module:hook('muc-occupant-joined', on_occupant_joined);
host_module:hook('muc-occupant-left', on_occupant_left);
host_module:hook('muc-room-destroyed', on_main_room_destroyed, 1); -- prosody handles it at 0
host_module:hook('muc-occupant-pre-leave', function(event)
local room, occupant, session, stanza = event.room, event.occupant, event.origin, event.stanza;
local presence_status = stanza:get_child_text('status');
if presence_status == 'switch_room' then
switching_room_cache:set(occupant.nick, jid_bare(occupant.jid));
end
end);
host_module:hook('muc-occupant-pre-join', on_occupant_pre_join_or_change);
host_module:hook('muc-occupant-pre-change', on_occupant_pre_join_or_change);
end
-- process or waits to process the main muc component
process_host_module(main_muc_component_config, function(host_module, host)
local muc_module = prosody.hosts[host].modules.muc;
if muc_module then
process_main_muc_loaded(muc_module, host_module);
else
module:log('debug', 'Will wait for muc to be available');
prosody.hosts[host].events.add_handler('module-loaded', function(event)
if (event.module == 'muc') then
process_main_muc_loaded(prosody.hosts[host].modules.muc, host_module);
end
end);
end
end);