nodebb-plugin-admin-chats 1.1.0 → 2.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/library.js CHANGED
@@ -1,372 +1,450 @@
1
- 'use strict';
2
-
3
- const User = require.main.require('./src/user');
4
- const Messaging = require.main.require('./src/messaging');
5
- const db = require.main.require('./src/database');
6
-
7
- const plugin = {};
8
- const LOCK_PREFIX = '[admin-chat-lock]';
9
- const LOCKED_MESSAGE_TEXT = '🔒 חדר זה ננעל ע"י המנהלים.';
10
- const UNLOCKED_MESSAGE_TEXT = '🔓 חדר זה נפתח מחדש ע"י המנהלים.';
11
-
12
- plugin.init = async function (params) {
13
- registerRoutes(params.router, params.middleware);
14
- overrideMessagingFunctions();
15
- };
16
-
17
- plugin.addProfileLink = async function (data) {
18
- try {
19
- const userSlug = data.user ? data.user.userslug : (data.userData ? data.userData.userslug : null);
20
-
21
- if (userSlug) {
22
- data.links.push({
23
- id: 'admin-view-chats',
24
- route: 'user/' + userSlug + '/chats',
25
- icon: 'fa-comments',
26
- name: 'צפה בצ\'אטים',
27
- visibility: {
28
- self: false,
29
- other: true,
30
- moderator: false,
31
- globalMod: false,
32
- admin: true,
33
- }
34
- });
35
- }
36
- } catch (e) {
37
- console.error('[Super Admin Chats] Error adding profile link:', e);
38
- }
39
- return data;
40
- };
41
-
42
- plugin.isRoomOwner = async function (payload) {
43
- const isAdmin = await User.isAdministrator(payload.uid);
44
- if (isAdmin) {
45
- payload.isOwner = true;
46
- }
47
- return payload;
48
- };
49
-
50
- plugin.isUserInRoom = async function (payload) {
51
- const isAdmin = await User.isAdministrator(payload.uid);
52
- if (isAdmin) {
53
- payload.inRoom = true;
54
- }
55
- return payload;
56
- };
57
-
58
- plugin.canReply = async function (payload) {
59
- const uid = getCallerUid(payload);
60
- const roomId = getRoomId(payload);
61
- const isAdmin = await User.isAdministrator(uid);
62
-
63
- if (isAdmin) {
64
- payload.canReply = true;
65
- return payload;
66
- }
67
-
68
- if (roomId && await isRoomLocked(roomId)) {
69
- payload.canReply = false;
70
- }
71
-
72
- return payload;
73
- };
74
-
75
- plugin.canGetMessages = async function (payload) {
76
- const isAdmin = await User.isAdministrator(payload.callerUid);
77
- if (isAdmin) {
78
- payload.canGet = true;
79
- }
80
- return payload;
81
- };
82
-
83
- plugin.canGetRecentChats = async function (payload) {
84
- const isAdmin = await User.isAdministrator(payload.callerUid);
85
- if (isAdmin) {
86
- payload.canGet = true;
87
- }
88
- return payload;
89
- };
90
-
91
- plugin.canGetPublicChats = async function (payload) {
92
- const isAdmin = await User.isAdministrator(payload.callerUid);
93
- if (isAdmin) {
94
- payload.canGet = true;
95
- }
96
- return payload;
97
- };
98
-
99
- plugin.onLoadRoom = async function (payload) {
100
- const { uid, room } = payload;
101
- if (!room) {
102
- return payload;
103
- }
104
-
105
- const isAdmin = await User.isAdministrator(uid);
106
- const lockData = await getRoomLockData(room.roomId);
107
- room.adminChatLock = lockData;
108
-
109
- if (lockData.isLocked) {
110
- room.messages = appendLockNotice(room.messages, lockData);
111
- }
112
-
113
- if (!isAdmin) {
114
- return payload;
115
- }
116
-
117
- const isOfficialMember = await db.isSortedSetMember(`chat:room:${room.roomId}:uids`, uid);
118
-
119
- if (!isOfficialMember) {
120
- await db.sortedSetRemove(`chat:room:${room.roomId}:uids:online`, uid);
121
-
122
- if (Array.isArray(room.users)) {
123
- room.users = room.users.filter(user => user && parseInt(user.uid, 10) !== parseInt(uid, 10));
124
- }
125
- if (room.userCount > 0) {
126
- room.userCount -= 1;
127
- }
128
- room.groupChat = room.userCount > 2;
129
-
130
- const allMids = await db.getSortedSetRevRange(`chat:room:${room.roomId}:mids`, 0, 49);
131
-
132
- if (allMids.length > 0) {
133
- const messages = await Messaging.getMessagesData(allMids, uid, room.roomId, false);
134
- const messageCount = await db.getObjectField(`chat:room:${room.roomId}`, 'messageCount');
135
- const count = parseInt(messageCount, 10) || 0;
136
-
137
- messages.forEach((msg, index) => {
138
- msg.index = count - index - 1;
139
- });
140
-
141
- room.messages = appendLockNotice(messages.reverse(), lockData);
142
-
143
- room.messages.forEach((msg, index) => {
144
- if (index === 0) {
145
- msg.newSet = true;
146
- } else {
147
- const prevMsg = room.messages[index - 1];
148
- msg.newSet = parseInt(msg.fromuid, 10) !== parseInt(prevMsg.fromuid, 10);
149
- }
150
- });
151
- }
152
-
153
- room.isAdmin = true;
154
- room.isOwner = true;
155
- } else {
156
- room.isAdmin = true;
157
- }
158
- return payload;
159
- };
160
-
161
- function overrideMessagingFunctions() {
162
- const originalCanEdit = Messaging.canEdit;
163
- const originalCanDelete = Messaging.canDelete;
164
- const originalCanViewMessage = Messaging.canViewMessage;
165
- const originalCanReply = Messaging.canReply;
166
-
167
- Messaging.canEdit = async function (messageId, uid) {
168
- if (await User.isAdministrator(uid)) {
169
- return true;
170
- }
171
- return await originalCanEdit(messageId, uid);
172
- };
173
-
174
- Messaging.canDelete = async function (messageId, uid) {
175
- if (await User.isAdministrator(uid)) {
176
- return true;
177
- }
178
- return await originalCanDelete(messageId, uid);
179
- };
180
-
181
- Messaging.canViewMessage = async function (mids, roomId, uid) {
182
- if (await User.isAdministrator(uid)) {
183
- return Array.isArray(mids) ? mids.map(() => true) : true;
184
- }
185
- return await originalCanViewMessage(mids, roomId, uid);
186
- };
187
-
188
- if (typeof originalCanReply === 'function') {
189
- Messaging.canReply = async function (...args) {
190
- const guessed = guessRoomIdUidFromArgs(args);
191
- if (await User.isAdministrator(guessed.uid)) {
192
- return true;
193
- }
194
- if (guessed.roomId && await isRoomLocked(guessed.roomId)) {
195
- return false;
196
- }
197
- return await originalCanReply.apply(this, args);
198
- };
199
- }
200
- };
201
-
202
- function registerRoutes(router, middleware) {
203
- if (!router) {
204
- return;
205
- }
206
-
207
- const routeMiddleware = [];
208
- if (middleware && typeof middleware.ensureLoggedIn === 'function') {
209
- routeMiddleware.push(middleware.ensureLoggedIn);
210
- }
211
-
212
- router.post('/api/admin-chats/:roomId/lock', ...routeMiddleware, async (req, res) => {
213
- try {
214
- const actingUid = req.uid;
215
- const roomId = parseInt(req.params.roomId, 10);
216
-
217
- if (!actingUid || Number.isNaN(roomId)) {
218
- return res.status(400).json({ status: { code: 'bad-request', message: 'Invalid room id' } });
219
- }
220
-
221
- if (!await User.isAdministrator(actingUid)) {
222
- return res.status(403).json({ status: { code: 'forbidden', message: 'Admin only' } });
223
- }
224
-
225
- if (!await roomExists(roomId)) {
226
- return res.status(404).json({ status: { code: 'not-found', message: 'Room not found' } });
227
- }
228
-
229
- const shouldLock = req.body && typeof req.body.locked === 'boolean' ? req.body.locked : true;
230
- const lockData = shouldLock ?
231
- await lockRoom(roomId, actingUid) :
232
- await unlockRoom(roomId, actingUid);
233
-
234
- return res.json({
235
- status: { code: 'ok', message: 'Room updated' },
236
- roomId,
237
- lockData,
238
- });
239
- } catch (err) {
240
- console.error('[Super Admin Chats] Error updating room lock:', err);
241
- return res.status(500).json({ status: { code: 'error', message: err.message } });
242
- }
243
- });
244
- }
245
-
246
- async function roomExists(roomId) {
247
- const roomData = await db.getObject(`chat:room:${roomId}`);
248
- return !!roomData;
249
- }
250
-
251
- async function lockRoom(roomId, actingUid) {
252
- const now = Date.now();
253
- await db.setObject(`chat:room:${roomId}:adminLock`, {
254
- isLocked: 1,
255
- lockedBy: actingUid,
256
- lockedAt: now,
257
- });
258
-
259
- await addLockSystemMessage(roomId, actingUid, true);
260
- return await getRoomLockData(roomId);
261
- }
262
-
263
- async function unlockRoom(roomId, actingUid) {
264
- await db.setObject(`chat:room:${roomId}:adminLock`, {
265
- isLocked: 0,
266
- lockedBy: actingUid,
267
- lockedAt: 0,
268
- });
269
-
270
- await addLockSystemMessage(roomId, actingUid, false);
271
- return await getRoomLockData(roomId);
272
- }
273
-
274
- async function addLockSystemMessage(roomId, actingUid, isLocked) {
275
- if (typeof Messaging.addSystemMessage !== 'function') {
276
- return;
277
- }
278
-
279
- const messageText = isLocked ? LOCK_PREFIX + ' ' + LOCKED_MESSAGE_TEXT : LOCK_PREFIX + ' ' + UNLOCKED_MESSAGE_TEXT;
280
-
281
- try {
282
- await Messaging.addSystemMessage(messageText, actingUid, roomId);
283
- } catch (err) {
284
- console.error('[Super Admin Chats] Error adding lock system message:', err);
285
- }
286
- }
287
-
288
- async function isRoomLocked(roomId) {
289
- const field = await db.getObjectField(`chat:room:${roomId}:adminLock`, 'isLocked');
290
- return field === '1' || field === 1 || field === true;
291
- }
292
-
293
- async function getRoomLockData(roomId) {
294
- const data = await db.getObject(`chat:room:${roomId}:adminLock`) || {};
295
- const lockedBy = parseInt(data.lockedBy, 10) || 0;
296
- const lockedAt = parseInt(data.lockedAt, 10) || 0;
297
- const isLockedValue = data.isLocked === '1' || data.isLocked === 1 || data.isLocked === true;
298
-
299
- return {
300
- isLocked: isLockedValue,
301
- lockedBy,
302
- lockedAt,
303
- };
304
- }
305
-
306
- function appendLockNotice(messages, lockData) {
307
- const list = Array.isArray(messages) ? messages.slice() : [];
308
- if (!lockData || !lockData.isLocked || !lockData.lockedAt) {
309
- return list;
310
- }
311
-
312
- const content = `${LOCK_PREFIX} ${LOCKED_MESSAGE_TEXT}`;
313
- const exists = list.some(msg => msg && msg.content === content);
314
- if (exists) {
315
- return list;
316
- }
317
-
318
- list.push({
319
- content,
320
- fromuid: lockData.lockedBy,
321
- uid: lockData.lockedBy,
322
- timestamp: lockData.lockedAt,
323
- datetime: lockData.lockedAt,
324
- system: true,
325
- newSet: true,
326
- });
327
-
328
- list.sort((left, right) => {
329
- const leftTime = parseInt(left && (left.timestamp || left.datetime), 10) || 0;
330
- const rightTime = parseInt(right && (right.timestamp || right.datetime), 10) || 0;
331
- return leftTime - rightTime;
332
- });
333
-
334
- return list;
335
- }
336
-
337
- function guessRoomIdUidFromArgs(args) {
338
- if (!Array.isArray(args) || !args.length) {
339
- return { roomId: null, uid: null };
340
- }
341
-
342
- if (args.length === 1 && args[0] && typeof args[0] === 'object') {
343
- return {
344
- roomId: getRoomId(args[0]),
345
- uid: getCallerUid(args[0]),
346
- };
347
- }
348
-
349
- return {
350
- roomId: args[0],
351
- uid: args[1],
352
- };
353
- }
354
-
355
- function getCallerUid(payload) {
356
- return payload && (
357
- payload.uid ||
358
- payload.callerUid ||
359
- (payload.data && payload.data.uid) ||
360
- (payload.message && payload.message.uid)
361
- );
362
- }
363
-
364
- function getRoomId(payload) {
365
- return payload && (
366
- payload.roomId ||
367
- (payload.data && payload.data.roomId) ||
368
- (payload.message && payload.message.roomId)
369
- );
370
- }
371
-
372
- module.exports = plugin;
1
+ 'use strict';
2
+
3
+ const User = require.main.require('./src/user');
4
+ const Messaging = require.main.require('./src/messaging');
5
+ const ChatsAPI = require.main.require('./src/api/chats');
6
+ const db = require.main.require('./src/database');
7
+
8
+ const plugin = {};
9
+ const LOCK_PREFIX = '[admin-chat-lock]';
10
+ const LOCKED_MESSAGE_TEXT = '🔒 חדר זה ננעל ע"י המנהלים.';
11
+
12
+ plugin.init = async function (params) {
13
+ registerRoutes(params.router, params.middleware);
14
+ overrideMessagingFunctions();
15
+ overrideChatsApi();
16
+ };
17
+
18
+ plugin.addProfileLink = async function (data) {
19
+ try {
20
+ const userSlug = data.user ? data.user.userslug : (data.userData ? data.userData.userslug : null);
21
+
22
+ if (userSlug) {
23
+ data.links.push({
24
+ id: 'admin-view-chats',
25
+ route: 'user/' + userSlug + '/chats',
26
+ icon: 'fa-comments',
27
+ name: 'צפה בצ\'אטים',
28
+ visibility: {
29
+ self: false,
30
+ other: true,
31
+ moderator: false,
32
+ globalMod: false,
33
+ admin: true,
34
+ }
35
+ });
36
+ }
37
+ } catch (e) {
38
+ console.error('[Super Admin Chats] Error adding profile link:', e);
39
+ }
40
+ return data;
41
+ };
42
+
43
+ plugin.isRoomOwner = async function (payload) {
44
+ const isAdmin = await User.isAdministrator(payload.uid);
45
+ if (isAdmin) {
46
+ payload.isOwner = true;
47
+ }
48
+ return payload;
49
+ };
50
+
51
+ plugin.isUserInRoom = async function (payload) {
52
+ const isAdmin = await User.isAdministrator(payload.uid);
53
+ if (isAdmin) {
54
+ payload.inRoom = true;
55
+ }
56
+ return payload;
57
+ };
58
+
59
+ plugin.canReply = async function (payload) {
60
+ const uid = getCallerUid(payload);
61
+ const roomId = getRoomId(payload);
62
+ const isAdmin = await User.isAdministrator(uid);
63
+
64
+ if (isAdmin) {
65
+ payload.canReply = true;
66
+ return payload;
67
+ }
68
+
69
+ if (roomId && await isRoomLocked(roomId)) {
70
+ payload.canReply = false;
71
+ }
72
+
73
+ return payload;
74
+ };
75
+
76
+ plugin.filterMessagingSend = async function (payload) {
77
+ const uid = getCallerUid(payload);
78
+ const roomId = getRoomId(payload);
79
+
80
+ if (!uid || !roomId) {
81
+ return payload;
82
+ }
83
+
84
+ if (await User.isAdministrator(uid)) {
85
+ return payload;
86
+ }
87
+
88
+ if (await isRoomLocked(roomId)) {
89
+ throw new Error('[[admin-chats:errors.lockedAction]]');
90
+ }
91
+
92
+ return payload;
93
+ };
94
+ plugin.filterAddUsersToRoom = async function (payload) {
95
+ if (await canNonAdminModifyLockedRoom(payload.uid, payload.roomId)) {
96
+ throw new Error('[[admin-chats:errors.lockedAction]]');
97
+ }
98
+ return payload;
99
+ };
100
+
101
+ plugin.filterRemoveUsersFromRoom = async function (payload) {
102
+ if (await canNonAdminModifyLockedRoom(payload.uid, payload.roomId)) {
103
+ throw new Error('[[admin-chats:errors.lockedAction]]');
104
+ }
105
+ return payload;
106
+ };
107
+
108
+ plugin.filterRenameRoom = async function (payload) {
109
+ if (await canNonAdminModifyLockedRoom(payload.uid, payload.roomId)) {
110
+ throw new Error('[[admin-chats:errors.lockedAction]]');
111
+ }
112
+ return payload;
113
+ };
114
+
115
+ plugin.canGetMessages = async function (payload) {
116
+ const isAdmin = await User.isAdministrator(payload.callerUid);
117
+ if (isAdmin) {
118
+ payload.canGet = true;
119
+ }
120
+ return payload;
121
+ };
122
+
123
+ plugin.canGetRecentChats = async function (payload) {
124
+ const isAdmin = await User.isAdministrator(payload.callerUid);
125
+ if (isAdmin) {
126
+ payload.canGet = true;
127
+ }
128
+ return payload;
129
+ };
130
+
131
+ plugin.canGetPublicChats = async function (payload) {
132
+ const isAdmin = await User.isAdministrator(payload.callerUid);
133
+ if (isAdmin) {
134
+ payload.canGet = true;
135
+ }
136
+ return payload;
137
+ };
138
+
139
+ plugin.onLoadRoom = async function (payload) {
140
+ const { uid, room } = payload;
141
+ if (!room) {
142
+ return payload;
143
+ }
144
+
145
+ const isAdmin = await User.isAdministrator(uid);
146
+ const lockData = await getRoomLockData(room.roomId);
147
+ const isLockedForUser = lockData.isLocked && !isAdmin;
148
+
149
+ room.adminChatLock = lockData;
150
+ room.messages = buildRoomMessagesWithLockNotice(room.messages, lockData);
151
+ room.canReply = room.canReply && !isLockedForUser;
152
+ room.showUserInput = room.showUserInput && !isLockedForUser;
153
+
154
+ if (!isAdmin) {
155
+ return payload;
156
+ }
157
+
158
+ const isOfficialMember = await db.isSortedSetMember(`chat:room:${room.roomId}:uids`, uid);
159
+
160
+ if (!isOfficialMember) {
161
+ await db.sortedSetRemove(`chat:room:${room.roomId}:uids:online`, uid);
162
+
163
+ if (Array.isArray(room.users)) {
164
+ room.users = room.users.filter(user => user && parseInt(user.uid, 10) !== parseInt(uid, 10));
165
+ }
166
+ if (room.userCount > 0) {
167
+ room.userCount -= 1;
168
+ }
169
+ room.groupChat = room.userCount > 2;
170
+
171
+ const allMids = await db.getSortedSetRevRange(`chat:room:${room.roomId}:mids`, 0, 49);
172
+
173
+ if (allMids.length > 0) {
174
+ const messages = await Messaging.getMessagesData(allMids, uid, room.roomId, false);
175
+ const messageCount = await db.getObjectField(`chat:room:${room.roomId}`, 'messageCount');
176
+ const count = parseInt(messageCount, 10) || 0;
177
+
178
+ messages.forEach((msg, index) => {
179
+ msg.index = count - index - 1;
180
+ });
181
+
182
+ room.messages = buildRoomMessagesWithLockNotice(messages.reverse(), lockData);
183
+
184
+ room.messages.forEach((msg, index) => {
185
+ if (index === 0) {
186
+ msg.newSet = true;
187
+ } else {
188
+ const prevMsg = room.messages[index - 1];
189
+ msg.newSet = parseInt(msg.fromuid, 10) !== parseInt(prevMsg.fromuid, 10);
190
+ }
191
+ });
192
+ }
193
+
194
+ room.isAdmin = true;
195
+ room.isOwner = true;
196
+ } else {
197
+ room.isAdmin = true;
198
+ }
199
+ return payload;
200
+ };
201
+
202
+ function overrideChatsApi() {
203
+ if (!ChatsAPI || typeof ChatsAPI.kick !== 'function' || ChatsAPI.kick._adminChatLockWrapped) {
204
+ return;
205
+ }
206
+
207
+ const originalKick = ChatsAPI.kick;
208
+ const wrappedKick = async function (caller, data) {
209
+ 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)) {
210
+ throw new Error('[[admin-chats:errors.lockedAction]]');
211
+ }
212
+
213
+ return await originalKick.call(this, caller, data);
214
+ };
215
+
216
+ wrappedKick._adminChatLockWrapped = true;
217
+ ChatsAPI.kick = wrappedKick;
218
+ }
219
+ function overrideMessagingFunctions() {
220
+ const originalCanEdit = Messaging.canEdit;
221
+ const originalCanDelete = Messaging.canDelete;
222
+ const originalCanViewMessage = Messaging.canViewMessage;
223
+ const originalCanReply = Messaging.canReply;
224
+
225
+ Messaging.canEdit = async function (messageId, uid) {
226
+ if (await User.isAdministrator(uid)) {
227
+ return true;
228
+ }
229
+
230
+ const roomId = await getMessageRoomId(messageId);
231
+ if (roomId && await isRoomLocked(roomId)) {
232
+ throw new Error('[[admin-chats:errors.lockedAction]]');
233
+ }
234
+
235
+ return await originalCanEdit(messageId, uid);
236
+ };
237
+
238
+ Messaging.canDelete = async function (messageId, uid) {
239
+ if (await User.isAdministrator(uid)) {
240
+ return true;
241
+ }
242
+
243
+ const roomId = await getMessageRoomId(messageId);
244
+ if (roomId && await isRoomLocked(roomId)) {
245
+ throw new Error('[[admin-chats:errors.lockedAction]]');
246
+ }
247
+
248
+ return await originalCanDelete(messageId, uid);
249
+ };
250
+
251
+ Messaging.canViewMessage = async function (mids, roomId, uid) {
252
+ if (await User.isAdministrator(uid)) {
253
+ return Array.isArray(mids) ? mids.map(() => true) : true;
254
+ }
255
+ return await originalCanViewMessage(mids, roomId, uid);
256
+ };
257
+
258
+ if (typeof originalCanReply === 'function') {
259
+ Messaging.canReply = async function (...args) {
260
+ const guessed = guessRoomIdUidFromArgs(args);
261
+ if (await User.isAdministrator(guessed.uid)) {
262
+ return true;
263
+ }
264
+ if (guessed.roomId && await isRoomLocked(guessed.roomId)) {
265
+ return false;
266
+ }
267
+ return await originalCanReply.apply(this, args);
268
+ };
269
+ }
270
+ }
271
+
272
+ function registerRoutes(router, middleware) {
273
+ if (!router) {
274
+ return;
275
+ }
276
+
277
+ const routeMiddleware = [];
278
+ if (middleware && typeof middleware.ensureLoggedIn === 'function') {
279
+ routeMiddleware.push(middleware.ensureLoggedIn);
280
+ }
281
+
282
+ router.post('/api/admin-chats/:roomId/lock', ...routeMiddleware, async (req, res) => {
283
+ try {
284
+ const actingUid = req.uid;
285
+ const roomId = parseInt(req.params.roomId, 10);
286
+
287
+ if (!actingUid || Number.isNaN(roomId)) {
288
+ return res.status(400).json({ status: { code: 'bad-request', message: 'Invalid room id' } });
289
+ }
290
+
291
+ if (!await User.isAdministrator(actingUid)) {
292
+ return res.status(403).json({ status: { code: 'forbidden', message: 'Admin only' } });
293
+ }
294
+
295
+ if (!await roomExists(roomId)) {
296
+ return res.status(404).json({ status: { code: 'not-found', message: 'Room not found' } });
297
+ }
298
+
299
+ const shouldLock = req.body && typeof req.body.locked === 'boolean' ? req.body.locked : true;
300
+ const lockData = shouldLock ?
301
+ await lockRoom(roomId, actingUid) :
302
+ await unlockRoom(roomId, actingUid);
303
+
304
+ return res.json({
305
+ status: { code: 'ok', message: 'Room updated' },
306
+ roomId,
307
+ lockData,
308
+ });
309
+ } catch (err) {
310
+ console.error('[Super Admin Chats] Error updating room lock:', err);
311
+ return res.status(500).json({ status: { code: 'error', message: err.message } });
312
+ }
313
+ });
314
+ }
315
+
316
+ async function canNonAdminModifyLockedRoom(uid, roomId) {
317
+ if (!uid || !roomId) {
318
+ return false;
319
+ }
320
+
321
+ if (await User.isAdministrator(uid)) {
322
+ return false;
323
+ }
324
+
325
+ return await isRoomLocked(roomId);
326
+ }
327
+ async function roomExists(roomId) {
328
+ const roomData = await db.getObject(`chat:room:${roomId}`);
329
+ return !!roomData;
330
+ }
331
+
332
+ async function lockRoom(roomId, actingUid) {
333
+ const now = Date.now();
334
+ await db.setObject(`chat:room:${roomId}:adminLock`, {
335
+ isLocked: 1,
336
+ lockedBy: actingUid,
337
+ lockedAt: now,
338
+ });
339
+
340
+ return await getRoomLockData(roomId);
341
+ }
342
+
343
+ async function unlockRoom(roomId, actingUid) {
344
+ await db.setObject(`chat:room:${roomId}:adminLock`, {
345
+ isLocked: 0,
346
+ lockedBy: actingUid,
347
+ lockedAt: 0,
348
+ });
349
+
350
+ return await getRoomLockData(roomId);
351
+ }
352
+
353
+ async function isRoomLocked(roomId) {
354
+ const field = await db.getObjectField(`chat:room:${roomId}:adminLock`, 'isLocked');
355
+ return field === '1' || field === 1 || field === true;
356
+ }
357
+
358
+ async function getMessageRoomId(messageId) {
359
+ const roomId = await db.getObjectField(`message:${messageId}`, 'roomId');
360
+ return parseInt(roomId, 10) || 0;
361
+ }
362
+
363
+ async function getRoomLockData(roomId) {
364
+ const data = await db.getObject(`chat:room:${roomId}:adminLock`) || {};
365
+ const lockedBy = parseInt(data.lockedBy, 10) || 0;
366
+ const lockedAt = parseInt(data.lockedAt, 10) || 0;
367
+ const isLockedValue = data.isLocked === '1' || data.isLocked === 1 || data.isLocked === true;
368
+
369
+ return {
370
+ isLocked: isLockedValue,
371
+ lockedBy,
372
+ lockedAt,
373
+ };
374
+ }
375
+
376
+ function buildRoomMessagesWithLockNotice(messages, lockData) {
377
+ const cleanedMessages = stripAdminLockMessages(messages);
378
+ if (!lockData || !lockData.isLocked || !lockData.lockedAt) {
379
+ return cleanedMessages;
380
+ }
381
+
382
+ cleanedMessages.push({
383
+ content: `${LOCK_PREFIX} ${LOCKED_MESSAGE_TEXT}`,
384
+ fromuid: lockData.lockedBy,
385
+ uid: lockData.lockedBy,
386
+ timestamp: lockData.lockedAt,
387
+ datetime: lockData.lockedAt,
388
+ system: true,
389
+ newSet: true,
390
+ });
391
+
392
+ cleanedMessages.sort((left, right) => {
393
+ const leftTime = parseInt(left && (left.timestamp || left.datetime), 10) || 0;
394
+ const rightTime = parseInt(right && (right.timestamp || right.datetime), 10) || 0;
395
+ return leftTime - rightTime;
396
+ });
397
+
398
+ return cleanedMessages;
399
+ }
400
+
401
+ function stripAdminLockMessages(messages) {
402
+ const list = Array.isArray(messages) ? messages.slice() : [];
403
+ return list.filter(msg => !isAdminLockMessage(msg));
404
+ }
405
+
406
+ function isAdminLockMessage(msg) {
407
+ const content = String(msg && msg.content || '');
408
+ return content.includes('admin-chat-lock') ||
409
+ content.includes(LOCKED_MESSAGE_TEXT) ||
410
+ content.includes('נפתח מחדש ע"י המנהלים') ||
411
+ content.includes('modules:chat.system.[admin-chat-lock]');
412
+ }
413
+
414
+ function guessRoomIdUidFromArgs(args) {
415
+ if (!Array.isArray(args) || !args.length) {
416
+ return { roomId: null, uid: null };
417
+ }
418
+
419
+ if (args.length === 1 && args[0] && typeof args[0] === 'object') {
420
+ return {
421
+ roomId: getRoomId(args[0]),
422
+ uid: getCallerUid(args[0]),
423
+ };
424
+ }
425
+
426
+ return {
427
+ roomId: args[0],
428
+ uid: args[1],
429
+ };
430
+ }
431
+
432
+ function getCallerUid(payload) {
433
+ return payload && (
434
+ payload.uid ||
435
+ payload.callerUid ||
436
+ (payload.data && payload.data.uid) ||
437
+ (payload.message && payload.message.uid)
438
+ );
439
+ }
440
+
441
+ function getRoomId(payload) {
442
+ return payload && (
443
+ payload.roomId ||
444
+ (payload.data && payload.data.roomId) ||
445
+ (payload.message && payload.message.roomId)
446
+ );
447
+ }
448
+
449
+ module.exports = plugin;
450
+