Pārlūkot izejas kodu

feat(av-moderation) Ask to Unmute and remove from Whitelist (#10043)

* feat(av-moderation) Ask to Unmute and remove from Whitelist

Make Ask to Unmute work without moderation
Add remove from moderation whitelist functionality

* chore(deps) lib-jitsi-meet@latest

* feat(av-moderation) Remove from moderation whitelist functionality (#1729)
* fix(chore) corrected typo in log message
* fix(e2ee) replace nullish coalescing with or
* fix(e2ee) restore initial key when RATCHET_WINDOW_SIZE reached

3b8baa9d3b/...0646bc3403807dbf1370c88f028d9e0a16bcab1a

Co-authored-by: Дамян Минков <damencho@jitsi.org>
master
robertpin 3 gadus atpakaļ
vecāks
revīzija
ace53c880b
Revīzijas autora e-pasta adrese nav piesaistīta nevienam kontam

+ 3
- 3
lang/main.json Parādīt failu

271
         "muteParticipantDialog": "Are you sure you want to mute this participant? You won't be able to unmute them, but they can unmute themselves at any time.",
271
         "muteParticipantDialog": "Are you sure you want to mute this participant? You won't be able to unmute them, but they can unmute themselves at any time.",
272
         "muteParticipantsVideoDialog": "Are you sure you want to turn off this participant's camera? You won't be able to turn the camera back on, but they can turn it back on at any time.",
272
         "muteParticipantsVideoDialog": "Are you sure you want to turn off this participant's camera? You won't be able to turn the camera back on, but they can turn it back on at any time.",
273
         "muteParticipantTitle": "Mute this participant?",
273
         "muteParticipantTitle": "Mute this participant?",
274
-        "muteParticipantsVideoButton": "Stop camera",
274
+        "muteParticipantsVideoButton": "Stop video",
275
         "muteParticipantsVideoTitle": "Disable camera of this participant?",
275
         "muteParticipantsVideoTitle": "Disable camera of this participant?",
276
         "muteParticipantsVideoBody": "You won't be able to turn the camera back on, but they can turn it back on at any time.",
276
         "muteParticipantsVideoBody": "You won't be able to turn the camera back on, but they can turn it back on at any time.",
277
         "noDropboxToken": "No valid Dropbox token",
277
         "noDropboxToken": "No valid Dropbox token",
897
             "mute": "Mute / Unmute",
897
             "mute": "Mute / Unmute",
898
             "muteEveryone": "Mute everyone",
898
             "muteEveryone": "Mute everyone",
899
             "muteEveryoneElse": "Mute everyone else",
899
             "muteEveryoneElse": "Mute everyone else",
900
-            "muteEveryonesVideo": "Disable everyone's camera",
901
-            "muteEveryoneElsesVideo": "Disable everyone else's camera",
900
+            "muteEveryonesVideo": "Disable everyone's video",
901
+            "muteEveryoneElsesVideo": "Disable everyone else's video",
902
             "participants": "Participants",
902
             "participants": "Participants",
903
             "pip": "Toggle Picture-in-Picture mode",
903
             "pip": "Toggle Picture-in-Picture mode",
904
             "privateMessage": "Send private message",
904
             "privateMessage": "Send private message",

+ 2
- 2
package-lock.json Parādīt failu

11117
       }
11117
       }
11118
     },
11118
     },
