Files
jitsi-meet/resources/prosody-plugins/mod_room_metadata_component.lua
damencho a76f9d548b feat: Move to use cjson everywhere.
We were using prosody,util.json and cjson at the same time, but the latter is more performant.
Adds some error handling which were missing with the prosody util json one.
2024-04-05 11:26:51 -05:00

228 lines
6.8 KiB
Lua

-- This module implements a generic metadata storage system for rooms.
--
-- VirtualHost "jitmeet.example.com"
-- modules_enabled = {
-- "room_metadata"
-- }
-- room_metadata_component = "metadata.jitmeet.example.com"
-- main_muc = "conference.jitmeet.example.com"
--
-- Component "metadata.jitmeet.example.com" "room_metadata_component"
-- muc_component = "conference.jitmeet.example.com"
-- breakout_rooms_component = "breakout.jitmeet.example.com"
local jid_node = require 'util.jid'.node;
local json = require 'cjson.safe';
local st = require 'util.stanza';
local util = module:require 'util';
local is_healthcheck_room = util.is_healthcheck_room;
local get_room_from_jid = util.get_room_from_jid;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
local process_host_module = util.process_host_module;
local COMPONENT_IDENTITY_TYPE = 'room_metadata';
local FORM_KEY = 'muc#roominfo_jitsimetadata';
local muc_component_host = module:get_option_string('muc_component');
if muc_component_host == nil then
module:log("error", "No muc_component specified. No muc to operate on!");
return;
end
local breakout_rooms_component_host = module:get_option_string('breakout_rooms_component');
module:log("info", "Starting room metadata for %s", muc_component_host);
-- Utility functions
function getMetadataJSON(room)
local res, error = json.encode({
type = COMPONENT_IDENTITY_TYPE,
metadata = room.jitsiMetadata or {}
});
if not res then
module:log('error', 'Error encoding data room:%s', room.jid, error);
end
return res;
end
-- Putting the information on the config form / disco-info allows us to save
-- an extra message to users who join later.
function getFormData(room)
return {
name = FORM_KEY;
type = 'text-multi';
label = 'Room metadata';
value = getMetadataJSON(room);
};
end
function broadcastMetadata(room)
local json_msg = getMetadataJSON(room);
for _, occupant in room:each_occupant() do
send_json_msg(occupant.jid, room.jid, json_msg)
end
end
function send_json_msg(to_jid, room_jid, json_msg)
local stanza = st.message({ from = module.host; to = to_jid; })
:tag('json-message', { xmlns = 'http://jitsi.org/jitmeet', room = room_jid }):text(json_msg):up();
module:send(stanza);
end
-- Handling events
function room_created(event)
local room = event.room;
if is_healthcheck_room(room.jid) then
return ;
end
room.jitsiMetadata = {};
end
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(COMPONENT_IDENTITY_TYPE, 'http://jitsi.org/jitmeet');
local messageText = message:get_text();
if not message or not messageText then
return false;
end
local roomJid = message.attr.room;
local room = get_room_from_jid(room_jid_match_rewrite(roomJid));
if not room then
module:log('warn', 'No room found 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 = room:get_occupant_by_real_jid(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
local jsonData, error = json.decode(messageText);
if jsonData == nil then -- invalid JSON
module:log("error", "Invalid JSON message: %s error:%s", messageText, error);
return false;
end
if jsonData.key == nil or jsonData.data == nil then
module:log("error", "Invalid JSON payload, key or data are missing: %s", messageText);
return false;
end
room.jitsiMetadata[jsonData.key] = jsonData.data;
broadcastMetadata(room);
return true;
end
-- Module operations
-- handle messages to this component
module:hook("message/host", on_message);
-- operates on already loaded main muc module
function process_main_muc_loaded(main_muc, host_module)
module:log('debug', 'Main muc loaded');
module:log("info", "Hook to muc events on %s", muc_component_host);
host_module:hook("muc-room-created", room_created, -1);
host_module:hook('muc-disco#info', function (event)
local room = event.room;
table.insert(event.form, getFormData(room));
end);
host_module:hook("muc-config-form", function(event)
local room = event.room;
table.insert(event.form, getFormData(room));
end);
end
-- process or waits to process the main muc component
process_host_module(muc_component_host, 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);
-- breakout rooms support
function process_breakout_muc_loaded(breakout_muc, host_module)
module:log('debug', 'Breakout rooms muc loaded');
module:log("info", "Hook to muc events on %s", breakout_rooms_component_host);
host_module:hook("muc-room-created", room_created, -1);
host_module:hook('muc-disco#info', function (event)
local room = event.room;
table.insert(event.form, getFormData(room));
end);
host_module:hook("muc-config-form", function(event)
local room = event.room;
table.insert(event.form, getFormData(room));
end);
end
if breakout_rooms_component_host then
process_host_module(breakout_rooms_component_host, function(host_module, host)
local muc_module = prosody.hosts[host].modules.muc;
if muc_module then
process_breakout_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_muc_loaded(prosody.hosts[host].modules.muc, host_module);
end
end);
end
end);
end