Merge pull request #13111 from kepstin/rec-anonymize-chat

recording: Anonymize names in chat
This commit is contained in:
Anton Georgiev 2021-09-03 15:40:32 -04:00 committed by GitHub
commit d332d200ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 13518 additions and 54 deletions

2
record-and-playback/core/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.bundle
vendor/bundle

View File

@ -34,7 +34,9 @@ gem 'rubyzip', '~> 2.0'
gem 'trollop', '2.1.3'
gem 'resque', '~> 2.0.0'
gem 'bbbevents', '~> 1.2'
gem 'rake', '>= 12.3', '<14'
group :test, optional: true do
gem 'rubocop', '~> 0.79.0'
gem 'minitest', '~> 5.14.1'
end

View File

@ -45,6 +45,7 @@ GEM
rack-protection (2.0.8.1)
rack
rainbow (3.0.0)
rake (13.0.6)
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (4.1.3)
@ -93,8 +94,10 @@ DEPENDENCIES
jwt (~> 2.2)
locale (~> 2.1)
loofah (~> 2.3)
minitest (~> 5.14.1)
nokogiri (~> 1.11)
open4 (~> 1.3)
rake (>= 12.3, < 14)
rb-inotify (~> 0.10)
redis (~> 4.1)
resque (~> 2.0.0)

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'resque/tasks'
require 'rake/testtask'
task 'resque:setup' => :environment do
props = BigBlueButton.read_props
@ -17,3 +18,9 @@ task :environment do
require_relative 'lib/boot'
require 'recordandplayback/workers'
end
Rake::TestTask.new(:test) do |t|
t.libs << 'test'
t.libs << 'lib'
t.test_files = FileList['test/**/test_*.rb']
end

View File

