mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-12 09:33:32 +01:00
feat(MessageComponents): clickybois (MessageButton, MessageActionRow, associated Collectors) (#5674)
Co-authored-by: Vicente <33096355+Vicente015@users.noreply.github.com> Co-authored-by: Shubham Parihar <shubhamparihar391@gmail.com> Co-authored-by: SpaceEEC <spaceeec@yahoo.com> Co-authored-by: BannerBomb <BannerBomb55@gmail.com> Co-authored-by: Arechi <22101241+Arechii@users.noreply.github.com> Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com> Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com> Co-authored-by: Antonio Román <kyradiscord@gmail.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const MessageAttachment = require('./MessageAttachment');
|
||||
const MessageEmbed = require('./MessageEmbed');
|
||||
const { RangeError } = require('../errors');
|
||||
@@ -151,6 +152,8 @@ class APIMessage {
|
||||
}
|
||||
const embeds = embedLikes.map(e => new MessageEmbed(e).toJSON());
|
||||
|
||||
const components = this.options.components?.map(c => BaseMessageComponent.create(c).toJSON());
|
||||
|
||||
let username;
|
||||
let avatarURL;
|
||||
if (isWebhook) {
|
||||
@@ -196,6 +199,7 @@ class APIMessage {
|
||||
nonce,
|
||||
embed: !isWebhookLike ? (this.options.embed === null ? null : embeds[0]) : undefined,
|
||||
embeds: isWebhookLike ? embeds : undefined,
|
||||
components,
|
||||
username,
|
||||
avatar_url: avatarURL,
|
||||
allowed_mentions:
|
||||
|
||||
94
src/structures/BaseMessageComponent.js
Normal file
94
src/structures/BaseMessageComponent.js
Normal file
@@ -0,0 +1,94 @@
|
||||
'use strict';
|
||||
|
||||
const { TypeError } = require('../errors');
|
||||
const { MessageComponentTypes, Events } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents an interactive component of a Message. It should not be necessary to construct this directly.
|
||||
* See {@link MessageComponent}
|
||||
*/
|
||||
class BaseMessageComponent {
|
||||
/**
|
||||
* Options for a BaseMessageComponent
|
||||
* @typedef {Object} BaseMessageComponentOptions
|
||||
* @property {MessageComponentTypeResolvable} type The type of this component
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved into options for a MessageComponent. This can be:
|
||||
* * MessageActionRowOptions
|
||||
* * MessageButtonOptions
|
||||
* @typedef {MessageActionRowOptions|MessageButtonOptions} MessageComponentOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Components that can be sent in a message
|
||||
* @typedef {MessageActionRow|MessageButton} MessageComponent
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved to a MessageComponentType. This can be:
|
||||
* * {@link MessageComponentType}
|
||||
* * string
|
||||
* * number
|
||||
* @typedef {string|number|MessageComponentType} MessageComponentTypeResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {BaseMessageComponent|BaseMessageComponentOptions} [data={}] The options for this component
|
||||
*/
|
||||
constructor(data) {
|
||||
/**
|
||||
* The type of this component
|
||||
* @type {?MessageComponentType}
|
||||
*/
|
||||
this.type = 'type' in data ? BaseMessageComponent.resolveType(data.type) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MessageComponent based on the type of the incoming data
|
||||
* @param {MessageComponentOptions} data Data for a MessageComponent
|
||||
* @param {Client|WebhookClient} [client] Client constructing this component
|
||||
* @param {boolean} [skipValidation=false] Whether or not to validate the component type
|
||||
* @returns {?MessageComponent}
|
||||
* @private
|
||||
*/
|
||||
static create(data, client, skipValidation = false) {
|
||||
let component;
|
||||
let type = data.type;
|
||||
|
||||
if (typeof type === 'string') type = MessageComponentTypes[type];
|
||||
|
||||
switch (type) {
|
||||
case MessageComponentTypes.ACTION_ROW: {
|
||||
const MessageActionRow = require('./MessageActionRow');
|
||||
component = new MessageActionRow(data);
|
||||
break;
|
||||
}
|
||||
case MessageComponentTypes.BUTTON: {
|
||||
const MessageButton = require('./MessageButton');
|
||||
component = new MessageButton(data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (client) {
|
||||
client.emit(Events.DEBUG, `[BaseMessageComponent] Received component with unknown type: ${data.type}`);
|
||||
} else if (!skipValidation) {
|
||||
throw new TypeError('INVALID_TYPE', 'data.type', 'valid MessageComponentType');
|
||||
}
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the type of a MessageComponent
|
||||
* @param {MessageComponentTypeResolvable} type The type to resolve
|
||||
* @returns {MessageComponentType}
|
||||
* @private
|
||||
*/
|
||||
static resolveType(type) {
|
||||
return typeof type === 'string' ? type : MessageComponentTypes[type];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseMessageComponent;
|
||||
@@ -1,16 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const APIMessage = require('./APIMessage');
|
||||
const Interaction = require('./Interaction');
|
||||
const InteractionResponses = require('./interfaces/InteractionResponses');
|
||||
const WebhookClient = require('../client/WebhookClient');
|
||||
const { Error } = require('../errors');
|
||||
const Collection = require('../util/Collection');
|
||||
const { ApplicationCommandOptionTypes, InteractionResponseTypes } = require('../util/Constants');
|
||||
const MessageFlags = require('../util/MessageFlags');
|
||||
const { ApplicationCommandOptionTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents a command interaction.
|
||||
* @extends {Interaction}
|
||||
* @implements {InteractionResponses}
|
||||
*/
|
||||
class CommandInteraction extends Interaction {
|
||||
constructor(client, data) {
|
||||
@@ -69,126 +68,6 @@ class CommandInteraction extends Interaction {
|
||||
return this.guild?.commands.cache.get(id) ?? this.client.application.commands.cache.get(id) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for deferring the reply to a {@link CommandInteraction}.
|
||||
* @typedef {Object} InteractionDeferOptions
|
||||
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defers the reply to this interaction.
|
||||
* @param {InteractionDeferOptions} [options] Options for deferring the reply to this interaction
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Defer the reply to this interaction
|
||||
* interaction.defer()
|
||||
* .then(console.log)
|
||||
* .catch(console.error)
|
||||
* @example
|
||||
* // Defer to send an ephemeral reply later
|
||||
* interaction.defer({ ephemeral: true })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async defer({ ephemeral } = {}) {
|
||||
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
|
||||
await this.client.api.interactions(this.id, this.token).callback.post({
|
||||
data: {
|
||||
type: InteractionResponseTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data: {
|
||||
flags: ephemeral ? MessageFlags.FLAGS.EPHEMERAL : undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
this.deferred = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for a reply to an interaction.
|
||||
* @typedef {BaseMessageOptions} InteractionReplyOptions
|
||||
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
|
||||
* @property {MessageEmbed[]|Object[]} [embeds] An array of embeds for the message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a reply to this interaction.
|
||||
* @param {string|APIMessage|MessageAdditions} content The content for the reply
|
||||
* @param {InteractionReplyOptions} [options] Additional options for the reply
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Reply to the interaction with an embed
|
||||
* const embed = new MessageEmbed().setDescription('Pong!');
|
||||
*
|
||||
* interaction.reply(embed)
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Create an ephemeral reply
|
||||
* interaction.reply('Pong!', { ephemeral: true })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async reply(content, options) {
|
||||
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
|
||||
const apiMessage = content instanceof APIMessage ? content : APIMessage.create(this, content, options);
|
||||
const { data, files } = await apiMessage.resolveData().resolveFiles();
|
||||
|
||||
await this.client.api.interactions(this.id, this.token).callback.post({
|
||||
data: {
|
||||
type: InteractionResponseTypes.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data,
|
||||
},
|
||||
files,
|
||||
});
|
||||
this.replied = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the initial reply to this interaction.
|
||||
* @see Webhook#fetchMessage
|
||||
* @returns {Promise<Message|Object>}
|
||||
* @example
|
||||
* // Fetch the reply to this interaction
|
||||
* interaction.fetchReply()
|
||||
* .then(reply => console.log(`Replied with ${reply.content}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async fetchReply() {
|
||||
const raw = await this.webhook.fetchMessage('@original');
|
||||
return this.channel?.messages.add(raw) ?? raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the initial reply to this interaction.
|
||||
* @see Webhook#editMessage
|
||||
* @param {string|APIMessage|MessageAdditions} content The new content for the message
|
||||
* @param {WebhookEditMessageOptions} [options] The options to provide
|
||||
* @returns {Promise<Message|Object>}
|
||||
* @example
|
||||
* // Edit the reply to this interaction
|
||||
* interaction.editReply('New content')
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async editReply(content, options) {
|
||||
const raw = await this.webhook.editMessage('@original', content, options);
|
||||
return this.channel?.messages.add(raw) ?? raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the initial reply to this interaction.
|
||||
* @see Webhook#deleteMessage
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Delete the reply to this interaction
|
||||
* interaction.deleteReply()
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async deleteReply() {
|
||||
await this.webhook.deleteMessage('@original');
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an option of a received command interaction.
|
||||
* @typedef {Object} CommandInteractionOption
|
||||
@@ -203,24 +82,6 @@ class CommandInteraction extends Interaction {
|
||||
* @property {Role|Object} [role] The resolved role
|
||||
*/
|
||||
|
||||
/**
|
||||
* Send a follow-up message to this interaction.
|
||||
* @param {string|APIMessage|MessageAdditions} content The content for the reply
|
||||
* @param {InteractionReplyOptions} [options] Additional options for the reply
|
||||
* @returns {Promise<Message|Object>}
|
||||
*/
|
||||
async followUp(content, options) {
|
||||
const apiMessage = content instanceof APIMessage ? content : APIMessage.create(this, content, options);
|
||||
const { data, files } = await apiMessage.resolveData().resolveFiles();
|
||||
|
||||
const raw = await this.client.api.webhooks(this.applicationID, this.token).post({
|
||||
data,
|
||||
files,
|
||||
});
|
||||
|
||||
return this.channel?.messages.add(raw) ?? raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an option received from the API.
|
||||
* @param {Object} option The received option
|
||||
@@ -267,6 +128,17 @@ class CommandInteraction extends Interaction {
|
||||
}
|
||||
return optionsCollection;
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by InteractionResponses
|
||||
/* eslint-disable no-empty-function */
|
||||
defer() {}
|
||||
reply() {}
|
||||
fetchReply() {}
|
||||
editReply() {}
|
||||
deleteReply() {}
|
||||
followUp() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(CommandInteraction, ['deferUpdate', 'update']);
|
||||
|
||||
module.exports = CommandInteraction;
|
||||
|
||||
@@ -91,6 +91,8 @@ class DMChannel extends Channel {
|
||||
get typingCount() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
createMessageComponentInteractionCollector() {}
|
||||
awaitMessageComponentInteractions() {}
|
||||
// Doesn't work on DM channels; bulkDelete() {}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,14 @@ class Interaction extends Base {
|
||||
isCommand() {
|
||||
return InteractionTypes[this.type] === InteractionTypes.APPLICATION_COMMAND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a component interaction.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isMessageComponent() {
|
||||
return InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Interaction;
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
const APIMessage = require('./APIMessage');
|
||||
const Base = require('./Base');
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const ClientApplication = require('./ClientApplication');
|
||||
const MessageAttachment = require('./MessageAttachment');
|
||||
const MessageComponentInteractionCollector = require('./MessageComponentInteractionCollector');
|
||||
const Embed = require('./MessageEmbed');
|
||||
const Mentions = require('./MessageMentions');
|
||||
const ReactionCollector = require('./ReactionCollector');
|
||||
@@ -123,6 +125,12 @@ class Message extends Base {
|
||||
*/
|
||||
this.embeds = (data.embeds || []).map(e => new Embed(e, true));
|
||||
|
||||
/**
|
||||
* A list of MessageActionRows in the message
|
||||
* @type {MessageActionRow[]}
|
||||
*/
|
||||
this.components = (data.components ?? []).map(c => BaseMessageComponent.create(c, this.client));
|
||||
|
||||
/**
|
||||
* A collection of attachments in the message - e.g. Pictures - mapped by their ID
|
||||
* @type {Collection<Snowflake, MessageAttachment>}
|
||||
@@ -282,6 +290,8 @@ class Message extends Base {
|
||||
if ('tts' in data) this.tts = data.tts;
|
||||
if ('embeds' in data) this.embeds = data.embeds.map(e => new Embed(e, true));
|
||||
else this.embeds = this.embeds.slice();
|
||||
if ('components' in data) this.components = data.components.map(c => BaseMessageComponent.create(c, this.client));
|
||||
else this.components = this.components.slice();
|
||||
|
||||
if ('attachments' in data) {
|
||||
this.attachments = new Collection();
|
||||
@@ -407,6 +417,51 @@ class Message extends Base {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message component interaction collector.
|
||||
* @param {CollectorFilter} filter The filter to apply
|
||||
* @param {MessageComponentInteractionCollectorOptions} [options={}] Options to send to the collector
|
||||
* @returns {MessageComponentInteractionCollector}
|
||||
* @example
|
||||
* // Create a message component interaction collector
|
||||
* const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID';
|
||||
* const collector = message.createMessageComponentInteractionCollector(filter, { time: 15000 });
|
||||
* collector.on('collect', i => console.log(`Collected ${i.customID}`));
|
||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
||||
*/
|
||||
createMessageComponentInteractionCollector(filter, options = {}) {
|
||||
return new MessageComponentInteractionCollector(this, filter, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing the same properties as CollectorOptions, but a few more:
|
||||
* @typedef {MessageComponentInteractionCollectorOptions} AwaitMessageComponentInteractionsOptions
|
||||
* @property {string[]} [errors] Stop/end reasons that cause the promise to reject
|
||||
*/
|
||||
|
||||
/**
|
||||
* Similar to createMessageComponentInteractionCollector but in promise form.
|
||||
* Resolves with a collection of interactions that pass the specified filter.
|
||||
* @param {CollectorFilter} filter The filter function to use
|
||||
* @param {AwaitMessageComponentInteractionsOptions} [options={}] Optional options to pass to the internal collector
|
||||
* @returns {Promise<Collection<string, MessageComponentInteraction>>}
|
||||
* @example
|
||||
* // Create a message component interaction collector
|
||||
* const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID';
|
||||
* message.awaitMessageComponentInteractions(filter, { time: 15000 })
|
||||
* .then(collected => console.log(`Collected ${collected.size} interactions`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
awaitMessageComponentInteractions(filter, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const collector = this.createMessageComponentInteractionCollector(filter, options);
|
||||
collector.once('end', (interactions, reason) => {
|
||||
if (options.errors && options.errors.includes(reason)) reject(interactions);
|
||||
else resolve(interactions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the message is editable by the client user
|
||||
* @type {boolean}
|
||||
|
||||
87
src/structures/MessageActionRow.js
Normal file
87
src/structures/MessageActionRow.js
Normal file
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { MessageComponentTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents an ActionRow containing message components.
|
||||
* @extends {BaseMessageComponent}
|
||||
*/
|
||||
class MessageActionRow extends BaseMessageComponent {
|
||||
/**
|
||||
* Components that can be placed in a MessageActionRow
|
||||
* * MessageButton
|
||||
* @typedef {MessageButton} MessageActionRowComponent
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for components that can be placed in a MessageActionRow
|
||||
* * MessageButtonOptions
|
||||
* @typedef {MessageButtonOptions} MessageActionRowComponentOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved into a components that can be placed in a MessageActionRow
|
||||
* * MessageActionRowComponent
|
||||
* * MessageActionRowComponentOptions
|
||||
* @typedef {MessageActionRowComponent|MessageActionRowComponentOptions} MessageActionRowComponentResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseMessageComponentOptions} MessageActionRowOptions
|
||||
* @property {MessageActionRowComponentResolvable[]} [components]
|
||||
* The components to place in this ActionRow
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {MessageActionRow|MessageActionRowOptions} [data={}] MessageActionRow to clone or raw data
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
super({ type: 'ACTION_ROW' });
|
||||
|
||||
/**
|
||||
* The components in this MessageActionRow
|
||||
* @type {MessageActionRowComponent[]}
|
||||
*/
|
||||
this.components = (data.components ?? []).map(c => BaseMessageComponent.create(c, null, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds components to the row.
|
||||
* @param {...MessageActionRowComponentResolvable[]} components The components to add
|
||||
* @returns {MessageActionRow}
|
||||
*/
|
||||
addComponents(...components) {
|
||||
this.components.push(...components.flat(Infinity).map(c => BaseMessageComponent.create(c, null, true)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, and inserts components in the action row.
|
||||
* @param {number} index The index to start at
|
||||
* @param {number} deleteCount The number of components to remove
|
||||
* @param {...MessageActionRowComponentResolvable[]} [components] The replacing components
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
spliceComponents(index, deleteCount, ...components) {
|
||||
this.components.splice(
|
||||
index,
|
||||
deleteCount,
|
||||
...components.flat(Infinity).map(c => BaseMessageComponent.create(c, null, true)),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the action row to a plain object.
|
||||
* @returns {Object} The raw data of this action row
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
components: this.components.map(c => c.toJSON()),
|
||||
type: MessageComponentTypes[this.type],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageActionRow;
|
||||
166
src/structures/MessageButton.js
Normal file
166
src/structures/MessageButton.js
Normal file
@@ -0,0 +1,166 @@
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { RangeError } = require('../errors');
|
||||
const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Represents a Button message component.
|
||||
* @extends {BaseMessageComponent}
|
||||
*/
|
||||
class MessageButton extends BaseMessageComponent {
|
||||
/**
|
||||
* @typedef {BaseMessageComponentOptions} MessageButtonOptions
|
||||
* @property {string} [label] The text to be displayed on this button
|
||||
* @property {string} [customID] A unique string to be sent in the interaction when clicked
|
||||
* @property {MessageButtonStyleResolvable} [style] The style of this button
|
||||
* @property {Emoji} [emoji] The emoji to be displayed to the left of the text
|
||||
* @property {string} [url] Optional URL for link-style buttons
|
||||
* @property {boolean} [disabled=false] Disables the button to prevent interactions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {MessageButton|MessageButtonOptions} [data={}] MessageButton to clone or raw data
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
super({ type: 'BUTTON' });
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The text to be displayed on this button
|
||||
* @type {?string}
|
||||
*/
|
||||
this.label = data.label ?? null;
|
||||
|
||||
/**
|
||||
* A unique string to be sent in the interaction when clicked
|
||||
* @type {?string}
|
||||
*/
|
||||
this.customID = data.custom_id ?? data.customID ?? null;
|
||||
|
||||
/**
|
||||
* The style of this button
|
||||
* @type {?MessageButtonStyle}
|
||||
*/
|
||||
this.style = data.style ? MessageButton.resolveStyle(data.style) : null;
|
||||
|
||||
/**
|
||||
* Emoji for this button
|
||||
* @type {?Emoji|string}
|
||||
*/
|
||||
this.emoji = data.emoji ?? null;
|
||||
|
||||
/**
|
||||
* The URL this button links to, if it is a Link style button
|
||||
* @type {?string}
|
||||
*/
|
||||
this.url = data.url ?? null;
|
||||
|
||||
/**
|
||||
* Whether this button is currently disabled
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.disabled = data.disabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom ID of this button
|
||||
* @param {string} customID A unique string to be sent in the interaction when clicked
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setCustomID(customID) {
|
||||
this.customID = Util.verifyString(customID, RangeError, 'BUTTON_CUSTOM_ID');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the interactive status of the button
|
||||
* @param {boolean} disabled Whether this button should be disabled
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setDisabled(disabled) {
|
||||
this.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the emoji of this button
|
||||
* @param {EmojiIdentifierResolvable} emoji The emoji to be displayed on this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setEmoji(emoji) {
|
||||
if (/^\d{17,19}$/.test(emoji)) this.emoji = { id: emoji };
|
||||
else this.emoji = Util.parseEmoji(`${emoji}`);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label of this button
|
||||
* @param {string} label The text to be displayed on this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setLabel(label) {
|
||||
this.label = Util.verifyString(label, RangeError, 'BUTTON_LABEL');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style of this button
|
||||
* @param {MessageButtonStyleResolvable} style The style of this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setStyle(style) {
|
||||
this.style = MessageButton.resolveStyle(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this button. MessageButton#style should be LINK
|
||||
* @param {string} url The URL of this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setURL(url) {
|
||||
this.url = Util.verifyString(url, RangeError, 'BUTTON_URL');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the button to a plain object.
|
||||
* @returns {Object} The raw data of this button
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
custom_id: this.customID,
|
||||
disabled: this.disabled,
|
||||
emoji: this.emoji,
|
||||
label: this.label,
|
||||
style: MessageButtonStyles[this.style],
|
||||
type: MessageComponentTypes[this.type],
|
||||
url: this.url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to a MessageButtonStyle. This can be
|
||||
* * {@link MessageButtonStyle}
|
||||
* * string
|
||||
* * number
|
||||
* @typedef {string|number|MessageButtonStyle} MessageButtonStyleResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves the style of a MessageButton
|
||||
* @param {MessageButtonStyleResolvable} style The style to resolve
|
||||
* @returns {MessageButtonStyle}
|
||||
* @private
|
||||
*/
|
||||
static resolveStyle(style) {
|
||||
return typeof style === 'string' ? style : MessageButtonStyles[style];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageButton;
|
||||
78
src/structures/MessageComponentInteraction.js
Normal file
78
src/structures/MessageComponentInteraction.js
Normal file
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const Interaction = require('./Interaction');
|
||||
const InteractionResponses = require('./interfaces/InteractionResponses');
|
||||
const WebhookClient = require('../client/WebhookClient');
|
||||
const { MessageComponentTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents a message component interaction.
|
||||
* @extends {Interaction}
|
||||
* @implements {InteractionResponses}
|
||||
*/
|
||||
class MessageComponentInteraction extends Interaction {
|
||||
constructor(client, data) {
|
||||
super(client, data);
|
||||
|
||||
/**
|
||||
* The message to which the component was attached
|
||||
* @type {?Message|Object}
|
||||
*/
|
||||
this.message = data.message ? this.channel?.messages.add(data.message) ?? data.message : null;
|
||||
|
||||
/**
|
||||
* The custom ID of the component which was clicked
|
||||
* @type {string}
|
||||
*/
|
||||
this.customID = data.data.custom_id;
|
||||
|
||||
/**
|
||||
* The type of component that was interacted with
|
||||
* @type {string}
|
||||
*/
|
||||
this.componentType = MessageComponentInteraction.resolveType(data.data.component_type);
|
||||
|
||||
/**
|
||||
* Whether the reply to this interaction has been deferred
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.deferred = false;
|
||||
|
||||
/**
|
||||
* Whether this interaction has already been replied to
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.replied = false;
|
||||
|
||||
/**
|
||||
* An associated webhook client, can be used to create deferred replies
|
||||
* @type {WebhookClient}
|
||||
*/
|
||||
this.webhook = new WebhookClient(this.applicationID, this.token, this.client.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the type of a MessageComponent
|
||||
* @param {MessageComponentTypeResolvable} type The type to resolve
|
||||
* @returns {MessageComponentType}
|
||||
* @private
|
||||
*/
|
||||
static resolveType(type) {
|
||||
return typeof type === 'string' ? type : MessageComponentTypes[type];
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by InteractionResponses
|
||||
/* eslint-disable no-empty-function */
|
||||
defer() {}
|
||||
reply() {}
|
||||
fetchReply() {}
|
||||
editReply() {}
|
||||
deleteReply() {}
|
||||
followUp() {}
|
||||
deferUpdate() {}
|
||||
update() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(MessageComponentInteraction);
|
||||
|
||||
module.exports = MessageComponentInteraction;
|
||||
178
src/structures/MessageComponentInteractionCollector.js
Normal file
178
src/structures/MessageComponentInteractionCollector.js
Normal file
@@ -0,0 +1,178 @@
|
||||
'use strict';
|
||||
|
||||
const Collector = require('./interfaces/Collector');
|
||||
const Collection = require('../util/Collection');
|
||||
const { Events } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* @typedef {CollectorOptions} MessageComponentInteractionCollectorOptions
|
||||
* @property {number} max The maximum total amount of interactions to collect
|
||||
* @property {number} maxComponents The maximum number of components to collect
|
||||
* @property {number} maxUsers The maximum number of users to interact
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collects interaction on message components.
|
||||
* Will automatically stop if the message (`'messageDelete'`),
|
||||
* channel (`'channelDelete'`), or guild (`'guildDelete'`) are deleted.
|
||||
* @extends {Collector}
|
||||
*/
|
||||
class MessageComponentInteractionCollector extends Collector {
|
||||
/**
|
||||
* @param {Message|TextChannel|DMChannel|NewsChannel} source
|
||||
* The source from which to collect message component interactions
|
||||
* @param {CollectorFilter} filter The filter to apply to this collector
|
||||
* @param {MessageComponentInteractionCollectorOptions} [options={}] The options to apply to this collector
|
||||
*/
|
||||
constructor(source, filter, options = {}) {
|
||||
super(source.client, filter, options);
|
||||
|
||||
/**
|
||||
* The message from which to collect message component interactions, if provided
|
||||
* @type {?Message}
|
||||
*/
|
||||
this.message = source instanceof require('./Message') ? source : null;
|
||||
|
||||
/**
|
||||
* The source channel from which to collect message component interactions
|
||||
* @type {TextChannel|DMChannel|NewsChannel}
|
||||
*/
|
||||
this.channel = this.message ? this.message.channel : source;
|
||||
|
||||
/**
|
||||
* The users which have interacted to buttons on this collector
|
||||
* @type {Collection}
|
||||
*/
|
||||
this.users = new Collection();
|
||||
|
||||
/**
|
||||
* The total number of interactions collected
|
||||
* @type {number}
|
||||
*/
|
||||
this.total = 0;
|
||||
|
||||
this.empty = this.empty.bind(this);
|
||||
this._handleChannelDeletion = this._handleChannelDeletion.bind(this);
|
||||
this._handleGuildDeletion = this._handleGuildDeletion.bind(this);
|
||||
this._handleMessageDeletion = this._handleMessageDeletion.bind(this);
|
||||
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on(Events.INTERACTION_CREATE, this.handleCollect);
|
||||
|
||||
if (this.message) this.client.on(Events.MESSAGE_DELETE, this._handleMessageDeletion);
|
||||
|
||||
this.client.on(Events.CHANNEL_DELETE, this._handleChannelDeletion);
|
||||
this.client.on(Events.GUILD_DELETE, this._handleGuildDeletion);
|
||||
|
||||
this.once('end', () => {
|
||||
this.client.removeListener(Events.INTERACTION_CREATE, this.handleCollect);
|
||||
|
||||
if (this.message) this.client.removeListener(Events.MESSAGE_DELETE, this._handleMessageDeletion);
|
||||
|
||||
this.client.removeListener(Events.CHANNEL_DELETE, this._handleChannelDeletion);
|
||||
this.client.removeListener(Events.GUILD_DELETE, this._handleGuildDeletion);
|
||||
this.client.decrementMaxListeners();
|
||||
});
|
||||
|
||||
this.on('collect', interaction => {
|
||||
this.total++;
|
||||
this.users.set(interaction.user.id, interaction.user);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an incoming interaction for possible collection.
|
||||
* @param {Interaction} interaction The interaction to possibly collect
|
||||
* @returns {?Snowflake|string}
|
||||
* @private
|
||||
*/
|
||||
collect(interaction) {
|
||||
/**
|
||||
* Emitted whenever a interaction is collected.
|
||||
* @event MessageComponentInteractionCollector#collect
|
||||
* @param {Interaction} interaction The interaction that was collected
|
||||
*/
|
||||
if (!interaction.isMessageComponent()) return null;
|
||||
|
||||
if (this.message) {
|
||||
return interaction.message.id === this.message.id ? interaction.id : null;
|
||||
}
|
||||
|
||||
return interaction.channel.id === this.channel.id ? interaction.id : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an interaction for possible disposal.
|
||||
* @param {Interaction} interaction The interaction that could be disposed of
|
||||
* @returns {?Snowflake}
|
||||
*/
|
||||
dispose(interaction) {
|
||||
/**
|
||||
* Emitted whenever an interaction is disposed of.
|
||||
* @event MessageComponentInteractionCollector#dispose
|
||||
* @param {Interaction} interaction The interaction that was disposed of
|
||||
*/
|
||||
if (!interaction.isMessageComponent()) return null;
|
||||
|
||||
if (this.message) {
|
||||
return interaction.message.id === this.message.id ? interaction.id : null;
|
||||
}
|
||||
|
||||
return interaction.channel.id === this.channel.id ? interaction.id : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties this message component collector.
|
||||
*/
|
||||
empty() {
|
||||
this.total = 0;
|
||||
this.collected.clear();
|
||||
this.users.clear();
|
||||
this.checkEnd();
|
||||
}
|
||||
|
||||
get endReason() {
|
||||
if (this.options.max && this.total >= this.options.max) return 'limit';
|
||||
if (this.options.maxComponents && this.collected.size >= this.options.maxComponents) return 'componentLimit';
|
||||
if (this.options.maxUsers && this.users.size >= this.options.maxUsers) return 'userLimit';
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles checking if the message has been deleted, and if so, stops the collector with the reason 'messageDelete'.
|
||||
* @private
|
||||
* @param {Message} message The message that was deleted
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleMessageDeletion(message) {
|
||||
if (message.id === this.message?.id) {
|
||||
this.stop('messageDelete');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles checking if the channel has been deleted, and if so, stops the collector with the reason 'channelDelete'.
|
||||
* @private
|
||||
* @param {GuildChannel} channel The channel that was deleted
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleChannelDeletion(channel) {
|
||||
if (channel.id === this.channel.id) {
|
||||
this.stop('channelDelete');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles checking if the guild has been deleted, and if so, stops the collector with the reason 'guildDelete'.
|
||||
* @private
|
||||
* @param {Guild} guild The guild that was deleted
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleGuildDeletion(guild) {
|
||||
if (guild.id === this.channel.guild?.id) {
|
||||
this.stop('guildDelete');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageComponentInteractionCollector;
|
||||
@@ -157,6 +157,8 @@ class TextChannel extends GuildChannel {
|
||||
get typingCount() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
createMessageComponentInteractionCollector() {}
|
||||
awaitMessageComponentInteractions() {}
|
||||
bulkDelete() {}
|
||||
}
|
||||
|
||||
|
||||
208
src/structures/interfaces/InteractionResponses.js
Normal file
208
src/structures/interfaces/InteractionResponses.js
Normal file
@@ -0,0 +1,208 @@
|
||||
'use strict';
|
||||
|
||||
const { InteractionResponseTypes } = require('../../util/Constants');
|
||||
const MessageFlags = require('../../util/MessageFlags');
|
||||
const APIMessage = require('../APIMessage');
|
||||
|
||||
/**
|
||||
* Interface for classes that support shared interaction response types.
|
||||
* @interface
|
||||
*/
|
||||
class InteractionResponses {
|
||||
/**
|
||||
* Options for deferring the reply to a {@link CommandInteraction}.
|
||||
* @typedef {InteractionDeferOptions}
|
||||
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for a reply to an interaction.
|
||||
* @typedef {BaseMessageOptions} InteractionReplyOptions
|
||||
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
|
||||
* @property {MessageEmbed[]|Object[]} [embeds] An array of embeds for the message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defers the reply to this interaction.
|
||||
* @param {InteractionDeferOptions} [options] Options for deferring the reply to this interaction
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Defer the reply to this interaction
|
||||
* interaction.defer()
|
||||
* .then(console.log)
|
||||
* .catch(console.error)
|
||||
* @example
|
||||
* // Defer to send an ephemeral reply later
|
||||
* interaction.defer({ ephemeral: true })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async defer({ ephemeral } = {}) {
|
||||
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
|
||||
await this.client.api.interactions(this.id, this.token).callback.post({
|
||||
data: {
|
||||
type: InteractionResponseTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data: {
|
||||
flags: ephemeral ? MessageFlags.FLAGS.EPHEMERAL : undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
this.deferred = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reply to this interaction.
|
||||
* @param {string|APIMessage|MessageAdditions} content The content for the reply
|
||||
* @param {InteractionReplyOptions} [options] Additional options for the reply
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Reply to the interaction with an embed
|
||||
* const embed = new MessageEmbed().setDescription('Pong!');
|
||||
*
|
||||
* interaction.reply(embed)
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Create an ephemeral reply
|
||||
* interaction.reply('Pong!', { ephemeral: true })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async reply(content, options) {
|
||||
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
|
||||
const apiMessage = content instanceof APIMessage ? content : APIMessage.create(this, content, options);
|
||||
const { data, files } = await apiMessage.resolveData().resolveFiles();
|
||||
|
||||
await this.client.api.interactions(this.id, this.token).callback.post({
|
||||
data: {
|
||||
type: InteractionResponseTypes.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data,
|
||||
},
|
||||
files,
|
||||
});
|
||||
this.replied = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the initial reply to this interaction.
|
||||
* @see Webhook#fetchMessage
|
||||
* @returns {Promise<Message|Object>}
|
||||
* @example
|
||||
* // Fetch the reply to this interaction
|
||||
* interaction.fetchReply()
|
||||
* .then(reply => console.log(`Replied with ${reply.content}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async fetchReply() {
|
||||
const raw = await this.webhook.fetchMessage('@original');
|
||||
return this.channel?.messages.add(raw) ?? raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the initial reply to this interaction.
|
||||
* @see Webhook#editMessage
|
||||
* @param {string|APIMessage|MessageAdditions} content The new content for the message
|
||||
* @param {WebhookEditMessageOptions} [options] The options to provide
|
||||
* @returns {Promise<Message|Object>}
|
||||
* @example
|
||||
* // Edit the reply to this interaction
|
||||
* interaction.editReply('New content')
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async editReply(content, options) {
|
||||
const raw = await this.webhook.editMessage('@original', content, options);
|
||||
return this.channel?.messages.add(raw) ?? raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the initial reply to this interaction.
|
||||
* @see Webhook#deleteMessage
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Delete the reply to this interaction
|
||||
* interaction.deleteReply()
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async deleteReply() {
|
||||
await this.webhook.deleteMessage('@original');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a follow-up message to this interaction.
|
||||
* @param {string|APIMessage|MessageAdditions} content The content for the reply
|
||||
* @param {InteractionReplyOptions} [options] Additional options for the reply
|
||||
* @returns {Promise<Message|Object>}
|
||||
*/
|
||||
async followUp(content, options) {
|
||||
const apiMessage = content instanceof APIMessage ? content : APIMessage.create(this, content, options);
|
||||
const { data, files } = await apiMessage.resolveData().resolveFiles();
|
||||
|
||||
const raw = await this.client.api.webhooks(this.applicationID, this.token).post({
|
||||
data,
|
||||
files,
|
||||
});
|
||||
|
||||
return this.channel?.messages.add(raw) ?? raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defers an update to the message to which the button was attached
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Defer to update the button to a loading state
|
||||
* interaction.deferUpdate()
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async deferUpdate() {
|
||||
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
|
||||
await this.client.api.interactions(this.id, this.token).callback.post({
|
||||
data: {
|
||||
type: InteractionResponseTypes.DEFERRED_MESSAGE_UPDATE,
|
||||
},
|
||||
});
|
||||
this.deferred = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the original message whose button was pressed
|
||||
* @param {string|APIMessage|MessageAdditions} content The content for the reply
|
||||
* @param {WebhookEditMessageOptions} [options] Additional options for the reply
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Remove the buttons from the message
|
||||
* interaction.update("A button was clicked", { components: [] })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async update(content, options) {
|
||||
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
|
||||
const apiMessage = content instanceof APIMessage ? content : APIMessage.create(this, content, options);
|
||||
const { data, files } = await apiMessage.resolveData().resolveFiles();
|
||||
|
||||
await this.client.api.interactions(this.id, this.token).callback.post({
|
||||
data: {
|
||||
type: InteractionResponseTypes.UPDATE_MESSAGE,
|
||||
data,
|
||||
},
|
||||
files,
|
||||
});
|
||||
this.replied = true;
|
||||
}
|
||||
|
||||
static applyToClass(structure, ignore = []) {
|
||||
const props = ['defer', 'reply', 'fetchReply', 'editReply', 'deleteReply', 'followUp', 'deferUpdate', 'update'];
|
||||
for (const prop of props) {
|
||||
if (ignore.includes(prop)) continue;
|
||||
Object.defineProperty(
|
||||
structure.prototype,
|
||||
prop,
|
||||
Object.getOwnPropertyDescriptor(InteractionResponses.prototype, prop),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InteractionResponses;
|
||||
@@ -6,6 +6,7 @@ const APIMessage = require('../APIMessage');
|
||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||
const Collection = require('../../util/Collection');
|
||||
const { RangeError, TypeError } = require('../../errors');
|
||||
const MessageComponentInteractionCollector = require('../MessageComponentInteractionCollector');
|
||||
|
||||
/**
|
||||
* Interface for classes that have text-channel-like features.
|
||||
@@ -315,6 +316,45 @@ class TextBasedChannel {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button interaction collector.
|
||||
* @param {CollectorFilter} filter The filter to apply
|
||||
* @param {MessageComponentInteractionCollectorOptions} [options={}] Options to send to the collector
|
||||
* @returns {MessageComponentInteractionCollector}
|
||||
* @example
|
||||
* // Create a button interaction collector
|
||||
* const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID';
|
||||
* const collector = channel.createMessageComponentInteractionCollector(filter, { time: 15000 });
|
||||
* collector.on('collect', i => console.log(`Collected ${i.customID}`));
|
||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
||||
*/
|
||||
createMessageComponentInteractionCollector(filter, options = {}) {
|
||||
return new MessageComponentInteractionCollector(this, filter, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to createMessageComponentInteractionCollector but in promise form.
|
||||
* Resolves with a collection of interactions that pass the specified filter.
|
||||
* @param {CollectorFilter} filter The filter function to use
|
||||
* @param {AwaitMessageComponentInteractionsOptions} [options={}] Optional options to pass to the internal collector
|
||||
* @returns {Promise<Collection<string, MessageComponentInteraction>>}
|
||||
* @example
|
||||
* // Create a button interaction collector
|
||||
* const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID';
|
||||
* channel.awaitMessageComponentInteractions(filter, { time: 15000 })
|
||||
* .then(collected => console.log(`Collected ${collected.size} interactions`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
awaitMessageComponentInteractions(filter, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const collector = this.createMessageComponentInteractionCollector(filter, options);
|
||||
collector.once('end', (interactions, reason) => {
|
||||
if (options.errors && options.errors.includes(reason)) reject(interactions);
|
||||
else resolve(interactions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk deletes given messages that are newer than two weeks.
|
||||
* @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages
|
||||
@@ -379,6 +419,8 @@ class TextBasedChannel {
|
||||
'typingCount',
|
||||
'createMessageCollector',
|
||||
'awaitMessages',
|
||||
'createMessageComponentInteractionCollector',
|
||||
'awaitMessageComponentInteractions',
|
||||
);
|
||||
}
|
||||
for (const prop of props) {
|
||||
|
||||
Reference in New Issue
Block a user