Skip to content

Commit

Permalink
Merge branch 'chore-stop-using-callbacks-for-mentions' of github.com:…
Browse files Browse the repository at this point in the history
…RocketChat/Rocket.Chat into chore-stop-using-callbacks-for-mentions
  • Loading branch information
sampaiodiego committed Dec 8, 2023
2 parents 307f407 + d654310 commit ef1a9ca
Show file tree
Hide file tree
Showing 6 changed files with 692 additions and 111 deletions.
1 change: 1 addition & 0 deletions apps/meteor/app/lib/server/functions/parseUrlsInMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getMessageUrlRegex } from '../../../../lib/getMessageUrlRegex';
import { Markdown } from '../../../markdown/server';
import { settings } from '../../../settings/server';

// TODO move this function to message service to be used like a "beforeSaveMessage" hook
export const parseUrlsInMessage = (message: AtLeast<IMessage, 'msg'> & { parseUrls?: boolean }, previewUrls?: string[]) => {
if (message.parseUrls === false) {
return message;
Expand Down
1 change: 0 additions & 1 deletion apps/meteor/app/oembed/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
import './jumpToMessage';
import './providers';
import './server';
108 changes: 0 additions & 108 deletions apps/meteor/app/oembed/server/jumpToMessage.ts

This file was deleted.

162 changes: 162 additions & 0 deletions apps/meteor/server/services/messages/hooks/BeforeSaveJumpToMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import QueryString from 'querystring';
import URL from 'url';

import type { MessageAttachment, IMessage, IUser, IOmnichannelRoom, IRoom } from '@rocket.chat/core-typings';
import { isOmnichannelRoom, isQuoteAttachment } from '@rocket.chat/core-typings';

import { createQuoteAttachment } from '../../../../lib/createQuoteAttachment';

const recursiveRemoveAttachments = (attachments: MessageAttachment, deep = 1, quoteChainLimit: number): MessageAttachment => {
if (attachments && isQuoteAttachment(attachments)) {
if (deep < quoteChainLimit - 1) {
attachments.attachments?.map((msg) => recursiveRemoveAttachments(msg, deep + 1, quoteChainLimit));
} else {
delete attachments.attachments;
}
}

return attachments;
};

const validateAttachmentDeepness = (message: IMessage, quoteChainLimit: number): IMessage => {
if (!message?.attachments) {
return message;
}

if ((message.attachments && quoteChainLimit < 2) || isNaN(quoteChainLimit)) {
delete message.attachments;
}

message.attachments = message.attachments?.map((attachment) => recursiveRemoveAttachments(attachment, 1, quoteChainLimit));

return message;
};

type JumpToMessageInit = {
getMessages(messageIds: IMessage['_id'][]): Promise<IMessage[]>;
getRooms(roomIds: IRoom['_id'][]): Promise<IRoom[] | IOmnichannelRoom[] | null>;
canAccessRoom(room: IRoom, user: Pick<IUser, '_id' | 'username' | 'name' | 'language'>): Promise<boolean>;
getUserAvatarURL(user?: string): string;
};

/**
* Transform URLs in messages into quote attachments
*/
export class BeforeSaveJumpToMessage {
private getMessages: JumpToMessageInit['getMessages'];

private getRooms: JumpToMessageInit['getRooms'];

private canAccessRoom: JumpToMessageInit['canAccessRoom'];

private getUserAvatarURL: JumpToMessageInit['getUserAvatarURL'];

constructor(options: JumpToMessageInit) {
this.getMessages = options.getMessages;
this.getRooms = options.getRooms;
this.canAccessRoom = options.canAccessRoom;
this.getUserAvatarURL = options.getUserAvatarURL;
}

async createAttachmentForMessageURLs({
message,
user: currentUser,
config,
}: {
message: IMessage;
user: Pick<IUser, '_id' | 'username' | 'name' | 'language'>;
config: {
chainLimit: number;
siteUrl: string;
useRealName: boolean;
};
}): Promise<IMessage> {
// if no message is present, or the message doesn't have any URL, skip
if (!message?.urls?.length) {
return message;
}

const linkedMessages = message.urls
.filter((item) => item.url.includes(config.siteUrl))
.map((item) => {
const urlObj = URL.parse(item.url);

// if the URL doesn't have query params (doesn't reference message) skip
if (!urlObj.query) {
return;
}

const { msg: msgId } = QueryString.parse(urlObj.query);

if (typeof msgId !== 'string') {
return;
}

return { msgId, url: item.url };
})
.filter(Boolean);

const msgs = await this.getMessages(linkedMessages.map((linkedMsg) => linkedMsg?.msgId) as string[]);

const validMessages = msgs.filter((msg) => validateAttachmentDeepness(msg, config.chainLimit));

const rooms = await this.getRooms(validMessages.map((msg) => msg.rid));

const roomsWithPermission =
rooms &&
(await Promise.all(
rooms.map(async (room) => {
if (!!message.token && isOmnichannelRoom(room) && !!room.v?.token && message.token === room.v.token) {
return room;
}

if (currentUser && (await this.canAccessRoom(room, currentUser))) {
return room;
}
}),
));

const validRooms = roomsWithPermission?.filter((room) => !!room);

const { useRealName } = config;

const quotes = [];

for (const item of message.urls) {
if (!item.url.includes(config.siteUrl)) {
continue;
}

const linkedMessage = linkedMessages.find((msg) => msg?.url === item.url);
if (!linkedMessage) {
continue;
}

const messageFromUrl = validMessages.find((msg) => msg._id === linkedMessage.msgId);
if (!messageFromUrl) {
continue;
}

if (!validRooms?.find((room) => room?._id === messageFromUrl.rid)) {
continue;
}

item.ignoreParse = true;

// Only QuoteAttachments have "message_link" property
const index = message.attachments?.findIndex((a) => isQuoteAttachment(a) && a.message_link === item.url);
if (index !== undefined && index > -1) {
message.attachments?.splice(index, 1);
}

quotes.push(createQuoteAttachment(messageFromUrl, item.url, useRealName, this.getUserAvatarURL(messageFromUrl.u.username)));
}

if (quotes.length > 0) {
message.attachments = message.attachments || [];
message.attachments.push(...quotes);
}

return message;
}
}
31 changes: 29 additions & 2 deletions apps/meteor/server/services/messages/service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import type { IMessageService } from '@rocket.chat/core-services';
import { ServiceClassInternal } from '@rocket.chat/core-services';
import { Authorization, ServiceClassInternal } from '@rocket.chat/core-services';
import { type IMessage, type MessageTypesValues, type IUser, type IRoom, isEditedMessage } from '@rocket.chat/core-typings';
import { Messages } from '@rocket.chat/models';
import { Messages, Rooms } from '@rocket.chat/models';

import { deleteMessage } from '../../../app/lib/server/functions/deleteMessage';
import { sendMessage } from '../../../app/lib/server/functions/sendMessage';
import { updateMessage } from '../../../app/lib/server/functions/updateMessage';
import { executeSendMessage } from '../../../app/lib/server/methods/sendMessage';
import { executeSetReaction } from '../../../app/reactions/server/setReaction';
import { settings } from '../../../app/settings/server';
import { getUserAvatarURL } from '../../../app/utils/server/getUserAvatarURL';
import { broadcastMessageSentEvent } from '../../modules/watchers/lib/messages';
import { BeforeSaveBadWords } from './hooks/BeforeSaveBadWords';
import { BeforeSaveJumpToMessage } from './hooks/BeforeSaveJumpToMessage';
import { mentionServer } from './hooks/BeforeSaveMentions';
import { BeforeSavePreventMention } from './hooks/BeforeSavePreventMention';
import { BeforeSaveSpotify } from './hooks/BeforeSaveSpotify';
Expand All @@ -24,10 +26,26 @@ export class MessageService extends ServiceClassInternal implements IMessageServ

private spotify: BeforeSaveSpotify;

private jumpToMessage: BeforeSaveJumpToMessage;

async created() {
this.preventMention = new BeforeSavePreventMention(this.api);
this.badWords = new BeforeSaveBadWords();
this.spotify = new BeforeSaveSpotify();
this.jumpToMessage = new BeforeSaveJumpToMessage({
getMessages(messageIds) {
return Messages.findVisibleByIds(messageIds).toArray();
},
getRooms(roomIds) {
return Rooms.findByIds(roomIds).toArray();
},
canAccessRoom(room: IRoom, user: IUser): Promise<boolean> {
return Authorization.canAccessRoom(room, user);
},
getUserAvatarURL(user?: string): string {
return (user && getUserAvatarURL(user)) || '';
},
});

await this.configureBadWords();
}
Expand Down Expand Up @@ -106,6 +124,15 @@ export class MessageService extends ServiceClassInternal implements IMessageServ
message = await mentionServer.execute(message);
message = await this.badWords.filterBadWords({ message });
message = await this.spotify.convertSpotifyLinks({ message });
message = await this.jumpToMessage.createAttachmentForMessageURLs({
message,
user,
config: {
chainLimit: settings.get<number>('Message_QuoteChainLimit'),
siteUrl: settings.get<string>('Site_Url'),
useRealName: settings.get<boolean>('UI_Use_Real_Name'),
},
});

if (!this.isEditedOrOld(message)) {
await Promise.all([
Expand Down
Loading

0 comments on commit ef1a9ca

Please sign in to comment.