mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-22 05:09:05 +00:00
feat: use jitsi file system
This commit is contained in:
212
server/contrib/jitsi/README.md
Normal file
212
server/contrib/jitsi/README.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# Event Logger for Docker-Jitsi-Meet
|
||||
|
||||
A Prosody module that logs Jitsi meeting events to JSONL files alongside recordings, enabling complete participant tracking and speaker statistics.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Running docker-jitsi-meet installation
|
||||
- Jibri configured for recording
|
||||
|
||||
## Installation
|
||||
|
||||
### Step 1: Copy the Module
|
||||
|
||||
Copy the Prosody module to your custom plugins directory:
|
||||
|
||||
```bash
|
||||
# Create the directory if it doesn't exist
|
||||
mkdir -p ~/.jitsi-meet-cfg/prosody/prosody-plugins-custom
|
||||
|
||||
# Copy the module
|
||||
cp mod_event_logger.lua ~/.jitsi-meet-cfg/prosody/prosody-plugins-custom/
|
||||
```
|
||||
|
||||
### Step 2: Update Your .env File
|
||||
|
||||
Add or modify these variables in your `.env` file:
|
||||
|
||||
```bash
|
||||
# If XMPP_MUC_MODULES already exists, append event_logger
|
||||
# Example: XMPP_MUC_MODULES=existing_module,event_logger
|
||||
XMPP_MUC_MODULES=event_logger
|
||||
|
||||
# Optional: Configure the module (these are defaults)
|
||||
JIBRI_RECORDINGS_PATH=/config/recordings
|
||||
JIBRI_LOG_SPEAKER_STATS=true
|
||||
JIBRI_SPEAKER_STATS_INTERVAL=10
|
||||
```
|
||||
|
||||
**Important**: If you already have `XMPP_MUC_MODULES` defined, add `event_logger` to the comma-separated list:
|
||||
```bash
|
||||
# Existing modules + our module
|
||||
XMPP_MUC_MODULES=mod_info,mod_alert,event_logger
|
||||
```
|
||||
|
||||
### Step 3: Modify docker-compose.yml
|
||||
|
||||
Add a shared recordings volume so Prosody can write events alongside Jibri recordings:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
prosody:
|
||||
# ... existing configuration ...
|
||||
volumes:
|
||||
- ${CONFIG}/prosody/config:/config:Z
|
||||
- ${CONFIG}/prosody/prosody-plugins-custom:/prosody-plugins-custom:Z
|
||||
- ${CONFIG}/recordings:/config/recordings:Z # Add this line
|
||||
environment:
|
||||
# Add if not using .env file
|
||||
- XMPP_MUC_MODULES=${XMPP_MUC_MODULES:-event_logger}
|
||||
- JIBRI_RECORDINGS_PATH=/config/recordings
|
||||
|
||||
jibri:
|
||||
# ... existing configuration ...
|
||||
volumes:
|
||||
- ${CONFIG}/jibri:/config:Z
|
||||
- ${CONFIG}/recordings:/config/recordings:Z # Add this line
|
||||
environment:
|
||||
# For Reflector webhook integration (optional)
|
||||
- REFLECTOR_WEBHOOK_URL=${REFLECTOR_WEBHOOK_URL:-}
|
||||
- JIBRI_FINALIZE_RECORDING_SCRIPT_PATH=/config/finalize.sh
|
||||
```
|
||||
|
||||
### Step 4: Add Finalize Script (Optional - For Reflector Integration)
|
||||
|
||||
If you want to notify Reflector when recordings complete:
|
||||
|
||||
```bash
|
||||
# Copy the finalize script
|
||||
cp finalize.sh ~/.jitsi-meet-cfg/jibri/finalize.sh
|
||||
chmod +x ~/.jitsi-meet-cfg/jibri/finalize.sh
|
||||
|
||||
# Add to .env
|
||||
REFLECTOR_WEBHOOK_URL=http://your-reflector-api:8000
|
||||
```
|
||||
|
||||
### Step 5: Restart Services
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## What Gets Created
|
||||
|
||||
After a recording, you'll find in `~/.jitsi-meet-cfg/recordings/{session-id}/`:
|
||||
- `recording.mp4` - The video recording (created by Jibri)
|
||||
- `metadata.json` - Basic metadata (created by Jibri)
|
||||
- `events.jsonl` - Complete participant timeline (created by this module)
|
||||
|
||||
## Event Format
|
||||
|
||||
Each line in `events.jsonl` is a JSON object:
|
||||
|
||||
```json
|
||||
{"type":"room_created","timestamp":1234567890,"room_name":"TestRoom","room_jid":"testroom@conference.meet.jitsi","meeting_url":"https://meet.jitsi/TestRoom"}
|
||||
{"type":"recording_started","timestamp":1234567891,"room_name":"TestRoom","session_id":"20240115120000_TestRoom","jibri_jid":"jibri@recorder.meet.jitsi"}
|
||||
{"type":"participant_joined","timestamp":1234567892,"room_name":"TestRoom","participant":{"jid":"user1@meet.jitsi/web","nick":"John Doe","id":"user1@meet.jitsi","is_moderator":false}}
|
||||
{"type":"speaker_active","timestamp":1234567895,"room_name":"TestRoom","speaker_jid":"user1@meet.jitsi","speaker_nick":"John Doe","duration":10}
|
||||
{"type":"participant_left","timestamp":1234567920,"room_name":"TestRoom","participant":{"jid":"user1@meet.jitsi/web","nick":"John Doe","duration_seconds":28}}
|
||||
{"type":"recording_stopped","timestamp":1234567950,"room_name":"TestRoom","session_id":"20240115120000_TestRoom","meeting_url":"https://meet.jitsi/TestRoom"}
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
All configuration can be done via environment variables:
|
||||
|
||||
| Environment Variable | Default | Description |
|
||||
|---------------------|---------|-------------|
|
||||
| `JIBRI_RECORDINGS_PATH` | `/config/recordings` | Path where recordings are stored |
|
||||
| `JIBRI_LOG_SPEAKER_STATS` | `true` | Enable speaker statistics logging |
|
||||
| `JIBRI_SPEAKER_STATS_INTERVAL` | `10` | Seconds between speaker stats updates |
|
||||
|
||||
## Verifying Installation
|
||||
|
||||
Check that the module is loaded:
|
||||
```bash
|
||||
docker-compose logs prosody | grep "Event Logger"
|
||||
# Should see: "Event Logger loaded - writing to /config/recordings"
|
||||
```
|
||||
|
||||
Check for events after a recording:
|
||||
```bash
|
||||
ls -la ~/.jitsi-meet-cfg/recordings/*/events.jsonl
|
||||
cat ~/.jitsi-meet-cfg/recordings/*/events.jsonl | jq .
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No events.jsonl file created
|
||||
|
||||
1. **Check module is enabled**:
|
||||
```bash
|
||||
docker-compose exec prosody grep -r "event_logger" /config
|
||||
```
|
||||
|
||||
2. **Verify volume permissions**:
|
||||
```bash
|
||||
docker-compose exec prosody ls -la /config/recordings
|
||||
```
|
||||
|
||||
3. **Check Prosody logs for errors**:
|
||||
```bash
|
||||
docker-compose logs prosody | grep -i error
|
||||
```
|
||||
|
||||
### Module not loading
|
||||
|
||||
1. **Verify file exists in container**:
|
||||
```bash
|
||||
docker-compose exec prosody ls -la /prosody-plugins-custom/
|
||||
```
|
||||
|
||||
2. **Check XMPP_MUC_MODULES format** (must be comma-separated, no spaces):
|
||||
- ✅ Correct: `XMPP_MUC_MODULES=mod1,mod2,event_logger`
|
||||
- ❌ Wrong: `XMPP_MUC_MODULES=mod1, mod2, event_logger`
|
||||
|
||||
## Common docker-compose.yml Patterns
|
||||
|
||||
### Minimal Addition (if you trust defaults)
|
||||
```yaml
|
||||
services:
|
||||
prosody:
|
||||
volumes:
|
||||
- ${CONFIG}/recordings:/config/recordings:Z # Just add this
|
||||
```
|
||||
|
||||
### Full Configuration
|
||||
```yaml
|
||||
services:
|
||||
prosody:
|
||||
volumes:
|
||||
- ${CONFIG}/prosody/config:/config:Z
|
||||
- ${CONFIG}/prosody/prosody-plugins-custom:/prosody-plugins-custom:Z
|
||||
- ${CONFIG}/recordings:/config/recordings:Z
|
||||
environment:
|
||||
- XMPP_MUC_MODULES=event_logger
|
||||
- JIBRI_RECORDINGS_PATH=/config/recordings
|
||||
- JIBRI_LOG_SPEAKER_STATS=true
|
||||
- JIBRI_SPEAKER_STATS_INTERVAL=10
|
||||
|
||||
jibri:
|
||||
volumes:
|
||||
- ${CONFIG}/jibri:/config:Z
|
||||
- ${CONFIG}/recordings:/config/recordings:Z
|
||||
environment:
|
||||
- JIBRI_RECORDING_DIR=/config/recordings
|
||||
- JIBRI_FINALIZE_RECORDING_SCRIPT_PATH=/config/finalize.sh
|
||||
```
|
||||
|
||||
## Integration with Reflector
|
||||
|
||||
The finalize.sh script will automatically notify Reflector when a recording completes if `REFLECTOR_WEBHOOK_URL` is set. Reflector will receive:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "20240115120000_TestRoom",
|
||||
"path": "20240115120000_TestRoom",
|
||||
"meeting_url": "https://meet.jitsi/TestRoom"
|
||||
}
|
||||
```
|
||||
|
||||
Reflector then processes the recording along with the complete participant timeline from `events.jsonl`.
|
||||
49
server/contrib/jitsi/finalize.sh
Executable file
49
server/contrib/jitsi/finalize.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Jibri finalize script to notify Reflector when recording is complete
|
||||
# This script is called by Jibri with the recording directory as argument
|
||||
|
||||
RECORDING_PATH="$1"
|
||||
SESSION_ID=$(basename "$RECORDING_PATH")
|
||||
METADATA_FILE="$RECORDING_PATH/metadata.json"
|
||||
|
||||
# Extract meeting URL from Jibri's metadata
|
||||
MEETING_URL=""
|
||||
if [ -f "$METADATA_FILE" ]; then
|
||||
MEETING_URL=$(jq -r '.meeting_url' "$METADATA_FILE" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
echo "[$(date)] Recording finalized: $RECORDING_PATH"
|
||||
echo "[$(date)] Session ID: $SESSION_ID"
|
||||
echo "[$(date)] Meeting URL: $MEETING_URL"
|
||||
|
||||
# Check if events.jsonl was created by our Prosody module
|
||||
if [ -f "$RECORDING_PATH/events.jsonl" ]; then
|
||||
EVENT_COUNT=$(wc -l < "$RECORDING_PATH/events.jsonl")
|
||||
echo "[$(date)] Found events.jsonl with $EVENT_COUNT events"
|
||||
else
|
||||
echo "[$(date)] Warning: No events.jsonl found"
|
||||
fi
|
||||
|
||||
# Notify Reflector if webhook URL is configured
|
||||
if [ -n "$REFLECTOR_WEBHOOK_URL" ]; then
|
||||
echo "[$(date)] Notifying Reflector at: $REFLECTOR_WEBHOOK_URL"
|
||||
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$REFLECTOR_WEBHOOK_URL/api/v1/jibri/recording-ready" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"session_id\":\"$SESSION_ID\",\"path\":\"$SESSION_ID\",\"meeting_url\":\"$MEETING_URL\"}")
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo "[$(date)] Reflector notified successfully"
|
||||
echo "[$(date)] Response: $BODY"
|
||||
else
|
||||
echo "[$(date)] Failed to notify Reflector. HTTP code: $HTTP_CODE"
|
||||
echo "[$(date)] Response: $BODY"
|
||||
fi
|
||||
else
|
||||
echo "[$(date)] No REFLECTOR_WEBHOOK_URL configured, skipping notification"
|
||||
fi
|
||||
|
||||
echo "[$(date)] Finalize script completed"
|
||||
372
server/contrib/jitsi/mod_event_logger.lua
Normal file
372
server/contrib/jitsi/mod_event_logger.lua
Normal file
@@ -0,0 +1,372 @@
|
||||
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
|
||||
Reference in New Issue
Block a user