11119
     "lib-jitsi-meet": {
11119
     "lib-jitsi-meet": {
11120
-      "version": "github:jitsi/lib-jitsi-meet#3b8baa9d3be2839510abaa954357d0b0ab023649",
11121
-      "from": "github:jitsi/lib-jitsi-meet#3b8baa9d3be2839510abaa954357d0b0ab023649",
11120
+      "version": "github:jitsi/lib-jitsi-meet#0646bc3403807dbf1370c88f028d9e0a16bcab1a",
11121
+      "from": "github:jitsi/lib-jitsi-meet#0646bc3403807dbf1370c88f028d9e0a16bcab1a",
11122
       "requires": {
11122
       "requires": {
11123
         "@jitsi/js-utils": "1.0.2",
11123
         "@jitsi/js-utils": "1.0.2",
11124
         "@jitsi/sdp-interop": "github:jitsi/sdp-interop#4669790bb9020cc8f10c1d1f3823c26b08497547",
11124
         "@jitsi/sdp-interop": "github:jitsi/sdp-interop#4669790bb9020cc8f10c1d1f3823c26b08497547",

+ 1
- 1
package.json Parādīt failu

59
     "jquery-i18next": "1.2.1",
59
     "jquery-i18next": "1.2.1",
60
     "js-md5": "0.6.1",
60
     "js-md5": "0.6.1",
61
     "jwt-decode": "2.2.0",
61
     "jwt-decode": "2.2.0",
62
-    "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#3b8baa9d3be2839510abaa954357d0b0ab023649",
62
+    "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0646bc3403807dbf1370c88f028d9e0a16bcab1a",
63
     "libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
63
     "libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
64
     "lodash": "4.17.21",
64
     "lodash": "4.17.21",
65
     "moment": "2.29.1",
65
     "moment": "2.29.1",

+ 21
- 0
react/features/av-moderation/actionTypes.js Parādīt failu

74
  */
74
  */
75
 export const LOCAL_PARTICIPANT_APPROVED = 'LOCAL_PARTICIPANT_APPROVED';
75
 export const LOCAL_PARTICIPANT_APPROVED = 'LOCAL_PARTICIPANT_APPROVED';
76
 
76
 
77
+/**
78
+ * The type of (redux) action which signals that the local participant had been blocked.
79
+ *
80
+ * {
81
+ *     type: LOCAL_PARTICIPANT_REJECTED,
82
+ *     mediaType: MediaType
83
+ * }
84
+ */
85
+export const LOCAL_PARTICIPANT_REJECTED = 'LOCAL_PARTICIPANT_REJECTED';
86
+
77
 /**
87
 /**
78
  * The type of (redux) action which signals to show notification to the local participant.
88
  * The type of (redux) action which signals to show notification to the local participant.
79
  *
89
  *
94
  */
104
  */
95
 export const PARTICIPANT_APPROVED = 'PARTICIPANT_APPROVED';
105
 export const PARTICIPANT_APPROVED = 'PARTICIPANT_APPROVED';
96
 
106
 
107
+/**
108
+ * The type of (redux) action which signals that a participant was blocked for a media type.
109
+ *
110
+ * {
111
+ *     type: PARTICIPANT_REJECTED,
112
+ *     mediaType: MediaType
113
+ *     participantId: String
114
+ * }
115
+ */
116
+export const PARTICIPANT_REJECTED = 'PARTICIPANT_REJECTED';
117
+
97
 
118
 
98
 /**
119
 /**
99
  * The type of (redux) action which signals that a participant asked to have its audio umuted.
120
  * The type of (redux) action which signals that a participant asked to have its audio umuted.

+ 81
- 4
react/features/av-moderation/actions.js Parādīt failu

2
 
2
 
3
 import { getConferenceState } from '../base/conference';
3
 import { getConferenceState } from '../base/conference';
4
 import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
4
 import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
5
-import { getParticipantById } from '../base/participants';
5
+import { getParticipantById, isParticipantModerator } from '../base/participants';
6
 import { isForceMuted } from '../participants-pane/functions';
6
 import { isForceMuted } from '../participants-pane/functions';
7
 
7
 
8
 import {
8
 import {
16
     REQUEST_DISABLE_AUDIO_MODERATION,
16
     REQUEST_DISABLE_AUDIO_MODERATION,
17
     REQUEST_ENABLE_AUDIO_MODERATION,
17
     REQUEST_ENABLE_AUDIO_MODERATION,
18
     REQUEST_DISABLE_VIDEO_MODERATION,
18
     REQUEST_DISABLE_VIDEO_MODERATION,
19
-    REQUEST_ENABLE_VIDEO_MODERATION
19
+    REQUEST_ENABLE_VIDEO_MODERATION,
20
+    LOCAL_PARTICIPANT_REJECTED,
21
+    PARTICIPANT_REJECTED
20
 } from './actionTypes';
22
 } from './actionTypes';
21
 import { isEnabledFromState } from './functions';
23
 import { isEnabledFromState } from './functions';
22
 
24
 
33
 
35
 
34
     const isAudioForceMuted = isForceMuted(participant, MEDIA_TYPE.AUDIO, state);
36
     const isAudioForceMuted = isForceMuted(participant, MEDIA_TYPE.AUDIO, state);
35
     const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
37
     const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
38
+    const isAudioModerationOn = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
39
+    const isVideoModerationOn = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
36
 
40
 
37
-    if (isEnabledFromState(MEDIA_TYPE.AUDIO, state) && isAudioForceMuted) {
41
+    if (!(isAudioModerationOn || isVideoModerationOn) || (isAudioModerationOn && isAudioForceMuted)) {
38
         conference.avModerationApprove(MEDIA_TYPE.AUDIO, id);
42
         conference.avModerationApprove(MEDIA_TYPE.AUDIO, id);
39
     }
43
     }
40
-    if (isEnabledFromState(MEDIA_TYPE.VIDEO, state) && isVideoForceMuted) {
44
+    if (isVideoModerationOn && isVideoForceMuted) {
41
         conference.avModerationApprove(MEDIA_TYPE.VIDEO, id);
45
         conference.avModerationApprove(MEDIA_TYPE.VIDEO, id);
42
     }
46
     }
43
 };
47
 };
44
 
48
 
49
+/**
50
+ * Action used by moderator to reject audio for a participant.
51
+ *
52
+ * @param {staring} id - The id of the participant to be rejected.
53
+ * @returns {void}
54
+ */
55
+export const rejectParticipantAudio = (id: string) => (dispatch: Function, getState: Function) => {
56
+    const state = getState();
57
+    const { conference } = getConferenceState(state);
58
+    const audioModeration = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
59
+
60
+    const participant = getParticipantById(state, id);
61
+    const isAudioForceMuted = isForceMuted(participant, MEDIA_TYPE.AUDIO, state);
62
+    const isModerator = isParticipantModerator(participant);
63
+
64
+    if (audioModeration && !isAudioForceMuted && !isModerator) {
65
+        conference.avModerationReject(MEDIA_TYPE.AUDIO, id);
66
+    }
67
+};
68
+
69
+/**
70
+ * Action used by moderator to reject video for a participant.
71
+ *
72
+ * @param {staring} id - The id of the participant to be rejected.
73
+ * @returns {void}
74
+ */
75
+export const rejectParticipantVideo = (id: string) => (dispatch: Function, getState: Function) => {
76
+    const state = getState();
77
+    const { conference } = getConferenceState(state);
78
+    const videoModeration = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
79
+
80
+    const participant = getParticipantById(state, id);
81
+    const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
82
+    const isModerator = isParticipantModerator(participant);
83
+
84
+    if (videoModeration && !isVideoForceMuted && !isModerator) {
85
+        conference.avModerationReject(MEDIA_TYPE.VIDEO, id);
86
+    }
87
+};
88
+
45
 /**
89
 /**
46
  * Audio or video moderation is disabled.
90
  * Audio or video moderation is disabled.
47
  *
91
  *
169
     };
213
     };
170
 };
214
 };
171
 
215
 
216
+/**
217
+ * Local participant was blocked to be able to unmute audio and video.
218
+ *
219
+ * @param {MediaType} mediaType - The media type to disable.
220
+ * @returns {{
221
+ *     type: LOCAL_PARTICIPANT_REJECTED
222
+ * }}
223
+ */
224
+export const localParticipantRejected = (mediaType: MediaType) => {
225
+    return {
226
+        type: LOCAL_PARTICIPANT_REJECTED,
227
+        mediaType
228
+    };
229
+};
230
+
172
 /**
231
 /**
173
  * Shows notification when A/V moderation is enabled and local participant is still not approved.
232
  * Shows notification when A/V moderation is enabled and local participant is still not approved.
174
  *
233
  *
211
         mediaType
270
         mediaType
212
     };
271
     };
213
 }
272
 }
273
+
274
+/**
275
+ * A participant was blocked to unmute for a mediaType.
276
+ *
277
+ * @param {string} id - The id of the approved participant.
278
+ * @param {MediaType} mediaType - The media type which was approved.
279
+ * @returns {{
280
+ *     type: PARTICIPANT_REJECTED,
281
+ * }}
282
+ */
283
+export function participantRejected(id: string, mediaType: MediaType) {
284
+    return {
285
+        type: PARTICIPANT_REJECTED,
286
+        id,
287
+        mediaType
288
+    };
289
+}
290
+

+ 16
- 1
react/features/av-moderation/middleware.js Parādīt failu

35
     enableModeration,
35
     enableModeration,
36
     localParticipantApproved,
36
     localParticipantApproved,
37
     participantApproved,
37
     participantApproved,
38
-    participantPendingAudio
38
+    participantPendingAudio,
39
+    localParticipantRejected,
40
+    participantRejected
39
 } from './actions';
41
 } from './actions';
40
 import {
42
 import {
41
     ASKED_TO_UNMUTE_SOUND_ID, AUDIO_MODERATION_NOTIFICATION_ID,
43
     ASKED_TO_UNMUTE_SOUND_ID, AUDIO_MODERATION_NOTIFICATION_ID,
176
                 }
178
                 }
177
             });
