Chat notifs (#11832)

* first part of chat notifs

* moved default notif to manage page

* spec

* notifs

* delete settings on room delete
This commit is contained in:
Barış Soner Uşaklı
2023-07-21 15:31:34 -04:00
committed by GitHub
parent f377650161
commit 61f036ce1d
15 changed files with 249 additions and 46 deletions

View File

@@ -25,6 +25,11 @@ require('./rooms')(Messaging);
require('./unread')(Messaging);
require('./notifications')(Messaging);
Messaging.notificationSettings = Object.create(null);
Messaging.notificationSettings.NONE = 1;
Messaging.notificationSettings.ATMENTION = 2;
Messaging.notificationSettings.ALLMESSAGES = 3;
Messaging.messageExists = async mid => db.exists(`message:${mid}`);
Messaging.getMessages = async (params) => {

View File

@@ -12,6 +12,23 @@ const meta = require('../meta');
module.exports = function (Messaging) {
// Only used to notify a user of a new chat message
Messaging.notifyQueue = {};
Messaging.setUserNotificationSetting = async (uid, roomId, value) => {
if (parseInt(value, 10) === -1) {
// go back to default
return await db.deleteObjectField(`chat:room:${roomId}:notification:settings`, uid);
}
await db.setObjectField(`chat:room:${roomId}:notification:settings`, uid, parseInt(value, 10));
};
Messaging.getUidsNotificationSetting = async (uids, roomId) => {
const [settings, roomData] = await Promise.all([
db.getObjectFields(`chat:room:${roomId}:notification:settings`, uids),
Messaging.getRoomData(roomId, ['notificationSetting']),
]);
return uids.map(uid => parseInt(settings[uid] || roomData.notificationSetting, 10));
};
Messaging.notifyUsersInRoom = async (fromUid, roomId, messageObj) => {
const isPublic = parseInt(await db.getObjectField(`chat:room:${roomId}`, 'public'), 10) === 1;
@@ -34,13 +51,15 @@ module.exports = function (Messaging) {
// delivers unread public msg to all online users on the chats page
io.in(`chat_room_public_${roomId}`).emit('event:chats.public.unread', unreadData);
}
if (messageObj.system || isPublic) {
if (messageObj.system) {
return;
}
// push unread count only for private rooms
const uids = await Messaging.getAllUidsInRoomFromSet(`chat:room:${roomId}:uids:online`);
Messaging.pushUnreadCount(uids, unreadData);
if (!isPublic) {
const uids = await Messaging.getAllUidsInRoomFromSet(`chat:room:${roomId}:uids:online`);
Messaging.pushUnreadCount(uids, unreadData);
}
// Delayed notifications
let queueObj = Messaging.notifyQueue[`${fromUid}:${roomId}`];
@@ -65,27 +84,41 @@ module.exports = function (Messaging) {
};
async function sendNotification(fromUid, roomId, messageObj) {
const { displayname } = messageObj.fromUser;
const isGroupChat = await Messaging.isGroupChat(roomId);
const notification = await notifications.create({
type: isGroupChat ? 'new-group-chat' : 'new-chat',
subject: `[[email:notif.chat.subject, ${displayname}]]`,
bodyShort: `[[notifications:new_message_from, ${displayname}]]`,
bodyLong: messageObj.content,
nid: `chat_${fromUid}_${roomId}`,
from: fromUid,
path: `/chats/${messageObj.roomId}`,
});
fromUid = parseInt(fromUid, 10);
const [settings, roomData] = await Promise.all([
db.getObject(`chat:room:${roomId}:notification:settings`),
Messaging.getRoomData(roomId, ['notificationSetting']),
]);
const roomDefault = roomData.notificationSetting;
const uidsToNotify = [];
const { ALLMESSAGES } = Messaging.notificationSettings;
await batch.processSortedSet(`chat:room:${roomId}:uids:online`, async (uids) => {
uids = uids.filter(
uid => (parseInt((settings && settings[uid]) || roomDefault, 10) === ALLMESSAGES) &&
fromUid !== parseInt(uid, 10)
);
const hasRead = await Messaging.hasRead(uids, roomId);
uids = uids.filter((uid, index) => !hasRead[index] && parseInt(fromUid, 10) !== parseInt(uid, 10));
notifications.push(notification, uids);
uidsToNotify.push(...uids.filter((uid, index) => !hasRead[index]));
}, {
reverse: true,
batch: 500,
interval: 1000,
interval: 100,
});
if (uidsToNotify.length) {
const { displayname } = messageObj.fromUser;
const isGroupChat = await Messaging.isGroupChat(roomId);
const notification = await notifications.create({
type: isGroupChat ? 'new-group-chat' : 'new-chat',
subject: `[[email:notif.chat.subject, ${displayname}]]`,
bodyShort: `[[notifications:new_message_from, ${displayname}]]`,
bodyLong: messageObj.content,
nid: `chat_${fromUid}_${roomId}`,
from: fromUid,
path: `/chats/${messageObj.roomId}`,
});
await notifications.push(notification, uidsToNotify);
}
}
};

View File

@@ -54,6 +54,15 @@ module.exports = function (Messaging) {
data.groupChat = parseInt(data.groupChat, 10) === 1;
}
if (!fields.length || fields.includes('notificationSetting')) {
data.notificationSetting = data.notificationSetting ||
(
data.public ?
Messaging.notificationSettings.ATMENTION :
Messaging.notificationSettings.ALLMESSAGES
);
}
if (data.hasOwnProperty('groups') || !fields.length || fields.includes('groups')) {
try {
data.groups = JSON.parse(data.groups || '[]');
@@ -76,6 +85,7 @@ module.exports = function (Messaging) {
const room = {
roomId: roomId,
timestamp: now,
notificationSetting: data.notificationSetting,
};
if (data.hasOwnProperty('roomName') && data.roomName) {
@@ -145,10 +155,14 @@ module.exports = function (Messaging) {
...roomIds.map(id => `chat:room:${id}:uids`),
...roomIds.map(id => `chat:room:${id}:owners`),
...roomIds.map(id => `chat:room:${id}:uids:online`),
...roomIds.map(id => `chat:room:${id}:notification:settings`),
]),
db.sortedSetRemove('chat:rooms', roomIds),
db.sortedSetRemove('chat:rooms:public', roomIds),
db.sortedSetRemove('chat:rooms:public:order', roomIds),
db.sortedSetRemove([
'chat:rooms',
'chat:rooms:public',
'chat:rooms:public:order',
'chat:rooms:public:lastpost',
], roomIds),
]);
cache.del([
'chat:rooms:public:all',
@@ -448,7 +462,36 @@ module.exports = function (Messaging) {
await db.sortedSetAdd(`chat:room:${roomId}:uids:online`, Date.now(), uid);
}
const [canReply, users, messages, settings, isOwner, onlineUids] = await Promise.all([
async function getNotificationOptions() {
const userSetting = await db.getObjectField(`chat:room:${roomId}:notification:settings`, uid);
const roomDefault = room.notificationSetting;
const currentSetting = userSetting || roomDefault;
const labels = {
[Messaging.notificationSettings.NONE]: { label: '[[modules:chat.notification-setting-none]]', icon: 'fa-ban' },
[Messaging.notificationSettings.ATMENTION]: { label: '[[modules:chat.notification-setting-at-mention-only]]', icon: 'fa-at' },
[Messaging.notificationSettings.ALLMESSAGES]: { label: '[[modules:chat.notification-setting-all-messages]]', icon: 'fa-comment-o' },
};
const options = [
{
label: '[[modules:chat.notification-setting-room-default]]',
subLabel: labels[roomDefault].label || '',
icon: labels[roomDefault].icon,
value: -1,
selected: userSetting === null,
},
];
Object.keys(labels).forEach((key) => {
options.push({
label: labels[key].label,
icon: labels[key].icon,
value: key,
selected: parseInt(userSetting, 10) === parseInt(key, 10),
});
});
return { options, selectedIcon: labels[currentSetting].icon };
}
const [canReply, users, messages, settings, isOwner, onlineUids, notifOptions] = await Promise.all([
Messaging.canReply(roomId, uid),
Messaging.getUsersInRoomFromSet(`chat:room:${roomId}:uids:online`, roomId, 0, 39, true),
Messaging.getMessages({
@@ -460,6 +503,7 @@ module.exports = function (Messaging) {
user.getSettings(uid),
Messaging.isRoomOwner(uid, roomId),
io.getUidsInRoom(`chat_room_${roomId}`),
getNotificationOptions(),
]);
users.forEach((user) => {
@@ -481,6 +525,8 @@ module.exports = function (Messaging) {
room.showUserInput = !room.maximumUsersInChatRoom || room.maximumUsersInChatRoom > 2;
room.isAdminOrGlobalMod = isAdmin || isGlobalMod;
room.isAdmin = isAdmin;
room.notificationOptions = notifOptions.options;
room.notificationOptionsIcon = notifOptions.selectedIcon;
const payload = await plugins.hooks.fire('filter:messaging.loadRoom', { uid, data, room });
return payload.room;

View File

@@ -33,6 +33,25 @@ module.exports = function (Messaging) {
};
Messaging.hasRead = async (uids, roomId) => {
if (!uids.length) {
return [];
}
const roomData = await Messaging.getRoomData(roomId);
if (!roomData) {
return uids.map(() => false);
}
if (roomData.public) {
const [userTimestamps, mids] = await Promise.all([
db.getObjectsFields(uids.map(uid => `uid:${uid}:chat:rooms:read`), [roomId]),
db.getSortedSetRevRangeWithScores(`chat:room:${roomId}:mids`, 0, 0),
]);
const lastMsgTimestamp = mids[0] ? mids[0].score : 0;
return uids.map(
(uid, index) => !userTimestamps[index] ||
!userTimestamps[index][roomId] ||
parseInt(userTimestamps[index][roomId], 10) > lastMsgTimestamp
);
}
const isMembers = await db.isMemberOfSortedSets(
uids.map(uid => `uid:${uid}:chat:rooms:unread`),
roomId