feat(guide): port legacy guide (#10938)

* feat: initial attempt at porting legacy guide

* feat: completion of legacy guide backport

* chore: lockfile shenanigans

* fix: handle svgs

* fix: replace svg with mermaid integration

* chore: format

* chore: remove unnecssary bullet

* chore: cleanup code highlights

* chore: explicit return

* chore: move display components after interactive components in sidebar

* chore: voice

* top link should be installation
* add docs link to sidebar

* feat: subguide-based accent styles

* chore: don't list faq twice

* chore: mention display components in interactive components

* fix: remove unoccs/order rule from guide

* chore: redirect to legacy guide instead of /guide root

* refactor: use `<kbd>`

* refactor: more kbd use

* Update apps/guide/content/docs/legacy/app-creation/handling-events.mdx

Co-authored-by: Naiyar <137700126+imnaiyar@users.noreply.github.com>

* chore: fix typos

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

* chore: fix typos

* chore: fix links regarding secret stores across coding platforms

* chore: fix typo

* chore: link node method directly

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

* chore: typos

Co-authored-by: Vlad Frangu <me@vladfrangu.dev>

* chore: typo

Co-authored-by: Vlad Frangu <me@vladfrangu.dev>

* fix: prevent v14 changes from being listed twice

* chore: prefer relative links

* chore: missed link conversion

* chore: missed link conversion

* chore: fix link

* chore: remove legacy code highlight markers

* chore: rephrase and extend contributing guidelines

* feat(setup): suggest cli flag over dotenv package

* chore: move introduction in sidebar

better navigation experience if the 'next page' in intro refers to getting started vs. updating/faq

* fix: replace outdated link

* fix: update voice dependencies

* chore: update node install instructions

* fix: list in missing access callout

* chore: match bun env file format

* chore: restore ffmpeg disclaimer

* fix: lockfile conflict

* chore: action row typo

Co-authored-by: Vlad Frangu <me@vladfrangu.dev>

* chore: no longer use at-next for pino

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>
Co-authored-by: Naiyar <137700126+imnaiyar@users.noreply.github.com>
Co-authored-by: Vlad Frangu <me@vladfrangu.dev>
This commit is contained in:
Souji
2025-07-08 15:01:50 +02:00
committed by GitHub
parent ee3ca6f7c6
commit bc6005f446
136 changed files with 11847 additions and 48 deletions

View File

@@ -0,0 +1,98 @@
---
title: Audit Logs
---
## Working with Audit Logs
### A Quick Background
Audit logs are an excellent moderation tool offered by Discord to know what happened in a server and usually by whom. Making use of audit logs requires the `ViewAuditLog` permission. Audit logs may be fetched on a server, or they may be received via the gateway event `guildAuditLogEntryCreate` which requires the `GuildModeration` intent.
There are quite a few cases where you may use audit logs. This guide will limit itself to the most common use cases. Feel free to consult the [relevant Discord API page](https://discord.com/developers/docs/resources/audit-log) for more information.
Keep in mind that these examples explore a straightforward case and are by no means exhaustive. Their purpose is to teach you how audit logs work, and expansion of these examples is likely needed to suit your specific use case.
## Fetching Audit Logs
Let's start by glancing at the `Guild#fetchAuditLogs` method and how to work with it. Like many discord.js methods, it returns a [`Promise`](../additional-info/async-await) containing the `GuildAuditLogs` object. This object has one property, `entries`, which holds a [`Collection`](../additional-info/collections) of `GuildAuditLogsEntry` objects, and consequently, the information you want to retrieve.
Here is the most basic fetch to look at some entries.
```js
const fetchedLogs = await guild.fetchAuditLogs();
const firstEntry = fetchedLogs.entries.first();
```
Simple, right? Now, let's look at utilizing its options:
```js
const { AuditLogEvent } = require('discord.js');
const fetchedLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.InviteCreate,
limit: 1,
});
const firstEntry = fetchedLogs.entries.first();
```
This will return the first entry where an invite was created. You used `limit: 1` here to specify only one entry.
## Receiving Audit Logs
Audit logs may be received via the gateway event `guildAuditLogEntryCreate`. This is the best way to receive audit logs if you want to monitor them. As soon as a message is deleted, or an invite or emoji is created, your application will receive an instance of this event. A common use case is to find out _who_ did the action that caused the audit log event to happen.
### Who deleted a message?
One of the most common use cases for audit logs is understanding who deleted a message in a Discord server. If a user deleted another user's message, you can find out who did that as soon as you receive the corresponding audit log event.
```js
const { AuditLogEvent, Events } = require('discord.js');
client.on(Events.GuildAuditLogEntryCreate, async (auditLog) => {
// Define your variables.
// The extra information here will be the channel.
const { action, extra: channel, executorId, targetId } = auditLog;
// Check only for deleted messages.
if (action !== AuditLogEvent.MessageDelete) return;
// Ensure the executor is cached.
const executor = await client.users.fetch(executorId);
// Ensure the author whose message was deleted is cached.
const target = await client.users.fetch(targetId);
// Log the output.
console.log(`A message by ${target.tag} was deleted by ${executor.tag} in ${channel}.`);
});
```
With this, you now have a very simple logger telling you who deleted a message authored by another person.
### Who kicked a user?
This is very similar to the example above.
```js
const { AuditLogEvent, Events } = require('discord.js');
client.on(Events.GuildAuditLogEntryCreate, async (auditLog) => {
// Define your variables.
const { action, executorId, targetId } = auditLog;
// Check only for kicked users.
if (action !== AuditLogEvent.MemberKick) return;
// Ensure the executor is cached.
const executor = await client.users.fetch(executorId);
// Ensure the kicked guild member is cached.
const kickedUser = await client.users.fetch(targetId);
// Now you can log the output!
console.log(`${kickedUser.tag} was kicked by ${executor.tag}.`);
});
```
If you want to check who banned a user, it's the same example as above except the `action` should be `AuditLogEvent.MemberBanAdd`. You can check the rest of the types over at the [discord-api-types documentation](https://discord-api-types.dev/api/discord-api-types-v10/enum/AuditLogEvent).

View File

@@ -0,0 +1,324 @@
---
title: Canvas
---
## Setting up @napi-rs/canvas
`@napi-rs/canvas` is an image manipulation tool that allows you to modify images with code. We'll explore how to use this module in a slash command to make a profile command.
<Callout>
This guide is last tested with `@napi-rs/canvas^0.1.25`, so make sure you have this or a similar version after
installation.
</Callout>
<Callout type="warn">
Be sure that you're familiar with things like [async/await](../additional-info/async-await) and [object
destructuring](../additional-info/es6-syntax#object-destructuring) before continuing, as we'll be making use of those
features in the coming sections.
</Callout>
## Package installation
Run the following command in your terminal:
```sh tab="npm"
npm install @napi-rs/canvas
```
```sh tab="yarn"
yarn add @napi-rs/canvas
```
```sh tab="pnpm"
pnpm add @napi-rs/canvas
```
```sh tab="bun"
bun add @napi-rs/canvas
```
## Getting started
Here is the base code you'll be using to get started:
```js lineNumbers title="index.js"
const { AttachmentBuilder, Client, Events, GatewayIntentBits } = require('discord.js');
const Canvas = require('@napi-rs/canvas');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, (readyClient) => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
client.on(Events.InteractionCreate, (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'profile') {
// ...
}
});
client.login('your-token-goes-here');
```
<Callout>
If you followed the [event handler section](../app-creation/handling-events) of this guide, consider putting this into
your interaction handler instead!
</Callout>
<Callout type="warn">
Remember to register the slash commands before continuing on with this section of the guide. You can view how to do
that [here](../interactions/slash-commands.md#registering-slash-commands).
</Callout>
### Basic image loading
The end goal will be to display the user's avatar and nickname.
After importing the `@napi-rs/canvas` module and initializing it, you should load the images. With `@napi-rs/canvas`, you have to specify where the image comes from first, naturally, and then specify how it gets loaded into the actual Canvas using `context`, which you will use to interact with Canvas.
<Callout>
`@napi-rs/canvas` works almost identical to HTML5 Canvas. You can read the HTML5 Canvas tutorials on
[w3Schools](https://www.w3schools.com/html/html5_canvas.asp) and
[MDN](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) for more information later!
</Callout>
```js lineNumbers=10 title="index.js"
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'profile') {
// [!code ++:4]
// Create a 700x250 pixel canvas and get its context
// The context will be used to modify the canvas
const canvas = Canvas.createCanvas(700, 250);
const context = canvas.getContext('2d');
// ...
}
});
```
Now, you need to load the image you want to use into Canvas.
We'll be using [this image](https://github.com/discordjs/guide/blob/main/guide/popular-topics/images/canvas.jpg) as the background in the welcome image, but you can use whatever you want. Be sure to download the file, name it `wallpaper.jpg`, and save it inside the same directory as your main bot file.
```js lineNumbers=10 title="index.js"
client.on(Events.InteractionCreate, async (interaction) => {
const canvas = Canvas.createCanvas(700, 250);
// [!code focus:10]
const context = canvas.getContext('2d');
// [!code ++:9]
const background = await Canvas.loadImage('./wallpaper.jpg');
// This uses the canvas dimensions to stretch the image onto the entire canvas
context.drawImage(background, 0, 0, canvas.width, canvas.height);
// Use the helpful Attachment class structure to process the file for you
const attachment = new AttachmentBuilder(await canvas.encode('png'), { name: 'profile-image.png' });
interaction.reply({ files: [attachment] });
});
```
![Basic canvas preview](./images/canvas-preview.png)
<Callout>
If you get an error such as `Error: ENOENT: no such file or directory`, then the file's provided path was incorrect.
</Callout>
### Manipulating images
Next, let's place a border around the image for the sake of demonstration purposes.
```js
client.on(Events.InteractionCreate, async (interaction) => {
// ... // [!code focus:9]
context.drawImage(background, 0, 0, canvas.width, canvas.height);
// [!code ++:5]
// Set the color of the stroke
context.strokeStyle = '#0099ff';
// Draw a rectangle with the dimensions of the entire canvas
context.strokeRect(0, 0, canvas.width, canvas.height);
// ...
});
```
![Image](./images/canvas-plain.png)
A bit plain, right? Fear not, for you have a bit more to do until you reach completion. Since this guide page's goal is focused more on actual code than design, let's place a basic square-shaped avatar for now on the left side of the image. In the interest of coverage, you will also make it a circle afterward.
```js
const { request } = require('undici'); // [!code focus] [!code ++]
client.on(Events.InteractionCreate, async (interaction) => {
// ... // [!code focus:13]
context.strokeRect(0, 0, canvas.width, canvas.height);
// [!code ++:9]
// Using undici to make HTTP requests for better performance
const { body } = await request(interaction.user.displayAvatarURL({ extension: 'jpg' }));
const avatar = await Canvas.loadImage(await body.arrayBuffer());
// If you don't care about the performance of HTTP requests, you can instead load the avatar using
// const avatar = await Canvas.loadImage(interaction.user.displayAvatarURL({ extension: 'jpg' }));
// Draw a shape onto the main canvas
context.drawImage(avatar, 25, 0, 200, canvas.height);
context.drawImage(background, 0, 0, canvas.width, canvas.height); // [!code --]
// ...
});
```
![Image](./images/canvas-stretched-avatar.png)
It works well, but the avatar image itself seems a bit stretched out. Let's remedy that.
```js
client.on(Events.InteractionCreate, async (interaction) => {
// ... // [!code focus:8]
const { body } = await request(interaction.user.displayAvatarURL({ extension: 'jpg' }));
const avatar = await Canvas.loadImage(await body.arrayBuffer());
context.drawImage(avatar, 25, 0, 200, canvas.height); // [!code --]
// [!code ++:2]
// Move the image downwards vertically and constrain its height to 200, so that it's square
context.drawImage(avatar, 25, 25, 200, 200);
// ...
});
```
![Image](./images/canvas-square-avatar.png)
The purpose of this small section is to demonstrate that working with Canvas is essentially a hit-and-miss workflow where you fiddle with properties until they work just right.
Since we covered how to load external images and fix dimensions, let's turn the avatar into a circle to improve the image's overall style.
```js
client.on(Events.InteractionCreate, async (interaction) => {
// [!code focus:15]
// ...
context.strokeRect(0, 0, canvas.width, canvas.height);
// [!code ++:11]
// Pick up the pen
context.beginPath();
// Start the arc to form a circle
context.arc(125, 125, 100, 0, Math.PI * 2, true);
// Put the pen down
context.closePath();
// Clip off the region you drew on
context.clip();
// ...
});
```
![Image](./images/canvas-circle-avatar.png)
<Callout>
You can read more about `context.arc()` on [w3schools](https://www.w3schools.com/tags/canvas_arc.asp) or
[MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc).
</Callout>
### Adding in text
Now, let's quickly go over adding text to your image. This will help make the purpose of this image apparent since currently, it's just an avatar floating on a starry background that comes out of nowhere.
```js
client.on(Events.InteractionCreate, async (interaction) => {
// ... // [!code focus:12]
context.strokeRect(0, 0, canvas.width, canvas.height);
// [!code ++:8]
// Select the font size and type from one of the natively available fonts
context.font = '60px sans-serif';
// Select the style that will be used to fill the text in
context.fillStyle = '#ffffff';
// Actually fill the text with a solid color
context.fillText(interaction.member.displayName, canvas.width / 2.5, canvas.height / 1.8);
// ...
});
```
![Image](./images/canvas-add-name.png)
<Callout type="error">
If you get an error like `Fontconfig error: Cannot load default config file`, it means you do not have any fonts
installed on your system. On Linux, you can run the following command to fix this: `sudo apt-get install fontconfig`.
This might also need to be installed if you see boxes where the text should be. As for Windows, you will need to find
a way to install fonts.
</Callout>
You may have noticed or considered that if a member's username is too long, then the output won't be quite nice. This is because the text overflows out of the canvas, and you don't have any measures in place for that. Let's take care of this issue!
```js
// [!code focus:16] [!code ++:16]
// Pass the entire Canvas object because you'll need access to its width and context
const applyText = (canvas, text) => {
const context = canvas.getContext('2d');
// Declare a base size of the font
let fontSize = 70;
do {
// Assign the font to the context and decrement it so it can be measured again
context.font = `${(fontSize -= 10)}px sans-serif`;
// Compare pixel width of the text to the canvas minus the approximate avatar size
} while (context.measureText(text).width > canvas.width - 300);
// Return the result to use in the actual canvas
return context.font;
};
client.on(Events.InteractionCreate, async (interaction) => {
// ... // [!code focus:8]
context.strokeRect(0, 0, canvas.width, canvas.height);
context.font = '60px sans-serif'; // [!code --]
context.font = applyText(canvas, interaction.member.displayName); // [!code ++]
context.fillStyle = '#ffffff';
context.fillText(interaction.member.displayName, canvas.width / 2.5, canvas.height / 1.8);
// ...
});
```
Before adjustment:
![Before adjustment](./images/canvas-before-text-wrap.png)
After adjustment:
![After adjustment](./images/canvas-after-text-wrap.png)
Let's move the welcome text inside the image itself instead of adding it outside as a nice finishing touch.
```js
client.on(Events.InteractionCreate, async (interaction) => {
// ... // [!code focus:15]
context.strokeRect(0, 0, canvas.width, canvas.height);
// Slightly smaller text placed above the member's display name // [!code ++:4]
context.font = '28px sans-serif';
context.fillStyle = '#ffffff';
context.fillText('Profile', canvas.width / 2.5, canvas.height / 3.5);
// Add an exclamation point here and below // [!code ++:2]
context.font = applyText(canvas, `${interaction.member.displayName}!`);
context.font = applyText(canvas, interaction.member.displayName); // [!code --]
context.fillStyle = '#ffffff';
context.fillText(`${interaction.member.displayName}!`, canvas.width / 2.5, canvas.height / 1.8); // [!code ++]
context.fillText(interaction.member.displayName, canvas.width / 2.5, canvas.height / 1.8); // [!code --]
// ...
});
```
![Final result](./images/canvas-final-result.png)
And that's it! We have covered the basics of image manipulation, text generation, and loading from a remote source.

View File

@@ -0,0 +1,191 @@
---
title: Collectors
---
## Message collectors
`Collector` 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.
### Basic message collector
For now, let's take the example that they have provided us:
```js
// `m` is a message object that will be passed through the filter function
const collectorFilter = (m) => m.content.includes('discord');
const collector = interaction.channel.createMessageCollector({ filter: collectorFilter, time: 15_000 });
collector.on('collect', (m) => {
console.log(`Collected ${m.content}`);
});
collector.on('end', (collected) => {
console.log(`Collected ${collected.size} items`);
});
```
You can provide a `filter` key to the object parameter of `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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#function_body) for the filter function and passes it to the options object using the [object property shorthand](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#property_definitions) notation.
If a message passes through the filter, it will trigger the `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 `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()`.
### Await 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:
```json
[
{
"question": "What color is the sky?",
"answers": ["blue"]
},
{
"question": "How many letters are there in the alphabet?",
"answers": ["26", "twenty-six", "twenty six", "twentysix"]
}
]
```
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.
```js
const quiz = require('./quiz.json');
// ...
const item = quiz[Math.floor(Math.random() * quiz.length)];
const collectorFilter = (response) => {
return item.answers.some((answer) => answer.toLowerCase() === response.content.toLowerCase());
};
interaction.reply({ content: item.question, withResponse: true }).then((response) => {
response.resource.message.channel
.awaitMessages({ filter: collectorFilter, max: 1, time: 30_000, errors: ['time'] })
.then((collected) => {
interaction.followUp(`${collected.first().author} got the correct answer!`);
})
.catch((collected) => {
interaction.followUp('Looks like nobody got the answer this time.');
});
});
```
<Callout>
If you don't understand how `.some()` works, you can read about it in more detail
[here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some).
</Callout>
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 options (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.
## Reaction collectors
### Basic 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 👍 emojiin 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.
```js
const collectorFilter = (reaction, user) => {
return reaction.emoji.name === '👍' && user.id === message.author.id;
};
const collector = message.createReactionCollector({ filter: collectorFilter, time: 15_000 });
collector.on('collect', (reaction, user) => {
console.log(`Collected ${reaction.emoji.name} from ${user.tag}`);
});
collector.on('end', (collected) => {
console.log(`Collected ${collected.size} items`);
});
```
### Await reactions
`Message#awaitReactions` works almost the same as a reaction collector, except it is Promise-based. The same differences apply as with channel collectors.
```js
const collectorFilter = (reaction, user) => {
return reaction.emoji.name === '👍' && user.id === message.author.id;
};
message
.awaitReactions({ filter: collectorFilter, max: 4, time: 60_000, errors: ['time'] })
.then((collected) => console.log(collected.size))
.catch((collected) => {
console.log(`After a minute, only ${collected.size} out of 4 reacted.`);
});
```
## Interaction 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.
### Basic 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.
```js
const { ComponentType } = require('discord.js');
const collector = message.createMessageComponentCollector({ componentType: ComponentType.Button, time: 15_000 });
collector.on('collect', (i) => {
if (i.user.id === interaction.user.id) {
i.reply(`${i.user.id} clicked on the ${i.customId} button.`);
} else {
i.reply({ content: `These buttons aren't for you!`, flags: MessageFlags.Ephemeral });
}
});
collector.on('end', (collected) => {
console.log(`Collected ${collected.size} interactions.`);
});
```
### Await 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.
```js
const { ComponentType } = require('discord.js');
const collectorFilter = (i) => {
i.deferUpdate();
return i.user.id === interaction.user.id;
};
message
.awaitMessageComponent({ filter: collectorFilter, componentType: ComponentType.StringSelect, time: 60_000 })
.then((interaction) => interaction.editReply(`You selected ${interaction.values.join(', ')}!`))
.catch((err) => console.log('No interactions were collected.'));
```
### Await 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:
```js
initialInteraction
.awaitModalSubmit({ time: 60_000, filter })
.then((interaction) => interaction.editReply('Thank you for your submission!'))
.catch((err) => console.log('No modal submit interaction was collected'));
```
For more information on working with modals, see the [modals section of this guide](../interactions/modals).

View File

@@ -0,0 +1,236 @@
---
title: Display Components
---
While you might be familiar with [embeds](./embeds) in Discord, there are more ways to style and format your apps messages using **display components**, a comprehensive set of layout and content elements.
To use the display components, you need to pass the `IsComponentsV2` message flag (in docs: `MessageFlags`) when sending a message. You only need to use this flag when sending a message using the display components system, not when deffering interaction responses.
<Callout type="warn">
Opting into using this system by passing the `IsComponentsV2` flag comes with a set of caveats:
- You **cannot** send `content`, `poll`, `embeds`, or `stickers`.
- You **cannot** opt out of using display components when editing a message
- You **can** opt into using display components when editing a message while explicitly setting `content`, `poll`, `embeds`, and `stickers` to null.
- Messages can have up to **40** total components (nested components count!)
- The amount of text across all text display components **cannot** exceed 4000 characters.
- All attached files have to explicitly be referenced in a component (refer to the [Thumbnail](#thumbnail), [Media Gallery](#media-gallery), and [File](#file) sections).
</Callout>
## The component `id`
All components can be passed an optional, unique, `id` field holding a 32-bit integer identifier to later identify them in interaction responses. Do not confuse this with the `custom_id` field for interactive components! You can find more information about this [in the discord api documentation](https://discord.com/developers/docs/components/reference#anatomy-of-a-component). Discord will automatically populate the `id` of components that don't have the `id` specified in the payload sequentially starting from `1`. The `id` value `0` is treated as empty. The order components are automatically filled in is an implementation detail and not officially document. If you want to work with the `id` (for example to find and replace the content of a specific component lateron), you should explicitly specify it.
In the following sections, we will explain all available display component types in detail and show you some examples on how you can use them.
## Text Display
Text Display components let you add markdown-formatted text to your message and directly replace the `content` field when opting to use display components. You can use the `TextDisplayBuilder` class to easily create a Text Display component.
<Callout type="error">
Sending user and role mentions in text display components **will notify users and roles**! You can and should control
mentions with the `allowedMentions` message option.
</Callout>
The example below shows how you can send a Text Display component in a channel.
```js
const { TextDisplayBuilder, MessageFlags } = require('discord.js');
const exampleTextDisplay = new TextDisplayBuilder().setContent(
'This text is inside a Text Display component! You can use **any __markdown__** available inside this component too.',
);
await channel.send({
components: [exampleTextDisplay],
flags: MessageFlags.IsComponentsV2,
});
```
![Text display component preview](./images/textdisplay-preview.png)
## Section
Sections represent text (one to three Text Display components) with an accessory. The accessory can either be an image (thumbnail) or button. If you do not want to send an accessory, use a [Text Display](#text-display) component instead. You can use the `SectionBuilder` class to easily create a Section component:
```js
const { SectionBuilder, ButtonStyle, MessageFlags } = require('discord.js');
const exampleSection = new SectionBuilder()
.addTextDisplayComponents(
(textDisplay) =>
textDisplay.setContent(
'This text is inside a Text Display component! You can use **any __markdown__** available inside this component too.',
),
(textDisplay) => textDisplay.setContent('Using a section, you may only use up to three Text Display components.'),
(textDisplay) => textDisplay.setContent('And you can place one button or one thumbnail component next to it!'),
)
.setButtonAccessory((button) =>
button.setCustomId('exampleButton').setLabel('Button inside a Section').setStyle(ButtonStyle.Primary),
);
await channel.send({
components: [exampleSection],
flags: MessageFlags.IsComponentsV2,
});
```
![Section component preview](./images/section-preview.png)
## Thumbnail
A Thumbnail is a display component that is visually similar to the `thumbnail` field inside an embed. Thumbnails are added as accessory inside a [Section](#section) component, support alt text for accessibility, and can be marked as a spoiler. You can use the `ThumbnailBuilder` class to easily create a Thumbnail component:
```js
const { AttachmentBuilder, SectionBuilder, MessageFlags } = require('discord.js');
const file = new AttachmentBuilder('../assets/image.png');
const exampleSection = new SectionBuilder()
.addTextDisplayComponents((textDisplay) =>
textDisplay.setContent(
'This text is inside a Text Display component! You can use **any __markdown__** available inside this component too.',
),
)
.setThumbnailAccessory(
(thumbnail) => thumbnail.setDescription('alt text displaying on the image').setURL('attachment://image.png'), // Supports arbitrary URLs such as 'https://i.imgur.com/AfFp7pu.png' as well.
);
await channel.send({
components: [exampleSection],
files: [file],
flags: MessageFlags.IsComponentsV2,
});
```
![Thumbnail component preview](./images/thumbnail-preview.png)
For more information about using attachments in components refer to the guide on [attaching images in embeds](./embeds#attaching-images).
## Media Gallery
A Media Gallery is a display component that can display a grid of up to 10 media attachments. Each media item can have an optional alt text (description) and can be marked as spoiler. You can use the `MediaGalleryBuilder` and `MediaGalleryItemBuilder` classes to easily create a Media Gallery component and its items:
```js
const { AttachmentBuilder, MediaGalleryBuilder, MessageFlags } = require('discord.js');
const file = new AttachmentBuilder('../assets/image.png');
const exampleGallery = new MediaGalleryBuilder().addItems(
(mediaGalleryItem) =>
mediaGalleryItem
.setDescription('alt text displaying on an image from the AttachmentBuilder')
.setURL('attachment://image.png'),
(mediaGalleryItem) =>
mediaGalleryItem
.setDescription('alt text displaying on an image from an external URL')
.setURL('https://i.imgur.com/AfFp7pu.png')
.setSpoiler(true), // Will display as a blurred image
);
await channel.send({
components: [exampleGallery],
files: [file],
flags: MessageFlags.IsComponentsV2,
});
```
![Media gallery component preview](./images/mediagallery-preview.png)
## File
A File is a display component that can display a single uploaded file within the body of the message. By using multiple File components, you can upload and display multiple files in a single message. File components cannot have alt texts (description), unlike a Thumbnail or Media Gallery component, but can be marked as a spoiler. You can use the `FileBuilder` class to easily create a File component:
```js
const { AttachmentBuilder, FileBuilder, MessageFlags } = require('discord.js');
const file = new AttachmentBuilder('../assets/guide.pdf');
const exampleFile = new FileBuilder().setURL('attachment://guide.pdf');
await channel.send({
components: [exampleFile],
files: [file],
flags: MessageFlags.IsComponentsV2,
});
```
![File component preview](./images/file-preview.png)
## Separator
A Separator is a layout component that adds vertical padding and optional visual division between components. You can select the amount of padding used for the Separator component (small or large) as well as whether a visual divider should be displayed (defaults to `true`). You can use the `SeparatorBuilder` class to easily create a Separator component.
<Callout type="warn">
When a Separator component is used without any non-Separator components in the message payload, the message will not
have any visible content.
</Callout>
The example below shows how you can send a Separator component in a channel, separating two Text Display components.
```js
const { TextDisplayBuilder, SeparatorBuilder, SeparatorSpacingSize, MessageFlags } = require('discord.js');
const exampleTextDisplay = new TextDisplayBuilder().setContent(
'This text is inside a Text Display component! You can use **any __markdown__** available inside this component too.',
);
const exampleSeparator = new SeparatorBuilder()
.setDivider(false) // No line displayed
.setSpacing(SeparatorSpacingSize.Large);
await channel.send({
components: [exampleTextDisplay, exampleSeparator, exampleTextDisplay],
flags: MessageFlags.IsComponentsV2,
});
```
![Separator component preview](./images/separator-preview.png)
## Container
A Container is a layout component which groups its child components inside a visually distinct rounded box with an optional accent color on the left, similar to the message embed look. Unlike embeds, not specifying a color will make the left side of the Container component match the background color. You can mark Container components as spoiler, which blurs all content inside the container. You can use the `ContainerBuilder` class to easily create a Container component.
The example below shows how to send a Container component in a channel. It contains:
- a Text Display component;
- an Action Row component with a User Select component;
- a Separator component;
- a Section component with two Text Display components and a Button component accessory.
```js
const { ContainerBuilder, UserSelectMenuBuilder, ButtonStyle, MessageFlags } = require('discord.js');
const exampleContainer = new ContainerBuilder()
.setAccentColor(0x0099ff)
.addTextDisplayComponents((textDisplay) =>
textDisplay.setContent(
'This text is inside a Text Display component! You can use **any __markdown__** available inside this component too.',
),
)
.addActionRowComponents((actionRow) =>
actionRow.setComponents(new UserSelectMenuBuilder().setCustomId('exampleSelect').setPlaceholder('Select users')),
)
.addSeparatorComponents((separator) => separator)
.addSectionComponents((section) =>
section
.addTextDisplayComponents(
(textDisplay) =>
textDisplay.setContent(
'This text is inside a Text Display component! You can use **any __markdown__** available inside this component too.',
),
(textDisplay) => textDisplay.setContent('And you can place one button or one thumbnail component next to it!'),
)
.setButtonAccessory((button) =>
button.setCustomId('exampleButton').setLabel('Button inside a Section').setStyle(ButtonStyle.Primary),
),
);
await channel.send({
components: [exampleContainer],
flags: MessageFlags.IsComponentsV2,
});
```
![Container component preview](./images/container-preview.png)

View File

@@ -0,0 +1,230 @@
---
title: Message Embeds
---
If you have been around on Discord for a bit, chances are you have seen these special messages, often sent by bots.
They can have a colored border, embedded images, text fields, and other fancy properties.
In the following section, we will explain how to compose an embed, send it, and what you need to be aware of while doing so.
## Embed preview
Here is an example of how an embed may look. We will go over embed construction in the next part of this guide.
## Using the embed constructor
discord.js features the `EmbedBuilder` utility class for easy construction and manipulation of embeds.
```js
// at the top of your file
const { EmbedBuilder } = require('discord.js');
// inside a command, event listener, etc.
const exampleEmbed = new EmbedBuilder()
.setColor(0x0099ff)
.setTitle('Some title')
.setURL('https://discord.js.org/')
.setAuthor({ name: 'Some name', iconURL: 'https://i.imgur.com/AfFp7pu.png', url: 'https://discord.js.org' })
.setDescription('Some description here')
.setThumbnail('https://i.imgur.com/AfFp7pu.png')
.addFields(
{ name: 'Regular field title', value: 'Some value here' },
{ name: '\u200B', value: '\u200B' },
{ name: 'Inline field title', value: 'Some value here', inline: true },
{ name: 'Inline field title', value: 'Some value here', inline: true },
)
.addFields({ name: 'Inline field title', value: 'Some value here', inline: true })
.setImage('https://i.imgur.com/AfFp7pu.png')
.setTimestamp()
.setFooter({ text: 'Some footer text here', iconURL: 'https://i.imgur.com/AfFp7pu.png' });
channel.send({ embeds: [exampleEmbed] });
```
<Callout>
You don't need to include all the elements showcased above. If you want a simpler embed, leave some out.
</Callout>
The `.setColor()` method accepts a `ColorResolvable`, e.g. an integer, HEX color string, an array of RGB values or specific color strings.
To add a blank field to the embed, you can use `.addFields({ name: '\u200b', value: '\u200b' })`.
The above example chains the manipulating methods to the newly created EmbedBuilder object.
If you want to modify the embed based on conditions, you will need to reference it as the constant `exampleEmbed` (for our example).
```js
const exampleEmbed = new EmbedBuilder().setTitle('Some title');
if (message.author.bot) {
exampleEmbed.setColor(0x7289da);
}
```
## Using an embed object
```js
const exampleEmbed = {
color: 0x0099ff,
title: 'Some title',
url: 'https://discord.js.org',
author: {
name: 'Some name',
icon_url: 'https://i.imgur.com/AfFp7pu.png',
url: 'https://discord.js.org',
},
description: 'Some description here',
thumbnail: {
url: 'https://i.imgur.com/AfFp7pu.png',
},
fields: [
{
name: 'Regular field title',
value: 'Some value here',
},
{
name: '\u200b',
value: '\u200b',
inline: false,
},
{
name: 'Inline field title',
value: 'Some value here',
inline: true,
},
{
name: 'Inline field title',
value: 'Some value here',
inline: true,
},
{
name: 'Inline field title',
value: 'Some value here',
inline: true,
},
],
image: {
url: 'https://i.imgur.com/AfFp7pu.png',
},
timestamp: new Date().toISOString(),
footer: {
text: 'Some footer text here',
icon_url: 'https://i.imgur.com/AfFp7pu.png',
},
};
channel.send({ embeds: [exampleEmbed] });
```
<Callout>
You don't need to include all the elements showcased above. If you want a simpler embed, leave some out.
</Callout>
If you want to modify the embed object based on conditions, you will need to reference it directly (as `exampleEmbed` for our example). You can then (re)assign the property values as you would with any other object.
```js
const exampleEmbed = { title: 'Some title' };
if (message.author.bot) {
exampleEmbed.color = 0x7289da;
}
```
## Attaching images
You can upload images with your embedded message and use them as source for embed fields that support image URLs by constructing a `AttachmentBuilder` from them to send as message option alongside the embed. The attachment parameter takes a BufferResolvable or Stream including the URL to an external image.
You can then reference and use the images inside the embed itself with `attachment://fileName.extension`.
<Callout>
If you plan to attach the same image repeatedly, **consider hosting it online and providing the URL** in the
respective embed field instead. This also makes your bot respond faster since it doesn't need to upload the image with
every response depending on it.
</Callout>
### Using the EmbedBuilder
```js
const { AttachmentBuilder, EmbedBuilder } = require('discord.js');
// ...
const file = new AttachmentBuilder('../assets/discordjs.png');
const exampleEmbed = new EmbedBuilder().setTitle('Some title').setImage('attachment://discordjs.png');
channel.send({ embeds: [exampleEmbed], files: [file] });
```
### Using an embed object
```js
const { AttachmentBuilder } = require('discord.js');
// ...
const file = new AttachmentBuilder('../assets/discordjs.png');
const exampleEmbed = {
title: 'Some title',
image: {
url: 'attachment://discordjs.png',
},
};
channel.send({ embeds: [exampleEmbed], files: [file] });
```
<Callout type="warn">
If the images don't display inside the embed but outside of it, double-check your syntax to make sure it's as shown
above.
</Callout>
## Resending and editing
We will now explain how to edit embedded message content and resend a received embed.
### Resending a received embed
To forward a received embed you retrieve it from the messages embed array (`message.embeds`) and pass it to the EmbedBuilder, then it can be edited before sending it again.
<Callout>
We create a new Embed from `EmbedBuilder` here since embeds are immutable (their values cannot be changed directly).
</Callout>
```js
const receivedEmbed = message.embeds[0];
const exampleEmbed = EmbedBuilder.from(receivedEmbed).setTitle('New title');
channel.send({ embeds: [exampleEmbed] });
```
### Editing the embedded message content
To edit the content of an embed you need to pass a new EmbedBuilder structure or embed object to the messages `.edit()` method.
```js
const exampleEmbed = new EmbedBuilder().setTitle('Some title').setDescription('Description after the edit');
message.edit({ embeds: [exampleEmbed] });
```
If you want to build the new embed data on a previously sent embed template, make sure to read the caveats in the previous section.
## Notes
- To display fields side-by-side, you need at least two consecutive fields set to `inline`
- The timestamp will automatically adjust the timezone depending on the user's device
- Mentions of any kind in embeds will only render correctly within embed descriptions and field values
- Mentions in embeds will not trigger a notification
- Embeds allow masked links (e.g. `[Guide](https://discordjs.guide/ 'optional hovertext')`), but only in description and field values
- Discord may strip characters from message content. See [the documentation](https://discord.com/developers/docs/resources/message#create-message) for more information
## Embed limits
There are a few limits to be aware of while planning your embeds due to the API's limitations. Here is a quick reference you can come back to:
- Embed titles are limited to **256** characters
- Embed descriptions are limited to **4096** characters
- There can be up to **25** fields
- A field's name is limited to **256** characters and its value to 1024 characters
- The footer text is limited to **2048** characters
- The author name is limited to **256** characters
- The sum of all characters from all embed structures in a message must not exceed **6000** characters
- **10** embeds can be sent per message
Source: [Discord API documentation](https://discord.com/developers/docs/resources/message#embed-object-embed-limits)

View File

@@ -0,0 +1,234 @@
---
title: Common Errors
---
There is no doubt that you have encountered errors while making bots. While errors are instrumental at warning you of what is going wrong, many people are stumped by them and how to track them down and fix them, but don't worry, we have you covered. This section will be all about diagnosing errors, identifying where they are coming from, and fixing them.
## Types of Errors
### API Errors
API Errors or DiscordAPIErrors are thrown by the Discord API when an invalid request carries out. API Errors can be mostly diagnosed using the message that is given. You can further examine errors by inspecting the HTTP method and path used. We will explore tracking these errors down in the next section.
Example: `DiscordAPIError: Cannot send an empty message`
### discord.js errors
discord.js errors are thrown by the library itself. They can usually be easily tracked down using the stack trace and error message.
Example: `The messages must be an Array, Collection, or number.`
### JavaScript errors
JavaScript errors are thrown by node itself or by discord.js. These errors can easily be fixed by looking at the type of error and the stack trace. You can find a full list of types [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) and a list of common JavaScript errors [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors).
Examples:
- `ReferenceError: "x" is not defined`
- `Cannot read properties of null (reading 'something')`
### WebSocket and Network errors
WebSocket and Network errors are common system errors thrown by Node in response to something wrong with the WebSocket connection. Unfortunately, these errors do not have a concrete solution and can be (usually) fixed by getting a better, more stable, and more robust connection. discord.js will automatically try to reconnect to the WebSocket if an error occurs.
In version 12, WebSocket errors are handled internally, meaning your process should never crash from them. If you want to log these errors, should they happen, you can listen to the `shardError` event as shown below.
```js
client.on(Events.ShardError, (error) => {
console.error('A websocket connection encountered an error:', error);
});
```
The commonly thrown codes for these errors are:
- `ECONNRESET` - The connection was forcibly closed by a peer, thrown by the loss of connection to a WebSocket due to timeout or reboot.
- `ETIMEDOUT` - A connect or send request failed because the receiving party did not respond after some time.
- `EPIPE` - The remote side of the stream being written to has been closed.
- `ENOTFOUND` - The domain being accessed is unavailable, usually caused by a lack of internet, can be thrown by the WebSocket and HTTP API.
- `ECONNREFUSED` - The target machine refused the connection; check your ports and firewall.
## How to diagnose API errors
API Errors can be tracked down by adding an event listener for unhandled rejections and looking at the extra info.
This can be done by adding this to your main file.
```js
process.on('unhandledRejection', (error) => {
console.error('Unhandled promise rejection:', error);
});
```
The next time you get the error it will show info along the bottom of the error which will look something like this for example:
```json
name: 'DiscordAPIError',
message: 'Invalid Form Body\nmessage_id: Value "[object Object]" is not snowflake.',
path: '/api/v10/channels/638200642359525387/messages/[object%20Object]',
code: 50035,
method: 'GET'
```
All of this information can help you track down what caused the error and how to fix it. In this section, we will run through what each property means.
### Message
The most important part of the error is the message. It tells you what went wrong, which can help you track down where it originates.
You can find a full list of messages [here](https://discord.com/developers/docs/topics/opcodes-and-status-codes#json) in the Discord API documentation.
### Path
Another helpful piece of information is the path, which tells you what API endpoint the error occurred on. We cannot possibly cover all endpoints, but they are usually very descriptive.
In the above example, the path tells you that the action was executed in the `/channels/` scope. The number you see next is the channel's id. Next, you can spot the `message/` scope. The number is again the object's id. Combined with the method `GET` you can conclude, that the bot tried to fetch the message with the id `[object Object]` from the channel with the id `638200642359525387`.
As the error message tells you `[object Object]` is not a valid id, so you now know where to look for an error! Find out where you pass an object as an id when trying to fetch a message and fix your code in that location.
### Code
The code is another partial representation of the message, in this case, `Invalid Form Body`. You can find a full list of codes [here](https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes)
The code is also handy if you want only to handle a specific error. Say you're trying to delete a message which may or may not be there, and wanted to ignore unknown message errors. This can be done by checking the code, either manually or using discord.js constants.
```js
message.delete().catch((error) => {
// Only log the error if it is not an Unknown Message error
if (error.code !== 10_008) {
console.error('Failed to delete the message:', error);
}
});
```
Or using Constants:
```js
const { RESTJSONErrorCodes } = require('discord.js');
message.delete().catch((error) => {
if (error.code !== RESTJSONErrorCodes.UnknownMessage) {
console.error('Failed to delete the message:', error);
}
});
```
You can find a list of constants [here](https://discord-api-types.dev/api/discord-api-types-v10/enum/RESTJSONErrorCodes).
### Method
The final piece of information can tell you a lot about what you tried to do to the path. There are a set of predefined keywords that describe our actions on the path.
```
GET - Used to retrieve a piece of data
POST - Used to send a piece of data
PATCH - Used to modify a piece of data
PUT - Used to replace a piece of data completely
DELETE - Used to delete a piece of data completely
```
In this particular example, you can see you are trying to access a piece of data, specifically, a message.
## Common discord.js and API errors
### An invalid token was provided.
This is a prevalent error; it originates from a wrong token being passed into `client.login()`. The most common causes of this error are:
- Not importing the config or env file correctly
- Copying the client secret instead of the bot token (the token is alphanumerical and three parts delimited by a period while the client secret is significantly smaller and one part only)
- Not updating the token after resetting it
<Callout>
**Before** the release of **version 12**, there used to be an issue where the token was not prefixed correctly, which
resulted in valid tokens being marked as invalid. If you have verified that all of the above is not the case, make
sure you have updated discord.js to the current stable version.
</Callout>
### Request to use token, but token was unavailable to the client.
This error originates from the client attempting to execute an action that requires the token but the token not being available. This is most commonly caused by destroying the client and then trying to perform an action.
This error is also caused by attempting to use a client that has not logged in. Both of the examples below will throw errors.
```js
const { Client, GatewayIntentBits } = require('discord.js');
// Should not be here!
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
module.exports = (interaction) => {
const id = interaction.options.getString('id');
// Should be `interaction.client` instead!
client.users.fetch(id).then((user) => {
interaction.reply(`Your requested user: ${user.tag}`);
});
};
```
```js
const { Client, Events, GatewayIntentBits } = require('discord.js');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on(Events.InteractionCreate, someHandlerFunction);
client.login('your-token-goes-here');
// client will not be logged in yet!
client.users.fetch('myId').then(someInitFunction);
```
### EmbedBuilder field values may not be empty.
This error originates from calling `EmbedBuilder#addFields()` with a field object's `name` property as an empty string. If you would like the title to be empty for a reason, you should use a zero width space, which can be input as `\u200b`.
In conjunction with the previous error, this error results from calling `EmbedBuilder#addFields()` with a field object's `value` property as an empty string. You can use a zero-width space if you would like this blank.
### The messages must be an Array, Collection, or number.
This error originates from an invalid call to `bulkDelete()`. Make sure you are inputting a valid Array or Collection of messages or a valid number.
### Members didn't arrive in time.
This error happens when fetching multiple members via `GuildMemberManager#fetch()` and:
- The `GuildMembers` intent is not specified or enabled in the dev dashboard
- The internet connection is somewhat bad
- The amount of members fetched is large (about 50 thousand and upwards)
You can specify the time to wait for with the `time` option in the `.fetch()` call. Another solution could be to move your bot to a faster infrastructure, if available.
### MaxListenersExceededWarning: Possible EventEmitter memory leak detected...
This error is caused by spawning a large number of event listeners, usually for the client. The most common cause of this is nesting your event listeners instead of separating them. The way to fix this error is to make sure you do not nest your listeners; it is **not** to use `emitter.setMaxListeners()` as the error suggests.
You can debug these messages in different ways:
- Through the [CLI](https://nodejs.org/api/cli.html#cli_trace_warnings): `node --trace-warnings index.js`
- Through the [`process#warning` event](https://nodejs.org/api/process.html#process_event_warning): `process.on('warning', console.warn);`
### Cannot send messages to this user.
This error throws when the bot attempts to send a DM message to a user but cannot do so. A variety of reasons causes this:
- The bot and the user do not share a guild (often, people attempt to DM the user after kicking or banning them).
- The bot tries to DM another bot.
- The user has blocked the bot.
- The user has disabled DMs in the privacy settings.
In the case of the last two reasons, the error is not preventable, as the Discord API does not provide a way to check if you can send a user a DM until you attempt to send one. The best way to handle this error is to add a `.catch()` where you try to DM the user and either ignore the rejected Promise or do what you want because of it.
## Common miscellaneous errors
### code ENOENT... syscall spawn git.
This error is commonly thrown by your system due to it not finding `git`. You need to install `git` or update your path if `git` is already installed. Here are the download links for it:
- Ubuntu/Debian: `sudo apt-get install git`
- Windows: [git-scm](https://git-scm.com/download/win)
### code ELIFECYCLE
This error is commonly thrown by your system in response to the process unexpectedly closing. Cleaning the npm cache and deleting node_modules can usually fix it. The instructions for doing that are as such:
- Clean npm cache with `npm cache clean --force`
- delete `node_modules`
- delete `package-lock.json` (make sure you have a `package.json`!)
- run `npm install` to reinstall packages from `package.json`

View File

@@ -0,0 +1,399 @@
---
title: FAQ
---
# Frequently asked Questions
## Legend
- `client` is a placeholder for the `Client` object, such as `const client = new Client({ intents: [GatewayIntentBits.Guilds] });`.
- `interaction` is a placeholder for the `BaseInteraction` object, such as `client.on(Events.InteractionCreate, interaction => { ... });`.
- `guild` is a placeholder for the `Guild` object, such as `interaction.guild` or `client.guilds.cache.get('id')`.
- `voiceChannel` is a placeholder for the `VoiceChannel` object, such as `interaction.member.voice.channel`
For a more detailed explanation of the notations commonly used in this guide, the docs, and the support server, see [here](../additional-info/notation).
## Administrative
### How do I ban a user?
```js
const user = interaction.options.getUser('target');
guild.members.ban(user);
```
### How do I unban a user?
```js
const user = interaction.options.getUser('target');
guild.members.unban(user);
```
<Callout>
Discord **validates** and **resolves** user ids for users not on the server in user slash command options. To retrieve
and use the full structure from the resulting interaction, you can use the `CommandInteractionOptionResolver#getUser`
method.
</Callout>
### How do I kick a guild member?
```js
const member = interaction.options.getMember('target');
member.kick();
```
### How do I timeout a guild member?
```js
const member = interaction.options.getMember('target');
member.timeout(60_000); // Timeout for one minute
```
<Callout>
Timeout durations are measured by the **millisecond**. The maximum timeout duration you can set is 28 days. To remove
a timeout set on a member, pass `null` instead of a timeout duration.
</Callout>
### How do I add a role to a guild member?
```js
const role = interaction.options.getRole('role');
const member = interaction.options.getMember('target');
member.roles.add(role);
```
### How do I check if a guild member has a specific role?
```js
const member = interaction.options.getMember('target');
if (member.roles.cache.some((role) => role.name === 'role name')) {
// ...
}
```
### How do I limit a command to a single user?
```js
if (interaction.user.id === 'id') {
// ...
}
```
## Bot Configuration and Utility
### How do I set my bot's username?
```js
client.user.setUsername('username');
```
### How do I set my bot's avatar?
```js
client.user.setAvatar('URL or path');
```
### How do I set my playing status?
```js
client.user.setActivity('activity');
```
### How do I set my status to "Watching/Listening to/Competing in ..."?
```js
const { ActivityType } = require('discord.js');
client.user.setActivity('activity', { type: ActivityType.Watching });
client.user.setActivity('activity', { type: ActivityType.Listening });
client.user.setActivity('activity', { type: ActivityType.Competing });
```
<Callout>
If you would like to set your activity upon startup, you can use the `ClientOptions` object to set the appropriate
`Presence` data.
</Callout>
### How do I make my bot display online/idle/dnd/invisible?
```js
const { PresenceUpdateStatus } = require('discord.js');
client.user.setStatus(PresenceUpdateStatus.Online);
client.user.setStatus(PresenceUpdateStatus.Idle);
client.user.setStatus(PresenceUpdateStatus.DoNotDisturb);
client.user.setStatus(PresenceUpdateStatus.Invisible);
```
### How do I set both status and activity in one go?
```js
const { PresenceUpdateStatus } = require('discord.js');
client.user.setPresence({ activities: [{ name: 'activity' }], status: PresenceUpdateStatus.Idle });
```
## Miscellaneous
### How do I send a message to a specific channel?
```js
const channel = client.channels.cache.get('id');
channel.send('content');
```
### How do I create a post in a forum channel?
<Callout>Currently, the only way to get tag ids is programmatically through `ForumChannel#availableTags`.</Callout>
```js
const channel = client.channels.cache.get('id');
channel.threads.create({
name: 'Post name',
message: { content: 'Message content' },
appliedTags: ['tagID', 'anotherTagID'],
});
```
### How do I DM a specific user?
```js
client.users.send('id', 'content');
```
<Callout>If you want to DM the user who sent the interaction, you can use `interaction.user.send()`.</Callout>
### How do I mention a specific user in a message?
```js
const user = interaction.options.getUser('target');
await interaction.reply(`Hi, ${user}.`);
await interaction.followUp(`Hi, <@${user.id}>.`);
```
<Callout>
Mentions in embeds may resolve correctly in embed titles, descriptions and field values but will never notify the
user. Other areas do not support mentions at all.
</Callout>
### How do I control which users and/or roles are mentioned in a message?
Controlling which mentions will send a ping is done via the `allowedMentions` option, which replaces `disableMentions`.
This can be set as a default in `ClientOptions`, and controlled per-message sent by your bot.
```js
new Client({ allowedMentions: { parse: ['users', 'roles'] } });
```
Even more control can be achieved by listing specific `users` or `roles` to be mentioned by ID, e.g.:
```js
channel.send({
content: '<@123456789012345678> <@987654321098765432> <@&102938475665748392>',
allowedMentions: { users: ['123456789012345678'], roles: ['102938475665748392'] },
});
```
### How do I prompt the user for additional input?
```js
interaction.reply('Please enter more input.').then(() => {
const collectorFilter = (m) => interaction.user.id === m.author.id;
interaction.channel
.awaitMessages({ filter: collectorFilter, time: 60_000, max: 1, errors: ['time'] })
.then((messages) => {
interaction.followUp(`You've entered: ${messages.first().content}`);
})
.catch(() => {
interaction.followUp('You did not enter any input!');
});
});
```
<Callout>
If you want to learn more about this syntax or other types of collectors, check out [this dedicated guide page for
collectors](./collectors)!
</Callout>
### How do I block a user from using my bot?
```js
const blockedUsers = ['id1', 'id2'];
client.on(Events.InteractionCreate, (interaction) => {
if (blockedUsers.includes(interaction.user.id)) return;
});
```
<Callout>
You do not need to have a constant local variable like `blockedUsers` above. If you have a database system that you use to store IDs of blocked users, you can query the database instead:
```js
client.on(Events.InteractionCreate, async (interaction) => {
const blockedUsers = await database.query('SELECT user_id FROM blocked_users;');
if (blockedUsers.includes(interaction.user.id)) return;
});
```
Note that this is just a showcase of how you could do such a check.
</Callout>
### How do I react to the message my bot sent?
```js
interaction.channel.send('My message to react to.').then((sentMessage) => {
// Unicode emoji
sentMessage.react('👍');
// Custom emoji
sentMessage.react('123456789012345678');
sentMessage.react('<emoji:123456789012345678>');
sentMessage.react('<a:emoji:123456789012345678>');
sentMessage.react('emoji:123456789012345678');
sentMessage.react('a:emoji:123456789012345678');
});
```
<Callout>
If you want to learn more about reactions, check out [this dedicated guide on reactions](./reactions)!
</Callout>
### How do I restart my bot with a command?
```js
process.exit();
```
<Callout type="error">
`process.exit()` will only kill your Node process, but when using [PM2](http://pm2.keymetrics.io/), it will restart
the process whenever it gets killed. You can read our guide on PM2 [here](../improving-dev-environment/pm2).
</Callout>
### What is the difference between a User and a GuildMember?
A User represents a global Discord user, and a GuildMember represents a Discord user on a specific server. That means only GuildMembers can have permissions, roles, and nicknames, for example, because all of these things are server-bound information that could be different on each server that the user is in.
### How do I find all online members of a guild?
```js
// First use guild.members.fetch to make sure all members are cached
guild.members.fetch({ withPresences: true }).then((fetchedMembers) => {
const totalOnline = fetchedMembers.filter((member) => member.presence?.status === PresenceUpdateStatus.Online);
// Now you have a collection with all online member objects in the totalOnline variable
console.log(`There are currently ${totalOnline.size} members online in this guild!`);
});
```
<Callout type="warn">
This only works correctly if you have the `GuildPresences` intent enabled for your application and client. If you want
to learn more about intents, check out [this dedicated guide on intents](./intents)!
</Callout>
### How do I check which role was added/removed and for which member?
```js
// Start by declaring a guildMemberUpdate listener
// This code should be placed outside of any other listener callbacks to prevent listener nesting
client.on(Events.GuildMemberUpdate, (oldMember, newMember) => {
// If the role(s) are present on the old member object but no longer on the new one (i.e role(s) were removed)
const removedRoles = oldMember.roles.cache.filter((role) => !newMember.roles.cache.has(role.id));
if (removedRoles.size > 0) {
console.log(`The roles ${removedRoles.map((r) => r.name)} were removed from ${oldMember.displayName}.`);
}
// If the role(s) are present on the new member object but are not on the old one (i.e role(s) were added)
const addedRoles = newMember.roles.cache.filter((role) => !oldMember.roles.cache.has(role.id));
if (addedRoles.size > 0) {
console.log(`The roles ${addedRoles.map((r) => r.name)} were added to ${oldMember.displayName}.`);
}
});
```
### How do I check the bot's ping?
There are two common measurements for bot pings. The first, **websocket heartbeat**, is the average interval of a regularly sent signal indicating the healthy operation of the websocket connection the library receives events over:
```js
interaction.reply(`Websocket heartbeat: ${client.ws.ping}ms.`);
```
<Callout>
If you're using [sharding](../sharding/), a specific shard's heartbeat can be found on the WebSocketShard instance,
accessible at `client.ws.shards.get(id).ping`.
</Callout>
The second, **Roundtrip Latency**, describes the amount of time a full API roundtrip (from the creation of the command message to the creation of the response message) takes. You then edit the response to the respective value to avoid needing to send yet another message:
```js
const sent = await interaction.reply({ content: 'Pinging...', withResponse: true });
interaction.editReply(`Roundtrip latency: ${sent.resource.message.createdTimestamp - interaction.createdTimestamp}ms`);
```
### Why do some emojis behave weirdly?
If you've tried using [the usual method of retrieving unicode emojis](./reactions#unicode-emojis), you may have noticed that some characters don't provide the expected results. Here's a short snippet that'll help with that issue. You can toss this into a file of its own and use it anywhere you need! Alternatively feel free to simply copy-paste the characters from below:
```js
// emojiCharacters.js
module.exports = {
a: '🇦',
b: '🇧',
c: '🇨',
d: '🇩',
e: '🇪',
f: '🇫',
g: '🇬',
h: '🇭',
i: '🇮',
j: '🇯',
k: '🇰',
l: '🇱',
m: '🇲',
n: '🇳',
o: '🇴',
p: '🇵',
q: '🇶',
r: '🇷',
s: '🇸',
t: '🇹',
u: '🇺',
v: '🇻',
w: '🇼',
x: '🇽',
y: '🇾',
z: '🇿',
0: '0⃣',
1: '1⃣',
2: '2⃣',
3: '3⃣',
4: '4⃣',
5: '5⃣',
6: '6⃣',
7: '7⃣',
8: '8⃣',
9: '9⃣',
10: '🔟',
'#': '#️⃣',
'*': '*️⃣',
'!': '❗',
'?': '❓',
};
```
```js
// index.js
const emojiCharacters = require('./emojiCharacters.js');
console.log(emojiCharacters.a); // 🇦
console.log(emojiCharacters[10]); // 🔟
console.log(emojiCharacters['!']); // ❗
```
<Callout>
On Windows, you may be able to use the <kbd>Win</kbd> <kbd>.</kbd> keyboard shortcut to open up an emoji picker that can be used for quick, easy access to all the Unicode emojis available to you. Some of the emojis listed above may not be represented there, though (e.g., the 0-9 emojis).
You can also use the <kbd>⌃</kbd> <kbd>⌘</kbd> <kbd>Space</kbd> keyboard shortcut to perform the same behavior on macOS.
</Callout>

View File

@@ -0,0 +1,73 @@
---
title: Formatters
---
discord.js provides the `formatters` package which contains a variety of utilities you can use when writing your Discord bot.
## Basic Markdown
These functions format strings into all the different Markdown styles supported by Discord.
```js
const { blockQuote, bold, italic, quote, spoiler, strikethrough, underline, subtext } = require('discord.js');
const string = 'Hello!';
const boldString = bold(string);
const italicString = italic(string);
const strikethroughString = strikethrough(string);
const underlineString = underline(string);
const spoilerString = spoiler(string);
const quoteString = quote(string);
const blockquoteString = blockQuote(string);
const subtextString = subtext(string);
```
## Links
There are also two functions to format hyperlinks. `hyperlink()` will format the URL into a masked markdown link, and `hideLinkEmbed()` will wrap the URL in `<>`, preventing it from embedding.
```js
const { hyperlink, hideLinkEmbed } = require('discord.js');
const url = 'https://discord.js.org/';
const link = hyperlink('discord.js', url);
const hiddenEmbed = hideLinkEmbed(url);
```
## Code blocks
You can use `inlineCode()` and `codeBlock()` to turn a string into an inline code block or a regular code block with or without syntax highlighting.
```js
const { inlineCode, codeBlock } = require('discord.js');
const jsString = 'const value = true;';
const inline = inlineCode(jsString);
const codeblock = codeBlock(jsString);
const highlighted = codeBlock('js', jsString);
```
## Timestamps
With `time()`, you can format Unix timestamps and dates into a Discord time string.
```js
const { time, TimestampStyles } = require('discord.js');
const date = new Date();
const timeString = time(date);
const relative = time(date, TimestampStyles.RelativeTime);
```
## Mentions
`userMention()`, `channelMention()`, and `roleMention()` all exist to format Snowflakes into mentions.
```js
const { channelMention, roleMention, userMention } = require('discord.js');
const id = '123456789012345678';
const channel = channelMention(id);
const role = roleMention(id);
const user = userMention(id);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

View File

@@ -0,0 +1,80 @@
---
title: Gateway Intents
---
Gateway Intents were introduced by Discord so bot developers can choose which events their bot receives based on which data it needs to function. Intents are named groups of pre-defined WebSocket events, which the discord.js client will receive. If you omit `DirectMessageTyping`, for example, you will no longer receive typing events from direct messages. If you do not specify intents, discord.js will throw an error.
Rather than blindly enabling all intents, consider what information you actually need. Reducing the number of unnecessary events your bot receives improves performance and reduces bandwidth and memory usage.
## Privileged Intents
Discord defines some intents as "privileged" due to the data's sensitive nature. At the time of writing this article, privileged intents are `GuildPresences`, `MessageContent` and `GuildMembers`. If your bot is not verified and in less than 100 guilds, you can enable privileged gateway intents in the [Discord Developer Portal](https://discord.com/developers/applications) under "Privileged Gateway Intents" in the "Bot" section. If your bot is already verified or is about to [require verification](https://support-dev.discord.com/hc/en-us/articles/23926564536471), you need to request privileged intents. You can do this in your verification application or by reaching out to Discord's [support team](https://dis.gd/contact), including why you require access to each privileged intent.
Before storming off and doing so, you should stop and carefully think about if you need these events. Discord made them opt-in so users across the platform can enjoy a higher level of [privacy](https://en.wikipedia.org/wiki/Privacy_by_design). Presences can expose quite a bit of personal information, including the games being played and overall online time. You might find that it isn't necessary for your bot to have this level of information about all guild members at all times, considering you still get the command author as GuildMember from the command execution message and can fetch other targets separately.
### Error: Disallowed Intents
Should you receive an error prefixed with `[DisallowedIntents]`, please review your developer dashboard settings for all privileged intents you use. Check on the [Discord API documentation](https://discord.com/developers/docs/topics/gateway#privileged-intents) for up to date information.
## Enabling Intents
To specify which events you want your bot to receive, first think about which events your bot needs to operate. Then select the required intents and add them to your client constructor, as shown below.
You can find the list of all current gateway intents and the events belonging to each on the [Discord API documentation](https://discord.com/developers/docs/topics/gateway#list-of-intents) and the enum values used in discord.js on the [Discord API types documentation](https://discord-api-types.dev/api/discord-api-types-v10/enum/GatewayIntentBits).
- If you need your bot to receive messages (`MESSAGE_CREATE` - `"messageCreate"` in discord.js), you need the `Guilds` and `GuildMessages` intent, plus the `MessageContent` privileged intent to receive the `content`, `attachments`, `embeds` and `components` fields of the message.
- If you want your bot to post welcome messages for new members (`GUILD_MEMBER_ADD` - `"guildMemberAdd"` in discord.js), you need the `GuildMembers` privileged intent, and so on.
```js
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMembers,
],
});
```
<Callout>
Note that discord.js relies heavily on caching to provide its functionality - this means an internal reliance on
certain events to ensure the caches are populated and up to date.
</Callout>
Some methods that seem unrelated might stop working if certain events do not arrive. For example:
- The `Guilds` intent populates and maintains the `guilds`, `channels` and `guild.roles` caches, plus thread-related events. \
If this intent is not enabled, data for interactions and messages will include only the guild and channel id, and will not resolve to the full class.
- The `GuildMembers` intent keeps cached guild members up to date, including changes to their roles and permissions, nickname etc. \
Note that you still receive full member data with interactions and messages without this intent enabled.
Please make sure to provide the list of gateway intents and partials you use in your Client constructor when asking for support on our [Discord server](https://discord.gg/djs) or [GitHub repository](https://github.com/discordjs/discord.js).
## The Intents Bitfield
discord.js provides the utility structure `IntentsBitField` to simplify the modification of intents bitfields.
You can use the `.add()` and `.remove()` methods to add or remove flags (Intents string literals representing a certain bit) and modify the bitfield. You can provide single flags as well as an array or bitfield. To use a set of intents as a template you can pass it to the constructor. Note that the empty constructor `new IntentsBitField()` creates an empty Intents instance, representing no intents or the bitfield `0`:
```js
const { Client, IntentsBitField } = require('discord.js');
const myIntents = new IntentsBitField();
myIntents.add(IntentsBitField.Flags.GuildPresences, IntentsBitField.Flags.GuildMembers);
const client = new Client({ intents: myIntents });
// other examples:
const otherIntents = new IntentsBitField([IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages]);
otherIntents.remove([IntentsBitField.Flags.DirectMessages]);
```
If you want to view the built flags you can utilize the `.toArray()`, `.serialize()` methods. The first returns an array of flags represented in this bitfield, the second an object mapping all possible flag values to a boolean, based on their representation in this bitfield.
## More on Bitfields
Discord Intents and Permissions are stored in a 53-bit integer and calculated using bitwise operations. If you want to dive deeper into what's happening behind the curtains, check the [Wikipedia](https://en.wikipedia.org/wiki/Bit_field) and [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#binary_bitwise_operators) articles on the topic.
In discord.js, Permissions and Intents bitfields are represented as either the decimal value of said bit field or its referenced flags. Every position in a permissions bitfield represents one of these flags and its state (either referenced `1` or not referenced `0`).

View File

@@ -0,0 +1,4 @@
{
"defaultOpen": true,
"pages": ["!faq", "...", "!display-components"]
}

View File

@@ -0,0 +1,74 @@
---
title: Partials
---
Partial Structures were introduced to the library in version 12 and are optionally received whenever there is insufficient data to emit the client event with a fully intact discord.js structure. They are (as the name suggests) incomplete, and you cannot expect them to have any information besides their ID. All other properties and methods on this object should be considered invalid and defunct. Before this feature, discord.js client events would not emit if one of the necessary structures could not be built with sufficient data to guarantee a fully functional structure. If you do not opt into partials, this is still the case.
One example leveraging partials is the handling of reactions on uncached messages, which is explained on [this page](./reactions#listening-for-reactions-on-old-messages).
Prior you had to either handle the undocumented `raw` event or fetch the respective messages on startup. The first approach was prone to errors and unexpected internal behavior. The second was not fully fail-proof either, as the messages could still be uncached if cache size was exceeded in busy channels.
## Enabling Partials
As we said earlier, partials do not have all the information necessary to make them fully functional discord.js structures, so it would not be a good idea to enable the functionality by default. Users should know how to handle them before opting into this feature.
You choose which structures you want to emit as partials as client options when instantiating your bot client. Available structures are: `User`, `Channel` (only DM channels can be uncached, server channels will always be available), `GuildMember`, `Message`, `Reaction`, `GuildScheduledEvent` and `ThreadMember`.
```js
const { Client, Partials } = require('discord.js');
const client = new Client({ partials: [Partials.Message, Partials.Channel, Partials.Reaction] });
```
<Callout type="warn">
Make sure you enable all partials you need for your use case! If you miss one, the event does not get emitted.
</Callout>
<Callout type="warn">
Partial structures are enabled **globally**. You cannot make them work for only a specific event or cache. You very
**likely need to adapt** other parts of your code that are accessing data from the relevant caches. All caches holding
the respective structure type might return partials as well!
</Callout>
## Handling Partial data
All structures you can choose to use partials for have a new property, fittingly called `.partial`, indicating if it is a fully functional or partial instance of its class. The value is `true` if partial, `false` if fully functional.
<Callout type="error">
Partial data is only ever guaranteed to contain an ID! **Do not assume any property or method to work when dealing
with a partial structure**!
</Callout>
```js
if (message.partial) {
console.log('The message is partial.');
} else {
console.log('The message is not partial.');
}
```
## Obtaining the full structure
Along with `.partial` to check if the structure you call it on is partial or not, the library also introduced a `.fetch()` method to retrieve the missing data from the API and complete the structure. The method returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) you need to handle. After the Promise resolves (and with it, the missing data arrived), you can use the structure as you would before.
```js
// [!code word:partial]
if (message.partial) {
message
.fetch()
.then((fullMessage) => {
console.log(fullMessage.content);
})
.catch((error) => {
console.log('Something went wrong when fetching the message: ', error);
});
} else {
console.log(message.content);
}
```
<Callout type="warn">
You **cannot fetch deleted data** from the API. For message deletions, `messageDelete` will only emit with the ID,
which you cannot use to fetch the complete message containing content, author, or other information, as it is already
inaccessible by the time you receive the event.
</Callout>

View File

@@ -0,0 +1,88 @@
---
title: Permissions (extended)
---
## Discord's permission system
Discord permissions are stored in a 53-bit integer and calculated using bitwise operations. If you want to dive deeper into what's happening behind the curtains, check the [Wikipedia](https://en.wikipedia.org/wiki/Bit_field) and [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#binary_bitwise_operators) articles on the topic.
In discord.js, permission bit fields are represented as either the decimal value of said bit field or its referenced flags.
Every position in a permissions bit field represents one of these flags and its state (either referenced `1` or not referenced `0`).
Before we get into actually assigning permissions, let's quickly go over the method Discord uses to determine a guild member's final permissions:
1. Take all permissions for all roles the guild member has and add them up.
2. Apply all denies for the default role (`@everyone`).
3. Apply all allows for the default role (`@everyone`).
4. Apply all denies for all additional roles the guild member has at once.
5. Apply all allows for all additional roles the guild member has at once.
6. Apply all denies for the specific guild member if they exist.
7. Apply all allows for the specific guild member if they exist.
Due to this system, you cannot deny base permissions. If you grant `SendMessages` to `@everyone` and don't grant it for a muted members role, muted members will still be able to send messages unless you specify channel-based overwrites.
All additional roles allow overwrites are applied after all additional roles denies! If any of a member's roles have an overwrite to allow a permission explicitly, the member can execute the associated actions in this channel regardless of the role hierarchy.
Placing an overwrite to allow `SendMessages` on a role will result in members with this role not being mutable via role assignment in this channel.
## Elevated permissions
If the guild owner enables the server's two-factor authentication option, everyone executing a specific subset of actions will need to have 2FA enabled on their account. As bots do not have 2FA themselves, you, as the application owner, will need to enable it on your account for your bot to work on those servers.
Check out [Discord's help article](https://support.discord.com/hc/articles/219576828) if you need assistance with this.
The permissions assigned to these actions are called "elevated permissions" and are:
`KickMembers`, `BanMembers`, `Administrator`, `ManageChannels`, `ManageGuild`, `ManageMessages`, `ManageRoles`, `ManageWebhooks`, `ManageThreads`, and `ManageGuildExpressions`.
## Implicit permissions
Some Discord permissions apply implicitly based on logical use, which can cause unwanted behavior if you are not aware of this fact.
The prime example for implicit permissions is `ViewChannel`. If this flag is missing in the final permissions, you can't do anything on that channel. It makes sense, right? If you can't view the channel, you can't read or send messages in it, set the topic, or change its name.
The library does not handle implicit permissions for you, so understanding how the system works is vital for you as a bot developer.
Let's say you want to send a message to a channel. To prevent unnecessary API calls, you want to make sure your bot's permissions in this channel include `SendMessages` (more on how to achieve this [here](./permissions#checking-for-permissions)). The check passes, but you still can't send the message and are greeted with `DiscordAPIError: Missing Access`.
This error means your bot is missing `ViewChannel`, and as such, can't send messages either.
One possible scenario causing this: the channel has permission overwrites for the default role `@everyone` to grant `SendMessages` so everyone who can see the channel can also write in it, but at the same time has an overwrite to deny `ViewChannel` to make it only accessible to a subset of members.
As you only check for `SendMessages`, the bot will try to execute the send, but since `ViewChannel` is missing, the API denies the request.
{/* prettier-ignore */}
<Callout title='Causes for "Missing Access"'>
- Text Channels require `ViewChannel` as detailed above.
- Voice Channels require `Connect` in the same way.
- Reacting to a message requires `ReadMessageHistory` in the channel the message was sent.
- When deploying slash commands: Enable the `applications.commands` scope (for more information see the [adding your bot](../preparations/adding-your-app) section).
- Timing out a member requires `ModerateMembers`.
- Editing threads (tags, locking, closing, renaming etc.) requires `SendMessagesInThreads`.
</Callout>
## Limitations and oddities
- Your bot needs `ManageRoles` in its base permissions to change base permissions.
- It needs `ManageRoles` in its final permissions to change permission overwrites.
- It cannot edit permissions for roles that are higher than or equal to its highest role.
- It cannot grant permissions it doesn't have.
- It can manage overwrites for roles or users with higher roles than its own highest role.
- It can manage overwrites for permissions it doesn't have.
- Members with the `Administrator` permission are not affected by overwrites at all.
## Missing permissions
During your development, you will likely run into `DiscordAPIError: Missing Permissions` at some point. One of the following can cause this error:
- Your bot is missing the needed permission to execute this action in its calculated base or final permissions (requirement changes based on the type of action you are trying to perform).
- You provided an invalid permission number while trying to create overwrites. (The calculator on the apps page returns decimal values while the developer documentation lists the flags in hex. Make sure you are not mixing the two and don't use the hex prefix `0x` where not applicable).
- Your bot is currently timed out.
- It is trying to execute an action on a guild member with a role higher than or equal to your bot's highest role.
- It is trying to modify or assign a role higher than or equal to its highest role.
- It is trying to add a managed role to a member.
- It is trying to remove a managed role from a member.
- It is trying to timeout a member with the `Administrator` permission.
- It is trying to execute a forbidden action on the server owner.
- It is trying to execute an action based on another unfulfilled factor (for example, reserved for partnered guilds).
- It is trying to execute an action on a voice channel without the `ViewChannel` permission.
- It is trying to create a channel or channel overwrite including the `ManageRoles` flag but does not have the `Administrator` permission or an explicit `ManageRoles` overwrite on this channel (note that the global permission does not count).
<Callout type="warn">Granting the `Administrator` permission does not skip any **hierarchical** check!</Callout>

View File

@@ -0,0 +1,370 @@
---
title: Permissions
---
Permissions are Discord's primary feature, enabling users to customize their server's workings to their liking.
Essentially, Permissions and permission overwrites tell Discord who is allowed to do what and where.
Permissions can be very confusing at first, but this guide is here to explain and clarify them, so let's dive in!
## Roles as bot permissions
If you want to keep your bot's permission checks simple, you might find it sufficient to check if the member executing the command has a specific role.
If you have the role ID, you can check if the `.roles` Collection on a GuildMember object includes it, using `.has()`. Should you not know the ID and want to check for something like a "Mod" role, you can use `.some()`.
```js
member.roles.cache.has('role-id-here');
// returns true if the member has the role
member.roles.cache.some((role) => role.name === 'Mod');
// returns true if any of the member's roles is exactly named "Mod"
```
If you want to enhance this system slightly, you can include the guild owner by comparing the executing member's ID with `interaction.guild.ownerId`.
To include permission checks like `Administrator` or `ManageGuild`, keep reading as we will cover Discord Permissions and all their intricacies in the following sections.
## Terminology
- Permission: The ability to execute a certain action in Discord
- Overwrite: Rule on a channel to modify the permissions for a member or role
- BitField: Binary representation of Discord permissions
- Base Permissions: Permissions for roles the member has, set on the guild level
- Final Permissions: Permissions for a member or role, after all overwrites are applied
- Flag: Human readable string in PascalCase (e.g., `KickMembers`) that refers to a position in the permission BitField. You can find a list of all valid flags on the `PermissionsBitField#Flags` page
<Callout>
You can provide permission decimals wherever we use flag literals in this guide. If you are interested in a handy
permission calculator, you can look at the "Bot" section in the [Discord developer
portal](https://discord.com/developers/applications).
</Callout>
## Base permissions
### Setting role permissions
Base permissions are set on roles, not the guild member itself. To change them, you access a Role object (for example via `member.roles.cache.first()` or `guild.roles.cache.random()`) and use the `.setPermissions()` method. This is how you'd change the base permissions for the `@everyone` role, for example:
```js
const { PermissionsBitField } = require('discord.js');
guild.roles.everyone.setPermissions([PermissionsBitField.Flags.SendMessages, PermissionsBitField.Flags.ViewChannel]);
```
Any permission not referenced in the flag array or bit field is not granted to the role.
<Callout>
Note that flag names are literal. Although `ViewChannel` grants access to view multiple channels, the permission flag
is still called `ViewChannel` in singular form.
</Callout>
### Creating a role with permissions
Alternatively you can provide permissions as a property of `RoleCreateOptions` during role creation as an array of flag strings or a permission number:
```js
const { PermissionsBitField } = require('discord.js');
guild.roles.create({
name: 'Mod',
permissions: [PermissionsBitField.Flags.SendMessages, PermissionsBitField.Flags.KickMembers],
});
```
### Checking member permissions
To know if one of a member's roles has a permission enabled, you can use the `.has()` method on `GuildMember#permissions` and provide a permission flag, array, or number to check for. You can also specify if you want to allow the `Administrator` permission or the guild owner status to override this check with the following parameters.
```js
const { PermissionsBitField } = require('discord.js');
if (member.permissions.has(PermissionsBitField.Flags.KickMembers)) {
console.log('This member can kick');
}
if (member.permissions.has([PermissionsBitField.Flags.KickMembers, PermissionsBitField.Flags.BanMembers])) {
console.log('This member can kick and ban');
}
if (member.permissions.has(PermissionsBitField.Flags.KickMembers, false)) {
console.log('This member can kick without allowing admin to override');
}
```
If you provide multiple permissions to the method, it will only return `true` if all permissions you specified are granted.
<Callout>You can learn more about the `.has()` method [here](#checking-for-permissions).</Callout>
## Channel overwrites
Permission overwrites control members' abilities for this specific channel or a set of channels if applied to a category with synchronized child channels.
As you have likely already seen in your desktop client, channel overwrites have three states:
- Explicit allow (`true`, green ✓)
- Explicit deny (`false`, red X)
- Default (`null`, gray /)
### Adding overwrites
To add a permission overwrite for a role or guild member, you access the channel's `TextChannel#permissionOverwrites` and use the `.create()` method. The first parameter is the target of the overwrite, either a Role or User object (or its respective resolvable), and the second is a `PermissionOverwriteOptions` object.
Let's add an overwrite to lock everyone out of the channel. The guild ID doubles as the role id for the default role `@everyone` as demonstrated below:
```js
channel.permissionOverwrites.create(channel.guild.roles.everyone, { ViewChannel: false });
```
Any permission flags not specified get neither an explicit allow nor deny overwrite and will use the base permission unless another role has an explicit overwrite set.
You can also provide an array of overwrites during channel creation, as shown below:
```js
const { ChannelType, PermissionsBitField } = require('discord.js');
guild.channels.create({
name: 'new-channel',
type: ChannelType.GuildText,
permissionOverwrites: [
{
id: interaction.guild.id,
deny: [PermissionsBitField.Flags.ViewChannel],
},
{
id: interaction.user.id,
allow: [PermissionsBitField.Flags.ViewChannel],
},
],
});
```
### Editing overwrites
To edit permission overwrites on the channel with a provided set of new overwrites, you can use the `.edit()` method.
```js
// edits overwrites to disallow everyone to view the channel
channel.permissionOverwrites.edit(guild.id, { ViewChannel: false });
// edits overwrites to allow a user to view the channel
channel.permissionOverwrites.edit(user.id, { ViewChannel: true });
```
### Replacing overwrites
To replace all permission overwrites on the channel with a provided set of new overwrites, you can use the `.set()` method. This is extremely handy if you want to copy a channel's full set of overwrites to another one, as this method also allows passing an array or Collection of `PermissionOverwrites`.
```js
const { PermissionsBitField } = require('discord.js');
// copying overwrites from another channel
channel.permissionOverwrites.set(otherChannel.permissionOverwrites.cache);
// replacing overwrites with PermissionOverwriteOptions
channel.permissionOverwrites.set([
{
id: guild.id,
deny: [PermissionsBitField.Flags.ViewChannel],
},
{
id: user.id,
allow: [PermissionsBitField.Flags.ViewChannel],
},
]);
```
### Removing overwrites
To remove the overwrite for a specific member or role, you can use the `.delete()` method.
```js
// deleting the channel's overwrite for the user who interacted
channel.permissionOverwrites.delete(interaction.user.id);
```
### Syncing with a category
If the permission overwrites on a channel under a category match with the parent (category), it is considered synchronized. This means that any changes in the categories overwrites will now also change the channels overwrites. Changing the child channels overwrites will not affect the parent.
<Callout>
This is indeed all that a "sync" in Discord categories means! Users and developers alike commonly misunderstand this
and expect a proper "sync" state that applies in both directions.
</Callout>
To easily synchronize permissions with the parent channel, you can call the `.lockPermissions()` method on the respective child channel.
```js
if (!channel.parent) {
return console.log('This channel is not listed under a category');
}
channel
.lockPermissions()
.then(() => console.log('Successfully synchronized permissions with parent channel'))
.catch(console.error);
```
### Permissions after overwrites
There are two utility methods to easily determine the final permissions for a guild member or role in a specific channel: `GuildChannel#permissionsFor` and `GuildMember#permissionsIn` - `Role#permissionsIn`. All return a `PermissionsBitField` object.
To check your bot's permissions in the channel the command was used in, you could use something like this:
```js
// final permissions for a guild member using permissionsFor
const botPermissionsFor = channel.permissionsFor(guild.members.me);
// final permissions for a guild member using permissionsIn
const botPermissionsIn = guild.members.me.permissionsIn(channel);
// final permissions for a role
const rolePermissions = channel.permissionsFor(role);
```
<Callout type="warn">
The `.permissionsFor()` and `.permissionsIn()` methods return a Permissions object with all permissions set if the
member or role has the global `Administrator` permission and does not take overwrites into consideration in this case.
Using the second parameter of the `.has()` method as described further down in the guide will not allow you to check
without taking `Administrator` into account here!
</Callout>
If you want to know how to work with the returned Permissions objects, keep reading as this will be our next topic.
## The Permissions object
The `PermissionsBitField` object is a discord.js class containing a permissions bit field and a bunch of utility methods to manipulate it easily.
Remember that using these methods will not manipulate permissions, but rather create a new instance representing the changed bit field.
### Displaying permission flags
discord.js provides a `toArray()` method, which can be used to convert a `Permissions` object into an array containing permission flags. This is useful if you want to display/list them and it enables you to use other array manipulation methods. For example:
```js
const memberPermissions = member.permissions.toArray();
const rolePermissions = role.permissions.toArray();
// output: ['SendMessages', 'AddReactions', 'ChangeNickname', ...]
```
<Callout>
The return value of `toArray()` always represents the permission flags present in the Permissions instance that the
method was called on. This means that if you call the method on, for example: `PermissionOverwrites#deny`, you will
receive an array of all denied permissions in that overwrite.
</Callout>
Additionally, you can serialize the Permissions object's underlying bit field by calling `.serialize()`. This returns an object that maps permission names to a boolean value, indicating whether the relevant "bit" is available in the Permissions instance.
```js
const memberPermissions = member.permissions.serialize();
const rolePermissions = role.permissions.serialize();
// output: {
// SendMessages: true,
// AddReactions: true,
// BanMembers: false,
// ...
// }
```
### Converting permission numbers
Some methods and properties in discord.js return permission decimals rather than a Permissions object, making it hard to manipulate or read them if you don't want to use bitwise operations.
However, you can pass these decimals to the Permissions constructor to convert them, as shown below.
```js
const { PermissionsBitField } = require('discord.js');
const permissions = new PermissionsBitField(268_550_160n);
```
You can also use this approach for other `PermissionResolvable` like flag arrays or flags.
```js
const { PermissionsBitField } = require('discord.js');
const flags = [
PermissionsBitField.Flags.ViewChannel,
PermissionsBitField.Flags.EmbedLinks,
PermissionsBitField.Flags.AttachFiles,
PermissionsBitField.Flags.ReadMessageHistory,
PermissionsBitField.Flags.ManageRoles,
];
const permissions = new PermissionsBitField(flags);
```
### Checking for permissions
The Permissions object features the `.has()` method, allowing an easy way to check flags in a Permissions bit field.
The `.has()` method takes two parameters: the first being either a permission number, single flag, or an array of permission numbers and flags, the second being a boolean, indicating if you want to allow the `Administrator` permission to override (defaults to `true`).
Let's say you want to know if the decimal bit field representation `268_550_160` has `ManageChannels` referenced:
```js
const { PermissionsBitField } = require('discord.js');
const bitPermissions = new PermissionsBitField(268_550_160n);
console.log(bitPermissions.has(PermissionsBitField.Flags.ManageChannels));
// output: true
console.log(bitPermissions.has([PermissionsBitField.Flags.ManageChannels, PermissionsBitField.Flags.EmbedLinks]));
// output: true
console.log(bitPermissions.has([PermissionsBitField.Flags.ManageChannels, PermissionsBitField.Flags.KickMembers]));
// output: false
const flagsPermissions = new PermissionsBitField([
PermissionsBitField.Flags.ManageChannels,
PermissionsBitField.Flags.EmbedLinks,
PermissionsBitField.Flags.AttachFiles,
PermissionsBitField.Flags.ReadMessageHistory,
PermissionsBitField.Flags.ManageRoles,
]);
console.log(flagsPermissions.has(PermissionsBitField.Flags.ManageChannels));
// output: true
console.log(flagsPermissions.has([PermissionsBitField.Flags.ManageChannels, PermissionsBitField.Flags.EmbedLinks]));
// output: true
console.log(flagsPermissions.has([PermissionsBitField.Flags.ManageChannels, PermissionsBitField.Flags.KickMembers]));
// output: false
const adminPermissions = new PermissionsBitField(PermissionsBitField.Flags.Administrator);
console.log(adminPermissions.has(PermissionsBitField.Flags.ManageChannels));
// output: true
console.log(adminPermissions.has(PermissionsBitField.Flags.ManageChannels, true));
// output: true
console.log(adminPermissions.has(PermissionsBitField.Flags.ManageChannels, false));
// output: false
```
### Manipulating permissions
The Permissions object enables you to easily add or remove individual permissions from an existing bit field without worrying about bitwise operations. Both `.add()` and `.remove()` can take a single permission flag or number, an array of permission flags or numbers, or multiple permission flags or numbers as multiple parameters.
```js
const { PermissionsBitField } = require('discord.js');
const permissions = new PermissionsBitField([
PermissionsBitField.Flags.ViewChannel,
PermissionsBitField.Flags.EmbedLinks,
PermissionsBitField.Flags.AttachFiles,
PermissionsBitField.Flags.ReadMessageHistory,
PermissionsBitField.Flags.ManageRoles,
]);
console.log(permissions.has(PermissionsBitField.Flags.KickMembers));
// output: false
permissions.add(PermissionsBitField.Flags.KickMembers);
console.log(permissions.has(PermissionsBitField.Flags.KickMembers));
// output: true
permissions.remove(PermissionsBitField.Flags.KickMembers);
console.log(permissions.has(PermissionsBitField.Flags.KickMembers));
// output: false
```
You can utilize these methods to adapt permissions or overwrites without touching the other flags. To achieve this, you can get the existing permissions for a role, manipulating the bit field as described above and passing the changed bit field to `role.setPermissions()`.

View File

@@ -0,0 +1,327 @@
---
title: Reactions
---
## Reacting to messages
One of the first things many people want to know is how to react with emojis, both custom and "regular" (Unicode). There are different routes you need to take for each of those, so let's look at both.
Here's the base code we'll be using:
```js title="reactionsample.js" lineNumbers
const { Client, Events, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions],
});
client.once(Events.ClientReady, (readyClient) => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
client.on(Events.InteractionCreate, (interaction) => {
// ...
});
client.login('your-token-goes-here');
```
### Unicode emojis
To react with a Unicode emoji, you will need the actual Unicode character of the emoji. There are many ways to get a Unicode character of an emoji, but the easiest way would be through Discord itself. If you send a message with a Unicode emoji (such as `:smile:`, for example) and put a `\` before it, it will "escape" the emoji and display the Unicode character instead of the standard emoji image.
To react with an emoji, you need to use the `message.react()` method. Once you have the emoji character, all you need to do is copy & paste it as a string inside the `.react()` method!
```js title="reactionsample.js" lineNumbers=11
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
if (commandName === 'react') {
const response = await interaction.reply({ content: 'You can react with Unicode emojis!', withResponse: true });
response.resource.message.react('😄'); // [!code word:react]
}
});
```
### Custom emojis
For custom emojis, there are multiple ways of reacting. Like Unicode emojis, you can also escape custom emojis. However, when you escape a custom emoji, the result will be different.
This format is essentially the name of the emoji, followed by its ID. Copy & paste the ID into the `.react()` method as a string.
```js title="reactionsample.js" lineNumbers=11
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
if (commandName === 'react-custom') {
const response = await interaction.reply({ content: 'You can react with custom emojis!', withResponse: true });
response.resource.message.react('😄'); // [!code --]
response.resource.message.react('123456789012345678'); // [!code word:123456789012345678] [!code ++]
}
});
```
<Callout>
You can also pass different formats of the emoji to the `.react()` method.
In the emoji mention format, animated emoji are prefixed with an `a`:
```js
message.react('<:blobreach:123456789012345678>');
message.react('blobreach:123456789012345678');
message.react('<a:animated_blobreach:123456789012345678>'); // [!code word:a\:]
message.react('a:animated_blobreach:123456789012345678');
```
</Callout>
Great! This route may not always be available to you, though. Sometimes you'll need to react with an emoji programmatically. To do so, you'll need to retrieve the emoji object.
Two of the easiest ways you can retrieve an emoji would be:
- Use `.find()` on a Collection of Emojis.
- Use `.get()` on the `client.emojis.cache` Collection.
<Callout>
Two or more emojis can have the same name, and using `.find()` will only return the **first** entry it finds. As such,
this can cause unexpected results.
</Callout>
Using `.find()`, your code would look something like this:
```js title="reactionsample.js" lineNumbers=16
if (commandName === 'react-custom') {
const response = await interaction.reply({ content: 'You can react with custom emojis!', withResponse: true });
const message = response.resource.message;
const reactionEmoji = message.guild.emojis.cache.find((emoji) => emoji.name === 'blobreach'); // [!code word:find]
message.react(reactionEmoji);
}
```
Using `.get()`, your code would look something like this:
```js title="reactionsample.js" lineNumbers=16
if (commandName === 'react-custom') {
const response = await interaction.reply({ content: 'You can react with custom emojis!', withResponse: true });
const reactionEmoji = client.emojis.cache.get('123456789012345678'); // [!code word:get]
response.resource.message.react(reactionEmoji);
}
```
Of course, if you already have the emoji ID, you should put that directly inside the `.react()` method. But if you want to do other things with the emoji data later on (e.g., display the name or image URL), it's best to retrieve the full emoji object.
### Reacting in order
If you just put one `message.react()` under another, it won't always react in order as-is. This is because `.react()` an asynchronous operation. Meaning `react('🍊')` will not wait for `react('🍎')` to complete before making the API call and cause a race condition. If you run this code multiple times you should be able to observe this inconsistent order:
```js title="reactionsample.js" lineNumbers=11
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
if (commandName === 'fruits') {
const response = await interaction.reply({ content: 'Reacting with fruits!', withResponse: true });
const { message } = response.resource;
message.react('🍎');
message.react('🍊');
message.react('🍇');
}
});
```
As you can see, if you leave it like that, it won't display as you want. It was able to react correctly on the first try but reacts differently each time after that.
Luckily, there are two easy solutions to this. The first would be to chain `.then()`s in the order you want it to display.
```js title="reactionsample.js" lineNumbers=11
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
if (commandName === 'fruits') {
const response = await interaction.reply({ content: 'Reacting with fruits!', withResponse: true });
response.resource.message
.react('🍎')
.then(() => message.react('🍊'))
.then(() => message.react('🍇'))
.catch((error) => console.error('One of the emojis failed to react:', error));
}
});
```
The other would be to use the `async`/`await` keywords.
```js title="reactionsample.js" lineNumbers=11
// [!code word:async]
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
if (commandName === 'fruits') {
const response = await interaction.reply({ content: 'Reacting with fruits!', withResponse: true });
const { message } = response.resource;
try {
await message.react('🍎'); // [!code word:await]
await message.react('🍊');
await message.react('🍇');
} catch (error) {
console.error('One of the emojis failed to react:', error);
}
}
});
```
If you try again with either of the code blocks above, you'll get the result you originally wanted!
<Callout>
If you aren't familiar with Promises or `async`/`await`, you can read more about them on
[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [our guide page on
async/await](../additional-info/async-await.md)!
</Callout>
### Handling multiple reactions if the order doesn't matter
However, if you don't mind the order the emojis react in, you can take advantage of `Promise.all()`, like so:
```js title="reactionsample.js" lineNumbers=16
if (commandName === 'fruits') {
const message = await interaction.reply({ content: 'Reacting with fruits!' });
Promise.all([message.react('🍎'), message.react('🍊'), message.react('🍇')]).catch((error) =>
console.error('One of the emojis failed to react:', error),
);
}
```
This small optimization allows you to use `.then()` to handle when all of the Promises have resolved, or `.catch()` when one fails. You can also `await` it to do things **after** all the reactions resolved since it returns a Promise.
## Removing reactions
Now that you know how to add reactions, you might be asking, how do you remove them? In this section, you will learn how to remove all reactions, remove reactions by user, and remove reactions by emoji.
<Callout type="warn">
All of these methods require `ManageMessages` permissions. Ensure your bot has permissions before attempting to
utilize any of these methods, as it will error if it doesn't.
</Callout>
### Removing all reactions
Removing all reactions from a message is the easiest, the API allows you to do this through a single call. It can be done through the `message.reactions.removeAll()` method.
```js
message.reactions.removeAll().catch((error) => console.error('Failed to clear reactions:', error)); // [!code word:removeAll]
```
### Removing reactions by emoji
Removing reactions by emoji is easily done by using `MessageReaction#remove`.
```js
message.reactions.cache
.get('123456789012345678')
.remove() // [!code word:remove]
.catch((error) => console.error('Failed to remove reactions:', error));
```
### Removing reactions by user
<Callout>
If you are not familiar with `Collection#filter` and
[`Map.has()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) take the time
to understand what they do and then come back.
</Callout>
Removing reactions by a user is not as straightforward as removing by emoji or removing all reactions. The API does not provide a method for selectively removing the reactions of a user. This means you will have to iterate through reactions that include the user and remove them.
```js
const userReactions = message.reactions.cache.filter((reaction) => reaction.users.cache.has(userId));
try {
for (const reaction of userReactions.values()) {
await reaction.users.remove(userId);
}
} catch (error) {
console.error('Failed to remove reactions.');
}
```
## Awaiting reactions
A common use case for reactions in commands is having a user confirm or deny an action or creating a poll system. Luckily, we actually [already have a guide page covering this](./collectors)! Check out that page if you want a more in-depth explanation. Otherwise, here's a basic example for reference:
```js
message.react('👍').then(() => message.react('👎'));
const collectorFilter = (reaction, user) => {
return ['👍', '👎'].includes(reaction.emoji.name) && user.id === interaction.user.id;
};
message
.awaitReactions({ filter: collectorFilter, max: 1, time: 60_000, errors: ['time'] })
.then((collected) => {
const reaction = collected.first();
if (reaction.emoji.name === '👍') {
message.reply('You reacted with a thumbs up.');
} else {
message.reply('You reacted with a thumbs down.');
}
})
.catch((collected) => {
message.reply('You reacted with neither a thumbs up, nor a thumbs down.');
});
```
## Listening for reactions on old messages
Messages sent before your bot started are uncached unless you fetch them first. By default, the library does not emit client events if the data received and cached is not sufficient to build fully functional objects.
Since version 12, you can change this behavior by activating partials. For a full explanation of partials see [this page](./partials).
Make sure you enable partial structures for `Message`, `Channel`, and `Reaction` when instantiating your client if you want reaction events on uncached messages for both server and direct message channels. If you do not want to support direct message channels, you can exclude `Channel`.
<Callout>
If you use [gateway intents](./intents) but can't or don't want to use the privileged `GuildPresences` intent, you
additionally need the `User` partial.
</Callout>
```js title="old-reactions.js"
const { Client, Events, GatewayIntentBits, Partials } = require('discord.js');
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions],
// [!code word:Partials]
partials: [Partials.Message, Partials.Channel, Partials.Reaction],
});
client.on(Events.MessageReactionAdd, async (reaction, user) => {
// When a reaction is received, check if the structure is partial
if (reaction.partial) {
// If the message this reaction belongs to was removed, the fetching might result in an API error which should be handled
try {
await reaction.fetch();
} catch (error) {
console.error('Something went wrong when fetching the message:', error);
// Return as `reaction.message.author` may be undefined/null
return;
}
}
// Now the message has been cached and is fully available
console.log(`${reaction.message.author}'s message "${reaction.message.content}" gained a reaction!`);
// The reaction is now also fully available and the properties will be reflected accurately:
console.log(`${reaction.count} user(s) have given the same reaction to this message!`);
});
```
<Callout>
Partial structures are enabled **globally**. You cannot only make them work for a specific event or cache, and you
very likely **need to adapt other parts of your code** that are accessing data from the relevant caches. All caches
holding the respective structure type might return partials as well! For more info, check out [this page](./partials).
</Callout>

View File

@@ -0,0 +1,162 @@
---
title: Threads
---
Threads can be thought of as temporary sub-channels inside an existing channel, to help better organize conversation in a busy channel.
## Thread related gateway events
<Callout>You can use the `ThreadChannel#isThread` type guard to make sure a channel is a `ThreadChannel`!</Callout>
Threads introduce a number of new gateway events, which are listed below:
- `Client#threadCreate`: Emitted whenever a thread is created or when the client user is added to a thread.
- `Client#threadDelete`: Emitted whenever a thread is deleted.
- `Client#threadUpdate`: Emitted whenever a thread is updated (e.g. name change, archive state change, locked state change).
- `Client#threadListSync`: Emitted whenever the client user gains access to a text or news channel that contains threads.
- `Client#threadMembersUpdate`: Emitted whenever members are added or removed from a thread. Requires `GuildMembers` privileged intent.
- `Client#threadMemberUpdate`: Emitted whenever the client user's thread member is updated.
## Creating and deleting threads
Threads are created and deleted using the `GuildTextThreadManager` of a text or news channel.
To create a thread you call the `GuildTextThreadManager#create` method:
```js
// [!code word:create]
const thread = await channel.threads.create({
name: 'food-talk',
autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
reason: 'Needed a separate thread for food',
});
console.log(`Created thread: ${thread.name}`);
```
To delete a thread, use the `ThreadChannel#delete` method:
```js
const thread = channel.threads.cache.find((x) => x.name === 'food-talk');
await thread.delete(); // [!code word:delete]
```
## Joining and leaving threads
To join your client to a ThreadChannel, use the `ThreadChannel#join` method:
```js
const thread = channel.threads.cache.find((x) => x.name === 'food-talk');
if (thread.joinable) await thread.join(); // [!code word:join()]
```
And to leave one, use `ThreadChannel#leave`;
```js
const thread = channel.threads.cache.find((x) => x.name === 'food-talk');
await thread.leave(); // [!code word:leave]
```
## Archiving, unarchiving, and locking threads
A thread can be either active or archived. Changing a thread from archived to active is referred to as unarchiving the thread. Threads that have `locked` set to true can only be unarchived by a member with the `ManageThreads` permission.
Threads are automatically archived after inactivity. "Activity" is defined as sending a message, unarchiving a thread, or changing the auto-archive time.
To archive or unarchive a thread, use the `ThreadChannel#setArchived` method and pass in a boolean parameter:
```js
const thread = channel.threads.cache.find((x) => x.name === 'food-talk');
await thread.setArchived(true); // archived // [!code word:setArchived]
await thread.setArchived(false); // unarchived
```
This same principle applies to locking and unlocking a thread via the `ThreadChannel#setLocked` method:
```js
const thread = channel.threads.cache.find((x) => x.name === 'food-talk');
await thread.setLocked(true); // locked [!code word:setLocked]
await thread.setLocked(false); // unlocked
```
## Public and private threads
Public threads are viewable by everyone who can view the parent channel of the thread. Public threads can be created with the `GuildTextThreadManager#create` method.
```js
// [!code word:create]
const thread = await channel.threads.create({
name: 'food-talk',
autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
reason: 'Needed a separate thread for food',
});
console.log(`Created thread: ${thread.name}`);
```
They can also be created from an existing message with the `Message#startThread` method, but will be "orphaned" if that message is deleted. The thread is not deleted along with the message and will still be available through the hcannels thread list!
```js
// [!code word:startThread]
const thread = await message.startThread({
name: 'food-talk',
autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
reason: 'Needed a separate thread for food',
});
console.log(`Created thread: ${thread.name}`);
```
<Callout>The created thread and the message it originated from will share the same ID.</Callout>
Private threads can only be created on text channels. Private threads can initially only be viewed by the user creating them as well as moderators with the `ManageThreads` permission. Users that are mentioned in threads are added to the thread! This is also true for roles with few role members. The notifications for `@here` and `@everyone` will only affect members in the thread and not add anyone.
To create a private thread, use `GuildTextThreadManager#create` and pass in `ChannelType.PrivateThread` as the `type`. Public channels are the default, hence passing `ChannelType.PublicThread` is not required in the example above:
```js
const { ChannelType, ThreadAutoArchiveDuration } = require('discord.js');
const thread = await channel.threads.create({
name: 'mod-talk',
autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
type: ChannelType.PrivateThread, // [!code word:PrivateThread] [!code ++]
reason: 'Needed a separate thread for moderation',
});
console.log(`Created thread: ${thread.name}`);
```
<Callout>You cannot create private threads on an existing message.</Callout>
## Adding and removing members
You can add and remove members to and from a thread channel.
To add a member to a thread, use the `ThreadMemberManager#add` method:
```js
const thread = channel.threads.cache.find((x) => x.name === 'food-talk');
await thread.members.add('140214425276776449'); // [!code word:add]
```
And to remove a member from a thread, use `ThreadMemberManager#remove`:
```js
const thread = channel.threads.cache.find((x) => x.name === 'food-talk');
await thread.members.remove('140214425276776449'); // [!code word:remove]
```
## Sending messages to threads with webhooks
It is possible for a webhook built on the parent channel to send messages to the channel's threads. For the purpose of this example, it is assumed a single webhook already exists for that channel (More specifically, that there are no followed channels or managed webhooks. These webhook types cannot be accessed by your app). If you wish to learn more about webhooks, see our [webhook guide](./webhooks).
```js
const webhooks = await channel.fetchWebhooks();
const webhook = webhooks.first();
await webhook.send({
content: "Look ma! I'm in a thread!",
threadId: '123456789012345678', // [!code word:threadId]
});
```
And that's it! Now you know all there is to know on working with threads using discord.js!

View File

@@ -0,0 +1,198 @@
---
title: Webhooks
---
Webhooks can send messages to a text channel without having to log in as a bot. They can also fetch, edit, and delete their own messages. There are a variety of methods in discord.js to interact with webhooks. In this section, you will learn how to create, fetch, edit, and use webhooks.
## What is a webhook
Webhooks are a utility used to send messages to text channels without needing a Discord application. Webhooks are useful for allowing something to send messages without requiring a Discord application. You can also directly edit or delete messages you sent through the webhook. There are two structures to make use of this functionality: `Webhook` and `WebhookClient`. `WebhookClient` is an extended version of a `Webhook`, which allows you to send messages through it without needing a bot client.
<Callout>
If you would like to read about using webhooks through the API without discord.js, you can read about them
[here](https://discord.com/developers/docs/resources/webhook).
</Callout>
## Detecting webhook messages
Bots receive webhook messages in a text channel as usual. You can detect if a webhook sent the message by checking if the `Message.webhookId` is not `null`. In this example, we return if a webhook sent the message.
```js
if (message.webhookId) return;
```
If you would like to get the webhook object that sent the message, you can use `Message#fetchWebhook`.
## Fetching webhooks
<Callout>
Webhook fetching will always make use of collections and Promises. If you do not understand either concept, revise
them, and then come back to this section. You can read about collections [here](../additional-info/collections), and
Promises [here](../additional-info/async-await) and
[here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises).
</Callout>
### Fetching all webhooks of a guild
If you would like to get all webhooks of a guild you can use `Guild#fetchWebhooks`. This will return a Promise which will resolve into a Collection of `Webhook`s.
### Fetching webhooks of a channel
Webhooks belonging to a channel can be fetched using `TextChannel#fetchWebhooks`. This will return a Promise which will resolve into a Collection of `Webhook`s. A collection will be returned even if the channel contains a single webhook. If you are certain the channel contains a single webhook, you can use `Collection#first` on the Collection to get the webhook.
### Fetching a single webhook
#### Using client
You can fetch a specific webhook using its `id` with `Client#fetchWebhook`. You can obtain the webhook id by looking at its link, the number after `https://discord.com/api/webhooks/` is the `id`, and the part after that is the `token`.
#### Using the WebhookClient constructor
If you are not using a bot client, you can get a webhook by creating a new instance of `WebhookClient` and passing the `id` and `token` into the constructor. These credentials do not require you to have a bot application, but it also offers limited information instead of fetching it using an authorized client.
```js
// [!code word:token\:]
const webhookClient = new WebhookClient({ id: 'id', token: 'token' }); // [!code word:id\:]
```
You can also pass in just a `url`:
```js
const webhookClient = new WebhookClient({ url: 'https://discord.com/api/webhooks/id/token' }); // [!code word:url\:]
```
## Creating webhooks
### Creating webhooks through server settings
You can create webhooks directly through the Discord client. Go to Server Settings, and you will see an `Integrations` tab.
![Integrations tab](./images/creating-webhooks-1.png)
If you already have created a webhook, the webhooks tab will look like this; you will need to click the `View Webhooks` button.
![Integrations tab](./images/creating-webhooks-2.png)
Once you are there, click on the `Create Webhook` / `New Webhook` button; this will create a webhook. From here, you can edit the channel, the name, and the avatar. Copy the link, the first part is the id, and the second is the token.
![Creating a Webhook](./images/creating-webhooks-3.png)
### Creating webhooks with discord.js
Webhooks can be created with the `TextChannel#createWebhook` method.
```js
channel
// [!code word:createWebhook]
.createWebhook({
name: 'Some-username',
avatar: 'https://i.imgur.com/AfFp7pu.png',
})
.then((webhook) => console.log(`Created webhook ${webhook}`))
.catch(console.error);
```
## Editing webhooks
You can edit Webhooks and WebhookClients to change their name, avatar, and channel using `Webhook#edit`.
```js
webhook
// [!code word:edit]
.edit({
name: 'Some-username',
avatar: 'https://i.imgur.com/AfFp7pu.png',
channel: '222197033908436994',
})
.then((webhook) => console.log(`Edited webhook ${webhook}`))
.catch(console.error);
```
## Using webhooks
Webhooks can send messages to text channels, as well as fetch, edit, and delete their own. These methods are the same for both `Webhook` and `WebhookClient`.
### Sending messages
Webhooks, like bots, can send up to 10 embeds per message. They can also send attachments and normal content. The `Webhook#send` method is very similar to the method used for sending a message to a text channel. Webhooks can also choose how the username and avatar will appear when they send the message.
Example using a WebhookClient:
```js
const { EmbedBuilder, WebhookClient } = require('discord.js'); // [!code word:WebhookClient]
const { webhookId, webhookToken } = require('./config.json');
const webhookClient = new WebhookClient({ id: webhookId, token: webhookToken });
const embed = new EmbedBuilder().setTitle('Some Title').setColor(0x00ffff);
// [!code word:send]
webhookClient.send({
content: 'Webhook test',
username: 'some-username',
avatarURL: 'https://i.imgur.com/AfFp7pu.png',
embeds: [embed],
});
```
Try to find a webhook your bot knows the token for. This makes sure your bot can execute the webhook later on.
```js
const { Client, EmbedBuilder, Events, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const embed = new EmbedBuilder().setTitle('Some Title').setColor(0x00ffff);
client.once(Events.ClientReady, async () => {
const channel = client.channels.cache.get('123456789012345678');
try {
const webhooks = await channel.fetchWebhooks();
const webhook = webhooks.find((wh) => wh.token); // [!code word:wh.token]
if (!webhook) {
return console.log('No webhook was found that I can use!');
}
await webhook.send({
content: 'Webhook test',
username: 'some-username',
avatarURL: 'https://i.imgur.com/AfFp7pu.png',
embeds: [embed],
});
} catch (error) {
console.error('Error trying to send a message: ', error);
}
});
client.login(token);
```
### Fetching messages
You can use `Webhook#fetchMessage` to fetch messages previously sent by the Webhook.
```js
const message = await webhookClient.fetchMessage('123456789012345678'); // [!code word:fetchMessage]
```
### Editing messages
You can use `Webhook#editMessage` to edit messages previously sent by the Webhook.
```js
//[!code word:editMessage]
const message = await webhook.editMessage('123456789012345678', {
content: 'Edited!',
embeds: [embed],
});
```
### Deleting messages
You can use `Webhook#deleteMessage` to delete messages previously sent by the Webhook.
```js
await webhookClient.deleteMessage('123456789012345678'); // [!code word:deleteMessage]
```