179
             });
178
 
180
 
181
+            conference.on(JitsiConferenceEvents.AV_MODERATION_REJECTED, ({ mediaType }) => {
182
+                dispatch(localParticipantRejected(mediaType));
183
+            });
184
+
179
             conference.on(JitsiConferenceEvents.AV_MODERATION_CHANGED, ({ enabled, mediaType, actor }) => {
185
             conference.on(JitsiConferenceEvents.AV_MODERATION_CHANGED, ({ enabled, mediaType, actor }) => {
180
                 enabled ? dispatch(enableModeration(mediaType, actor)) : dispatch(disableModeration(mediaType, actor));
186
                 enabled ? dispatch(enableModeration(mediaType, actor)) : dispatch(disableModeration(mediaType, actor));
181
             });
187
             });
194
                         dispatch(dismissPendingParticipant(id, mediaType));
200
                         dispatch(dismissPendingParticipant(id, mediaType));
195
                     });
201
                     });
196
                 });
202
                 });
203
+
204
+            // this is received by moderators
205
+            conference.on(
206
+                JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_REJECTED,
207
+                ({ participant, mediaType }) => {
208
+                    const { _id: id } = participant;
209
+
210
+                    dispatch(participantRejected(id, mediaType));
211
+                });
197
         }
212
         }
