ReactionCollector.js (6691B)
1 'use strict'; 2 3 const Collector = require('./interfaces/Collector'); 4 const Collection = require('../util/Collection'); 5 const { Events } = require('../util/Constants'); 6 7 /** 8 * @typedef {CollectorOptions} ReactionCollectorOptions 9 * @property {number} max The maximum total amount of reactions to collect 10 * @property {number} maxEmojis The maximum number of emojis to collect 11 * @property {number} maxUsers The maximum number of users to react 12 */ 13 14 /** 15 * Collects reactions on messages. 16 * Will automatically stop if the message (`'messageDelete'`), 17 * channel (`'channelDelete'`), or guild (`'guildDelete'`) are deleted. 18 * @extends {Collector} 19 */ 20 class ReactionCollector extends Collector { 21 /** 22 * @param {Message} message The message upon which to collect reactions 23 * @param {CollectorFilter} filter The filter to apply to this collector 24 * @param {ReactionCollectorOptions} [options={}] The options to apply to this collector 25 */ 26 constructor(message, filter, options = {}) { 27 super(message.client, filter, options); 28 29 /** 30 * The message upon which to collect reactions 31 * @type {Message} 32 */ 33 this.message = message; 34 35 /** 36 * The users which have reacted to this message 37 * @type {Collection} 38 */ 39 this.users = new Collection(); 40 41 /** 42 * The total number of reactions collected 43 * @type {number} 44 */ 45 this.total = 0; 46 47 this.empty = this.empty.bind(this); 48 this._handleChannelDeletion = this._handleChannelDeletion.bind(this); 49 this._handleGuildDeletion = this._handleGuildDeletion.bind(this); 50 this._handleMessageDeletion = this._handleMessageDeletion.bind(this); 51 52 if (this.client.getMaxListeners() !== 0) this.client.setMaxListeners(this.client.getMaxListeners() + 1); 53 this.client.on(Events.MESSAGE_REACTION_ADD, this.handleCollect); 54 this.client.on(Events.MESSAGE_REACTION_REMOVE, this.handleDispose); 55 this.client.on(Events.MESSAGE_REACTION_REMOVE_ALL, this.empty); 56 this.client.on(Events.MESSAGE_DELETE, this._handleMessageDeletion); 57 this.client.on(Events.CHANNEL_DELETE, this._handleChannelDeletion); 58 this.client.on(Events.GUILD_DELETE, this._handleGuildDeletion); 59 60 this.once('end', () => { 61 this.client.removeListener(Events.MESSAGE_REACTION_ADD, this.handleCollect); 62 this.client.removeListener(Events.MESSAGE_REACTION_REMOVE, this.handleDispose); 63 this.client.removeListener(Events.MESSAGE_REACTION_REMOVE_ALL, this.empty); 64 this.client.removeListener(Events.MESSAGE_DELETE, this._handleMessageDeletion); 65 this.client.removeListener(Events.CHANNEL_DELETE, this._handleChannelDeletion); 66 this.client.removeListener(Events.GUILD_DELETE, this._handleGuildDeletion); 67 if (this.client.getMaxListeners() !== 0) this.client.setMaxListeners(this.client.getMaxListeners() - 1); 68 }); 69 70 this.on('collect', (reaction, user) => { 71 this.total++; 72 this.users.set(user.id, user); 73 }); 74 75 this.on('remove', (reaction, user) => { 76 this.total--; 77 if (!this.collected.some(r => r.users.cache.has(user.id))) this.users.delete(user.id); 78 }); 79 } 80 81 /** 82 * Handles an incoming reaction for possible collection. 83 * @param {MessageReaction} reaction The reaction to possibly collect 84 * @returns {?Snowflake|string} 85 * @private 86 */ 87 collect(reaction) { 88 /** 89 * Emitted whenever a reaction is collected. 90 * @event ReactionCollector#collect 91 * @param {MessageReaction} reaction The reaction that was collected 92 * @param {User} user The user that added the reaction 93 */ 94 if (reaction.message.id !== this.message.id) return null; 95 return ReactionCollector.key(reaction); 96 } 97 98 /** 99 * Handles a reaction deletion for possible disposal. 100 * @param {MessageReaction} reaction The reaction to possibly dispose of 101 * @param {User} user The user that removed the reaction 102 * @returns {?Snowflake|string} 103 */ 104 dispose(reaction, user) { 105 /** 106 * Emitted whenever a reaction is disposed of. 107 * @event ReactionCollector#dispose 108 * @param {MessageReaction} reaction The reaction that was disposed of 109 * @param {User} user The user that removed the reaction 110 */ 111 if (reaction.message.id !== this.message.id) return null; 112 113 /** 114 * Emitted whenever a reaction is removed from a message. Will emit on all reaction removals, 115 * as opposed to {@link Collector#dispose} which will only be emitted when the entire reaction 116 * is removed. 117 * @event ReactionCollector#remove 118 * @param {MessageReaction} reaction The reaction that was removed 119 * @param {User} user The user that removed the reaction 120 */ 121 if (this.collected.has(ReactionCollector.key(reaction)) && this.users.has(user.id)) { 122 this.emit('remove', reaction, user); 123 } 124 return reaction.count ? null : ReactionCollector.key(reaction); 125 } 126 127 /** 128 * Empties this reaction collector. 129 */ 130 empty() { 131 this.total = 0; 132 this.collected.clear(); 133 this.users.clear(); 134 this.checkEnd(); 135 } 136 137 endReason() { 138 if (this.options.max && this.total >= this.options.max) return 'limit'; 139 if (this.options.maxEmojis && this.collected.size >= this.options.maxEmojis) return 'emojiLimit'; 140 if (this.options.maxUsers && this.users.size >= this.options.maxUsers) return 'userLimit'; 141 return null; 142 } 143 144 /** 145 * Handles checking if the message has been deleted, and if so, stops the collector with the reason 'messageDelete'. 146 * @private 147 * @param {Message} message The message that was deleted 148 * @returns {void} 149 */ 150 _handleMessageDeletion(message) { 151 if (message.id === this.message.id) { 152 this.stop('messageDelete'); 153 } 154 } 155 156 /** 157 * Handles checking if the channel has been deleted, and if so, stops the collector with the reason 'channelDelete'. 158 * @private 159 * @param {GuildChannel} channel The channel that was deleted 160 * @returns {void} 161 */ 162 _handleChannelDeletion(channel) { 163 if (channel.id === this.message.channel.id) { 164 this.stop('channelDelete'); 165 } 166 } 167 168 /** 169 * Handles checking if the guild has been deleted, and if so, stops the collector with the reason 'guildDelete'. 170 * @private 171 * @param {Guild} guild The guild that was deleted 172 * @returns {void} 173 */ 174 _handleGuildDeletion(guild) { 175 if (this.message.guild && guild.id === this.message.guild.id) { 176 this.stop('guildDelete'); 177 } 178 } 179 180 /** 181 * Gets the collector key for a reaction. 182 * @param {MessageReaction} reaction The message reaction to get the key for 183 * @returns {Snowflake|string} 184 */ 185 static key(reaction) { 186 return reaction.emoji.id || reaction.emoji.name; 187 } 188 } 189 190 module.exports = ReactionCollector;