From a99c8c0f53b119e58841979f319eb3e49cbe9b5f Mon Sep 17 00:00:00 2001 From: kshitijanurag Date: Wed, 25 Mar 2026 18:15:57 +0530 Subject: [PATCH] feat: guild messages search (#1583) Co-authored-by: Almeida --- deno/payloads/v10/message.ts | 197 ++++++++++++++++++++++++++++++++++- deno/payloads/v9/message.ts | 197 ++++++++++++++++++++++++++++++++++- deno/rest/common.ts | 3 +- deno/rest/v10/guild.ts | 151 +++++++++++++++++++++++++++ deno/rest/v10/mod.ts | 8 ++ deno/rest/v9/guild.ts | 151 +++++++++++++++++++++++++++ deno/rest/v9/mod.ts | 8 ++ payloads/v10/message.ts | 197 ++++++++++++++++++++++++++++++++++- payloads/v9/message.ts | 197 ++++++++++++++++++++++++++++++++++- rest/common.ts | 3 +- rest/v10/guild.ts | 151 +++++++++++++++++++++++++++ rest/v10/index.ts | 8 ++ rest/v9/guild.ts | 151 +++++++++++++++++++++++++++ rest/v9/index.ts | 8 ++ 14 files changed, 1424 insertions(+), 6 deletions(-) diff --git a/deno/payloads/v10/message.ts b/deno/payloads/v10/message.ts index c851a654..9a2a88a1 100644 --- a/deno/payloads/v10/message.ts +++ b/deno/payloads/v10/message.ts @@ -3,7 +3,7 @@ import type { Snowflake } from '../../globals.ts'; import type { _NonNullableFields } from '../../utils/internals.ts'; import type { APIApplication } from './application.ts'; -import type { APIChannel, ChannelType } from './channel.ts'; +import type { APIChannel, APIThreadChannel, APIThreadMember, ChannelType } from './channel.ts'; import type { APIPartialEmoji } from './emoji.ts'; import type { APIInteractionDataResolved, APIMessageInteraction, APIMessageInteractionMetadata } from './interactions.ts'; import type { APIRole } from './permissions.ts'; @@ -1998,3 +1998,198 @@ export interface APIMessagePin { */ message: APIMessage; } + +/** + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-author-types} + */ +export enum MessageSearchAuthorType { + /** + * Return messages sent by user accounts + */ + User = 'user', + /** + * Return messages sent by bot accounts + */ + Bot = 'bot', + /** + * Return messages sent by webhooks + */ + Webhook = 'webhook', + /** + * Return messages not sent by user accounts + */ + NotUser = '-user', + /** + * Return messages not sent by bot accounts + */ + NotBot = '-bot', + /** + * Return messages not sent by webhooks + */ + NotWebhook = '-webhook', +} + +/** + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-has-types} + */ +export enum MessageSearchHasType { + /** + * Return messages that have an image + */ + Image = 'image', + /** + * Return messages that have a sound attachment + */ + Sound = 'sound', + /** + * Return messages that have a video + */ + Video = 'video', + /** + * Return messages that have an attachment + */ + File = 'file', + /** + * Return messages that have a sent sticker + */ + Sticker = 'sticker', + /** + * Return messages that have an embed + */ + Embed = 'embed', + /** + * Return messages that have a link + */ + Link = 'link', + /** + * Return messages that have a poll + */ + Poll = 'poll', + /** + * Return messages that have a forwarded message + */ + Snapshot = 'snapshot', + /** + * Return messages that don't have an image + */ + NotImage = '-image', + /** + * Return messages that don't have a sound attachment + */ + NotSound = '-sound', + /** + * Return messages that don't have a video + */ + NotVideo = '-video', + /** + * Return messages that don't have an attachment + */ + NotFile = '-file', + /** + * Return messages that don't have a sent sticker + */ + NotSticker = '-sticker', + /** + * Return messages that don't have an embed + */ + NotEmbed = '-embed', + /** + * Return messages that don't have a link + */ + NotLink = '-link', + /** + * Return messages that don't have a poll + */ + NotPoll = '-poll', + /** + * Return messages that don't have a forwarded message + */ + NotSnapshot = '-snapshot', +} + +/** + * @remarks These do not correspond 1:1 to actual {@link https://docs.discord.com/developers/resources/message#embed-object-embed-types | embed types} and encompass a wider range of actual types. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-embed-types} + */ +export enum MessageSearchEmbedType { + /** + * Return messages that have an image embed + */ + Image = 'image', + /** + * Return messages that have a video embed + */ + Video = 'video', + /** + * Return messages that have a gifv embed + * + * @remarks Messages sent before February 24, 2026 may not be properly indexed under the `gif` embed type. + */ + Gif = 'gif', + /** + * Return messages that have a sound embed + */ + Sound = 'sound', + /** + * Return messages that have an article embed + */ + Article = 'article', +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-sort-modes} + */ +export enum MessageSearchSortMode { + /** + * Sort by the message creation time (default) + */ + Timestamp = 'timestamp', + /** + * Sort by the relevance of the message to the search query + */ + Relevance = 'relevance', +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages} + */ +export interface APIMessageSearchIndexNotReadyResponse { + message: string; + code: number; + documents_indexed: number; + retry_after: number; +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-response-body} + */ +export interface APIMessageSearchResult { + /** + * Whether the guild is undergoing a deep historical indexing operation + */ + doing_deep_historical_index: boolean; + /** + * The number of documents that have been indexed during the current index operation, if any + */ + documents_indexed?: number; + /** + * The total number of results that match the query + */ + total_results: number; + /** + * A nested array of messages that match the query + * + * @remarks The nested array was used to provide surrounding context to search results. However, surrounding context is no longer returned. + */ + messages: Omit[][]; + /** + * The threads that contain the returned messages + */ + threads?: APIThreadChannel[]; + /** + * A thread member object for each returned thread the current user has joined + */ + members?: APIThreadMember[]; +} diff --git a/deno/payloads/v9/message.ts b/deno/payloads/v9/message.ts index ce414473..5fae341e 100644 --- a/deno/payloads/v9/message.ts +++ b/deno/payloads/v9/message.ts @@ -3,7 +3,7 @@ import type { Snowflake } from '../../globals.ts'; import type { _NonNullableFields } from '../../utils/internals.ts'; import type { APIApplication } from './application.ts'; -import type { APIChannel, ChannelType } from './channel.ts'; +import type { APIChannel, APIThreadChannel, APIThreadMember, ChannelType } from './channel.ts'; import type { APIPartialEmoji } from './emoji.ts'; import type { APIInteractionDataResolved, APIMessageInteraction, APIMessageInteractionMetadata } from './interactions.ts'; import type { APIRole } from './permissions.ts'; @@ -1994,3 +1994,198 @@ export interface APIMessagePin { */ message: APIMessage; } + +/** + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-author-types} + */ +export enum MessageSearchAuthorType { + /** + * Return messages sent by user accounts + */ + User = 'user', + /** + * Return messages sent by bot accounts + */ + Bot = 'bot', + /** + * Return messages sent by webhooks + */ + Webhook = 'webhook', + /** + * Return messages not sent by user accounts + */ + NotUser = '-user', + /** + * Return messages not sent by bot accounts + */ + NotBot = '-bot', + /** + * Return messages not sent by webhooks + */ + NotWebhook = '-webhook', +} + +/** + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-has-types} + */ +export enum MessageSearchHasType { + /** + * Return messages that have an image + */ + Image = 'image', + /** + * Return messages that have a sound attachment + */ + Sound = 'sound', + /** + * Return messages that have a video + */ + Video = 'video', + /** + * Return messages that have an attachment + */ + File = 'file', + /** + * Return messages that have a sent sticker + */ + Sticker = 'sticker', + /** + * Return messages that have an embed + */ + Embed = 'embed', + /** + * Return messages that have a link + */ + Link = 'link', + /** + * Return messages that have a poll + */ + Poll = 'poll', + /** + * Return messages that have a forwarded message + */ + Snapshot = 'snapshot', + /** + * Return messages that don't have an image + */ + NotImage = '-image', + /** + * Return messages that don't have a sound attachment + */ + NotSound = '-sound', + /** + * Return messages that don't have a video + */ + NotVideo = '-video', + /** + * Return messages that don't have an attachment + */ + NotFile = '-file', + /** + * Return messages that don't have a sent sticker + */ + NotSticker = '-sticker', + /** + * Return messages that don't have an embed + */ + NotEmbed = '-embed', + /** + * Return messages that don't have a link + */ + NotLink = '-link', + /** + * Return messages that don't have a poll + */ + NotPoll = '-poll', + /** + * Return messages that don't have a forwarded message + */ + NotSnapshot = '-snapshot', +} + +/** + * @remarks These do not correspond 1:1 to actual {@link https://docs.discord.com/developers/resources/message#embed-object-embed-types | embed types} and encompass a wider range of actual types. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-embed-types} + */ +export enum MessageSearchEmbedType { + /** + * Return messages that have an image embed + */ + Image = 'image', + /** + * Return messages that have a video embed + */ + Video = 'video', + /** + * Return messages that have a gifv embed + * + * @remarks Messages sent before February 24, 2026 may not be properly indexed under the `gif` embed type. + */ + Gif = 'gif', + /** + * Return messages that have a sound embed + */ + Sound = 'sound', + /** + * Return messages that have an article embed + */ + Article = 'article', +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-sort-modes} + */ +export enum MessageSearchSortMode { + /** + * Sort by the message creation time (default) + */ + Timestamp = 'timestamp', + /** + * Sort by the relevance of the message to the search query + */ + Relevance = 'relevance', +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages} + */ +export interface APIMessageSearchIndexNotReadyResponse { + message: string; + code: number; + documents_indexed: number; + retry_after: number; +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-response-body} + */ +export interface APIMessageSearchResult { + /** + * Whether the guild is undergoing a deep historical indexing operation + */ + doing_deep_historical_index: boolean; + /** + * The number of documents that have been indexed during the current index operation, if any + */ + documents_indexed?: number; + /** + * The total number of results that match the query + */ + total_results: number; + /** + * A nested array of messages that match the query + * + * @remarks The nested array was used to provide surrounding context to search results. However, surrounding context is no longer returned. + */ + messages: Omit[][]; + /** + * The threads that contain the returned messages + */ + threads?: APIThreadChannel[]; + /** + * A thread member object for each returned thread the current user has joined + */ + members?: APIThreadMember[]; +} diff --git a/deno/rest/common.ts b/deno/rest/common.ts index 079f3daa..30c8cdf4 100644 --- a/deno/rest/common.ts +++ b/deno/rest/common.ts @@ -287,7 +287,8 @@ export enum RESTJSONErrorCodes { ReactionWasBlocked = 90_001, UserCannotUseBurstReactions, - ApplicationNotYetAvailable = 110_001, + IndexNotYetAvailable = 110_000, + ApplicationNotYetAvailable, APIResourceOverloaded = 130_000, diff --git a/deno/rest/v10/guild.ts b/deno/rest/v10/guild.ts index b59b04a3..9cda1a71 100644 --- a/deno/rest/v10/guild.ts +++ b/deno/rest/v10/guild.ts @@ -30,6 +30,12 @@ import type { APIRoleColors, APIIncidentsData, APIGuildChannel, + APIMessageSearchIndexNotReadyResponse, + APIMessageSearchResult, + MessageSearchAuthorType, + MessageSearchEmbedType, + MessageSearchHasType, + MessageSearchSortMode, } from '../../payloads/v10/mod.ts'; import type { _AddUndefinedToPossiblyUndefinedPropertiesOfInterface, @@ -536,6 +542,151 @@ export interface RESTPatchAPIGuildMemberJSONBody { */ export type RESTPatchAPIGuildMemberResult = APIGuildMember; +/** + * Returns a list of messages without the `reactions` key that match a search query in the guild. Requires the `READ_MESSAGE_HISTORY` permission. + * + * @remarks The Search Guild Messages endpoint is restricted according to whether the `MESSAGE_CONTENT` Privileged Intent is enabled for your application. + * + * If the entity you are searching is not yet indexed, the endpoint will return a 202 accepted response. The response body will not contain any search results, and will look similar to an error response: + * ```json + * { + * "message": "Index not yet available. Try again later", + * "code": 110000, + * "documents_indexed": 0, + * "retry_after": 2 + * } + * ``` + * + * Due to speed optimizations, search may return slightly fewer results than the limit specified when messages have not been accessed for a long time. + * Clients should not rely on the length of the `messages` array to paginate results. + * + * Additionally, when messages are actively being created or deleted, the `total_results` field may not be accurate. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages} + */ +export interface RESTGetAPIGuildMessagesSearchQuery { + /** + * Max number of messages to return (1-25) + * + * @defaultValue `25` + */ + limit?: number; + /** + * Number to offset the returned messages by (max 9975) + */ + offset?: number; + /** + * Get messages before this message ID + */ + max_id?: Snowflake; + /** + * Get messages after this message ID + */ + min_id?: Snowflake; + /** + * Max number of words to skip between matching tokens in the search `content` (max 100) + * + * @defaultValue `2` + */ + slop?: number; + /** + * Filter messages by content (max 1024 characters) + */ + content?: string; + /** + * Filter messages by these channels (max 500) + */ + channel_id?: Snowflake[]; + /** + * Filter messages by author type + * + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-author-types} + */ + author_type?: MessageSearchAuthorType[]; + /** + * Filter messages by these authors (max 100) + */ + author_id?: Snowflake[]; + /** + * Filter messages that mention these users (max 100) + */ + mentions?: Snowflake[]; + /** + * Filter messages that mention these roles (max 100) + */ + mentions_role_id?: Snowflake[]; + /** + * Filter messages that do or do not mention @everyone + */ + mention_everyone?: boolean; + /** + * Filter messages that reply to these users (max 100) + */ + replied_to_user_id?: Snowflake[]; + /** + * Filter messages that reply to these messages (max 100) + */ + replied_to_message_id?: Snowflake[]; + /** + * Filter messages by whether they are or are not pinned + */ + pinned?: boolean; + /** + * Filter messages by whether or not they have specific things + * + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-has-types} + */ + has?: MessageSearchHasType[]; + /** + * Filter messages by embed type + * + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-embed-types} + */ + embed_type?: MessageSearchEmbedType[]; + /** + * Filter messages by embed provider (case-sensitive, e.g. `Tenor`) (max 256 characters, max 100) + */ + embed_provider?: string[]; + /** + * Filter messages by link hostname (e.g. `discordapp.com`) (max 256 characters, max 100) + */ + link_hostname?: string[]; + /** + * Filter messages by attachment filename (max 1024 characters, max 100) + */ + attachment_filename?: string[]; + /** + * Filter messages by attachment extension (e.g. `txt`) (max 256 characters, max 100) + */ + attachment_extension?: string[]; + /** + * The sorting algorithm to use + * + * @remarks Sort order is not respected when sorting by relevance. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-sort-modes} + */ + sort_by?: MessageSearchSortMode; + /** + * The direction to sort (`asc` or `desc`) + * + * @defaultValue `'desc'` + * @remarks Sort order is not respected when sorting by relevance. + */ + sort_order?: 'asc' | 'desc'; + /** + * Whether to include results from age-restricted channels + * + * @defaultValue `false` + */ + include_nsfw?: boolean; +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-response-body} + */ +export type RESTGetAPIGuildMessagesSearchResult = APIMessageSearchIndexNotReadyResponse | APIMessageSearchResult; + /** * @see {@link https://discord.com/developers/docs/resources/guild#modify-current-user-nick} * @deprecated Use {@link https://discord.com/developers/docs/resources/guild#modify-current-member | Modify Current Member} instead. diff --git a/deno/rest/v10/mod.ts b/deno/rest/v10/mod.ts index 1cdecc56..462904a1 100644 --- a/deno/rest/v10/mod.ts +++ b/deno/rest/v10/mod.ts @@ -313,6 +313,14 @@ export const Routes = { return `/guilds/${guildId}/members/search` as const; }, + /** + * Route for: + * - GET `/guilds/{guild.id}/messages/search` + */ + guildMessagesSearch(guildId: Snowflake) { + return `/guilds/${guildId}/messages/search` as const; + }, + /** * Route for: * - PATCH `/guilds/{guild.id}/members/@me/nick` diff --git a/deno/rest/v9/guild.ts b/deno/rest/v9/guild.ts index 3fb7fa92..41be27bb 100644 --- a/deno/rest/v9/guild.ts +++ b/deno/rest/v9/guild.ts @@ -30,6 +30,12 @@ import type { APIRoleColors, APIIncidentsData, APIGuildChannel, + APIMessageSearchIndexNotReadyResponse, + APIMessageSearchResult, + MessageSearchAuthorType, + MessageSearchEmbedType, + MessageSearchHasType, + MessageSearchSortMode, } from '../../payloads/v9/mod.ts'; import type { _AddUndefinedToPossiblyUndefinedPropertiesOfInterface, @@ -536,6 +542,151 @@ export interface RESTPatchAPIGuildMemberJSONBody { */ export type RESTPatchAPIGuildMemberResult = APIGuildMember; +/** + * Returns a list of messages without the `reactions` key that match a search query in the guild. Requires the `READ_MESSAGE_HISTORY` permission. + * + * @remarks The Search Guild Messages endpoint is restricted according to whether the `MESSAGE_CONTENT` Privileged Intent is enabled for your application. + * + * If the entity you are searching is not yet indexed, the endpoint will return a 202 accepted response. The response body will not contain any search results, and will look similar to an error response: + * ```json + * { + * "message": "Index not yet available. Try again later", + * "code": 110000, + * "documents_indexed": 0, + * "retry_after": 2 + * } + * ``` + * + * Due to speed optimizations, search may return slightly fewer results than the limit specified when messages have not been accessed for a long time. + * Clients should not rely on the length of the `messages` array to paginate results. + * + * Additionally, when messages are actively being created or deleted, the `total_results` field may not be accurate. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages} + */ +export interface RESTGetAPIGuildMessagesSearchQuery { + /** + * Max number of messages to return (1-25) + * + * @defaultValue `25` + */ + limit?: number; + /** + * Number to offset the returned messages by (max 9975) + */ + offset?: number; + /** + * Get messages before this message ID + */ + max_id?: Snowflake; + /** + * Get messages after this message ID + */ + min_id?: Snowflake; + /** + * Max number of words to skip between matching tokens in the search `content` (max 100) + * + * @defaultValue `2` + */ + slop?: number; + /** + * Filter messages by content (max 1024 characters) + */ + content?: string; + /** + * Filter messages by these channels (max 500) + */ + channel_id?: Snowflake[]; + /** + * Filter messages by author type + * + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-author-types} + */ + author_type?: MessageSearchAuthorType[]; + /** + * Filter messages by these authors (max 100) + */ + author_id?: Snowflake[]; + /** + * Filter messages that mention these users (max 100) + */ + mentions?: Snowflake[]; + /** + * Filter messages that mention these roles (max 100) + */ + mentions_role_id?: Snowflake[]; + /** + * Filter messages that do or do not mention @everyone + */ + mention_everyone?: boolean; + /** + * Filter messages that reply to these users (max 100) + */ + replied_to_user_id?: Snowflake[]; + /** + * Filter messages that reply to these messages (max 100) + */ + replied_to_message_id?: Snowflake[]; + /** + * Filter messages by whether they are or are not pinned + */ + pinned?: boolean; + /** + * Filter messages by whether or not they have specific things + * + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-has-types} + */ + has?: MessageSearchHasType[]; + /** + * Filter messages by embed type + * + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-embed-types} + */ + embed_type?: MessageSearchEmbedType[]; + /** + * Filter messages by embed provider (case-sensitive, e.g. `Tenor`) (max 256 characters, max 100) + */ + embed_provider?: string[]; + /** + * Filter messages by link hostname (e.g. `discordapp.com`) (max 256 characters, max 100) + */ + link_hostname?: string[]; + /** + * Filter messages by attachment filename (max 1024 characters, max 100) + */ + attachment_filename?: string[]; + /** + * Filter messages by attachment extension (e.g. `txt`) (max 256 characters, max 100) + */ + attachment_extension?: string[]; + /** + * The sorting algorithm to use + * + * @remarks Sort order is not respected when sorting by relevance. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-sort-modes} + */ + sort_by?: MessageSearchSortMode; + /** + * The direction to sort (`asc` or `desc`) + * + * @defaultValue `'desc'` + * @remarks Sort order is not respected when sorting by relevance. + */ + sort_order?: 'asc' | 'desc'; + /** + * Whether to include results from age-restricted channels + * + * @defaultValue `false` + */ + include_nsfw?: boolean; +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-response-body} + */ +export type RESTGetAPIGuildMessagesSearchResult = APIMessageSearchIndexNotReadyResponse | APIMessageSearchResult; + /** * @see {@link https://discord.com/developers/docs/resources/guild#modify-current-user-nick} * @deprecated Use {@link https://discord.com/developers/docs/resources/guild#modify-current-member | Modify Current Member} instead. diff --git a/deno/rest/v9/mod.ts b/deno/rest/v9/mod.ts index 03ae6096..98ee7952 100644 --- a/deno/rest/v9/mod.ts +++ b/deno/rest/v9/mod.ts @@ -313,6 +313,14 @@ export const Routes = { return `/guilds/${guildId}/members/search` as const; }, + /** + * Route for: + * - GET `/guilds/{guild.id}/messages/search` + */ + guildMessagesSearch(guildId: Snowflake) { + return `/guilds/${guildId}/messages/search` as const; + }, + /** * Route for: * - PATCH `/guilds/{guild.id}/members/@me/nick` diff --git a/payloads/v10/message.ts b/payloads/v10/message.ts index 63c1614d..28b853f9 100644 --- a/payloads/v10/message.ts +++ b/payloads/v10/message.ts @@ -3,7 +3,7 @@ import type { Snowflake } from '../../globals'; import type { _NonNullableFields } from '../../utils/internals'; import type { APIApplication } from './application'; -import type { APIChannel, ChannelType } from './channel'; +import type { APIChannel, APIThreadChannel, APIThreadMember, ChannelType } from './channel'; import type { APIPartialEmoji } from './emoji'; import type { APIInteractionDataResolved, APIMessageInteraction, APIMessageInteractionMetadata } from './interactions'; import type { APIRole } from './permissions'; @@ -1998,3 +1998,198 @@ export interface APIMessagePin { */ message: APIMessage; } + +/** + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-author-types} + */ +export enum MessageSearchAuthorType { + /** + * Return messages sent by user accounts + */ + User = 'user', + /** + * Return messages sent by bot accounts + */ + Bot = 'bot', + /** + * Return messages sent by webhooks + */ + Webhook = 'webhook', + /** + * Return messages not sent by user accounts + */ + NotUser = '-user', + /** + * Return messages not sent by bot accounts + */ + NotBot = '-bot', + /** + * Return messages not sent by webhooks + */ + NotWebhook = '-webhook', +} + +/** + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-has-types} + */ +export enum MessageSearchHasType { + /** + * Return messages that have an image + */ + Image = 'image', + /** + * Return messages that have a sound attachment + */ + Sound = 'sound', + /** + * Return messages that have a video + */ + Video = 'video', + /** + * Return messages that have an attachment + */ + File = 'file', + /** + * Return messages that have a sent sticker + */ + Sticker = 'sticker', + /** + * Return messages that have an embed + */ + Embed = 'embed', + /** + * Return messages that have a link + */ + Link = 'link', + /** + * Return messages that have a poll + */ + Poll = 'poll', + /** + * Return messages that have a forwarded message + */ + Snapshot = 'snapshot', + /** + * Return messages that don't have an image + */ + NotImage = '-image', + /** + * Return messages that don't have a sound attachment + */ + NotSound = '-sound', + /** + * Return messages that don't have a video + */ + NotVideo = '-video', + /** + * Return messages that don't have an attachment + */ + NotFile = '-file', + /** + * Return messages that don't have a sent sticker + */ + NotSticker = '-sticker', + /** + * Return messages that don't have an embed + */ + NotEmbed = '-embed', + /** + * Return messages that don't have a link + */ + NotLink = '-link', + /** + * Return messages that don't have a poll + */ + NotPoll = '-poll', + /** + * Return messages that don't have a forwarded message + */ + NotSnapshot = '-snapshot', +} + +/** + * @remarks These do not correspond 1:1 to actual {@link https://docs.discord.com/developers/resources/message#embed-object-embed-types | embed types} and encompass a wider range of actual types. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-embed-types} + */ +export enum MessageSearchEmbedType { + /** + * Return messages that have an image embed + */ + Image = 'image', + /** + * Return messages that have a video embed + */ + Video = 'video', + /** + * Return messages that have a gifv embed + * + * @remarks Messages sent before February 24, 2026 may not be properly indexed under the `gif` embed type. + */ + Gif = 'gif', + /** + * Return messages that have a sound embed + */ + Sound = 'sound', + /** + * Return messages that have an article embed + */ + Article = 'article', +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-sort-modes} + */ +export enum MessageSearchSortMode { + /** + * Sort by the message creation time (default) + */ + Timestamp = 'timestamp', + /** + * Sort by the relevance of the message to the search query + */ + Relevance = 'relevance', +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages} + */ +export interface APIMessageSearchIndexNotReadyResponse { + message: string; + code: number; + documents_indexed: number; + retry_after: number; +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-response-body} + */ +export interface APIMessageSearchResult { + /** + * Whether the guild is undergoing a deep historical indexing operation + */ + doing_deep_historical_index: boolean; + /** + * The number of documents that have been indexed during the current index operation, if any + */ + documents_indexed?: number; + /** + * The total number of results that match the query + */ + total_results: number; + /** + * A nested array of messages that match the query + * + * @remarks The nested array was used to provide surrounding context to search results. However, surrounding context is no longer returned. + */ + messages: Omit[][]; + /** + * The threads that contain the returned messages + */ + threads?: APIThreadChannel[]; + /** + * A thread member object for each returned thread the current user has joined + */ + members?: APIThreadMember[]; +} diff --git a/payloads/v9/message.ts b/payloads/v9/message.ts index 6d513a36..d02b04be 100644 --- a/payloads/v9/message.ts +++ b/payloads/v9/message.ts @@ -3,7 +3,7 @@ import type { Snowflake } from '../../globals'; import type { _NonNullableFields } from '../../utils/internals'; import type { APIApplication } from './application'; -import type { APIChannel, ChannelType } from './channel'; +import type { APIChannel, APIThreadChannel, APIThreadMember, ChannelType } from './channel'; import type { APIPartialEmoji } from './emoji'; import type { APIInteractionDataResolved, APIMessageInteraction, APIMessageInteractionMetadata } from './interactions'; import type { APIRole } from './permissions'; @@ -1994,3 +1994,198 @@ export interface APIMessagePin { */ message: APIMessage; } + +/** + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-author-types} + */ +export enum MessageSearchAuthorType { + /** + * Return messages sent by user accounts + */ + User = 'user', + /** + * Return messages sent by bot accounts + */ + Bot = 'bot', + /** + * Return messages sent by webhooks + */ + Webhook = 'webhook', + /** + * Return messages not sent by user accounts + */ + NotUser = '-user', + /** + * Return messages not sent by bot accounts + */ + NotBot = '-bot', + /** + * Return messages not sent by webhooks + */ + NotWebhook = '-webhook', +} + +/** + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-has-types} + */ +export enum MessageSearchHasType { + /** + * Return messages that have an image + */ + Image = 'image', + /** + * Return messages that have a sound attachment + */ + Sound = 'sound', + /** + * Return messages that have a video + */ + Video = 'video', + /** + * Return messages that have an attachment + */ + File = 'file', + /** + * Return messages that have a sent sticker + */ + Sticker = 'sticker', + /** + * Return messages that have an embed + */ + Embed = 'embed', + /** + * Return messages that have a link + */ + Link = 'link', + /** + * Return messages that have a poll + */ + Poll = 'poll', + /** + * Return messages that have a forwarded message + */ + Snapshot = 'snapshot', + /** + * Return messages that don't have an image + */ + NotImage = '-image', + /** + * Return messages that don't have a sound attachment + */ + NotSound = '-sound', + /** + * Return messages that don't have a video + */ + NotVideo = '-video', + /** + * Return messages that don't have an attachment + */ + NotFile = '-file', + /** + * Return messages that don't have a sent sticker + */ + NotSticker = '-sticker', + /** + * Return messages that don't have an embed + */ + NotEmbed = '-embed', + /** + * Return messages that don't have a link + */ + NotLink = '-link', + /** + * Return messages that don't have a poll + */ + NotPoll = '-poll', + /** + * Return messages that don't have a forwarded message + */ + NotSnapshot = '-snapshot', +} + +/** + * @remarks These do not correspond 1:1 to actual {@link https://docs.discord.com/developers/resources/message#embed-object-embed-types | embed types} and encompass a wider range of actual types. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-embed-types} + */ +export enum MessageSearchEmbedType { + /** + * Return messages that have an image embed + */ + Image = 'image', + /** + * Return messages that have a video embed + */ + Video = 'video', + /** + * Return messages that have a gifv embed + * + * @remarks Messages sent before February 24, 2026 may not be properly indexed under the `gif` embed type. + */ + Gif = 'gif', + /** + * Return messages that have a sound embed + */ + Sound = 'sound', + /** + * Return messages that have an article embed + */ + Article = 'article', +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-sort-modes} + */ +export enum MessageSearchSortMode { + /** + * Sort by the message creation time (default) + */ + Timestamp = 'timestamp', + /** + * Sort by the relevance of the message to the search query + */ + Relevance = 'relevance', +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages} + */ +export interface APIMessageSearchIndexNotReadyResponse { + message: string; + code: number; + documents_indexed: number; + retry_after: number; +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-response-body} + */ +export interface APIMessageSearchResult { + /** + * Whether the guild is undergoing a deep historical indexing operation + */ + doing_deep_historical_index: boolean; + /** + * The number of documents that have been indexed during the current index operation, if any + */ + documents_indexed?: number; + /** + * The total number of results that match the query + */ + total_results: number; + /** + * A nested array of messages that match the query + * + * @remarks The nested array was used to provide surrounding context to search results. However, surrounding context is no longer returned. + */ + messages: Omit[][]; + /** + * The threads that contain the returned messages + */ + threads?: APIThreadChannel[]; + /** + * A thread member object for each returned thread the current user has joined + */ + members?: APIThreadMember[]; +} diff --git a/rest/common.ts b/rest/common.ts index 079f3daa..30c8cdf4 100644 --- a/rest/common.ts +++ b/rest/common.ts @@ -287,7 +287,8 @@ export enum RESTJSONErrorCodes { ReactionWasBlocked = 90_001, UserCannotUseBurstReactions, - ApplicationNotYetAvailable = 110_001, + IndexNotYetAvailable = 110_000, + ApplicationNotYetAvailable, APIResourceOverloaded = 130_000, diff --git a/rest/v10/guild.ts b/rest/v10/guild.ts index 5e6fafd0..afe69232 100644 --- a/rest/v10/guild.ts +++ b/rest/v10/guild.ts @@ -30,6 +30,12 @@ import type { APIRoleColors, APIIncidentsData, APIGuildChannel, + APIMessageSearchIndexNotReadyResponse, + APIMessageSearchResult, + MessageSearchAuthorType, + MessageSearchEmbedType, + MessageSearchHasType, + MessageSearchSortMode, } from '../../payloads/v10/index'; import type { _AddUndefinedToPossiblyUndefinedPropertiesOfInterface, @@ -536,6 +542,151 @@ export interface RESTPatchAPIGuildMemberJSONBody { */ export type RESTPatchAPIGuildMemberResult = APIGuildMember; +/** + * Returns a list of messages without the `reactions` key that match a search query in the guild. Requires the `READ_MESSAGE_HISTORY` permission. + * + * @remarks The Search Guild Messages endpoint is restricted according to whether the `MESSAGE_CONTENT` Privileged Intent is enabled for your application. + * + * If the entity you are searching is not yet indexed, the endpoint will return a 202 accepted response. The response body will not contain any search results, and will look similar to an error response: + * ```json + * { + * "message": "Index not yet available. Try again later", + * "code": 110000, + * "documents_indexed": 0, + * "retry_after": 2 + * } + * ``` + * + * Due to speed optimizations, search may return slightly fewer results than the limit specified when messages have not been accessed for a long time. + * Clients should not rely on the length of the `messages` array to paginate results. + * + * Additionally, when messages are actively being created or deleted, the `total_results` field may not be accurate. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages} + */ +export interface RESTGetAPIGuildMessagesSearchQuery { + /** + * Max number of messages to return (1-25) + * + * @defaultValue `25` + */ + limit?: number; + /** + * Number to offset the returned messages by (max 9975) + */ + offset?: number; + /** + * Get messages before this message ID + */ + max_id?: Snowflake; + /** + * Get messages after this message ID + */ + min_id?: Snowflake; + /** + * Max number of words to skip between matching tokens in the search `content` (max 100) + * + * @defaultValue `2` + */ + slop?: number; + /** + * Filter messages by content (max 1024 characters) + */ + content?: string; + /** + * Filter messages by these channels (max 500) + */ + channel_id?: Snowflake[]; + /** + * Filter messages by author type + * + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-author-types} + */ + author_type?: MessageSearchAuthorType[]; + /** + * Filter messages by these authors (max 100) + */ + author_id?: Snowflake[]; + /** + * Filter messages that mention these users (max 100) + */ + mentions?: Snowflake[]; + /** + * Filter messages that mention these roles (max 100) + */ + mentions_role_id?: Snowflake[]; + /** + * Filter messages that do or do not mention @everyone + */ + mention_everyone?: boolean; + /** + * Filter messages that reply to these users (max 100) + */ + replied_to_user_id?: Snowflake[]; + /** + * Filter messages that reply to these messages (max 100) + */ + replied_to_message_id?: Snowflake[]; + /** + * Filter messages by whether they are or are not pinned + */ + pinned?: boolean; + /** + * Filter messages by whether or not they have specific things + * + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-has-types} + */ + has?: MessageSearchHasType[]; + /** + * Filter messages by embed type + * + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-embed-types} + */ + embed_type?: MessageSearchEmbedType[]; + /** + * Filter messages by embed provider (case-sensitive, e.g. `Tenor`) (max 256 characters, max 100) + */ + embed_provider?: string[]; + /** + * Filter messages by link hostname (e.g. `discordapp.com`) (max 256 characters, max 100) + */ + link_hostname?: string[]; + /** + * Filter messages by attachment filename (max 1024 characters, max 100) + */ + attachment_filename?: string[]; + /** + * Filter messages by attachment extension (e.g. `txt`) (max 256 characters, max 100) + */ + attachment_extension?: string[]; + /** + * The sorting algorithm to use + * + * @remarks Sort order is not respected when sorting by relevance. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-sort-modes} + */ + sort_by?: MessageSearchSortMode; + /** + * The direction to sort (`asc` or `desc`) + * + * @defaultValue `'desc'` + * @remarks Sort order is not respected when sorting by relevance. + */ + sort_order?: 'asc' | 'desc'; + /** + * Whether to include results from age-restricted channels + * + * @defaultValue `false` + */ + include_nsfw?: boolean; +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-response-body} + */ +export type RESTGetAPIGuildMessagesSearchResult = APIMessageSearchIndexNotReadyResponse | APIMessageSearchResult; + /** * @see {@link https://discord.com/developers/docs/resources/guild#modify-current-user-nick} * @deprecated Use {@link https://discord.com/developers/docs/resources/guild#modify-current-member | Modify Current Member} instead. diff --git a/rest/v10/index.ts b/rest/v10/index.ts index f6c49d0c..d76b1725 100644 --- a/rest/v10/index.ts +++ b/rest/v10/index.ts @@ -313,6 +313,14 @@ export const Routes = { return `/guilds/${guildId}/members/search` as const; }, + /** + * Route for: + * - GET `/guilds/{guild.id}/messages/search` + */ + guildMessagesSearch(guildId: Snowflake) { + return `/guilds/${guildId}/messages/search` as const; + }, + /** * Route for: * - PATCH `/guilds/{guild.id}/members/@me/nick` diff --git a/rest/v9/guild.ts b/rest/v9/guild.ts index ef62caa5..e075f44a 100644 --- a/rest/v9/guild.ts +++ b/rest/v9/guild.ts @@ -30,6 +30,12 @@ import type { APIRoleColors, APIIncidentsData, APIGuildChannel, + APIMessageSearchIndexNotReadyResponse, + APIMessageSearchResult, + MessageSearchAuthorType, + MessageSearchEmbedType, + MessageSearchHasType, + MessageSearchSortMode, } from '../../payloads/v9/index'; import type { _AddUndefinedToPossiblyUndefinedPropertiesOfInterface, @@ -536,6 +542,151 @@ export interface RESTPatchAPIGuildMemberJSONBody { */ export type RESTPatchAPIGuildMemberResult = APIGuildMember; +/** + * Returns a list of messages without the `reactions` key that match a search query in the guild. Requires the `READ_MESSAGE_HISTORY` permission. + * + * @remarks The Search Guild Messages endpoint is restricted according to whether the `MESSAGE_CONTENT` Privileged Intent is enabled for your application. + * + * If the entity you are searching is not yet indexed, the endpoint will return a 202 accepted response. The response body will not contain any search results, and will look similar to an error response: + * ```json + * { + * "message": "Index not yet available. Try again later", + * "code": 110000, + * "documents_indexed": 0, + * "retry_after": 2 + * } + * ``` + * + * Due to speed optimizations, search may return slightly fewer results than the limit specified when messages have not been accessed for a long time. + * Clients should not rely on the length of the `messages` array to paginate results. + * + * Additionally, when messages are actively being created or deleted, the `total_results` field may not be accurate. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages} + */ +export interface RESTGetAPIGuildMessagesSearchQuery { + /** + * Max number of messages to return (1-25) + * + * @defaultValue `25` + */ + limit?: number; + /** + * Number to offset the returned messages by (max 9975) + */ + offset?: number; + /** + * Get messages before this message ID + */ + max_id?: Snowflake; + /** + * Get messages after this message ID + */ + min_id?: Snowflake; + /** + * Max number of words to skip between matching tokens in the search `content` (max 100) + * + * @defaultValue `2` + */ + slop?: number; + /** + * Filter messages by content (max 1024 characters) + */ + content?: string; + /** + * Filter messages by these channels (max 500) + */ + channel_id?: Snowflake[]; + /** + * Filter messages by author type + * + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-author-types} + */ + author_type?: MessageSearchAuthorType[]; + /** + * Filter messages by these authors (max 100) + */ + author_id?: Snowflake[]; + /** + * Filter messages that mention these users (max 100) + */ + mentions?: Snowflake[]; + /** + * Filter messages that mention these roles (max 100) + */ + mentions_role_id?: Snowflake[]; + /** + * Filter messages that do or do not mention @everyone + */ + mention_everyone?: boolean; + /** + * Filter messages that reply to these users (max 100) + */ + replied_to_user_id?: Snowflake[]; + /** + * Filter messages that reply to these messages (max 100) + */ + replied_to_message_id?: Snowflake[]; + /** + * Filter messages by whether they are or are not pinned + */ + pinned?: boolean; + /** + * Filter messages by whether or not they have specific things + * + * @remarks All types can be negated by prefixing them with `-`, which means results will not include messages that match the type. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-has-types} + */ + has?: MessageSearchHasType[]; + /** + * Filter messages by embed type + * + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-embed-types} + */ + embed_type?: MessageSearchEmbedType[]; + /** + * Filter messages by embed provider (case-sensitive, e.g. `Tenor`) (max 256 characters, max 100) + */ + embed_provider?: string[]; + /** + * Filter messages by link hostname (e.g. `discordapp.com`) (max 256 characters, max 100) + */ + link_hostname?: string[]; + /** + * Filter messages by attachment filename (max 1024 characters, max 100) + */ + attachment_filename?: string[]; + /** + * Filter messages by attachment extension (e.g. `txt`) (max 256 characters, max 100) + */ + attachment_extension?: string[]; + /** + * The sorting algorithm to use + * + * @remarks Sort order is not respected when sorting by relevance. + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-search-sort-modes} + */ + sort_by?: MessageSearchSortMode; + /** + * The direction to sort (`asc` or `desc`) + * + * @defaultValue `'desc'` + * @remarks Sort order is not respected when sorting by relevance. + */ + sort_order?: 'asc' | 'desc'; + /** + * Whether to include results from age-restricted channels + * + * @defaultValue `false` + */ + include_nsfw?: boolean; +} + +/** + * @see {@link https://docs.discord.com/developers/resources/message#search-guild-messages-response-body} + */ +export type RESTGetAPIGuildMessagesSearchResult = APIMessageSearchIndexNotReadyResponse | APIMessageSearchResult; + /** * @see {@link https://discord.com/developers/docs/resources/guild#modify-current-user-nick} * @deprecated Use {@link https://discord.com/developers/docs/resources/guild#modify-current-member | Modify Current Member} instead. diff --git a/rest/v9/index.ts b/rest/v9/index.ts index eb895e2c..87ce7795 100644 --- a/rest/v9/index.ts +++ b/rest/v9/index.ts @@ -313,6 +313,14 @@ export const Routes = { return `/guilds/${guildId}/members/search` as const; }, + /** + * Route for: + * - GET `/guilds/{guild.id}/messages/search` + */ + guildMessagesSearch(guildId: Snowflake) { + return `/guilds/${guildId}/messages/search` as const; + }, + /** * Route for: * - PATCH `/guilds/{guild.id}/members/@me/nick`