diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 14bf5982c5..b294107955 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -1,5 +1,5 @@ "use strict"; -/* globals app, config, define, socket, templates, utils, ajaxify */ +/* globals app, define, socket, templates, utils, ajaxify */ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'translator'], function(components, taskbar, S, sounds, Chats, translator) { @@ -90,15 +90,16 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra module.loadChatsDropdown = function(chatsListEl) { var dropdownEl; - socket.emit('modules.chats.getRecentChats', {after: 0}, function(err, chats) { + socket.emit('modules.chats.getRecentChats', {after: 0}, function(err, data) { if (err) { return app.alertError(err.message); } - chats = chats.users; + + var rooms = data.rooms; chatsListEl.empty(); - if (!chats.length) { + if (!rooms.length) { translator.translate('[[modules:chat.no_active]]', function(str) { $('
  • ') .addClass('no_active') @@ -108,17 +109,23 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra return; } - chats.forEach(function(userObj) { - dropdownEl = $('
  • ') - .attr('data-uid', userObj.uid) - .html(''+ + rooms.forEach(function(roomObj) { + function createUserImage(userObj) { + return '' + (userObj.picture ? '' : '
    ' + userObj['icon:text'] + '
    ') + ' ' + - userObj.username + '
    ') + userObj.username + ''; + } + + dropdownEl = $('
  • ') + .attr('data-roomId', roomObj.roomId) .appendTo(chatsListEl); + roomObj.users.forEach(function(userObj) { + dropdownEl.append(createUserImage(userObj)); + }); dropdownEl.click(function() { if (!ajaxify.currentPage.match(/^chats\//)) { @@ -229,7 +236,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra chatModal.find('.modal-header').on('dblclick', gotoChats); chatModal.find('button[data-action="maximize"]').on('click', gotoChats); - chatModal.on('click', function(e) { + chatModal.on('click', function() { module.bringModalToTop(chatModal); if (!dragged) { diff --git a/src/messaging.js b/src/messaging.js index a3d6d58fd0..63b64beca2 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -1,27 +1,28 @@ 'use strict'; -var db = require('./database'), - async = require('async'), - nconf = require('nconf'), + +var async = require('async'), winston = require('winston'), S = require('string'), + nconf = require('nconf'), + db = require('./database'), user = require('./user'), plugins = require('./plugins'), meta = require('./meta'), + emailer = require('./emailer'), utils = require('../public/src/utils'), notifications = require('./notifications'), userNotifications = require('./user/notifications'), - emailer = require('./emailer'), sockets = require('./socket.io'); (function(Messaging) { - require('./create')(Messaging); - require('./delete')(Messaging); - require('./edit')(Messaging); - require('./rooms')(Messaging); - require('./unread')(Messaging); + require('./messaging/create')(Messaging); + require('./messaging/delete')(Messaging); + require('./messaging/edit')(Messaging); + require('./messaging/rooms')(Messaging); + require('./messaging/unread')(Messaging); Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser @@ -32,13 +33,9 @@ var db = require('./database'), threemonths: 7776000000 }; - function sortUids(fromuid, touid) { - return [fromuid, touid].sort(); - } - Messaging.getMessageField = function(mid, field, callback) { Messaging.getMessageFields(mid, [field], function(err, fields) { - callback(err, fields[field]); + callback(err, fields ? fields[field] : null); }); }; @@ -55,22 +52,21 @@ var db = require('./database'), }; Messaging.getMessages = function(params, callback) { - var fromuid = params.fromuid, - touid = params.touid, + var uid = params.uid, + roomId = params.roomId, since = params.since, isNew = params.isNew, count = params.count || parseInt(meta.config.chatMessageInboxSize, 10) || 250, markRead = params.markRead || true; - var uids = sortUids(fromuid, touid), - min = params.count ? 0 : Date.now() - (terms[since] || terms.day); + var min = params.count ? 0 : Date.now() - (terms[since] || terms.day); if (since === 'recent') { count = 49; min = 0; } - db.getSortedSetRevRangeByScore('messages:uid:' + uids[0] + ':to:' + uids[1], 0, count, '+inf', min, function(err, mids) { + db.getSortedSetRevRangeByScore('uid:' + uid + ':chat:room:' + roomId + ':mids', 0, count, '+inf', min, function(err, mids) { if (err) { return callback(err); } @@ -81,115 +77,122 @@ var db = require('./database'), mids.reverse(); - Messaging.getMessagesData(mids, fromuid, touid, isNew, callback); + Messaging.getMessagesData(mids, uid, roomId, isNew, callback); }); if (markRead) { - notifications.markRead('chat_' + touid + '_' + fromuid, fromuid, function(err) { + notifications.markRead('chat_' + roomId + '_' + uid, uid, function(err) { if (err) { winston.error('[messaging] Could not mark notifications related to this chat as read: ' + err.message); } - userNotifications.pushCount(fromuid); + userNotifications.pushCount(uid); }); } }; - Messaging.getMessagesData = function(mids, fromuid, touid, isNew, callback) { - user.getUsersFields([fromuid, touid], ['uid', 'username', 'userslug', 'picture', 'status'], function(err, userData) { - if(err) { - return callback(err); - } + Messaging.getMessagesData = function(mids, uid, roomId, isNew, callback) { - var keys = mids.map(function(mid) { - return 'message:' + mid; - }); + var keys = mids.map(function(mid) { + return 'message:' + mid; + }); - async.waterfall([ - async.apply(db.getObjects, keys), - function(messages, next) { - messages = messages.map(function(msg, idx) { - if (msg) { - msg.messageId = parseInt(mids[idx], 10); - } - return msg; - }).filter(Boolean); - async.map(messages, function(message, next) { - var self = parseInt(message.fromuid, 10) === parseInt(fromuid, 10); - message.fromUser = self ? userData[0] : userData[1]; - message.toUser = self ? userData[1] : userData[0]; - message.timestampISO = utils.toISOString(message.timestamp); - message.self = self ? 1 : 0; - message.newSet = false; - - if (message.hasOwnProperty('edited')) { - message.editedISO = new Date(parseInt(message.edited, 10)).toISOString(); - } + var messages; - Messaging.parse(message.content, message.fromuid, fromuid, userData[1], userData[0], isNew, function(result) { - message.content = result; - message.cleanedContent = S(result).stripTags().decodeHTMLEntities().s; - next(null, message); - }); - }, next); - }, - function(messages, next) { - if (messages.length > 1) { - // Add a spacer in between messages with time gaps between them - messages = messages.map(function(message, index) { - // Compare timestamps with the previous message, and check if a spacer needs to be added - if (index > 0 && parseInt(message.timestamp, 10) > parseInt(messages[index-1].timestamp, 10) + (1000*60*5)) { - // If it's been 5 minutes, this is a new set of messages - message.newSet = true; - } else if (index > 0 && message.fromuid !== messages[index-1].fromuid) { - // If the previous message was from the other person, this is also a new set - message.newSet = true; - } + async.waterfall([ + function (next) { + db.getObjects(keys, next); + }, + function (_messages, next) { + messages = _messages.map(function(msg, idx) { + if (msg) { + msg.messageId = parseInt(mids[idx], 10); + } + return msg; + }).filter(Boolean); - return message; - }); + var uids = messages.map(function(msg) { + return msg && msg.fromuid; + }); - next(undefined, messages); - } else { - // For single messages, we don't know the context, so look up the previous message and compare - var uids = [fromuid, touid].sort(function(a, b) { return a > b ? 1 : -1; }); - var key = 'messages:uid:' + uids[0] + ':to:' + uids[1]; - async.waterfall([ - async.apply(db.sortedSetRank, key, messages[0].messageId), - function(index, next) { - // Continue only if this isn't the first message in sorted set - if (index > 0) { - db.getSortedSetRange(key, index-1, index-1, next); - } else { - messages[0].newSet = true; - return next(undefined, messages); - } - }, - function(mid, next) { - Messaging.getMessageFields(mid, ['fromuid', 'timestamp'], next); - } - ], function(err, fields) { - if (err) { - return next(err); - } + user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], next); + }, + function (users, next) { + messages.forEach(function(message, index) { + message.fromUser = users[index]; + var self = parseInt(message.fromuid, 10) === parseInt(uid, 10); + message.self = self ? 1 : 0; + message.timestampISO = utils.toISOString(message.timestamp); + message.newSet = false; + if (message.hasOwnProperty('edited')) { + message.editedISO = new Date(parseInt(message.edited, 10)).toISOString(); + } + }); - if ( - (parseInt(messages[0].timestamp, 10) > parseInt(fields.timestamp, 10) + (1000*60*5)) || - (parseInt(messages[0].fromuid, 10) !== parseInt(fields.fromuid, 10)) - ) { - // If it's been 5 minutes, this is a new set of messages + async.map(messages, function(message, next) { + Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function(result) { + message.content = result; + message.cleanedContent = S(result).stripTags().decodeHTMLEntities().s; + next(null, message); + }); + }, next); + }, + function(messages, next) { + if (messages.length > 1) { + // Add a spacer in between messages with time gaps between them + messages = messages.map(function(message, index) { + // Compare timestamps with the previous message, and check if a spacer needs to be added + if (index > 0 && parseInt(message.timestamp, 10) > parseInt(messages[index-1].timestamp, 10) + (1000*60*5)) { + // If it's been 5 minutes, this is a new set of messages + message.newSet = true; + } else if (index > 0 && message.fromuid !== messages[index-1].fromuid) { + // If the previous message was from the other person, this is also a new set + message.newSet = true; + } + + return message; + }); + + next(undefined, messages); + } else { + // For single messages, we don't know the context, so look up the previous message and compare + var key = 'uid:' + uid + ':chat:room:' + roomId + ':mids'; + async.waterfall([ + async.apply(db.sortedSetRank, key, messages[0].messageId), + function(index, next) { + // Continue only if this isn't the first message in sorted set + if (index > 0) { + db.getSortedSetRange(key, index-1, index-1, next); + } else { messages[0].newSet = true; + return next(undefined, messages); } + }, + function(mid, next) { + Messaging.getMessageFields(mid, ['fromuid', 'timestamp'], next); + } + ], function(err, fields) { + if (err) { + return next(err); + } - next(undefined, messages); - }); - } + if ( + (parseInt(messages[0].timestamp, 10) > parseInt(fields.timestamp, 10) + (1000*60*5)) || + (parseInt(messages[0].fromuid, 10) !== parseInt(fields.fromuid, 10)) + ) { + // If it's been 5 minutes, this is a new set of messages + messages[0].newSet = true; + } + + next(undefined, messages); + }); } - ], callback); - }); + } + ], callback); + }; - Messaging.parse = function (message, fromuid, myuid, toUserData, myUserData, isNew, callback) { + Messaging.parse = function (message, fromuid, uid, roomId, isNew, callback) { plugins.fireHook('filter:parse.raw', message, function(err, parsed) { if (err) { return callback(message); @@ -199,9 +202,8 @@ var db = require('./database'), message: message, parsed: parsed, fromuid: fromuid, - myuid: myuid, - toUserData: toUserData, - myUserData: myUserData, + uid: uid, + roomId: roomId, isNew: isNew, parsedMessage: parsed }; @@ -212,15 +214,14 @@ var db = require('./database'), }); }; - Messaging.isNewSet = function(fromuid, touid, mid, callback) { - var uids = sortUids(fromuid, touid), - setKey = 'messages:uid:' + uids[0] + ':to:' + uids[1]; + Messaging.isNewSet = function(uid, roomId, mid, callback) { + var setKey = 'uid:' + uid + ':chat:room:' + roomId + ':mids'; async.waterfall([ async.apply(db.sortedSetRank, setKey, mid), function(index, next) { if (index > 0) { - db.getSortedSetRange(setKey, index-1, index, next); + db.getSortedSetRange(setKey, index - 1, index, next); } else { next(null, true); } @@ -244,23 +245,33 @@ var db = require('./database'), Messaging.getRecentChats = function(uid, start, stop, callback) { - db.getSortedSetRevRange('uid:' + uid + ':chats', start, stop, function(err, uids) { + db.getSortedSetRevRange('uid:' + uid + ':chat:rooms', start, stop, function(err, roomIds) { if (err) { return callback(err); } async.parallel({ unread: function(next) { - db.isSortedSetMembers('uid:' + uid + ':chats:unread', uids, next); + db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds, next); }, users: function(next) { - user.getUsersFields(uids, ['uid', 'username', 'picture', 'status', 'lastonline'] , next); + async.map(roomIds, function(roomId, next) { + db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 3, function(err, uids) { + if (err) { + return next(err); + } + uids = uids.filter(function(value, index, array) { + return value && parseInt(value, 10) !== parseInt(uid, 10); + }); + user.getUsersFields(uids, ['uid', 'username', 'picture', 'status', 'lastonline'] , next); + }); + }, next); }, teasers: function(next) { - async.map(uids, function(fromuid, next) { + async.map(roomIds, function(roomId, next) { Messaging.getMessages({ - fromuid: fromuid, - touid: uid, + uid: uid, + roomId: roomId, isNew: false, count: 1, markRead: false @@ -275,20 +286,25 @@ var db = require('./database'), if (err) { return callback(err); } - - results.users.forEach(function(userData, index) { - if (userData && parseInt(userData.uid, 10)) { - userData.unread = results.unread[index]; - userData.status = user.getStatus(userData); - userData.teaser = results.teasers[index]; - } - }); - - results.users = results.users.filter(function(user) { - return user && parseInt(user.uid, 10); + var rooms = results.users.map(function(users, index) { + var data = { + users: users, + unread: results.unread[index], + roomId: roomIds[index], + teaser: results.teasers[index] + }; + data.users.forEach(function(userData) { + if (userData && parseInt(userData.uid, 10)) { + userData.status = user.getStatus(userData); + } + }); + data.users = data.users.filter(function(user) { + return user && parseInt(user.uid, 10); + }); + return data; }); - callback(null, {users: results.users, nextStart: stop + 1}); + callback(null, {rooms: rooms, nextStart: stop + 1}); }); }); }; diff --git a/src/messaging/create.js b/src/messaging/create.js index b14b8d9868..9389ee6188 100644 --- a/src/messaging/create.js +++ b/src/messaging/create.js @@ -95,7 +95,7 @@ module.exports = function(Messaging) { ], next); }, function (results, next) { - getMessages([mid], fromuid, touid, true, next); + Messaging.getMessagesData([mid], fromuid, touid, true, next); }, function (messages, next) { Messaging.isNewSet(fromuid, touid, mid, next); @@ -119,7 +119,7 @@ module.exports = function(Messaging) { var keys = uids.map(function(uid) { return 'uid:' + uid + ':chat:rooms'; }); - db.sortedSetsAdd(keys, timestamp, roomId, next); + db.sortedSetsAdd(keys, timestamp, roomId, callback); }; Messaging.addMessageToUsers = function(roomId, uids, mid, timestamp, callback) { diff --git a/src/messaging/rooms.js b/src/messaging/rooms.js index e94a696b5c..9f13994b0e 100644 --- a/src/messaging/rooms.js +++ b/src/messaging/rooms.js @@ -43,4 +43,5 @@ module.exports = function(Messaging) { Messaging.getUidsInRoom = function(roomId, start, stop, callback) { db.getSortedSetRange('chat:room:' + roomId + ':uids', start, stop, callback); }; + }; \ No newline at end of file diff --git a/src/routes/debug.js b/src/routes/debug.js index b81938ccc9..0e4691b4d6 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -57,7 +57,12 @@ module.exports = function(app, middleware, controllers) { }); router.get('/test', function(req, res) { - res.redirect(404); + //res.redirect(404); + var messaging = require('../messaging'); + + messaging.getRecentChats(1, 0, 9, function(err, data) { + res.json(data); + }) }); app.use(nconf.get('relative_path') + '/debug', router); diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 701975dcce..599d57ec15 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -22,14 +22,13 @@ SocketModules.chats.get = function(socket, data, callback) { } Messaging.getMessages({ - fromuid: socket.uid, - touid: data.touid, + uid: socket.uid, + roomId: data.roomId, since: data.since, isNew: false }, callback); - // Mark chat as read - Messaging.markRead(socket.uid, data.touid); + Messaging.markRead(socket.uid, data.roomId); }; SocketModules.chats.getRaw = function(socket, data, callback) {