Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
14538f3
send ephemeral message
gabriellsh Sep 21, 2023
352634e
register app and first actions
gabriellsh Sep 21, 2023
2462d5c
translations
gabriellsh Sep 21, 2023
06a3ebb
wip
gabriellsh Sep 26, 2023
507c808
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
gabriellsh Sep 28, 2023
859461c
Fix chat.postMessage types
gabriellsh Sep 28, 2023
eabac32
share-message
gabriellsh Sep 28, 2023
1740492
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
gabriellsh Oct 9, 2023
2d231f6
Real name and translations
gabriellsh Oct 11, 2023
c7e9a78
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
gabriellsh Oct 11, 2023
e7992ff
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
gabriellsh Oct 16, 2023
6a7abd6
finish tests
gabriellsh Oct 16, 2023
14990dc
Fix room permission
gabriellsh Oct 16, 2023
80242ec
undo chat.postmessage
gabriellsh Oct 16, 2023
8a9cc0e
Add cs
gabriellsh Oct 16, 2023
d8c6711
do not add user to room automatically anymore
gabriellsh Oct 17, 2023
16d9c41
Fix mention style
gabriellsh Oct 17, 2023
8fc1113
remove comment
gabriellsh Oct 20, 2023
d9054d8
remove unused vars
gabriellsh Oct 20, 2023
88a791e
Merge branch 'develop' into feat/mentionBot
scuciatto Oct 24, 2023
458aada
fix typo
guijun13 Oct 26, 2023
4ef3b7c
Fix share message
gabriellsh Oct 26, 2023
d6694f4
fix thread mentions
gabriellsh Oct 26, 2023
1ae93ca
Merge branch 'develop' into feat/mentionBot
gabriellsh Nov 7, 2023
f59ba5a
Add extra message
gabriellsh Nov 7, 2023
0248959
Review
gabriellsh Nov 9, 2023
d664a10
fix conflicts
yash-rajpal Nov 20, 2023
458e289
Merge branch 'develop' into feat/mentionBot
scuciatto Nov 20, 2023
eb5581a
fix conflicts
yash-rajpal Nov 22, 2023
2bd42a6
Merge branch 'develop' into feat/mentionBot
scuciatto Dec 1, 2023
c937f83
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] Dec 4, 2023
9b6f0b2
Merge branch 'develop' into feat/mentionBot
gabriellsh Dec 5, 2023
3d19d0f
Merge branch 'develop' into feat/mentionBot
scuciatto Dec 8, 2023
b79a4ed
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] Dec 14, 2023
f1ac5b5
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] Dec 14, 2023
c3cdff0
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] Dec 14, 2023
486954a
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] Dec 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fresh-socks-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": minor
---

Mentioning users that are not in the channel now dispatches a warning message with actions
1 change: 1 addition & 0 deletions apps/meteor/app/lib/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import './methods/deleteUserOwnAccount';
import './methods/executeSlashCommandPreview';
import './startup/filterATAllTag';
import './startup/filterATHereTag';
import './startup/mentionUserNotInChannel';
import './methods/getChannelHistory';
import './methods/getRoomJoinCode';
import './methods/getRoomRoles';
Expand Down
47 changes: 1 addition & 46 deletions apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Room } from '@rocket.chat/core-services';
import { Subscriptions, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import moment from 'moment';

import { callbacks } from '../../../../lib/callbacks';
Expand Down Expand Up @@ -341,50 +339,7 @@ export async function sendAllNotifications(message, room) {
return message;
}

const { sender, hasMentionToAll, hasMentionToHere, notificationMessage, mentionIds, mentionIdsWithoutGroups } =
await sendMessageNotifications(message, room);

// on public channels, if a mentioned user is not member of the channel yet, he will first join the channel and then be notified based on his preferences.
if (room.t === 'c') {
// get subscriptions from users already in room (to not send them a notification)
const mentions = [...mentionIdsWithoutGroups];
const cursor = Subscriptions.findByRoomIdAndUserIds(room._id, mentionIdsWithoutGroups, {
projection: { 'u._id': 1 },
});

for await (const subscription of cursor) {
const index = mentions.indexOf(subscription.u._id);
if (index !== -1) {
mentions.splice(index, 1);
}
}

const users = await Promise.all(
mentions.map(async (userId) => {
await Room.join({ room, user: { _id: userId } });

return userId;
}),
).catch((error) => {
throw new Meteor.Error(error);
});

const subscriptions = await Subscriptions.findByRoomIdAndUserIds(room._id, users).toArray();
users.forEach((userId) => {
const subscription = subscriptions.find((subscription) => subscription.u._id === userId);

void sendNotification({
subscription,
sender,
hasMentionToAll,
hasMentionToHere,
message,
notificationMessage,
room,
mentionIds,
});
});
}
await sendMessageNotifications(message, room);

return message;
}
Expand Down
143 changes: 143 additions & 0 deletions apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { api } from '@rocket.chat/core-services';
import type { IMessage } from '@rocket.chat/core-typings';
import { isDirectMessageRoom, isEditedMessage, isRoomFederated } from '@rocket.chat/core-typings';
import { Subscriptions, Rooms, Users, Settings } from '@rocket.chat/models';
import type { ActionsBlock } from '@rocket.chat/ui-kit';
import moment from 'moment';

