"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-7309/resources/prosody-plugins/mod_av_moderation_component.lua" (31 May 2023, 13330 Bytes) of package /linux/misc/jitsi-meet-7309.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Lua source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the last
Fossies "Diffs" side-by-side code changes report for "mod_av_moderation_component.lua":
jitsi-meet_8319_vs_jitsi-meet_8615.
1 local get_room_by_name_and_subdomain = module:require 'util'.get_room_by_name_and_subdomain;
2 local is_healthcheck_room = module:require 'util'.is_healthcheck_room;
3 local internal_room_jid_match_rewrite = module:require "util".internal_room_jid_match_rewrite;
4 local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
5 local array = require "util.array";
6 local json = require 'util.json';
7 local st = require 'util.stanza';
8
9 local muc_component_host = module:get_option_string('muc_component');
10 if muc_component_host == nil then
11 module:log('error', 'No muc_component specified. No muc to operate on!');
12 return;
13 end
14
15 module:log('info', 'Starting av_moderation for %s', muc_component_host);
16
17 -- Returns the index of the given element in the table
18 -- @param table in which to look
19 -- @param elem the element for which to find the index
20 function get_index_in_table(table, elem)
21 for index, value in pairs(table) do
22 if value == elem then
23 return index
24 end
25 end
26 end
27
28 -- Sends a json-message to the destination jid
29 -- @param to_jid the destination jid
30 -- @param json_message the message content to send
31 function send_json_message(to_jid, json_message)
32 local stanza = st.message({ from = module.host; to = to_jid; })
33 :tag('json-message', { xmlns = 'http://jitsi.org/jitmeet' }):text(json_message):up();
34 module:send(stanza);
35 end
36
37 -- Notifies that av moderation has been enabled or disabled
38 -- @param jid the jid to notify, if missing will notify all occupants
39 -- @param enable whether it is enabled or disabled
40 -- @param room the room
41 -- @param actorJid the jid that is performing the enable/disable operation (the muc jid)
42 -- @param mediaType the media type for the moderation
43 function notify_occupants_enable(jid, enable, room, actorJid, mediaType)
44 local body_json = {};
45 body_json.type = 'av_moderation';
46 body_json.enabled = enable;
47 body_json.room = internal_room_jid_match_rewrite(room.jid);
48 body_json.actor = actorJid;
49 body_json.mediaType = mediaType;
50 local body_json_str = json.encode(body_json);
51
52 if jid then
53 send_json_message(jid, body_json_str)
54 else
55 for _, occupant in room:each_occupant() do
56 send_json_message(occupant.jid, body_json_str)
57 end
58 end
59 end
60
61 -- Notifies about a change to the whitelist. Notifies all moderators and admin and the jid itself
62 -- @param jid the jid to notify about the change
63 -- @param moderators whether to notify all moderators in the room
64 -- @param room the room where to send it
65 -- @param mediaType used only when a participant is approved (not sent to moderators)
66 -- @param removed whether the jid is removed or added
67 function notify_whitelist_change(jid, moderators, room, mediaType, removed)
68 local body_json = {};
69 body_json.type = 'av_moderation';
70 body_json.room = internal_room_jid_match_rewrite(room.jid);
71 body_json.whitelists = room.av_moderation;
72 if removed then
73 body_json.removed = true;
74 end
75 body_json.mediaType = mediaType;
76 local moderators_body_json_str = json.encode(body_json);
77 body_json.whitelists = nil;
78 if not removed then
79 body_json.approved = true; -- we want to send to participants only that they were approved to unmute
80 end
81 local participant_body_json_str = json.encode(body_json);
82
83 for _, occupant in room:each_occupant() do
84 if moderators and occupant.role == 'moderator' then
85 send_json_message(occupant.jid, moderators_body_json_str);
86 elseif occupant.jid == jid then
87 -- if the occupant is not moderator we send him that it is approved
88 -- if it is moderator we update him with the list, this is moderator joining or grant moderation was executed
89 if occupant.role == 'moderator' then
90 send_json_message(occupant.jid, moderators_body_json_str);
91 else
92 send_json_message(occupant.jid, participant_body_json_str);
93 end
94 end
95 end
96 end
97
98 -- Notifies jid that is approved. This is a moderator to jid message to ask to unmute,
99 -- @param jid the jid to notify about the change
100 -- @param from the jid that triggered this
101 -- @param room the room where to send it
102 -- @param mediaType the mediaType it was approved for
103 function notify_jid_approved(jid, from, room, mediaType)
104 local body_json = {};
105 body_json.type = 'av_moderation';
106 body_json.room = internal_room_jid_match_rewrite(room.jid);
107 body_json.approved = true; -- we want to send to participants only that they were approved to unmute
108 body_json.mediaType = mediaType;
109 body_json.from = from;
110
111 send_json_message(jid, json.encode(body_json));
112 end
113
114 -- receives messages from clients to the component sending A/V moderation enable/disable commands or adding
115 -- jids to the whitelist
116 function on_message(event)
117 local session = event.origin;
118
119 -- Check the type of the incoming stanza to avoid loops:
120 if event.stanza.attr.type == 'error' then
121 return; -- We do not want to reply to these, so leave.
122 end
123
124 if not session or not session.jitsi_web_query_room then
125 return false;
126 end
127
128 local moderation_command = event.stanza:get_child('av_moderation');
129
130 if moderation_command then
131 -- get room name with tenant and find room
132 local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
133
134 if not room then
135 module:log('warn', 'No room found found for %s/%s',
136 session.jitsi_web_query_prefix, session.jitsi_web_query_room);
137 return false;
138 end
139
140 -- check that the participant requesting is a moderator and is an occupant in the room
141 local from = event.stanza.attr.from;
142 local occupant = room:get_occupant_by_real_jid(from);
143 if not occupant then
144 module:log('warn', 'No occupant %s found for %s', from, room.jid);
145 return false;
146 end
147 if occupant.role ~= 'moderator' then
148 module:log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid);
149 return false;
150 end
151
152 local mediaType = moderation_command.attr.mediaType;
153 if mediaType then
154 if mediaType ~= 'audio' and mediaType ~= 'video' then
155 module:log('warn', 'Wrong mediaType %s for %s', mediaType, room.jid);
156 return false;
157 end
158 else
159 module:log('warn', 'Missing mediaType for %s', room.jid);
160 return false;
161 end
162
163 if moderation_command.attr.enable ~= nil then
164 local enabled;
165 if moderation_command.attr.enable == 'true' then
166 enabled = true;
167 if room.av_moderation and room.av_moderation[mediaType] then
168 module:log('warn', 'Concurrent moderator enable/disable request or something is out of sync');
169 return true;
170 else
171 if not room.av_moderation then
172 room.av_moderation = {};
173 room.av_moderation_actors = {};
174 end
175 room.av_moderation[mediaType] = array{};
176 room.av_moderation_actors[mediaType] = occupant.nick;
177 end
178 else
179 enabled = false;
180 if not room.av_moderation then
181 module:log('warn', 'Concurrent moderator enable/disable request or something is out of sync');
182 return true;
183 else
184 room.av_moderation[mediaType] = nil;
185 room.av_moderation_actors[mediaType] = nil;
186
187 -- clears room.av_moderation if empty
188 local is_empty = true;
189 for key,_ in pairs(room.av_moderation) do
190 if room.av_moderation[key] then
191 is_empty = false;
192 end
193 end
194 if is_empty then
195 room.av_moderation = nil;
196 end
197 end
198 end
199
200 -- send message to all occupants
201 notify_occupants_enable(nil, enabled, room, occupant.nick, mediaType);
202 return true;
203 elseif moderation_command.attr.jidToWhitelist then
204 local occupant_jid = moderation_command.attr.jidToWhitelist;
205 -- check if jid is in the room, if so add it to whitelist
206 -- inform all moderators and admins and the jid
207 local occupant_to_add = room:get_occupant_by_nick(room_jid_match_rewrite(occupant_jid));
208 if not occupant_to_add then
209 module:log('warn', 'No occupant %s found for %s', occupant_jid, room.jid);
210 return false;
211 end
212
213 if room.av_moderation then
214 local whitelist = room.av_moderation[mediaType];
215 if not whitelist then
216 whitelist = array{};
217 room.av_moderation[mediaType] = whitelist;
218 end
219 whitelist:push(occupant_jid);
220
221 notify_whitelist_change(occupant_to_add.jid, true, room, mediaType, false);
222
223 return true;
224 else
225 -- this is a moderator asking the jid to unmute without enabling av moderation
226 -- let's just send the event
227 notify_jid_approved(occupant_to_add.jid, occupant.nick, room, mediaType);
228 end
229 elseif moderation_command.attr.jidToBlacklist then
230 local occupant_jid = moderation_command.attr.jidToBlacklist;
231 -- check if jid is in the room, if so remove it from the whitelist
232 -- inform all moderators and admins
233 local occupant_to_remove = room:get_occupant_by_nick(room_jid_match_rewrite(occupant_jid));
234 if not occupant_to_remove then
235 module:log('warn', 'No occupant %s found for %s', occupant_jid, room.jid);
236 return false;
237 end
238
239 if room.av_moderation then
240 local whitelist = room.av_moderation[mediaType];
241 if whitelist then
242 local index = get_index_in_table(whitelist, occupant_jid)
243 if(index) then
244 whitelist:pop(index);
245 notify_whitelist_change(occupant_to_remove.jid, true, room, mediaType, true);
246 end
247 end
248
249 return true;
250 end
251 end
252 end
253
254 -- return error
255 return false
256 end
257
258 -- handles new occupants to inform them about the state enabled/disabled, new moderators also get and the whitelist
259 function occupant_joined(event)
260 local room, occupant = event.room, event.occupant;
261
262 if is_healthcheck_room(room.jid) then
263 return;
264 end
265
266 if room.av_moderation then
267 for _,mediaType in pairs({'audio', 'video'}) do
268 if room.av_moderation[mediaType] then
269 notify_occupants_enable(
270 occupant.jid, true, room, room.av_moderation_actors[mediaType], mediaType);
271 end
272 end
273
274 -- NOTE for some reason event.occupant.role is not reflecting the actual occupant role (when changed
275 -- from allowners module) but iterating over room occupants returns the correct role
276 for _, room_occupant in room:each_occupant() do
277 -- if moderator send the whitelist
278 if room_occupant.nick == occupant.nick and room_occupant.role == 'moderator' then
279 notify_whitelist_change(room_occupant.jid, false, room);
280 end
281 end
282 end
283 end
284
285 -- when a occupant was granted moderator we need to update him with the whitelist
286 function occupant_affiliation_changed(event)
287 -- the actor can be nil if is coming from allowners or similar module we want to skip it here
288 -- as we will handle it in occupant_joined
289 if event.actor and event.affiliation == 'owner' and event.room.av_moderation then
290 local room = event.room;
291 -- event.jid is the bare jid of participant
292 for _, occupant in room:each_occupant() do
293 if occupant.bare_jid == event.jid then
294 notify_whitelist_change(occupant.jid, false, room);
295 end
296 end
297 end
298 end
299
300 -- we will receive messages from the clients
301 module:hook('message/host', on_message);
302
303 -- executed on every host added internally in prosody, including components
304 function process_host(host)
305 if host == muc_component_host then -- the conference muc component
306 module:log('info','Hook to muc events on %s', host);
307
308 local muc_module = module:context(host);
309 muc_module:hook('muc-occupant-joined', occupant_joined, -2); -- make sure it runs after allowners or similar
310 muc_module:hook('muc-set-affiliation', occupant_affiliation_changed, -1);
311 end
312 end
313
314 if prosody.hosts[muc_component_host] == nil then
315 module:log('info', 'No muc component found, will listen for it: %s', muc_component_host);
316
317 -- when a host or component is added
318 prosody.events.add_handler('host-activated', process_host);
319 else
320 process_host(muc_component_host);
321 end