Update to 2025-06-16 16:00

This commit is contained in:
Daniel Berteaud
2025-06-16 16:00:13 +02:00
parent ffe9caded5
commit eb0714be6f
37 changed files with 1718 additions and 393 deletions

View File

@@ -14,8 +14,11 @@ 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 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;
@@ -23,6 +26,12 @@ 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 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');
@@ -43,6 +52,9 @@ local local_muc_domain = muc_domain_prefix..'.'..local_domain;
local NICK_NS = 'http://jabber.org/protocol/nick';
-- 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');
@@ -52,17 +64,72 @@ local sent_iq_cache = require 'util.cache'.new(200);
local sessions = prosody.full_sessions;
local um_is_admin = require 'core.usermanager'.is_admin;
local function is_admin(jid)
return um_is_admin(jid, module.host);
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 prosody.hosts[host] and not is_admin(occupant.bare_jid) then
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' }));
@@ -70,6 +137,9 @@ module:hook('muc-occupant-pre-join', function (event)
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);
@@ -89,7 +159,7 @@ module:hook('muc-occupant-pre-leave', function (event)
-- to main prosody
local pr = occupant:get_presence();
local raiseHand = pr:get_child_text('jitsi_participant_raisedHand');
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
@@ -111,6 +181,7 @@ module:hook('muc-occupant-pre-leave', function (event)
module:send(promotion_request);
end
remove_transcription(room, occupant);
end, 1); -- rate limit is 0
-- Returns the main participants count and the visitors count
@@ -136,6 +207,20 @@ local function cancel_destroy_timer(room)
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);
@@ -165,7 +250,9 @@ module:hook('muc-occupant-left', function (event)
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
module:log('info', 'No focus found for %s', room.jid);
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
@@ -194,10 +281,15 @@ module:hook('muc-occupant-left', function (event)
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;
@@ -227,10 +319,11 @@ module:hook('muc-broadcast-presence', function (event)
full_p.attr.to = focus_occupant.jid;
room:route_to_occupant(focus_occupant, full_p);
local raiseHand = full_p:get_child_text('jitsi_participant_raisedHand');
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;
@@ -246,14 +339,14 @@ module:hook('muc-broadcast-presence', function (event)
-- 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 room._data.moderator_id then
if room._data.moderator_id == user_id then
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
elseif session.auth_token then
-- non-vpass and having a token is considered a moderator
is_moderator = true;
end
end
end
@@ -272,6 +365,7 @@ module:hook('muc-broadcast-presence', function (event)
jid = occupant.jid,
time = raiseHand,
userId = user_id,
groupId = group_id,
forcePromote = is_moderator and 'true' or 'false';
}):up();
@@ -283,6 +377,18 @@ module:hook('muc-broadcast-presence', function (event)
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);
@@ -317,7 +423,6 @@ local function stanza_handler(event)
local room = get_room_from_jid(room_jid_match_rewrite(room_jid));
if not room then
module:log('warn', 'No room found %s', room_jid);
return;
end
@@ -327,12 +432,7 @@ local function stanza_handler(event)
end
-- respond with successful receiving the iq
origin.send(st.iq({
type = 'result';
from = stanza.attr.to;
to = stanza.attr.from;
id = stanza.attr.id
}));
respond_iq_result(origin, stanza);
local req_jid = request_promotion.attr.jid;
-- now let's find the occupant and forward the response
@@ -469,7 +569,15 @@ module:hook('jicofo-unlock-room', function(e)
return true;
end);
-- handles incoming iq connect stanzas
-- 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;
@@ -500,7 +608,7 @@ local function iq_from_main_handler(event)
local room = get_room_from_jid(room_jid_match_rewrite(room_jid));
if not room then
module:log('warn', 'No room found %s', room_jid);
module:log('warn', 'No room found %s in iq_from_main_handler for:%s', room_jid, visitors_iq);
return;
end
@@ -523,27 +631,16 @@ local function iq_from_main_handler(event)
end
-- respond with successful receiving the iq
origin.send(st.iq({
type = 'result';
from = stanza.attr.to;
to = stanza.attr.from;
id = stanza.attr.id
}));
respond_iq_result(origin, stanza);
if process_disconnect then
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;
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;
room._data.moderator_id = node.attr.moderatorId;
local createdTimestamp = node.attr.createdTimestamp;
room.created_timestamp = createdTimestamp and tonumber(createdTimestamp) or nil;
@@ -553,6 +650,28 @@ local function iq_from_main_handler(event)
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
if fire_jicofo_unlock then
-- everything is connected allow participants to join
module:fire_event('jicofo-unlock-room', { room = room; fmuc_fired = true; });