import { callbacks } from '../../../../lib/callbacks';
import { getUserDisplayName } from '../../../../lib/getUserDisplayName';
import { isTruthy } from '../../../../lib/isTruthy';
import { i18n } from '../../../../server/lib/i18n';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';

const APP_ID = 'mention-core';
const getBlocks = (mentions: IMessage['mentions'], messageId: string, lng: string | undefined) => {
const stringifiedMentions = JSON.stringify(mentions);
return {
addUsersBlock: {
type: 'button',
appId: APP_ID,
blockId: messageId,
value: stringifiedMentions,
actionId: 'add-users',
text: {
type: 'plain_text',
text: i18n.t('Add_them', undefined, lng),
},
},
dismissBlock: {
type: 'button',
appId: APP_ID,
blockId: messageId,
value: stringifiedMentions,
actionId: 'dismiss',
text: {
type: 'plain_text',
text: i18n.t('Do_nothing', undefined, lng),
},
},
dmBlock: {
type: 'button',
appId: APP_ID,
value: stringifiedMentions,
blockId: messageId,
actionId: 'share-message',
text: {
type: 'plain_text',
text: i18n.t('Let_them_know', undefined, lng),
},
},
} as const;
};

callbacks.add(
'beforeSaveMessage',
async (message) => {
// TODO: check if I need to test this 60 second rule.
// If the message was edited, or is older than 60 seconds (imported)
// the notifications will be skipped, so we can also skip this validation
if (isEditedMessage(message) || (message.ts && Math.abs(moment(message.ts).diff(moment())) > 60000) || !message.mentions) {
return message;
}

const room = await Rooms.findOneById(message.rid);
if (!room || isDirectMessageRoom(room) || isRoomFederated(room) || room.t === 'l') {
return message;
}

const mentions = message.mentions.filter(({ _id }) => _id !== 'all' && _id !== 'here');
Comment thread
gabriellsh marked this conversation as resolved.
Outdated

if (!mentions.length) {
return message;
}

const subs = await Subscriptions.findByRoomIdAndUserIds(
message.rid,
mentions.map(({ _id }) => _id),
{ projection: { u: 1 } },
).toArray();

// get all users that are mentioned but not in the channel
const mentionsUsersNotInChannel = mentions.filter(({ _id }) => !subs.some((sub) => sub.u._id === _id));

if (!mentionsUsersNotInChannel.length) {
return message;
}

const canAddUsersToThisRoom = await hasPermissionAsync(message.u._id, 'add-user-to-joined-room', message.rid);
const canAddToAnyRoom = await (room.t === 'c'
Comment thread
gabriellsh marked this conversation as resolved.
? hasPermissionAsync(message.u._id, 'add-user-to-any-c-room')
: hasPermissionAsync(message.u._id, 'add-user-to-any-p-room'));
const canDMUsers = await hasPermissionAsync(message.u._id, 'create-d'); // TODO: Perhaps check if user has DM with mentioned user (might be too expensive)
const canAddUsers = canAddUsersToThisRoom || canAddToAnyRoom;
const { language } = (await Users.findOneById(message.u._id)) || {};

const actionBlocks = getBlocks(mentionsUsersNotInChannel, message._id, language);
const elements: ActionsBlock['elements'] = [
canAddUsers && actionBlocks.addUsersBlock,
(canAddUsers || canDMUsers) && actionBlocks.dismissBlock,
canDMUsers && actionBlocks.dmBlock,
].filter(isTruthy);

const messageLabel = canAddUsers
? 'You_mentioned___mentions__but_theyre_not_in_this_room'
: 'You_mentioned___mentions__but_theyre_not_in_this_room_You_can_ask_a_room_admin_to_add_them';

const { value: useRealName } = (await Settings.findOneById('UI_Use_Real_Name')) || {};

const usernamesOrNames = mentionsUsersNotInChannel.map(
({ username, name }) => `*${getUserDisplayName(name, username, Boolean(useRealName))}*`,
);

const mentionsText = usernamesOrNames.join(', ');

// TODO: Mentions style
void api.broadcast('notify.ephemeralMessage', message.u._id, message.rid, {
msg: '',
mentions: mentionsUsersNotInChannel,
tmid: message.tmid,
blocks: [
{
appId: APP_ID,
type: 'section',
text: {
type: 'mrkdwn',
text: i18n.t(messageLabel, { mentions: mentionsText }, language),
},
} as const,
Boolean(elements.length) &&
({
type: 'actions',
appId: APP_ID,
elements,
} as const),
].filter(isTruthy),
private: true,
});

return message;
},
callbacks.priority.LOW,
'mention-user-not-in-channel',
);
6 changes: 6 additions & 0 deletions apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@
"add-team-channel_description": "Permission to add a channel to a team",
"add-team-member": "Add Team Member",
"add-team-member_description": "Permission to add members to a team",
"Add_them": "Add them",
"add-user": "Add User",
"add-user_description": "Permission to add new users to the server via users screen",
"add-user-to-any-c-room": "Add User to Any Public Channel",
Expand Down Expand Up @@ -1703,6 +1704,7 @@
"Do_not_display_unread_counter": "Do not display any counter of this channel",
"Do_not_provide_this_code_to_anyone": "Do not provide this code to anyone.",
"Do_Nothing": "Do Nothing",
"Do_nothing": "Do nothing",
"Do_you_have_any_notes_for_this_conversation": "Do you have any notes for this conversation?",
"Do_you_want_to_accept": "Do you want to accept?",
"Do_you_want_to_change_to_s_question": "Do you want to change to <strong>%s</strong>?",
Expand Down Expand Up @@ -3055,6 +3057,7 @@
"leave-p": "Leave Private Groups",
"leave-p_description": "Permission to leave private groups",
"Lets_get_you_new_one": "Let's get you a new one!",
"Let_them_know": "Let them know",
"License": "License",
"Line": "Line",
"Link": "Link",
Expand Down Expand Up @@ -5793,6 +5796,9 @@
"You_have_not_verified_your_email": "You have not verified your email.",
"You_have_successfully_unsubscribed": "You have successfully unsubscribed from our Mailling List.",
"You_must_join_to_view_messages_in_this_channel": "You must join to view messages in this channel",
"You_mentioned___mentions__but_theyre_not_in_this_room": "You mentioned {{mentions}}, but they're not in this room.",
"You_mentioned___mentions__but_theyre_not_in_this_room_You_can_ask_a_room_admin_to_add_them": "You mentioned {{mentions}}, but they're not in this room. You can ask a room admin to add them.",
"You_mentioned___mentions__but_theyre_not_in_this_room_You_let_them_know_via_dm": "You mentioned {{mentions}}, but they're not in this room. You let them know via DM.",
"You_need_confirm_email": "You need to confirm your email to login!",
"You_need_install_an_extension_to_allow_screen_sharing": "You need install an extension to allow screen sharing",
"You_need_to_change_your_password": "You need to change your password",
Expand Down
115 changes: 115 additions & 0 deletions apps/meteor/server/modules/core-apps/mention.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { api } from '@rocket.chat/core-services';
import type { IUiKitCoreApp } from '@rocket.chat/core-services';
import type { IMessage } from '@rocket.chat/core-typings';
import { Subscriptions, Messages } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import { processWebhookMessage } from '../../../app/lib/server/functions/processWebhookMessage';
import { addUsersToRoomMethod } from '../../../app/lib/server/methods/addUsersToRoom';
import { i18n } from '../../lib/i18n';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';

