瀏覽代碼

feat: Visitors chat (#13112)

* feat(visitors): Visitors chat.

Only live chat is supported, no room history is loaded on visitor nodes.

* feat(visitors): Append display name to promotion requests.

* feat: Indicate visitor's chat messages.

* fix: Fixes preconfigure vnodes script.

* fix: Fixes few cases around routing messages.

Disables private messaging on visitor node and add anonymous name to those visitors without a pre-set name.

* fix: Fixes grouping visitor's messages and some error translations.

* squash: Fixes main.json.
factor2
Дамян Минков 2 年之前
父節點
當前提交
397b94da79
沒有連結到貢獻者的電子郵件帳戶。

+ 1
- 1
lang/main-bg.json 查看文件

58
         "today": "Днес"
58
         "today": "Днес"
59
     },
59
     },
60
     "chat": {
60
     "chat": {
61
-        "error": "Грешка: вашето съобщение не бе изпратено, пради: {{error}}",
61
+        "error": "Грешка: вашето съобщение не бе изпратено, поради: {{error}}",
62
         "fieldPlaceHolder": "Въведете съобщението",
62
         "fieldPlaceHolder": "Въведете съобщението",
63
         "messageTo": "Лично съобщение до {{recipient}}",
63
         "messageTo": "Лично съобщение до {{recipient}}",
64
         "messagebox": "Въведете съобщение",
64
         "messagebox": "Въведете съобщение",

+ 1
- 7
lang/main-enGB.json 查看文件

46
         "today": "Today"
46
         "today": "Today"
47
     },
47
     },
48
     "chat": {
48
     "chat": {
49
-        "error": "Error: your message \"{{originalText}}\" was not sent. Reason: {{error}}",
50
-        "fieldPlaceHolder": "",
51
-        "messageTo": "",
52
         "messagebox": "Type a message",
49
         "messagebox": "Type a message",
53
         "nickname": {
50
         "nickname": {
54
             "popover": "Choose a nickname",
51
             "popover": "Choose a nickname",
55
             "title": "Enter a nickname to use chat",
52
             "title": "Enter a nickname to use chat",
56
             "titleWithPolls": "Enter a nickname to use chat"
53
             "titleWithPolls": "Enter a nickname to use chat"
57
         },
54
         },
58
-        "noMessagesMessage": "",
59
-        "privateNotice": "",
60
         "sendButton": "Send",
55
         "sendButton": "Send",
61
         "title": "Chat",
56
         "title": "Chat",
62
-        "titleWithPolls": "Chat",
63
-        "you": ""
57
+        "titleWithPolls": "Chat"
64
     },
58
     },
65
     "chromeExtensionBanner": {
59
     "chromeExtensionBanner": {
66
         "buttonText": "",
60
         "buttonText": "",

+ 1
- 0
lang/main.json 查看文件

1378
         "webAssemblyWarningDescription": "WebAssembly disabled or not supported by this browser"
1378
         "webAssemblyWarningDescription": "WebAssembly disabled or not supported by this browser"
1379
     },
1379
     },
