import type { ISubscription } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Logger } from '@rocket.chat/logger'; import { Messages, Subscriptions, Users } from '@rocket.chat/models'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { readSecondaryPreferred } from '../../database/readSecondaryPreferred'; import { canAccessRoomIdAsync } from '../../lib/authorization/canAccessRoom'; import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger'; import { parseMessageSearchQuery } from '../../lib/parseMessageSearchQuery'; import type { IRawSearchResult } from '../../lib/search/model/ISearchResult'; import { settings } from '../../settings'; const logger = new Logger('MessageSearch'); declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { messageSearch(text: string, rid?: string, limit?: number, offset?: number): IRawSearchResult | false; } } export const messageSearch = async function ( userId: string, text: string, rid?: string, limit?: number, offset?: number, ): Promise { check(text, String); check(rid, Match.Maybe(String)); check(limit, Match.Optional(Number)); check(offset, Match.Optional(Number)); // Don't process anything else if the user can't access the room if (rid) { if (!(await canAccessRoomIdAsync(rid, userId))) { return false; } } else if (settings.get('Search.defaultProvider.GlobalSearchEnabled') !== true) { return { message: { docs: [], }, }; } const user = (await Users.findOneById(userId)) || undefined; let parsedQuery: ReturnType; try { parsedQuery = parseMessageSearchQuery(text, { user, offset, limit, forceRegex: settings.get('Message_AlwaysSearchRegExp'), }); } catch (error: unknown) { logger.error({ msg: 'Error while parsing message search query', error }); if (error instanceof SyntaxError) { return { message: { docs: [] } }; } throw error; } const { query, options } = parsedQuery; if (Object.keys(query).length === 0) { return { message: { docs: [], }, }; } query.t = { $ne: 'rm', // hide removed messages (useful when searching for user messages) }; query._hidden = { $ne: true, // don't return _hidden messages }; if (rid) { query.rid = rid; } else { query.rid = { $in: user?._id ? (await Subscriptions.findByUserId(user._id).toArray()).map((subscription: ISubscription) => subscription.rid) : [], }; } try { return { message: { docs: await Messages.find(query, { // @ts-expect-error col.s.db is not typed readPreference: readSecondaryPreferred(Messages.col.s.db), ...options, }).toArray(), }, }; } catch (error) { logger.error({ msg: 'Error while finding messages', error }); throw new Error('error-while-finding-messages', { cause: error }); } }; Meteor.methods({ async messageSearch(text, rid, limit, offset) { methodDeprecationLogger.method('messageSearch', '9.0.0', '/v1/chat.search'); const currentUserId = Meteor.userId(); if (!currentUserId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'messageSearch', }); } return messageSearch(currentUserId, text, rid, limit, offset); }, });