Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(InteractionCollector): reworked to be more generic #5999

Merged
merged 10 commits into from
Jul 3, 2021
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ module.exports = {
Integration: require('./structures/Integration'),
IntegrationApplication: require('./structures/IntegrationApplication'),
Interaction: require('./structures/Interaction'),
InteractionCollector: require('./structures/InteractionCollector'),
InteractionWebhook: require('./structures/InteractionWebhook'),
Invite: require('./structures/Invite'),
Message: require('./structures/Message'),
Expand All @@ -94,7 +95,6 @@ module.exports = {
MessageButton: require('./structures/MessageButton'),
MessageCollector: require('./structures/MessageCollector'),
MessageComponentInteraction: require('./structures/MessageComponentInteraction'),
MessageComponentInteractionCollector: require('./structures/MessageComponentInteractionCollector'),
MessageEmbed: require('./structures/MessageEmbed'),
MessageMentions: require('./structures/MessageMentions'),
MessagePayload: require('./structures/MessagePayload'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,69 @@
const Collector = require('./interfaces/Collector');
const Collection = require('../util/Collection');
const { Events } = require('../util/Constants');
const { InteractionTypes, MessageComponentTypes } = 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
* @typedef {CollectorOptions} InteractionCollectorOptions
* @property {TextChannel|DMChannel|NewsChannel} [channel] The channel to listen to interactions from
* @property {MessageComponentType} [componentType] The type of component to listen for
* @property {Guild} [guild] The guild to listen to interactions from
* @property {InteractionType} [interactionType] The type of interaction to listen for
* @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
* @property {Message} [message] The message to listen to interactions from
*/

/**
* Collects interaction on message components.
* Collects interactions.
* Will automatically stop if the message (`'messageDelete'`),
* channel (`'channelDelete'`), or guild (`'guildDelete'`) are deleted.
* @extends {Collector}
*/
class MessageComponentInteractionCollector extends Collector {
class InteractionCollector extends Collector {
/**
* @param {Message|TextChannel|DMChannel|NewsChannel} source
* The source from which to collect message component interactions
* @param {MessageComponentInteractionCollectorOptions} [options={}] The options to apply to this collector
* @param {Client} client The client on which to collect message component interactions
* @param {InteractionCollectorOptions} [options={}] The options to apply to this collector
*/
constructor(source, options = {}) {
super(source.client, options);
constructor(client, options = {}) {
super(client, options);

/**
* The message from which to collect message component interactions, if provided
* The message from which to collect interactions, if provided
* @type {?Message}
*/
this.message = source instanceof require('./Message') ? source : null;
this.message = options.message ?? null;

/**
* The source channel from which to collect message component interactions
* @type {TextChannel|DMChannel|NewsChannel}
* The channel from which to collect interactions, if provided
* @type {?TextChannel|DMChannel|NewsChannel}
*/
this.channel = this.message?.channel ?? source;
this.channel = this.message?.channel ?? options.channel ?? null;

/**
* The guild from which to collect interactions, if provided
* @type {?Guild}
*/
this.guild = this.channel?.guild ?? options.guild ?? null;

/**
* The the type of interaction to collect
* @type {?InteractionType}
*/
this.interactionType =
typeof options.interactionType === 'number'
? InteractionTypes[options.interactionType]
: options.interactionType ?? null;

/**
* The the type of component to collect
* @type {?MessageComponentType}
*/
this.componentType =
typeof options.componentType === 'number'
? MessageComponentTypes[options.componentType]
: options.componentType ?? null;

/**
* The users which have interacted to components on this collector
Expand All @@ -51,23 +80,28 @@ class MessageComponentInteractionCollector extends Collector {
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);
if (this.message) {
this._handleMessageDeletion = this._handleMessageDeletion.bind(this);
this.client.on(Events.MESSAGE_DELETE, this._handleMessageDeletion);
}

if (this.channel) {
this._handleChannelDeletion = this._handleChannelDeletion.bind(this);
this.client.on(Events.CHANNEL_DELETE, this._handleChannelDeletion);
}

this.client.on(Events.CHANNEL_DELETE, this._handleChannelDeletion);
this.client.on(Events.GUILD_DELETE, this._handleGuildDeletion);
if (this.guild) {
this._handleGuildDeletion = this._handleGuildDeletion.bind(this);
this.client.on(Events.GUILD_DELETE, this._handleGuildDeletion);
}

this.client.on(Events.INTERACTION_CREATE, this.handleCollect);

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.MESSAGE_DELETE, this._handleMessageDeletion);
this.client.removeListener(Events.CHANNEL_DELETE, this._handleChannelDeletion);
this.client.removeListener(Events.GUILD_DELETE, this._handleGuildDeletion);
this.client.decrementMaxListeners();
Expand All @@ -91,13 +125,13 @@ class MessageComponentInteractionCollector extends Collector {
* @event MessageComponentInteractionCollector#collect
* @param {Interaction} interaction The interaction that was collected
*/
if (!interaction.isMessageComponent()) return null;
if (this.interactionType && interaction.type !== this.interactionType) return null;
if (this.componentType && interaction.componentType !== this.componentType) return null;
if (this.message && interaction.message?.id !== this.message.id) return null;
if (this.channel && interaction.channelID !== this.channel.id) return null;
if (this.guild && interaction.guildID !== this.guild.id) 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;
return interaction.id;
}

/**
Expand All @@ -111,13 +145,13 @@ class MessageComponentInteractionCollector extends Collector {
* @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;
}
if (this.type && interaction.type !== this.type) return null;
if (this.componentType && interaction.componentType !== this.componentType) return null;
if (this.message && interaction.message?.id !== this.message.id) return null;
if (this.channel && interaction.channelID !== this.channel.id) return null;
if (this.guild && interaction.guildID !== this.guild.id) return null;

return interaction.channel.id === this.channel.id ? interaction.id : null;
return interaction.id;
}

/**
Expand Down Expand Up @@ -161,7 +195,7 @@ class MessageComponentInteractionCollector extends Collector {
* @returns {void}
*/
_handleChannelDeletion(channel) {
if (channel.id === this.channel.id) {
if (channel.id === this.channel?.id) {
this.stop('channelDelete');
}
}
Expand All @@ -173,10 +207,10 @@ class MessageComponentInteractionCollector extends Collector {
* @returns {void}
*/
_handleGuildDeletion(guild) {
if (guild.id === this.channel.guild?.id) {
if (guild.id === this.guild?.id) {
this.stop('guildDelete');
}
}
}

module.exports = MessageComponentInteractionCollector;
module.exports = InteractionCollector;
32 changes: 23 additions & 9 deletions src/structures/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
const Base = require('./Base');
const BaseMessageComponent = require('./BaseMessageComponent');
const ClientApplication = require('./ClientApplication');
const InteractionCollector = require('./InteractionCollector');
const MessageAttachment = require('./MessageAttachment');
const MessageComponentInteractionCollector = require('./MessageComponentInteractionCollector');
const Embed = require('./MessageEmbed');
const Mentions = require('./MessageMentions');
const MessagePayload = require('./MessagePayload');
Expand Down Expand Up @@ -439,32 +439,45 @@ class Message extends Base {
});
}

/**
* @typedef {CollectorOptions} MessageComponentCollectorOptions
* @property {MessageComponentType} [componentType] The type of component to listen for
* @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
*/

/**
* Creates a message component interaction collector.
* @param {MessageComponentInteractionCollectorOptions} [options={}] Options to send to the collector
* @returns {MessageComponentInteractionCollector}
* @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
* @returns {InteractionCollector}
* @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(options = {}) {
return new MessageComponentInteractionCollector(this, options);
createMessageComponentCollector(options = {}) {
return new InteractionCollector(this.client, {
...options,
interactionType: InteractionTypes.MESSAGE_COMPONENT,
message: this,
});
}

/**
* An object containing the same properties as CollectorOptions, but a few more:
* @typedef {Object} AwaitMessageComponentInteractionOptions
* @typedef {Object} AwaitMessageComponentOptions
* @property {CollectorFilter} [filter] The filter applied to this collector
* @property {number} [time] Time to wait for an interaction before rejecting
* @property {MessageComponentType} [componentType] The type of component interaction to collect
*/

/**
* Collects a single component interaction that passes the filter.
* The Promise will reject if the time expires.
* @param {AwaitMessageComponentInteractionOptions} [options={}] Options to pass to the internal collector
* @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
* @returns {Promise<MessageComponentInteraction>}
* @example
* // Collect a message component interaction
Expand All @@ -473,9 +486,10 @@ class Message extends Base {
* .then(interaction => console.log(`${interaction.customID} was clicked!`))
* .catch(console.error);
*/
awaitMessageComponentInteraction(options = {}) {
awaitMessageComponent(options = {}) {
const _options = { ...options, max: 1 };
return new Promise((resolve, reject) => {
const collector = this.createMessageComponentInteractionCollector({ ...options, max: 1 });
const collector = this.createMessageComponentCollector(_options);
collector.once('end', (interactions, reason) => {
const interaction = interactions.first();
if (interaction) resolve(interaction);
Expand Down
26 changes: 16 additions & 10 deletions src/structures/interfaces/TextBasedChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ const MessageCollector = require('../MessageCollector');
const MessagePayload = require('../MessagePayload');
const SnowflakeUtil = require('../../util/SnowflakeUtil');
const Collection = require('../../util/Collection');
const { InteractionTypes } = require('../../util/Constants');
const { RangeError, TypeError, Error } = require('../../errors');
const MessageComponentInteractionCollector = require('../MessageComponentInteractionCollector');
const InteractionCollector = require('../InteractionCollector');

/**
* Interface for classes that have text-channel-like features.
Expand Down Expand Up @@ -304,23 +305,27 @@ class TextBasedChannel {

/**
* Creates a button interaction collector.
* @param {MessageComponentInteractionCollectorOptions} [options={}] Options to send to the collector
* @returns {MessageComponentInteractionCollector}
* @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
* @returns {InteractionCollector}
* @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(options = {}) {
return new MessageComponentInteractionCollector(this, options);
createMessageComponentCollector(options = {}) {
return new InteractionCollector(this.client, {
...options,
interactionType: InteractionTypes.MESSAGE_COMPONENT,
channel: this,
});
}

/**
* Collects a single component interaction that passes the filter.
* The Promise will reject if the time expires.
* @param {AwaitMessageComponentInteractionOptions} [options={}] Options to pass to the internal collector
* @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
* @returns {Promise<MessageComponentInteraction>}
* @example
* // Collect a message component interaction
Expand All @@ -329,9 +334,10 @@ class TextBasedChannel {
* .then(interaction => console.log(`${interaction.customID} was clicked!`))
* .catch(console.error);
*/
awaitMessageComponentInteraction(options = {}) {
awaitMessageComponent(options = {}) {
const _options = { ...options, max: 1 };
return new Promise((resolve, reject) => {
const collector = this.createMessageComponentInteractionCollector({ ...options, max: 1 });
const collector = this.createMessageComponentCollector(_options);
collector.once('end', (interactions, reason) => {
const interaction = interactions.first();
if (interaction) resolve(interaction);
Expand Down Expand Up @@ -404,8 +410,8 @@ class TextBasedChannel {
'typingCount',
'createMessageCollector',
'awaitMessages',
'createMessageComponentInteractionCollector',
'awaitMessageComponentInteraction',
'createMessageComponentCollector',
'awaitMessageComponent',
);
}
for (const prop of props) {
Expand Down
Loading