'use strict'; var async = require('async'); var validator = require('validator'); var db = require('../database'); var user = require('../user'); var plugins = require('../plugins'); var privileges = require('../privileges'); var meta = require('../meta'); module.exports = function (Messaging) { Messaging.getRoomData = function (roomId, callback) { async.waterfall([ function (next) { db.getObject('chat:room:' + roomId, next); }, function (data, next) { if (!data) { return callback(new Error('[[error:no-chat-room]]')); } modifyRoomData([data]); next(null, data); }, ], callback); }; Messaging.getRoomsData = function (roomIds, callback) { var keys = roomIds.map(function (roomId) { return 'chat:room:' + roomId; }); async.waterfall([ function (next) { db.getObjects(keys, next); }, function (roomData, next) { modifyRoomData(roomData); next(null, roomData); }, ], callback); }; function modifyRoomData(rooms) { rooms.forEach(function (data) { if (data) { data.roomName = data.roomName || ''; data.roomName = validator.escape(String(data.roomName)); if (data.hasOwnProperty('groupChat')) { data.groupChat = parseInt(data.groupChat, 10) === 1; } } }); } Messaging.newRoom = function (uid, toUids, callback) { var roomId; var now = Date.now(); async.waterfall([ function (next) { db.incrObjectField('global', 'nextChatRoomId', next); }, function (_roomId, next) { roomId = _roomId; var room = { owner: uid, roomId: roomId, }; db.setObject('chat:room:' + roomId, room, next); }, function (next) { db.sortedSetAdd('chat:room:' + roomId + ':uids', now, uid, next); }, 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) { async.waterfall([ function (next) { db.isSortedSetMember('chat:room:' + roomId + ':uids', uid, next); }, 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) { db.exists('chat:room:' + roomId + ':uids', callback); }; Messaging.getUserCountInRoom = function (roomId, callback) { db.sortedSetCard('chat:room:' + roomId + ':uids', callback); }; Messaging.isRoomOwner = function (uid, roomId, callback) { async.waterfall([ function (next) { db.getObjectField('chat:room:' + roomId, 'owner', next); }, function (owner, next) { next(null, parseInt(uid, 10) === parseInt(owner, 10)); }, ], callback); }; Messaging.addUsersToRoom = function (uid, uids, roomId, callback) { async.waterfall([ function (next) { Messaging.isUserInRoom(uid, roomId, next); }, function (inRoom, next) { if (!inRoom) { return next(new Error('[[error:cant-add-users-to-chat-room]]')); } var now = Date.now(); var timestamps = uids.map(function () { return now; }); db.sortedSetAdd('chat:room:' + roomId + ':uids', timestamps, uids, next); }, function (next) { async.parallel({ userCount: async.apply(db.sortedSetCard, 'chat:room:' + roomId + ':uids'), 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) { async.waterfall([ function (next) { async.parallel({ isOwner: async.apply(Messaging.isRoomOwner, uid, roomId), userCount: async.apply(Messaging.getUserCountInRoom, roomId), }, next); }, function (results, next) { if (!results.isOwner) { return next(new Error('[[error:cant-remove-users-from-chat-room]]')); } if (results.userCount === 2) { return next(new Error('[[error:cant-remove-last-user]]')); } Messaging.leaveRoom(uids, roomId, next); }, ], callback); }; Messaging.leaveRoom = function (uids, roomId, callback) { async.waterfall([ function (next) { db.sortedSetRemove('chat:room:' + roomId + ':uids', uids, next); }, 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) { var roomKeys = roomIds.map(function (roomId) { return 'chat:room:' + roomId + ':uids'; }); db.sortedSetsRemove(roomKeys, uid, next); }, function (next) { db.sortedSetRemove('uid:' + uid + ':chat:rooms', roomIds, next); }, function (next) { db.sortedSetRemove('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) { db.getSortedSetRevRange('chat:room:' + roomId + ':uids', start, stop, callback); }; Messaging.getUsersInRoom = function (roomId, start, stop, callback) { 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) { return callback(new Error('[[error:invalid-name]]')); } newName = newName.trim(); if (newName.length > 75) { return callback(new Error('[[error:chat-room-name-too-long]]')); } async.waterfall([ function (next) { plugins.fireHook('filter:chat.renameRoom', { uid: uid, roomId: roomId, newName: newName, }, next); }, function (result, next) { Messaging.isRoomOwner(uid, roomId, next); }, function (isOwner, next) { if (!isOwner) { return next(new Error('[[error:no-privileges]]')); } db.setObjectField('chat:room:' + roomId, 'roomName', newName, next); }, async.apply(plugins.fireHook, 'action:chat.renameRoom', { roomId: roomId, newName: newName, }), ], callback); }; Messaging.canReply = function (roomId, uid, callback) { async.waterfall([ function (next) { db.isSortedSetMember('chat:room:' + roomId + ':uids', uid, next); }, function (inRoom, next) { plugins.fireHook('filter:messaging.canReply', { uid: uid, roomId: roomId, inRoom: inRoom, canReply: inRoom }, next); }, function (data, next) { next(null, data.canReply); }, ], callback); }; Messaging.loadRoom = function (uid, data, callback) { 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); }; };