Repository navigation
feat: Bot warning when mentioning users not in a channel #30464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 24 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
14538f3
send ephemeral message
gabriellsh 352634e
register app and first actions
gabriellsh 2462d5c
translations
gabriellsh 06a3ebb
wip
gabriellsh 507c808
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
gabriellsh 859461c
Fix chat.postMessage types
gabriellsh eabac32
share-message
gabriellsh 1740492
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
gabriellsh 2d231f6
Real name and translations
gabriellsh c7e9a78
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
gabriellsh e7992ff
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
gabriellsh 6a7abd6
finish tests
gabriellsh 14990dc
Fix room permission
gabriellsh 80242ec
undo chat.postmessage
gabriellsh 8a9cc0e
Add cs
gabriellsh d8c6711
do not add user to room automatically anymore
gabriellsh 16d9c41
Fix mention style
gabriellsh 8fc1113
remove comment
gabriellsh d9054d8
remove unused vars
gabriellsh 88a791e
Merge branch 'develop' into feat/mentionBot
scuciatto 458aada
fix typo
guijun13 4ef3b7c
Fix share message
gabriellsh d6694f4
fix thread mentions
gabriellsh 1ae93ca
Merge branch 'develop' into feat/mentionBot
gabriellsh f59ba5a
Add extra message
gabriellsh 0248959
Review
gabriellsh d664a10
fix conflicts
yash-rajpal 458e289
Merge branch 'develop' into feat/mentionBot
scuciatto eb5581a
fix conflicts
yash-rajpal 2bd42a6
Merge branch 'develop' into feat/mentionBot
scuciatto c937f83
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] 9b6f0b2
Merge branch 'develop' into feat/mentionBot
gabriellsh 3d19d0f
Merge branch 'develop' into feat/mentionBot
scuciatto b79a4ed
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] f1ac5b5
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] c3cdff0
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] 486954a
Merge branch 'develop' into feat/mentionBot
kodiakhq[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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'); | ||
|
|
||
| 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' | ||
|
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', | ||
| ); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
|
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(', @')}`, | ||
|
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); | ||
|
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()})`; | ||
|
gabriellsh marked this conversation as resolved.
Outdated
|
||
|
|
||
| // forwards message to all DMs | ||
| await processWebhookMessage( | ||
|
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, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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()); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.