@ -21,13 +21,14 @@
require 'rubygems'
require 'time'
require 'nokogiri'
require 'loofah'
require 'set'
module BigBlueButton
module Events
# Get the total number of participants
def self.get_num_participants(events)
participants_ids = Set.new
@ -75,31 +76,31 @@ module BigBlueButton
external_meeting_id = metadata['meetingId'] if !metadata['meetingId'].nil?
external_meeting_id
end
# Get the timestamp of the first event.
def self.first_event_timestamp(events)
first_event = events.at_xpath('/recording/event[position() = 1]')
first_event['timestamp'].to_i if first_event && first_event.key?('timestamp')
end
# Get the timestamp of the last event.
def self.last_event_timestamp(events)
last_event = events.at_xpath('/recording/event[position() = last()]')
last_event['timestamp'].to_i if last_event && last_event.key?('timestamp')
end
end
# Determine if the start and stop event matched.
def self.find_video_event_matched(start_events, stop)
def self.find_video_event_matched(start_events, stop)
BigBlueButton.logger.info("Task: Finding video events that match")
start_events.each do |start|
if (start[:stream] == stop[:stream])
return start
end
end
end
return nil
end
# Get start video events
# Get start video events
def self.get_start_video_events(events)
start_events = []
events.xpath("/recording/event[@eventname='StartWebcamShareEvent']").each do |start_event|
@ -235,7 +236,7 @@ module BigBlueButton
end
def self.get_stop_deskshare_events(events)
BigBlueButton.logger.info("Task: Getting stop DESKSHARE events")
BigBlueButton.logger.info("Task: Getting stop DESKSHARE events")
stop_events = []
events.xpath('/recording/event[@module="Deskshare" or (@module="bbb-webrtc-sfu" and @eventname="StopWebRTCDesktopShareEvent")]').each do |stop_event|
case stop_event['eventname']
@ -476,12 +477,134 @@ module BigBlueButton
node['href'] = node['href'][6..-1] if node.name == 'a' && node['href'] && node['href'].start_with?('event:')
end
def self.linkify( text )
def self.linkify(text)
html = Loofah.fragment(text)
html.scrub!(@remove_link_event_prefix).scrub!(:strip).scrub!(:nofollow).scrub!(:unprintable)
html.to_html
end
# Build a map of internal user IDs to anonymized names. This can be used to anonymize users in
# chat, cursor overlays, etc.
def self.anonymous_user_map(events, moderators: false)
viewer_count = 0
moderator_count = 0
external_map = {}
map = {}
events.xpath('/recording/event[@module="PARTICIPANT" and @eventname="ParticipantJoinEvent"]').each do |event|
internal_id = event.at_xpath('./userId')&.content
next if internal_id.nil?
external_id = event.at_xpath('./externalUserId')&.content || internal_id
name = external_map.fetch(external_id) do
role = event.at_xpath('./role').content
new_name = \
if role == 'MODERATOR' && moderators
moderator_count += 1
"Moderator #{moderator_count}"
elsif role == 'MODERATOR'
event.at_xpath('./name')&.content
else
viewer_count += 1
"Viewer #{viewer_count}"
end
external_map[external_id] = new_name unless new_name.nil?
end
map[internal_id] = name unless name.nil?
end
map
end
# Get a list of chat events, with start/end time for segments and recording marks applied.
# Optionally anonymizes chat participant names.
# Reads the keys 'anonymize_chat' and 'anonymize_chat_moderators' from bbb_props, but allows
# per-meeting override using the create meta params 'meta_bbb-anonymize-chat' and
# 'meta_bbb-anonymize-chat-moderators'
# Each event in the return value has the following properties:
# in: 0-based milliseconds timestamp of when chat was sent
# out: 0-based milliseconds timestamp of when chat was cleared (or nil if chat was never cleared)
# sender_id: The internal user id of the sender (can be nil on really old BBB versions)
# sender: The display name of the sender
# message: The chat message, with link cleanup already applied
# date: The real time of when the message was sent (if available) as a DateTime
# text_color: The RGB color value of the chat message text as an integer (old BBB versions only)
# avatar_color: The color of the user's avatar (initials) box (newer BBB versions only)
def self.get_chat_events(events, start_time, end_time, bbb_props = {})
BigBlueButton.logger.info('Getting chat events')
initial_timestamp = first_event_timestamp(events)
start_time -= initial_timestamp
end_time -= initial_timestamp
last_stop_timestamp = start_time
offset = start_time
# Recordings without status events are assumed to have been recorded from the beginning
record = events.at_xpath('/recording/event[@eventname="RecordStatusEvent"]').nil?
# Load the anonymize settings; defaults from bigbluebutton.yml, override with meta params
metadata = events.at_xpath('/recording/metadata')
anonymize_senders = metadata['bbb-anonymize-chat'] unless metadata.nil?
anonymize_senders = bbb_props['anonymize_chat'] if anonymize_senders.nil?
anonymize_senders = anonymize_senders.to_s.casecmp?('true')
anonymize_moderators = metadata['bbb-anonymize-chat-moderators'] unless metadata.nil?
anonymize_moderators = bbb_props['anonymize_chat_moderators'] if anonymize_moderators.nil?
anonymize_moderators = anonymize_moderators.to_s.casecmp?('true')
user_map = anonymize_senders ? anonymous_user_map(events, moderators: anonymize_moderators) : {}
chats = []
events.xpath('/recording/event').each do |event|
timestamp = event[:timestamp].to_i - initial_timestamp
break if timestamp >= end_time
case [event[:module], event[:eventname]]
when %w[CHAT PublicChatEvent]
next if timestamp < start_time || !record
date = event.at_xpath('./date')&.content
date = DateTime.iso8601(date) unless date.nil?
sender_id = event.at_xpath('./senderId')&.content
color = event.at_xpath('./color')&.content
if color&.start_with?('#')
avatar_color = color
else
text_color = color.to_i
end
chats << {
in: timestamp - offset,
out: nil,
sender_id: sender_id,
sender: user_map.fetch(sender_id) { event.at_xpath('./sender').content },
message: linkify(event.at_xpath('./message').content.strip),
avatar_color: avatar_color,
text_color: text_color,
date: date,
}
when %w[CHAT ClearPublicChatEvent]
next if timestamp < start_time
clear_timestamp = (record ? timestamp : last_stop_timestamp) - offset
chats.each do |chat|
chat[:out] = clear_timestamp if chat[:out].nil?
end
when %w[PARTICIPANT RecordStatusEvent]
record = event.at_xpath('status').content == 'true'
next if timestamp < start_time
if record
offset += timestamp - last_stop_timestamp
else
last_stop_timestamp = timestamp
end
end
end
chats
end
def self.get_record_status_events(events_xml)
BigBlueButton.logger.info "Getting record status events"
rec_events = []
@ -496,8 +619,8 @@ module BigBlueButton
BigBlueButton.logger.info "Getting external video events"
external_videos_events = []
events_xml.xpath("//event[@eventname='StartExternalVideoRecordEvent']").each do |event|
s = {
:timestamp => event['timestamp'].to_i,
s = {
:timestamp => event['timestamp'].to_i,
:external_video_url => event.at_xpath("externalVideoUrl").text
}
external_videos_events << s
@ -523,7 +646,7 @@ module BigBlueButton
end
rec_events.sort_by {|a| a[:timestamp]}
end
# Get events when the moderator wants the recording to start or stop
def self.get_start_and_stop_external_video_events(events_xml)
BigBlueButton.logger.info "Getting start and stop externalvideo events"
@ -534,7 +657,7 @@ module BigBlueButton
end
external_video_events.sort_by {|a| a[:timestamp]}
end
# Match recording start and stop events
def self.match_start_and_stop_rec_events(rec_events)
BigBlueButton.logger.info ("Matching record events")

View File

@ -0,0 +1,422 @@
<?xml version="1.0" encoding="UTF-8"?>
<recording meeting_id="2a1de53edf0543d950056bf3c0d4d357eba3383f-1630607370684" bbb_version="2.1.0">
<meeting id="2a1de53edf0543d950056bf3c0d4d357eba3383f-1630607370684" externalId="random-9678161_calvin_" name="random-9678161" breakout="false"/>
<metadata bbb-anonymize-chat="true" isBreakout="false" meetingId="random-9678161_calvin_" meetingName="random-9678161"/>
<event timestamp="1131799575" module="PRESENTATION" eventname="CreatePresentationPodEvent">
<currentPresenter/>
<timestampUTC>1630607370704</timestampUTC>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:30.704-04</date>
</event>
<event timestamp="1131799900" module="PAD" eventname="AddPadEvent">
<timestampUTC>1630607371029</timestampUTC>
<date>2021-09-02T14:29:31.029-04</date>
<padId>[2]cfe107fd5780210103d9d4017f8c751d5594</padId>
</event>
<event timestamp="1131803050" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>User 7520456</name>
<timestampUTC>1630607374179</timestampUTC>
<role>MODERATOR</role>
<date>2021-09-02T14:29:34.179-04</date>
<externalUserId>w_jw2fcjeovwa6</externalUserId>
<userId>w_jw2fcjeovwa6</userId>
</event>
<event timestamp="1131803051" module="PARTICIPANT" eventname="AssignPresenterEvent">
<name>User 7520456</name>
<timestampUTC>1630607374180</timestampUTC>
<userid>w_jw2fcjeovwa6</userid>
<assignedBy>w_jw2fcjeovwa6</assignedBy>
<date>2021-09-02T14:29:34.180-04</date>
</event>
<event timestamp="1131803066" module="PARTICIPANT" eventname="AssignPresenterEvent">
<name>User 7520456</name>
<timestampUTC>1630607374195</timestampUTC>
<userid>w_jw2fcjeovwa6</userid>
<assignedBy>w_jw2fcjeovwa6</assignedBy>
<date>2021-09-02T14:29:34.195-04</date>
</event>
<event timestamp="1131803067" module="PRESENTATION" eventname="SetPresenterInPodEvent">
<timestampUTC>1630607374196</timestampUTC>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:34.196-04</date>
<nextPresenterId>w_jw2fcjeovwa6</nextPresenterId>
</event>
<event timestamp="1131803071" module="PARTICIPANT" eventname="AssignPresenterEvent">
<name>User 7520456</name>
<timestampUTC>1630607374200</timestampUTC>
<userid>w_jw2fcjeovwa6</userid>
<assignedBy>w_jw2fcjeovwa6</assignedBy>
<date>2021-09-02T14:29:34.200-04</date>
</event>
<event timestamp="1131803072" module="PRESENTATION" eventname="SetPresenterInPodEvent">
<timestampUTC>1630607374201</timestampUTC>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:34.201-04</date>
<nextPresenterId>w_jw2fcjeovwa6</nextPresenterId>
</event>
<event timestamp="1131805474" module="PRESENTATION" eventname="ConversionCompletedEvent">
<originalFilename>default.pdf</originalFilename>
<timestampUTC>1630607376603</timestampUTC>
<presentationName>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentationName>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:36.603-04</date>
</event>
<event timestamp="1131805477" module="PRESENTATION" eventname="SharePresentationEvent">
<timestampUTC>1630607376606</timestampUTC>
<presentationName>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentationName>
<podId>DEFAULT_PRESENTATION_POD</podId>
<share>true</share>
<date>2021-09-02T14:29:36.606-04</date>
</event>
<event timestamp="1131805479" module="PRESENTATION" eventname="SetPresentationDownloadable">
<timestampUTC>1630607376608</timestampUTC>
<presentationName>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentationName>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:36.608-04</date>
<downloadable>false</downloadable>
</event>
<event timestamp="1131824162" module="VOICE" eventname="ParticipantJoinedEvent">
<participant>w_jw2fcjeovwa6</participant>
<timestampUTC>1630607395291</timestampUTC>
<bridge>578447737</bridge>
<callername>User 7520456</callername>
<talking>false</talking>
<callernumber>w_jw2fcjeovwa6_1-bbbID-User 7520456</callernumber>
<date>2021-09-02T14:29:55.291-04</date>
<muted>false</muted>
</event>
<event timestamp="1131824186" module="VOICE" eventname="StartRecordingEvent">
<filename>/var/freeswitch/meetings/2a1de53edf0543d950056bf3c0d4d357eba3383f-1630607370684-1131824162.opus</filename>
<bridge>578447737</bridge>
<timestampUTC>1630607395314</timestampUTC>
<date>2021-09-02T14:29:55.314-04</date>
<recordingTimestamp>1131824184</recordingTimestamp>
</event>
<event timestamp="1131824226" module="VOICE" eventname="ParticipantTalkingEvent">
<participant>w_jw2fcjeovwa6</participant>
<timestampUTC>1630607395355</timestampUTC>
<bridge>578447737</bridge>
<talking>true</talking>
<date>2021-09-02T14:29:55.355-04</date>
</event>
<event timestamp="1131825122" module="VOICE" eventname="ParticipantMutedEvent">
<participant>w_jw2fcjeovwa6</participant>
<timestampUTC>1630607396250</timestampUTC>
<bridge>578447737</bridge>
<date>2021-09-02T14:29:56.250-04</date>
<muted>true</muted>
</event>
<event timestamp="1131825327" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607396456</timestampUTC>
<xOffset>42.81167030334473</xOffset>
<date>2021-09-02T14:29:56.456-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>87.12742558232061</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131825473" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607396602</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:29:56.602-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131834887" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607406015</timestampUTC>
<xOffset>34.58885828653971</xOffset>
<date>2021-09-02T14:30:06.015-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>75.1026690447772</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131835039" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607406168</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:30:06.168-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131839891" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607411020</timestampUTC>
<xOffset>40.557028452555336</xOffset>
<date>2021-09-02T14:30:11.020-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>0.8321106875384296</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131840041" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607411169</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:30:11.169-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131841537" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607412665</timestampUTC>
<xOffset>50.63660303751628</xOffset>
<date>2021-09-02T14:30:12.665-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>9.084394949453849</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131841691" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607412819</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:30:12.819-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131841840" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607412969</timestampUTC>
<xOffset>36.57824834187826</xOffset>
<date>2021-09-02T14:30:12.969-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>33.60546818485967</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131842003" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607413131</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:30:13.131-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131882243" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>(guest) Calvin</name>
<timestampUTC>1630607453372</timestampUTC>
<role>VIEWER</role>
<date>2021-09-02T14:30:53.372-04</date>
<externalUserId>3ff859e3-1c2e-48e9-860d-cf230648cfca</externalUserId>
<userId>w_tvumguamxhhs</userId>
</event>
<event timestamp="1131885821" module="VOICE" eventname="ParticipantJoinedEvent">
<participant>w_tvumguamxhhs</participant>
<timestampUTC>1630607456950</timestampUTC>
<bridge>578447737</bridge>
<callername>(guest) Calvin</callername>
<talking>false</talking>
<callernumber>(guest) Calvin</callernumber>
<date>2021-09-02T14:30:56.950-04</date>
<muted>true</muted>
</event>
<event timestamp="1131895190" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607466319</timestampUTC>
<color>#6a1b9a</color>
<senderId>w_jw2fcjeovwa6</senderId>
<date>2021-09-02T14:31:06.319-04</date>
<sender>User 7520456</sender>
<message>Hello, moderator chat message!</message>
</event>
<event timestamp="1131905701" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607476829</timestampUTC>
<color>#4a148c</color>
<senderId>w_tvumguamxhhs</senderId>
<date>2021-09-02T14:31:16.829-04</date>
<sender>(guest) Calvin</sender>
<message>Hello, guest chat message!</message>
</event>
<event timestamp="1131908051" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607479180</timestampUTC>
<color>#4a148c</color>
<senderId>w_tvumguamxhhs</senderId>
<date>2021-09-02T14:31:19.180-04</date>
<sender>(guest) Calvin</sender>
<message>yay!</message>
</event>
<event timestamp="1131949647" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607520776</timestampUTC>
<xOffset>10.185674826304119</xOffset>
<date>2021-09-02T14:32:00.776-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>96.79438838252314</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131949798" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607520927</timestampUTC>
<xOffset>14.297080039978027</xOffset>
<date>2021-09-02T14:32:00.927-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>90.1925602665654</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131949893" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607521022</timestampUTC>
<xOffset>14.297080039978027</xOffset>
<date>2021-09-02T14:32:01.022-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>89.95677806712963</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131950258" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607521387</timestampUTC>
<xOffset>14.297080039978027</xOffset>
<date>2021-09-02T14:32:01.387-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>90.1925602665654</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131950408" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607521537</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:32:01.537-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131955635" module="CHAT" eventname="ClearPublicChatEvent">
<timestampUTC>1630607526763</timestampUTC>
<date>2021-09-02T14:32:06.763-04</date>
</event>
<event timestamp="1131964123" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607535252</timestampUTC>
<color>#6a1b9a</color>
<senderId>w_jw2fcjeovwa6</senderId>
<date>2021-09-02T14:32:15.252-04</date>
<sender>User 7520456</sender>
<message>Chat was cleared</message>
</event>
<event timestamp="1131994645" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607565774</timestampUTC>
<xOffset>43.34217389424642</xOffset>
<date>2021-09-02T14:32:45.774-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>96.55861183449073</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131994767" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607565896</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:32:45.896-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131997197" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607568326</timestampUTC>
<xOffset>32.20158894856771</xOffset>
<date>2021-09-02T14:32:48.326-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>96.55861183449073</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131997346" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607568475</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:32:48.475-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131999450" module="PARTICIPANT" eventname="RecordStatusEvent">
<timestampUTC>1630607570579</timestampUTC>
<date>2021-09-02T14:32:50.579-04</date>
<status>true</status>
<userId>w_jw2fcjeovwa6</userId>
</event>
<event timestamp="1131999466" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607570595</timestampUTC>
<xOffset>13.501324653625488</xOffset>
<date>2021-09-02T14:32:50.595-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>59.77699562355324</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132001481" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607572610</timestampUTC>
<xOffset>13.103446960449219</xOffset>
<date>2021-09-02T14:32:52.610-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>59.77699562355324</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132001799" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607572927</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:32:52.927-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132011085" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607582214</timestampUTC>
<color>#4a148c</color>
<senderId>w_tvumguamxhhs</senderId>
<date>2021-09-02T14:33:02.214-04</date>
<sender>(guest) Calvin</sender>
<message>whoops, forgot to start recording…</message>
</event>
<event timestamp="1132049253" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607620382</timestampUTC>
<xOffset>36.180369059244796</xOffset>
<date>2021-09-02T14:33:40.382-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>97.9732824254919</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132049403" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607620531</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:33:40.531-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132053637" module="PARTICIPANT" eventname="EndAndKickAllEvent">
<timestampUTC>1630607624766</timestampUTC>
<reason>ENDED_AFTER_USER_LOGGED_OUT</reason>
<date>2021-09-02T14:33:44.766-04</date>
</event>
</recording>

View File

@ -0,0 +1,344 @@
<?xml version="1.0" encoding="UTF-8"?>
<recording meeting_id="afa22bf4e2a55835006a0016776f700dcf8e981e-1630339972856" bbb_version="0.9.0">
<meeting
id="afa22bf4e2a55835006a0016776f700dcf8e981e-1630339972856"
externalId="chat_0_9" name="Chat 0.9 Test"
breakout="false"/>
<metadata
meetingName="Chat 0.9 Test"
meetingId="chat_0_9"
isBreakout="false"/>
<event timestamp="1061316615" module="PRESENTATION" eventname="SharePresentationEvent">
<presentationName>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1503524575790</presentationName>
<share>true</share>
<originalFilename>default.pdf</originalFilename>
</event>
<event timestamp="1061329979" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Marinda Collins</name>
<role>MODERATOR</role>
<userId>9izxq660i7vr_1</userId>
<externalUserId>1000</externalUserId>
</event>
<event timestamp="1061397065" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>vvyha6umxoyt_1</userId>
<externalUserId>1001</externalUserId>
<name>Phelix Fishman</name>
<role>VIEWER</role>
</event>
<event timestamp="1061511972" module="PARTICIPANT" eventname="RecordStatusEvent">
<userId>9izxq660i7vr_1</userId>
<status>true</status>
</event>
<event timestamp="1061536072" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1002</externalUserId>
<userId>hs7iskkr7xrt_1</userId>
<name>Isaías Seelen</name>
</event>
<event timestamp="1061574575" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<userId>7m940cic73r3_1</userId>
<name>Mireia Castell</name>
<externalUserId>1003</externalUserId>
</event>
<event timestamp="1061629575" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1004</externalUserId>
<userId>tgfbj6f828sp_1</userId>
<name>Liborius Hayes</name>
</event>
<event timestamp="1061703579" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1005</externalUserId>
<userId>bepguk6d7dza_1</userId>
<name>Eva Aquino</name>
</event>
<event timestamp="1061749302" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1006</externalUserId>
<userId>66ntqzexswc2_1</userId>
<name>Rodge Palazzo</name>
</event>
<event timestamp="1061825270" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1007</externalUserId>
<role>VIEWER</role>
<name>Elias Stablum</name>
<userId>0q1hkmla9asu_1</userId>
</event>
<event timestamp="1061881172" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>yyynfpyca09g_1</userId>
<externalUserId>1008</externalUserId>
<role>VIEWER</role>
<name>Evelina Keller</name>
</event>
<event timestamp="1061933105" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>dmsj3897dwss_1</userId>
<role>VIEWER</role>
<name>Xhesika De Lange</name>
<externalUserId>1009</externalUserId>
</event>
<event timestamp="1061952342" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Nimue Harlan</name>
<role>VIEWER</role>
<externalUserId>1010</externalUserId>
<userId>42dnty7rovjt_1</userId>
</event>
<event timestamp="1061962737" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Elpidio O'Gorman</name>
<role>VIEWER</role>
<externalUserId>1011</externalUserId>
<userId>7ur69btts657_1</userId>
</event>
<event timestamp="1061998179" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>dmsj3897dwss_1</userId>
</event>
<event timestamp="1062008158" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>66ntqzexswc2_1</userId>
</event>
<event timestamp="1062013094" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1012</externalUserId>
<role>VIEWER</role>
<userId>23uydbo9nauq_1</userId>
<name>Asa Darby</name>
</event>
<event timestamp="1062017073" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1006</externalUserId>
<userId>12ipastd9pw1_1</userId>
<role>VIEWER</role>
<name>Rodge Palazzo</name>
</event>
<event timestamp="1062026555" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Xhesika De Lange</name>
<role>VIEWER</role>
<externalUserId>1009</externalUserId>
<userId>ainnu65fiycz_1</userId>
</event>
<event timestamp="1062058796" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1013</externalUserId>
<role>VIEWER</role>
<name>Arethusa Mann</name>
<userId>j73nq5k8xcaa_1</userId>
</event>
<event timestamp="1062142085" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Ninel Mac Ruaidhrí</name>
<role>VIEWER</role>
<userId>nfuklna24flg_1</userId>
<externalUserId>1014</externalUserId>
</event>
<event timestamp="1062159696" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>ainnu65fiycz_1</userId>
</event>
<event timestamp="1062170527" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1009</externalUserId>
<name>Xhesika De Lange</name>
<role>VIEWER</role>
<userId>2dc8jctma0nj_1</userId>
</event>
<event timestamp="1062260997" module="CHAT" eventname="PublicChatEvent">
<sender>Xhesika De Lange</sender>
<senderId>2dc8jctma0nj_1</senderId>
<message>
<![CDATA[Public chat 1]]>
</message>
<color>0</color>
</event>
<event timestamp="1062483994" module="CHAT" eventname="PublicChatEvent">
<color>0</color>
<senderId>23uydbo9nauq_1</senderId>
<message>
<![CDATA[Public chat 2]]>
</message>
<sender>Asa Darby</sender>
</event>
<event timestamp="1062500000" module="CHAT" eventname="ClearPublicChatEvent">
</event>
<event timestamp="1062914576" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>j73nq5k8xcaa_1</userId>
</event>
<event timestamp="1063007465" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>fzlsahcijxo4_1</userId>
<externalUserId>1013</externalUserId>
<role>VIEWER</role>
<name>Arethusa Mann</name>
</event>
<event timestamp="1063309296" module="CHAT" eventname="PublicChatEvent">
<color>0</color>
<senderId>hs7iskkr7xrt_1</senderId>
<message>
<![CDATA[Public chat 3]]>
</message>
<sender>Isaías Seelen</sender>
</event>
<event timestamp="1064118099" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>fzlsahcijxo4_1</userId>
</event>
<event timestamp="1064118099" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>fzlsahcijxo4_1</userId>
<externalUserId>1013</externalUserId>
<name>Arethusa Mann</name>
<role>VIEWER</role>
</event>
<event timestamp="1064123456" module="PARTICIPANT" eventname="RecordStatusEvent">
<userId>9izxq660i7vr_1</userId>
<status>false</status>
</event>
<event timestamp="1064181418" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>hs7iskkr7xrt_1</userId>
</event>
<event timestamp="1064370077" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1002</externalUserId>
<role>VIEWER</role>
<userId>y096bmb53yu5_1</userId>
<name>Isaías Seelen</name>
</event>
<event timestamp="1064621935" module="CHAT" eventname="PublicChatEvent">
<color>0</color>
<senderId>nfuklna24flg_1</senderId>
<message>
<![CDATA[Public chat 4]]>
</message>
<sender>Ninel Mac Ruaidhrí</sender>
</event>
<event timestamp="1064635147" module="CHAT" eventname="PublicChatEvent">
<senderId>bepguk6d7dza_1</senderId>
<sender>Eva Aquino</sender>
<message>
<![CDATA[Public chat 5]]>
</message>
<color>0</color>
</event>
<event timestamp="1064645678" module="PARTICIPANT" eventname="RecordStatusEvent">
<userId>9izxq660i7vr_1</userId>
<status>true</status>
</event>
<event timestamp="1064656291" module="CHAT" eventname="PublicChatEvent">
<sender>Elias Stablum</sender>
<senderId>0q1hkmla9asu_1</senderId>
<color>0</color>
<message>
<![CDATA[Public chat 6]]>
</message>
</event>
<event timestamp="1064660118" module="CHAT" eventname="PublicChatEvent">
<senderId>fzlsahcijxo4_1</senderId>
<message>
<![CDATA[Public chat 7]]>
</message>
<sender>Arethusa Mann</sender>
<color>0</color>
</event>
<event timestamp="1064669521" module="CHAT" eventname="PublicChatEvent">
<message>
<![CDATA[Public chat 8]]>
</message>
<senderId>nfuklna24flg_1</senderId>
<sender>Ninel Mac Ruaidhrí</sender>
<color>0</color>
</event>
<event timestamp="1064671034" module="CHAT" eventname="PublicChatEvent">
<message>
<![CDATA[Public chat 9]]>
</message>
<color>0</color>
<sender>Mireia Castell</sender>
<senderId>7m940cic73r3_1</senderId>
</event>
<event timestamp="1064679602" module="CHAT" eventname="PublicChatEvent">
<sender>Ninel Mac Ruaidhrí</sender>
<message>
<![CDATA[Public chat 10]]>
</message>
<color>0</color>
<senderId>nfuklna24flg_1</senderId>
</event>
<event timestamp="1066752701" module="CHAT" eventname="PublicChatEvent">
<sender>Xhesika De Lange</sender>
<color>0</color>
<senderId>2dc8jctma0nj_1</senderId>
<message>
<![CDATA[Public chat 11]]>
</message>
</event>
<event timestamp="1066777963" module="CHAT" eventname="PublicChatEvent">
<sender>Arethusa Mann</sender>
<senderId>fzlsahcijxo4_1</senderId>
<color>0</color>
<message>
<![CDATA[Public chat 12]]>
</message>
</event>
<event timestamp="1066909573" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>y096bmb53yu5_2</userId>
<role>VIEWER</role>
<name>Isaías Seelen</name>
<externalUserId>1002</externalUserId>
</event>
<event timestamp="1066966371" module="CHAT" eventname="PublicChatEvent">
<message>
<![CDATA[Public chat 13]]>
</message>
<color>0</color>
<senderId>2dc8jctma0nj_1</senderId>
<sender>Xhesika De Lange</sender>
</event>
<event timestamp="1069500636" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>0q1hkmla9asu_1</userId>
</event>
<event timestamp="1069502806" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1008</externalUserId>
<userId>yyynfpyca09g_1</userId>
<name>Evelina Keller</name>
</event>
<event timestamp="1069502545" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>nfuklna24flg_1</userId>
</event>
<event timestamp="1069502806" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>yyynfpyca09g_1</userId>
</event>
<event timestamp="1069504995" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>2dc8jctma0nj_1</userId>
</event>
<event timestamp="1069505733" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>bepguk6d7dza_1</userId>
</event>
<event timestamp="1069507663" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>fzlsahcijxo4_1</userId>
</event>
<event timestamp="1069508668" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>7ur69btts657_1</userId>
</event>
<event timestamp="1069509022" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>23uydbo9nauq_1</userId>
</event>
<event timestamp="1069512636" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>12ipastd9pw1_1</userId>
</event>
<event timestamp="1069514445" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>7m940cic73r3_1</userId>
</event>
<event timestamp="1069595144" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>42dnty7rovjt_1</userId>
</event>
<event timestamp="1069686925" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>vvyha6umxoyt_1</userId>
</event>
<event timestamp="1069700912" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>y096bmb53yu5_2</userId>
</event>
<event timestamp="1069717589" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>tgfbj6f828sp_1</userId>
</event>
<event timestamp="1069722053" module="PARTICIPANT" eventname="RecordStatusEvent">
<userId>9izxq660i7vr_1</userId>
<status>false</status>
</event>
<event timestamp="1069746114" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>9izxq660i7vr_1</userId>
</event>
<event timestamp="1069802390" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>yyynfpyca09g_1</userId>
</event>
<event timestamp="1069897986" module="PARTICIPANT" eventname="EndAndKickAllEvent">
</event>
</recording>

View File

@ -28,6 +28,16 @@ redis_port: 6379
# and have another script process it.
store_recording_status: false
# Whether to anonymize the sender of chat messages in the processed
# recordings. The settings here are the defaults; they can be overridden
# by passing meta parameters on the meeting create call.
# meta param: meta_bbb-anonymize-chat (true/false)
anonymize_chat: false
# By default only names of viewers are anonymized - if you would also
# like to anonymize moderators, you can set this to true:
# meta param: meta_bbb-anonymize-chat-moderators (true/false)
anonymize_chat_moderators: false
# Sequence of recording steps. Keys are the current step, values
# are the next step(s). Examples:
# current_step: next_step

View File

@ -0,0 +1,273 @@
# frozen_string_literal: true
require 'minitest/autorun'
require 'nokogiri'
require 'recordandplayback'
class TestEvents < Minitest::Test
def setup
@events_legacy = File.open('resources/raw/1b199e88-7df7-4842-a5f1-0e84b781c5c8/events.xml') do |io|
Nokogiri::XML(io)
end
@events_chat09 = File.open('resources/raw/chat_0_9.xml') do |io|
Nokogiri::XML(io)
end
@events_devcall = File.open('resources/raw/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1630430006889/events.xml') do |io|
Nokogiri::XML(io)
end
@events_meta_edt = File.open('resources/raw/2a1de53edf0543d950056bf3c0d4d357eba3383f-1630607370684/events.xml') do |io|
Nokogiri::XML(io)
end
end
def test_anonymous_user_map_legacy
map = BigBlueButton::Events.anonymous_user_map(@events_legacy)
assert_empty(map)
end
def test_anonymous_user_map_legacy_no_viewer_only
map = BigBlueButton::Events.anonymous_user_map(@events_legacy, moderators: true)
assert_equal(1, map.length)
assert_equal('Moderator 1', map['1'])
end
def test_anonymous_user_map_bbb_0_9
map = BigBlueButton::Events.anonymous_user_map(@events_chat09)
assert_equal(21, map.length)
assert_equal('Marinda Collins', map['9izxq660i7vr_1']) # Moderator
assert_equal('Viewer 1', map['vvyha6umxoyt_1'])
assert_equal('Viewer 2', map['hs7iskkr7xrt_1'])
assert_equal('Viewer 3', map['7m940cic73r3_1'])
assert_equal('Viewer 4', map['tgfbj6f828sp_1'])
assert_equal('Viewer 5', map['bepguk6d7dza_1'])
assert_equal('Viewer 6', map['66ntqzexswc2_1'])
assert_equal('Viewer 7', map['0q1hkmla9asu_1'])
assert_equal('Viewer 8', map['yyynfpyca09g_1'])
assert_equal('Viewer 9', map['dmsj3897dwss_1'])
assert_equal('Viewer 10', map['42dnty7rovjt_1'])
assert_equal('Viewer 11', map['7ur69btts657_1'])
assert_equal('Viewer 12', map['23uydbo9nauq_1'])
assert_equal('Viewer 6', map['12ipastd9pw1_1'])
assert_equal('Viewer 9', map['ainnu65fiycz_1'])
assert_equal('Viewer 13', map['j73nq5k8xcaa_1'])
assert_equal('Viewer 14', map['nfuklna24flg_1'])
assert_equal('Viewer 9', map['2dc8jctma0nj_1'])
assert_equal('Viewer 13', map['fzlsahcijxo4_1'])
assert_equal('Viewer 2', map['y096bmb53yu5_1'])
assert_equal('Viewer 2', map['y096bmb53yu5_2'])
assert_equal('Viewer 8', map['yyynfpyca09g_1'])
end
def test_anonymous_user_map_bbb_0_9_no_viewer_only
map = BigBlueButton::Events.anonymous_user_map(@events_chat09, moderators: true)
assert_equal(21, map.length)
assert_equal('Moderator 1', map['9izxq660i7vr_1'])
assert_equal('Viewer 1', map['vvyha6umxoyt_1'])
assert_equal('Viewer 2', map['hs7iskkr7xrt_1'])
assert_equal('Viewer 3', map['7m940cic73r3_1'])
assert_equal('Viewer 4', map['tgfbj6f828sp_1'])
assert_equal('Viewer 5', map['bepguk6d7dza_1'])
assert_equal('Viewer 6', map['66ntqzexswc2_1'])
assert_equal('Viewer 7', map['0q1hkmla9asu_1'])
assert_equal('Viewer 8', map['yyynfpyca09g_1'])
assert_equal('Viewer 9', map['dmsj3897dwss_1'])
assert_equal('Viewer 10', map['42dnty7rovjt_1'])
assert_equal('Viewer 11', map['7ur69btts657_1'])
assert_equal('Viewer 12', map['23uydbo9nauq_1'])
assert_equal('Viewer 6', map['12ipastd9pw1_1'])
assert_equal('Viewer 9', map['ainnu65fiycz_1'])
assert_equal('Viewer 13', map['j73nq5k8xcaa_1'])
assert_equal('Viewer 14', map['nfuklna24flg_1'])
assert_equal('Viewer 9', map['2dc8jctma0nj_1'])
assert_equal('Viewer 13', map['fzlsahcijxo4_1'])
assert_equal('Viewer 2', map['y096bmb53yu5_1'])
assert_equal('Viewer 2', map['y096bmb53yu5_2'])
assert_equal('Viewer 8', map['yyynfpyca09g_1'])
end
def test_get_chat_events_legacy
start_time = BigBlueButton::Events.first_event_timestamp(@events_legacy)
end_time = BigBlueButton::Events.last_event_timestamp(@events_legacy)
bbb_props = { 'anonymize_chat' => true, 'anonymize_chat_moderators' => true }
chats_enum = BigBlueButton::Events.get_chat_events(@events_legacy, start_time, end_time, bbb_props).each
chat = chats_enum.next
assert_equal(34_876, chat[:in])
assert_nil(chat.fetch(:out))
assert_nil(chat.fetch(:sender_id))
# Anonymization doesn't work on really old recordings since there's no connection between
# chat user names and user ids
assert_equal('FRED', chat[:sender])
assert_equal('hello', chat[:message])
assert_nil(chat.fetch(:date))
assert_equal(0, chat[:text_color])
chat = chats_enum.next
assert_equal(42_388, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('FRED', chat[:sender])
assert_equal('how are you?', chat[:message])
chat = chats_enum.next
assert_equal(90_561, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('FRED', chat[:sender])
assert_equal('hi fred', chat[:message])
assert_raises(StopIteration) { chats_enum.next }
end
def test_get_chat_events_0_9
start_time = BigBlueButton::Events.first_event_timestamp(@events_chat09)
end_time = BigBlueButton::Events.last_event_timestamp(@events_chat09)
chats_enum = BigBlueButton::Events.get_chat_events(@events_chat09, start_time, end_time).each
chat = chats_enum.next
assert_equal(749_025, chat[:in])
assert_equal(988_028, chat[:out])
assert_equal('2dc8jctma0nj_1', chat[:sender_id])
assert_equal('Xhesika De Lange', chat[:sender])
assert_equal('Public chat 1', chat[:message])
assert_nil(chat.fetch(:date))
assert_equal(0, chat[:text_color])
chat = chats_enum.next
assert_equal(972_022, chat[:in])
assert_equal(988_028, chat[:out])
assert_equal('23uydbo9nauq_1', chat[:sender_id])
assert_equal('Asa Darby', chat[:sender])
assert_equal('Public chat 2', chat[:message])
chat = chats_enum.next
assert_equal(1_797_324, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('hs7iskkr7xrt_1', chat[:sender_id])
assert_equal('Isaías Seelen', chat[:sender])
assert_equal('Public chat 3', chat[:message])
chat = chats_enum.next
assert_equal(3_144_319 - 522_222, chat[:in])
assert_equal('0q1hkmla9asu_1', chat[:sender_id])
assert_equal('Elias Stablum', chat[:sender])
assert_equal('Public chat 6', chat[:message])
chat = chats_enum.next
assert_equal(3_148_146 - 522_222, chat[:in])
assert_equal('fzlsahcijxo4_1', chat[:sender_id])
assert_equal('Arethusa Mann', chat[:sender])
assert_equal('Public chat 7', chat[:message])
chat = chats_enum.next
assert_equal(3_157_549 - 522_222, chat[:in])
assert_equal('nfuklna24flg_1', chat[:sender_id])
assert_equal('Ninel Mac Ruaidhrí', chat[:sender])
assert_equal('Public chat 8', chat[:message])
chat = chats_enum.next
assert_equal(3_159_062 - 522_222, chat[:in])
assert_equal('7m940cic73r3_1', chat[:sender_id])
assert_equal('Mireia Castell', chat[:sender])
assert_equal('Public chat 9', chat[:message])
chat = chats_enum.next
assert_equal(3_167_630 - 522_222, chat[:in])
assert_equal('nfuklna24flg_1', chat[:sender_id])
assert_equal('Ninel Mac Ruaidhrí', chat[:sender])
assert_equal('Public chat 10', chat[:message])
chat = chats_enum.next
assert_equal(5_240_729 - 522_222, chat[:in])
assert_equal('2dc8jctma0nj_1', chat[:sender_id])
assert_equal('Xhesika De Lange', chat[:sender])
assert_equal('Public chat 11', chat[:message])
chat = chats_enum.next
assert_equal(5_265_991 - 522_222, chat[:in])
assert_equal('fzlsahcijxo4_1', chat[:sender_id])
assert_equal('Arethusa Mann', chat[:sender])
assert_equal('Public chat 12', chat[:message])
chat = chats_enum.next
assert_equal(5_454_399 - 522_222, chat[:in])
assert_equal('2dc8jctma0nj_1', chat[:sender_id])
assert_equal('Xhesika De Lange', chat[:sender])
assert_equal('Public chat 13', chat[:message])
assert_raises(StopIteration) { chats_enum.next }
end
def test_get_chat_events_0_9_anonymized
start_time = BigBlueButton::Events.first_event_timestamp(@events_chat09)
end_time = BigBlueButton::Events.last_event_timestamp(@events_chat09)
bbb_props = { 'anonymize_chat' => true }
chats_enum = BigBlueButton::Events.get_chat_events(@events_chat09, start_time, end_time, bbb_props).each
assert_equal('Viewer 9', chats_enum.next[:sender])
assert_equal('Viewer 12', chats_enum.next[:sender])
assert_equal('Viewer 2', chats_enum.next[:sender])
assert_equal('Viewer 7', chats_enum.next[:sender])
assert_equal('Viewer 13', chats_enum.next[:sender])
assert_equal('Viewer 14', chats_enum.next[:sender])
assert_equal('Viewer 3', chats_enum.next[:sender])
assert_equal('Viewer 14', chats_enum.next[:sender])
assert_equal('Viewer 9', chats_enum.next[:sender])
assert_equal('Viewer 13', chats_enum.next[:sender])
assert_equal('Viewer 9', chats_enum.next[:sender])
assert_raises(StopIteration) { chats_enum.next }
end
def test_get_chat_events_0_9_start_time
end_time = BigBlueButton::Events.last_event_timestamp(@events_chat09)
chats = BigBlueButton::Events.get_chat_events(@events_chat09, 1_063_007_465, end_time)
chat = chats.first
assert_equal(301_831, chat[:in])
assert_equal('hs7iskkr7xrt_1', chat[:sender_id])
assert_equal('Isaías Seelen', chat[:sender])
assert_equal('Public chat 3', chat[:message])
end
def test_get_chat_events_0_9_end_time
start_time = BigBlueButton::Events.first_event_timestamp(@events_chat09)
chats = BigBlueButton::Events.get_chat_events(@events_chat09, start_time, 1_062_490_000)
chat = chats.last
assert_equal(972_022, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('23uydbo9nauq_1', chat[:sender_id])
assert_equal('Asa Darby', chat[:sender])
assert_equal('Public chat 2', chat[:message])
end
def test_get_chat_events_devcall
start_time = BigBlueButton::Events.first_event_timestamp(@events_devcall)
end_time = BigBlueButton::Events.last_event_timestamp(@events_devcall)
chats = BigBlueButton::Events.get_chat_events(@events_devcall, start_time, end_time)
assert_equal(11, chats.length)
chat = chats[0]
assert_equal(17_013, chat[:in])
assert_equal(148_701, chat[:out])
assert_equal('w_kmm96j1as24f', chat[:sender_id])
assert_equal('Mario', chat[:sender])
assert_equal('#7b1fa2', chat[:avatar_color])
assert_nil(chat.fetch(:text_color))
assert_equal(DateTime.rfc3339('2021-08-31T18:06:14.330+00:00'), chat[:date])
# rubocop:disable Layout/LineLength
assert_equal(
"i get logs of these: \n\n ERROR: clientLogger: Camera VIEWER failed. Reconnecting. <a href=\"https://develop.bigbluebutton.org/html5client/8fb14b479570f65105c7ff9a2960b679501f34ff.js?meteor_js_resource=true:348:579447\" rel=\"nofollow\"><u>https://develop.bigbluebutton.org/html5client/8fb14b479570f65105c7ff9a2960b679501f34ff.js?meteor_js_resource=true:348:579447</u></a>",
chat[:message]
)
# rubocop:enable Layout/LineLength
chat = chats[7]
assert_equal(1_241_014, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('w_vk0ebqjxox9d', chat[:sender_id])
assert_equal('Anton G', chat[:sender])
assert_equal('#0277bd', chat[:avatar_color])
assert_nil(chat.fetch(:text_color))
assert_equal(DateTime.rfc3339('2021-08-31T18:26:38.332+00:00'), chat[:date])
assert_equal('Nice!', chat[:message])
end
def test_get_chat_events_meta_edt
start_time = BigBlueButton::Events.first_event_timestamp(@events_meta_edt)
end_time = BigBlueButton::Events.last_event_timestamp(@events_meta_edt)
chats = BigBlueButton::Events.get_chat_events(@events_meta_edt, start_time, end_time)
assert_equal(1, chats.length)
chat = chats[0]
assert_equal(11_635, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('w_tvumguamxhhs', chat[:sender_id])
# This recording has the meta_bbb-anonymize-chat param set
assert_equal('Viewer 1', chat[:sender])
assert_equal(DateTime.rfc3339('2021-09-02T14:33:02.214-04:00'), chat[:date])
assert_equal('whoops, forgot to start recording…', chat[:message])
end
end

View File

@ -1056,33 +1056,21 @@ def processPresentation(package_dir)
File.write("#{package_dir}/#{$cursor_xml_filename}", cursors_doc.to_xml)
end
def processChatMessages
def processChatMessages(events, bbb_props)
BigBlueButton.logger.info("Processing chat events")
# Create slides.xml and chat.
$slides_doc = Nokogiri::XML::Builder.new do |xml|
$xml = xml
$xml.popcorn {
# Process chat events.
current_time = 0
$rec_events.each do |re|
$chat_events.each do |node|
if (node[:timestamp].to_i >= re[:start_timestamp] and node[:timestamp].to_i <= re[:stop_timestamp])
chat_timestamp = node[:timestamp]
chat_sender = node.xpath(".//sender")[0].text()
chat_message = BigBlueButton::Events.linkify(node.xpath(".//message")[0].text())
chat_start = ( translateTimestamp(chat_timestamp) / 1000).to_i
# Creates a list of the clear timestamps that matter for this message
next_clear_timestamps = $clear_chat_timestamps.select{ |e| e >= node[:timestamp] }
# If there is none we skip it, or else we add the out time that will remove a message
if next_clear_timestamps.empty?
$xml.chattimeline(:in => chat_start, :direction => :down, :name => chat_sender, :message => chat_message, :target => :chat )
else
chat_end = ( translateTimestamp( next_clear_timestamps.first ) / 1000).to_i
$xml.chattimeline(:in => chat_start, :out => chat_end, :direction => :down, :name => chat_sender, :message => chat_message, :target => :chat )
end
end
end
current_time += re[:stop_timestamp] - re[:start_timestamp]
Nokogiri::XML::Builder.new do |xml|
xml.popcorn {
BigBlueButton::Events.get_chat_events(events, $meeting_start.to_i, $meeting_end.to_i, bbb_props).each do |chat|
chattimeline = {
in: (chat[:in] / 1000.0).round(1),
direction: 'down',
name: chat[:sender],
message: chat[:message],
target: 'chat'
}
chattimeline[:out] = (chat[:out] / 1000.0).round(1) unless chat[:out].nil?
xml.chattimeline(**chattimeline)
end
}
end
@ -1216,7 +1204,7 @@ def processExternalVideoEvents(events, package_dir)
timestamp = (translateTimestamp(event[:start_timestamp]) / 1000).to_i
# do not add same external_video twice
if (external_videos.find {|ev| ev[:timestamp] == timestamp}.nil?)
if ((event[:start_timestamp] >= re[:start_timestamp] and event[:start_timestamp] <= re[:stop_timestamp]) ||
if ((event[:start_timestamp] >= re[:start_timestamp] and event[:start_timestamp] <= re[:stop_timestamp]) ||
(event[:start_timestamp] < re[:start_timestamp] and event[:stop_timestamp] >= re[:start_timestamp]))
external_videos << {
:timestamp => timestamp,
@ -1414,18 +1402,11 @@ begin
#Create slides.xml
BigBlueButton.logger.info("Generating xml for slides and chat")
# Gathering all the events from the events.xml
$chat_events = @doc.xpath("//event[@eventname='PublicChatEvent']")
# Create a list of timestamps when the moderator cleared the public chat
$clear_chat_timestamps = [ ]
clear_chat_events = @doc.xpath("//event[@eventname='ClearPublicChatEvent']")
clear_chat_events.each { |clear| $clear_chat_timestamps << clear[:timestamp] }
$clear_chat_timestamps.sort!
calculateRecordEventsOffset()
processChatMessages()
# Write slides.xml to file
slides_doc = processChatMessages(@doc, bbb_props)
File.open("#{package_dir}/slides_new.xml", 'w') { |f| f.puts slides_doc.to_xml }
processPresentation(package_dir)
@ -1435,9 +1416,6 @@ begin
processExternalVideoEvents(@doc, package_dir)
# Write slides.xml to file
File.open("#{package_dir}/slides_new.xml", 'w') { |f| f.puts $slides_doc.to_xml }
# Write deskshare.xml to file
File.open("#{package_dir}/#{$deskshare_xml_filename}", 'w') { |f| f.puts $deskshare_xml.to_xml }