Section titled CollectorsCollectors
Section titled Message collectorsMessage collectors
Collectors are useful to enable your bot to obtain additional input after the first command was sent. An example would be initiating a quiz, where the bot will "await" a correct response from somebody.
Section titled Basic message collectorBasic message collector
Let's take a look at a basic message collector:
_10const collectorFilter = (message) => message.content.includes('discord');_10const collector = interaction.channel.createMessageCollector({ filter: collectorFilter, time: 15_000 });_10_10collector.on('collect', (message) => {_10 console.log(`Collected ${message.content}`);_10});_10_10collector.on('end', (collected) => {_10 console.log(`Collected ${collected.size} messages`);_10});
You can provide a filter
key to the object parameter of TextChannel#createMessageCollector(). The value to this key should be a function that returns a boolean value to indicate if this message should be collected or not. To check for multiple conditions in your filter you can connect them using logical operators. If you don't provide a filter all messages in the channel the collector was started on will be collected.
Note that the above example uses implicit return for the filter function and passes it to the options object using the object property shorthand notation.
If a message passes through the filter, it will trigger the Collector#collect event for the collector
you've created. This message is then passed into the event listener as collected
and the provided function is executed. In the above example, you simply log the message. Once the collector finishes collecting based on the provided end conditions the Collector#end event emits.
You can control when a collector ends by supplying additional option keys when creating a collector:
time
: Amount of time in milliseconds the collector should run formax
: Number of messages to successfully pass the filtermaxProcessed
: Number of messages encountered (no matter the filter result)
The benefit of using an event-based collector over awaitMessages()
(its promise-based counterpart) is that you can do something directly after each message is collected, rather than just after the collector ended. You can also stop the collector manually by calling Collector#stop().
Section titled Await messagesAwait messages
Using TextChannel#awaitMessages() can be easier if you understand Promises, and it allows you to have cleaner code overall. It is essentially identical to TextChannel#createMessageCollector(), except promisified. However, the drawback of using this method is that you cannot do things before the Promise is resolved or rejected, either by an error or completion. However, it should do for most purposes, such as awaiting the correct response in a quiz. Instead of taking their example, let's set up a basic quiz command using the .awaitMessages()
feature.
First, you'll need some questions and answers to choose from, so here's a basic set:
_10[_10 {_10 "question": "What color is the sky?",_10 "answers": ["blue"]_10 },_10 {_10 "question": "How many letters are there in the alphabet?",_10 "answers": ["26", "twenty-six", "twenty six", "twentysix"]_10 }_10]
The provided set allows for responder error with an array of answers permitted. Ideally, it would be best to place this in a JSON file, which you can call quiz.json
for simplicity.
_24import quiz from './quiz.json' assert { type: 'json' };_24_24// ..._24_24const item = quiz[Math.floor(Math.random() * quiz.length)];_24_24const collectorFilter = (response) => {_24 return item.answers.some((answer) => answer.toLowerCase() === response.content.toLowerCase());_24};_24_24await interaction.reply({ content: item.question });_24_24try {_24 const collected = await interaction.channel.awaitMessages({_24 filter: collectorFilter,_24 max: 1,_24 time: 30_000,_24 errors: ['time'],_24 });_24_24 await interaction.followUp(`${collected.first().author} got the correct answer!`);_24} catch {_24 await interaction.followUp('Looks like nobody got the answer this time.');_24}
If you don't understand how .some()
works, you can read about it in more detail
here.
In this filter, you iterate through the answers to find what you want. You would like to ignore the case because simple typos can happen, so you convert each answer to its lowercase form and check if it's equal to the response in lowercase form as well. In the options section, you only want to allow one answer to pass through, hence the max: 1
setting.
The filter looks for messages that match one of the answers in the array of possible answers to pass through the collector. The max
option (the second parameter) specifies that only a maximum of one message can go through the filter successfully before the Promise successfully resolves. The errors
section specifies that time will cause it to error out, which will cause the Promise to reject if one correct answer is not received within the time limit of one minute. As you can see, there is no collect
event, so you are limited in that regard.
Section titled Reaction collectorsReaction collectors
Section titled Basic reaction collectorBasic reaction collector
These work quite similarly to message collectors, except that you apply them on a message rather than a channel. This example uses the Message#createReactionCollector() method. The filter will check for the ๐ emojiโin the default skin tone specifically, so be wary of that. It will also check that the person who reacted shares the same id as the author of the original message that the collector was assigned to.
_13const collectorFilter = (reaction, user) => {_13 return reaction.emoji.name === '๐' && user.id === message.author.id;_13};_13_13const collector = message.createReactionCollector({ filter: collectorFilter, time: 15_000 });_13_13collector.on('collect', (reaction, user) => {_13 console.log(`Collected ${reaction.emoji.name} from ${user.tag}`);_13});_13_13collector.on('end', (collected) => {_13 console.log(`Collected ${collected.size} items`);_13});
Section titled Await reactionsAwait reactions
Message#awaitReactions() works almost the same as a reaction collector, except it is Promise-based. The same differences apply as with channel collectors.
_10const collectorFilter = (reaction, user) => {_10 return reaction.emoji.name === '๐' && user.id === message.author.id;_10};_10_10try {_10 const collected = await message.awaitReactions({ filter: collectorFilter, max: 1, time: 60_000, errors: ['time'] });_10 console.log(collected.size);_10} catch (collected) {_10 console.log(`After a minute, the user did not react.`);_10}
Section titled Interaction collectorsInteraction collectors
The third type of collector allows you to collect interactions; such as when users activate a slash command or click on a button in a message.
Section titled Basic message component collectorBasic message component collector
Collecting interactions from message components works similarly to reaction collectors. In the following example, you will check that the interaction came from a button, and that the user clicking the button is the same user that initiated the command.
One important difference to note with interaction collectors is that Discord expects a response to all interactions within 3 seconds - even ones that you don't want to collect. For this reason, you may wish to .deferUpdate()
all interactions in your filter, or not use a filter at all and handle this behavior in the collect
event.
_15import { ComponentType } from 'discord.js';_15_15const collector = message.createMessageComponentCollector({ componentType: ComponentType.Button, time: 15_000 });_15_15collector.on('collect', (i) => {_15 if (i.user.id === interaction.user.id) {_15 await i.reply(`${i.user.id} clicked on the ${i.customId} button.`);_15 } else {_15 await i.reply({ content: `These buttons aren't for you!`, ephemeral: true });_15 }_15});_15_15collector.on('end', (collected) => {_15 console.log(`Collected ${collected.size} interactions.`);_15});
Section titled Await message componentAwait message component
As before, this works similarly to the message component collector, except it is Promise-based.
Unlike other Promise-based collectors, this method will only ever collect one interaction that passes the filter. If no interactions are collected before the time runs out, the Promise will reject. This behavior aligns with Discord's requirement that actions should immediately receive a response. In this example, you will use .deferUpdate()
on all interactions in the filter.
_18import { ComponentType } from 'discord.js';_18_18const collectorFilter = (i) => {_18 i.deferUpdate();_18 return i.user.id === interaction.user.id;_18};_18_18try {_18 const interaction = await message.awaitMessageComponent({_18 filter: collectorFilter,_18 componentType: ComponentType.StringSelect,_18 time: 60_000,_18 });_18_18 await interaction.editReply(`You selected ${interaction.values.join(', ')}!`);_18} catch (error) {_18 console.log('No interactions were collected.');_18}
Section titled Await modal submitAwait modal submit
If you want to wait for the submission of a modal within the context of another command or button execution, you may find the promisified collector CommandInteraction#awaitModalSubmit() useful.
As Discord does not inform you if the user dismisses the modal, supplying a maximum time
to wait for is crucial:
_10try {_10 const interaction = await initialInteraction.awaitModalSubmit({ time: 60_000, filter });_10 await interaction.editReply('Thank you for your submission!');_10} catch (error) {_10 console.log('No modal submit interaction was collected');_10}
For more information on working with modals, see the modals section of this guide.