From ba1889f441f1402aee3bcfdc8db962380ddd69a4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 3 Jan 2017 20:02:24 +0300 Subject: [PATCH] messaging refactor --- src/messaging.js | 723 +++++++++++++++++------------------------- src/messaging/data.js | 136 ++++++++ 2 files changed, 428 insertions(+), 431 deletions(-) create mode 100644 src/messaging/data.js diff --git a/src/messaging.js b/src/messaging.js index d5d43e1041..5bb668ae6e 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -2,7 +2,6 @@ var async = require('async'); -var winston = require('winston'); var S = require('string'); var db = require('./database'); @@ -10,471 +9,333 @@ var user = require('./user'); var plugins = require('./plugins'); var meta = require('./meta'); var utils = require('../public/src/utils'); -var notifications = require('./notifications'); -var userNotifications = require('./user/notifications'); -(function (Messaging) { - - require('./messaging/create')(Messaging); - require('./messaging/delete')(Messaging); - require('./messaging/edit')(Messaging); - require('./messaging/rooms')(Messaging); - require('./messaging/unread')(Messaging); - require('./messaging/notifications')(Messaging); - - Messaging.getMessageField = function (mid, field, callback) { - Messaging.getMessageFields(mid, [field], function (err, fields) { - callback(err, fields ? fields[field] : null); - }); - }; - - Messaging.getMessageFields = function (mid, fields, callback) { - db.getObjectFields('message:' + mid, fields, callback); - }; - - Messaging.setMessageField = function (mid, field, content, callback) { - db.setObjectField('message:' + mid, field, content, callback); - }; - - Messaging.setMessageFields = function (mid, data, callback) { - db.setObject('message:' + mid, data, callback); - }; - - Messaging.getMessages = function (params, callback) { - var uid = params.uid; - var roomId = params.roomId; - var isNew = params.isNew || false; - var start = params.hasOwnProperty('start') ? params.start : 0; - var stop = parseInt(start, 10) + ((params.count || 50) - 1); - - var indices = {}; - async.waterfall([ - function (next) { - canGetMessages(params.callerUid, params.uid, next); - }, - function (canGet, next) { - if (!canGet) { - return callback(null, null); - } - db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', start, stop, next); - }, - function (mids, next) { - if (!Array.isArray(mids) || !mids.length) { - return callback(null, []); - } +var Messaging = module.exports; + +require('./messaging/data')(Messaging); +require('./messaging/create')(Messaging); +require('./messaging/delete')(Messaging); +require('./messaging/edit')(Messaging); +require('./messaging/rooms')(Messaging); +require('./messaging/unread')(Messaging); +require('./messaging/notifications')(Messaging); + + +Messaging.getMessages = function (params, callback) { + var uid = params.uid; + var roomId = params.roomId; + var isNew = params.isNew || false; + var start = params.hasOwnProperty('start') ? params.start : 0; + var stop = parseInt(start, 10) + ((params.count || 50) - 1); + + var indices = {}; + async.waterfall([ + function (next) { + canGet('filter:messaging.canGetMessages', params.callerUid, params.uid, next); + }, + function (canGet, next) { + if (!canGet) { + return callback(null, null); + } + db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', start, stop, next); + }, + function (mids, next) { + if (!Array.isArray(mids) || !mids.length) { + return callback(null, []); + } - mids.forEach(function (mid, index) { - indices[mid] = start + index; - }); + mids.forEach(function (mid, index) { + indices[mid] = start + index; + }); - mids.reverse(); + mids.reverse(); - Messaging.getMessagesData(mids, uid, roomId, isNew, next); - }, - function (messageData, next) { - messageData.forEach(function (messageData) { - messageData.index = indices[messageData.messageId.toString()]; - }); - next(null, messageData); - } - ], callback); - }; + Messaging.getMessagesData(mids, uid, roomId, isNew, next); + }, + function (messageData, next) { + messageData.forEach(function (messageData) { + messageData.index = indices[messageData.messageId.toString()]; + }); + next(null, messageData); + } + ], callback); +}; + +function canGet(hook, callerUid, uid, callback) { + plugins.fireHook(hook, { + callerUid: callerUid, + uid: uid, + canGet: parseInt(callerUid, 10) === parseInt(uid, 10) + }, function (err, data) { + callback(err, data ? data.canGet : false); + }); +} + +Messaging.parse = function (message, fromuid, uid, roomId, isNew, callback) { + plugins.fireHook('filter:parse.raw', message, function (err, parsed) { + if (err) { + return callback(err); + } - function canGetMessages(callerUid, uid, callback) { - plugins.fireHook('filter:messaging.canGetMessages', { - callerUid: callerUid, + var messageData = { + message: message, + parsed: parsed, + fromuid: fromuid, uid: uid, - canGet: parseInt(callerUid, 10) === parseInt(uid, 10) - }, function (err, data) { - callback(err, data ? data.canGet : false); - }); - } + roomId: roomId, + isNew: isNew, + parsedMessage: parsed + }; - Messaging.getMessagesData = function (mids, uid, roomId, isNew, callback) { - - var keys = mids.map(function (mid) { - return 'message:' + mid; + plugins.fireHook('filter:messaging.parse', messageData, function (err, messageData) { + callback(err, messageData ? messageData.parsedMessage : ''); }); - - var messages; - - 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); - - var uids = messages.map(function (msg) { - return msg && msg.fromuid; - }); - - 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; - message.roomId = String(message.roomId || roomId); - if (message.hasOwnProperty('edited')) { - message.editedISO = new Date(parseInt(message.edited, 10)).toISOString(); - } - }); - - async.map(messages, function (message, next) { - Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function (err, result) { - if (err) { - return next(err); - } - 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 if (messages.length === 1) { - // 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); - } - - 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); - }); - } else { - next(null, []); - } + }); +}; + +Messaging.isNewSet = function (uid, roomId, timestamp, callback) { + var setKey = 'uid:' + uid + ':chat:room:' + roomId + ':mids'; + + async.waterfall([ + function (next) { + db.getSortedSetRevRangeWithScores(setKey, 0, 0, next); + }, + function (messages, next) { + if (messages && messages.length) { + next(null, parseInt(timestamp, 10) > parseInt(messages[0].score, 10) + (1000 * 60 * 5)); + } else { + next(null, true); } - ], callback); - - }; - - Messaging.parse = function (message, fromuid, uid, roomId, isNew, callback) { - plugins.fireHook('filter:parse.raw', message, function (err, parsed) { - if (err) { - return callback(err); - } - - var messageData = { - message: message, - parsed: parsed, - fromuid: fromuid, - uid: uid, - roomId: roomId, - isNew: isNew, - parsedMessage: parsed - }; - - plugins.fireHook('filter:messaging.parse', messageData, function (err, messageData) { - callback(err, messageData ? messageData.parsedMessage : ''); - }); - }); - }; - - Messaging.isNewSet = function (uid, roomId, timestamp, callback) { - var setKey = 'uid:' + uid + ':chat:room:' + roomId + ':mids'; - - async.waterfall([ - function (next) { - db.getSortedSetRevRangeWithScores(setKey, 0, 0, next); - }, - function (messages, next) { - if (messages && messages.length) { - next(null, parseInt(timestamp, 10) > parseInt(messages[0].score, 10) + (1000 * 60 * 5)); - } else { - next(null, true); - } + } + ], callback); +}; + + +Messaging.getRecentChats = function (callerUid, uid, start, stop, callback) { + async.waterfall([ + function (next) { + canGet('filter:messaging.canGetRecentChats', callerUid, uid, next); + }, + function (canGet, next) { + if (!canGet) { + return callback(null, null); } - ], callback); - }; - - - Messaging.getRecentChats = function (callerUid, uid, start, stop, callback) { - async.waterfall([ - function (next) { - canGetRecentChats(callerUid, uid, next); - }, - function (canGet, next) { - if (!canGet) { - return callback(null, null); - } - db.getSortedSetRevRange('uid:' + uid + ':chat:rooms', start, stop, next); - }, - function (roomIds, next) { - async.parallel({ - roomData: function (next) { - Messaging.getRoomsData(roomIds, next); - }, - unread: function (next) { - db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds, next); - }, - users: function (next) { - async.map(roomIds, function (roomId, next) { - db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 9, function (err, uids) { - if (err) { - return next(err); - } - uids = uids.filter(function (value) { - return value && parseInt(value, 10) !== parseInt(uid, 10); - }); - user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next); + db.getSortedSetRevRange('uid:' + uid + ':chat:rooms', start, stop, next); + }, + function (roomIds, next) { + async.parallel({ + roomData: function (next) { + Messaging.getRoomsData(roomIds, next); + }, + unread: function (next) { + db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds, next); + }, + users: function (next) { + async.map(roomIds, function (roomId, next) { + db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 9, function (err, uids) { + if (err) { + return next(err); + } + uids = uids.filter(function (value) { + return value && parseInt(value, 10) !== parseInt(uid, 10); }); - }, next); - }, - teasers: function (next) { - async.map(roomIds, function (roomId, next) { - Messaging.getTeaser(uid, roomId, next); - }, next); + user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next); + }); + }, next); + }, + teasers: function (next) { + async.map(roomIds, function (roomId, next) { + Messaging.getTeaser(uid, roomId, next); + }, next); + } + }, next); + }, + function (results, next) { + results.roomData.forEach(function (room, index) { + room.users = results.users[index]; + room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : room.users.length > 2; + room.unread = results.unread[index]; + room.teaser = results.teasers[index]; + + room.users.forEach(function (userData) { + if (userData && parseInt(userData.uid, 10)) { + userData.status = user.getStatus(userData); } - }, next); - }, - function (results, next) { - results.roomData.forEach(function (room, index) { - room.users = results.users[index]; - room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : room.users.length > 2; - room.unread = results.unread[index]; - room.teaser = results.teasers[index]; - - room.users.forEach(function (userData) { - if (userData && parseInt(userData.uid, 10)) { - userData.status = user.getStatus(userData); - } - }); - room.users = room.users.filter(function (user) { - return user && parseInt(user.uid, 10); - }); - room.lastUser = room.users[0]; - - room.usernames = Messaging.generateUsernames(room.users, uid); }); + room.users = room.users.filter(function (user) { + return user && parseInt(user.uid, 10); + }); + room.lastUser = room.users[0]; - next(null, {rooms: results.roomData, nextStart: stop + 1}); - } - ], callback); - }; - - Messaging.generateUsernames = function (users, excludeUid) { - users = users.filter(function (user) { - return user && parseInt(user.uid, 10) !== excludeUid; - }); - return users.map(function (user) { - return user.username; - }).join(', '); - }; - - function canGetRecentChats(callerUid, uid, callback) { - plugins.fireHook('filter:messaging.canGetRecentChats', { - callerUid: callerUid, - uid: uid, - canGet: parseInt(callerUid, 10) === parseInt(uid, 10) - }, function (err, data) { - callback(err, data ? data.canGet : false); - }); - } - - Messaging.getTeaser = function (uid, roomId, callback) { - var teaser; - async.waterfall([ - function (next) { - db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', 0, 0, next); - }, - function (mids, next) { - if (!mids || !mids.length) { - return next(null, null); - } - Messaging.getMessageFields(mids[0], ['fromuid', 'content', 'timestamp'], next); - }, - function (_teaser, next) { - teaser = _teaser; - if (!teaser) { - return callback(); - } - if (teaser.content) { - teaser.content = S(teaser.content).stripTags().decodeHTMLEntities().s; - } + room.usernames = Messaging.generateUsernames(room.users, uid); + }); - teaser.timestampISO = utils.toISOString(teaser.timestamp); - user.getUserFields(teaser.fromuid, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next); - }, - function (user, next) { - teaser.user = user; - next(null, teaser); + next(null, {rooms: results.roomData, nextStart: stop + 1}); + } + ], callback); +}; + +Messaging.generateUsernames = function (users, excludeUid) { + users = users.filter(function (user) { + return user && parseInt(user.uid, 10) !== excludeUid; + }); + return users.map(function (user) { + return user.username; + }).join(', '); +}; + +Messaging.getTeaser = function (uid, roomId, callback) { + var teaser; + async.waterfall([ + function (next) { + db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', 0, 0, next); + }, + function (mids, next) { + if (!mids || !mids.length) { + return next(null, null); + } + Messaging.getMessageFields(mids[0], ['fromuid', 'content', 'timestamp'], next); + }, + function (_teaser, next) { + teaser = _teaser; + if (!teaser) { + return callback(); + } + if (teaser.content) { + teaser.content = S(teaser.content).stripTags().decodeHTMLEntities().s; } - ], callback); - }; - Messaging.canMessageUser = function (uid, toUid, callback) { - if (parseInt(meta.config.disableChat) === 1 || !uid || uid === toUid) { - return callback(new Error('[[error:chat-disabled]]')); + teaser.timestampISO = utils.toISOString(teaser.timestamp); + user.getUserFields(teaser.fromuid, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next); + }, + function (user, next) { + teaser.user = user; + next(null, teaser); } + ], callback); +}; - if (parseInt(uid, 10) === parseInt(toUid, 10)) { - return callback(new Error('[[error:cant-chat-with-yourself')); - } +Messaging.canMessageUser = function (uid, toUid, callback) { + if (parseInt(meta.config.disableChat) === 1 || !uid || uid === toUid) { + return callback(new Error('[[error:chat-disabled]]')); + } - async.waterfall([ - function (next) { - user.exists(toUid, next); - }, - function (exists, next) { - if (!exists) { - return callback(new Error('[[error:no-user]]')); - } - user.getUserFields(uid, ['banned', 'email:confirmed'], next); - }, - function (userData, next) { - if (parseInt(userData.banned, 10) === 1) { - return callback(new Error('[[error:user-banned]]')); - } + if (parseInt(uid, 10) === parseInt(toUid, 10)) { + return callback(new Error('[[error:cant-chat-with-yourself')); + } - if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) { - return callback(new Error('[[error:email-not-confirmed-chat]]')); - } + async.waterfall([ + function (next) { + user.exists(toUid, next); + }, + function (exists, next) { + if (!exists) { + return callback(new Error('[[error:no-user]]')); + } + user.getUserFields(uid, ['banned', 'email:confirmed'], next); + }, + function (userData, next) { + if (parseInt(userData.banned, 10) === 1) { + return callback(new Error('[[error:user-banned]]')); + } - async.parallel({ - settings: async.apply(user.getSettings, toUid), - isAdmin: async.apply(user.isAdministrator, uid), - isFollowing: async.apply(user.isFollowing, toUid, uid) - }, next); - }, - function (results, next) { - if (!results.settings.restrictChat || results.isAdmin || results.isFollowing) { - return next(); - } + if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) { + return callback(new Error('[[error:email-not-confirmed-chat]]')); + } - next(new Error('[[error:chat-restricted]]')); + async.parallel({ + settings: async.apply(user.getSettings, toUid), + isAdmin: async.apply(user.isAdministrator, uid), + isFollowing: async.apply(user.isFollowing, toUid, uid) + }, next); + }, + function (results, next) { + if (!results.settings.restrictChat || results.isAdmin || results.isFollowing) { + return next(); } - ], callback); - }; - Messaging.canMessageRoom = function (uid, roomId, callback) { - if (parseInt(meta.config.disableChat) === 1 || !uid) { - return callback(new Error('[[error:chat-disabled]]')); + next(new Error('[[error:chat-restricted]]')); } + ], callback); +}; - async.waterfall([ - function (next) { - Messaging.isUserInRoom(uid, roomId, next); - }, - function (inRoom, next) { - if (!inRoom) { - return next(new Error('[[error:not-in-room]]')); - } +Messaging.canMessageRoom = function (uid, roomId, callback) { + if (parseInt(meta.config.disableChat) === 1 || !uid) { + return callback(new Error('[[error:chat-disabled]]')); + } - Messaging.getUserCountInRoom(roomId, next); - }, - function (count, next) { - if (count < 2) { - return next(new Error('[[error:no-users-in-room]]')); - } + async.waterfall([ + function (next) { + Messaging.isUserInRoom(uid, roomId, next); + }, + function (inRoom, next) { + if (!inRoom) { + return next(new Error('[[error:not-in-room]]')); + } - user.getUserFields(uid, ['banned', 'email:confirmed'], next); - }, - function (userData, next) { - if (parseInt(userData.banned, 10) === 1) { - return next(new Error('[[error:user-banned]]')); - } + Messaging.getUserCountInRoom(roomId, next); + }, + function (count, next) { + if (count < 2) { + return next(new Error('[[error:no-users-in-room]]')); + } - if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) { - return next(new Error('[[error:email-not-confirmed-chat]]')); - } + user.getUserFields(uid, ['banned', 'email:confirmed'], next); + }, + function (userData, next) { + if (parseInt(userData.banned, 10) === 1) { + return next(new Error('[[error:user-banned]]')); + } - next(); + if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) { + return next(new Error('[[error:email-not-confirmed-chat]]')); } - ], callback); - }; - Messaging.hasPrivateChat = function (uid, withUid, callback) { - if (parseInt(uid, 10) === parseInt(withUid, 10)) { - return callback(null, 0); + next(); } - async.waterfall([ - function (next) { - async.parallel({ - myRooms: async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':chat:rooms', 0, -1), - theirRooms: async.apply(db.getSortedSetRevRange, 'uid:' + withUid + ':chat:rooms', 0, -1) - }, next); - }, - function (results, next) { - var roomIds = results.myRooms.filter(function (roomId) { - return roomId && results.theirRooms.indexOf(roomId) !== -1; - }); + ], callback); +}; - if (!roomIds.length) { - return callback(); - } +Messaging.hasPrivateChat = function (uid, withUid, callback) { + if (parseInt(uid, 10) === parseInt(withUid, 10)) { + return callback(null, 0); + } + async.waterfall([ + function (next) { + async.parallel({ + myRooms: async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':chat:rooms', 0, -1), + theirRooms: async.apply(db.getSortedSetRevRange, 'uid:' + withUid + ':chat:rooms', 0, -1) + }, next); + }, + function (results, next) { + var roomIds = results.myRooms.filter(function (roomId) { + return roomId && results.theirRooms.indexOf(roomId) !== -1; + }); - var index = 0; - var roomId = 0; - async.whilst(function () { - return index < roomIds.length && !roomId; - }, function (next) { - Messaging.getUserCountInRoom(roomIds[index], function (err, count) { - if (err) { - return next(err); - } - if (count === 2) { - roomId = roomIds[index]; - next(null, roomId); - } else { - ++ index; - next(); - } - }); - }, function (err) { - next(err, roomId); - }); + if (!roomIds.length) { + return callback(); } - ], callback); - }; - -}(exports)); + var index = 0; + var roomId = 0; + async.whilst(function () { + return index < roomIds.length && !roomId; + }, function (next) { + Messaging.getUserCountInRoom(roomIds[index], function (err, count) { + if (err) { + return next(err); + } + if (count === 2) { + roomId = roomIds[index]; + next(null, roomId); + } else { + ++ index; + next(); + } + }); + }, function (err) { + next(err, roomId); + }); + } + ], callback); +}; diff --git a/src/messaging/data.js b/src/messaging/data.js new file mode 100644 index 0000000000..d1a00bd8f4 --- /dev/null +++ b/src/messaging/data.js @@ -0,0 +1,136 @@ +'use strict'; + +var async = require('async'); +var S = require('string'); + +var db = require('../database'); +var user = require('../user'); +var utils = require('../../public/src/utils'); + +module.exports = function (Messaging) { + + Messaging.getMessageField = function (mid, field, callback) { + Messaging.getMessageFields(mid, [field], function (err, fields) { + callback(err, fields ? fields[field] : null); + }); + }; + + Messaging.getMessageFields = function (mid, fields, callback) { + db.getObjectFields('message:' + mid, fields, callback); + }; + + Messaging.setMessageField = function (mid, field, content, callback) { + db.setObjectField('message:' + mid, field, content, callback); + }; + + Messaging.setMessageFields = function (mid, data, callback) { + db.setObject('message:' + mid, data, callback); + }; + + Messaging.getMessagesData = function (mids, uid, roomId, isNew, callback) { + + var messages; + + async.waterfall([ + function (next) { + var keys = mids.map(function (mid) { + return 'message:' + mid; + }); + + 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); + + var uids = messages.map(function (msg) { + return msg && msg.fromuid; + }); + + 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; + message.roomId = String(message.roomId || roomId); + if (message.hasOwnProperty('edited')) { + message.editedISO = new Date(parseInt(message.edited, 10)).toISOString(); + } + }); + + async.map(messages, function (message, next) { + Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function (err, result) { + if (err) { + return next(err); + } + 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 if (messages.length === 1) { + // 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); + } + + 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); + }); + } else { + next(null, []); + } + } + ], callback); + }; + +};