nodebb-plugin-admin-chats 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/client.js +240 -28
- package/library.js +268 -32
- package/package.json +2 -5
- package/plugin.json +14 -1
- package/public/languages/en-GB/admin-chats.json +3 -1
- package/public/languages/he/admin-chats.json +3 -1
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ A professional administration plugin for NodeBB that allows administrators to mo
|
|
|
4
4
|
|
|
5
5
|
## Main Features
|
|
6
6
|
|
|
7
|
-
* **Global Chat Access**: Administrators can view any private or group conversation.
|
|
8
|
-
* **Management Privileges**:
|
|
7
|
+
* **Global Chat Access**: Administrators and users with chat view permission can view any private or group conversation.
|
|
8
|
+
* **Management Privileges**: Administrators and users with chat management permission can edit or delete any message in any room.
|
|
9
9
|
* **Admin Room Locking**: Administrators can lock any chat room so only admins can continue replying.
|
|
10
10
|
* **Profile Integration**: Adds a "View Chats" button to the user profile menu for administrators to quickly audit a user's interactions.
|
|
11
11
|
* **Non-Intrusive Monitoring**: Admins can load and view room history without being added as permanent members, maintaining a clean member list.
|
|
@@ -16,7 +16,17 @@ A professional administration plugin for NodeBB that allows administrators to mo
|
|
|
16
16
|
### Plugin Information
|
|
17
17
|
* **Name**: Super Admin Chat Control
|
|
18
18
|
* **ID**: `nodebb-plugin-admin-chats`
|
|
19
|
-
* **Compatibility**: NodeBB
|
|
19
|
+
* **Compatibility**: NodeBB version `^4.0.0`
|
|
20
|
+
|
|
21
|
+
### Permissions
|
|
22
|
+
The plugin exposes two global permissions in the ACP:
|
|
23
|
+
|
|
24
|
+
* `admin-chats:view`: View any chat room and load history (read-only access).
|
|
25
|
+
* `admin-chats:manage`: Manage any chat room (edit/delete messages, lock/unlock rooms, and other moderator-level actions).
|
|
26
|
+
|
|
27
|
+
### Chat Route Behavior (`/chats`)
|
|
28
|
+
NodeBB exposes a core route at `/chats` (and `/chats/:roomId/:index?`) that redirects logged-in users to their personal chat page under `/user/:userslug/chats/...`.
|
|
29
|
+
This plugin also rewrites chat notification links to use `/user/:userslug/chats/...`, ensuring admins land on the correct user-scoped chat view when opening notifications.
|
|
20
30
|
|
|
21
31
|
### Implemented Hooks
|
|
22
32
|
The plugin utilizes several filters to elevate administrator permissions:
|
|
@@ -37,6 +47,14 @@ The plugin overrides core messaging functions to ensure full administrative cont
|
|
|
37
47
|
|
|
38
48
|
## Installation
|
|
39
49
|
|
|
50
|
+
### Option 1: Via Admin Control Panel (Recommended)
|
|
51
|
+
1. Navigate to the NodeBB Admin Control Panel (ACP).
|
|
52
|
+
2. Go to **Extend** → **Plugins**.
|
|
53
|
+
3. Search for "nodebb-plugin-admin-chats".
|
|
54
|
+
4. Click **Install**.
|
|
55
|
+
5. Activate the plugin and restart NodeBB.
|
|
56
|
+
|
|
57
|
+
### Option 2: Via Terminal
|
|
40
58
|
1. Install the plugin via terminal:
|
|
41
59
|
```bash
|
|
42
60
|
npm install nodebb-plugin-admin-chats
|
|
@@ -53,4 +71,4 @@ The plugin includes a `client.js` script that:
|
|
|
53
71
|
* Cleans up the chat UI empty states for a better admin experience.
|
|
54
72
|
|
|
55
73
|
---
|
|
56
|
-
*Developed by [palmoni5](https://github.com/palmoni5/nodebb-plugin-admin-chats).*
|
|
74
|
+
*Developed by [palmoni5](https://github.com/palmoni5/nodebb-plugin-admin-chats).*
|
package/client.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
$(document).ready(function() {
|
|
2
|
+
syncAdminAccessFromAjaxify();
|
|
3
|
+
fetchAdminPrivileges();
|
|
2
4
|
const LOCK_PREFIX = '[admin-chat-lock]';
|
|
3
5
|
const roomStateCache = new Map();
|
|
4
6
|
const TEXT = {
|
|
@@ -25,12 +27,106 @@ $(document).ready(function() {
|
|
|
25
27
|
function isEnglishSystem() {
|
|
26
28
|
return $('html').attr('lang') && $('html').attr('lang').startsWith('en');
|
|
27
29
|
}
|
|
28
|
-
|
|
29
30
|
function t(key) {
|
|
30
31
|
const dict = isEnglishSystem() ? TEXT.en : TEXT.he;
|
|
31
32
|
return dict[key];
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
function hasAdminChatsAccess() {
|
|
36
|
+
return !!((ajaxify && ajaxify.data && (ajaxify.data.adminChatsAccess || ajaxify.data.adminChatsCanManage)) || window.adminChatsAccess || window.adminChatsCanManage || (app.user && app.user.isAdmin));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isAdminUser() {
|
|
40
|
+
return !!((ajaxify && ajaxify.data && ajaxify.data.adminChatsIsAdmin) || window.adminChatsIsAdmin || (app.user && app.user.isAdmin));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function fetchAdminPrivileges() {
|
|
44
|
+
if (!hasAdminChatsAccess() || window.adminChatsPrivilegesPending) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
window.adminChatsPrivilegesPending = true;
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(`${config.relative_path || ''}/api/admin-chats/privileges`, {
|
|
51
|
+
headers: {
|
|
52
|
+
'x-csrf-token': config.csrf_token,
|
|
53
|
+
},
|
|
54
|
+
credentials: 'same-origin',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const payload = await response.json();
|
|
62
|
+
if (payload && typeof payload.adminChatsAccess === 'boolean') {
|
|
63
|
+
window.adminChatsAccess = payload.adminChatsAccess;
|
|
64
|
+
}
|
|
65
|
+
if (payload && typeof payload.adminChatsCanManage === 'boolean') {
|
|
66
|
+
window.adminChatsCanManage = payload.adminChatsCanManage;
|
|
67
|
+
}
|
|
68
|
+
if (payload && typeof payload.adminChatsIsAdmin === 'boolean') {
|
|
69
|
+
window.adminChatsIsAdmin = payload.adminChatsIsAdmin;
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// ignore
|
|
73
|
+
} finally {
|
|
74
|
+
window.adminChatsPrivilegesPending = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function syncAdminAccessFromAjaxify() {
|
|
79
|
+
if (!ajaxify || !ajaxify.data) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (typeof ajaxify.data.adminChatsAccess === 'boolean') {
|
|
83
|
+
window.adminChatsAccess = ajaxify.data.adminChatsAccess;
|
|
84
|
+
}
|
|
85
|
+
if (typeof ajaxify.data.adminChatsCanManage === 'boolean') {
|
|
86
|
+
window.adminChatsCanManage = ajaxify.data.adminChatsCanManage;
|
|
87
|
+
}
|
|
88
|
+
if (typeof ajaxify.data.adminChatsIsAdmin === 'boolean') {
|
|
89
|
+
window.adminChatsIsAdmin = ajaxify.data.adminChatsIsAdmin;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function canManageChats() {
|
|
94
|
+
return !!((ajaxify && ajaxify.data && ajaxify.data.adminChatsCanManage) || window.adminChatsCanManage || isAdminUser());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let adminChatsAccessProbe = null;
|
|
98
|
+
|
|
99
|
+
function resolveAdminChatsAccess() {
|
|
100
|
+
if (hasAdminChatsAccess()) {
|
|
101
|
+
return Promise.resolve(true);
|
|
102
|
+
}
|
|
103
|
+
if (window.adminChatsAccessChecked) {
|
|
104
|
+
return Promise.resolve(false);
|
|
105
|
+
}
|
|
106
|
+
if (adminChatsAccessProbe) {
|
|
107
|
+
return adminChatsAccessProbe;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
adminChatsAccessProbe = fetch(`${config.relative_path || ''}/api/admin-chats?start=0`, {
|
|
111
|
+
headers: {
|
|
112
|
+
'x-csrf-token': config.csrf_token,
|
|
113
|
+
},
|
|
114
|
+
credentials: 'same-origin',
|
|
115
|
+
}).then((response) => {
|
|
116
|
+
const ok = response && response.ok;
|
|
117
|
+
window.adminChatsAccess = ok;
|
|
118
|
+
return ok;
|
|
119
|
+
}).catch(() => {
|
|
120
|
+
window.adminChatsAccess = false;
|
|
121
|
+
return false;
|
|
122
|
+
}).finally(() => {
|
|
123
|
+
window.adminChatsAccessChecked = true;
|
|
124
|
+
adminChatsAccessProbe = null;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return adminChatsAccessProbe;
|
|
128
|
+
}
|
|
129
|
+
|
|
34
130
|
// Get lockedAction message from server translations only (based on forum settings)
|
|
35
131
|
function getLockedActionMessage() {
|
|
36
132
|
if (window.adminChatsTranslations && window.adminChatsTranslations.lockedAction) {
|
|
@@ -160,12 +256,20 @@ $(document).ready(function() {
|
|
|
160
256
|
Chats._adminAllChatsPatched = true;
|
|
161
257
|
}
|
|
162
258
|
|
|
259
|
+
// Auto-open chat if roomId is specified in URL
|
|
260
|
+
const currentRoomId = ajaxify && ajaxify.data && ajaxify.data.roomId;
|
|
261
|
+
if (currentRoomId && !$('body').hasClass('chat-loaded')) {
|
|
262
|
+
setTimeout(function() {
|
|
263
|
+
Chats.switchChat(currentRoomId);
|
|
264
|
+
}, 100);
|
|
265
|
+
}
|
|
266
|
+
|
|
163
267
|
bindAdminRecentChatsInfiniteScroll();
|
|
164
268
|
});
|
|
165
269
|
}
|
|
166
270
|
|
|
167
271
|
function replaceAdminEmptyStateText() {
|
|
168
|
-
if (!
|
|
272
|
+
if (!hasAdminChatsAccess()) {
|
|
169
273
|
return;
|
|
170
274
|
}
|
|
171
275
|
|
|
@@ -239,19 +343,50 @@ $(document).ready(function() {
|
|
|
239
343
|
credentials: 'same-origin',
|
|
240
344
|
});
|
|
241
345
|
if (!response.ok) {
|
|
346
|
+
if (hasAdminChatsAccess() && !isAdminAllChatsPage()) {
|
|
347
|
+
const adminResponse = await fetch(`${config.relative_path || ''}/api/admin-chats/page/${roomId}`, {
|
|
348
|
+
headers: {
|
|
349
|
+
'x-csrf-token': config.csrf_token,
|
|
350
|
+
},
|
|
351
|
+
credentials: 'same-origin',
|
|
352
|
+
});
|
|
353
|
+
if (!adminResponse.ok) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
const adminPayload = await adminResponse.json();
|
|
357
|
+
const adminRoom = adminPayload && adminPayload.roomId ? adminPayload : (adminPayload && adminPayload.room ? adminPayload.room : null);
|
|
358
|
+
if (adminRoom) {
|
|
359
|
+
setCachedRoomData(roomId, adminRoom);
|
|
360
|
+
}
|
|
361
|
+
return adminRoom;
|
|
362
|
+
}
|
|
242
363
|
return null;
|
|
243
364
|
}
|
|
244
365
|
|
|
245
366
|
const payload = await response.json();
|
|
246
367
|
const roomData = payload && payload.roomId ? payload : (payload && payload.room ? payload.room : null);
|
|
368
|
+
if (roomData && !roomData.adminChatLock && hasAdminChatsAccess() && !isAdminAllChatsPage()) {
|
|
369
|
+
const adminResponse = await fetch(`${config.relative_path || ''}/api/admin-chats/page/${roomId}`, {
|
|
370
|
+
headers: {
|
|
371
|
+
'x-csrf-token': config.csrf_token,
|
|
372
|
+
},
|
|
373
|
+
credentials: 'same-origin',
|
|
374
|
+
});
|
|
375
|
+
if (adminResponse.ok) {
|
|
376
|
+
const adminPayload = await adminResponse.json();
|
|
377
|
+
const adminRoom = adminPayload && adminPayload.roomId ? adminPayload : (adminPayload && adminPayload.room ? adminPayload.room : null);
|
|
378
|
+
if (adminRoom && adminRoom.adminChatLock) {
|
|
379
|
+
roomData.adminChatLock = adminRoom.adminChatLock;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
247
383
|
if (roomData) {
|
|
248
384
|
setCachedRoomData(roomId, roomData);
|
|
249
385
|
}
|
|
250
386
|
return roomData;
|
|
251
387
|
}
|
|
252
|
-
|
|
253
388
|
function isLockedForUser(roomData) {
|
|
254
|
-
return !!(roomData && roomData.adminChatLock && roomData.adminChatLock.isLocked && !(
|
|
389
|
+
return !!(roomData && roomData.adminChatLock && roomData.adminChatLock.isLocked && !canManageChats());
|
|
255
390
|
}
|
|
256
391
|
|
|
257
392
|
function setComposerHidden($window, hidden) {
|
|
@@ -306,6 +441,15 @@ $(document).ready(function() {
|
|
|
306
441
|
.toggleClass('hidden', hidden);
|
|
307
442
|
}
|
|
308
443
|
|
|
444
|
+
function updateDeleteRestoreVisibility($window) {
|
|
445
|
+
$window.find('[component="chat/message"]').each(function() {
|
|
446
|
+
const $message = $(this);
|
|
447
|
+
const isDeleted = $message.hasClass('deleted');
|
|
448
|
+
$message.find('[data-action="delete"]').toggleClass('hidden', isDeleted).toggle(!isDeleted);
|
|
449
|
+
$message.find('[data-action="restore"]').toggleClass('hidden', !isDeleted).toggle(isDeleted);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
309
453
|
function normalizeLockMessages($window) {
|
|
310
454
|
$window.find('[component="chat/system-message"] > div').each(function() {
|
|
311
455
|
const $el = $(this);
|
|
@@ -328,7 +472,7 @@ $(document).ready(function() {
|
|
|
328
472
|
function renderAdminLockControl($window, roomData) {
|
|
329
473
|
$window.find('.admin-chat-lock-toggle-item, .admin-chat-lock-divider, .admin-chat-lock-item-wrap').remove();
|
|
330
474
|
|
|
331
|
-
if (!
|
|
475
|
+
if (!canManageChats() || !roomData || !roomData.roomId) {
|
|
332
476
|
return;
|
|
333
477
|
}
|
|
334
478
|
|
|
@@ -374,6 +518,7 @@ $(document).ready(function() {
|
|
|
374
518
|
const hidden = isLockedForUser(roomData);
|
|
375
519
|
setComposerHidden($window, hidden);
|
|
376
520
|
updateLockedActionVisibility($window, hidden);
|
|
521
|
+
updateDeleteRestoreVisibility($window);
|
|
377
522
|
renderLockBanner($window, hidden);
|
|
378
523
|
renderAdminLockControl($window, roomData);
|
|
379
524
|
normalizeLockMessages($window);
|
|
@@ -444,33 +589,30 @@ $(document).ready(function() {
|
|
|
444
589
|
}
|
|
445
590
|
|
|
446
591
|
$(window).on('action:ajaxify.end', function(ev, data) {
|
|
592
|
+
syncAdminAccessFromAjaxify();
|
|
593
|
+
fetchAdminPrivileges();
|
|
447
594
|
const templateName = ajaxify && ajaxify.data && ajaxify.data.template ? ajaxify.data.template.name : '';
|
|
448
595
|
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
if (userSlug) {
|
|
453
|
-
const relativePath = config.relative_path || '';
|
|
454
|
-
const btnHtml = `
|
|
455
|
-
<li role="presentation">
|
|
456
|
-
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="${relativePath}/user/${userSlug}/chats" role="menuitem">
|
|
457
|
-
<i class="far fa-fw fa-comments"></i>
|
|
458
|
-
<span>${t('viewChats')}</span>
|
|
459
|
-
</a>
|
|
460
|
-
</li>
|
|
461
|
-
<li role="presentation" class="dropdown-divider"></li>
|
|
462
|
-
`;
|
|
463
|
-
const menu = $('.account-sub-links');
|
|
464
|
-
if (menu.length) {
|
|
465
|
-
menu.find(`a[href*="/user/${userSlug}/chats"]`).parent().remove();
|
|
466
|
-
menu.prepend(btnHtml);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
596
|
+
if (templateName.startsWith('account/')) {
|
|
597
|
+
injectProfileChatsMenu();
|
|
598
|
+
setTimeout(injectProfileChatsMenu, 200);
|
|
469
599
|
}
|
|
470
600
|
|
|
471
601
|
if (isAdminAllChatsPage()) {
|
|
472
602
|
patchForumChatsForAdminAll();
|
|
473
603
|
bindAdminRecentChatsInfiniteScroll();
|
|
604
|
+
|
|
605
|
+
// Auto-open chat if roomId is specified in URL and chat is not already loaded
|
|
606
|
+
const currentRoomId = ajaxify && ajaxify.data && ajaxify.data.roomId;
|
|
607
|
+
if (currentRoomId && !$('body').hasClass('chat-loaded')) {
|
|
608
|
+
setTimeout(function() {
|
|
609
|
+
require(['forum/chats'], function(Chats) {
|
|
610
|
+
if (Chats && Chats.switchChat) {
|
|
611
|
+
Chats.switchChat(currentRoomId);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
}, 200);
|
|
615
|
+
}
|
|
474
616
|
}
|
|
475
617
|
|
|
476
618
|
const url = data && data.url ? data.url : '';
|
|
@@ -478,6 +620,11 @@ $(document).ready(function() {
|
|
|
478
620
|
refreshChatUi();
|
|
479
621
|
setTimeout(refreshChatUi, 500);
|
|
480
622
|
setTimeout(refreshChatUi, 1200);
|
|
623
|
+
|
|
624
|
+
// Handle chat navigation for internal URL changes
|
|
625
|
+
if (url.match(/^chats\/\d+/)) {
|
|
626
|
+
setTimeout(handleChatNavigation, 300);
|
|
627
|
+
}
|
|
481
628
|
}
|
|
482
629
|
});
|
|
483
630
|
|
|
@@ -485,6 +632,20 @@ $(document).ready(function() {
|
|
|
485
632
|
if (isAdminAllChatsPage()) {
|
|
486
633
|
patchForumChatsForAdminAll();
|
|
487
634
|
bindAdminRecentChatsInfiniteScroll();
|
|
635
|
+
|
|
636
|
+
// Auto-open chat if roomId is specified in URL and no chat is currently active
|
|
637
|
+
const currentRoomId = ajaxify && ajaxify.data && ajaxify.data.roomId;
|
|
638
|
+
const activeChatId = $('[component="chat/main-wrapper"]').attr('data-roomid');
|
|
639
|
+
|
|
640
|
+
if (currentRoomId && (!activeChatId || activeChatId !== String(currentRoomId))) {
|
|
641
|
+
setTimeout(function() {
|
|
642
|
+
require(['forum/chats'], function(Chats) {
|
|
643
|
+
if (Chats && Chats.switchChat) {
|
|
644
|
+
Chats.switchChat(currentRoomId);
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
}, 100);
|
|
648
|
+
}
|
|
488
649
|
}
|
|
489
650
|
refreshChatUi();
|
|
490
651
|
setTimeout(refreshChatUi, 200);
|
|
@@ -499,6 +660,58 @@ $(document).ready(function() {
|
|
|
499
660
|
setTimeout(refreshChatUi, 0);
|
|
500
661
|
});
|
|
501
662
|
|
|
663
|
+
if (window.socket) {
|
|
664
|
+
socket.removeListener('event:chats.delete', refreshChatUi);
|
|
665
|
+
socket.removeListener('event:chats.restore', refreshChatUi);
|
|
666
|
+
socket.on('event:chats.delete', function() { setTimeout(refreshChatUi, 0); });
|
|
667
|
+
socket.on('event:chats.restore', function() { setTimeout(refreshChatUi, 0); });
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Handle browser back/forward navigation
|
|
671
|
+
$(window).on('popstate', function() {
|
|
672
|
+
if (isAdminAllChatsPage()) {
|
|
673
|
+
setTimeout(handleChatNavigation, 100);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// Handle URL changes for internal navigation
|
|
678
|
+
function handleChatNavigation() {
|
|
679
|
+
if (!isAdminAllChatsPage()) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Try to get roomId from multiple sources
|
|
684
|
+
let currentRoomId = ajaxify && ajaxify.data && ajaxify.data.roomId;
|
|
685
|
+
|
|
686
|
+
// If not found in ajaxify.data, try to extract from URL
|
|
687
|
+
if (!currentRoomId) {
|
|
688
|
+
const urlMatch = window.location.pathname.match(/\/chats\/(\d+)/);
|
|
689
|
+
if (urlMatch) {
|
|
690
|
+
currentRoomId = parseInt(urlMatch[1], 10);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const activeChatId = $('[component="chat/main-wrapper"]').attr('data-roomid');
|
|
695
|
+
|
|
696
|
+
if (currentRoomId && (!activeChatId || activeChatId !== String(currentRoomId))) {
|
|
697
|
+
require(['forum/chats'], function(Chats) {
|
|
698
|
+
if (Chats && Chats.switchChat) {
|
|
699
|
+
Chats.switchChat(currentRoomId);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
$(document).on('click', '[data-action="delete"], [data-action="restore"]', function() {
|
|
706
|
+
const $window = $(this).closest('[component="chat/message/window"]');
|
|
707
|
+
if (!$window.length) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
setTimeout(function() {
|
|
711
|
+
updateDeleteRestoreVisibility($window);
|
|
712
|
+
}, 50);
|
|
713
|
+
});
|
|
714
|
+
|
|
502
715
|
$(document).on('click', '.admin-chat-lock-toggle-item', async function(ev) {
|
|
503
716
|
ev.preventDefault();
|
|
504
717
|
|
|
@@ -517,7 +730,7 @@ $(document).ready(function() {
|
|
|
517
730
|
const currentRoom = ajaxify && ajaxify.data && (ajaxify.data.room || ajaxify.data);
|
|
518
731
|
if (currentRoom && parseInt(currentRoom.roomId, 10) === roomId) {
|
|
519
732
|
currentRoom.adminChatLock = result.lockData;
|
|
520
|
-
currentRoom.canReply = !result.lockData.isLocked || (
|
|
733
|
+
currentRoom.canReply = !result.lockData.isLocked || canManageChats();
|
|
521
734
|
currentRoom.showUserInput = currentRoom.canReply;
|
|
522
735
|
if (ajaxify.data.room) {
|
|
523
736
|
ajaxify.data.room.adminChatLock = result.lockData;
|
|
@@ -528,7 +741,7 @@ $(document).ready(function() {
|
|
|
528
741
|
|
|
529
742
|
const cached = getCachedRoomData(roomId) || { roomId };
|
|
530
743
|
cached.adminChatLock = result.lockData;
|
|
531
|
-
cached.canReply = !result.lockData.isLocked || (
|
|
744
|
+
cached.canReply = !result.lockData.isLocked || canManageChats();
|
|
532
745
|
cached.showUserInput = cached.canReply;
|
|
533
746
|
setCachedRoomData(roomId, cached);
|
|
534
747
|
await refreshChatUi();
|
|
@@ -552,4 +765,3 @@ $(document).ready(function() {
|
|
|
552
765
|
app.alertError._adminChatsWrapped = true;
|
|
553
766
|
}
|
|
554
767
|
});
|
|
555
|
-
|
package/library.js
CHANGED
|
@@ -11,6 +11,8 @@ const helpers = require.main.require('./src/controllers/helpers');
|
|
|
11
11
|
const plugin = {};
|
|
12
12
|
const LOCK_PREFIX = '[admin-chat-lock]';
|
|
13
13
|
const LOCKED_MESSAGE_TEXT = '🔒 חדר זה ננעל ע"י המנהלים.';
|
|
14
|
+
const ADMIN_CHATS_PRIVILEGE = 'admin-chats:view';
|
|
15
|
+
const ADMIN_CHATS_MANAGE_PRIVILEGE = 'admin-chats:manage';
|
|
14
16
|
|
|
15
17
|
async function getLockedActionMessage(uid) {
|
|
16
18
|
try {
|
|
@@ -36,13 +38,121 @@ async function getLockedActionMessage(uid) {
|
|
|
36
38
|
const ADMIN_CHAT_PAGE_SIZE = 30;
|
|
37
39
|
const ADMIN_CHAT_SCAN_SIZE = 100;
|
|
38
40
|
|
|
41
|
+
async function canAccessAdminChats(uid) {
|
|
42
|
+
if (!uid) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const [isAdmin, canView, canManage] = await Promise.all([
|
|
47
|
+
User.isAdministrator(uid),
|
|
48
|
+
privileges.global.can(ADMIN_CHATS_PRIVILEGE, uid),
|
|
49
|
+
privileges.global.can(ADMIN_CHATS_MANAGE_PRIVILEGE, uid),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
return isAdmin || canView || canManage;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function canManageAdminChats(uid) {
|
|
56
|
+
if (!uid) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const [isAdmin, canManage] = await Promise.all([
|
|
61
|
+
User.isAdministrator(uid),
|
|
62
|
+
privileges.global.can(ADMIN_CHATS_MANAGE_PRIVILEGE, uid),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
return isAdmin || canManage;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function isUserMemberOfRoom(uid, roomId) {
|
|
69
|
+
if (!uid || !roomId) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return await db.isSortedSetMember(`chat:room:${roomId}:uids`, uid);
|
|
74
|
+
}
|
|
39
75
|
plugin.init = async function (params) {
|
|
40
76
|
registerRoutes(params.app, params.router, params.middleware);
|
|
41
77
|
overrideMessagingFunctions();
|
|
42
78
|
overrideChatsApi();
|
|
43
79
|
overrideCoreChatRedirect(params.controllers);
|
|
44
80
|
};
|
|
81
|
+
plugin.registerPrivileges = async function (data) {
|
|
82
|
+
if (!data || !data.privileges || typeof data.privileges.set !== 'function') {
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
data.privileges.set(ADMIN_CHATS_PRIVILEGE, {
|
|
87
|
+
label: '[[admin-chats:admin-chats-view]]',
|
|
88
|
+
type: 'moderation',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
data.privileges.set(ADMIN_CHATS_MANAGE_PRIVILEGE, {
|
|
92
|
+
label: '[[admin-chats:admin-chats-manage]]',
|
|
93
|
+
type: 'moderation',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return data;
|
|
97
|
+
};
|
|
45
98
|
|
|
99
|
+
plugin.filterNotificationCreate = async function (data) {
|
|
100
|
+
if (!data || !data.data) {
|
|
101
|
+
return data;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const notification = data.data;
|
|
105
|
+
|
|
106
|
+
// Check if this is a chat notification with path like /chats/123
|
|
107
|
+
if (notification.path && notification.path.match(/^\/chats\/\d+/)) {
|
|
108
|
+
const chatMatch = notification.path.match(/^\/chats\/(\d+)(?:\/(\d+))?$/);
|
|
109
|
+
if (chatMatch && notification && notification.uid) {
|
|
110
|
+
const roomId = chatMatch[1];
|
|
111
|
+
const index = chatMatch[2];
|
|
112
|
+
|
|
113
|
+
// Get the user's slug to redirect to their personal chat page
|
|
114
|
+
const userSlug = await User.getUserField(notification.uid, 'userslug');
|
|
115
|
+
if (userSlug) {
|
|
116
|
+
notification.path = `/user/${userSlug}/chats/${roomId}${index ? `/${index}` : ''}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return data;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
plugin.filterUserNotificationsGetNotifications = async function (data) {
|
|
125
|
+
if (!data || !data.uid || !Array.isArray(data.notifications) || !data.notifications.length) {
|
|
126
|
+
return data;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const userSlug = await User.getUserField(data.uid, 'userslug');
|
|
130
|
+
if (!userSlug) {
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const userPathRegex = /\/user\/[^/]+\/chats\//;
|
|
135
|
+
const chatPathRegex = /\/chats\/(\d+)(?:\/(\d+))?(?=$|[/?#])/;
|
|
136
|
+
|
|
137
|
+
data.notifications.forEach((notification) => {
|
|
138
|
+
if (!notification || !notification.path) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (userPathRegex.test(notification.path)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const match = notification.path.match(chatPathRegex);
|
|
145
|
+
if (!match) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const roomId = match[1];
|
|
149
|
+
const index = match[2];
|
|
150
|
+
const replacement = `/user/${userSlug}/chats/${roomId}${index ? `/${index}` : ''}`;
|
|
151
|
+
notification.path = notification.path.replace(chatPathRegex, replacement);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return data;
|
|
155
|
+
};
|
|
46
156
|
plugin.addScripts = async function (data) {
|
|
47
157
|
let isHebrew = false;
|
|
48
158
|
|
|
@@ -70,10 +180,14 @@ plugin.addScripts = async function (data) {
|
|
|
70
180
|
lockedAction: isHebrew ? 'לא ניתן לבצע פעולה זו בחדר נעול' : 'This action cannot be performed in a locked room.',
|
|
71
181
|
};
|
|
72
182
|
|
|
183
|
+
const isAdmin = uid ? await User.isAdministrator(uid) : false;
|
|
184
|
+
const adminChatsManage = uid ? (isAdmin || await privileges.global.can(ADMIN_CHATS_MANAGE_PRIVILEGE, uid)) : false;
|
|
185
|
+
const adminChatsAccess = uid ? (isAdmin || adminChatsManage || await privileges.global.can(ADMIN_CHATS_PRIVILEGE, uid)) : false;
|
|
186
|
+
|
|
73
187
|
data.scripts = data.scripts || [];
|
|
74
188
|
data.scripts.push({
|
|
75
189
|
src: false,
|
|
76
|
-
script: `window.adminChatsTranslations = ${JSON.stringify(translations)};`
|
|
190
|
+
script: `window.adminChatsTranslations = ${JSON.stringify(translations)}; window.adminChatsAccess = ${adminChatsAccess}; window.adminChatsIsAdmin = ${isAdmin}; window.adminChatsCanManage = ${adminChatsManage};`
|
|
77
191
|
});
|
|
78
192
|
|
|
79
193
|
return data;
|
|
@@ -81,6 +195,11 @@ plugin.addScripts = async function (data) {
|
|
|
81
195
|
|
|
82
196
|
plugin.addProfileLink = async function (data) {
|
|
83
197
|
try {
|
|
198
|
+
const uid = data && (data.uid || (data.req && data.req.uid));
|
|
199
|
+
if (!uid || !await canAccessAdminChats(uid)) {
|
|
200
|
+
return data;
|
|
201
|
+
}
|
|
202
|
+
|
|
84
203
|
const userSlug = data.user ? data.user.userslug : (data.userData ? data.userData.userslug : null);
|
|
85
204
|
|
|
86
205
|
if (userSlug) {
|
|
@@ -88,12 +207,12 @@ plugin.addProfileLink = async function (data) {
|
|
|
88
207
|
id: 'admin-view-chats',
|
|
89
208
|
route: 'user/' + userSlug + '/chats',
|
|
90
209
|
icon: 'fa-comments',
|
|
91
|
-
name: '
|
|
210
|
+
name: '[[admin-chats:profile.viewChats]]',
|
|
92
211
|
visibility: {
|
|
93
|
-
self:
|
|
212
|
+
self: true,
|
|
94
213
|
other: true,
|
|
95
|
-
moderator:
|
|
96
|
-
globalMod:
|
|
214
|
+
moderator: true,
|
|
215
|
+
globalMod: true,
|
|
97
216
|
admin: true,
|
|
98
217
|
}
|
|
99
218
|
});
|
|
@@ -105,16 +224,19 @@ plugin.addProfileLink = async function (data) {
|
|
|
105
224
|
};
|
|
106
225
|
|
|
107
226
|
plugin.isRoomOwner = async function (payload) {
|
|
108
|
-
const isAdmin = await
|
|
109
|
-
|
|
227
|
+
const [isAdmin, canManage] = await Promise.all([
|
|
228
|
+
User.isAdministrator(payload.uid),
|
|
229
|
+
canManageAdminChats(payload.uid),
|
|
230
|
+
]);
|
|
231
|
+
if (isAdmin || canManage) {
|
|
110
232
|
payload.isOwner = true;
|
|
111
233
|
}
|
|
112
234
|
return payload;
|
|
113
235
|
};
|
|
114
236
|
|
|
115
237
|
plugin.isUserInRoom = async function (payload) {
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
238
|
+
const uid = getCallerUid(payload);
|
|
239
|
+
if (await canAccessAdminChats(uid)) {
|
|
118
240
|
payload.inRoom = true;
|
|
119
241
|
}
|
|
120
242
|
return payload;
|
|
@@ -123,13 +245,28 @@ plugin.isUserInRoom = async function (payload) {
|
|
|
123
245
|
plugin.canReply = async function (payload) {
|
|
124
246
|
const uid = getCallerUid(payload);
|
|
125
247
|
const roomId = getRoomId(payload);
|
|
126
|
-
const isAdmin = await User.isAdministrator(uid);
|
|
127
248
|
|
|
128
|
-
|
|
249
|
+
const [isAdmin, canView, canManage] = await Promise.all([
|
|
250
|
+
User.isAdministrator(uid),
|
|
251
|
+
privileges.global.can(ADMIN_CHATS_PRIVILEGE, uid),
|
|
252
|
+
privileges.global.can(ADMIN_CHATS_MANAGE_PRIVILEGE, uid),
|
|
253
|
+
]);
|
|
254
|
+
const isManager = isAdmin || canManage;
|
|
255
|
+
const hasAccess = isAdmin || canView || canManage;
|
|
256
|
+
|
|
257
|
+
if (isManager) {
|
|
129
258
|
payload.canReply = true;
|
|
130
259
|
return payload;
|
|
131
260
|
}
|
|
132
261
|
|
|
262
|
+
if (roomId && hasAccess) {
|
|
263
|
+
const isMember = await isUserMemberOfRoom(uid, roomId);
|
|
264
|
+
if (!isMember) {
|
|
265
|
+
payload.canReply = false;
|
|
266
|
+
return payload;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
133
270
|
if (roomId && await isRoomLocked(roomId)) {
|
|
134
271
|
payload.canReply = false;
|
|
135
272
|
}
|
|
@@ -149,6 +286,17 @@ plugin.filterMessagingSend = async function (payload) {
|
|
|
149
286
|
return payload;
|
|
150
287
|
}
|
|
151
288
|
|
|
289
|
+
if (await canManageAdminChats(uid)) {
|
|
290
|
+
return payload;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (await canAccessAdminChats(uid)) {
|
|
294
|
+
const isMember = await isUserMemberOfRoom(uid, roomId);
|
|
295
|
+
if (!isMember) {
|
|
296
|
+
throw new Error('[[error:no-privileges]]');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
152
300
|
if (await isRoomLocked(roomId)) {
|
|
153
301
|
const message = await getLockedActionMessage(uid);
|
|
154
302
|
throw new Error(message);
|
|
@@ -157,6 +305,9 @@ plugin.filterMessagingSend = async function (payload) {
|
|
|
157
305
|
return payload;
|
|
158
306
|
};
|
|
159
307
|
plugin.filterAddUsersToRoom = async function (payload) {
|
|
308
|
+
if (await canManageAdminChats(payload.uid)) {
|
|
309
|
+
return payload;
|
|
310
|
+
}
|
|
160
311
|
if (await canNonAdminModifyLockedRoom(payload.uid, payload.roomId)) {
|
|
161
312
|
const message = await getLockedActionMessage(payload.uid);
|
|
162
313
|
throw new Error(message);
|
|
@@ -165,6 +316,9 @@ plugin.filterAddUsersToRoom = async function (payload) {
|
|
|
165
316
|
};
|
|
166
317
|
|
|
167
318
|
plugin.filterRemoveUsersFromRoom = async function (payload) {
|
|
319
|
+
if (await canManageAdminChats(payload.uid)) {
|
|
320
|
+
return payload;
|
|
321
|
+
}
|
|
168
322
|
if (await canNonAdminModifyLockedRoom(payload.uid, payload.roomId)) {
|
|
169
323
|
const message = await getLockedActionMessage(payload.uid);
|
|
170
324
|
throw new Error(message);
|
|
@@ -173,6 +327,9 @@ plugin.filterRemoveUsersFromRoom = async function (payload) {
|
|
|
173
327
|
};
|
|
174
328
|
|
|
175
329
|
plugin.filterRenameRoom = async function (payload) {
|
|
330
|
+
if (await canManageAdminChats(payload.uid)) {
|
|
331
|
+
return payload;
|
|
332
|
+
}
|
|
176
333
|
if (await canNonAdminModifyLockedRoom(payload.uid, payload.roomId)) {
|
|
177
334
|
const message = await getLockedActionMessage(payload.uid);
|
|
178
335
|
throw new Error(message);
|
|
@@ -181,24 +338,21 @@ plugin.filterRenameRoom = async function (payload) {
|
|
|
181
338
|
};
|
|
182
339
|
|
|
183
340
|
plugin.canGetMessages = async function (payload) {
|
|
184
|
-
|
|
185
|
-
if (isAdmin) {
|
|
341
|
+
if (await canAccessAdminChats(payload.callerUid)) {
|
|
186
342
|
payload.canGet = true;
|
|
187
343
|
}
|
|
188
344
|
return payload;
|
|
189
345
|
};
|
|
190
346
|
|
|
191
347
|
plugin.canGetRecentChats = async function (payload) {
|
|
192
|
-
|
|
193
|
-
if (isAdmin) {
|
|
348
|
+
if (await canAccessAdminChats(payload.callerUid)) {
|
|
194
349
|
payload.canGet = true;
|
|
195
350
|
}
|
|
196
351
|
return payload;
|
|
197
352
|
};
|
|
198
353
|
|
|
199
354
|
plugin.canGetPublicChats = async function (payload) {
|
|
200
|
-
|
|
201
|
-
if (isAdmin) {
|
|
355
|
+
if (await canAccessAdminChats(payload.callerUid)) {
|
|
202
356
|
payload.canGet = true;
|
|
203
357
|
}
|
|
204
358
|
return payload;
|
|
@@ -210,20 +364,27 @@ plugin.onLoadRoom = async function (payload) {
|
|
|
210
364
|
return payload;
|
|
211
365
|
}
|
|
212
366
|
|
|
213
|
-
const isAdmin = await
|
|
367
|
+
const [isAdmin, canView, canManage] = await Promise.all([
|
|
368
|
+
User.isAdministrator(uid),
|
|
369
|
+
privileges.global.can(ADMIN_CHATS_PRIVILEGE, uid),
|
|
370
|
+
privileges.global.can(ADMIN_CHATS_MANAGE_PRIVILEGE, uid),
|
|
371
|
+
]);
|
|
372
|
+
const isManager = isAdmin || canManage;
|
|
373
|
+
const hasAccess = isAdmin || canView || canManage;
|
|
374
|
+
|
|
214
375
|
const lockData = await getRoomLockData(room.roomId);
|
|
215
|
-
const isLockedForUser = lockData.isLocked && !
|
|
376
|
+
const isLockedForUser = lockData.isLocked && !isManager;
|
|
216
377
|
|
|
217
378
|
room.adminChatLock = lockData;
|
|
218
379
|
room.messages = ensureFirstVisibleMessageHeader(buildRoomMessagesWithLockNotice(room.messages, lockData));
|
|
219
380
|
room.canReply = room.canReply && !isLockedForUser;
|
|
220
381
|
room.showUserInput = room.showUserInput && !isLockedForUser;
|
|
221
382
|
|
|
222
|
-
if (!
|
|
383
|
+
if (!hasAccess) {
|
|
223
384
|
return payload;
|
|
224
385
|
}
|
|
225
386
|
|
|
226
|
-
const isOfficialMember = await
|
|
387
|
+
const isOfficialMember = await isUserMemberOfRoom(uid, room.roomId);
|
|
227
388
|
|
|
228
389
|
if (!isOfficialMember) {
|
|
229
390
|
await db.sortedSetRemove(`chat:room:${room.roomId}:uids:online`, uid);
|
|
@@ -268,12 +429,24 @@ plugin.onLoadRoom = async function (payload) {
|
|
|
268
429
|
|
|
269
430
|
room.messages = ensureFirstVisibleMessageHeader(room.messages);
|
|
270
431
|
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (!isManager && !isOfficialMember) {
|
|
435
|
+
room.canReply = false;
|
|
436
|
+
room.showUserInput = false;
|
|
437
|
+
}
|
|
271
438
|
|
|
272
|
-
|
|
439
|
+
room.isAdmin = isManager;
|
|
440
|
+
room.isAdminOrGlobalMod = isManager;
|
|
441
|
+
payload.isAdminOrGlobalMod = isManager;
|
|
442
|
+
if (isManager) {
|
|
273
443
|
room.isOwner = true;
|
|
274
|
-
} else {
|
|
275
|
-
room.isAdmin = true;
|
|
276
444
|
}
|
|
445
|
+
room.adminChatsAccess = hasAccess;
|
|
446
|
+
payload.adminChatsAccess = hasAccess;
|
|
447
|
+
payload.adminChatsCanManage = isManager;
|
|
448
|
+
payload.adminChatsIsAdmin = isAdmin;
|
|
449
|
+
|
|
277
450
|
return payload;
|
|
278
451
|
};
|
|
279
452
|
|
|
@@ -285,7 +458,7 @@ function overrideCoreChatRedirect(controllers) {
|
|
|
285
458
|
|
|
286
459
|
const originalRedirectToChat = chatsController.redirectToChat;
|
|
287
460
|
const wrappedRedirectToChat = async function (req, res, next) {
|
|
288
|
-
if (req && req.uid && await
|
|
461
|
+
if (req && req.uid && await canAccessAdminChats(req.uid)) {
|
|
289
462
|
return await renderAdminChatsPage(req, res, next);
|
|
290
463
|
}
|
|
291
464
|
|
|
@@ -303,7 +476,7 @@ function overrideChatsApi() {
|
|
|
303
476
|
|
|
304
477
|
const originalKick = ChatsAPI.kick;
|
|
305
478
|
const wrappedKick = async function (caller, data) {
|
|
306
|
-
if (caller && !await User.isAdministrator(caller.uid) && data && data.roomId && Array.isArray(data.uids) && data.uids.length === 1 && parseInt(data.uids[0], 10) === parseInt(caller.uid, 10) && await isRoomLocked(data.roomId)) {
|
|
479
|
+
if (caller && !await User.isAdministrator(caller.uid) && !await canManageAdminChats(caller.uid) && data && data.roomId && Array.isArray(data.uids) && data.uids.length === 1 && parseInt(data.uids[0], 10) === parseInt(caller.uid, 10) && await isRoomLocked(data.roomId)) {
|
|
307
480
|
const message = await getLockedActionMessage(caller.uid);
|
|
308
481
|
throw new Error(message);
|
|
309
482
|
}
|
|
@@ -325,6 +498,10 @@ function overrideMessagingFunctions() {
|
|
|
325
498
|
return true;
|
|
326
499
|
}
|
|
327
500
|
|
|
501
|
+
if (await canManageAdminChats(uid)) {
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
|
|
328
505
|
const roomId = await getMessageRoomId(messageId);
|
|
329
506
|
if (roomId && await isRoomLocked(roomId)) {
|
|
330
507
|
const message = await getLockedActionMessage(uid);
|
|
@@ -339,6 +516,10 @@ function overrideMessagingFunctions() {
|
|
|
339
516
|
return true;
|
|
340
517
|
}
|
|
341
518
|
|
|
519
|
+
if (await canManageAdminChats(uid)) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
|
|
342
523
|
const roomId = await getMessageRoomId(messageId);
|
|
343
524
|
if (roomId && await isRoomLocked(roomId)) {
|
|
344
525
|
const message = await getLockedActionMessage(uid);
|
|
@@ -349,7 +530,7 @@ function overrideMessagingFunctions() {
|
|
|
349
530
|
};
|
|
350
531
|
|
|
351
532
|
Messaging.canViewMessage = async function (mids, roomId, uid) {
|
|
352
|
-
if (await
|
|
533
|
+
if (await canAccessAdminChats(uid)) {
|
|
353
534
|
return Array.isArray(mids) ? mids.map(() => true) : true;
|
|
354
535
|
}
|
|
355
536
|
return await originalCanViewMessage(mids, roomId, uid);
|
|
@@ -358,9 +539,23 @@ function overrideMessagingFunctions() {
|
|
|
358
539
|
if (typeof originalCanReply === 'function') {
|
|
359
540
|
Messaging.canReply = async function (...args) {
|
|
360
541
|
const guessed = guessRoomIdUidFromArgs(args);
|
|
361
|
-
|
|
542
|
+
const [isAdmin, canView, canManage] = await Promise.all([
|
|
543
|
+
User.isAdministrator(guessed.uid),
|
|
544
|
+
privileges.global.can(ADMIN_CHATS_PRIVILEGE, guessed.uid),
|
|
545
|
+
privileges.global.can(ADMIN_CHATS_MANAGE_PRIVILEGE, guessed.uid),
|
|
546
|
+
]);
|
|
547
|
+
const isManager = isAdmin || canManage;
|
|
548
|
+
const hasAccess = isAdmin || canView || canManage;
|
|
549
|
+
|
|
550
|
+
if (isManager) {
|
|
362
551
|
return true;
|
|
363
552
|
}
|
|
553
|
+
if (guessed.roomId && hasAccess) {
|
|
554
|
+
const isMember = await isUserMemberOfRoom(guessed.uid, guessed.roomId);
|
|
555
|
+
if (!isMember) {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
364
559
|
if (guessed.roomId && await isRoomLocked(guessed.roomId)) {
|
|
365
560
|
return false;
|
|
366
561
|
}
|
|
@@ -393,6 +588,15 @@ function registerRoutes(app, router, middleware) {
|
|
|
393
588
|
|
|
394
589
|
const pageController = async (req, res, next) => {
|
|
395
590
|
try {
|
|
591
|
+
// If user is not allowed, redirect to user-specific chat URL
|
|
592
|
+
if (req.uid && !await canAccessAdminChats(req.uid)) {
|
|
593
|
+
const userSlug = await User.getUserField(req.uid, 'userslug');
|
|
594
|
+
if (userSlug && req.params.roomId) {
|
|
595
|
+
const redirectUrl = `${req.baseUrl || ''}/user/${userSlug}/chats/${req.params.roomId}${req.params.index ? `/${req.params.index}` : ''}`;
|
|
596
|
+
return res.redirect(redirectUrl);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
396
600
|
await renderAdminChatsPage(req, res, next);
|
|
397
601
|
} catch (err) {
|
|
398
602
|
next(err);
|
|
@@ -416,6 +620,25 @@ function registerRoutes(app, router, middleware) {
|
|
|
416
620
|
}
|
|
417
621
|
});
|
|
418
622
|
|
|
623
|
+
|
|
624
|
+
router.get('/api/admin-chats/privileges', ...routeMiddleware, async (req, res) => {
|
|
625
|
+
try {
|
|
626
|
+
if (!await assertAdminChatsAccess(req, res)) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const isAdmin = await User.isAdministrator(req.uid);
|
|
631
|
+
const canManage = isAdmin || await privileges.global.can(ADMIN_CHATS_MANAGE_PRIVILEGE, req.uid);
|
|
632
|
+
res.json({
|
|
633
|
+
adminChatsAccess: true,
|
|
634
|
+
adminChatsCanManage: canManage,
|
|
635
|
+
adminChatsIsAdmin: isAdmin,
|
|
636
|
+
});
|
|
637
|
+
} catch (err) {
|
|
638
|
+
res.status(500).json({ status: { code: 'error', message: err.message } });
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
|
|
419
642
|
router.get('/api/admin-chats/page/:roomId?/:index?', ...routeMiddleware, async (req, res) => {
|
|
420
643
|
try {
|
|
421
644
|
if (!await assertAdminChatsAccess(req, res)) {
|
|
@@ -438,8 +661,8 @@ function registerRoutes(app, router, middleware) {
|
|
|
438
661
|
return res.status(400).json({ status: { code: 'bad-request', message: 'Invalid room id' } });
|
|
439
662
|
}
|
|
440
663
|
|
|
441
|
-
if (!await User.isAdministrator(actingUid)) {
|
|
442
|
-
return res.status(403).json({ status: { code: 'forbidden', message: 'Admin
|
|
664
|
+
if (!await User.isAdministrator(actingUid) && !await canManageAdminChats(actingUid)) {
|
|
665
|
+
return res.status(403).json({ status: { code: 'forbidden', message: 'Admin or manage privilege required' } });
|
|
443
666
|
}
|
|
444
667
|
|
|
445
668
|
if (!await roomExists(roomId)) {
|
|
@@ -480,7 +703,7 @@ async function renderAdminChatsPage(req, res, next) {
|
|
|
480
703
|
}
|
|
481
704
|
|
|
482
705
|
async function assertAdminChatsAccess(req, res) {
|
|
483
|
-
if (!req.uid || !await
|
|
706
|
+
if (!req.uid || !await canAccessAdminChats(req.uid)) {
|
|
484
707
|
helpers.notAllowed(req, res);
|
|
485
708
|
return false;
|
|
486
709
|
}
|
|
@@ -490,6 +713,13 @@ async function assertAdminChatsAccess(req, res) {
|
|
|
490
713
|
|
|
491
714
|
async function buildAdminChatsPayload(req) {
|
|
492
715
|
const userslug = await User.getUserField(req.uid, 'userslug');
|
|
716
|
+
const [isAdmin, canView, canManage] = await Promise.all([
|
|
717
|
+
User.isAdministrator(req.uid),
|
|
718
|
+
privileges.global.can(ADMIN_CHATS_PRIVILEGE, req.uid),
|
|
719
|
+
privileges.global.can(ADMIN_CHATS_MANAGE_PRIVILEGE, req.uid),
|
|
720
|
+
]);
|
|
721
|
+
const adminChatsManage = isAdmin || canManage;
|
|
722
|
+
const adminChatsAccess = isAdmin || canView || canManage;
|
|
493
723
|
const [recentChats, publicRooms, privateRoomCount] = await Promise.all([
|
|
494
724
|
getAdminRecentChats(req.uid, 0, ADMIN_CHAT_PAGE_SIZE),
|
|
495
725
|
Messaging.getPublicRooms(req.uid, req.uid),
|
|
@@ -501,6 +731,9 @@ async function buildAdminChatsPayload(req) {
|
|
|
501
731
|
uid: req.uid,
|
|
502
732
|
userslug,
|
|
503
733
|
adminAllChats: true,
|
|
734
|
+
adminChatsAccess,
|
|
735
|
+
adminChatsIsAdmin: isAdmin,
|
|
736
|
+
adminChatsCanManage: adminChatsManage,
|
|
504
737
|
rooms: recentChats.rooms,
|
|
505
738
|
nextStart: recentChats.nextStart,
|
|
506
739
|
publicRooms: publicRooms || [],
|
|
@@ -697,6 +930,10 @@ async function canNonAdminModifyLockedRoom(uid, roomId) {
|
|
|
697
930
|
return false;
|
|
698
931
|
}
|
|
699
932
|
|
|
933
|
+
if (await canManageAdminChats(uid)) {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
|
|
700
937
|
return await isRoomLocked(roomId);
|
|
701
938
|
}
|
|
702
939
|
async function roomExists(roomId) {
|
|
@@ -833,4 +1070,3 @@ function getRoomId(payload) {
|
|
|
833
1070
|
}
|
|
834
1071
|
|
|
835
1072
|
module.exports = plugin;
|
|
836
|
-
|
package/package.json
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-admin-chats",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A plugin that allows the administrator to view and manage user chats.",
|
|
5
5
|
"main": "library.js",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"lodash": "^4.17.0"
|
|
8
|
-
},
|
|
9
6
|
"nbbpm": {
|
|
10
|
-
"compatibility": "^
|
|
7
|
+
"compatibility": "^4.0.0"
|
|
11
8
|
}
|
|
12
9
|
}
|
package/plugin.json
CHANGED
|
@@ -63,6 +63,19 @@
|
|
|
63
63
|
{
|
|
64
64
|
"hook": "filter:scripts.get",
|
|
65
65
|
"method": "addScripts"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"hook": "filter:notifications.create",
|
|
69
|
+
"method": "filterNotificationCreate"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"hook": "filter:user.notifications.getNotifications",
|
|
73
|
+
"method": "filterUserNotificationsGetNotifications"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"hook": "static:privileges.global.init",
|
|
77
|
+
"method": "registerPrivileges"
|
|
66
78
|
}
|
|
67
|
-
]
|
|
79
|
+
],
|
|
80
|
+
"languages": "public/languages"
|
|
68
81
|
}
|
|
@@ -5,5 +5,7 @@
|
|
|
5
5
|
"errors.update": "Unable to update room lock.",
|
|
6
6
|
"profile.viewChats": "View Chats",
|
|
7
7
|
"empty.selectChat": "Please select a chat from the sidebar.",
|
|
8
|
-
"errors.lockedAction": "This action cannot be performed in a locked room."
|
|
8
|
+
"errors.lockedAction": "This action cannot be performed in a locked room.",
|
|
9
|
+
"admin-chats-view": "View all chats",
|
|
10
|
+
"admin-chats-manage": "Manage chat rooms"
|
|
9
11
|
}
|
|
@@ -5,5 +5,7 @@
|
|
|
5
5
|
"errors.update": "לא ניתן לעדכן את נעילת החדר.",
|
|
6
6
|
"profile.viewChats": "צפיה בצ'אטים",
|
|
7
7
|
"empty.selectChat": "אנא בחר צ'אט מסרגל הצד.",
|
|
8
|
-
"errors.lockedAction": "לא ניתן לבצע פעולה זו בחדר נעול"
|
|
8
|
+
"errors.lockedAction": "לא ניתן לבצע פעולה זו בחדר נעול",
|
|
9
|
+
"admin-chats-view": "צפיה בכל הצ'אטים",
|
|
10
|
+
"admin-chats-manage": "ניהול חדרי צ'אט"
|
|
9
11
|
}
|