mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-28 17:46:16 +01:00
System chat messages (#7771)
* fix: removed duplicate checkContent call in addMessage addMessage is called in one place (sendMessage), and the checks are already contained there. addMessage is the lower level call and so should be called only from within core itself. * feat: #7330 chat system messages for join, leave, rename * fix: add back content checking in .addMessage(); * fix: tests, and added .addSystemMessage() method Tests were relying on message indices that changed due to the new system messages. * feat: add tests for system chat messages * refactor: rewrite half of src/messaging/rooms.js, fix tests * feat: #7743 messaging/room.js * fix: tests for messaging/room.js, #7743 * fix: trying to fix tests * fix: omg :rage2:
This commit is contained in:
@@ -35,6 +35,10 @@
|
|||||||
"chat.show-ip": "Show IP",
|
"chat.show-ip": "Show IP",
|
||||||
"chat.owner": "Room Owner",
|
"chat.owner": "Room Owner",
|
||||||
|
|
||||||
|
"chat.system.user-join": "%1 has joined the room",
|
||||||
|
"chat.system.user-leave": "%1 has left the room",
|
||||||
|
"chat.system.room-rename": "%2 has renamed this room: %1",
|
||||||
|
|
||||||
"composer.compose": "Compose",
|
"composer.compose": "Compose",
|
||||||
"composer.show_preview": "Show Preview",
|
"composer.show_preview": "Show Preview",
|
||||||
"composer.hide_preview": "Hide Preview",
|
"composer.hide_preview": "Hide Preview",
|
||||||
|
|||||||
@@ -94,11 +94,19 @@ define('forum/chats/messages', ['components', 'sounds', 'translator', 'benchpres
|
|||||||
|
|
||||||
|
|
||||||
messages.parseMessage = function (data, callback) {
|
messages.parseMessage = function (data, callback) {
|
||||||
Benchpress.parse('partials/chats/message' + (Array.isArray(data) ? 's' : ''), {
|
function done(html) {
|
||||||
messages: data,
|
|
||||||
}, function (html) {
|
|
||||||
translator.translate(html, callback);
|
translator.translate(html, callback);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
Benchpress.parse('partials/chats/message' + (Array.isArray(data) ? 's' : ''), {
|
||||||
|
messages: data,
|
||||||
|
}, done);
|
||||||
|
} else {
|
||||||
|
Benchpress.parse('partials/chats/' + (data.system ? 'system-message' : 'message'), {
|
||||||
|
messages: data,
|
||||||
|
}, done);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ module.exports = function (Messaging) {
|
|||||||
var mid;
|
var mid;
|
||||||
var message;
|
var message;
|
||||||
var isNewSet;
|
var isNewSet;
|
||||||
|
const timestamp = data.timestamp || new Date().getTime();
|
||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
function (next) {
|
function (next) {
|
||||||
@@ -65,10 +66,11 @@ module.exports = function (Messaging) {
|
|||||||
mid = _mid;
|
mid = _mid;
|
||||||
message = {
|
message = {
|
||||||
content: String(data.content),
|
content: String(data.content),
|
||||||
timestamp: data.timestamp,
|
timestamp: timestamp,
|
||||||
fromuid: data.uid,
|
fromuid: data.uid,
|
||||||
roomId: data.roomId,
|
roomId: data.roomId,
|
||||||
deleted: 0,
|
deleted: 0,
|
||||||
|
system: data.system || 0,
|
||||||
};
|
};
|
||||||
if (data.ip) {
|
if (data.ip) {
|
||||||
message.ip = data.ip;
|
message.ip = data.ip;
|
||||||
@@ -80,7 +82,7 @@ module.exports = function (Messaging) {
|
|||||||
db.setObject('message:' + mid, message, next);
|
db.setObject('message:' + mid, message, next);
|
||||||
},
|
},
|
||||||
function (next) {
|
function (next) {
|
||||||
Messaging.isNewSet(data.uid, data.roomId, data.timestamp, next);
|
Messaging.isNewSet(data.uid, data.roomId, timestamp, next);
|
||||||
},
|
},
|
||||||
function (_isNewSet, next) {
|
function (_isNewSet, next) {
|
||||||
isNewSet = _isNewSet;
|
isNewSet = _isNewSet;
|
||||||
@@ -91,8 +93,8 @@ module.exports = function (Messaging) {
|
|||||||
},
|
},
|
||||||
function (uids, next) {
|
function (uids, next) {
|
||||||
async.parallel([
|
async.parallel([
|
||||||
async.apply(Messaging.addRoomToUsers, data.roomId, uids, data.timestamp),
|
async.apply(Messaging.addRoomToUsers, data.roomId, uids, timestamp),
|
||||||
async.apply(Messaging.addMessageToUsers, data.roomId, uids, mid, data.timestamp),
|
async.apply(Messaging.addMessageToUsers, data.roomId, uids, mid, timestamp),
|
||||||
async.apply(Messaging.markUnread, uids, data.roomId),
|
async.apply(Messaging.markUnread, uids, data.roomId),
|
||||||
], next);
|
], next);
|
||||||
},
|
},
|
||||||
@@ -115,6 +117,16 @@ module.exports = function (Messaging) {
|
|||||||
], callback);
|
], callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Messaging.addSystemMessage = async (content, uid, roomId) => {
|
||||||
|
const message = await Messaging.addMessage({
|
||||||
|
content: content,
|
||||||
|
uid: uid,
|
||||||
|
roomId: roomId,
|
||||||
|
system: 1,
|
||||||
|
});
|
||||||
|
Messaging.notifyUsersInRoom(uid, roomId, message);
|
||||||
|
};
|
||||||
|
|
||||||
Messaging.addRoomToUsers = function (roomId, uids, timestamp, callback) {
|
Messaging.addRoomToUsers = function (roomId, uids, timestamp, callback) {
|
||||||
if (!uids.length) {
|
if (!uids.length) {
|
||||||
return callback();
|
return callback();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ var user = require('../user');
|
|||||||
var utils = require('../utils');
|
var utils = require('../utils');
|
||||||
var plugins = require('../plugins');
|
var plugins = require('../plugins');
|
||||||
|
|
||||||
const intFields = ['timestamp', 'edited', 'fromuid', 'roomId', 'deleted'];
|
const intFields = ['timestamp', 'edited', 'fromuid', 'roomId', 'deleted', 'system'];
|
||||||
|
|
||||||
module.exports = function (Messaging) {
|
module.exports = function (Messaging) {
|
||||||
Messaging.newMessageCutoff = 1000 * 60 * 3;
|
Messaging.newMessageCutoff = 1000 * 60 * 3;
|
||||||
@@ -86,9 +86,14 @@ module.exports = function (Messaging) {
|
|||||||
message.newSet = false;
|
message.newSet = false;
|
||||||
message.roomId = String(message.roomId || roomId);
|
message.roomId = String(message.roomId || roomId);
|
||||||
message.deleted = !!message.deleted;
|
message.deleted = !!message.deleted;
|
||||||
|
message.system = !!message.system;
|
||||||
});
|
});
|
||||||
|
|
||||||
async.map(messages, function (message, next) {
|
async.map(messages, function (message, next) {
|
||||||
|
if (message.system) {
|
||||||
|
return setImmediate(next, null, message);
|
||||||
|
}
|
||||||
|
|
||||||
Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function (err, result) {
|
Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function (err, result) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|||||||
@@ -259,11 +259,11 @@ Messaging.getLatestUndeletedMessage = function (uid, roomId, callback) {
|
|||||||
done = true;
|
done = true;
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
Messaging.getMessageField(mids[0], 'deleted', _next);
|
Messaging.getMessageFields(mids[0], ['deleted', 'system'], _next);
|
||||||
},
|
},
|
||||||
function (deleted, _next) {
|
function (states, _next) {
|
||||||
done = !deleted;
|
done = !states.deleted && !states.system;
|
||||||
if (!deleted) {
|
if (done) {
|
||||||
latestMid = mids[0];
|
latestMid = mids[0];
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var async = require('async');
|
|
||||||
var validator = require('validator');
|
var validator = require('validator');
|
||||||
|
|
||||||
var db = require('../database');
|
var db = require('../database');
|
||||||
@@ -10,34 +9,23 @@ var privileges = require('../privileges');
|
|||||||
var meta = require('../meta');
|
var meta = require('../meta');
|
||||||
|
|
||||||
module.exports = function (Messaging) {
|
module.exports = function (Messaging) {
|
||||||
Messaging.getRoomData = function (roomId, callback) {
|
Messaging.getRoomData = async (roomId) => {
|
||||||
async.waterfall([
|
const data = await db.getObject('chat:room:' + roomId);
|
||||||
function (next) {
|
if (!data) {
|
||||||
db.getObject('chat:room:' + roomId, next);
|
throw new Error('[[error:no-chat-room]]');
|
||||||
},
|
}
|
||||||
function (data, next) {
|
|
||||||
if (!data) {
|
modifyRoomData([data]);
|
||||||
return callback(new Error('[[error:no-chat-room]]'));
|
return data;
|
||||||
}
|
|
||||||
modifyRoomData([data]);
|
|
||||||
next(null, data);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Messaging.getRoomsData = function (roomIds, callback) {
|
Messaging.getRoomsData = async (roomIds) => {
|
||||||
var keys = roomIds.map(function (roomId) {
|
const roomData = await db.getObjects(roomIds.map(function (roomId) {
|
||||||
return 'chat:room:' + roomId;
|
return 'chat:room:' + roomId;
|
||||||
});
|
}));
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
modifyRoomData(roomData);
|
||||||
db.getObjects(keys, next);
|
return roomData;
|
||||||
},
|
|
||||||
function (roomData, next) {
|
|
||||||
modifyRoomData(roomData);
|
|
||||||
next(null, roomData);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function modifyRoomData(rooms) {
|
function modifyRoomData(rooms) {
|
||||||
@@ -52,284 +40,204 @@ module.exports = function (Messaging) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Messaging.newRoom = function (uid, toUids, callback) {
|
Messaging.newRoom = async (uid, toUids) => {
|
||||||
var roomId;
|
const now = Date.now();
|
||||||
var now = Date.now();
|
const roomId = await db.incrObjectField('global', 'nextChatRoomId');
|
||||||
async.waterfall([
|
const room = {
|
||||||
function (next) {
|
owner: uid,
|
||||||
db.incrObjectField('global', 'nextChatRoomId', next);
|
roomId: roomId,
|
||||||
},
|
};
|
||||||
function (_roomId, next) {
|
|
||||||
roomId = _roomId;
|
await Promise.all([
|
||||||
var room = {
|
db.setObject('chat:room:' + roomId, room),
|
||||||
owner: uid,
|
db.sortedSetAdd('chat:room:' + roomId + ':uids', now, uid),
|
||||||
roomId: roomId,
|
]);
|
||||||
};
|
await Promise.all([
|
||||||
db.setObject('chat:room:' + roomId, room, next);
|
Messaging.addUsersToRoom(uid, toUids, roomId),
|
||||||
},
|
Messaging.addRoomToUsers(roomId, [uid].concat(toUids), now),
|
||||||
function (next) {
|
]);
|
||||||
db.sortedSetAdd('chat:room:' + roomId + ':uids', now, uid, next);
|
|
||||||
},
|
return roomId;
|
||||||
function (next) {
|
|
||||||
Messaging.addUsersToRoom(uid, toUids, roomId, next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
Messaging.addRoomToUsers(roomId, [uid].concat(toUids), now, next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
next(null, roomId);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Messaging.isUserInRoom = function (uid, roomId, callback) {
|
Messaging.isUserInRoom = async (uid, roomId) => {
|
||||||
async.waterfall([
|
const inRoom = db.isSortedSetMember('chat:room:' + roomId + ':uids', uid);
|
||||||
function (next) {
|
const data = await plugins.fireHook('filter:messaging.isUserInRoom', { uid: uid, roomId: roomId, inRoom: inRoom });
|
||||||
db.isSortedSetMember('chat:room:' + roomId + ':uids', uid, next);
|
return data.inRoom;
|
||||||
},
|
|
||||||
function (inRoom, next) {
|
|
||||||
plugins.fireHook('filter:messaging.isUserInRoom', { uid: uid, roomId: roomId, inRoom: inRoom }, next);
|
|
||||||
},
|
|
||||||
function (data, next) {
|
|
||||||
next(null, data.inRoom);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Messaging.roomExists = function (roomId, callback) {
|
Messaging.roomExists = async roomId => db.exists('chat:room:' + roomId + ':uids');
|
||||||
db.exists('chat:room:' + roomId + ':uids', callback);
|
|
||||||
|
Messaging.getUserCountInRoom = async roomId => db.sortedSetCard('chat:room:' + roomId + ':uids');
|
||||||
|
|
||||||
|
Messaging.isRoomOwner = async (uid, roomId) => {
|
||||||
|
const owner = await db.getObjectField('chat:room:' + roomId, 'owner');
|
||||||
|
return parseInt(uid, 10) === parseInt(owner, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
Messaging.getUserCountInRoom = function (roomId, callback) {
|
Messaging.addUsersToRoom = async function (uid, uids, roomId) {
|
||||||
db.sortedSetCard('chat:room:' + roomId + ':uids', callback);
|
const now = Date.now();
|
||||||
|
const timestamps = uids.map(() => now);
|
||||||
|
const inRoom = await Messaging.isUserInRoom(uid, roomId);
|
||||||
|
if (!inRoom) {
|
||||||
|
throw new Error('[[error:cant-add-users-to-chat-room]]');
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.sortedSetAdd('chat:room:' + roomId + ':uids', timestamps, uids);
|
||||||
|
const [userCount, roomData] = await Promise.all([
|
||||||
|
db.sortedSetCard('chat:room:' + roomId + ':uids'),
|
||||||
|
db.getObject('chat:room:' + roomId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!roomData.hasOwnProperty('groupChat') && userCount > 2) {
|
||||||
|
await db.setObjectField('chat:room:' + roomId, 'groupChat', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(uids.map(uid => Messaging.addSystemMessage('user-join', uid, roomId)));
|
||||||
};
|
};
|
||||||
|
|
||||||
Messaging.isRoomOwner = function (uid, roomId, callback) {
|
Messaging.removeUsersFromRoom = async (uid, uids, roomId) => {
|
||||||
async.waterfall([
|
const [isOwner, userCount] = await Promise.all([
|
||||||
function (next) {
|
Messaging.isRoomOwner(uid, roomId),
|
||||||
db.getObjectField('chat:room:' + roomId, 'owner', next);
|
Messaging.getUserCountInRoom(roomId),
|
||||||
},
|
]);
|
||||||
function (owner, next) {
|
|
||||||
next(null, parseInt(uid, 10) === parseInt(owner, 10));
|
if (!isOwner) {
|
||||||
},
|
throw new Error('[[error:cant-remove-users-from-chat-room]]');
|
||||||
], callback);
|
}
|
||||||
|
if (userCount === 2) {
|
||||||
|
throw new Error('[[error:cant-remove-last-user]]');
|
||||||
|
}
|
||||||
|
|
||||||
|
await Messaging.leaveRoom(uids, roomId);
|
||||||
};
|
};
|
||||||
|
|
||||||
Messaging.addUsersToRoom = function (uid, uids, roomId, callback) {
|
Messaging.leaveRoom = async (uids, roomId) => {
|
||||||
async.waterfall([
|
const keys = uids
|
||||||
function (next) {
|
.map(function (uid) {
|
||||||
Messaging.isUserInRoom(uid, roomId, next);
|
return 'uid:' + uid + ':chat:rooms';
|
||||||
},
|
})
|
||||||
function (inRoom, next) {
|
.concat(uids.map(function (uid) {
|
||||||
if (!inRoom) {
|
return 'uid:' + uid + ':chat:rooms:unread';
|
||||||
return next(new Error('[[error:cant-add-users-to-chat-room]]'));
|
}));
|
||||||
}
|
|
||||||
const now = Date.now();
|
await Promise.all([
|
||||||
const timestamps = uids.map(() => now);
|
db.sortedSetRemove('chat:room:' + roomId + ':uids', uids),
|
||||||
db.sortedSetAdd('chat:room:' + roomId + ':uids', timestamps, uids, next);
|
db.sortedSetsRemove(keys, roomId),
|
||||||
},
|
]);
|
||||||
function (next) {
|
|
||||||
async.parallel({
|
await Promise.all(uids.map(uid => Messaging.addSystemMessage('user-leave', uid, roomId)));
|
||||||
userCount: async.apply(db.sortedSetCard, 'chat:room:' + roomId + ':uids'),
|
await updateOwner(roomId);
|
||||||
roomData: async.apply(db.getObject, 'chat:room:' + roomId),
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function (results, next) {
|
|
||||||
if (!results.roomData.hasOwnProperty('groupChat') && results.userCount > 2) {
|
|
||||||
return db.setObjectField('chat:room:' + roomId, 'groupChat', 1, next);
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Messaging.removeUsersFromRoom = function (uid, uids, roomId, callback) {
|
Messaging.leaveRooms = async (uid, roomIds) => {
|
||||||
async.waterfall([
|
const roomKeys = roomIds.map(roomId => 'chat:room:' + roomId + ':uids');
|
||||||
function (next) {
|
await Promise.all([
|
||||||
async.parallel({
|
db.sortedSetsRemove(roomKeys, uid),
|
||||||
isOwner: async.apply(Messaging.isRoomOwner, uid, roomId),
|
db.sortedSetRemove([
|
||||||
userCount: async.apply(Messaging.getUserCountInRoom, roomId),
|
'uid:' + uid + ':chat:rooms',
|
||||||
}, next);
|
'uid:' + uid + ':chat:rooms:unread',
|
||||||
},
|
], roomIds),
|
||||||
function (results, next) {
|
]);
|
||||||
if (!results.isOwner) {
|
|
||||||
return next(new Error('[[error:cant-remove-users-from-chat-room]]'));
|
await Promise.all(
|
||||||
}
|
roomIds.map(roomId => updateOwner(roomId))
|
||||||
if (results.userCount === 2) {
|
.concat(roomIds.map(roomId => Messaging.addSystemMessage('user-leave', uid, roomId)))
|
||||||
return next(new Error('[[error:cant-remove-last-user]]'));
|
);
|
||||||
}
|
|
||||||
Messaging.leaveRoom(uids, roomId, next);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Messaging.leaveRoom = function (uids, roomId, callback) {
|
async function updateOwner(roomId) {
|
||||||
async.waterfall([
|
const uids = await db.getSortedSetRange('chat:room:' + roomId + ':uids', 0, 0);
|
||||||
function (next) {
|
const newOwner = uids[0] || 0;
|
||||||
db.sortedSetRemove('chat:room:' + roomId + ':uids', uids, next);
|
await db.setObjectField('chat:room:' + roomId, 'owner', newOwner);
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
var keys = uids.map(function (uid) {
|
|
||||||
return 'uid:' + uid + ':chat:rooms';
|
|
||||||
});
|
|
||||||
keys = keys.concat(uids.map(function (uid) {
|
|
||||||
return 'uid:' + uid + ':chat:rooms:unread';
|
|
||||||
}));
|
|
||||||
db.sortedSetsRemove(keys, roomId, next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
updateOwner(roomId, next);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
Messaging.leaveRooms = function (uid, roomIds, callback) {
|
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
const roomKeys = roomIds.map(roomId => 'chat:room:' + roomId + ':uids');
|
|
||||||
db.sortedSetsRemove(roomKeys, uid, next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
db.sortedSetRemove([
|
|
||||||
'uid:' + uid + ':chat:rooms',
|
|
||||||
'uid:' + uid + ':chat:rooms:unread',
|
|
||||||
], roomIds, next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
async.eachSeries(roomIds, updateOwner, next);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateOwner(roomId, callback) {
|
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
db.getSortedSetRange('chat:room:' + roomId + ':uids', 0, 0, next);
|
|
||||||
},
|
|
||||||
function (uids, next) {
|
|
||||||
var newOwner = uids[0] || 0;
|
|
||||||
db.setObjectField('chat:room:' + roomId, 'owner', newOwner, next);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Messaging.getUidsInRoom = function (roomId, start, stop, callback) {
|
Messaging.getUidsInRoom = async (roomId, start, stop) => db.getSortedSetRevRange('chat:room:' + roomId + ':uids', start, stop);
|
||||||
db.getSortedSetRevRange('chat:room:' + roomId + ':uids', start, stop, callback);
|
|
||||||
|
Messaging.getUsersInRoom = async (roomId, start, stop) => {
|
||||||
|
const uids = await Messaging.getUidsInRoom(roomId, start, stop);
|
||||||
|
const [users, ownerId] = await Promise.all([
|
||||||
|
user.getUsersFields(uids, ['uid', 'username', 'picture', 'status']),
|
||||||
|
db.getObjectField('chat:room:' + roomId, 'owner'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return users.map(function (user) {
|
||||||
|
user.isOwner = parseInt(user.uid, 10) === parseInt(ownerId, 10);
|
||||||
|
return user;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Messaging.getUsersInRoom = function (roomId, start, stop, callback) {
|
Messaging.renameRoom = async function (uid, roomId, newName) {
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
Messaging.getUidsInRoom(roomId, start, stop, next);
|
|
||||||
},
|
|
||||||
function (uids, next) {
|
|
||||||
user.getUsersFields(uids, ['uid', 'username', 'picture', 'status'], next);
|
|
||||||
},
|
|
||||||
function (users, next) {
|
|
||||||
db.getObjectField('chat:room:' + roomId, 'owner', function (err, ownerId) {
|
|
||||||
next(err, users.map(function (user) {
|
|
||||||
user.isOwner = parseInt(user.uid, 10) === parseInt(ownerId, 10);
|
|
||||||
return user;
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
Messaging.renameRoom = function (uid, roomId, newName, callback) {
|
|
||||||
if (!newName) {
|
if (!newName) {
|
||||||
return callback(new Error('[[error:invalid-name]]'));
|
throw new Error('[[error:invalid-name]]');
|
||||||
}
|
}
|
||||||
newName = newName.trim();
|
newName = newName.trim();
|
||||||
if (newName.length > 75) {
|
if (newName.length > 75) {
|
||||||
return callback(new Error('[[error:chat-room-name-too-long]]'));
|
throw new Error('[[error:chat-room-name-too-long]]');
|
||||||
}
|
}
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
const payload = await plugins.fireHook('filter:chat.renameRoom', {
|
||||||
plugins.fireHook('filter:chat.renameRoom', {
|
uid: uid,
|
||||||
uid: uid,
|
roomId: roomId,
|
||||||
roomId: roomId,
|
newName: newName,
|
||||||
newName: newName,
|
});
|
||||||
}, next);
|
const isOwner = await Messaging.isRoomOwner(payload.uid, payload.roomId);
|
||||||
},
|
if (!isOwner) {
|
||||||
function (result, next) {
|
throw new Error('[[error:no-privileges]]');
|
||||||
Messaging.isRoomOwner(uid, roomId, next);
|
}
|
||||||
},
|
|
||||||
function (isOwner, next) {
|
await db.setObjectField('chat:room:' + payload.roomId, 'roomName', payload.newName);
|
||||||
if (!isOwner) {
|
await Messaging.addSystemMessage('room-rename, ' + payload.newName.replace(',', '%2C'), payload.uid, payload.roomId);
|
||||||
return next(new Error('[[error:no-privileges]]'));
|
|
||||||
}
|
plugins.fireHook('action:chat.renameRoom', {
|
||||||
db.setObjectField('chat:room:' + roomId, 'roomName', newName, next);
|
roomId: payload.roomId,
|
||||||
},
|
newName: payload.newName,
|
||||||
async.apply(plugins.fireHook, 'action:chat.renameRoom', {
|
});
|
||||||
roomId: roomId,
|
};
|
||||||
newName: newName,
|
|
||||||
|
Messaging.canReply = async (roomId, uid) => {
|
||||||
|
const inRoom = db.isSortedSetMember('chat:room:' + roomId + ':uids', uid);
|
||||||
|
const data = await plugins.fireHook('filter:messaging.canReply', { uid: uid, roomId: roomId, inRoom: inRoom, canReply: inRoom });
|
||||||
|
return data.canReply;
|
||||||
|
};
|
||||||
|
|
||||||
|
Messaging.loadRoom = async (uid, data) => {
|
||||||
|
const canChat = await privileges.global.can('chat', uid);
|
||||||
|
if (!canChat) {
|
||||||
|
throw new Error('[[error:no-privileges]]');
|
||||||
|
}
|
||||||
|
const inRoom = await Messaging.isUserInRoom(uid, data.roomId);
|
||||||
|
if (!inRoom) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [roomData, canReply, users, messages, isAdminOrGlobalMod] = await Promise.all([
|
||||||
|
Messaging.getRoomData(data.roomId),
|
||||||
|
Messaging.canReply(data.roomId, uid),
|
||||||
|
Messaging.getUsersInRoom(data.roomId, 0, -1),
|
||||||
|
Messaging.getMessages({
|
||||||
|
callerUid: uid,
|
||||||
|
uid: data.uid || uid,
|
||||||
|
roomId: data.roomId,
|
||||||
|
isNew: false,
|
||||||
}),
|
}),
|
||||||
], callback);
|
user.isAdminOrGlobalMod(uid),
|
||||||
};
|
]);
|
||||||
|
|
||||||
Messaging.canReply = function (roomId, uid, callback) {
|
var room = roomData;
|
||||||
async.waterfall([
|
room.messages = messages;
|
||||||
function (next) {
|
room.isOwner = parseInt(room.owner, 10) === parseInt(uid, 10);
|
||||||
db.isSortedSetMember('chat:room:' + roomId + ':uids', uid, next);
|
room.users = users.filter(function (user) {
|
||||||
},
|
return user && parseInt(user.uid, 10) && parseInt(user.uid, 10) !== uid;
|
||||||
function (inRoom, next) {
|
});
|
||||||
plugins.fireHook('filter:messaging.canReply', { uid: uid, roomId: roomId, inRoom: inRoom, canReply: inRoom }, next);
|
room.canReply = canReply;
|
||||||
},
|
room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : users.length > 2;
|
||||||
function (data, next) {
|
room.usernames = Messaging.generateUsernames(users, uid);
|
||||||
next(null, data.canReply);
|
room.maximumUsersInChatRoom = meta.config.maximumUsersInChatRoom;
|
||||||
},
|
room.maximumChatMessageLength = meta.config.maximumChatMessageLength;
|
||||||
], callback);
|
room.showUserInput = !room.maximumUsersInChatRoom || room.maximumUsersInChatRoom > 2;
|
||||||
};
|
room.isAdminOrGlobalMod = isAdminOrGlobalMod;
|
||||||
|
|
||||||
Messaging.loadRoom = function (uid, data, callback) {
|
return room;
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
privileges.global.can('chat', uid, next);
|
|
||||||
},
|
|
||||||
function (canChat, next) {
|
|
||||||
if (!canChat) {
|
|
||||||
return next(new Error('[[error:no-privileges]]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
Messaging.isUserInRoom(uid, data.roomId, next);
|
|
||||||
},
|
|
||||||
function (inRoom, next) {
|
|
||||||
if (!inRoom) {
|
|
||||||
return callback(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
async.parallel({
|
|
||||||
roomData: async.apply(Messaging.getRoomData, data.roomId),
|
|
||||||
canReply: async.apply(Messaging.canReply, data.roomId, uid),
|
|
||||||
users: async.apply(Messaging.getUsersInRoom, data.roomId, 0, -1),
|
|
||||||
messages: async.apply(Messaging.getMessages, {
|
|
||||||
callerUid: uid,
|
|
||||||
uid: data.uid || uid,
|
|
||||||
roomId: data.roomId,
|
|
||||||
isNew: false,
|
|
||||||
}),
|
|
||||||
isAdminOrGlobalMod: function (next) {
|
|
||||||
user.isAdminOrGlobalMod(uid, next);
|
|
||||||
},
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function (results, next) {
|
|
||||||
var room = results.roomData;
|
|
||||||
room.messages = results.messages;
|
|
||||||
room.isOwner = parseInt(room.owner, 10) === parseInt(uid, 10);
|
|
||||||
room.users = results.users.filter(function (user) {
|
|
||||||
return user && parseInt(user.uid, 10) && parseInt(user.uid, 10) !== uid;
|
|
||||||
});
|
|
||||||
room.canReply = results.canReply;
|
|
||||||
room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : results.users.length > 2;
|
|
||||||
room.usernames = Messaging.generateUsernames(results.users, uid);
|
|
||||||
room.maximumUsersInChatRoom = meta.config.maximumUsersInChatRoom;
|
|
||||||
room.maximumChatMessageLength = meta.config.maximumChatMessageLength;
|
|
||||||
room.showUserInput = !room.maximumUsersInChatRoom || room.maximumUsersInChatRoom > 2;
|
|
||||||
room.isAdminOrGlobalMod = results.isAdminOrGlobalMod;
|
|
||||||
next(null, room);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -113,6 +113,16 @@ describe('Messaging Library', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should send a user-join system message when a chat room is created', (done) => {
|
||||||
|
socketModules.chats.getMessages({ uid: fooUid }, { uid: fooUid, roomId: roomId, start: 0 }, function (err, messages) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(messages.length, 1);
|
||||||
|
assert.strictEqual(messages[0].system, true);
|
||||||
|
assert.strictEqual(messages[0].content, 'user-join');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should fail to add user to room with invalid data', function (done) {
|
it('should fail to add user to room with invalid data', function (done) {
|
||||||
socketModules.chats.addUserToRoom({ uid: fooUid }, null, function (err) {
|
socketModules.chats.addUserToRoom({ uid: fooUid }, null, function (err) {
|
||||||
assert.equal(err.message, '[[error:invalid-data]]');
|
assert.equal(err.message, '[[error:invalid-data]]');
|
||||||
@@ -185,6 +195,17 @@ describe('Messaging Library', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should send a user-leave system message when a user leaves the chat room', (done) => {
|
||||||
|
socketModules.chats.getMessages({ uid: fooUid }, { uid: fooUid, roomId: roomId, start: 0 }, function (err, messages) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(messages.length, 3);
|
||||||
|
const message = messages.pop();
|
||||||
|
assert.strictEqual(message.system, true);
|
||||||
|
assert.strictEqual(message.content, 'user-leave');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should change owner when owner leaves room', function (done) {
|
it('should change owner when owner leaves room', function (done) {
|
||||||
socketModules.chats.newRoom({ uid: herpUid }, { touid: fooUid }, function (err, roomId) {
|
socketModules.chats.newRoom({ uid: herpUid }, { touid: fooUid }, function (err, roomId) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
@@ -330,7 +351,8 @@ describe('Messaging Library', function () {
|
|||||||
myRoomId = _roomId;
|
myRoomId = _roomId;
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert(myRoomId);
|
assert(myRoomId);
|
||||||
socketModules.chats.getRaw({ uid: bazUid }, { mid: 1 }, function (err) {
|
socketModules.chats.getRaw({ uid: bazUid }, { mid: 2 }, function (err) {
|
||||||
|
assert(err);
|
||||||
assert.equal(err.message, '[[error:not-allowed]]');
|
assert.equal(err.message, '[[error:not-allowed]]');
|
||||||
socketModules.chats.send({ uid: bazUid }, { roomId: myRoomId, message: 'admin will see this' }, function (err, message) {
|
socketModules.chats.send({ uid: bazUid }, { roomId: myRoomId, message: 'admin will see this' }, function (err, message) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
@@ -392,8 +414,8 @@ describe('Messaging Library', function () {
|
|||||||
}, function (err, messages) {
|
}, function (err, messages) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert(Array.isArray(messages));
|
assert(Array.isArray(messages));
|
||||||
assert.equal(messages[0].roomId, roomId);
|
assert.equal(messages[4].roomId, roomId);
|
||||||
assert.equal(messages[0].fromuid, fooUid);
|
assert.equal(messages[4].fromuid, fooUid);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -449,6 +471,16 @@ describe('Messaging Library', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should send a room-rename system message when a room is renamed', (done) => {
|
||||||
|
socketModules.chats.getMessages({ uid: fooUid }, { uid: fooUid, roomId: roomId, start: 0 }, function (err, messages) {
|
||||||
|
assert.ifError(err);
|
||||||
|
const message = messages.pop();
|
||||||
|
assert.strictEqual(message.system, true);
|
||||||
|
assert.strictEqual(message.content, 'room-rename, new room name');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should fail to load room with invalid-data', function (done) {
|
it('should fail to load room with invalid-data', function (done) {
|
||||||
socketModules.chats.loadRoom({ uid: fooUid }, null, function (err) {
|
socketModules.chats.loadRoom({ uid: fooUid }, null, function (err) {
|
||||||
assert.equal(err.message, '[[error:invalid-data]]');
|
assert.equal(err.message, '[[error:invalid-data]]');
|
||||||
|
|||||||
Reference in New Issue
Block a user