refactor: always return Message instances in interactions (#7917)

Co-authored-by: Almeida <almeidx@pm.me>
This commit is contained in:
Suneet Tipirneni
2022-06-05 13:00:35 -04:00
committed by GitHub
parent ad36c0be77
commit 9720e55534
8 changed files with 104 additions and 70 deletions

View File

@@ -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) {

View File

@@ -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]),
});
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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<Message|APIMessage>}
* @returns {Promise<Message>}
* @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<Message|APIMessage>} 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<Message>} 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<Message|APIMessage>} 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<Message>} 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);

View File

@@ -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;

View File

@@ -391,14 +391,14 @@ export interface InteractionResponseFields<Cached extends CacheType = CacheType>
ephemeral: boolean | null;
replied: boolean;
webhook: InteractionWebhook;
reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<Message>;
reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
deleteReply(): Promise<void>;
editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<GuildCacheMessage<Cached>>;
deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<Message>;
deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise<Message>;
deferReply(options?: InteractionDeferReplyOptions): Promise<void>;
fetchReply(): Promise<GuildCacheMessage<Cached>>;
followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
fetchReply(): Promise<Message>;
followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<Message>;
showModal(
modal: JSONEncodable<APIModalInteractionResponseCallbackData> | ModalData | APIModalInteractionResponseCallbackData,
): Promise<void>;
@@ -435,13 +435,15 @@ export abstract class CommandInteraction<Cached extends CacheType = CacheType> 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<GuildCacheMessage<Cached>>;
public deferReply(
options: InteractionDeferReplyOptions & { fetchReply: true },
): Promise<Message<BooleanCache<Cached>>>;
public deferReply(options?: InteractionDeferReplyOptions): Promise<InteractionResponse<BooleanCache<Cached>>>;
public deleteReply(): Promise<void>;
public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<GuildCacheMessage<Cached>>;
public fetchReply(): Promise<GuildCacheMessage<Cached>>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<Message>;
public fetchReply(): Promise<Message>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<Message>;
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<Message<BooleanCache<Cached>>>;
public reply(
options: string | MessagePayload | InteractionReplyOptions,
): Promise<InteractionResponse<BooleanCache<Cached>>>;
@@ -1536,7 +1538,7 @@ export class InteractionCollector<T extends Interaction> extends Collector<Snowf
export class InteractionWebhook extends PartialWebhookMixin() {
public constructor(client: Client, id: Snowflake, token: string);
public token: string;
public send(options: string | MessagePayload | InteractionReplyOptions): Promise<Message | APIMessage>;
public send(options: string | MessagePayload | InteractionReplyOptions): Promise<Message>;
}
export class Invite extends Base {
@@ -1758,25 +1760,29 @@ export class MessageComponentInteraction<Cached extends CacheType = CacheType> e
public channelId: Snowflake;
public deferred: boolean;
public ephemeral: boolean | null;
public message: GuildCacheMessage<Cached>;
public message: Message<BooleanCache<Cached>>;
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<GuildCacheMessage<Cached>>;
public deferReply(
options: InteractionDeferReplyOptions & { fetchReply: true },
): Promise<Message<BooleanCache<Cached>>>;
public deferReply(options?: InteractionDeferReplyOptions): Promise<InteractionResponse<BooleanCache<Cached>>>;
public deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public deferUpdate(
options: InteractionDeferUpdateOptions & { fetchReply: true },
): Promise<Message<BooleanCache<Cached>>>;
public deferUpdate(options?: InteractionDeferUpdateOptions): Promise<InteractionResponse<BooleanCache<Cached>>>;
public deleteReply(): Promise<void>;
public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<GuildCacheMessage<Cached>>;
public fetchReply(): Promise<GuildCacheMessage<Cached>>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<Message>;
public fetchReply(): Promise<Message>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<Message>;
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<Message<BooleanCache<Cached>>>;
public reply(
options: string | MessagePayload | InteractionReplyOptions,
): Promise<InteractionResponse<BooleanCache<Cached>>>;
public update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<Message>;
public update(
options: string | MessagePayload | InteractionUpdateOptions,
): Promise<InteractionResponse<BooleanCache<Cached>>>;
@@ -1904,12 +1910,12 @@ export class ModalSubmitFields {
export interface ModalMessageModalSubmitInteraction<Cached extends CacheType = CacheType>
extends ModalSubmitInteraction<Cached> {
message: GuildCacheMessage<Cached>;
update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
message: Message<BooleanCache<Cached>>;
update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<Message>;
update(
options: string | MessagePayload | InteractionUpdateOptions,
): Promise<InteractionResponse<BooleanCache<Cached>>>;
deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise<Message>;
deferUpdate(options?: InteractionDeferUpdateOptions): Promise<InteractionResponse<BooleanCache<Cached>>>;
inGuild(): this is ModalMessageModalSubmitInteraction<'raw' | 'cached'>;
inCachedGuild(): this is ModalMessageModalSubmitInteraction<'cached'>;
@@ -1923,19 +1929,23 @@ export class ModalSubmitInteraction<Cached extends CacheType = CacheType> extend
public readonly fields: ModalSubmitFields;
public deferred: boolean;
public ephemeral: boolean | null;
public message: GuildCacheMessage<Cached> | null;
public message: Message<BooleanCache<Cached>> | null;
public replied: boolean;
public readonly webhook: InteractionWebhook;
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<Message>;
public reply(
options: string | MessagePayload | InteractionReplyOptions,
): Promise<InteractionResponse<BooleanCache<Cached>>>;
public deleteReply(): Promise<void>;
public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<GuildCacheMessage<Cached>>;
public deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public editReply(
options: string | MessagePayload | WebhookEditMessageOptions,
): Promise<Message<BooleanCache<Cached>>>;
public deferReply(
options: InteractionDeferReplyOptions & { fetchReply: true },
): Promise<Message<BooleanCache<Cached>>>;
public deferReply(options?: InteractionDeferReplyOptions): Promise<InteractionResponse<BooleanCache<Cached>>>;
public fetchReply(): Promise<GuildCacheMessage<Cached>>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
public fetchReply(): Promise<Message<BooleanCache<Cached>>>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<Message<BooleanCache<Cached>>>;
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<APIMessage>;
public fetchMessage(message: Snowflake, options?: WebhookFetchMessageOptions): Promise<APIMessage>;
public send(options: string | MessagePayload | WebhookMessageOptions): Promise<APIMessage>;
): Promise<Message>;
public fetchMessage(message: Snowflake, options?: WebhookFetchMessageOptions): Promise<Message>;
public send(options: string | MessagePayload | WebhookMessageOptions): Promise<Message>;
}
export class WebSocketManager extends EventEmitter {
@@ -3428,9 +3438,9 @@ export interface PartialWebhookFields {
editMessage(
message: MessageResolvable | '@original',
options: string | MessagePayload | WebhookEditMessageOptions,
): Promise<Message | APIMessage>;
fetchMessage(message: Snowflake | '@original', options?: WebhookFetchMessageOptions): Promise<Message | APIMessage>;
send(options: string | MessagePayload | Omit<WebhookMessageOptions, 'flags'>): Promise<Message | APIMessage>;
): Promise<Message>;
fetchMessage(message: Snowflake | '@original', options?: WebhookFetchMessageOptions): Promise<Message>;
send(options: string | MessagePayload | Omit<WebhookMessageOptions, 'flags'>): Promise<Message>;
}
export interface WebhookFields extends PartialWebhookFields {
@@ -3962,7 +3972,7 @@ export interface CommandInteractionOption<Cached extends CacheType = CacheType>
channel?: CacheTypeReducer<Cached, GuildBasedChannel, APIInteractionDataResolvedChannel>;
role?: CacheTypeReducer<Cached, Role, APIRole>;
attachment?: AttachmentBuilder;
message?: GuildCacheMessage<Cached>;
message?: Message<BooleanCache<Cached>>;
}
export interface CommandInteractionResolvedData<Cached extends CacheType = CacheType> {

View File

@@ -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<Message | APIMessage>(interaction.targetMessage);
expectType<Message>(interaction.targetMessage);
if (interaction.inCachedGuild()) {
expectType<Message<true>>(interaction.targetMessage);
} else if (interaction.inRawGuild()) {
expectType<APIMessage>(interaction.targetMessage);
expectType<Message<false>>(interaction.targetMessage);
} else if (interaction.inGuild()) {
expectType<Message | APIMessage>(interaction.targetMessage);
expectType<Message>(interaction.targetMessage);
}
}
if (interaction.isButton()) {
expectType<ButtonInteraction>(interaction);
expectType<ButtonComponent | APIButtonComponent>(interaction.component);
expectType<Message | APIMessage>(interaction.message);
expectType<Message>(interaction.message);
if (interaction.inCachedGuild()) {
expectAssignable<ButtonInteraction>(interaction);
expectType<ButtonComponent>(interaction.component);
@@ -1209,22 +1208,22 @@ client.on('interactionCreate', async interaction => {
} else if (interaction.inRawGuild()) {
expectAssignable<ButtonInteraction>(interaction);
expectType<APIButtonComponent>(interaction.component);
expectType<APIMessage>(interaction.message);
expectType<Message<false>>(interaction.message);
expectType<null>(interaction.guild);
expectType<Promise<APIMessage>>(interaction.reply({ fetchReply: true }));
expectType<Promise<Message<false>>>(interaction.reply({ fetchReply: true }));
} else if (interaction.inGuild()) {
expectAssignable<ButtonInteraction>(interaction);
expectType<ButtonComponent | APIButtonComponent>(interaction.component);
expectType<Message | APIMessage>(interaction.message);
expectType<Message>(interaction.message);
expectAssignable<Guild | null>(interaction.guild);
expectType<Promise<APIMessage | Message>>(interaction.reply({ fetchReply: true }));
expectType<Promise<Message>>(interaction.reply({ fetchReply: true }));
}
}
if (interaction.isMessageComponent()) {
expectType<MessageComponentInteraction>(interaction);
expectType<MessageActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
expectType<Message | APIMessage>(interaction.message);
expectType<Message>(interaction.message);
if (interaction.inCachedGuild()) {
expectAssignable<MessageComponentInteraction>(interaction);
expectType<MessageActionRowComponent>(interaction.component);
@@ -1234,22 +1233,22 @@ client.on('interactionCreate', async interaction => {
} else if (interaction.inRawGuild()) {
expectAssignable<MessageComponentInteraction>(interaction);
expectType<APIButtonComponent | APISelectMenuComponent>(interaction.component);
expectType<APIMessage>(interaction.message);
expectType<Message<false>>(interaction.message);
expectType<null>(interaction.guild);
expectType<Promise<APIMessage>>(interaction.reply({ fetchReply: true }));
expectType<Promise<Message<false>>>(interaction.reply({ fetchReply: true }));
} else if (interaction.inGuild()) {
expectAssignable<MessageComponentInteraction>(interaction);
expectType<MessageActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
expectType<Message | APIMessage>(interaction.message);
expectType<Message>(interaction.message);
expectType<Guild | null>(interaction.guild);
expectType<Promise<APIMessage | Message>>(interaction.reply({ fetchReply: true }));
expectType<Promise<Message>>(interaction.reply({ fetchReply: true }));
}
}
if (interaction.isSelectMenu()) {
expectType<SelectMenuInteraction>(interaction);
expectType<SelectMenuComponent | APISelectMenuComponent>(interaction.component);
expectType<Message | APIMessage>(interaction.message);
expectType<Message>(interaction.message);
if (interaction.inCachedGuild()) {
expectAssignable<SelectMenuInteraction>(interaction);
expectType<SelectMenuComponent>(interaction.component);
@@ -1259,15 +1258,15 @@ client.on('interactionCreate', async interaction => {
} else if (interaction.inRawGuild()) {
expectAssignable<SelectMenuInteraction>(interaction);
expectType<APISelectMenuComponent>(interaction.component);
expectType<APIMessage>(interaction.message);
expectType<Message<false>>(interaction.message);
expectType<null>(interaction.guild);
expectType<Promise<APIMessage>>(interaction.reply({ fetchReply: true }));
expectType<Promise<Message<false>>>(interaction.reply({ fetchReply: true }));
} else if (interaction.inGuild()) {
expectAssignable<SelectMenuInteraction>(interaction);
expectType<SelectMenuComponent | APISelectMenuComponent>(interaction.component);
expectType<Message | APIMessage>(interaction.message);
expectType<Message>(interaction.message);
expectType<Guild | null>(interaction.guild);
expectType<Promise<Message | APIMessage>>(interaction.reply({ fetchReply: true }));
expectType<Promise<Message>>(interaction.reply({ fetchReply: true }));
}
}
@@ -1275,7 +1274,7 @@ client.on('interactionCreate', async interaction => {
if (interaction.inRawGuild()) {
expectNotAssignable<Interaction<'cached'>>(interaction);
expectAssignable<ChatInputCommandInteraction>(interaction);
expectType<Promise<APIMessage>>(interaction.reply({ fetchReply: true }));
expectType<Promise<Message<false>>>(interaction.reply({ fetchReply: true }));
expectType<APIInteractionDataResolvedGuildMember | null>(interaction.options.getMember('test'));
expectType<APIInteractionDataResolvedChannel>(interaction.options.getChannel('test', true));
@@ -1297,7 +1296,7 @@ client.on('interactionCreate', async interaction => {
// @ts-expect-error
consumeCachedCommand(interaction);
expectType<ChatInputCommandInteraction>(interaction);
expectType<Promise<Message | APIMessage>>(interaction.reply({ fetchReply: true }));
expectType<Promise<Message>>(interaction.reply({ fetchReply: true }));
expectType<APIInteractionDataResolvedGuildMember | GuildMember | null>(interaction.options.getMember('test'));
expectType<GuildBasedChannel | APIInteractionDataResolvedChannel>(interaction.options.getChannel('test', true));