198
     });
213
     });

+ 39
- 1
react/features/av-moderation/reducer.js Parādīt failu

13
     DISMISS_PENDING_PARTICIPANT,
13
     DISMISS_PENDING_PARTICIPANT,
14
     ENABLE_MODERATION,
14
     ENABLE_MODERATION,
15
     LOCAL_PARTICIPANT_APPROVED,
15
     LOCAL_PARTICIPANT_APPROVED,
16
+    LOCAL_PARTICIPANT_REJECTED,
16
     PARTICIPANT_APPROVED,
17
     PARTICIPANT_APPROVED,
17
-    PARTICIPANT_PENDING_AUDIO
18
+    PARTICIPANT_PENDING_AUDIO,
19
+    PARTICIPANT_REJECTED
18
 } from './actionTypes';
20
 } from './actionTypes';
19
 import { MEDIA_TYPE_TO_PENDING_STORE_KEY } from './constants';
21
 import { MEDIA_TYPE_TO_PENDING_STORE_KEY } from './constants';
20
 
22
 
105
         };
107
         };
106
     }
108
     }
107
 
109
 
110
+    case LOCAL_PARTICIPANT_REJECTED: {
111
+        const newState = action.mediaType === MEDIA_TYPE.AUDIO
112
+            ? { audioUnmuteApproved: false } : { videoUnmuteApproved: false };
113
+
114
+        return {
115
+            ...state,
116
+            ...newState
117
+        };
118
+    }
119
+
108
     case PARTICIPANT_PENDING_AUDIO: {
120
     case PARTICIPANT_PENDING_AUDIO: {
109
         const { participant } = action;
121
         const { participant } = action;
110
 
122
 
228
         return state;
240
         return state;
229
     }
241
     }