const retrieveMentionsFromPayload = (stringifiedMentions: string): Exclude<IMessage['mentions'], undefined> => {
try {
const mentions = JSON.parse(stringifiedMentions);
if (!Array.isArray(mentions) || !mentions.length || !('username' in mentions[0])) {
throw new Error('Invalid payload');
}
return mentions;
} catch (error) {
throw new Error('Invalid payload');
}
};

export class MentionModule implements IUiKitCoreApp {
appId = 'mention-core';

async blockAction(payload: any): Promise<any> {
const {
actionId,
payload: { value: stringifiedMentions, blockId: referenceMessageId },
} = payload;

const mentions = retrieveMentionsFromPayload(stringifiedMentions);

const usernames = mentions.map(({ username }) => username);

const message = await Messages.findOneById(referenceMessageId);
Comment thread
gabriellsh marked this conversation as resolved.
Outdated

if (!message) {
throw new Error('Mention bot - Failed to retrieve message information');
}

if (actionId === 'dismiss') {
void api.broadcast('notify.ephemeralMessage', payload.user._id, payload.room, {
msg: i18n.t(
'You_mentioned___mentions__but_theyre_not_in_this_room',
{
mentions: `@${usernames.join(', @')}`,
Comment thread
gabriellsh marked this conversation as resolved.
Outdated
},
payload.user.language,
),
_id: payload.message,
tmid: message.tmid,
mentions,
});
return;
}

if (actionId === 'add-users') {
void addUsersToRoomMethod(payload.user._id, { rid: payload.room, users: usernames as string[] }, payload.user);
void api.broadcast('notify.ephemeralMessage', payload.user._id, payload.room, {
msg: i18n.t(
'You_mentioned___mentions__but_theyre_not_in_this_room',
{
mentions: `@${usernames.join(', @')}`,
},
payload.user.language,
),
tmid: message.tmid,
_id: payload.message,
mentions,
});
return;
}

if (actionId === 'share-message') {
const sub = await Subscriptions.findOneByRoomIdAndUserId(payload.room, payload.user._id);
Comment thread
gabriellsh marked this conversation as resolved.
Outdated
// this should exist since the event is fired from withing the room (e.g the user sent a message)
if (!sub) {
throw new Error('Mention bot - Failed to retrieve room information');
}

const roomPath = roomCoordinator.getRouteLink(sub.t, { rid: sub.rid, name: sub.name });
if (!roomPath) {
throw new Error('Mention bot - Failed to retrieve path to room');
}

const link = new URL(Meteor.absoluteUrl(roomPath));
link.searchParams.set('msg', message._id);
const text = `[ ](${link.toString()})`;
Comment thread
gabriellsh marked this conversation as resolved.
Outdated

// forwards message to all DMs
await processWebhookMessage(
Comment thread
gabriellsh marked this conversation as resolved.
{
roomId: mentions.map(({ _id }) => _id),
text,
},
payload.user,
);

void api.broadcast('notify.ephemeralMessage', payload.user._id, payload.room, {
msg: i18n.t(
'You_mentioned___mentions__but_theyre_not_in_this_room_You_let_them_know_via_dm',
{
mentions: `@${usernames.join(', @')}`,
},
payload.user.language,
),
tmid: message.tmid,
_id: payload.message,
mentions,
});
}
}
}
2 changes: 2 additions & 0 deletions apps/meteor/server/startup/coreApps.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { BannerModule } from '../modules/core-apps/banner.module';
import { MentionModule } from '../modules/core-apps/mention.module';
import { Nps } from '../modules/core-apps/nps.module';
import { VideoConfModule } from '../modules/core-apps/videoconf.module';
import { registerCoreApp } from '../services/uikit-core-app/service';

registerCoreApp(new Nps());
registerCoreApp(new BannerModule());
registerCoreApp(new VideoConfModule());
registerCoreApp(new MentionModule());
Loading