From 0d0c8f07f2535dd315ca95abe35e848c13d0ddf5 Mon Sep 17 00:00:00 2001 From: Jan <66554238+vaporox@users.noreply.github.com> Date: Sat, 26 Jun 2021 11:57:06 +0200 Subject: [PATCH] refactor(Collector): make filter an option (#5903) Co-authored-by: SpaceEEC --- src/structures/Message.js | 29 +++++------ src/structures/MessageCollector.js | 5 +- .../MessageComponentInteractionCollector.js | 5 +- src/structures/ReactionCollector.js | 11 ++-- src/structures/interfaces/Collector.js | 16 +++--- src/structures/interfaces/TextBasedChannel.js | 28 +++++------ typings/index.d.ts | 50 +++++++------------ 7 files changed, 61 insertions(+), 83 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 59ea77fdd..e6dedc78a 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -399,18 +399,17 @@ class Message extends Base { /** * Creates a reaction collector. - * @param {CollectorFilter} filter The filter to apply * @param {ReactionCollectorOptions} [options={}] Options to send to the collector * @returns {ReactionCollector} * @example * // Create a reaction collector * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someID'; - * const collector = message.createReactionCollector(filter, { time: 15000 }); + * const collector = message.createReactionCollector({ filter, time: 15000 }); * collector.on('collect', r => console.log(`Collected ${r.emoji.name}`)); * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); */ - createReactionCollector(filter, options = {}) { - return new ReactionCollector(this, filter, options); + createReactionCollector(options = {}) { + return new ReactionCollector(this, options); } /** @@ -422,19 +421,18 @@ class Message extends Base { /** * Similar to createReactionCollector but in promise form. * Resolves with a collection of reactions that pass the specified filter. - * @param {CollectorFilter} filter The filter function to use * @param {AwaitReactionsOptions} [options={}] Optional options to pass to the internal collector * @returns {Promise>} * @example * // Create a reaction collector * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someID' - * message.awaitReactions(filter, { time: 15000 }) + * message.awaitReactions({ filter, time: 15000 }) * .then(collected => console.log(`Collected ${collected.size} reactions`)) * .catch(console.error); */ - awaitReactions(filter, options = {}) { + awaitReactions(options = {}) { return new Promise((resolve, reject) => { - const collector = this.createReactionCollector(filter, options); + const collector = this.createReactionCollector(options); collector.once('end', (reactions, reason) => { if (options.errors && options.errors.includes(reason)) reject(reactions); else resolve(reactions); @@ -444,42 +442,41 @@ 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 }); + * 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); + createMessageComponentInteractionCollector(options = {}) { + return new MessageComponentInteractionCollector(this, options); } /** * An object containing the same properties as CollectorOptions, but a few more: * @typedef {Object} AwaitMessageComponentInteractionOptions + * @property {CollectorFilter} [filter] The filter applied to this collector * @property {number} [time] Time to wait for an interaction before rejecting */ /** * Collects a single component interaction that passes the filter. * The Promise will reject if the time expires. - * @param {CollectorFilter} filter The filter function to use * @param {AwaitMessageComponentInteractionOptions} [options={}] Options to pass to the internal collector * @returns {Promise} * @example * // Collect a message component interaction * const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID'; - * message.awaitMessageComponentInteraction(filter, { time: 15000 }) + * message.awaitMessageComponentInteraction({ filter, time: 15000 }) * .then(interaction => console.log(`${interaction.customID} was clicked!`)) * .catch(console.error); */ - awaitMessageComponentInteraction(filter, { time } = {}) { + awaitMessageComponentInteraction(options = {}) { return new Promise((resolve, reject) => { - const collector = this.createMessageComponentInteractionCollector(filter, { max: 1, time }); + const collector = this.createMessageComponentInteractionCollector({ ...options, max: 1 }); collector.once('end', (interactions, reason) => { const interaction = interactions.first(); if (interaction) resolve(interaction); diff --git a/src/structures/MessageCollector.js b/src/structures/MessageCollector.js index 3630d5ab6..68dc3737a 100644 --- a/src/structures/MessageCollector.js +++ b/src/structures/MessageCollector.js @@ -17,12 +17,11 @@ const { Events } = require('../util/Constants'); class MessageCollector extends Collector { /** * @param {TextChannel|DMChannel} channel The channel - * @param {CollectorFilter} filter The filter to be applied to this collector * @param {MessageCollectorOptions} options The options to be applied to this collector * @emits MessageCollector#message */ - constructor(channel, filter, options = {}) { - super(channel.client, filter, options); + constructor(channel, options = {}) { + super(channel.client, options); /** * The channel diff --git a/src/structures/MessageComponentInteractionCollector.js b/src/structures/MessageComponentInteractionCollector.js index 07105ab5a..e2b8841e5 100644 --- a/src/structures/MessageComponentInteractionCollector.js +++ b/src/structures/MessageComponentInteractionCollector.js @@ -21,11 +21,10 @@ 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); + constructor(source, options = {}) { + super(source.client, options); /** * The message from which to collect message component interactions, if provided diff --git a/src/structures/ReactionCollector.js b/src/structures/ReactionCollector.js index 70a32fcb2..149dd80a8 100644 --- a/src/structures/ReactionCollector.js +++ b/src/structures/ReactionCollector.js @@ -20,11 +20,10 @@ const { Events } = require('../util/Constants'); class ReactionCollector extends Collector { /** * @param {Message} message The message upon which to collect reactions - * @param {CollectorFilter} filter The filter to apply to this collector * @param {ReactionCollectorOptions} [options={}] The options to apply to this collector */ - constructor(message, filter, options = {}) { - super(message.client, filter, options); + constructor(message, options = {}) { + super(message.client, options); /** * The message upon which to collect reactions @@ -82,10 +81,10 @@ class ReactionCollector extends Collector { * Handles an incoming reaction for possible collection. * @param {MessageReaction} reaction The reaction to possibly collect * @param {User} user The user that added the reaction - * @returns {?(Snowflake|string)} + * @returns {Promise} * @private */ - collect(reaction, user) { + async collect(reaction, user) { /** * Emitted whenever a reaction is collected. * @event ReactionCollector#collect @@ -102,7 +101,7 @@ class ReactionCollector extends Collector { * @param {MessageReaction} reaction The reaction that was added * @param {User} user The user that added the reaction */ - if (reaction.count === 1 && this.filter(reaction, user, this.collected)) { + if (reaction.count === 1 && (await this.filter(reaction, user, this.collected))) { this.emit('create', reaction, user); } diff --git a/src/structures/interfaces/Collector.js b/src/structures/interfaces/Collector.js index d16269c48..bce34f089 100644 --- a/src/structures/interfaces/Collector.js +++ b/src/structures/interfaces/Collector.js @@ -16,6 +16,7 @@ const Util = require('../../util/Util'); /** * Options to be applied to the collector. * @typedef {Object} CollectorOptions + * @property {CollectorFilter} [filter] The filter applied to this collector * @property {number} [time] How long to run the collector for in milliseconds * @property {number} [idle] How long to stop the collector after inactivity in milliseconds * @property {boolean} [dispose=false] Whether to dispose data when it's deleted @@ -26,7 +27,7 @@ const Util = require('../../util/Util'); * @abstract */ class Collector extends EventEmitter { - constructor(client, filter, options = {}) { + constructor(client, options = {}) { super(); /** @@ -40,8 +41,9 @@ class Collector extends EventEmitter { /** * The filter applied to this collector * @type {CollectorFilter} + * @returns {boolean|Promise} */ - this.filter = filter; + this.filter = options.filter ?? (() => true); /** * The options of this collector @@ -75,8 +77,8 @@ class Collector extends EventEmitter { */ this._idletimeout = null; - if (typeof filter !== 'function') { - throw new TypeError('INVALID_TYPE', 'filter', 'function'); + if (typeof this.filter !== 'function') { + throw new TypeError('INVALID_TYPE', 'options.filter', 'function'); } this.handleCollect = this.handleCollect.bind(this); @@ -89,6 +91,7 @@ class Collector extends EventEmitter { /** * Call this to handle an event as a collectable element. Accepts any event data as parameters. * @param {...*} args The arguments emitted by the listener + * @returns {Promise} * @emits Collector#collect */ async handleCollect(...args) { @@ -115,13 +118,14 @@ class Collector extends EventEmitter { /** * Call this to remove an element from the collection. Accepts any event data as parameters. * @param {...*} args The arguments emitted by the listener + * @returns {Promise} * @emits Collector#dispose */ - handleDispose(...args) { + async handleDispose(...args) { if (!this.options.dispose) return; const dispose = this.dispose(...args); - if (!dispose || !this.filter(...args) || !this.collected.has(dispose)) return; + if (!dispose || !(await this.filter(...args)) || !this.collected.has(dispose)) return; this.collected.delete(dispose); /** diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index 6cbc79849..1beadc5a3 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -275,18 +275,17 @@ class TextBasedChannel { /** * Creates a Message Collector. - * @param {CollectorFilter} filter The filter to create the collector with * @param {MessageCollectorOptions} [options={}] The options to pass to the collector * @returns {MessageCollector} * @example * // Create a message collector * const filter = m => m.content.includes('discord'); - * const collector = channel.createMessageCollector(filter, { time: 15000 }); + * const collector = channel.createMessageCollector({ filter, time: 15000 }); * collector.on('collect', m => console.log(`Collected ${m.content}`)); * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); */ - createMessageCollector(filter, options = {}) { - return new MessageCollector(this, filter, options); + createMessageCollector(options = {}) { + return new MessageCollector(this, options); } /** @@ -298,20 +297,19 @@ class TextBasedChannel { /** * Similar to createMessageCollector but in promise form. * Resolves with a collection of messages that pass the specified filter. - * @param {CollectorFilter} filter The filter function to use * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector * @returns {Promise>} * @example * // Await !vote messages * const filter = m => m.content.startsWith('!vote'); * // Errors: ['time'] treats ending because of the time limit as an error - * channel.awaitMessages(filter, { max: 4, time: 60000, errors: ['time'] }) + * channel.awaitMessages({ filter, max: 4, time: 60000, errors: ['time'] }) * .then(collected => console.log(collected.size)) * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`)); */ - awaitMessages(filter, options = {}) { + awaitMessages(options = {}) { return new Promise((resolve, reject) => { - const collector = this.createMessageCollector(filter, options); + const collector = this.createMessageCollector(options); collector.once('end', (collection, reason) => { if (options.errors && options.errors.includes(reason)) { reject(collection); @@ -324,36 +322,34 @@ 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 }); + * 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); + createMessageComponentInteractionCollector(options = {}) { + return new MessageComponentInteractionCollector(this, options); } /** * Collects a single component interaction that passes the filter. * The Promise will reject if the time expires. - * @param {CollectorFilter} filter The filter function to use * @param {AwaitMessageComponentInteractionOptions} [options={}] Options to pass to the internal collector * @returns {Promise} * @example * // Collect a message component interaction * const filter = (interaction) => interaction.customID === 'button' && interaction.user.id === 'someID'; - * channel.awaitMessageComponentInteraction(filter, { time: 15000 }) + * channel.awaitMessageComponentInteraction({ filter, time: 15000 }) * .then(interaction => console.log(`${interaction.customID} was clicked!`)) * .catch(console.error); */ - awaitMessageComponentInteraction(filter, { time } = {}) { + awaitMessageComponentInteraction(options = {}) { return new Promise((resolve, reject) => { - const collector = this.createMessageComponentInteractionCollector(filter, { max: 1, time }); + const collector = this.createMessageComponentInteractionCollector({ ...options, max: 1 }); collector.once('end', (interactions, reason) => { const interaction = interactions.first(); if (interaction) resolve(interaction); diff --git a/typings/index.d.ts b/typings/index.d.ts index 4eba62122..6231d16eb 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -482,7 +482,7 @@ declare module 'discord.js' { } export abstract class Collector extends EventEmitter { - constructor(client: Client, filter: CollectorFilter<[V]>, options?: CollectorOptions); + constructor(client: Client, options?: CollectorOptions<[V]>); private _timeout: NodeJS.Timeout | null; private _idletimeout: NodeJS.Timeout | null; @@ -492,10 +492,10 @@ declare module 'discord.js' { public abstract endReason: string | null; public filter: CollectorFilter<[V]>; public readonly next: Promise; - public options: CollectorOptions; + public options: CollectorOptions<[V]>; public checkEnd(): void; - public handleCollect(...args: any[]): void; - public handleDispose(...args: any[]): void; + public handleCollect(...args: any[]): Promise; + public handleDispose(...args: any[]): Promise; public stop(reason?: string): void; public resetTimer(options?: CollectorResetTimerOptions): void; public [Symbol.asyncIterator](): AsyncIterableIterator; @@ -1281,19 +1281,11 @@ declare module 'discord.js' { public flags: Readonly; public reference: MessageReference | null; public awaitMessageComponentInteraction( - filter: CollectorFilter<[MessageComponentInteraction]>, options?: AwaitMessageComponentInteractionOptions, ): Promise; - public awaitReactions( - filter: CollectorFilter<[MessageReaction, User]>, - options?: AwaitReactionsOptions, - ): Promise>; - public createReactionCollector( - filter: CollectorFilter<[MessageReaction, User]>, - options?: ReactionCollectorOptions, - ): ReactionCollector; + public awaitReactions(options?: AwaitReactionsOptions): Promise>; + public createReactionCollector(options?: ReactionCollectorOptions): ReactionCollector; public createMessageComponentInteractionCollector( - filter: CollectorFilter<[MessageComponentInteraction]>, options?: MessageComponentInteractionCollectorOptions, ): MessageComponentInteractionCollector; public delete(): Promise; @@ -1372,11 +1364,7 @@ declare module 'discord.js' { } export class MessageCollector extends Collector { - constructor( - channel: TextChannel | DMChannel, - filter: CollectorFilter<[Message]>, - options?: MessageCollectorOptions, - ); + constructor(channel: TextChannel | DMChannel, options?: MessageCollectorOptions); private _handleChannelDeletion(channel: GuildChannel): void; private _handleGuildDeletion(guild: Guild): void; @@ -1413,7 +1401,6 @@ declare module 'discord.js' { export class MessageComponentInteractionCollector extends Collector { constructor( source: Message | TextChannel | NewsChannel | DMChannel, - filter: CollectorFilter<[MessageComponentInteraction]>, options?: MessageComponentInteractionCollectorOptions, ); private _handleMessageDeletion(message: Message): void; @@ -1637,7 +1624,7 @@ declare module 'discord.js' { } export class ReactionCollector extends Collector { - constructor(message: Message, filter: CollectorFilter<[MessageReaction, User]>, options?: ReactionCollectorOptions); + constructor(message: Message, options?: ReactionCollectorOptions); private _handleChannelDeletion(channel: GuildChannel): void; private _handleGuildDeletion(guild: Guild): void; private _handleMessageDeletion(message: Message): void; @@ -1650,7 +1637,7 @@ declare module 'discord.js' { public static key(reaction: MessageReaction): Snowflake | string; - public collect(reaction: MessageReaction): Snowflake | string; + public collect(reaction: MessageReaction): Promise; public dispose(reaction: MessageReaction, user: User): Snowflake | string; public empty(): void; @@ -2619,22 +2606,17 @@ declare module 'discord.js' { typing: boolean; typingCount: number; awaitMessageComponentInteraction( - filter: CollectorFilter<[MessageComponentInteraction]>, options?: AwaitMessageComponentInteractionOptions, ): Promise; - awaitMessages( - filter: CollectorFilter<[Message]>, - options?: AwaitMessagesOptions, - ): Promise>; + awaitMessages(options?: AwaitMessagesOptions): Promise>; bulkDelete( messages: Collection | readonly MessageResolvable[] | number, filterOld?: boolean, ): Promise>; createMessageComponentInteractionCollector( - filter: CollectorFilter<[MessageComponentInteraction]>, options?: MessageComponentInteractionCollectorOptions, ): MessageComponentInteractionCollector; - createMessageCollector(filter: CollectorFilter<[Message]>, options?: MessageCollectorOptions): MessageCollector; + createMessageCollector(options?: MessageCollectorOptions): MessageCollector; startTyping(count?: number): Promise; stopTyping(force?: boolean): void; } @@ -2873,6 +2855,7 @@ declare module 'discord.js' { } interface AwaitMessageComponentInteractionOptions { + filter?: CollectorFilter<[MessageComponentInteraction]>; time?: number; } @@ -3070,7 +3053,8 @@ declare module 'discord.js' { type CollectorFilter = (...args: T) => boolean | Promise; - interface CollectorOptions { + interface CollectorOptions { + filter?: CollectorFilter; time?: number; idle?: number; dispose?: boolean; @@ -3641,14 +3625,14 @@ declare module 'discord.js' { type MessageButtonStyleResolvable = MessageButtonStyle | MessageButtonStyles; - interface MessageCollectorOptions extends CollectorOptions { + interface MessageCollectorOptions extends CollectorOptions<[Message]> { max?: number; maxProcessed?: number; } type MessageComponent = BaseMessageComponent | MessageActionRow | MessageButton | MessageSelectMenu; - interface MessageComponentInteractionCollectorOptions extends CollectorOptions { + interface MessageComponentInteractionCollectorOptions extends CollectorOptions<[MessageComponentInteraction]> { max?: number; maxComponents?: number; maxUsers?: number; @@ -4082,7 +4066,7 @@ declare module 'discord.js' { remainingTime: number; } - interface ReactionCollectorOptions extends CollectorOptions { + interface ReactionCollectorOptions extends CollectorOptions<[MessageReaction, User]> { max?: number; maxEmojis?: number; maxUsers?: number;