230
 
242
 
243
+    case PARTICIPANT_REJECTED: {
244
+        const { mediaType, id } = action;
245
+
246
+        if (mediaType === MEDIA_TYPE.AUDIO) {
247
+            return {
248
+                ...state,
249
+                audioWhitelist: {
250
+                    ...state.audioWhitelist,
251
+                    [id]: false
252
+                }
253
+            };
254
+        }
255
+
256
+        if (mediaType === MEDIA_TYPE.VIDEO) {
257
+            return {
258
+                ...state,
259
+                videoWhitelist: {
260
+                    ...state.videoWhitelist,
261
+                    [id]: false
262
+                }
263
+            };
264
+        }
265
+
266
+        return state;
267
+    }
268
+
231
     }
269
     }
232
 
270
 
233
     return state;
271
     return state;

+ 1
- 4
react/features/participants-pane/components/ParticipantQuickAction.js Parādīt failu

63
             </QuickActionButton>
63
             </QuickActionButton>
64
         );
64
         );
65
     }
65
     }
66
-    case QUICK_ACTION_BUTTON.ASK_TO_UNMUTE: {
66
+    default: {
67
         return (
67
         return (
68
             <AskToUnmuteButton
68
             <AskToUnmuteButton
69
                 askUnmuteText = { askUnmuteText }
69
                 askUnmuteText = { askUnmuteText }
70
                 participantID = { participantID } />
70
                 participantID = { participantID } />
71
         );
71
         );
72
     }
72
     }
73
-    default: {
74
-        return null;
75
-    }
76
     }
73
     }
77
 }
74
 }

+ 2
- 0
react/features/participants-pane/components/web/MeetingParticipants.js Parādīt failu

4
 import { useTranslation } from 'react-i18next';
4
 import { useTranslation } from 'react-i18next';
5
 import { useDispatch } from 'react-redux';
5
 import { useDispatch } from 'react-redux';
6
 
6
 
7
+import { rejectParticipantAudio } from '../../../av-moderation/actions';
7
 import { isToolbarButtonEnabled } from '../../../base/config/functions.web';
8
 import { isToolbarButtonEnabled } from '../../../base/config/functions.web';
8
 import { MEDIA_TYPE } from '../../../base/media';
9
 import { MEDIA_TYPE } from '../../../base/media';
9
 import {
10
 import {
104
 
105
 
105
     const muteAudio = useCallback(id => () => {
106
     const muteAudio = useCallback(id => () => {
106
         dispatch(muteRemote(id, MEDIA_TYPE.AUDIO));
107
         dispatch(muteRemote(id, MEDIA_TYPE.AUDIO));
108
+        dispatch(rejectParticipantAudio(id));
107
     }, [ dispatch ]);
109
     }, [ dispatch ]);
108
     const [ drawerParticipant, closeDrawer, openDrawerForParticipant ] = useParticipantDrawer();
110
     const [ drawerParticipant, closeDrawer, openDrawerForParticipant ] = useParticipantDrawer();
109
 
111
 

+ 6
- 1
react/features/video-menu/actions.any.js Parādīt failu

10
     sendAnalytics,
10
     sendAnalytics,
11
     VIDEO_MUTE
11
     VIDEO_MUTE
12
 } from '../analytics';
12
 } from '../analytics';
13
-import { showModeratedNotification } from '../av-moderation/actions';
13
+import { rejectParticipantAudio, rejectParticipantVideo, showModeratedNotification } from '../av-moderation/actions';
14
 import { shouldShowModeratedNotification } from '../av-moderation/functions';
14
 import { shouldShowModeratedNotification } from '../av-moderation/functions';
15
 import {
15
 import {
16
     MEDIA_TYPE,
16
     MEDIA_TYPE,
112
             }
112
             }
113
 
113
 
114
             dispatch(muteRemote(id, mediaType));
114
             dispatch(muteRemote(id, mediaType));
