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:


_10
const collectorFilter = (message) => message.content.includes('discord');
_10
const collector = interaction.channel.createMessageCollector({ filter: collectorFilter, time: 15_000 });
_10
_10
collector.on('collect', (message) => {
_10
console.log(`Collected ${message.content}`);
_10
});
_10
_10
collector.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 for
  • max: Number of messages to successfully pass the filter
  • maxProcessed: 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.


_24
import quiz from './quiz.json' assert { type: 'json' };
_24
_24
// ...
_24
_24
const item = quiz[Math.floor(Math.random() * quiz.length)];
_24
_24
const collectorFilter = (response) => {
_24
return item.answers.some((answer) => answer.toLowerCase() === response.content.toLowerCase());
_24
};
_24
_24
await interaction.reply({ content: item.question });
_24
_24
try {
_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.

Tip

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.


_13
const collectorFilter = (reaction, user) => {
_13
return reaction.emoji.name === '๐Ÿ‘' && user.id === message.author.id;
_13
};
_13
_13
const collector = message.createReactionCollector({ filter: collectorFilter, time: 15_000 });
_13
_13
collector.on('collect', (reaction, user) => {
_13
console.log(`Collected ${reaction.emoji.name} from ${user.tag}`);
_13
});
_13
_13
collector.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.


_10
const collectorFilter = (reaction, user) => {
_10
return reaction.emoji.name === '๐Ÿ‘' && user.id === message.author.id;
_10
};
_10
_10
try {
_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.


_15
import { ComponentType } from 'discord.js';
_15
_15
const collector = message.createMessageComponentCollector({ componentType: ComponentType.Button, time: 15_000 });
_15
_15
collector.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
_15
collector.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.


_18
import { ComponentType } from 'discord.js';
_18
_18
const collectorFilter = (i) => {
_18
i.deferUpdate();
_18
return i.user.id === interaction.user.id;
_18
};
_18
_18
try {
_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:


_10
try {
_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.