mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 12:19:06 +00:00
372 lines
12 KiB
Lua
372 lines
12 KiB
Lua
local json = require "util.json"
|
|
local st = require "util.stanza"
|
|
local jid_bare = require "util.jid".bare
|
|
|
|
local recordings_path = os.getenv("JIBRI_RECORDINGS_PATH") or
|
|
module:get_option_string("jibri_recordings_path", "/recordings")
|
|
|
|
-- room_jid -> { session_id, participants = {jid -> info} }
|
|
local active_recordings = {}
|
|
-- room_jid -> { participants = {jid -> info}, created_at }
|
|
local room_states = {}
|
|
|
|
local function get_timestamp()
|
|
return os.time()
|
|
end
|
|
|
|
local function write_event(session_id, event)
|
|
if not session_id then
|
|
module:log("warn", "No session_id for event: %s", event.type)
|
|
return
|
|
end
|
|
|
|
local session_dir = string.format("%s/%s", recordings_path, session_id)
|
|
local event_file = string.format("%s/events.jsonl", session_dir)
|
|
|
|
module:log("info", "Writing event %s to %s", event.type, event_file)
|
|
|
|
-- Create directory
|
|
local mkdir_cmd = string.format("mkdir -p '%s' 2>&1", session_dir)
|
|
local mkdir_result = os.execute(mkdir_cmd)
|
|
module:log("debug", "mkdir result: %s", tostring(mkdir_result))
|
|
|
|
local file, err = io.open(event_file, "a")
|
|
if file then
|
|
local json_str = json.encode(event)
|
|
file:write(json_str .. "\n")
|
|
file:close()
|
|
module:log("info", "Successfully wrote event %s", event.type)
|
|
else
|
|
module:log("error", "Failed to write event to %s: %s", event_file, err)
|
|
end
|
|
end
|
|
|
|
local function extract_participant_info(occupant)
|
|
local info = {
|
|
jid = occupant.jid,
|
|
bare_jid = occupant.bare_jid,
|
|
nick = occupant.nick,
|
|
display_name = nil,
|
|
role = occupant.role
|
|
}
|
|
|
|
local presence = occupant:get_presence()
|
|
if presence then
|
|
local nick_element = presence:get_child("nick", "http://jabber.org/protocol/nick")
|
|
if nick_element then
|
|
info.display_name = nick_element:get_text()
|
|
end
|
|
|
|
local identity = presence:get_child("identity")
|
|
if identity then
|
|
local user = identity:get_child("user")
|
|
if user then
|
|
local name = user:get_child("name")
|
|
if name then
|
|
info.display_name = name:get_text()
|
|
end
|
|
|
|
local id_element = user:get_child("id")
|
|
if id_element then
|
|
info.id = id_element:get_text()
|
|
end
|
|
end
|
|
end
|
|
|
|
if not info.display_name and occupant.nick then
|
|
local _, _, resource = occupant.nick:match("([^@]+)@([^/]+)/(.+)")
|
|
if resource then
|
|
info.display_name = resource
|
|
end
|
|
end
|
|
end
|
|
|
|
return info
|
|
end
|
|
|
|
local function get_room_participant_count(room)
|
|
local count = 0
|
|
for _ in room:each_occupant() do
|
|
count = count + 1
|
|
end
|
|
return count
|
|
end
|
|
|
|
local function snapshot_room_participants(room)
|
|
local participants = {}
|
|
local total = 0
|
|
local skipped = 0
|
|
|
|
module:log("info", "Snapshotting room participants")
|
|
|
|
for _, occupant in room:each_occupant() do
|
|
total = total + 1
|
|
-- Skip recorders (Jibri)
|
|
if occupant.bare_jid and (occupant.bare_jid:match("^recorder@") or
|
|
occupant.bare_jid:match("^jibri@")) then
|
|
skipped = skipped + 1
|
|
else
|
|
local info = extract_participant_info(occupant)
|
|
participants[occupant.jid] = info
|
|
module:log("debug", "Added participant: %s", info.display_name or info.bare_jid)
|
|
end
|
|
end
|
|
|
|
module:log("info", "Snapshot: %d total, %d participants", total, total - skipped)
|
|
return participants
|
|
end
|
|
|
|
-- Import utility functions if available
|
|
local util = module:require "util";
|
|
local get_room_from_jid = util.get_room_from_jid;
|
|
local room_jid_match_rewrite = util.room_jid_match_rewrite;
|
|
|
|
-- Main IQ handler for Jibri stanzas
|
|
module:hook("pre-iq/full", function(event)
|
|
local stanza = event.stanza
|
|
if stanza.name ~= "iq" then
|
|
return
|
|
end
|
|
|
|
local jibri = stanza:get_child('jibri', 'http://jitsi.org/protocol/jibri')
|
|
if not jibri then
|
|
return
|
|
end
|
|
|
|
module:log("info", "=== Jibri IQ intercepted ===")
|
|
|
|
local action = jibri.attr.action
|
|
local session_id = jibri.attr.session_id
|
|
local room_jid = jibri.attr.room
|
|
local recording_mode = jibri.attr.recording_mode
|
|
local app_data = jibri.attr.app_data
|
|
|
|
module:log("info", "Jibri %s - session: %s, room: %s, mode: %s",
|
|
action or "?", session_id or "?", room_jid or "?", recording_mode or "?")
|
|
|
|
if not room_jid or not session_id then
|
|
module:log("warn", "Missing room_jid or session_id")
|
|
return
|
|
end
|
|
|
|
-- Get the room using util function
|
|
local room = get_room_from_jid(room_jid_match_rewrite(jid_bare(stanza.attr.to)))
|
|
if not room then
|
|
-- Try with the room_jid directly
|
|
room = get_room_from_jid(room_jid)
|
|
end
|
|
|
|
if not room then
|
|
module:log("error", "Room not found for jid: %s", room_jid)
|
|
return
|
|
end
|
|
|
|
module:log("info", "Room found: %s", room:get_name() or room_jid)
|
|
|
|
if action == "start" then
|
|
module:log("info", "Recording START for session %s", session_id)
|
|
|
|
-- Count and snapshot participants
|
|
local participant_count = 0
|
|
for _ in room:each_occupant() do
|
|
participant_count = participant_count + 1
|
|
end
|
|
|
|
local participants = snapshot_room_participants(room)
|
|
local participant_list = {}
|
|
for jid, info in pairs(participants) do
|
|
table.insert(participant_list, info)
|
|
end
|
|
|
|
active_recordings[room_jid] = {
|
|
session_id = session_id,
|
|
participants = participants,
|
|
started_at = get_timestamp()
|
|
}
|
|
|
|
write_event(session_id, {
|
|
type = "recording_started",
|
|
timestamp = get_timestamp(),
|
|
room_jid = room_jid,
|
|
room_name = room:get_name(),
|
|
session_id = session_id,
|
|
recording_mode = recording_mode,
|
|
app_data = app_data,
|
|
participant_count = participant_count,
|
|
participants_at_start = participant_list
|
|
})
|
|
|
|
elseif action == "stop" then
|
|
module:log("info", "Recording STOP for session %s", session_id)
|
|
|
|
local recording = active_recordings[room_jid]
|
|
if recording and recording.session_id == session_id then
|
|
write_event(session_id, {
|
|
type = "recording_stopped",
|
|
timestamp = get_timestamp(),
|
|
room_jid = room_jid,
|
|
room_name = room:get_name(),
|
|
session_id = session_id,
|
|
duration = get_timestamp() - recording.started_at,
|
|
participant_count = get_room_participant_count(room)
|
|
})
|
|
|
|
active_recordings[room_jid] = nil
|
|
else
|
|
module:log("warn", "No active recording found for room %s", room_jid)
|
|
end
|
|
end
|
|
end);
|
|
|
|
-- Room and participant event hooks
|
|
local function setup_room_hooks(host_module)
|
|
module:log("info", "Setting up room hooks on %s", host_module.host or "unknown")
|
|
|
|
-- Room created
|
|
host_module:hook("muc-room-created", function(event)
|
|
local room = event.room
|
|
local room_jid = room.jid
|
|
|
|
room_states[room_jid] = {
|
|
participants = {},
|
|
created_at = get_timestamp()
|
|
}
|
|
|
|
module:log("info", "Room created: %s", room_jid)
|
|
end)
|
|
|
|
-- Room destroyed
|
|
host_module:hook("muc-room-destroyed", function(event)
|
|
local room = event.room
|
|
local room_jid = room.jid
|
|
|
|
room_states[room_jid] = nil
|
|
active_recordings[room_jid] = nil
|
|
|
|
module:log("info", "Room destroyed: %s", room_jid)
|
|
end)
|
|
|
|
-- Occupant joined
|
|
host_module:hook("muc-occupant-joined", function(event)
|
|
local room = event.room
|
|
local occupant = event.occupant
|
|
local room_jid = room.jid
|
|
|
|
-- Skip recorders
|
|
if occupant.bare_jid and (occupant.bare_jid:match("^recorder@") or
|
|
occupant.bare_jid:match("^jibri@")) then
|
|
return
|
|
end
|
|
|
|
local participant_info = extract_participant_info(occupant)
|
|
|
|
-- Update room state
|
|
if room_states[room_jid] then
|
|
room_states[room_jid].participants[occupant.jid] = participant_info
|
|
end
|
|
|
|
-- Log to active recording if exists
|
|
local recording = active_recordings[room_jid]
|
|
if recording then
|
|
recording.participants[occupant.jid] = participant_info
|
|
|
|
write_event(recording.session_id, {
|
|
type = "participant_joined",
|
|
timestamp = get_timestamp(),
|
|
room_jid = room_jid,
|
|
room_name = room:get_name(),
|
|
participant = participant_info,
|
|
participant_count = get_room_participant_count(room)
|
|
})
|
|
end
|
|
|
|
module:log("info", "Participant joined %s: %s (%d total)",
|
|
room:get_name() or room_jid,
|
|
participant_info.display_name or participant_info.bare_jid,
|
|
get_room_participant_count(room))
|
|
end)
|
|
|
|
-- Occupant left
|
|
host_module:hook("muc-occupant-left", function(event)
|
|
local room = event.room
|
|
local occupant = event.occupant
|
|
local room_jid = room.jid
|
|
|
|
-- Skip recorders
|
|
if occupant.bare_jid and (occupant.bare_jid:match("^recorder@") or
|
|
occupant.bare_jid:match("^jibri@")) then
|
|
return
|
|
end
|
|
|
|
local participant_info = extract_participant_info(occupant)
|
|
|
|
-- Update room state
|
|
if room_states[room_jid] then
|
|
room_states[room_jid].participants[occupant.jid] = nil
|
|
end
|
|
|
|
-- Log to active recording if exists
|
|
local recording = active_recordings[room_jid]
|
|
if recording then
|
|
if recording.participants[occupant.jid] then
|
|
recording.participants[occupant.jid] = nil
|
|
end
|
|
|
|
write_event(recording.session_id, {
|
|
type = "participant_left",
|
|
timestamp = get_timestamp(),
|
|
room_jid = room_jid,
|
|
room_name = room:get_name(),
|
|
participant = participant_info,
|
|
participant_count = get_room_participant_count(room)
|
|
})
|
|
end
|
|
|
|
module:log("info", "Participant left %s: %s (%d remaining)",
|
|
room:get_name() or room_jid,
|
|
participant_info.display_name or participant_info.bare_jid,
|
|
get_room_participant_count(room))
|
|
end)
|
|
end
|
|
|
|
-- Module initialization
|
|
local current_host = module:get_host()
|
|
local host_type = module:get_host_type()
|
|
|
|
module:log("info", "Event Logger loading on %s (type: %s)", current_host, host_type or "unknown")
|
|
module:log("info", "Recording path: %s", recordings_path)
|
|
|
|
-- Setup room hooks based on host type
|
|
if host_type == "component" and current_host:match("^[^.]+%.") then
|
|
setup_room_hooks(module)
|
|
else
|
|
-- Try to find and hook to MUC component
|
|
local process_host_module = util.process_host_module
|
|
local muc_component_host = module:get_option_string("muc_component") or
|
|
module:get_option_string("main_muc")
|
|
|
|
if not muc_component_host then
|
|
local possible_hosts = {
|
|
"muc." .. current_host,
|
|
"conference." .. current_host,
|
|
"rooms." .. current_host
|
|
}
|
|
|
|
for _, host in ipairs(possible_hosts) do
|
|
if prosody.hosts[host] then
|
|
muc_component_host = host
|
|
module:log("info", "Auto-detected MUC component: %s", muc_component_host)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if muc_component_host then
|
|
process_host_module(muc_component_host, function(host_module, host)
|
|
module:log("info", "Hooking to MUC events on %s", host)
|
|
setup_room_hooks(host_module)
|
|
end)
|
|
else
|
|
module:log("error", "Could not find MUC component")
|
|
end
|
|
end |