The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Rocket.Chat/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js

485 lines
15 KiB

/* globals Push */
import _ from 'underscore';
import s from 'underscore.string';
import moment from 'moment';
const CATEGORY_MESSAGE = 'MESSAGE';
const CATEGORY_MESSAGE_NOREPLY = 'MESSAGE_NOREPLY';
/**
* Replaces @username with full name
*
* @param {string} message The message to replace
* @param {object[]} mentions Array of mentions used to make replacements
*
* @returns {string}
*/
function replaceMentionedUsernamesWithFullNames(message, mentions) {
if (!mentions || !mentions.length) {
return message;
}
mentions.forEach((mention) => {
const user = RocketChat.models.Users.findOneById(mention._id);
if (user && user.name) {
message = message.replace(`@${ mention.username }`, user.name);
}
});
return message;
}
function canSendMessageToRoom(room, username) {
return !((room.muted || []).includes(username));
}
/**
* This function returns a string ready to be shown in the notification
9 years ago
*
* @param {object} message the message to be parsed
*/
function parseMessageText(message, userId) {
const user = RocketChat.models.Users.findOneById(userId);
const lng = user && user.language || RocketChat.settings.get('language') || 'en';
if (!message.msg && message.attachments && message.attachments[0]) {
message.msg = message.attachments[0].image_type ? TAPi18n.__('User_uploaded_image', {lng}) : TAPi18n.__('User_uploaded_file', {lng});
}
message.msg = RocketChat.callbacks.run('beforeNotifyUser', message.msg);
return message.msg;
}
/**
* Send notification to user
*
* @param {string} userId The user to notify
* @param {object} user The sender
* @param {object} room The room send from
* @param {number} duration Duration of notification
*/
8 years ago
function notifyDesktopUser(userId, user, message, room, duration) {
const UI_Use_Real_Name = RocketChat.settings.get('UI_Use_Real_Name') === true;
message.msg = parseMessageText(message, userId);
if (UI_Use_Real_Name) {
message.msg = replaceMentionedUsernamesWithFullNames(message.msg, message.mentions);
}
let title = '';
let text = '';
if (room.t === 'd') {
title = UI_Use_Real_Name ? user.name : `@${ user.username }`;
text = message.msg;
} else if (room.name) {
title = `#${ room.name }`;
text = `${ user.username }: ${ message.msg }`;
}
if (title === '' || text === '') {
return;
}
RocketChat.Notifications.notifyUser(userId, 'notification', {
title,
text,
duration,
payload: {
_id: message._id,
rid: message.rid,
sender: message.u,
type: room.t,
name: room.name
}
});
}
8 years ago
function notifyAudioUser(userId, message, room) {
8 years ago
RocketChat.Notifications.notifyUser(userId, 'audioNotification', {
payload: {
_id: message._id,
rid: message.rid,
sender: message.u,
type: room.t,
name: room.name
}
});
}
/**
* Checks if a message contains a user highlight
*
* @param {string} message
* @param {array|undefined} highlights
*
* @returns {boolean}
*/
function messageContainsHighlight(message, highlights) {
if (! highlights || highlights.length === 0) { return false; }
let has = false;
highlights.some(function(highlight) {
const regexp = new RegExp(s.escapeRegExp(highlight), 'i');
if (regexp.test(message.msg)) {
has = true;
return true;
}
});
return has;
}
function getBadgeCount(userId) {
const subscriptions = RocketChat.models.Subscriptions.findUnreadByUserId(userId).fetch();
return subscriptions.reduce((unread, sub) => {
return sub.unread + unread;
}, 0);
}
const sendPushNotifications = (userIdsToPushNotify = [], message, room, push_room, push_username, push_message, pushUsernames) => {
8 years ago
if (userIdsToPushNotify.length > 0 && Push.enabled === true) {
// send a push notification for each user individually (to get his/her badge count)
userIdsToPushNotify.forEach((userIdToNotify) => {
RocketChat.PushNotification.send({
roomId: message.rid,
roomName: push_room,
username: push_username,
message: push_message,
badge: getBadgeCount(userIdToNotify),
payload: {
host: Meteor.absoluteUrl(),
rid: message.rid,
sender: message.u,
type: room.t,
name: room.name
},
usersTo: {
userId: userIdToNotify
},
category: canSendMessageToRoom(room, pushUsernames[userIdToNotify]) ? CATEGORY_MESSAGE : CATEGORY_MESSAGE_NOREPLY
8 years ago
});
});
}
};
8 years ago
const callJoin = (user, rid) => user.active && Meteor.runAsUser(user._id, () => Meteor.call('joinRoom', rid));
RocketChat.callbacks.add('afterSaveMessage', function(message, room, userId) {
// skips this callback if the message was edited
if (message.editedAt) {
return message;
}
if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) {
return message;
}
const pushUsernames = {};
const user = (room.t !== 'l') ? RocketChat.models.Users.findOneById(message.u._id) : room.v;
if (!user) {
return message;
}
/*
Increment unread couter if direct messages
*/
const settings = {
alwaysNotifyDesktopUsers: [],
dontNotifyDesktopUsers: [],
alwaysNotifyMobileUsers: [],
dontNotifyMobileUsers: [],
8 years ago
desktopNotificationDurations: {},
alwaysNotifyAudioUsers: [],
dontNotifyAudioUsers: [],
audioNotificationValues: {},
dontNotifyUsersOnGroupMentions: []
};
/**
* Checks if a given user can be notified
*
* @param {string} id
* @param {string} type - mobile|desktop
*
* @returns {boolean}
*/
function canBeNotified(id, type) {
const types = {
desktop: [ 'dontNotifyDesktopUsers', 'alwaysNotifyDesktopUsers' ],
mobile: [ 'dontNotifyMobileUsers', 'alwaysNotifyMobileUsers' ],
8 years ago
audio: [ 'dontNotifyAudioUsers', 'alwaysNotifyAudioUsers' ]
};
return (settings[types[type][0]].indexOf(id) === -1 || settings[types[type][1]].indexOf(id) !== -1);
}
9 years ago
// Don't fetch all users if room exceeds max members
const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members');
const disableAllMessageNotifications = room.usernames.length > maxMembersForNotification && maxMembersForNotification !== 0;
const subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id, disableAllMessageNotifications) || [];
const userIds = [];
subscriptions.forEach(s => userIds.push(s.u._id));
const users = {};
RocketChat.models.Users.findUsersByIds(userIds, { fields: { 'settings.preferences': 1 } }).forEach((user) => {
users[user._id] = user;
});
9 years ago
8 years ago
subscriptions.forEach(subscription => {
if (subscription.disableNotifications) {
settings.dontNotifyDesktopUsers.push(subscription.u._id);
settings.dontNotifyMobileUsers.push(subscription.u._id);
8 years ago
settings.dontNotifyAudioUsers.push(subscription.u._id);
8 years ago
return;
}
if (Array.isArray(subscription.ignored) && subscription.ignored.find(message.u._id)) {
return;
}
8 years ago
const {
audioNotifications = RocketChat.getUserPreference(users[subscription.u._id], 'audioNotifications'),
desktopNotifications = RocketChat.getUserPreference(users[subscription.u._id], 'desktopNotifications'),
mobilePushNotifications = RocketChat.getUserPreference(users[subscription.u._id], 'mobileNotifications')
8 years ago
} = subscription;
8 years ago
if (audioNotifications === 'all' && !disableAllMessageNotifications) {
settings.alwaysNotifyAudioUsers.push(subscription.u._id);
}
if (desktopNotifications === 'all' && !disableAllMessageNotifications) {
settings.alwaysNotifyDesktopUsers.push(subscription.u._id);
} else if (desktopNotifications === 'nothing') {
settings.dontNotifyDesktopUsers.push(subscription.u._id);
}
if (mobilePushNotifications === 'all' && !disableAllMessageNotifications) {
settings.alwaysNotifyMobileUsers.push(subscription.u._id);
} else if (mobilePushNotifications === 'nothing') {
settings.dontNotifyMobileUsers.push(subscription.u._id);
}
8 years ago
8 years ago
settings.audioNotificationValues[subscription.u._id] = subscription.audioNotificationValue;
settings.desktopNotificationDurations[subscription.u._id] = subscription.desktopNotificationDuration;
if (subscription.muteGroupMentions) {
settings.dontNotifyUsersOnGroupMentions.push(subscription.u._id);
}
});
8 years ago
let userIdsForAudio = [];
let userIdsToNotify = [];
let userIdsToPushNotify = [];
8 years ago
const mentions = [];
const alwaysNotifyMobileBoolean = RocketChat.settings.get('Notifications_Always_Notify_Mobile');
8 years ago
const usersWithHighlights = RocketChat.models.Users.findUsersByUsernamesWithHighlights(room.usernames, { fields: { '_id': 1, 'settings.preferences.highlights': 1 }}).fetch()
.filter(user => messageContainsHighlight(message, user.settings.preferences.highlights));
let push_message = ' ';
//Set variables depending on Push Notification settings
if (RocketChat.settings.get('Push_show_message')) {
push_message = parseMessageText(message, userId);
}
let push_username = '';
let push_room = '';
if (RocketChat.settings.get('Push_show_username_room')) {
push_username = user.username;
push_room = `#${ room.name }`;
}
if (room.t == null || room.t === 'd') {
const userOfMentionId = message.rid.replace(message.u._id, '');
const userOfMention = RocketChat.models.Users.findOne({
_id: userOfMentionId
}, {
fields: {
username: 1,
statusConnection: 1
}
});
// Always notify Sandstorm
if (userOfMention != null) {
RocketChat.Sandstorm.notify(message, [userOfMention._id],
`@${ user.username }: ${ message.msg }`, 'privateMessage');
if (canBeNotified(userOfMentionId, 'desktop')) {
const duration = settings.desktopNotificationDurations[userOfMention._id];
notifyDesktopUser(userOfMention._id, user, message, room, duration);
}
if (canBeNotified(userOfMentionId, 'mobile')) {
if (Push.enabled === true && (userOfMention.statusConnection !== 'online' || alwaysNotifyMobileBoolean === true)) {
RocketChat.PushNotification.send({
roomId: message.rid,
username: push_username,
message: push_message,
badge: getBadgeCount(userOfMention._id),
payload: {
host: Meteor.absoluteUrl(),
rid: message.rid,
sender: message.u,
type: room.t,
name: room.name
},
usersTo: {
userId: userOfMention._id
},
category: canSendMessageToRoom(room, userOfMention.username) ? CATEGORY_MESSAGE : CATEGORY_MESSAGE_NOREPLY
});
return message;
}
}
}
} else {
8 years ago
const mentionIds = (message.mentions || []).map(({_id}) => _id);
const toAll = mentionIds.includes('all');
const toHere = mentionIds.includes('here');
8 years ago
if (mentionIds.length + settings.alwaysNotifyDesktopUsers.length > 0) {
let desktopMentionIds = _.union(mentionIds, settings.alwaysNotifyDesktopUsers);
desktopMentionIds = _.difference(desktopMentionIds, settings.dontNotifyDesktopUsers);
let usersOfDesktopMentions = RocketChat.models.Users.find({
_id: {
$in: desktopMentionIds
}
}, {
fields: {
_id: 1,
username: 1,
active: 1
}
}).fetch();
8 years ago
mentions.push(...usersOfDesktopMentions);
if (room.t !== 'c') {
usersOfDesktopMentions = _.reject(usersOfDesktopMentions, (usersOfMentionItem) => {
return room.usernames.indexOf(usersOfMentionItem.username) === -1;
});
}
userIdsToNotify = _.pluck(usersOfDesktopMentions, '_id');
}
8 years ago
if (mentionIds.length + settings.alwaysNotifyMobileUsers.length > 0) {
let mobileMentionIds = _.union(mentionIds, settings.alwaysNotifyMobileUsers);
mobileMentionIds = _.difference(mobileMentionIds, settings.dontNotifyMobileUsers);
const usersOfMobileMentionsQuery = {
_id: {
$in: mobileMentionIds
}
};
if (alwaysNotifyMobileBoolean !== true) {
usersOfMobileMentionsQuery.statusConnection = { $ne: 'online' };
}
let usersOfMobileMentions = RocketChat.models.Users.find(usersOfMobileMentionsQuery, {
fields: {
_id: 1,
username: 1,
8 years ago
statusConnection: 1,
active: 1
}
}).fetch();
8 years ago
mentions.push(...usersOfMobileMentions);
if (room.t !== 'c') {
8 years ago
usersOfMobileMentions = _.reject(usersOfMobileMentions, usersOfMentionItem => !room.usernames.includes(usersOfMentionItem.username));
}
userIdsToPushNotify = usersOfMobileMentions.map(userMobile => {
pushUsernames[userMobile._id] = userMobile.username;
return userMobile._id;
});
}
8 years ago
if (mentionIds.length + settings.alwaysNotifyAudioUsers.length > 0) {
8 years ago
let audioMentionIds = _.union(mentionIds, settings.alwaysNotifyAudioUsers);
audioMentionIds = _.difference(audioMentionIds, userIdsToNotify);
8 years ago
let usersOfAudioMentions = RocketChat.models.Users.find({ _id: { $in: audioMentionIds }, statusConnection: {
$ne:'offline'
} }, {
8 years ago
fields: {
_id: 1,
username: 1,
active: 1
}
}).fetch();
8 years ago
mentions.push(...usersOfAudioMentions);
if (room.t !== 'c') {
8 years ago
usersOfAudioMentions = _.reject(usersOfAudioMentions, (usersOfMentionItem) => {
return room.usernames.indexOf(usersOfMentionItem.username) === -1;
});
}
8 years ago
userIdsForAudio = _.pluck(usersOfAudioMentions, '_id');
}
8 years ago
if (room.t === 'c') {
mentions.filter(user => !room.usernames.includes(user.username))
.forEach(user =>callJoin(user, room._id));
}
8 years ago
if ([toAll, toHere].some(e => e) && room.usernames && room.usernames.length > 0) {
RocketChat.models.Users.find({
8 years ago
username: { $in: room.usernames },
_id: { $ne: user._id }
}, {
fields: {
_id: 1,
username: 1,
status: 1,
statusConnection: 1
}
}).forEach(function({ status, _id, username, statusConnection }) { // user
if (Array.isArray(settings.dontNotifyUsersOnGroupMentions) && settings.dontNotifyUsersOnGroupMentions.includes(_id)) {
return;
}
if (['online', 'away', 'busy'].includes(status) && !(settings.dontNotifyDesktopUsers || []).includes(_id)) {
userIdsToNotify.push(_id);
userIdsForAudio.push(_id);
}
if (toAll && statusConnection !== 'online' && !(settings.dontNotifyMobileUsers || []).includes(_id)) {
pushUsernames[_id] = username;
return userIdsToPushNotify.push(_id);
}
if (toAll && statusConnection !== 'online') {
userIdsForAudio.push(_id);
8 years ago
}
});
}
if (usersWithHighlights.length > 0) {
const highlightsIds = _.pluck(usersWithHighlights, '_id');
8 years ago
userIdsForAudio = userIdsForAudio.concat(highlightsIds);
userIdsToNotify = userIdsToNotify.concat(highlightsIds);
userIdsToPushNotify = userIdsToPushNotify.concat(highlightsIds);
}
userIdsToNotify = _.without(_.compact(_.unique(userIdsToNotify)), message.u._id);
userIdsToPushNotify = _.without(_.compact(_.unique(userIdsToPushNotify)), message.u._id);
8 years ago
userIdsForAudio = _.without(_.compact(_.unique(userIdsForAudio)), message.u._id);
8 years ago
for (const usersOfMentionId of userIdsToNotify) {
const duration = settings.desktopNotificationDurations[usersOfMentionId];
notifyDesktopUser(usersOfMentionId, user, message, room, duration);
}
8 years ago
for (const usersOfMentionId of userIdsForAudio) {
8 years ago
notifyAudioUser(usersOfMentionId, message, room);
}
sendPushNotifications(userIdsToPushNotify, message, room, push_room, push_username, push_message, pushUsernames);
const allUserIdsToNotify = _.unique(userIdsToNotify.concat(userIdsToPushNotify));
8 years ago
RocketChat.Sandstorm.notify(message, allUserIdsToNotify,
`@${ user.username }: ${ message.msg }`, room.t === 'p' ? 'privateMessage' : 'message');
}
return message;
}, RocketChat.callbacks.priority.LOW, 'sendNotificationOnMessage');