115
+            if (mediaType === MEDIA_TYPE.AUDIO) {
116
+                dispatch(rejectParticipantAudio(id));
117
+            } else {
118
+                dispatch(rejectParticipantVideo(id));
119
+            }
115
         });
120
         });
116
     };
121
     };
117
 }
122
 }

+ 2
- 0
react/features/video-menu/components/AbstractMuteRemoteParticipantsVideoDialog.js Parādīt failu

2
 
2
 
3
 import { Component } from 'react';
3
 import { Component } from 'react';
4
 
4
 
5
+import { rejectParticipantVideo } from '../../av-moderation/actions';
5
 import { MEDIA_TYPE } from '../../base/media';
6
 import { MEDIA_TYPE } from '../../base/media';
6
 import { muteRemote } from '../actions';
7
 import { muteRemote } from '../actions';
7
 
8
 
59
         const { dispatch, participantID } = this.props;
60
         const { dispatch, participantID } = this.props;
60
 
61
 
61
         dispatch(muteRemote(participantID, MEDIA_TYPE.VIDEO));
62
         dispatch(muteRemote(participantID, MEDIA_TYPE.VIDEO));
63
+        dispatch(rejectParticipantVideo(participantID));
62
 
64
 
63
         return true;
65
         return true;
64
     }
66
     }

+ 68
- 10
resources/prosody-plugins/mod_av_moderation_component.lua Parādīt failu

13
 
13
 
14
 module:log('info', 'Starting av_moderation for %s', muc_component_host);
14
 module:log('info', 'Starting av_moderation for %s', muc_component_host);
15
 
15
 
16
+-- Returns the index of the given element in the table
17
+-- @param table in which to look
18
+-- @param elem the element for which to find the index
19
+function get_index_in_table(table, elem)
20
+    for index, value in pairs(table) do
21
+        if value == elem then
22
+            return index
23
+        end
24
+    end
25
+end
26
+
16
 -- Sends a json-message to the destination jid
27
 -- Sends a json-message to the destination jid
17
 -- @param to_jid the destination jid
28
 -- @param to_jid the destination jid
18
 -- @param json_message the message content to send
29
 -- @param json_message the message content to send
46
     end
57
     end
47
 end
58
 end
48
 
59
 
60
+-- Notifies about a change to the whitelist. Notifies all moderators and admin and the jid itself
49
 -- @param jid the jid to notify about the change
61
 -- @param jid the jid to notify about the change
50
 -- @param moderators whether to notify all moderators in the room
62
 -- @param moderators whether to notify all moderators in the room
51
 -- @param room the room where to send it
63
 -- @param room the room where to send it
52
 -- @param mediaType used only when a participant is approved (not sent to moderators)
64
 -- @param mediaType used only when a participant is approved (not sent to moderators)
53
-function notify_whitelist_change(jid, moderators, room, mediaType)
65
+-- @param removed whether the jid is removed or added
66
+function notify_whitelist_change(jid, moderators, room, mediaType, removed)
54
     local body_json = {};
67
     local body_json = {};
55
     body_json.type = 'av_moderation';
68
     body_json.type = 'av_moderation';
56
     body_json.room = internal_room_jid_match_rewrite(room.jid);
69
     body_json.room = internal_room_jid_match_rewrite(room.jid);
57
     body_json.whitelists = room.av_moderation;
70
     body_json.whitelists = room.av_moderation;
71
+    body_json.removed = removed;
72
+    body_json.mediaType = mediaType;
58
     local moderators_body_json_str = json.encode(body_json);
73
     local moderators_body_json_str = json.encode(body_json);
59
     body_json.whitelists = nil;
74
     body_json.whitelists = nil;
60
     body_json.approved = true; -- we want to send to participants only that they were approved to unmute
75
     body_json.approved = true; -- we want to send to participants only that they were approved to unmute
61
-    body_json.mediaType = mediaType;
62
     local participant_body_json_str = json.encode(body_json);
76
     local participant_body_json_str = json.encode(body_json);
63
 
77
 
64
     for _, occupant in room:each_occupant() do
78
     for _, occupant in room:each_occupant() do
77
     end
90
     end
78
 end
