diff --git a/packages/discord.js/src/structures/CommandInteractionOptionResolver.js b/packages/discord.js/src/structures/CommandInteractionOptionResolver.js index 8d596c17cae7..6449e684d2e7 100644 --- a/packages/discord.js/src/structures/CommandInteractionOptionResolver.js +++ b/packages/discord.js/src/structures/CommandInteractionOptionResolver.js @@ -248,7 +248,7 @@ class CommandInteractionOptionResolver { * Gets a message option. * @param {string} name The name of the option. * @param {boolean} [required=false] Whether to throw an error if the option is not found. - * @returns {?(Message|APIMessage)} + * @returns {?Message} * The value of the option, or null if not set and not required. */ getMessage(name, required = false) { diff --git a/packages/discord.js/src/structures/ContextMenuCommandInteraction.js b/packages/discord.js/src/structures/ContextMenuCommandInteraction.js index 1db4a7ddb1eb..b5e1d56c64c7 100644 --- a/packages/discord.js/src/structures/ContextMenuCommandInteraction.js +++ b/packages/discord.js/src/structures/ContextMenuCommandInteraction.js @@ -3,6 +3,9 @@ const { ApplicationCommandOptionType } = require('discord-api-types/v10'); const CommandInteraction = require('./CommandInteraction'); const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver'); +const { lazy } = require('../util/Util'); + +const getMessage = lazy(() => require('./Message').Message); /** * Represents a context menu interaction. @@ -48,7 +51,9 @@ class ContextMenuCommandInteraction extends CommandInteraction { name: 'message', type: '_MESSAGE', value: target_id, - message: this.channel?.messages._add(resolved.messages[target_id]) ?? resolved.messages[target_id], + message: + this.channel?.messages._add(resolved.messages[target_id]) ?? + new (getMessage())(this.client, resolved.messages[target_id]), }); } diff --git a/packages/discord.js/src/structures/MessageComponentInteraction.js b/packages/discord.js/src/structures/MessageComponentInteraction.js index 8cbe2ed245df..f2598fe99577 100644 --- a/packages/discord.js/src/structures/MessageComponentInteraction.js +++ b/packages/discord.js/src/structures/MessageComponentInteraction.js @@ -3,6 +3,9 @@ const Interaction = require('./Interaction'); const InteractionWebhook = require('./InteractionWebhook'); const InteractionResponses = require('./interfaces/InteractionResponses'); +const { lazy } = require('../util/Util'); + +const getMessage = lazy(() => require('./Message').Message); /** * Represents a message component interaction. @@ -21,9 +24,9 @@ class MessageComponentInteraction extends Interaction { /** * The message to which the component was attached - * @type {Message|APIMessage} + * @type {Message} */ - this.message = this.channel?.messages._add(data.message) ?? data.message; + this.message = this.channel?.messages._add(data.message) ?? new (getMessage())(client, data.message); /** * The custom id of the component which was interacted with diff --git a/packages/discord.js/src/structures/ModalSubmitInteraction.js b/packages/discord.js/src/structures/ModalSubmitInteraction.js index 28a60d9f4462..8362448ba32b 100644 --- a/packages/discord.js/src/structures/ModalSubmitInteraction.js +++ b/packages/discord.js/src/structures/ModalSubmitInteraction.js @@ -4,6 +4,9 @@ const Interaction = require('./Interaction'); const InteractionWebhook = require('./InteractionWebhook'); const ModalSubmitFields = require('./ModalSubmitFields'); const InteractionResponses = require('./interfaces/InteractionResponses'); +const { lazy } = require('../util/Util'); + +const getMessage = lazy(() => require('./Message').Message); /** * @typedef {Object} ModalData @@ -34,9 +37,9 @@ class ModalSubmitInteraction extends Interaction { if ('message' in data) { /** * The message associated with this interaction - * @type {?(Message|APIMessage)} + * @type {?Message} */ - this.message = this.channel?.messages._add(data.message) ?? data.message; + this.message = this.channel?.messages._add(data.message) ?? new (getMessage())(this.client, data.message); } else { this.message = null; } diff --git a/packages/discord.js/src/structures/Webhook.js b/packages/discord.js/src/structures/Webhook.js index f563954a80a4..a208a35465de 100644 --- a/packages/discord.js/src/structures/Webhook.js +++ b/packages/discord.js/src/structures/Webhook.js @@ -6,6 +6,9 @@ const { Routes, WebhookType } = require('discord-api-types/v10'); const MessagePayload = require('./MessagePayload'); const { Error } = require('../errors'); const DataResolver = require('../util/DataResolver'); +const { lazy } = require('../util/Util'); + +const getMessage = lazy(() => require('./Message').Message); /** * Represents a webhook. @@ -145,7 +148,7 @@ class Webhook { /** * Sends a message with this webhook. * @param {string|MessagePayload|WebhookMessageOptions} options The options to provide - * @returns {Promise} + * @returns {Promise} * @example * // Send a basic message * webhook.send('hello!') @@ -208,7 +211,7 @@ class Webhook { const { body, files } = await messagePayload.resolveFiles(); const d = await this.client.rest.post(Routes.webhook(this.id, this.token), { body, files, query, auth: false }); - return this.client.channels?.cache.get(d.channel_id)?.messages._add(d, false) ?? d; + return this.client.channels?.cache.get(d.channel_id)?.messages._add(d, false) ?? new (getMessage())(this.client, d); } /** @@ -283,8 +286,7 @@ class Webhook { * Gets a message that was sent by this webhook. * @param {Snowflake|'@original'} message The id of the message to fetch * @param {WebhookFetchMessageOptions} [options={}] The options to provide to fetch the message. - * @returns {Promise} Returns the raw message data if the webhook was instantiated as a - * {@link WebhookClient} or if the channel is uncached, otherwise a {@link Message} will be returned + * @returns {Promise} Returns the message sent by this webhook */ async fetchMessage(message, { cache = true, threadId } = {}) { if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE'); @@ -293,15 +295,17 @@ class Webhook { query: threadId ? makeURLSearchParams({ thread_id: threadId }) : undefined, auth: false, }); - return this.client.channels?.cache.get(data.channel_id)?.messages._add(data, cache) ?? data; + return ( + this.client.channels?.cache.get(data.channel_id)?.messages._add(data, cache) ?? + new (getMessage())(this.client, data) + ); } /** * Edits a message that was sent by this webhook. * @param {MessageResolvable|'@original'} message The message to edit * @param {string|MessagePayload|WebhookEditMessageOptions} options The options to provide - * @returns {Promise} Returns the raw message data if the webhook was instantiated as a - * {@link WebhookClient} or if the channel is uncached, otherwise a {@link Message} will be returned + * @returns {Promise} Returns the message edited by this webhook */ async editMessage(message, options) { if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE'); @@ -326,7 +330,7 @@ class Webhook { ); const messageManager = this.client.channels?.cache.get(d.channel_id)?.messages; - if (!messageManager) return d; + if (!messageManager) return new (getMessage())(this.client, d); const existing = messageManager.cache.get(d.id); if (!existing) return messageManager._add(d); diff --git a/packages/discord.js/src/util/Util.js b/packages/discord.js/src/util/Util.js index a16cdbea6b51..6b3a693d4c72 100644 --- a/packages/discord.js/src/util/Util.js +++ b/packages/discord.js/src/util/Util.js @@ -521,6 +521,16 @@ class Util extends null { static cleanCodeBlockContent(text) { return text.replaceAll('```', '`\u200b``'); } + + /** + * Lazily evaluates a callback function + * @param {Function} cb The callback to lazily evaluate + * @returns {Function} + */ + static lazy(cb) { + let defaultValue; + return () => (defaultValue ??= cb()); + } } module.exports = Util; diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index acb4d73a2155..c2b1823ddf69 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -391,14 +391,14 @@ export interface InteractionResponseFields ephemeral: boolean | null; replied: boolean; webhook: InteractionWebhook; - reply(options: InteractionReplyOptions & { fetchReply: true }): Promise>; + reply(options: InteractionReplyOptions & { fetchReply: true }): Promise; reply(options: string | MessagePayload | InteractionReplyOptions): Promise; deleteReply(): Promise; - editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise>; - deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise>; + editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise; + deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise; deferReply(options?: InteractionDeferReplyOptions): Promise; - fetchReply(): Promise>; - followUp(options: string | MessagePayload | InteractionReplyOptions): Promise>; + fetchReply(): Promise; + followUp(options: string | MessagePayload | InteractionReplyOptions): Promise; showModal( modal: JSONEncodable | ModalData | APIModalInteractionResponseCallbackData, ): Promise; @@ -435,13 +435,15 @@ export abstract class CommandInteraction e public inGuild(): this is CommandInteraction<'raw' | 'cached'>; public inCachedGuild(): this is CommandInteraction<'cached'>; public inRawGuild(): this is CommandInteraction<'raw'>; - public deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise>; + public deferReply( + options: InteractionDeferReplyOptions & { fetchReply: true }, + ): Promise>>; public deferReply(options?: InteractionDeferReplyOptions): Promise>>; public deleteReply(): Promise; - public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise>; - public fetchReply(): Promise>; - public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise>; - public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise>; + public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise; + public fetchReply(): Promise; + public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise; + public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise>>; public reply( options: string | MessagePayload | InteractionReplyOptions, ): Promise>>; @@ -1536,7 +1538,7 @@ export class InteractionCollector extends Collector; + public send(options: string | MessagePayload | InteractionReplyOptions): Promise; } export class Invite extends Base { @@ -1758,25 +1760,29 @@ export class MessageComponentInteraction e public channelId: Snowflake; public deferred: boolean; public ephemeral: boolean | null; - public message: GuildCacheMessage; + public message: Message>; public replied: boolean; public webhook: InteractionWebhook; public inGuild(): this is MessageComponentInteraction<'raw' | 'cached'>; public inCachedGuild(): this is MessageComponentInteraction<'cached'>; public inRawGuild(): this is MessageComponentInteraction<'raw'>; - public deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise>; + public deferReply( + options: InteractionDeferReplyOptions & { fetchReply: true }, + ): Promise>>; public deferReply(options?: InteractionDeferReplyOptions): Promise>>; - public deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise>; + public deferUpdate( + options: InteractionDeferUpdateOptions & { fetchReply: true }, + ): Promise>>; public deferUpdate(options?: InteractionDeferUpdateOptions): Promise>>; public deleteReply(): Promise; - public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise>; - public fetchReply(): Promise>; - public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise>; - public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise>; + public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise; + public fetchReply(): Promise; + public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise; + public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise>>; public reply( options: string | MessagePayload | InteractionReplyOptions, ): Promise>>; - public update(options: InteractionUpdateOptions & { fetchReply: true }): Promise>; + public update(options: InteractionUpdateOptions & { fetchReply: true }): Promise; public update( options: string | MessagePayload | InteractionUpdateOptions, ): Promise>>; @@ -1904,12 +1910,12 @@ export class ModalSubmitFields { export interface ModalMessageModalSubmitInteraction extends ModalSubmitInteraction { - message: GuildCacheMessage; - update(options: InteractionUpdateOptions & { fetchReply: true }): Promise>; + message: Message>; + update(options: InteractionUpdateOptions & { fetchReply: true }): Promise; update( options: string | MessagePayload | InteractionUpdateOptions, ): Promise>>; - deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise>; + deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise; deferUpdate(options?: InteractionDeferUpdateOptions): Promise>>; inGuild(): this is ModalMessageModalSubmitInteraction<'raw' | 'cached'>; inCachedGuild(): this is ModalMessageModalSubmitInteraction<'cached'>; @@ -1923,19 +1929,23 @@ export class ModalSubmitInteraction extend public readonly fields: ModalSubmitFields; public deferred: boolean; public ephemeral: boolean | null; - public message: GuildCacheMessage | null; + public message: Message> | null; public replied: boolean; public readonly webhook: InteractionWebhook; - public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise>; + public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise; public reply( options: string | MessagePayload | InteractionReplyOptions, ): Promise>>; public deleteReply(): Promise; - public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise>; - public deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise>; + public editReply( + options: string | MessagePayload | WebhookEditMessageOptions, + ): Promise>>; + public deferReply( + options: InteractionDeferReplyOptions & { fetchReply: true }, + ): Promise>>; public deferReply(options?: InteractionDeferReplyOptions): Promise>>; - public fetchReply(): Promise>; - public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise>; + public fetchReply(): Promise>>; + public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise>>; public inGuild(): this is ModalSubmitInteraction<'raw' | 'cached'>; public inCachedGuild(): this is ModalSubmitInteraction<'cached'>; public inRawGuild(): this is ModalSubmitInteraction<'raw'>; @@ -2740,9 +2750,9 @@ export class WebhookClient extends WebhookMixin(BaseClient) { public editMessage( message: MessageResolvable, options: string | MessagePayload | WebhookEditMessageOptions, - ): Promise; - public fetchMessage(message: Snowflake, options?: WebhookFetchMessageOptions): Promise; - public send(options: string | MessagePayload | WebhookMessageOptions): Promise; + ): Promise; + public fetchMessage(message: Snowflake, options?: WebhookFetchMessageOptions): Promise; + public send(options: string | MessagePayload | WebhookMessageOptions): Promise; } export class WebSocketManager extends EventEmitter { @@ -3428,9 +3438,9 @@ export interface PartialWebhookFields { editMessage( message: MessageResolvable | '@original', options: string | MessagePayload | WebhookEditMessageOptions, - ): Promise; - fetchMessage(message: Snowflake | '@original', options?: WebhookFetchMessageOptions): Promise; - send(options: string | MessagePayload | Omit): Promise; + ): Promise; + fetchMessage(message: Snowflake | '@original', options?: WebhookFetchMessageOptions): Promise; + send(options: string | MessagePayload | Omit): Promise; } export interface WebhookFields extends PartialWebhookFields { @@ -3962,7 +3972,7 @@ export interface CommandInteractionOption channel?: CacheTypeReducer; role?: CacheTypeReducer; attachment?: AttachmentBuilder; - message?: GuildCacheMessage; + message?: Message>; } export interface CommandInteractionResolvedData { diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index c58c0e225009..4240c2ad59d7 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -1,7 +1,6 @@ import type { ChildProcess } from 'child_process'; import { APIInteractionGuildMember, - APIMessage, APIPartialChannel, APIPartialGuild, APIInteractionDataResolvedGuildMember, @@ -1186,20 +1185,20 @@ client.on('interactionCreate', async interaction => { } if (interaction.isMessageContextMenuCommand()) { - expectType(interaction.targetMessage); + expectType(interaction.targetMessage); if (interaction.inCachedGuild()) { expectType>(interaction.targetMessage); } else if (interaction.inRawGuild()) { - expectType(interaction.targetMessage); + expectType>(interaction.targetMessage); } else if (interaction.inGuild()) { - expectType(interaction.targetMessage); + expectType(interaction.targetMessage); } } if (interaction.isButton()) { expectType(interaction); expectType(interaction.component); - expectType(interaction.message); + expectType(interaction.message); if (interaction.inCachedGuild()) { expectAssignable(interaction); expectType(interaction.component); @@ -1209,22 +1208,22 @@ client.on('interactionCreate', async interaction => { } else if (interaction.inRawGuild()) { expectAssignable(interaction); expectType(interaction.component); - expectType(interaction.message); + expectType>(interaction.message); expectType(interaction.guild); - expectType>(interaction.reply({ fetchReply: true })); + expectType>>(interaction.reply({ fetchReply: true })); } else if (interaction.inGuild()) { expectAssignable(interaction); expectType(interaction.component); - expectType(interaction.message); + expectType(interaction.message); expectAssignable(interaction.guild); - expectType>(interaction.reply({ fetchReply: true })); + expectType>(interaction.reply({ fetchReply: true })); } } if (interaction.isMessageComponent()) { expectType(interaction); expectType(interaction.component); - expectType(interaction.message); + expectType(interaction.message); if (interaction.inCachedGuild()) { expectAssignable(interaction); expectType(interaction.component); @@ -1234,22 +1233,22 @@ client.on('interactionCreate', async interaction => { } else if (interaction.inRawGuild()) { expectAssignable(interaction); expectType(interaction.component); - expectType(interaction.message); + expectType>(interaction.message); expectType(interaction.guild); - expectType>(interaction.reply({ fetchReply: true })); + expectType>>(interaction.reply({ fetchReply: true })); } else if (interaction.inGuild()) { expectAssignable(interaction); expectType(interaction.component); - expectType(interaction.message); + expectType(interaction.message); expectType(interaction.guild); - expectType>(interaction.reply({ fetchReply: true })); + expectType>(interaction.reply({ fetchReply: true })); } } if (interaction.isSelectMenu()) { expectType(interaction); expectType(interaction.component); - expectType(interaction.message); + expectType(interaction.message); if (interaction.inCachedGuild()) { expectAssignable(interaction); expectType(interaction.component); @@ -1259,15 +1258,15 @@ client.on('interactionCreate', async interaction => { } else if (interaction.inRawGuild()) { expectAssignable(interaction); expectType(interaction.component); - expectType(interaction.message); + expectType>(interaction.message); expectType(interaction.guild); - expectType>(interaction.reply({ fetchReply: true })); + expectType>>(interaction.reply({ fetchReply: true })); } else if (interaction.inGuild()) { expectAssignable(interaction); expectType(interaction.component); - expectType(interaction.message); + expectType(interaction.message); expectType(interaction.guild); - expectType>(interaction.reply({ fetchReply: true })); + expectType>(interaction.reply({ fetchReply: true })); } } @@ -1275,7 +1274,7 @@ client.on('interactionCreate', async interaction => { if (interaction.inRawGuild()) { expectNotAssignable>(interaction); expectAssignable(interaction); - expectType>(interaction.reply({ fetchReply: true })); + expectType>>(interaction.reply({ fetchReply: true })); expectType(interaction.options.getMember('test')); expectType(interaction.options.getChannel('test', true)); @@ -1297,7 +1296,7 @@ client.on('interactionCreate', async interaction => { // @ts-expect-error consumeCachedCommand(interaction); expectType(interaction); - expectType>(interaction.reply({ fetchReply: true })); + expectType>(interaction.reply({ fetchReply: true })); expectType(interaction.options.getMember('test')); expectType(interaction.options.getChannel('test', true));