diff --git a/src/messaging.js b/src/messaging.js index acf32a256b..5cfbca1318 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -4,17 +4,15 @@ 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'), - sockets = require('./socket.io'); + userNotifications = require('./user/notifications'); (function(Messaging) { @@ -23,6 +21,7 @@ var async = require('async'), require('./messaging/edit')(Messaging); require('./messaging/rooms')(Messaging); require('./messaging/unread')(Messaging); + require('./messaging/notifications')(Messaging); Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser @@ -309,46 +308,50 @@ var async = require('async'), }); }; + Messaging.canMessageUser = function(uid, toUid, callback) { + if (parseInt(meta.config.disableChat) === 1 || !uid || uid === toUid) { + return callback(null, false); + } + async.waterfall([ + function (next) { + user.exists(toUid, next); + }, + function (exists, next) { + if (!exists) { + return callback(null, false); + } + user.getUserFields(uid, ['banned', 'email:confirmed'], next); + }, + function (userData, next) { + if (parseInt(userData.banned, 10) === 1) { + return callback(null, false); + } - Messaging.notifyUser = function(uid, roomId, messageObj) { - // Immediate notifications - // Recipient - Messaging.pushUnreadCount(touid); - sockets.in('uid_' + touid).emit('event:chats.receive', { - withUid: fromuid, - message: messageObj, - self: 0 - }); - // Sender - Messaging.pushUnreadCount(fromuid); - sockets.in('uid_' + fromuid).emit('event:chats.receive', { - withUid: touid, - message: messageObj, - self: 1 - }); + if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) { + return callback(null, false); + } - // Delayed notifications - var queueObj = Messaging.notifyQueue[fromuid + ':' + touid]; - if (queueObj) { - queueObj.message.content += '\n' + messageObj.content; - clearTimeout(queueObj.timeout); - } else { - queueObj = Messaging.notifyQueue[fromuid + ':' + touid] = { - message: messageObj - }; - } + user.getSettings(toUid, next); + }, + function(settings, next) { + if (!settings.restrictChat) { + return callback(null, true); + } - queueObj.timeout = setTimeout(function() { - sendNotifications(fromuid, touid, queueObj.message, function(err) { - if (!err) { - delete Messaging.notifyQueue[fromuid + ':' + touid]; + user.isAdministrator(uid, next); + }, + function(isAdmin, next) { + if (isAdmin) { + return callback(null, true); } - }); - }, 1000*60); // wait 60s before sending + user.isFollowing(toUid, uid, next); + } + ], callback); + }; - Messaging.canMessage = function(uid, roomId, callback) { + Messaging.canMessageRoom = function(uid, roomId, callback) { if (parseInt(meta.config.disableChat) === 1 || !uid) { return callback(null, false); } @@ -378,39 +381,4 @@ var async = require('async'), }; - function sendNotifications(fromuid, touid, messageObj, callback) { - user.isOnline(touid, function(err, isOnline) { - if (err || isOnline) { - return callback(err); - } - - notifications.create({ - bodyShort: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', - bodyLong: messageObj.content, - nid: 'chat_' + fromuid + '_' + touid, - from: fromuid, - path: '/chats/' + messageObj.fromUser.username - }, function(err, notification) { - if (!err && notification) { - notifications.push(notification, [touid], callback); - } - }); - - user.getSettings(messageObj.toUser.uid, function(err, settings) { - if (settings.sendChatNotifications && !parseInt(meta.config.disableEmailSubscriptions, 10)) { - emailer.send('notif_chat', touid, { - subject: '[[email:notif.chat.subject, ' + messageObj.fromUser.username + ']]', - username: messageObj.toUser.username, - userslug: utils.slugify(messageObj.toUser.username), - summary: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', - message: messageObj, - site_title: meta.config.title || 'NodeBB', - url: nconf.get('url'), - fromUserslug: utils.slugify(messageObj.fromUser.username) - }); - } - }); - }); - } - }(exports)); diff --git a/src/messaging/create.js b/src/messaging/create.js index 4a941e161b..360f800e81 100644 --- a/src/messaging/create.js +++ b/src/messaging/create.js @@ -106,6 +106,7 @@ module.exports = function(Messaging) { results.messages[0].newSet = results.isNewSet; results.messages[0].mid = mid; + results.messages[0].roomId = roomId; next(null, results.messages[0]); } ], callback); diff --git a/src/messaging/notifications.js b/src/messaging/notifications.js new file mode 100644 index 0000000000..b1b8564659 --- /dev/null +++ b/src/messaging/notifications.js @@ -0,0 +1,104 @@ +'use strict'; + +var async = require('async'); +var nconf = require('nconf'); + +var user = require('../user'); +var emailer = require('../emailer'); +var notifications = require('./notifications'); +var meta = require('../meta'); +var utils = require('../../public/src/utils'); +var sockets = require('../socket.io'); + +module.exports = function(Messaging) { + + Messaging.notifyUsersInRoom = function(fromUid, roomId, messageObj) { + Messaging.getUidsInRoom(roomId, 0, -1, function(err, uids) { + if (err) { + return; + } + + var data = { + roomId: roomId, + fromUid: fromUid, + message: messageObj + }; + uids.forEach(function(uid) { + data.self = parseInt(uid, 10) === parseInt(fromUid) ? 1 : 0; + Messaging.pushUnreadCount(uid); + sockets.in('uid_' + uid).emit('event:chats.receive', data); + }); + + // Delayed notifications + var queueObj = Messaging.notifyQueue[fromUid + ':' + roomId]; + if (queueObj) { + queueObj.message.content += '\n' + messageObj.content; + clearTimeout(queueObj.timeout); + } else { + queueObj = Messaging.notifyQueue[fromUid + ':' + roomId] = { + message: messageObj + }; + } + + queueObj.timeout = setTimeout(function() { + sendNotifications(fromUid, uids, roomId, queueObj.message, function(err) { + if (!err) { + delete Messaging.notifyQueue[fromUid + ':' + roomId]; + } + }); + }, 1000*60); // wait 60s before sending + }); + }; + + function sendNotifications(fromuid, uids, roomId, messageObj, callback) { + user.isOnline(uids, function(err, isOnline) { + if (err) { + return callback(err); + } + + uids = uids.filter(function(uid, index) { + return isOnline[index]; + }); + + if (!uids.length) { + return callback(); + } + + notifications.create({ + bodyShort: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', + bodyLong: messageObj.content, + nid: 'chat_' + fromuid + '_' + roomId, + from: fromuid, + path: '/chats/' + messageObj.fromUser.username + }, function(err, notification) { + if (!err && notification) { + notifications.push(notification, uids, callback); + } + }); + + if (parseInt(meta.config.disableEmailSubscriptions, 10) === 1) { + return callback(); + } + + user.getMultipleUserSettings(uids, function(err, userSettings) { + if (err) { + return callback(err); + } + userSettings = userSettings.filter(function(settings) { + return settings && settings.sendChatNotifications; + }); + async.each(userSettings, function(settings, next) { + emailer.send('notif_chat', settings.uid, { + subject: '[[email:notif.chat.subject, ' + messageObj.fromUser.username + ']]', + summary: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', + message: messageObj, + site_title: meta.config.title || 'NodeBB', + url: nconf.get('url'), + fromUserslug: utils.slugify(messageObj.fromUser.username) + }, next); + }, callback); + + }); + }); + } +}; \ No newline at end of file diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index e1c1d78b8f..bb0de7b268 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -39,6 +39,36 @@ SocketModules.chats.getRaw = function(socket, data, callback) { Messaging.getMessageField(data.mid, 'content', callback); }; +SocketModules.chats.newMessage = function(socket, data, callback) { + if (!data) { + return callback(new Error('[[error:invalid-data]]')); + } + var now = Date.now(); + // Websocket rate limiting + socket.lastChatMessageTime = socket.lastChatMessageTime || 0; + if (now - socket.lastChatMessageTime < 200) { + return callback(new Error('[[error:too-many-messages]]')); + } else { + socket.lastChatMessageTime = now; + } + + Messaging.canMessageUser(socket.uid, data.touid, function(err, allowed) { + if (err || !allowed) { + return callback(err || new Error('[[error:chat-restricted]]')); + } + + Messaging.newMessage(socket.uid, [data.touid], data.content, now, function(err, message) { + if (err) { + return callback(err); + } + + Messaging.notifyUsersInRoom(socket.uid, message.roomId, message); + + callback(); + }); + }); +}; + SocketModules.chats.send = function(socket, data, callback) { if (!data || !data.roomId) { return callback(new Error('[[error:invalid-data]]')); @@ -54,7 +84,7 @@ SocketModules.chats.send = function(socket, data, callback) { socket.lastChatMessageTime = now; } - Messaging.canMessage(socket.uid, data.roomId, function(err, allowed) { + Messaging.canMessageRoom(socket.uid, data.roomId, function(err, allowed) { if (err || !allowed) { return callback(err || new Error('[[error:chat-restricted]]')); } @@ -64,7 +94,7 @@ SocketModules.chats.send = function(socket, data, callback) { return callback(err); } - Messaging.notifyUser(socket.uid, data.roomId, message); + Messaging.notifyUsersInRoom(socket.uid, data.roomId, message); callback(); }); @@ -95,7 +125,7 @@ SocketModules.chats.delete = function(socket, data, callback) { Messaging.deleteMessage(data.messageId, data.roomId, callback); } }); -} +}; SocketModules.chats.canMessage = function(socket, roomId, callback) { Messaging.canMessage(socket.uid, roomId, function(err, allowed) { diff --git a/src/user.js b/src/user.js index 4b64b52dbd..7a9213a963 100644 --- a/src/user.js +++ b/src/user.js @@ -132,13 +132,27 @@ var async = require('async'), }; User.isOnline = function(uid, callback) { - db.sortedSetScore('users:online', uid, function(err, lastonline) { - if (err) { - return callback(err); - } - var isOnline = Date.now() - parseInt(lastonline, 10) < 300000; - callback(null, isOnline); - }); + if (Array.isArray(uid)) { + db.sortedSetScores('users:online', uid, function(err, lastonline) { + if (err) { + return callback(err); + } + var now = Date.now(); + var isOnline = uid.map(function(uid, index) { + return now - lastonline[index] < 300000; + }); + callback(null, isOnline); + }); + } else { + db.sortedSetScore('users:online', uid, function(err, lastonline) { + if (err) { + return callback(err); + } + var isOnline = Date.now() - parseInt(lastonline, 10) < 300000; + callback(null, isOnline); + }); + } + }; User.exists = function(uid, callback) {