feat: allow createMessageComponentCollector without using fetchReply (#7623)

* feat: allow creation of message component collectors without fetchReply

* chore: attempt at requested changes

* fix: collector bug

* refactor: use better names

* feat: add update() support

* feat: add defer support

* refactor: InteractionReply -> InteractionResponse

* fix: remove log

* chore: make requested changes

* Update packages/discord.js/src/structures/InteractionResponse.js

Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>

* chore: make requested changes

Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
This commit is contained in:
Suneet Tipirneni
2022-04-17 04:59:36 -04:00
committed by GitHub
parent 40b9a1d67d
commit a58556adc0
5 changed files with 119 additions and 19 deletions

View File

@@ -117,6 +117,7 @@ exports.Integration = require('./structures/Integration');
exports.IntegrationApplication = require('./structures/IntegrationApplication');
exports.Interaction = require('./structures/Interaction');
exports.InteractionCollector = require('./structures/InteractionCollector');
exports.InteractionResponse = require('./structures/InteractionResponse');
exports.InteractionWebhook = require('./structures/InteractionWebhook');
exports.Invite = require('./structures/Invite');
exports.InviteStageInstance = require('./structures/InviteStageInstance');

View File

@@ -14,6 +14,8 @@ const Events = require('../util/Events');
* @property {number} [maxComponents] The maximum number of components to collect
* @property {number} [maxUsers] The maximum number of users to interact
* @property {Message|APIMessage} [message] The message to listen to interactions from
* @property {InteractionResponse} interactionResponse The interaction response to listen
* to message component interactions from
*/
/**
@@ -40,18 +42,28 @@ class InteractionCollector extends Collector {
*/
this.messageId = options.message?.id ?? null;
/**
* The message interaction id from which to collect interactions, if provided
* @type {?Snowflake}
*/
this.messageInteractionId = options.interactionResponse?.id ?? null;
/**
* The channel from which to collect interactions, if provided
* @type {?Snowflake}
*/
this.channelId =
options.message?.channelId ?? options.message?.channel_id ?? this.client.channels.resolveId(options.channel);
options.interactionResponse?.interaction.channelId ??
options.message?.channelId ??
options.message?.channel_id ??
this.client.channels.resolveId(options.channel);
/**
* The guild from which to collect interactions, if provided
* @type {?Snowflake}
*/
this.guildId =
options.interactionResponse?.interaction.guildId ??
options.message?.guildId ??
options.message?.guild_id ??
this.client.guilds.resolveId(options.channel?.guild) ??
@@ -87,7 +99,7 @@ class InteractionCollector extends Collector {
if (messages.has(this.messageId)) this.stop('messageDelete');
};
if (this.messageId) {
if (this.messageId || this.messageInteractionId) {
this._handleMessageDeletion = this._handleMessageDeletion.bind(this);
this.client.on(Events.MessageDelete, this._handleMessageDeletion);
this.client.on(Events.MessageBulkDelete, bulkDeleteListener);
@@ -138,6 +150,7 @@ class InteractionCollector extends Collector {
if (this.interactionType && interaction.type !== this.interactionType) return null;
if (this.componentType && interaction.componentType !== this.componentType) return null;
if (this.messageId && interaction.message?.id !== this.messageId) return null;
if (this.messageInteractionId && interaction.message?.interaction?.id !== this.messageInteractionId) return null;
if (this.channelId && interaction.channelId !== this.channelId) return null;
if (this.guildId && interaction.guildId !== this.guildId) return null;
@@ -158,6 +171,7 @@ class InteractionCollector extends Collector {
if (this.type && interaction.type !== this.type) return null;
if (this.componentType && interaction.componentType !== this.componentType) return null;
if (this.messageId && interaction.message?.id !== this.messageId) return null;
if (this.messageInteractionId && interaction.message?.interaction?.id !== this.messageInteractionId) return null;
if (this.channelId && interaction.channelId !== this.channelId) return null;
if (this.guildId && interaction.guildId !== this.guildId) return null;
@@ -196,6 +210,10 @@ class InteractionCollector extends Collector {
if (message.id === this.messageId) {
this.stop('messageDelete');
}
if (message.interaction.id === this.messageInteractionId) {
this.stop('messageDelete');
}
}
/**

View File

@@ -0,0 +1,61 @@
'use strict';
const { InteractionType } = require('discord-api-types/v10');
/**
* Represents an interaction's response
*/
class InteractionResponse {
/**
* @param {Interaction} interaction The interaction associated with this response
* @param {Snowflake?} id The interaction id associated with the original response
* @private
*/
constructor(interaction, id) {
/**
* The interaction associated with the interaction response
* @type {Interaction}
*/
this.interaction = interaction;
/**
* The id of the original interaction response
* @type {Snowflake}
*/
this.id = id ?? interaction.id;
this.client = interaction.client;
}
/**
* Collects a single component interaction that passes the filter.
* The Promise will reject if the time expires.
* @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
* @returns {Promise<MessageComponentInteraction>}
*/
awaitMessageComponent(options = {}) {
const _options = { ...options, max: 1 };
return new Promise((resolve, reject) => {
const collector = this.createMessageComponentCollector(_options);
collector.once('end', (interactions, reason) => {
const interaction = interactions.first();
if (interaction) resolve(interaction);
else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
});
});
}
/**
* Creates a message component interaction collector
* @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
* @returns {InteractionCollector}
*/
createMessageComponentCollector(options = {}) {
return new InteractionCollector(this.client, {
...options,
interactionResponse: this,
interactionType: InteractionType.MessageComponent,
});
}
}
const InteractionCollector = require('./InteractionCollector');
module.exports = InteractionResponse;

View File

@@ -4,6 +4,7 @@ const { isJSONEncodable } = require('@discordjs/builders');
const { InteractionResponseType, MessageFlags, Routes, InteractionType } = require('discord-api-types/v10');
const { Error } = require('../../errors');
const InteractionCollector = require('../InteractionCollector');
const InteractionResponse = require('../InteractionResponse');
const MessagePayload = require('../MessagePayload');
/**
@@ -49,7 +50,7 @@ class InteractionResponses {
/**
* Defers the reply to this interaction.
* @param {InteractionDeferReplyOptions} [options] Options for deferring the reply to this interaction
* @returns {Promise<Message|APIMessage|void>}
* @returns {Promise<Message|APIMessage|InteractionResponse>}
* @example
* // Defer the reply to this interaction
* interaction.deferReply()
@@ -75,14 +76,14 @@ class InteractionResponses {
});
this.deferred = true;
return options.fetchReply ? this.fetchReply() : undefined;
return options.fetchReply ? this.fetchReply() : new InteractionResponse(this);
}
/**
* Creates a reply to this interaction.
* <info>Use the `fetchReply` option to get the bot's reply message.</info>
* @param {string|MessagePayload|InteractionReplyOptions} options The options for the reply
* @returns {Promise<Message|APIMessage|void>}
* @returns {Promise<Message|APIMessage|InteractionResponse>}
* @example
* // Reply to the interaction and fetch the response
* interaction.reply({ content: 'Pong!', fetchReply: true })
@@ -116,7 +117,7 @@ class InteractionResponses {
});
this.replied = true;
return options.fetchReply ? this.fetchReply() : undefined;
return options.fetchReply ? this.fetchReply() : new InteractionResponse(this);
}
/**
@@ -179,7 +180,7 @@ class InteractionResponses {
/**
* Defers an update to the message to which the component was attached.
* @param {InteractionDeferUpdateOptions} [options] Options for deferring the update to this interaction
* @returns {Promise<Message|APIMessage|void>}
* @returns {Promise<Message|APIMessage|InteractionResponse>}
* @example
* // Defer updating and reset the component's loading state
* interaction.deferUpdate()
@@ -196,7 +197,7 @@ class InteractionResponses {
});
this.deferred = true;
return options.fetchReply ? this.fetchReply() : undefined;
return options.fetchReply ? this.fetchReply() : new InteractionResponse(this, this.message.interaction.id);
}
/**
@@ -231,7 +232,7 @@ class InteractionResponses {
});
this.replied = true;
return options.fetchReply ? this.fetchReply() : undefined;
return options.fetchReply ? this.fetchReply() : new InteractionResponse(this, this.message.interaction.id);
}
/**

View File

@@ -397,6 +397,8 @@ export interface InteractionResponseFields<Cached extends CacheType = CacheType>
awaitModalSubmit(options: AwaitModalSubmitOptions<ModalSubmitInteraction>): Promise<ModalSubmitInteraction<Cached>>;
}
export type BooleanCache<T extends CacheType> = T extends ['cached'] ? true : false;
export abstract class CommandInteraction<Cached extends CacheType = CacheType> extends Interaction<Cached> {
public get command(): ApplicationCommand | ApplicationCommand<{ guild: GuildResolvable }> | null;
public options: Omit<
@@ -426,13 +428,15 @@ export abstract class CommandInteraction<Cached extends CacheType = CacheType> e
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): Promise<void>;
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 reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
public reply(
options: string | MessagePayload | InteractionReplyOptions,
): Promise<InteractionResponse<BooleanCache<Cached>>>;
public showModal(
modal: JSONEncodable<APIModalInteractionResponseCallbackData> | ModalData | APIModalInteractionResponseCallbackData,
): Promise<void>;
@@ -445,6 +449,19 @@ export abstract class CommandInteraction<Cached extends CacheType = CacheType> e
): CommandInteractionResolvedData<Cached>;
}
export class InteractionResponse<Cached extends boolean = boolean> {
private constructor(interaction: Interaction, id?: Snowflake);
public interaction: Interaction<WrapBooleanCache<Cached>>;
public client: Client;
public id: Snowflake;
public awaitMessageComponent<T extends MessageComponentType = ComponentType.ActionRow>(
options?: AwaitMessageCollectorOptionsParams<T, Cached>,
): Promise<MappedInteractionTypes<Cached>[T]>;
public createMessageComponentCollector<T extends MessageComponentType = ComponentType.ActionRow>(
options?: MessageCollectorOptionsParams<T, Cached>,
): InteractionCollector<MappedInteractionTypes<Cached>[T]>;
}
export abstract class BaseGuild extends Base {
protected constructor(client: Client, data: RawBaseGuildData);
public get createdAt(): Date;
@@ -1522,6 +1539,7 @@ export class InteractionCollector<T extends Interaction> extends Collector<Snowf
private _handleGuildDeletion(guild: Guild): void;
public channelId: Snowflake | null;
public messageInteractionId: Snowflake | null;
public componentType: ComponentType | null;
public get endReason(): string | null;
public guildId: Snowflake | null;
@@ -1767,17 +1785,17 @@ export class MessageComponentInteraction<Cached extends CacheType = CacheType> e
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): Promise<void>;
public deferReply(options?: InteractionDeferReplyOptions): Promise<InteractionResponse<BooleanCache<Cached>>>;
public deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public deferUpdate(options?: InteractionDeferUpdateOptions): Promise<void>;
public deferUpdate(options?: InteractionDeferUpdateOptions): Promise<InteractionResponse>;
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 reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<InteractionResponse>;
public update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public update(options: string | MessagePayload | InteractionUpdateOptions): Promise<void>;
public update(options: string | MessagePayload | InteractionUpdateOptions): Promise<InteractionResponse>;
public showModal(
modal: JSONEncodable<APIModalInteractionResponseCallbackData> | ModalData | APIModalInteractionResponseCallbackData,
): Promise<void>;
@@ -1882,9 +1900,9 @@ export interface ModalMessageModalSubmitInteraction<Cached extends CacheType = C
extends ModalSubmitInteraction<Cached> {
message: GuildCacheMessage<Cached>;
update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
update(options: string | MessagePayload | InteractionUpdateOptions): Promise<void>;
update(options: string | MessagePayload | InteractionUpdateOptions): Promise<InteractionResponse>;
deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
deferUpdate(options?: InteractionDeferUpdateOptions): Promise<void>;
deferUpdate(options?: InteractionDeferUpdateOptions): Promise<InteractionResponse>;
inGuild(): this is ModalMessageModalSubmitInteraction<'raw' | 'cached'>;
inCachedGuild(): this is ModalMessageModalSubmitInteraction<'cached'>;
inRawGuild(): this is ModalMessageModalSubmitInteraction<'raw'>;
@@ -1901,11 +1919,11 @@ export class ModalSubmitInteraction<Cached extends CacheType = CacheType> extend
public replied: boolean;
public readonly webhook: InteractionWebhook;
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<InteractionResponse>;
public deleteReply(): Promise<void>;
public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<GuildCacheMessage<Cached>>;
public deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
public deferReply(options?: InteractionDeferReplyOptions): Promise<void>;
public deferReply(options?: InteractionDeferReplyOptions): Promise<InteractionResponse>;
public fetchReply(): Promise<GuildCacheMessage<Cached>>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
public inGuild(): this is ModalSubmitInteraction<'raw' | 'cached'>;
@@ -4661,6 +4679,7 @@ export interface InteractionCollectorOptions<T extends Interaction, Cached exten
maxComponents?: number;
maxUsers?: number;
message?: CacheTypeReducer<Cached, Message, APIMessage>;
interactionResponse?: InteractionResponse;
}
export interface InteractionDeferReplyOptions {