mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 11:22:31 +00:00
The display name is used in messages when messages are coming from visitors or from the history. The display name is used only when the participant is not available in the meeting to get its name.
820 lines
29 KiB
Lua
820 lines
29 KiB
Lua
--- activate under main muc component
|
|
--- Add the following config under the main muc component
|
|
--- muc_room_default_presence_broadcast = {
|
|
--- visitor = false;
|
|
--- participant = true;
|
|
--- moderator = true;
|
|
--- };
|
|
--- Enable in global modules: 's2s_bidi'
|
|
--- Make sure 's2s' is not in modules_disabled
|
|
--- NOTE: Make sure all communication between prosodies is using the real jids ([foo]room1@muc.example.com), as there
|
|
--- are certain configs for whitelisted domains and connections that are domain based
|
|
--- TODO: filter presence from main occupants back to main prosody
|
|
local jid = require 'util.jid';
|
|
local st = require 'util.stanza';
|
|
local new_id = require 'util.id'.medium;
|
|
local filters = require 'util.filters';
|
|
local array = require 'util.array';
|
|
local set = require 'util.set';
|
|
local json = require 'cjson.safe';
|
|
|
|
local util = module:require 'util';
|
|
local is_admin = util.is_admin;
|
|
local ends_with = util.ends_with;
|
|
local is_vpaas = util.is_vpaas;
|
|
local room_jid_match_rewrite = util.room_jid_match_rewrite;
|
|
local get_room_from_jid = util.get_room_from_jid;
|
|
local get_focus_occupant = util.get_focus_occupant;
|
|
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
|
|
local presence_check_status = util.presence_check_status;
|
|
local respond_iq_result = util.respond_iq_result;
|
|
local table_compare = util.table_compare;
|
|
|
|
local PARTICIPANT_PROP_RAISE_HAND = 'jitsi_participant_raisedHand';
|
|
local PARTICIPANT_PROP_REQUEST_TRANSCRIPTION = 'jitsi_participant_requestingTranscription';
|
|
local PARTICIPANT_PROP_TRANSLATION_LANG = 'jitsi_participant_translation_language';
|
|
local TRANSCRIPT_DEFAULT_LANG = module:get_option_string('transcriptions_default_language', 'en');
|
|
|
|
-- this is the main virtual host of this vnode
|
|
local local_domain = module:get_option_string('muc_mapper_domain_base');
|
|
if not local_domain then
|
|
module:log('warn', "No 'muc_mapper_domain_base' option set, disabling fmuc plugin");
|
|
return;
|
|
end
|
|
|
|
-- this is the main virtual host of the main prosody that this vnode serves
|
|
local main_domain = module:get_option_string('main_domain');
|
|
if not main_domain then
|
|
module:log('warn', "No 'main_domain' option set, disabling fmuc plugin");
|
|
return;
|
|
end
|
|
|
|
local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference');
|
|
local local_muc_domain = muc_domain_prefix..'.'..local_domain;
|
|
|
|
local NICK_NS = 'http://jabber.org/protocol/nick';
|
|
local DISPLAY_NAME_NS = 'http://jitsi.org/protocol/display-name';
|
|
|
|
-- in certain cases we consider participants with token as moderators, this is the default behavior which can be turned off
|
|
local auto_promoted_with_token = module:get_option_boolean('visitors_auto_promoted_with_token', true);
|
|
|
|
-- we send stats for the total number of rooms, total number of participants and total number of visitors
|
|
local measure_rooms = module:measure('vnode-rooms', 'amount');
|
|
local measure_participants = module:measure('vnode-participants', 'amount');
|
|
local measure_visitors = module:measure('vnode-visitors', 'amount');
|
|
|
|
local sent_iq_cache = require 'util.cache'.new(200);
|
|
|
|
local sessions = prosody.full_sessions;
|
|
|
|
local function send_transcriptions_update(room)
|
|
-- let's notify main prosody
|
|
local lang_array = array();
|
|
local count = 0;
|
|
|
|
for k, v in pairs(room._transcription_languages) do
|
|
lang_array:push(v);
|
|
count = count + 1;
|
|
end
|
|
|
|
local iq_id = new_id();
|
|
sent_iq_cache:set(iq_id, socket.gettime());
|
|
module:send(st.iq({
|
|
type = 'set',
|
|
to = 'visitors.'..main_domain,
|
|
from = local_domain,
|
|
id = iq_id })
|
|
:tag('visitors', { xmlns = 'jitsi:visitors',
|
|
room = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..main_domain) })
|
|
:tag('transcription-languages', {
|
|
xmlns = 'jitsi:visitors',
|
|
langs = lang_array:unique():sort():concat(','),
|
|
count = tostring(count)
|
|
}):up());
|
|
end
|
|
|
|
local function remove_transcription(room, occupant)
|
|
local send_update = false;
|
|
if room._transcription_languages then
|
|
if room._transcription_languages[occupant.jid] then
|
|
send_update = true;
|
|
end
|
|
room._transcription_languages[occupant.jid] = nil;
|
|
end
|
|
|
|
if send_update then
|
|
send_transcriptions_update(room);
|
|
end
|
|
end
|
|
|
|
-- if lang is nil we will remove it from the list
|
|
local function add_transcription(room, occupant, lang)
|
|
if not room._transcription_languages then
|
|
room._transcription_languages = {};
|
|
end
|
|
|
|
local old = room._transcription_languages[occupant.jid];
|
|
room._transcription_languages[occupant.jid] = lang or TRANSCRIPT_DEFAULT_LANG;
|
|
|
|
if old ~= room._transcription_languages[occupant.jid] then
|
|
send_transcriptions_update(room);
|
|
end
|
|
end
|
|
|
|
-- mark all occupants as visitors
|
|
module:hook('muc-occupant-pre-join', function (event)
|
|
local occupant, room, origin, stanza = event.occupant, event.room, event.origin, event.stanza;
|
|
local node, host = jid.split(occupant.bare_jid);
|
|
local resource = jid.resource(occupant.nick);
|
|
|
|
if is_admin(occupant.bare_jid) then
|
|
return;
|
|
end
|
|
|
|
if prosody.hosts[host] then
|
|
-- local participants which host is defined in this prosody
|
|
if room._main_room_lobby_enabled then
|
|
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Visitors not allowed while lobby is on!')
|
|
:tag('no-visitors-lobby', { xmlns = 'jitsi:visitors' }));
|
|
return true;
|
|
else
|
|
occupant.role = 'visitor';
|
|
end
|
|
elseif room.moderators_list and room.moderators_list:contains(resource) then
|
|
-- remote participants, host is the main prosody
|
|
occupant.role = 'moderator';
|
|
end
|
|
end, 3);
|
|
|
|
-- if a visitor leaves we want to lower its hand if it was still raised before leaving
|
|
-- this is to clear indication for promotion on moderators visitors list
|
|
module:hook('muc-occupant-pre-leave', function (event)
|
|
local occupant = event.occupant;
|
|
|
|
---- we are interested only of visitors presence
|
|
if occupant.role ~= 'visitor' then
|
|
return;
|
|
end
|
|
|
|
local room = event.room;
|
|
|
|
-- let's check if the visitor has a raised hand send a lower hand
|
|
-- to main prosody
|
|
local pr = occupant:get_presence();
|
|
|
|
local raiseHand = pr:get_child_text(PARTICIPANT_PROP_RAISE_HAND);
|
|
|
|
-- a promotion detected let's send it to main prosody
|
|
if raiseHand and #raiseHand > 0 then
|
|
local iq_id = new_id();
|
|
sent_iq_cache:set(iq_id, socket.gettime());
|
|
local promotion_request = st.iq({
|
|
type = 'set',
|
|
to = 'visitors.'..main_domain,
|
|
from = local_domain,
|
|
id = iq_id })
|
|
:tag('visitors', { xmlns = 'jitsi:visitors',
|
|
room = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..main_domain) })
|
|
:tag('promotion-request', {
|
|
xmlns = 'jitsi:visitors',
|
|
jid = occupant.jid,
|
|
time = nil;
|
|
}):up();
|
|
|
|
module:send(promotion_request);
|
|
end
|
|
|
|
remove_transcription(room, occupant);
|
|
end, 1); -- rate limit is 0
|
|
|
|
-- Returns the main participants count and the visitors count
|
|
local function get_occupant_counts(room)
|
|
local main_count = 0;
|
|
local visitors_count = 0;
|
|
|
|
for _, o in room:each_occupant() do
|
|
if o.role == 'visitor' then
|
|
visitors_count = visitors_count + 1;
|
|
elseif not is_admin(o.bare_jid) then
|
|
main_count = main_count + 1;
|
|
end
|
|
end
|
|
|
|
return main_count, visitors_count;
|
|
end
|
|
|
|
local function cancel_destroy_timer(room)
|
|
if room.visitors_destroy_timer then
|
|
room.visitors_destroy_timer:stop();
|
|
room.visitors_destroy_timer = nil;
|
|
end
|
|
end
|
|
|
|
local function destroy_with_conference_ended(room)
|
|
-- if the room is being destroyed, ignore
|
|
if room.destroying then
|
|
return;
|
|
end
|
|
|
|
cancel_destroy_timer(room);
|
|
|
|
local main_count, visitors_count = get_occupant_counts(room);
|
|
module:log('info', 'Will destroy:%s main_occupants:%s visitors:%s', room.jid, main_count, visitors_count);
|
|
room:destroy(nil, 'Conference ended.');
|
|
return true;
|
|
end
|
|
|
|
-- schedules a new destroy timer which will destroy the room if there are no visitors after the timeout
|
|
local function schedule_destroy_timer(room)
|
|
cancel_destroy_timer(room);
|
|
|
|
room.visitors_destroy_timer = module:add_timer(15, function()
|
|
-- if the room is being destroyed, ignore
|
|
if room.destroying then
|
|
return;
|
|
end
|
|
|
|
local main_count, visitors_count = get_occupant_counts(room);
|
|
|
|
if visitors_count == 0 then
|
|
module:log('info', 'Will destroy:%s main_occupants:%s visitors:%s', room.jid, main_count, visitors_count);
|
|
room:destroy(nil, 'No visitors.');
|
|
end
|
|
end);
|
|
end
|
|
|
|
-- when occupant is leaving forward presences to jicofo for visitors
|
|
-- do not check occupant.role as it maybe already reset
|
|
-- if there are no main occupants or no visitors, destroy the room (give 15 seconds of grace period for reconnections)
|
|
module:hook('muc-occupant-left', function (event)
|
|
local room, occupant = event.room, event.occupant;
|
|
local occupant_domain = jid.host(occupant.bare_jid);
|
|
|
|
if prosody.hosts[occupant_domain] and not is_admin(occupant.bare_jid) then
|
|
local focus_occupant = get_focus_occupant(room);
|
|
if not focus_occupant then
|
|
if not room.destroying then
|
|
module:log('warn', 'No focus found for %s', room.jid);
|
|
end
|
|
return;
|
|
end
|
|
-- Let's forward unavailable presence to the special jicofo
|
|
room:route_stanza(st.presence({
|
|
to = focus_occupant.jid,
|
|
from = internal_room_jid_match_rewrite(occupant.nick),
|
|
type = 'unavailable' })
|
|
:tag('x', { xmlns = 'http://jabber.org/protocol/muc#user' })
|
|
:tag('item', {
|
|
affiliation = room:get_affiliation(occupant.bare_jid) or 'none';
|
|
role = 'none';
|
|
nick = event.nick;
|
|
jid = occupant.bare_jid }):up():up());
|
|
end
|
|
|
|
-- if the room is being destroyed, ignore
|
|
if room.destroying then
|
|
return;
|
|
end
|
|
|
|
-- if there are no main participants, the main room will be destroyed and
|
|
-- we can destroy and the visitor one as when jicofo leaves all visitors will reload
|
|
-- if there are no visitors give them 15 secs to reconnect, if not destroy it
|
|
local main_count, visitors_count = get_occupant_counts(room);
|
|
|
|
if visitors_count == 0 then
|
|
schedule_destroy_timer(room);
|
|
end
|
|
|
|
if main_count == 0 then
|
|
destroy_with_conference_ended(room);
|
|
end
|
|
end);
|
|
|
|
-- forward visitor presences to jicofo
|
|
-- detects raise hand in visitors presence, this is request for promotion
|
|
-- detects the requested transcription and its language to send updates for it
|
|
module:hook('muc-broadcast-presence', function (event)
|
|
local occupant = event.occupant;
|
|
|
|
---- we are interested only of visitors presence to send it to jicofo
|
|
if occupant.role ~= 'visitor' then
|
|
return;
|
|
end
|
|
|
|
local room = event.room;
|
|
local focus_occupant = get_focus_occupant(room);
|
|
|
|
if not focus_occupant then
|
|
return;
|
|
end
|
|
|
|
local actor, base_presence, nick, reason, x = event.actor, event.stanza, event.nick, event.reason, event.x;
|
|
local actor_nick;
|
|
if actor then
|
|
actor_nick = jid.resource(room:get_occupant_jid(actor));
|
|
end
|
|
|
|
-- create a presence to send it to jicofo, as jicofo is special :)
|
|
local full_x = st.clone(x.full or x);
|
|
|
|
room:build_item_list(occupant, full_x, false, nick, actor_nick, actor, reason);
|
|
local full_p = st.clone(base_presence):add_child(full_x);
|
|
full_p.attr.to = focus_occupant.jid;
|
|
room:route_to_occupant(focus_occupant, full_p);
|
|
|
|
local raiseHand = full_p:get_child_text(PARTICIPANT_PROP_RAISE_HAND);
|
|
-- a promotion detected let's send it to main prosody
|
|
if raiseHand then
|
|
local user_id;
|
|
local group_id;
|
|
local is_moderator;
|
|
local session = sessions[occupant.jid];
|
|
local identity = session and session.jitsi_meet_context_user;
|
|
|
|
if is_vpaas(room) and identity then
|
|
-- in case of moderator in vpaas meeting we want to do auto-promotion
|
|
local is_vpaas_moderator = identity.moderator;
|
|
if is_vpaas_moderator == 'true' or is_vpaas_moderator == true then
|
|
is_moderator = true;
|
|
end
|
|
else
|
|
-- The case with single moderator in the room, we want to report our id
|
|
-- so we can be auto promoted
|
|
if identity and identity.id then
|
|
user_id = session.jitsi_meet_context_user.id;
|
|
group_id = session.jitsi_meet_context_group;
|
|
|
|
if session.auth_token and auto_promoted_with_token then
|
|
if not session.jitsi_meet_tenant_mismatch or session.jitsi_web_query_prefix == '' then
|
|
-- non-vpaas and having a token is considered a moderator, and if it is not in '/' tenant
|
|
-- the tenant from url and token should match
|
|
is_moderator = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local iq_id = new_id();
|
|
sent_iq_cache:set(iq_id, socket.gettime());
|
|
local promotion_request = st.iq({
|
|
type = 'set',
|
|
to = 'visitors.'..main_domain,
|
|
from = local_domain,
|
|
id = iq_id })
|
|
:tag('visitors', { xmlns = 'jitsi:visitors',
|
|
room = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..main_domain) })
|
|
:tag('promotion-request', {
|
|
xmlns = 'jitsi:visitors',
|
|
jid = occupant.jid,
|
|
time = raiseHand,
|
|
userId = user_id,
|
|
groupId = group_id,
|
|
forcePromote = is_moderator and 'true' or 'false';
|
|
}):up();
|
|
|
|
local nick_element = occupant:get_presence():get_child('nick', NICK_NS);
|
|
if nick_element then
|
|
promotion_request:add_child(nick_element);
|
|
end
|
|
|
|
module:send(promotion_request);
|
|
end
|
|
|
|
local requestTranscriptionValue = full_p:get_child_text(PARTICIPANT_PROP_REQUEST_TRANSCRIPTION);
|
|
local hasTranscriptionEnabled = room._transcription_languages and room._transcription_languages[occupant.jid];
|
|
|
|
-- detect transcription
|
|
if requestTranscriptionValue == 'true' then
|
|
local lang = full_p:get_child_text(PARTICIPANT_PROP_TRANSLATION_LANG);
|
|
|
|
add_transcription(room, occupant, lang);
|
|
elseif hasTranscriptionEnabled then
|
|
remove_transcription(room, occupant, nil);
|
|
end
|
|
|
|
return;
|
|
end);
|
|
|
|
-- listens for responses to the iq sent for requesting promotion and forward it to the visitor
|
|
local function stanza_handler(event)
|
|
local origin, stanza = event.origin, event.stanza;
|
|
|
|
if stanza.name ~= 'iq' then
|
|
return;
|
|
end
|
|
|
|
if stanza.attr.type == 'result' and sent_iq_cache:get(stanza.attr.id) then
|
|
sent_iq_cache:set(stanza.attr.id, nil);
|
|
return true;
|
|
end
|
|
|
|
if stanza.attr.type ~= 'set' then
|
|
return;
|
|
end
|
|
|
|
local visitors_iq = event.stanza:get_child('visitors', 'jitsi:visitors');
|
|
if not visitors_iq then
|
|
return;
|
|
end
|
|
|
|
if stanza.attr.from ~= 'visitors.'..main_domain then
|
|
module:log('warn', 'not from visitors component, ignore! %s', stanza);
|
|
return true;
|
|
end
|
|
|
|
local room_jid = visitors_iq.attr.room;
|
|
local room = get_room_from_jid(room_jid_match_rewrite(room_jid));
|
|
|
|
if not room then
|
|
return;
|
|
end
|
|
|
|
local request_promotion = visitors_iq:get_child('promotion-response');
|
|
if not request_promotion then
|
|
return;
|
|
end
|
|
|
|
-- respond with successful receiving the iq
|
|
respond_iq_result(origin, stanza);
|
|
|
|
local req_jid = request_promotion.attr.jid;
|
|
-- now let's find the occupant and forward the response
|
|
local occupant = room:get_occupant_by_real_jid(req_jid);
|
|
|
|
if occupant then
|
|
stanza.attr.to = occupant.jid;
|
|
stanza.attr.from = room.jid;
|
|
room:route_to_occupant(occupant, stanza);
|
|
return true;
|
|
end
|
|
end
|
|
|
|
--process a host module directly if loaded or hooks to wait for its load
|
|
function process_host_module(name, callback)
|
|
local function process_host(host)
|
|
if host == name then
|
|
callback(module:context(host), host);
|
|
end
|
|
end
|
|
|
|
if prosody.hosts[name] == nil then
|
|
module:log('debug', 'No host/component found, will wait for it: %s', name)
|
|
|
|
-- when a host or component is added
|
|
prosody.events.add_handler('host-activated', process_host);
|
|
else
|
|
process_host(name);
|
|
end
|
|
end
|
|
-- if the message received ends with the main domain, these are system messages
|
|
-- for visitors, let's correct the room name there
|
|
local function message_handler(event)
|
|
local origin, stanza = event.origin, event.stanza;
|
|
|
|
if ends_with(stanza.attr.from, main_domain) then
|
|
stanza.attr.from = stanza.attr.from:sub(1, -(main_domain:len() + 1))..local_domain;
|
|
end
|
|
end
|
|
|
|
process_host_module(local_domain, function(host_module, host)
|
|
host_module:hook('iq/host', stanza_handler, 10);
|
|
host_module:hook('message/full', message_handler);
|
|
end);
|
|
|
|
-- add to message stanza display name for the visitor
|
|
-- remove existing nick to avoid forgery
|
|
local function process_display_name(stanza, occupant)
|
|
-- TODO drop nick extension used in messages in favor of new display-name extension
|
|
-- we want mobile client to update before dropping support for nick from messages
|
|
stanza:remove_children('nick', NICK_NS);
|
|
stanza:remove_children('display-name', DISPLAY_NAME_NS);
|
|
local nick_element = occupant:get_presence():get_child('nick', NICK_NS);
|
|
if nick_element then
|
|
stanza:add_child(nick_element);
|
|
stanza:tag('display-name', {
|
|
xmlns = DISPLAY_NAME_NS,
|
|
source = 'visitor'
|
|
}):text(nick_element:get_text()):up();
|
|
else
|
|
stanza:tag('nick', { xmlns = NICK_NS })
|
|
:text('anonymous'):up();
|
|
stanza:tag('display-name', {
|
|
xmlns = DISPLAY_NAME_NS,
|
|
source = 'visitor'
|
|
}):text('anonymous'):up();
|
|
end
|
|
end
|
|
|
|
-- only live chat is supported for visitors
|
|
module:hook('muc-occupant-groupchat', function(event)
|
|
local occupant, room, stanza = event.occupant, event.room, event.stanza;
|
|
local from = stanza.attr.from;
|
|
local occupant_host;
|
|
|
|
-- if there is no occupant this is a message from main, probably coming from other vnode
|
|
if occupant then
|
|
occupant_host = jid.host(occupant.bare_jid);
|
|
|
|
-- we manage nick only for visitors
|
|
if occupant_host ~= main_domain then
|
|
process_display_name(stanza, occupant);
|
|
end
|
|
|
|
stanza.attr.from = occupant.nick;
|
|
else
|
|
stanza.attr.from = jid.join(jid.node(from), module.host);
|
|
end
|
|
|
|
-- let's send it to main chat and rest of visitors here
|
|
for _, o in room:each_occupant() do
|
|
-- filter remote occupants
|
|
if jid.host(o.bare_jid) == local_domain then
|
|
room:route_to_occupant(o, stanza)
|
|
end
|
|
end
|
|
|
|
-- send to main participants only messages from local occupants (skip from remote vnodes)
|
|
if occupant and occupant_host == local_domain then
|
|
local main_message = st.clone(stanza);
|
|
main_message.attr.to = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..main_domain);
|
|
-- make sure we fix the from to be the real jid
|
|
main_message.attr.from = room_jid_match_rewrite(stanza.attr.from);
|
|
module:send(main_message);
|
|
end
|
|
stanza.attr.from = from; -- something prosody does internally
|
|
|
|
return true;
|
|
end, 55); -- prosody check for visitor's chat is prio 50, we want to override it
|
|
|
|
-- Private messaging support for visitors
|
|
module:hook('muc-private-message', function(event)
|
|
local room, stanza = event.room, event.stanza;
|
|
local from = stanza.attr.from;
|
|
local to = stanza.attr.to;
|
|
local recipient_occupant = room:get_occupant_by_nick(to);
|
|
local recipient_domain = recipient_occupant and jid.host(recipient_occupant.bare_jid) or nil;
|
|
local sender_occupant = room:get_occupant_by_nick(from);
|
|
local sender_domain = sender_occupant and jid.host(sender_occupant.bare_jid) or nil;
|
|
|
|
if sender_domain == nil or recipient_domain == nil then
|
|
return false;
|
|
end
|
|
|
|
-- If both sender and recipient are local (on this vnode)
|
|
if sender_domain == local_domain and recipient_domain == local_domain then
|
|
event.origin.send(st.error_reply(event.stanza, 'auth', 'forbidden',
|
|
'Private messaging between visitors is disabled on visitor nodes'));
|
|
return false; -- Prevent sending the original message and stop further processing
|
|
end
|
|
|
|
-- If sender is local (visitor node) and recipient is from main prosody, forward to main prosody
|
|
if sender_domain == local_domain and recipient_domain == main_domain then
|
|
local original_to = stanza.attr.to;
|
|
local original_from = stanza.attr.from;
|
|
|
|
process_display_name(stanza, sender_occupant);
|
|
|
|
-- Forward to main prosody, preserving the resource and original from
|
|
stanza.attr.to = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..main_domain, jid.resource(to));
|
|
module:send(stanza);
|
|
return false; -- Prevent sending the original message and stop further processing
|
|
end
|
|
|
|
-- For main->visitor messages, let the default MUC handler process it
|
|
-- We don't need to do anything special
|
|
if sender_domain == main_domain and recipient_domain == local_domain then
|
|
return; -- Return nothing, let other handlers continue. The default MUC handler will process it.
|
|
end
|
|
|
|
return false; -- Prevent sending the original message and stop further processing
|
|
end, 100); -- Lower priority to run after other handlers
|
|
|
|
-- we calculate the stats on the configured interval (60 seconds by default)
|
|
module:hook_global('stats-update', function ()
|
|
local participants_count, rooms_count, visitors_count = 0, 0, 0;
|
|
|
|
-- iterate over all rooms
|
|
for room in prosody.hosts[module.host].modules.muc.each_room() do
|
|
rooms_count = rooms_count + 1;
|
|
for _, o in room:each_occupant() do
|
|
if not is_admin(o.bare_jid) then
|
|
local _, host = jid.split(o.bare_jid);
|
|
if prosody.hosts[host] then -- local hosts are visitors (including jigasi)
|
|
visitors_count = visitors_count + 1;
|
|
else
|
|
participants_count = participants_count + 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
measure_rooms(rooms_count);
|
|
measure_visitors(visitors_count);
|
|
measure_participants(participants_count);
|
|
end);
|
|
|
|
-- we skip it till the main participants are added from the main prosody
|
|
module:hook('jicofo-unlock-room', function(e)
|
|
-- we do not block events we fired
|
|
if e.fmuc_fired then
|
|
return;
|
|
end
|
|
|
|
return true;
|
|
end);
|
|
|
|
-- handles incoming iq visitors stanzas
|
|
-- connect - sent after sending all main participant's presences
|
|
-- disconnect - sent when main room is destroyed or when we receive a 'disconnect-vnode' iq from jicofo
|
|
-- update - sent on:
|
|
-- * room secret is changed
|
|
-- * lobby enabled or disabled
|
|
-- * initially before connect to report currently joined moderators
|
|
-- * moderator participant joins main room
|
|
-- * a participant has been granted moderator rights
|
|
local function iq_from_main_handler(event)
|
|
local origin, stanza = event.origin, event.stanza;
|
|
|
|
if stanza.name ~= 'iq' then
|
|
return;
|
|
end
|
|
|
|
if stanza.attr.type == 'result' and sent_iq_cache:get(stanza.attr.id) then
|
|
sent_iq_cache:set(stanza.attr.id, nil);
|
|
return true;
|
|
end
|
|
|
|
if stanza.attr.type ~= 'set' then
|
|
return;
|
|
end
|
|
|
|
local visitors_iq = event.stanza:get_child('visitors', 'jitsi:visitors');
|
|
if not visitors_iq then
|
|
return;
|
|
end
|
|
|
|
if stanza.attr.from ~= main_domain then
|
|
module:log('warn', 'not from main prosody, ignore! %s', stanza);
|
|
return true;
|
|
end
|
|
|
|
local room_jid = visitors_iq.attr.room;
|
|
local room = get_room_from_jid(room_jid_match_rewrite(room_jid));
|
|
|
|
if not room then
|
|
module:log('warn', 'No room found %s in iq_from_main_handler for:%s', room_jid, visitors_iq);
|
|
return;
|
|
end
|
|
|
|
local node = visitors_iq:get_child('connect');
|
|
local fire_jicofo_unlock = true;
|
|
local process_disconnect = false;
|
|
|
|
if not node then
|
|
node = visitors_iq:get_child('update');
|
|
fire_jicofo_unlock = false;
|
|
end
|
|
|
|
if not node then
|
|
node = visitors_iq:get_child('disconnect');
|
|
process_disconnect = true;
|
|
end
|
|
|
|
if not node then
|
|
return;
|
|
end
|
|
|
|
-- respond with successful receiving the iq
|
|
respond_iq_result(origin, stanza);
|
|
|
|
if process_disconnect then
|
|
return destroy_with_conference_ended(room);
|
|
end
|
|
|
|
-- if there is password supplied use it
|
|
-- if this is update it will either set or remove the password
|
|
room:set_password(node.attr.password);
|
|
room._data.meetingId = node.attr.meetingId;
|
|
local createdTimestamp = node.attr.createdTimestamp;
|
|
room.created_timestamp = createdTimestamp and tonumber(createdTimestamp) or nil;
|
|
|
|
if node.attr.lobby == 'true' then
|
|
room._main_room_lobby_enabled = true;
|
|
elseif node.attr.lobby == 'false' then
|
|
room._main_room_lobby_enabled = false;
|
|
end
|
|
|
|
-- read the moderators list
|
|
room.moderators_list = room.moderators_list or set.new();
|
|
local moderators = node:get_child('moderators');
|
|
|
|
if moderators then
|
|
for _, child in ipairs(moderators.tags) do
|
|
if child.name == 'item' then
|
|
room.moderators_list:add(child.attr.epId);
|
|
end
|
|
end
|
|
|
|
-- let's check current occupants roles and promote them if needed
|
|
-- we change only main participants which are not moderators, but participant
|
|
for _, o in room:each_occupant() do
|
|
if not is_admin(o.bare_jid)
|
|
and o.role == 'participant'
|
|
and room.moderators_list:contains(jid.resource(o.nick)) then
|
|
room:set_affiliation(true, o.bare_jid, 'owner');
|
|
end
|
|
end
|
|
end
|
|
|
|
local files = node:get_child('files');
|
|
if files then
|
|
local received_files = {};
|
|
for _, child in ipairs(files.tags) do
|
|
if child.name == 'file' then
|
|
received_files[child.attr.id] = json.decode(child:get_text());
|
|
end
|
|
end
|
|
|
|
-- fire events so file sharing component will add/remove files and will notify clients
|
|
local removed, added = table_compare(room.jitsi_shared_files or {}, received_files)
|
|
for _, id in ipairs(removed) do
|
|
module:context(local_domain):fire_event('jitsi-filesharing-remove', {
|
|
room = room; id = id;
|
|
});
|
|
end
|
|
for _, id in ipairs(added) do
|
|
module:context(local_domain):fire_event('jitsi-filesharing-add', {
|
|
room = room; file = received_files[id];
|
|
});
|
|
end
|
|
end
|
|
|
|
if fire_jicofo_unlock then
|
|
-- everything is connected allow participants to join
|
|
module:fire_event('jicofo-unlock-room', { room = room; fmuc_fired = true; });
|
|
end
|
|
|
|
return true;
|
|
end
|
|
module:hook('iq/host', iq_from_main_handler, 10);
|
|
|
|
-- Filters presences (if detected) that are with destination the main prosody
|
|
function filter_stanza(stanza, session)
|
|
if (stanza.name == 'presence' or stanza.name == 'message') and session.type ~= 'c2s' then
|
|
-- we clone it so we do not affect broadcast using same stanza, sending it to clients
|
|
local f_st = st.clone(stanza);
|
|
f_st.skipMapping = true;
|
|
return f_st;
|
|
elseif stanza.name == 'presence' and session.type == 'c2s' and jid.node(stanza.attr.to) == 'focus' then
|
|
local x = stanza:get_child('x', 'http://jabber.org/protocol/muc#user');
|
|
if presence_check_status(x, '110') then
|
|
return stanza; -- no filter
|
|
end
|
|
|
|
-- we want to filter presences to jicofo for the main participants, skipping visitors
|
|
-- no point of having them, but if it is the one of the first to be sent
|
|
-- when first visitor is joining can produce the 'No hosts[from_host]' error as we
|
|
-- rewrite the from, but we need to not do it to be able to filter it later for the s2s
|
|
if jid.host(room_jid_match_rewrite(stanza.attr.from)) ~= local_muc_domain then
|
|
return nil; -- returning nil filters the stanza
|
|
end
|
|
end
|
|
return stanza; -- no filter
|
|
end
|
|
function filter_session(session)
|
|
-- domain mapper is filtering on default priority 0, and we need it before that
|
|
filters.add_filter(session, 'stanzas/out', filter_stanza, 2);
|
|
end
|
|
|
|
filters.add_filter_hook(filter_session);
|
|
|
|
function route_s2s_stanza(event)
|
|
local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
|
|
|
|
if to_host ~= main_domain then
|
|
return; -- continue with hook listeners
|
|
end
|
|
|
|
if stanza.name == 'message' then
|
|
if jid.resource(stanza.attr.to) then
|
|
-- there is no point of delivering messages to main participants individually
|
|
return true; -- drop it
|
|
end
|
|
return;
|
|
end
|
|
|
|
if stanza.name == 'presence' then
|
|
-- we want to leave only unavailable presences to go to main node
|
|
-- all other presences from jicofo or the main participants there is no point to go to the main node
|
|
-- they are anyway not handled
|
|
if stanza.attr.type ~= 'unavailable' then
|
|
return true; -- drop it
|
|
end
|
|
return;
|
|
end
|
|
end
|
|
|
|
-- routing to sessions in mod_s2s is -1 and -10, we want to hook before that to make sure to is correct
|
|
-- or if we want to filter that stanza
|
|
module:hook("route/remote", route_s2s_stanza, 10);
|