91
 end
79
 
92
 
93
+-- Notifies jid that is approved. This is a moderator to jid message to ask to unmute,
94
+-- @param jid the jid to notify about the change
95
+-- @param from the jid that triggered this
96
+-- @param room the room where to send it
97
+-- @param mediaType the mediaType it was approved for
98
+function notify_jid_approved(jid, from, room, mediaType)
99
+    local body_json = {};
100
+    body_json.type = 'av_moderation';
101
+    body_json.room = internal_room_jid_match_rewrite(room.jid);
102
+    body_json.approved = true; -- we want to send to participants only that they were approved to unmute
103
+    body_json.mediaType = mediaType;
104
+    body_json.from = from;
105
+
106
+    send_json_message(jid, json.encode(body_json));
107
+end
108
+
80
 -- receives messages from clients to the component sending A/V moderation enable/disable commands or adding
109
 -- receives messages from clients to the component sending A/V moderation enable/disable commands or adding
81
 -- jids to the whitelist
110
 -- jids to the whitelist
82
 function on_message(event)
111
 function on_message(event)
166
             -- send message to all occupants
195
             -- send message to all occupants
167
             notify_occupants_enable(nil, enabled, room, occupant.nick, mediaType);
196
             notify_occupants_enable(nil, enabled, room, occupant.nick, mediaType);
168
             return true;
197
             return true;
169
-        elseif moderation_command.attr.jidToWhitelist and room.av_moderation then
198
+        elseif moderation_command.attr.jidToWhitelist then
170
             local occupant_jid = moderation_command.attr.jidToWhitelist;
199
             local occupant_jid = moderation_command.attr.jidToWhitelist;
171
             -- check if jid is in the room, if so add it to whitelist
200
             -- check if jid is in the room, if so add it to whitelist
172
             -- inform all moderators and admins and the jid
201
             -- inform all moderators and admins and the jid
176
                 return false;
205
                 return false;
177
             end
206
             end
178
 
207
 
179
-            local whitelist = room.av_moderation[mediaType];
180
-            if not whitelist then
181
-                whitelist = {};
182
-                room.av_moderation[mediaType] = whitelist;
208
+            if room.av_moderation then
209
+                local whitelist = room.av_moderation[mediaType];
210
+                if not whitelist then
211
+                    whitelist = {};
212
+                    room.av_moderation[mediaType] = whitelist;
213
+                end
214
+                table.insert(whitelist, occupant_jid);
215
+
216
+                notify_whitelist_change(occupant_to_add.jid, true, room, mediaType, false);
217
+
218
+                return true;
219
+            else
220
+                -- this is a moderator asking the jid to unmute without enabling av moderation
221
+                -- let's just send the event
222
+                notify_jid_approved(occupant_to_add.jid, occupant.nick, room, mediaType);
223
+            end
224
+        elseif moderation_command.attr.jidToBlacklist then
225
+            local occupant_jid = moderation_command.attr.jidToBlacklist;
226
+            -- check if jid is in the room, if so remove it from the whitelist
227
+            -- inform all moderators and admins
228
+            local occupant_to_remove = room:get_occupant_by_nick(room_jid_match_rewrite(occupant_jid));
229
+            if not occupant_to_remove then
230
+                module:log('warn', 'No occupant %s found for %s', occupant_jid, room.jid);
231
+                return false;
183
             end
232
             end
184
-            table.insert(whitelist, occupant_jid);
185
 
233
 
186
-            notify_whitelist_change(occupant_to_add.jid, true, room, mediaType);
234
+            if room.av_moderation then
235
+                local whitelist = room.av_moderation[mediaType];
236
+                if whitelist then
237
+                    local index = get_index_in_table(whitelist, occupant_jid)
238
+                    if(index) then
239
+                        table.remove(whitelist, index);
240
+                        notify_whitelist_change(occupant_to_remove.jid, true, room, mediaType, true);
241
+                    end
242
+                end
187
 
243
 
188
-            return true;
244
+                return true;
245
+            end
189
         end
246
         end
190
     end
247
     end
191
 
248
 

Notiek ielāde…
Atcelt
Saglabāt