snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
This commit is contained in:
36
apps/meteor/server/modules/core-apps/banner.module.ts
Normal file
36
apps/meteor/server/modules/core-apps/banner.module.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Banner } from '@rocket.chat/core-services';
|
||||
import type { IUiKitCoreApp, UiKitCoreAppViewClosedPayload } from '@rocket.chat/core-services';
|
||||
import type * as UiKit from '@rocket.chat/ui-kit';
|
||||
|
||||
export class BannerModule implements IUiKitCoreApp {
|
||||
appId = 'banner-core';
|
||||
|
||||
// when banner view is closed we need to dismiss that banner for that user
|
||||
async viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction> {
|
||||
const {
|
||||
payload: { view: { viewId: bannerId } = {} },
|
||||
user: { _id: userId } = {},
|
||||
} = payload;
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('invalid user');
|
||||
}
|
||||
|
||||
if (!bannerId) {
|
||||
throw new Error('invalid banner');
|
||||
}
|
||||
|
||||
if (!payload.triggerId) {
|
||||
throw new Error('invalid triggerId');
|
||||
}
|
||||
|
||||
await Banner.dismiss(userId, bannerId);
|
||||
|
||||
return {
|
||||
type: 'banner.close',
|
||||
triggerId: payload.triggerId,
|
||||
appId: payload.appId,
|
||||
viewId: bannerId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
import { Banner } from '@rocket.chat/core-services';
|
||||
import type {
|
||||
IUiKitCoreApp,
|
||||
UiKitCoreAppBlockActionPayload,
|
||||
UiKitCoreAppViewClosedPayload,
|
||||
UiKitCoreAppViewSubmitPayload,
|
||||
} from '@rocket.chat/core-services';
|
||||
import type { Cloud, IUser } from '@rocket.chat/core-typings';
|
||||
import { Banners } from '@rocket.chat/models';
|
||||
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
|
||||
import { isTruthy } from '@rocket.chat/tools';
|
||||
import type * as UiKit from '@rocket.chat/ui-kit';
|
||||
|
||||
import { CloudWorkspaceConnectionError } from '../../../lib/errors/CloudWorkspaceConnectionError';
|
||||
import { InvalidCloudAnnouncementInteractionError } from '../../../lib/errors/InvalidCloudAnnouncementInteractionError';
|
||||
import { InvalidCoreAppInteractionError } from '../../../lib/errors/InvalidCoreAppInteractionError';
|
||||
import { getWorkspaceAccessToken } from '../../lib/cloud';
|
||||
import { assertNotOfflineLicense } from '../../lib/cloud/offlineLicense';
|
||||
import { syncWorkspace } from '../../lib/cloud/syncWorkspace';
|
||||
import { SystemLogger } from '../../lib/logger/system';
|
||||
import { settings } from '../../settings';
|
||||
|
||||
type CloudAnnouncementInteractant =
|
||||
| {
|
||||
user: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
}
|
||||
| {
|
||||
visitor: Pick<NonNullable<UiKitCoreAppBlockActionPayload['visitor']>, 'id' | 'username' | 'name' | 'department' | 'phone'>;
|
||||
};
|
||||
|
||||
type CloudAnnouncementInteractionRequest = UiKit.UserInteraction & CloudAnnouncementInteractant;
|
||||
|
||||
export class CloudAnnouncementsModule implements IUiKitCoreApp {
|
||||
appId = 'cloud-announcements-core';
|
||||
|
||||
protected async getWorkspaceAccessToken() {
|
||||
return getWorkspaceAccessToken(true);
|
||||
}
|
||||
|
||||
protected getCloudUrl() {
|
||||
return settings.get('Cloud_Url');
|
||||
}
|
||||
|
||||
blockAction(payload: UiKitCoreAppBlockActionPayload): Promise<UiKit.ServerInteraction | undefined> {
|
||||
return this.handlePayload(payload);
|
||||
}
|
||||
|
||||
viewSubmit(payload: UiKitCoreAppViewSubmitPayload): Promise<UiKit.ServerInteraction | undefined> {
|
||||
return this.handlePayload(payload);
|
||||
}
|
||||
|
||||
async viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction | undefined> {
|
||||
const {
|
||||
payload: { view: { viewId, id } = {} },
|
||||
user: { _id: userId } = {},
|
||||
} = payload;
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('invalid user');
|
||||
}
|
||||
|
||||
if (!id && !viewId) {
|
||||
throw new Error('invalid view');
|
||||
}
|
||||
|
||||
if (!payload.triggerId) {
|
||||
throw new Error('invalid triggerId');
|
||||
}
|
||||
|
||||
// For backwards compatibility: we prefer to use viewId, but some legacy banners
|
||||
// may only have id. We fetch all matching banners and prioritize viewId match.
|
||||
const bannerIds = [viewId, id].filter((bannerId) => isTruthy(bannerId));
|
||||
const banners = await Banners.findByIds(bannerIds).toArray();
|
||||
const announcement = banners.find((b) => b._id === viewId) || banners.find((b) => b._id === id);
|
||||
if (!announcement) {
|
||||
throw new Error('Banner not found');
|
||||
}
|
||||
|
||||
await Banner.dismiss(userId, announcement._id);
|
||||
|
||||
const type = announcement.surface === 'banner' ? 'banner.close' : 'modal.close';
|
||||
|
||||
// for viewClosed we just need to let Cloud know that the banner was closed, no need to wait for the response
|
||||
|
||||
void this.handlePayload(payload);
|
||||
|
||||
return {
|
||||
type,
|
||||
triggerId: payload.triggerId,
|
||||
appId: payload.appId,
|
||||
viewId: announcement._id,
|
||||
};
|
||||
}
|
||||
|
||||
protected async handlePayload(
|
||||
payload: UiKitCoreAppBlockActionPayload | UiKitCoreAppViewSubmitPayload | UiKitCoreAppViewClosedPayload,
|
||||
): Promise<UiKit.ServerInteraction | undefined> {
|
||||
const interactant = this.getInteractant(payload);
|
||||
const interaction = this.getInteraction(payload);
|
||||
|
||||
try {
|
||||
const serverInteraction = await this.pushUserInteraction(interactant, interaction);
|
||||
|
||||
if (serverInteraction.appId !== this.appId) {
|
||||
throw new InvalidCloudAnnouncementInteractionError(`Invalid appId`);
|
||||
}
|
||||
|
||||
if (serverInteraction.triggerId !== interaction.triggerId) {
|
||||
throw new InvalidCloudAnnouncementInteractionError(`Invalid triggerId`);
|
||||
}
|
||||
|
||||
return serverInteraction;
|
||||
} catch (err) {
|
||||
SystemLogger.error({ err });
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected getInteractant(
|
||||
payload: UiKitCoreAppBlockActionPayload | UiKitCoreAppViewSubmitPayload | UiKitCoreAppViewClosedPayload,
|
||||
): CloudAnnouncementInteractant {
|
||||
if (payload.user) {
|
||||
return {
|
||||
user: {
|
||||
_id: payload.user._id,
|
||||
username: payload.user.username,
|
||||
name: payload.user.name,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if ('visitor' in payload && payload.visitor) {
|
||||
return {
|
||||
visitor: {
|
||||
id: payload.visitor.id,
|
||||
username: payload.visitor.username,
|
||||
name: payload.visitor.name,
|
||||
department: payload.visitor.department,
|
||||
phone: payload.visitor.phone,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
throw new CloudWorkspaceConnectionError(`Invalid user data received from Rocket.Chat Cloud`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the payload received from the Core App back to the format the UI sends from the client
|
||||
*/
|
||||
protected getInteraction(
|
||||
payload: UiKitCoreAppBlockActionPayload | UiKitCoreAppViewSubmitPayload | UiKitCoreAppViewClosedPayload,
|
||||
): UiKit.UserInteraction {
|
||||
if (payload.type === 'blockAction' && payload.container?.type === 'message') {
|
||||
const {
|
||||
actionId,
|
||||
payload: { blockId, value },
|
||||
message,
|
||||
room,
|
||||
triggerId,
|
||||
} = payload;
|
||||
|
||||
if (!actionId || !blockId || !triggerId) {
|
||||
throw new InvalidCoreAppInteractionError();
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'blockAction',
|
||||
actionId,
|
||||
payload: {
|
||||
blockId,
|
||||
value,
|
||||
},
|
||||
container: {
|
||||
type: 'message',
|
||||
id: String(message),
|
||||
},
|
||||
mid: String(message),
|
||||
tmid: undefined,
|
||||
rid: String(room),
|
||||
triggerId,
|
||||
};
|
||||
}
|
||||
|
||||
if (payload.type === 'blockAction' && payload.container?.type === 'view') {
|
||||
const {
|
||||
actionId,
|
||||
payload: { blockId, value },
|
||||
container: { id },
|
||||
triggerId,
|
||||
} = payload;
|
||||
|
||||
if (!actionId || !blockId || !triggerId) {
|
||||
throw new InvalidCoreAppInteractionError();
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'blockAction',
|
||||
actionId,
|
||||
payload: {
|
||||
blockId,
|
||||
value,
|
||||
},
|
||||
container: {
|
||||
type: 'view',
|
||||
id,
|
||||
},
|
||||
triggerId,
|
||||
};
|
||||
}
|
||||
|
||||
if (payload.type === 'viewClosed') {
|
||||
const {
|
||||
payload: { view, isCleared },
|
||||
triggerId,
|
||||
} = payload;
|
||||
|
||||
if (!view?.id || !triggerId) {
|
||||
throw new InvalidCoreAppInteractionError();
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'viewClosed',
|
||||
payload: {
|
||||
viewId: view.id,
|
||||
view: view as any,
|
||||
isCleared: Boolean(isCleared),
|
||||
},
|
||||
triggerId,
|
||||
};
|
||||
}
|
||||
|
||||
if (payload.type === 'viewSubmit') {
|
||||
const {
|
||||
payload: { view },
|
||||
triggerId,
|
||||
} = payload;
|
||||
|
||||
if (!view?.id || !triggerId) {
|
||||
throw new InvalidCoreAppInteractionError();
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'viewSubmit',
|
||||
payload: {
|
||||
view: view as any,
|
||||
},
|
||||
triggerId,
|
||||
viewId: view.id,
|
||||
};
|
||||
}
|
||||
|
||||
throw new InvalidCoreAppInteractionError();
|
||||
}
|
||||
|
||||
protected async pushUserInteraction(
|
||||
interactant: CloudAnnouncementInteractant,
|
||||
userInteraction: UiKit.UserInteraction,
|
||||
): Promise<UiKit.ServerInteraction> {
|
||||
assertNotOfflineLicense();
|
||||
|
||||
const token = await this.getWorkspaceAccessToken();
|
||||
|
||||
const request: CloudAnnouncementInteractionRequest = {
|
||||
...interactant,
|
||||
...userInteraction,
|
||||
};
|
||||
|
||||
const response = await fetch(`${this.getCloudUrl()}/api/v3/comms/workspace/interaction`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
ignoreSsrfValidation: true,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const { error } = await response.json();
|
||||
throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`);
|
||||
}
|
||||
|
||||
const payload: Cloud.WorkspaceInteractionResponsePayload = await response.json();
|
||||
|
||||
const { serverInteraction, serverAction } = payload;
|
||||
|
||||
if (serverAction) {
|
||||
switch (serverAction) {
|
||||
case 'syncWorkspace': {
|
||||
await syncWorkspace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return serverInteraction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { UiKitCoreAppViewClosedPayload } from '@rocket.chat/core-services';
|
||||
import type * as UiKit from '@rocket.chat/ui-kit';
|
||||
|
||||
import { CloudAnnouncementsModule } from './cloudAnnouncements.module';
|
||||
|
||||
export class CloudSubscriptionCommunication extends CloudAnnouncementsModule {
|
||||
override appId = 'cloud-communication-core';
|
||||
|
||||
override async viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction> {
|
||||
const {
|
||||
payload: { view: { viewId } = {} },
|
||||
user: { _id: userId } = {},
|
||||
} = payload;
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('invalid user');
|
||||
}
|
||||
|
||||
if (!viewId) {
|
||||
throw new Error('invalid view');
|
||||
}
|
||||
|
||||
if (!payload.triggerId) {
|
||||
throw new Error('invalid triggerId');
|
||||
}
|
||||
|
||||
// for viewClosed we just need to let Cloud know that the banner was closed, no need to wait for the response
|
||||
|
||||
void this.handlePayload(payload);
|
||||
|
||||
return {
|
||||
type: 'modal.close',
|
||||
triggerId: payload.triggerId,
|
||||
appId: payload.appId,
|
||||
};
|
||||
}
|
||||
}
|
||||
118
apps/meteor/server/modules/core-apps/mention.module.ts
Normal file
118
apps/meteor/server/modules/core-apps/mention.module.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { api } from '@rocket.chat/core-services';
|
||||
import type { IUiKitCoreApp, UiKitCoreAppBlockActionPayload } from '@rocket.chat/core-services';
|
||||
import type { IMessage, IUser } from '@rocket.chat/core-typings';
|
||||
import { Subscriptions, Messages } from '@rocket.chat/models';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { i18n } from '../../lib/i18n';
|
||||
import { processWebhookMessage } from '../../lib/messages/processWebhookMessage';
|
||||
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
|
||||
import { addUsersToRoomMethod } from '../../meteor-methods/rooms/addUsersToRoom';
|
||||
|
||||
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: UiKitCoreAppBlockActionPayload): Promise<undefined> {
|
||||
const {
|
||||
actionId,
|
||||
payload: { value: stringifiedMentions, blockId: referenceMessageId },
|
||||
} = payload;
|
||||
|
||||
const user = payload.user!;
|
||||
const room = payload.room!;
|
||||
|
||||
const mentions = retrieveMentionsFromPayload(stringifiedMentions as string);
|
||||
|
||||
const usernames = mentions.map(({ username }) => username);
|
||||
|
||||
const message = await Messages.findOneById(referenceMessageId, { projection: { _id: 1, tmid: 1 } });
|
||||
|
||||
if (!message) {
|
||||
throw new Error('Mention bot - Failed to retrieve message information');
|
||||
}
|
||||
|
||||
const joinedUsernames = `@${usernames.join(', @')}`;
|
||||
|
||||
if (actionId === 'dismiss') {
|
||||
void api.broadcast('notify.ephemeralMessage', user._id, room, {
|
||||
msg: i18n.t('You_mentioned___mentions__but_theyre_not_in_this_room', {
|
||||
mentions: joinedUsernames,
|
||||
lng: user.language,
|
||||
}),
|
||||
_id: payload.message,
|
||||
tmid: message.tmid,
|
||||
mentions,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (actionId === 'add-users') {
|
||||
void addUsersToRoomMethod(user._id, { rid: room, users: usernames as string[] }, user);
|
||||
void api.broadcast('notify.ephemeralMessage', user._id, room, {
|
||||
msg: i18n.t('You_mentioned___mentions__but_theyre_not_in_this_room', {
|
||||
mentions: joinedUsernames,
|
||||
lng: user.language,
|
||||
}),
|
||||
tmid: message.tmid,
|
||||
_id: payload.message,
|
||||
mentions,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (actionId === 'share-message') {
|
||||
const sub = await Subscriptions.findOneByRoomIdAndUserId(room, user._id, { projection: { t: 1, rid: 1, name: 1 } });
|
||||
// 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 messageText = i18n.t('Youre_not_a_part_of__channel__and_I_mentioned_you_there', {
|
||||
channel: `#${sub.name}`,
|
||||
lng: user.language,
|
||||
});
|
||||
|
||||
const link = new URL(Meteor.absoluteUrl(roomPath));
|
||||
link.searchParams.set('msg', message._id);
|
||||
const text = `[ ](${link.toString()})\n${messageText}`;
|
||||
|
||||
// forwards message to all DMs
|
||||
await processWebhookMessage(
|
||||
{
|
||||
roomId: mentions.map(({ _id }) => _id),
|
||||
text,
|
||||
separateResponse: true, // so that messages are sent to other DMs even if one or more fails
|
||||
},
|
||||
user as IUser & { username: string },
|
||||
);
|
||||
|
||||
void api.broadcast('notify.ephemeralMessage', user._id, room, {
|
||||
msg: i18n.t('You_mentioned___mentions__but_theyre_not_in_this_room_You_let_them_know_via_dm', {
|
||||
mentions: joinedUsernames,
|
||||
lng: user.language,
|
||||
}),
|
||||
tmid: message.tmid,
|
||||
_id: payload.message,
|
||||
mentions,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
apps/meteor/server/modules/core-apps/nps.module.ts
Normal file
70
apps/meteor/server/modules/core-apps/nps.module.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { IUiKitCoreApp, UiKitCoreAppBlockActionPayload, UiKitCoreAppViewSubmitPayload } from '@rocket.chat/core-services';
|
||||
import { Banner, NPS } from '@rocket.chat/core-services';
|
||||
import type * as UiKit from '@rocket.chat/ui-kit';
|
||||
|
||||
import { createModal } from './nps/createModal';
|
||||
|
||||
export class Nps implements IUiKitCoreApp {
|
||||
appId = 'nps-core';
|
||||
|
||||
async blockAction(payload: UiKitCoreAppBlockActionPayload): Promise<UiKit.ServerInteraction> {
|
||||
const {
|
||||
triggerId,
|
||||
actionId,
|
||||
container: { id: viewId } = {},
|
||||
payload: { value: score, blockId: npsId },
|
||||
user,
|
||||
} = payload;
|
||||
|
||||
if (!viewId || !triggerId || !user || !npsId) {
|
||||
throw new Error('Invalid payload');
|
||||
}
|
||||
|
||||
const bannerId = viewId.replace(`${npsId}-`, '');
|
||||
|
||||
return createModal({
|
||||
type: actionId === 'nps-score' ? 'modal.update' : 'modal.open',
|
||||
id: `${npsId}-${bannerId}`,
|
||||
appId: this.appId,
|
||||
npsId,
|
||||
triggerId,
|
||||
score: String(score),
|
||||
user,
|
||||
});
|
||||
}
|
||||
|
||||
async viewSubmit(payload: UiKitCoreAppViewSubmitPayload): Promise<undefined> {
|
||||
if (!payload.payload?.view?.state || !payload.payload?.view?.id) {
|
||||
throw new Error('Invalid payload');
|
||||
}
|
||||
|
||||
const {
|
||||
payload: {
|
||||
view: { state, id: viewId },
|
||||
},
|
||||
user: { _id: userId, roles } = {},
|
||||
} = payload;
|
||||
|
||||
const [npsId] = Object.keys(state);
|
||||
|
||||
const bannerId = viewId.replace(`${npsId}-`, '');
|
||||
|
||||
const {
|
||||
[npsId]: { 'nps-score': score, comment },
|
||||
} = state;
|
||||
|
||||
await NPS.vote({
|
||||
npsId,
|
||||
userId,
|
||||
comment: String(comment),
|
||||
roles,
|
||||
score: Number(score),
|
||||
});
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('invalid user');
|
||||
}
|
||||
|
||||
await Banner.dismiss(userId, bannerId);
|
||||
}
|
||||
}
|
||||
89
apps/meteor/server/modules/core-apps/nps/createModal.ts
Normal file
89
apps/meteor/server/modules/core-apps/nps/createModal.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
|
||||
import { i18n } from '../../../lib/i18n';
|
||||
import { settings } from '../../../settings';
|
||||
|
||||
type ModalParams = {
|
||||
id: string;
|
||||
type: string;
|
||||
appId: string;
|
||||
npsId: string;
|
||||
triggerId: string;
|
||||
score: string;
|
||||
user: IUser;
|
||||
};
|
||||
|
||||
export const createModal = ({ type = 'modal.open', id, appId, npsId, triggerId, score, user }: ModalParams): any => {
|
||||
const language = user.language || settings.get('Language') || 'en';
|
||||
|
||||
return {
|
||||
type,
|
||||
triggerId,
|
||||
appId,
|
||||
view: {
|
||||
appId,
|
||||
type: 'modal',
|
||||
id,
|
||||
title: {
|
||||
type: 'plain_text',
|
||||
text: i18n.t('We_appreciate_your_feedback', { lng: language }),
|
||||
emoji: false,
|
||||
},
|
||||
submit: {
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: i18n.t('Send', { lng: language }),
|
||||
emoji: false,
|
||||
},
|
||||
actionId: 'send-vote',
|
||||
},
|
||||
close: {
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: i18n.t('Cancel', { lng: language }),
|
||||
emoji: false,
|
||||
},
|
||||
actionId: 'cancel',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
blockId: npsId,
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'linear_scale',
|
||||
initialValue: score,
|
||||
actionId: 'nps-score',
|
||||
preLabel: { type: 'plain_text', text: i18n.t('Not_likely', { lng: language }) },
|
||||
postLabel: {
|
||||
type: 'plain_text',
|
||||
text: i18n.t('Extremely_likely', { lng: language }),
|
||||
},
|
||||
},
|
||||
],
|
||||
label: {
|
||||
type: 'plain_text',
|
||||
text: i18n.t('Score', { lng: language }),
|
||||
emoji: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
blockId: npsId,
|
||||
type: 'input',
|
||||
element: {
|
||||
type: 'plain_text_input',
|
||||
multiline: true,
|
||||
actionId: 'comment',
|
||||
},
|
||||
label: {
|
||||
type: 'plain_text',
|
||||
text: i18n.t('Why_did_you_chose__score__', { score, lng: language }),
|
||||
emoji: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
57
apps/meteor/server/modules/core-apps/videoconf.module.ts
Normal file
57
apps/meteor/server/modules/core-apps/videoconf.module.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { IUiKitCoreApp, UiKitCoreAppBlockActionPayload } from '@rocket.chat/core-services';
|
||||
import { VideoConf } from '@rocket.chat/core-services';
|
||||
import type * as UiKit from '@rocket.chat/ui-kit';
|
||||
|
||||
import { i18n } from '../../lib/i18n';
|
||||
|
||||
export class VideoConfModule implements IUiKitCoreApp {
|
||||
appId = 'videoconf-core';
|
||||
|
||||
async blockAction(payload: UiKitCoreAppBlockActionPayload): Promise<UiKit.ServerInteraction | undefined> {
|
||||
const {
|
||||
triggerId,
|
||||
actionId,
|
||||
payload: { blockId: callId },
|
||||
user: { _id: userId } = {},
|
||||
} = payload;
|
||||
|
||||
if (!callId) {
|
||||
throw new Error('invalid call');
|
||||
}
|
||||
|
||||
if (actionId === 'join') {
|
||||
await VideoConf.join(userId, callId, {});
|
||||
}
|
||||
|
||||
if (actionId === 'info') {
|
||||
const blocks = await VideoConf.getInfo(callId, userId);
|
||||
|
||||
return {
|
||||
type: 'modal.open',
|
||||
triggerId,
|
||||
appId: this.appId,
|
||||
view: {
|
||||
appId: this.appId,
|
||||
id: `${callId}-info`,
|
||||
title: {
|
||||
type: 'plain_text',
|
||||
text: i18n.t('Video_Conference_Info'),
|
||||
emoji: false,
|
||||
},
|
||||
close: {
|
||||
type: 'button',
|
||||
appId: this.appId,
|
||||
blockId: callId,
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: i18n.t('Close'),
|
||||
emoji: false,
|
||||
},
|
||||
actionId: 'cancel',
|
||||
},
|
||||
blocks,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user