1380
     "visitors": {
1380
     "visitors": {
1381
+        "chatIndicator": "(visitor)",
1381
         "labelTooltip": "Number of visitors: {{count}}",
1382
         "labelTooltip": "Number of visitors: {{count}}",
1382
         "notification": {
1383
         "notification": {
1383
             "description": "To participate raise your hand",
1384
             "description": "To participate raise your hand",

+ 1
- 1
react/features/base/config/constants.ts 查看文件

64
 /**
64
 /**
65
  * The toolbar buttons to show when in visitors mode.
65
  * The toolbar buttons to show when in visitors mode.
66
  */
66
  */
67
-export const VISITORS_MODE_BUTTONS = [ 'hangup', 'raisehand', 'tileview' ];
67
+export const VISITORS_MODE_BUTTONS = [ 'chat', 'hangup', 'raisehand', 'tileview' ];
68
 
68
 
69
 /**
69
 /**
70
  * The set of feature flags.
70
  * The set of feature flags.

+ 27
- 14
react/features/chat/middleware.ts 查看文件

6
 import { getCurrentConference } from '../base/conference/functions';
6
 import { getCurrentConference } from '../base/conference/functions';
7
 import { IJitsiConference } from '../base/conference/reducer';
7
 import { IJitsiConference } from '../base/conference/reducer';
8
 import { openDialog } from '../base/dialog/actions';
8
 import { openDialog } from '../base/dialog/actions';
9
+import i18next from '../base/i18n/i18next';
9
 import {
10
 import {
10
     JitsiConferenceErrors,
11
     JitsiConferenceErrors,
11
     JitsiConferenceEvents
12
     JitsiConferenceEvents
229
 
230
 
230
     conference.on(
231
     conference.on(
231
         JitsiConferenceEvents.MESSAGE_RECEIVED,
232
         JitsiConferenceEvents.MESSAGE_RECEIVED,
232
-        (id: string, message: string, timestamp: number) => {
233
-            _onConferenceMessageReceived(store, { id,
233
+        // eslint-disable-next-line max-params
234
+        (id: string, message: string, timestamp: number, displayName: string, isGuest?: boolean) => {
235
+            _onConferenceMessageReceived(store, {
236
+                id: id || displayName, // in case of messages coming from visitors we can have unknown id
234
                 message,
237
                 message,
235
                 timestamp,
238
                 timestamp,
239
+                displayName,
240
+                isGuest,
236
                 privateMessage: false });
241
                 privateMessage: false });
237
         }
242
         }
238
     );
243
     );
291
  * @param {Object} message - The message object.
296
  * @param {Object} message - The message object.
292
  * @returns {void}
297
  * @returns {void}
293
  */
298
  */
294
-function _onConferenceMessageReceived(store: IStore, { id, message, timestamp, privateMessage }: {
295
-    id: string; message: string; privateMessage: boolean; timestamp: number; }) {
299
+function _onConferenceMessageReceived(store: IStore, { displayName, id, isGuest, message, timestamp, privateMessage }: {
300
+    displayName?: string; id: string; isGuest?: boolean;
301
+    message: string; privateMessage: boolean; timestamp: number; }) {
296
     const isGif = isGifMessage(message);
302
     const isGif = isGifMessage(message);
297
 
303
 
298
     if (isGif) {
304
     if (isGif) {
302
         }
308
         }
303
     }
309
     }
304
     _handleReceivedMessage(store, {
310
     _handleReceivedMessage(store, {
311
+        displayName,
305
         id,
312
         id,
313
+        isGuest,
306
         message,
314
         message,
307
         privateMessage,
315
         privateMessage,
308
         lobbyChat: false,
316
         lobbyChat: false,
389
  *
397
  *
390
  * @param {Store} store - The Redux store.
398
  * @param {Store} store - The Redux store.
391
  * @param {Object} message - The message object.
399
  * @param {Object} message - The message object.
392
- * @param {boolean} shouldPlaySound - Whether or not to play the incoming message sound.
393
- * @param {boolean} isReaction - Whether or not the message is a reaction message.
400
+ * @param {boolean} shouldPlaySound - Whether to play the incoming message sound.
401
+ * @param {boolean} isReaction - Whether the message is a reaction message.
394
  * @returns {void}
402
  * @returns {void}
395
  */
403
  */
396
 function _handleReceivedMessage({ dispatch, getState }: IStore,
404
 function _handleReceivedMessage({ dispatch, getState }: IStore,
397
-        { id, message, privateMessage, timestamp, lobbyChat }: {
398
-        id: string; lobbyChat: boolean; message: string; privateMessage: boolean; timestamp: number; },
405
+        { displayName, id, isGuest, message, privateMessage, timestamp, lobbyChat }: {
406
+        displayName?: string; id: string; isGuest?: boolean; lobbyChat: boolean;
407
+        message: string; privateMessage: boolean; timestamp: number; },
399
         shouldPlaySound = true,
408
         shouldPlaySound = true,
400
         isReaction = false
409
         isReaction = false
401
 ) {
410
 ) {
408
         dispatch(playSound(INCOMING_MSG_SOUND_ID));
417
         dispatch(playSound(INCOMING_MSG_SOUND_ID));
409
     }
418
     }
410
 
419
 
411
-    // Provide a default for for the case when a message is being
420
+    // Provide a default for the case when a message is being
412
     // backfilled for a participant that has left the conference.
421
     // backfilled for a participant that has left the conference.
413
     const participant = getParticipantById(state, id) || { local: undefined };
422
     const participant = getParticipantById(state, id) || { local: undefined };
414
 
423
 
415
     const localParticipant = getLocalParticipant(getState);
424
     const localParticipant = getLocalParticipant(getState);
416
-    const displayName = lobbyChat
425
+    let displayNameToShow = lobbyChat
417
         ? getLobbyChatDisplayName(state, id)
426
         ? getLobbyChatDisplayName(state, id)
418
-        : getParticipantDisplayName(state, id);
427
+        : displayName || getParticipantDisplayName(state, id);
419
     const hasRead = participant.local || isChatOpen;
428
     const hasRead = participant.local || isChatOpen;
420
     const timestampToDate = timestamp ? new Date(timestamp) : new Date();
429
     const timestampToDate = timestamp ? new Date(timestamp) : new Date();
421
     const millisecondsTimestamp = timestampToDate.getTime();
430
     const millisecondsTimestamp = timestampToDate.getTime();
424
     const shouldShowNotification = userSelectedNotifications?.['notify.chatMessages']
433
     const shouldShowNotification = userSelectedNotifications?.['notify.chatMessages']
425
         && !hasRead && !isReaction && !timestamp;
434
         && !hasRead && !isReaction && !timestamp;
426
 
435
 
436
+    if (isGuest) {
437
+        displayNameToShow = `${displayNameToShow} ${i18next.t('visitors.chatIndicator')}`;
438
+    }
439
+
427
     dispatch(addMessage({
440
     dispatch(addMessage({
428
-        displayName,
441
+        displayName: displayNameToShow,
429
         hasRead,
442
         hasRead,
430
         id,
443
         id,
431
         messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
444
         messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
439
 
452
 
440
     if (shouldShowNotification) {
453
     if (shouldShowNotification) {
441
         dispatch(showMessageNotification({
454
         dispatch(showMessageNotification({
442
-            title: displayName,
455
+            title: displayNameToShow,
443
             description: message
456
             description: message
444
         }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
457
         }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
445
     }
458
     }
450
         APP.API.notifyReceivedChatMessage({
463
         APP.API.notifyReceivedChatMessage({
451
             body: message,
464
             body: message,
452
             id,
465
             id,
453
-            nick: displayName,
466
+            nick: displayNameToShow,
454
             privateMessage,
467
             privateMessage,
455
             ts: timestamp
468
             ts: timestamp
456
         });
469
         });

+ 3
- 1
react/features/video-menu/components/web/ParticipantContextMenu.tsx 查看文件

25
 // @ts-ignore
25
 // @ts-ignore
26
 import { requestRemoteControl, stopController } from '../../../remote-control';
26
 import { requestRemoteControl, stopController } from '../../../remote-control';
27
 import { showOverflowDrawer } from '../../../toolbox/functions.web';
27
 import { showOverflowDrawer } from '../../../toolbox/functions.web';
28
+import { iAmVisitor } from '../../../visitors/functions';
28
 
29
 
29
 import CustomOptionButton from './CustomOptionButton';
30
 import CustomOptionButton from './CustomOptionButton';
30
 // @ts-ignore
31
 // @ts-ignore
146
     const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
147
     const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
147
     const { remoteVideoMenu = {}, disableRemoteMute, startSilent, customParticipantMenuButtons }
148
     const { remoteVideoMenu = {}, disableRemoteMute, startSilent, customParticipantMenuButtons }
148
         = useSelector((state: IReduxState) => state['features/base/config']);
149
         = useSelector((state: IReduxState) => state['features/base/config']);
150
+    const visitorsMode = useSelector((state: IReduxState) => iAmVisitor(state));
149
     const { disableKick, disableGrantModerator, disablePrivateChat } = remoteVideoMenu;
151
     const { disableKick, disableGrantModerator, disablePrivateChat } = remoteVideoMenu;
150
     const { participantsVolume } = useSelector((state: IReduxState) => state['features/filmstrip']);
152
     const { participantsVolume } = useSelector((state: IReduxState) => state['features/filmstrip']);
151
     const _volume = (participant?.local ?? true ? undefined
153
     const _volume = (participant?.local ?? true ? undefined
264
             participantID = { _getCurrentParticipantId() } />);
266
             participantID = { _getCurrentParticipantId() } />);
265
     }
267
     }
266
 
268
 
267
-    if (!disablePrivateChat) {
269
+    if (!disablePrivateChat && !visitorsMode) {
268
         buttons2.push(<PrivateMessageMenuButton
270
         buttons2.push(<PrivateMessageMenuButton
269
             key = 'privateMessage'
271
             key = 'privateMessage'
270
             participantID = { _getCurrentParticipantId() } />
272
             participantID = { _getCurrentParticipantId() } />

+ 5
- 0
resources/extra-large-conference/pre-configure.sh 查看文件

26
     cp prosody.cfg.lua.visitor.template /etc/prosody-v${i}/prosody.cfg.lua
26
     cp prosody.cfg.lua.visitor.template /etc/prosody-v${i}/prosody.cfg.lua
27
     sed -i "s/vX/v${i}/g" /etc/prosody-v${i}/prosody.cfg.lua
27
     sed -i "s/vX/v${i}/g" /etc/prosody-v${i}/prosody.cfg.lua
28
     sed -i "s/jitmeet.example.com/$JICOFO_HOSTNAME/g" /etc/prosody-v${i}/prosody.cfg.lua
28
     sed -i "s/jitmeet.example.com/$JICOFO_HOSTNAME/g" /etc/prosody-v${i}/prosody.cfg.lua
29
+    # fix the ports
30
+    sed -i "s/52691/5269${i}/g" /etc/prosody-v${i}/prosody.cfg.lua
31
+    sed -i "s/52221/5222${i}/g" /etc/prosody-v${i}/prosody.cfg.lua
32
+    sed -i "s/52801/5280${i}/g" /etc/prosody-v${i}/prosody.cfg.lua
33
+    sed -i "s/52811/5281${i}/g" /etc/prosody-v${i}/prosody.cfg.lua
29
 done
34
 done
30
 
35
 
31
 # Configure jicofo
36
 # Configure jicofo

+ 2
- 0
resources/extra-large-conference/prosody.cfg.lua.visitor.template 查看文件

35
 };
35
 };
36
 
36
 
37
 s2s_whitelist = {
37
 s2s_whitelist = {
38
+    'conference.jitmeet.example.com', -- needed for visitors to send messages to main room
38
     'visitors.jitmeet.example.com'; -- needed for sending promotion request to visitors.jitmeet.example.com component
39
     'visitors.jitmeet.example.com'; -- needed for sending promotion request to visitors.jitmeet.example.com component
39
     'jitmeet.example.com'; -- unavailable presences back to main room
40
     'jitmeet.example.com'; -- unavailable presences back to main room
40
 };
41
 };
41
 
42
 
42
 s2s_connect_overrides = {
43
 s2s_connect_overrides = {
44
+    ["conference.jitmeet.example.com"] = { "127.0.0.1", 5269 }; -- needed for visitors to send messages to main room
43
     ["jitmeet.example.com"] = { "127.0.0.1", 5269 }; -- needed for the main room when connecting in to send main participants
45
     ["jitmeet.example.com"] = { "127.0.0.1", 5269 }; -- needed for the main room when connecting in to send main participants
44
     ["visitors.jitmeet.example.com"] = { "127.0.0.1", 5269 }; -- needed for sending promotion request to visitors.jitmeet.example.com component
46
     ["visitors.jitmeet.example.com"] = { "127.0.0.1", 5269 }; -- needed for sending promotion request to visitors.jitmeet.example.com component
45
 }
47
 }

+ 61
- 3
resources/prosody-plugins/mod_fmuc.lua 查看文件

10
 --- NOTE: Make sure all communication between prosodies is using the real jids ([foo]room1@muc.example.com), as there
10
 --- NOTE: Make sure all communication between prosodies is using the real jids ([foo]room1@muc.example.com), as there
11
 --- are certain configs for whitelisted domains and connections that are domain based
11
 --- are certain configs for whitelisted domains and connections that are domain based
12
 --- TODO: filter presence from main occupants back to main prosody
12
 --- TODO: filter presence from main occupants back to main prosody
13
---- TODO: filter messages back to main prosody
14
 local jid = require 'util.jid';
13
 local jid = require 'util.jid';
15
 local st = require 'util.stanza';
14
 local st = require 'util.stanza';
16
 local new_id = require 'util.id'.medium;
15
 local new_id = require 'util.id'.medium;
24
 local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference');
23
 local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference');
25
 local main_domain = string.gsub(module.host, muc_domain_prefix..'.', '');
24
 local main_domain = string.gsub(module.host, muc_domain_prefix..'.', '');
26
 
25
 
26
+local NICK_NS = 'http://jabber.org/protocol/nick';
27
+
27
 -- This is the domain of the main prosody that is federating with us;
28
 -- This is the domain of the main prosody that is federating with us;
28
 local fmuc_main_domain;
29
 local fmuc_main_domain;
29
 
30
 
138
     local raiseHand = full_p:get_child_text('jitsi_participant_raisedHand');
139
     local raiseHand = full_p:get_child_text('jitsi_participant_raisedHand');
139
     -- a promotion detected let's send it to main prosody
140
     -- a promotion detected let's send it to main prosody
140
     if raiseHand then
141
     if raiseHand then
141
-        -- TODO check room= with tenants
142
         local iq_id = new_id();
142
         local iq_id = new_id();
143
         sent_iq_cache:set(iq_id, socket.gettime());
143
         sent_iq_cache:set(iq_id, socket.gettime());
144
         local promotion_request = st.iq({
144
         local promotion_request = st.iq({
149
           :tag('visitors', { xmlns = 'jitsi:visitors',
149
           :tag('visitors', { xmlns = 'jitsi:visitors',
150
                              room = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..fmuc_main_domain) })
150
                              room = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..fmuc_main_domain) })
151
           :tag('promotion-request', { xmlns = 'jitsi:visitors', jid = occupant.jid }):up();
151
           :tag('promotion-request', { xmlns = 'jitsi:visitors', jid = occupant.jid }):up();
152
-        -- TODO what about name ???? it will be coming from the token, but we need to extract it and send it to the moderators
152
+
153
+        local nick_element = occupant:get_presence():get_child('nick', NICK_NS);
154
+        if nick_element then
155
+            promotion_request:add_child(nick_element);
156
+        end
157
+
153
         module:send(promotion_request);
158
         module:send(promotion_request);
154
     end
159
     end
155
 
160
 
236
 process_host_module(main_domain, function(host_module, host)
241
 process_host_module(main_domain, function(host_module, host)
237
     host_module:hook('iq/host', stanza_handler, 10);
242
     host_module:hook('iq/host', stanza_handler, 10);
238
 end);
243
 end);
244
+
245
+-- only live chat is supported for visitors
246
+module:hook("muc-occupant-groupchat", function(event)
247
+    local occupant, room, stanza = event.occupant, event.room, event.stanza;
248
+    local from = stanza.attr.from;
249
+    local occupant_host = jid.host(occupant.bare_jid);
250
+
251
+    -- if there is no occupant this is a message from main, probably coming from other vnode
252
+    if occupant then
253
+        -- we manage nick only for visitors
254
+        if occupant_host ~= fmuc_main_domain then
255
+            -- add to message stanza display name for the visitor
256
+            -- remove existing nick to avoid forgery
257
+            stanza:remove_children('nick', NICK_NS);
258
+            local nick_element = occupant:get_presence():get_child('nick', NICK_NS);
259
+            if nick_element then
260
+                stanza:add_child(nick_element);
261
+            else
262
+                stanza:tag('nick', { xmlns = NICK_NS })
263
+                    :text('anonymous'):up();
264
+            end
265
+        end
266
+
267
+        stanza.attr.from = occupant.nick;
268
+    else
269
+        stanza.attr.from = jid.join(jid.node(from), module.host);
270
+    end
271
+
272
+    -- let's send it to main chat and rest of visitors here
273
+    for _, o in room:each_occupant() do
274
+        -- filter remote occupants
275
+        if jid.host(o.bare_jid) == main_domain then
276
+            room:route_to_occupant(o, stanza)
277
+        end
278
+    end
279
+
280
+    -- send to main participants only messages from local occupants (skip from remote vnodes)
281
+    if occupant and occupant_host ~= fmuc_main_domain then
282
+        local main_message = st.clone(stanza);
283
+        main_message.attr.to = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..fmuc_main_domain);
284
+        module:send(main_message);
285
+    end
286
+    stanza.attr.from = from; -- something prosody does internally
287
+
288
+    return true;
289
+end, 55); -- prosody check for visitor's chat is prio 50, we want to override it
290
+
291
+module:hook('muc-private-message', function(event)
292
+    -- private messaging is forbidden
293
+    event.origin.send(st.error_reply(event.stanza, "auth", "forbidden",
294
+            "Private messaging is disabled on visitor nodes"));
295
+    return true;
296
+end, 10);

+ 6
- 0
resources/prosody-plugins/mod_muc_meeting_id.lua 查看文件

99
 end
99
 end
100
 
100
 
101
 module:hook('jicofo-unlock-room', handle_jicofo_unlock);
101
 module:hook('jicofo-unlock-room', handle_jicofo_unlock);
102
+
103
+-- make sure we remove nick if someone is sending it with a message to protect
104
+-- forgery of display name
105
+module:hook("muc-occupant-groupchat", function(event)
106
+    event.stanza:remove_children('nick', 'http://jabber.org/protocol/nick');
107
+end, 45); -- prosody check is prio 50, we want to run after it

+ 49
- 3
resources/prosody-plugins/mod_visitors.lua 查看文件

4
 --- vm1-ip-address conference.visitors1.domain.com
4
 --- vm1-ip-address conference.visitors1.domain.com
5
 --- vm2-ip-address visitors2.domain.com
5
 --- vm2-ip-address visitors2.domain.com
6
 --- vm2-ip-address conference.visitors2.domain.com
6
 --- vm2-ip-address conference.visitors2.domain.com
7
---- TODO: drop the /etc/hosts changes for https://modules.prosody.im/mod_s2soutinjection.html
8
 --- Enable in global modules: 's2s_bidi' and 'certs_all'
7
 --- Enable in global modules: 's2s_bidi' and 'certs_all'
9
 --- Make sure 's2s' is not in modules_disabled
8
 --- Make sure 's2s' is not in modules_disabled
10
 --- Open port 5269 on the provider side and on the firewall on the machine (iptables -I INPUT 4 -p tcp -m tcp --dport 5269 -j ACCEPT)
9
 --- Open port 5269 on the provider side and on the firewall on the machine (iptables -I INPUT 4 -p tcp -m tcp --dport 5269 -j ACCEPT)
11
---- TODO: make it work with tenants
12
 --- NOTE: Make sure all communication between prosodies is using the real jids ([foo]room1@muc.example.com)
10
 --- NOTE: Make sure all communication between prosodies is using the real jids ([foo]room1@muc.example.com)
13
 local st = require 'util.stanza';
11
 local st = require 'util.stanza';
14
 local jid = require 'util.jid';
12
 local jid = require 'util.jid';
127
     local stanza = event.stanza;
125
     local stanza = event.stanza;
128
     local room_name, from_host = jid.split(stanza.attr.from);
126
     local room_name, from_host = jid.split(stanza.attr.from);
129
     if stanza.attr.type == 'unavailable' and from_host ~= main_muc_component_config then
127
     if stanza.attr.type == 'unavailable' and from_host ~= main_muc_component_config then
130
-        -- TODO tenants???
131
         local room_jid = jid.join(room_name, main_muc_component_config); -- converts from visitor to main room jid
128
         local room_jid = jid.join(room_name, main_muc_component_config); -- converts from visitor to main room jid
132
 
129
 
133
         local x = stanza:get_child('x', 'http://jabber.org/protocol/muc#user');
130
         local x = stanza:get_child('x', 'http://jabber.org/protocol/muc#user');
231
             module:send(fmuc_pr);
228
             module:send(fmuc_pr);
232
         end
229
         end
233
     end);
230
     end);
231
+    -- forwards messages from main participants to vnodes
232
+    host_module:hook("muc-occupant-groupchat", function(event)
233
+        local room, stanza, occupant = event.room, event.stanza, event.occupant;
234
+
235
+        if not visitors_nodes[room.jid] then
236
+            return;
237
+        end
238
+
239
+        local vnodes = visitors_nodes[room.jid].nodes;
240
+        local user = jid.node(occupant.nick);
241
+        -- a main participant we need to update all active visitor nodes
242
+        for k in pairs(vnodes) do
243
+            local fmuc_msg = st.clone(stanza);
244
+            fmuc_msg.attr.to = jid.join(user, k);
245
+            fmuc_msg.attr.from = occupant.jid;
246
+            module:send(fmuc_msg);
247
+        end
248
+    end);
249
+    -- receiving messages from visitor nodes and forward them to local main participants
250
+    -- and forward them to the rest of visitor nodes
251
+    host_module:hook("muc-occupant-groupchat", function(event)
252
+        local occupant, room, stanza = event.occupant, event.room, event.stanza;
253
+        local to = stanza.attr.to;
254
+        local from = stanza.attr.from;
255
+        local from_vnode = jid.host(from);
256
+
257
+        if occupant or not (visitors_nodes[to] or visitors_nodes[to].nodes[from_vnode]) then
258
+            return;
259
+        end
260
+
261
+        -- a message from visitor occupant of known visitor node
262
+        stanza.attr.from = to;
263
+        for _, o in room:each_occupant() do
264
+            -- send it to the nick to be able to route it to the room (ljm multiple rooms) from unknown occupant
265
+            room:route_to_occupant(o, stanza);
266
+        end
267
+
268
+        -- now we need to send to rest of visitor nodes
269
+        local vnodes = visitors_nodes[room.jid].nodes;
270
+        for k in pairs(vnodes) do
271
+            if k ~= from_vnode then
272
+                local st_copy = st.clone(stanza);
273
+                st_copy.attr.to = jid.join(jid.node(room.jid), k);
274
+                module:send(st_copy);
275
+            end
276
+        end
277
+
278
+        return true;
279
+    end, 55); -- prosody check for unknown participant chat is prio 50, we want to override it
234
 end);
280
 end);

Loading…
取消
儲存