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>
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Hello World
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
I love Anime.
|
||||
103
apps/guide/content/docs/legacy/additional-features/cooldowns.mdx
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
title: Cooldowns
|
||||
---
|
||||
|
||||
Spam is something you generally want to avoid, especially if one of your commands require calls to other APIs or takes a bit of time to build/send.
|
||||
|
||||
<Callout>
|
||||
This section assumes you followed the [Command Handling](../app-creation/handling-commands) part of the guide.
|
||||
</Callout>
|
||||
|
||||
First, add a cooldown property to your command. This will determine how long the user would have to wait (in seconds) before using the command again.
|
||||
|
||||
```js title="commands/utility/ping.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
cooldown: 5, // [!code ++]
|
||||
data: new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'),
|
||||
async execute(interaction) {
|
||||
// ...
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
In your main file, initialize a [Collection](../additional-info/collections) to store cooldowns of commands:
|
||||
|
||||
```js
|
||||
client.cooldowns = new Collection();
|
||||
```
|
||||
|
||||
The key will be the command names, and the values will be Collections associating the user's id (key) to the last time (value) this user used this command. Overall the logical path to get a user's last usage of a command will be `cooldowns > command > user > timestamp`.
|
||||
|
||||
In your `InteractionCreate` event handler, add the following code:
|
||||
|
||||
```js title="index.js / interactionCreate.js (if you followed the event handler section)"
|
||||
// ...
|
||||
// [!code ++:14]
|
||||
const { cooldowns } = interaction.client;
|
||||
|
||||
if (!cooldowns.has(command.data.name)) {
|
||||
cooldowns.set(command.data.name, new Collection());
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const timestamps = cooldowns.get(command.data.name);
|
||||
const defaultCooldownDuration = 3;
|
||||
const cooldownAmount = (command.cooldown ?? defaultCooldownDuration) * 1_000;
|
||||
|
||||
if (timestamps.has(interaction.user.id)) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You check if the `cooldowns` Collection already has an entry for the command being used. If this is not the case, you can add a new entry, where the value is initialized as an empty Collection. Next, create the following variables:
|
||||
|
||||
1. `now`: The current timestamp.
|
||||
2. `timestamps`: A reference to the Collection of user ids and timestamp key/value pairs for the triggered command.
|
||||
3. `cooldownAmount`: The specified cooldown for the command, converted to milliseconds for straightforward calculation. If none is specified, this defaults to three seconds.
|
||||
|
||||
If the user has already used this command in this session, get the timestamp, calculate the expiration time, and inform the user of the amount of time they need to wait before using this command again. Note the use of the `return` statement here, causing the code below this snippet to execute only if the user has not used this command in this session or the wait has already expired.
|
||||
|
||||
Continuing with your current setup, this is the complete `if` statement:
|
||||
|
||||
```js title="index.js / interactionCreate.js (if you followed the event handler section)"
|
||||
const defaultCooldownDuration = 3;
|
||||
const cooldownAmount = (command.cooldown ?? defaultCooldownDuration) * 1_000;
|
||||
|
||||
// [!code focus:13]
|
||||
if (timestamps.has(interaction.user.id)) {
|
||||
// ... // [!code --]
|
||||
// [!code ++:9]
|
||||
const expirationTime = timestamps.get(interaction.user.id) + cooldownAmount;
|
||||
|
||||
if (now < expirationTime) {
|
||||
const expiredTimestamp = Math.round(expirationTime / 1_000);
|
||||
return interaction.reply({
|
||||
content: `Please wait, you are on a cooldown for \`${command.data.name}\`. You can use it again <t:${expiredTimestamp}:R>.`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Since the `timestamps` Collection has the user's id as the key, you can use the `get()` method on it to get the value and sum it up with the `cooldownAmount` variable to get the correct expiration timestamp and further check to see if it's expired or not.
|
||||
|
||||
The previous user check serves as a precaution in case the user leaves the guild. You can now use the `setTimeout` method, which will allow you to execute a function after a specified amount of time and remove the timeout.
|
||||
|
||||
```js
|
||||
// [!code focus]
|
||||
if (timestamps.has(interaction.user.id)) {
|
||||
const expiredTimestamp = Math.round(expirationTime / 1_000);
|
||||
return interaction.reply({
|
||||
content: `Please wait, you are on a cooldown for \`${command.data.name}\`. You can use it again <t:${expiredTimestamp}:R>.`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} // [!code focus]
|
||||
|
||||
// [!code ++:2] [!code focus:2]
|
||||
timestamps.set(interaction.user.id, now);
|
||||
setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount);
|
||||
```
|
||||
|
||||
This line causes the entry for the user under the specified command to be deleted after the command's cooldown time has expired for them.
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Reloading Commands
|
||||
---
|
||||
|
||||
When writing your commands, you may find it tedious to restart your bot every time for testing the smallest changes. With a command handler, you can eliminate this issue and reload your commands while your bot is running.
|
||||
|
||||
<Callout>
|
||||
ESM does not support require and clearing import cache. You can use [hot-esm](https://www.npmjs.com/package/hot-esm)
|
||||
to import files without cache. Windows support is experimental per [this
|
||||
issue](https://github.com/vinsonchuong/hot-esm/issues/33).
|
||||
</Callout>
|
||||
|
||||
<Callout>
|
||||
This section assumes you followed the [Command Handling](../app-creation/handling-commands) part of the guide.
|
||||
</Callout>
|
||||
|
||||
```js title="commands/utility/reload.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('reload')
|
||||
.setDescription('Reloads a command.')
|
||||
.addStringOption((option) => option.setName('command').setDescription('The command to reload.').setRequired(true)),
|
||||
async execute(interaction) {
|
||||
// ...
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
First off, you need to check if the command you want to reload exists. You can do this check similarly to getting a command.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// ...
|
||||
// [!code focus:10]
|
||||
async execute(interaction) {
|
||||
// ... // [!code --]
|
||||
// [!code ++:6]
|
||||
const commandName = interaction.options.getString('command', true).toLowerCase();
|
||||
const command = interaction.client.commands.get(commandName);
|
||||
|
||||
if (!command) {
|
||||
return interaction.reply(`There is no command with name \`${commandName}\`!`);
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
The reload command ideally should not be used by every user. You should deploy it as a guild command in a private
|
||||
guild.
|
||||
</Callout>
|
||||
|
||||
To build the correct file path, you will need the file name. You can use `command.data.name` for doing that.
|
||||
|
||||
In theory, all there is to do is delete the previous command from `client.commands` and require the file again. In practice, you cannot do this easily as `require()` caches the file. If you were to require it again, you would load the previously cached file without any changes. You first need to delete the file from `require.cache`, and only then should you require and set the command file to `client.commands`:
|
||||
|
||||
```js
|
||||
delete require.cache[require.resolve(`./${command.data.name}.js`)];
|
||||
|
||||
try {
|
||||
const newCommand = require(`./${command.data.name}.js`);
|
||||
interaction.client.commands.set(newCommand.data.name, newCommand);
|
||||
await interaction.reply(`Command \`${newCommand.data.name}\` was reloaded!`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
await interaction.reply(
|
||||
`There was an error while reloading a command \`${command.data.name}\`:\n\`${error.message}\``,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The snippet above uses a `try...catch` block to load the command file and add it to `client.commands`. In case of an error, it will log the full error to the console and notify the user about it with the error's message component `error.message`. Note that you never actually delete the command from the commands Collection and instead overwrite it. This behavior prevents you from deleting a command and ending up with no command at all after a failed `require()` call, as each use of the reload command checks that Collection again.
|
||||
219
apps/guide/content/docs/legacy/additional-info/async-await.mdx
Normal file
@@ -0,0 +1,219 @@
|
||||
---
|
||||
title: Understanding async/await
|
||||
---
|
||||
|
||||
If you aren't very familiar with ECMAScript 2017, you may not know about async/await. It's a useful way to handle Promises in a hoisted manner. It's also slightly faster and increases overall readability.
|
||||
|
||||
## How do Promises work?
|
||||
|
||||
Before we can get into async/await, you should know what Promises are and how they work because async/await is just a way to handle Promises. If you know what Promises are and how to deal with them, you can skip this part.
|
||||
|
||||
Promises are a way to handle asynchronous tasks in JavaScript; they are the newer alternative to callbacks. A Promise has many similarities to a progress bar; they represent an unfinished and ongoing process. An excellent example of this is a request to a server (e.g., discord.js sends requests to Discord's API).
|
||||
|
||||
A Promise can have three states; pending, resolved, and rejected.
|
||||
|
||||
- The **pending** state means that the Promise still is ongoing and neither resolved nor rejected.
|
||||
- The **resolved** state means that the Promise is done and executed without any errors.
|
||||
- The **rejected** state means that the Promise encountered an error and could not execute correctly.
|
||||
|
||||
One important thing to know is that a Promise can only have one state simultaneously; it can never be pending and resolved, rejected and resolved, or pending and rejected. You may be asking, "How would that look in code?". Here is a small example:
|
||||
|
||||
<Callout>
|
||||
This example uses ES6 code. If you do not know what that is, you should read up on that [here](./es6-syntax).
|
||||
</Callout>
|
||||
|
||||
```js
|
||||
function deleteMessages(amount) {
|
||||
// [!code word:Promise]
|
||||
return new Promise((resolve, reject) => {
|
||||
if (amount > 10) return reject(new Error("You can't delete more than 10 Messages at a time."));
|
||||
setTimeout(() => resolve('Deleted 10 messages.'), 2_000);
|
||||
});
|
||||
}
|
||||
|
||||
deleteMessages(5)
|
||||
// [!code word:then]
|
||||
.then((value) => {
|
||||
// `deleteMessages` is complete and has not encountered any errors
|
||||
// the resolved value will be the string "Deleted 10 messages"
|
||||
})
|
||||
// [!code word:catch]
|
||||
.catch((error) => {
|
||||
// `deleteMessages` encountered an error
|
||||
// the error will be an Error Object
|
||||
});
|
||||
```
|
||||
|
||||
In this scenario, the `deleteMessages` function returns a Promise. The `.then()` method will trigger if the Promise resolves, and the `.catch()` method if the Promise rejects. In the `deleteMessages` function, the Promise is resolved after 2 seconds with the string "Deleted 10 messages.", so the `.catch()` method will never be executed. You can also pass the `.catch()` function as the second parameter of `.then()`.
|
||||
|
||||
## How to implement async/await
|
||||
|
||||
### Theory
|
||||
|
||||
The following information is essential to know before working with async/await. You can only use the `await` keyword inside a function declared as `async` (you put the `async` keyword before the `function` keyword or before the parameters when using a callback function).
|
||||
|
||||
A simple example would be:
|
||||
|
||||
```js
|
||||
// [!code word:async]
|
||||
async function declaredAsAsync() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
// [!code word:async]
|
||||
const declaredAsAsync = async () => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
You can use that as well if you use the arrow function as an event listener.
|
||||
|
||||
```js
|
||||
client.on('event', async (first, last) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
An important thing to know is that a function declared as `async` will always return a Promise. In addition to this, if you return something, the Promise will resolve with that value, and if you throw an error, it will reject the Promise with that error.
|
||||
|
||||
### Execution with discord.js code
|
||||
|
||||
Now that you know how Promises work and what they are used for, let's look at an example that handles multiple Promises. Let's say you want to react with letters (regional indicators) in a specific order. For this example, here's a basic template for a discord.js bot with some ES6 adjustments.
|
||||
|
||||
```js title="promise-example.js" lineNumbers
|
||||
const { Client, Events, GatewayIntentBits } = require('discord.js');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.once(Events.ClientReady, () => {
|
||||
console.log('I am ready!');
|
||||
});
|
||||
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
if (interaction.commandName === 'react') {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
|
||||
client.login('your-token-goes-here');
|
||||
```
|
||||
|
||||
If you don't know how Node.js asynchronous execution works, you would probably try something like this:
|
||||
|
||||
```js title="promise-example.js" lineNumbers=9
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
// ...
|
||||
// [!code focus:7]
|
||||
if (commandName === 'react') {
|
||||
const response = interaction.reply({ content: 'Reacting!', withResponse: true }); // [!code ++:5]
|
||||
const { message } = response.resource;
|
||||
message.react('🇦');
|
||||
message.react('🇧');
|
||||
message.react('🇨');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
But since all of these methods are started at the same time, it would just be a race to which server request finished first, so there would be no guarantee that it would react at all (if the message isn't fetched) or in the order you wanted it to. In order to make sure it reacts after the message is sent and in order (a, b, c), you'd need to use the `.then()` callback from the Promises that these methods return. The code would look like this:
|
||||
|
||||
```js title="promise-example.js" lineNumbers=9
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
// ...
|
||||
if (commandName === 'react') {
|
||||
interaction.reply({ content: 'Reacting!', withResponse: true }).then((response) => {
|
||||
const { message } = response.resource;
|
||||
message.react('🇦'); // [!code --:3]
|
||||
message.react('🇧');
|
||||
message.react('🇨');
|
||||
message // [!code ++:7]
|
||||
.react('🇦')
|
||||
.then(() => message.react('🇧'))
|
||||
.then(() => message.react('🇨'))
|
||||
.catch((error) => {
|
||||
// handle failure of any Promise rejection inside here
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
In this piece of code, the Promises are [chain resolved](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then#Chaining) with each other, and if one of the Promises gets rejected, the function passed to `.catch()` gets called. Here's the same code but with async/await:
|
||||
|
||||
```js title="promise-example.js" lineNumbers=9
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
// ...
|
||||
if (commandName === 'react') {
|
||||
const response = await interaction.reply({ content: 'Reacting!', withResponse: true });
|
||||
const { message } = response.resource;
|
||||
message.react('🇦'); // [!code --:3]
|
||||
message.react('🇧');
|
||||
message.react('🇨');
|
||||
await message.react('🇦'); // [!code ++:3]
|
||||
await message.react('🇧');
|
||||
await message.react('🇨');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
It's mostly the same code, but how would you catch Promise rejections now since `.catch()` isn't there anymore? That is also a useful feature with async/await; the error will be thrown if you await it so that you can wrap the awaited Promises inside a try/catch, and you're good to go.
|
||||
|
||||
```js title="promise-example.js" lineNumbers=9
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (commandName === 'react') {
|
||||
// [!code ++]
|
||||
try {
|
||||
const response = await interaction.reply({ content: 'Reacting!', withResponse: true });
|
||||
const { message } = response.resource;
|
||||
await message.react('🇦');
|
||||
await message.react('🇧');
|
||||
await message.react('🇨');
|
||||
// [!code ++:3]
|
||||
} catch (error) {
|
||||
// handle failure of any Promise rejection inside here
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This code looks clean and is also easy to read.
|
||||
|
||||
So you may be asking, "How would I get the value the Promise resolved with?".
|
||||
|
||||
Let's look at an example where you want to delete a sent reply.
|
||||
|
||||
```js title="promise-example.js"
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
// ...
|
||||
if (commandName === 'delete') {
|
||||
interaction
|
||||
.reply({ content: 'This message will be deleted.', withResponse: true })
|
||||
.then((response) => setTimeout(() => response.resource.message.delete(), 10_000)) // [!code word:response]
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The return value of a `.reply()` with the `withResponse` option set to `true` is a promise which resolves with `InteractionCallbackResponse`, but how would the same code with async/await look?
|
||||
|
||||
```js title="promise-example.js"
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (commandName === 'delete') {
|
||||
try {
|
||||
const response = await interaction.reply({ content: 'This message will be deleted.', withResponse: true }); // [!code word:response]
|
||||
setTimeout(() => response.resource.message.delete(), 10_000);
|
||||
} catch (error) {
|
||||
// handle error
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
With async/await, you can assign the awaited function to a variable representing the returned value. Now you know how you use async/await.
|
||||
@@ -0,0 +1,674 @@
|
||||
---
|
||||
title: Updating to v14
|
||||
---
|
||||
|
||||
## Before you start
|
||||
|
||||
Make sure you're using the latest LTS version of Node. To check your Node version, use `node -v` in your terminal or command prompt, and if it's not high enough, update it! There are many resources online to help you with this step based on your host system.
|
||||
|
||||
### Various packages are now included in v14
|
||||
|
||||
If you previously had `@discordjs/builders`, `@discordjs/formatters`, `@discordjs/rest`, or `discord-api-types` manually installed, it's _highly_ recommended that you uninstall the packages to avoid package version conflicts.
|
||||
|
||||
```sh tab="npm"
|
||||
npm uninstall @discordjs/builders @discordjs/formatters @discordjs/rest discord-api-types
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn remove @discordjs/builders @discordjs/formatters @discordjs/rest discord-api-types
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm remove @discordjs/builders @discordjs/formatters @discordjs/rest discord-api-types
|
||||
```
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### API version
|
||||
|
||||
discord.js v14 makes the switch to Discord API v10!
|
||||
|
||||
### Common Breakages
|
||||
|
||||
### Enum Values
|
||||
|
||||
Any areas that used to accept a `string` or `number` type for an enum parameter will now only accept exclusively `number`s.
|
||||
|
||||
In addition, the old enums exported by discord.js v13 and lower are replaced with new enums from [discord-api-types](https://discord-api-types.dev/api/discord-api-types-v10).
|
||||
|
||||
#### New enum differences
|
||||
|
||||
Most of the difference between enums from discord.js and discord-api-types can be summarized as so:
|
||||
|
||||
1. Enums are singular, i.e., `ApplicationCommandOptionTypes` -> `ApplicationCommandOptionType`
|
||||
2. Enums that are prefixed with `Message` no longer have the `Message` prefix, i.e., `MessageButtonStyles` -> `ButtonStyle`
|
||||
3. Enum values are `PascalCase` rather than `SCREAMING_SNAKE_CASE`, i.e., `.CHAT_INPUT` -> `.ChatInput`
|
||||
|
||||
<Callout>
|
||||
You might be inclined to use raw `number`s (most commonly referred to as [magic numbers](<https://en.wikipedia.org/wiki/Magic_number_(programming)>)) instead of enum values. This is highly discouraged. Enums provide more readability and are more resistant to changes in the API. Magic numbers can obscure the meaning of your code in many ways, check out this [blog post](https://blog.webdevsimplified.com/2020-02/magic-numbers/) if you want more context on as to why they shouldn't be used.
|
||||
</Callout>
|
||||
|
||||
#### Common enum breakages
|
||||
|
||||
Areas like `Client` initialization, JSON slash commands and JSON message components will likely need to be modified to accommodate these changes:
|
||||
|
||||
##### Common Client Initialization Changes
|
||||
|
||||
```js
|
||||
const { Client, Intents } = require('discord.js'); // [!code --]
|
||||
const { Client, GatewayIntentBits, Partials } = require('discord.js'); // [!code ++]
|
||||
|
||||
const client = new Client({ intents: [Intents.FLAGS.GUILDS], partials: ['CHANNEL'] }); // [!code --]
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds], partials: [Partials.Channel] }); // [!code ++]
|
||||
```
|
||||
|
||||
##### Common Application Command Data changes
|
||||
|
||||
```js
|
||||
const { ApplicationCommandType, ApplicationCommandOptionType } = require('discord.js'); // [!code ++]
|
||||
|
||||
const command = {
|
||||
name: 'ping',
|
||||
type: 'CHAT_INPUT', // [!code --]
|
||||
type: ApplicationCommandType.ChatInput, // [!code ++]
|
||||
options: [
|
||||
{
|
||||
name: 'option',
|
||||
description: 'A sample option',
|
||||
type: 'STRING', // [!code --]
|
||||
type: ApplicationCommandOptionType.String, // [!code ++]
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
##### Common Button Data changes
|
||||
|
||||
```js
|
||||
const { ButtonStyle } = require('discord.js'); // [!code ++]
|
||||
|
||||
const button = {
|
||||
label: 'test',
|
||||
style: 'PRIMARY', // [!code --]
|
||||
style: ButtonStyle.Primary, // [!code ++]
|
||||
customId: '1234',
|
||||
};
|
||||
```
|
||||
|
||||
### Removal of method-based type guards
|
||||
|
||||
#### Channels
|
||||
|
||||
Some channel type guard methods that narrowed to one channel type have been removed. Instead compare the `type` property against a [ChannelType](https://discord-api-types.dev/api/discord-api-types-v10/enum/ChannelType) enum member to narrow channels.
|
||||
|
||||
```js
|
||||
const { ChannelType } = require('discord.js'); // [!code ++]
|
||||
|
||||
channel.isText(); // [!code --]
|
||||
channel.type === ChannelType.GuildText; // [!code ++]
|
||||
|
||||
channel.isVoice(); // [!code --]
|
||||
channel.type === ChannelType.GuildVoice; // [!code ++]
|
||||
|
||||
channel.isDM(); // [!code --]
|
||||
channel.type === ChannelType.DM; // [!code ++]
|
||||
```
|
||||
|
||||
### Builders
|
||||
|
||||
Builders are no longer returned by the API like they were previously. For example you send the API an `EmbedBuilder` but you receive an `Embed` of the same data from the API. This may affect how your code handles received structures such as components. Refer to [message component changes section](#messagecomponent) for more details.
|
||||
|
||||
Added `disableValidators()` and `enableValidators()` as top-level exports which disable or enable validation (enabled by default).
|
||||
|
||||
### Consolidation of `create()` & `edit()` parameters
|
||||
|
||||
Various `create()` and `edit()` methods on managers and objects have had their parameters consolidated. The changes are below:
|
||||
|
||||
- `Guild#edit()` now takes `reason` in the `data` parameter
|
||||
- `GuildChannel#edit()` now takes `reason` in the `data` parameter
|
||||
- `GuildEmoji#edit()` now takes `reason` in the `data` parameter
|
||||
- `Role#edit()` now takes `reason` in the `data` parameter
|
||||
- `Sticker#edit()` now takes `reason` in the `data` parameter
|
||||
- `ThreadChannel#edit()` now takes `reason` in the `data` parameter
|
||||
- `GuildChannelManager#create()` now takes `name` in the `options` parameter
|
||||
- `GuildChannelManager#createWebhook()` (and other text-based channels) now takes `channel` and `name` in the `options` parameter
|
||||
- `GuildChannelManager#edit()` now takes `reason` as a part of `data`
|
||||
- `GuildEmojiManager#edit()` now takes `reason` as a part of `data`
|
||||
- `GuildManager#create()` now takes `name` as a part of `options`
|
||||
- `GuildMemberManager#edit()` now takes `reason` as a part of `data`
|
||||
- `GuildMember#edit()` now takes `reason` as a part of `data`
|
||||
- `GuildStickerManager#edit()` now takes `reason` as a part of `data`
|
||||
- `RoleManager#edit()` now takes `reason` as a part of `options`
|
||||
- `Webhook#edit()` now takes `reason` as a part of `options`
|
||||
- `GuildEmojiManager#create()` now takes `attachment` and `name` as a part of `options`
|
||||
- `GuildStickerManager#create()` now takes `file`, `name`, and `tags` as a part of `options`
|
||||
|
||||
### Activity
|
||||
|
||||
The following properties have been removed as they are not documented by Discord:
|
||||
|
||||
- `Activity#id`
|
||||
- `Activity#platform`
|
||||
- `Activity#sessionId`
|
||||
- `Activity#syncId`
|
||||
|
||||
### Application
|
||||
|
||||
`Application#fetchAssets()` has been removed as it is no longer supported by the API.
|
||||
|
||||
### BitField
|
||||
|
||||
- BitField constituents now have a `BitField` suffix to avoid naming conflicts with the enum names:
|
||||
|
||||
```js
|
||||
new Permissions(); // [!code --]
|
||||
new PermissionsBitField(); // [!code ++]
|
||||
|
||||
new MessageFlags(); // [!code --]
|
||||
new MessageFlagsBitField(); // [!code ++]
|
||||
|
||||
new ThreadMemberFlags(); // [!code --]
|
||||
new ThreadMemberFlagsBitField(); // [!code ++]
|
||||
|
||||
new UserFlags(); // [!code --]
|
||||
new UserFlagsBitField(); // [!code ++]
|
||||
|
||||
new SystemChannelFlags(); // [!code --]
|
||||
new SystemChannelFlagsBitField(); // [!code ++]
|
||||
|
||||
new ApplicationFlags(); // [!code --]
|
||||
new ApplicationFlagsBitField(); // [!code ++]
|
||||
|
||||
new Intents(); // [!code --]
|
||||
new IntentsBitField(); // [!code ++]
|
||||
|
||||
new ActivityFlags(); // [!code --]
|
||||
new ActivityFlagsBitField(); // [!code ++]
|
||||
```
|
||||
|
||||
- `#FLAGS` has been renamed to `#Flags`
|
||||
|
||||
### CDN
|
||||
|
||||
The methods that return CDN URLs have changed. Here is an example on a User:
|
||||
|
||||
```js
|
||||
const url = user.displayAvatarURL({ dynamic: true, format: 'png', size: 1_024 }); // [!code --]
|
||||
const url = user.displayAvatarURL({ extension: 'png', size: 1_024 }); // [!code ++]
|
||||
```
|
||||
|
||||
Dynamic URLs use `ImageURLOptions` and static URLs use `BaseImageURLOptions`. Since dynamic URLs are returned by default, this option has been renamed to `forceStatic` which forces the return of a static URL. Additionally, `format` has been renamed to `extension`.
|
||||
|
||||
### CategoryChannel
|
||||
|
||||
`CategoryChannel#children` is no longer a `Collection` of channels the category contains. It is now a manager (`CategoryChannelChildManager`). This also means `CategoryChannel#createChannel()` has been moved to the `CategoryChannelChildManager`.
|
||||
|
||||
### Channel
|
||||
|
||||
The following type guards have been removed:
|
||||
|
||||
- `Channel#isText()`
|
||||
- `Channel#isVoice()`
|
||||
- `Channel#isDirectory()`
|
||||
- `Channel#isDM()`
|
||||
- `Channel#isGroupDM()`
|
||||
- `Channel#isCategory()`
|
||||
- `Channel#isNews()`
|
||||
|
||||
Refer to [this section](#channels) for more context.
|
||||
|
||||
The base channel class is now `BaseChannel`.
|
||||
|
||||
### Client
|
||||
|
||||
The `restWsBridgeTimeout` client option has been removed.
|
||||
|
||||
### CommandInteractionOptionResolver
|
||||
|
||||
`CommandInteractionOptionResolver#getMember()` no longer has a parameter for `required`. See [this pull request](https://github.com/discordjs/discord.js/pull/7188) for more information.
|
||||
|
||||
### Constants
|
||||
|
||||
- Many constant objects and key arrays are now top-level exports for example:
|
||||
|
||||
```js
|
||||
const { Constants } = require('discord.js'); // [!code --]
|
||||
const { Colors } = Constants; // [!code --]
|
||||
const { Colors } = require('discord.js'); // [!code ++]
|
||||
```
|
||||
|
||||
- The refactored constants structures have `PascalCase` member names as opposed to `SCREAMING_SNAKE_CASE` member names.
|
||||
|
||||
- Many of the exported constants structures have been replaced and renamed:
|
||||
|
||||
```js
|
||||
Opcodes; // [!code --]
|
||||
GatewayOpcodes; // [!code ++]
|
||||
|
||||
WSEvents; // [!code --]
|
||||
GatewayDispatchEvents; // [!code ++]
|
||||
|
||||
WSCodes; // [!code --]
|
||||
GatewayCloseCodes; // [!code ++]
|
||||
|
||||
InviteScopes; // [!code --]
|
||||
OAuth2Scopes; // [!code ++]
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
The `message` and `interaction` events are now removed. Use `messageCreate` and `interactionCreate` instead.
|
||||
|
||||
`applicationCommandCreate`, `applicationCommandDelete` and `applicationCommandUpdate` have all been removed. See [this pull request](https://github.com/discordjs/discord.js/pull/6492) for more information.
|
||||
|
||||
The `threadMembersUpdate` event now emits the users who were added, the users who were removed, and the thread respectively.
|
||||
|
||||
### GuildBanManager
|
||||
|
||||
Developers should utilise `deleteMessageSeconds` instead of `days` and `deleteMessageDays`:
|
||||
|
||||
```js
|
||||
<GuildBanManager>.create('123456789', {
|
||||
days: 3 // [!code --]
|
||||
deleteMessageDays: 3 // [!code --]
|
||||
deleteMessageSeconds: 3 * 24 * 60 * 60 // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
`deleteMessageDays` (introduced with version 14) and `days` are both deprecated and will be removed in the future.
|
||||
|
||||
### Guild
|
||||
|
||||
`Guild#setRolePositions()` and `Guild#setChannelPositions()` have been removed. Use `RoleManager#setPositions()` and `GuildChannelManager#setPositions()` instead respectively.
|
||||
|
||||
`Guild#maximumPresences` no longer has a default value of 25,000.
|
||||
|
||||
`Guild#me` has been moved to `GuildMemberManager#me`. See [this pull request](https://github.com/discordjs/discord.js/pull/7669) for more information.
|
||||
|
||||
### GuildAuditLogs & GuildAuditLogsEntry
|
||||
|
||||
`GuildAuditLogs.build()` has been removed as it has been deemed defunct. There is no alternative.
|
||||
|
||||
The following properties & methods have been moved to the `GuildAuditLogsEntry` class:
|
||||
|
||||
- `GuildAuditLogs.Targets`
|
||||
- `GuildAuditLogs.actionType()`
|
||||
- `GuildAuditLogs.targetType()`
|
||||
|
||||
### GuildMember
|
||||
|
||||
`GuildMember#pending` is now nullable to account for partial guild members. See [this issue](https://github.com/discordjs/discord.js/issues/6546) for more information.
|
||||
|
||||
### IntegrationApplication
|
||||
|
||||
`IntegrationApplication#summary` has been removed as it is no longer supported by the API.
|
||||
|
||||
### Interaction
|
||||
|
||||
Whenever an interaction is replied to and one fetches the reply, it could possibly give an `APIMessage` if the guild was not cached. However, interaction replies now always return an `InteractionCallbackResponse` with `withResponse` set to `true`.
|
||||
|
||||
The base interaction class is now `BaseInteraction`.
|
||||
|
||||
### Invite
|
||||
|
||||
`Invite#inviter` is now a getter and resolves structures from the cache.
|
||||
|
||||
### MessageAttachment
|
||||
|
||||
`MessageAttachment` has now been renamed to `AttachmentBuilder`. // [!code --]
|
||||
|
||||
```js
|
||||
new MessageAttachment(buffer, 'image.png'); // [!code --]
|
||||
new AttachmentBuilder(buffer, { name: 'image.png' }); // [!code ++]
|
||||
```
|
||||
|
||||
### MessageComponent
|
||||
|
||||
- MessageComponents have been renamed as well. They no longer have the `Message` prefix, and now have a `Builder` suffix:
|
||||
|
||||
```js
|
||||
const button = new MessageButton(); // [!code --]
|
||||
const button = new ButtonBuilder(); // [!code ++]
|
||||
|
||||
const selectMenu = new MessageSelectMenu(); // [!code --]
|
||||
const selectMenu = new StringSelectMenuBuilder(); // [!code ++]
|
||||
|
||||
const actionRow = new MessageActionRow(); // [!code --]
|
||||
const actionRow = new ActionRowBuilder(); // [!code ++]
|
||||
|
||||
const textInput = new TextInputComponent(); // [!code --]
|
||||
const textInput = new TextInputBuilder(); // [!code ++]
|
||||
```
|
||||
|
||||
- Components received from the API are no longer directly mutable. If you wish to mutate a component from the API, use `ComponentBuilder#from`. For example, if you want to make a button mutable:
|
||||
|
||||
```js
|
||||
const editedButton = receivedButton // [!code --]
|
||||
.setDisabled(true); // [!code --]
|
||||
const { ButtonBuilder } = require('discord.js'); // [!code ++]
|
||||
const editedButton = ButtonBuilder.from(receivedButton) // [!code ++]
|
||||
.setDisabled(true); // [!code ++]
|
||||
```
|
||||
|
||||
### MessageManager
|
||||
|
||||
`MessageManager#fetch()`'s second parameter has been removed. The `BaseFetchOptions` the second parameter once was is now merged into the first parameter.
|
||||
|
||||
```js
|
||||
messageManager.fetch('1234567890', { cache: false, force: true }); // [!code --]
|
||||
messageManager.fetch({ message: '1234567890', cache: false, force: true }); // [!code ++]
|
||||
```
|
||||
|
||||
### MessageSelectMenu
|
||||
|
||||
- `MessageSelectMenu` has been renamed to `StringSelectMenuBuilder`
|
||||
- `StringSelectMenuBuilder#addOption()` has been removed. Use `StringSelectMenuBuilder#addOptions()` instead.
|
||||
|
||||
### MessageEmbed
|
||||
|
||||
- `MessageEmbed` has now been renamed to `EmbedBuilder`.
|
||||
- `EmbedBuilder#setAuthor()` now accepts a sole `EmbedAuthorOptions` object.
|
||||
- `EmbedBuilder#setFooter()` now accepts a sole `EmbedFooterOptions` object.
|
||||
- `EmbedBuilder#addField()` has been removed. Use `EmbedBuilder#addFields()` instead.
|
||||
|
||||
```js
|
||||
new MessageEmbed().addField('Inline field title', 'Some value here', true); // [!code --]
|
||||
new EmbedBuilder().addFields([ // [!code ++]
|
||||
{ name: 'one', value: 'one', inline: true }, // [!code ++]
|
||||
{ name: 'two', value: 'two', inline: true }, // [!code ++]
|
||||
+]);
|
||||
```
|
||||
|
||||
### Modal
|
||||
|
||||
- `Modal` has been renamed as well and now has a `Builder` suffix:
|
||||
|
||||
```js
|
||||
const modal = new Modal(); // [!code --]
|
||||
const modal = new ModalBuilder(); // [!code ++]
|
||||
```
|
||||
|
||||
### PartialTypes
|
||||
|
||||
The `PartialTypes` string array has been removed. Use the `Partials` enum instead.
|
||||
|
||||
In addition to this, there is now a new partial: `Partials.ThreadMember`.
|
||||
|
||||
### Permissions
|
||||
|
||||
Thread permissions `USE_PUBLIC_THREADS` and `USE_PRIVATE_THREADS` have been removed as they are deprecated in the API. Use `CREATE_PUBLIC_THREADS` and `CREATE_PRIVATE_THREADS` respectively.
|
||||
|
||||
`ManageEmojisAndStickers` has been deprecated due to API changes. Its replacement is `ManageGuildExpressions`. See [this pull request](https://github.com/discord/discord-api-docs/pull/6017) for more information.
|
||||
|
||||
### PermissionOverwritesManager
|
||||
|
||||
Overwrites are now keyed by the `PascalCase` permission key rather than the `SCREAMING_SNAKE_CASE` permission key.
|
||||
|
||||
### REST Events
|
||||
|
||||
#### apiRequest
|
||||
|
||||
This REST event has been removed as discord.js now uses [Undici](https://github.com/nodejs/undici) as the underlying request handler. You must now use a [Diagnostics Channel](https://undici.nodejs.org/#/docs/api/DiagnosticsChannel). Here is a simple example:
|
||||
|
||||
```js
|
||||
import diagnosticsChannel from 'node:diagnostics_channel';
|
||||
|
||||
diagnosticsChannel.channel('undici:request:create').subscribe((data) => {
|
||||
// If you use TypeScript, `data` may be casted as
|
||||
// `DiagnosticsChannel.RequestCreateMessage`
|
||||
// from Undici to receive type definitions.
|
||||
const { request } = data;
|
||||
console.log(request.method); // Log the method
|
||||
console.log(request.path); // Log the path
|
||||
console.log(request.headers); // Log the headers
|
||||
console.log(request); // Or just log everything!
|
||||
});
|
||||
```
|
||||
|
||||
You can find further examples at the [Undici Diagnostics Channel documentation](https://undici.nodejs.org/#/docs/api/DiagnosticsChannel).
|
||||
|
||||
#### apiResponse
|
||||
|
||||
This REST event has been renamed to `response` and moved to `Client#rest`:
|
||||
|
||||
```js
|
||||
client.on('apiResponse', ...); // [!code --]
|
||||
client.rest.on('response', ...); // [!code ++]
|
||||
```
|
||||
|
||||
#### invalidRequestWarning
|
||||
|
||||
This REST event has been moved to `Client#rest`:
|
||||
|
||||
```js
|
||||
client.on('invalidRequestWarning', ...); // [!code --]
|
||||
client.rest.on('invalidRequestWarning', ...); // [!code ++]
|
||||
```
|
||||
|
||||
#### rateLimit
|
||||
|
||||
This REST event has been renamed to `rateLimited` and moved to `Client#rest`:
|
||||
|
||||
```js
|
||||
client.on('rateLimit', ...); // [!code --]
|
||||
client.rest.on('rateLimited', ...); // [!code ++]
|
||||
```
|
||||
|
||||
### RoleManager
|
||||
|
||||
`Role.comparePositions()` has been removed. Use `RoleManager#comparePositions()` instead.
|
||||
|
||||
### Sticker
|
||||
|
||||
`Sticker#tags` is now a nullable string (`string | null`). Previously, it was a nullable array of strings (`string[] | null`). See [this pull request](https://github.com/discordjs/discord.js/pull/8010) for more information.
|
||||
|
||||
### ThreadChannel
|
||||
|
||||
The `MAX` helper used in `ThreadAutoArchiveDuration` has been removed. Discord has since allowed any guild to use any auto archive time which makes this helper redundant.
|
||||
|
||||
### ThreadMemberManager
|
||||
|
||||
`ThreadMemberManager#fetch()`'s second parameter has been removed. The `BaseFetchOptions` the second parameter once was is now merged into the first parameter. In addition, the boolean helper to specify `cache` has been removed.
|
||||
|
||||
Usage is now as follows:
|
||||
|
||||
```js
|
||||
// The second parameter is merged into the first parameter.
|
||||
threadMemberManager.fetch('1234567890', { cache: false, force: true }); // [!code --]
|
||||
threadMemberManager.fetch({ member: '1234567890', cache: false, force: true }); // [!code ++]
|
||||
|
||||
// The lone boolean has been removed. One must be explicit here.
|
||||
threadMemberManager.fetch(false); // [!code --]
|
||||
threadMemberManager.fetch({ cache: false }); // [!code ++]
|
||||
```
|
||||
|
||||
### Util
|
||||
|
||||
`Util.removeMentions()` has been removed. To control mentions, you should use `allowedMentions` on `BaseMessageOptions` instead.
|
||||
|
||||
`Util.splitMessage()` has been removed. This utility method is something the developer themselves should do.
|
||||
|
||||
`Util.resolveAutoArchiveMaxLimit()` has been removed. Discord has since allowed any guild to use any auto archive time which makes this method redundant.
|
||||
|
||||
Other functions in `Util` have been moved to top-level exports so you can directly import them from `discord.js`.
|
||||
|
||||
```js
|
||||
import { Util } from 'discord.js'; // [!code --]
|
||||
Util.escapeMarkdown(message); // [!code --]
|
||||
import { escapeMarkdown } from 'discord.js'; // [!code ++]
|
||||
escapeMarkdown(message); // [!code ++]
|
||||
```
|
||||
|
||||
### `.deleted` Field(s) have been removed
|
||||
|
||||
You can no longer use the `deleted` property to check if a structure was deleted. See [this issue](https://github.com/discordjs/discord.js/issues/7091) for more information.
|
||||
|
||||
### VoiceChannel
|
||||
|
||||
`VoiceChannel#editable` has been removed. You should use `GuildChannel#manageable` instead.
|
||||
|
||||
### VoiceRegion
|
||||
|
||||
`VoiceRegion#vip` has been removed as it is no longer part of the API.
|
||||
|
||||
### Webhook
|
||||
|
||||
`Webhook#fetchMessage()`'s second parameter no longer allows a boolean to be passed. The `cache` option in `WebhookFetchMessageOptions` should be used instead.
|
||||
|
||||
## Features
|
||||
|
||||
### ApplicationCommand
|
||||
|
||||
NFSW commands are supported.
|
||||
|
||||
### Attachment
|
||||
|
||||
Added support for voice message metadata fields.
|
||||
|
||||
### AutocompleteInteraction
|
||||
|
||||
`AutocompleteInteraction#commandGuildId` has been added which is the id of the guild the invoked application command is registered to.
|
||||
|
||||
### BaseChannel
|
||||
|
||||
Added support for `BaseChannel#flags`.
|
||||
|
||||
Store channels have been removed as they are no longer part of the API.
|
||||
|
||||
`BaseChannel#url` has been added which is a link to a channel, just like in the client.
|
||||
|
||||
Additionally, new typeguards have been added:
|
||||
|
||||
- `BaseChannel#isDMBased()`
|
||||
- `BaseChannel#isTextBased()`
|
||||
- `BaseChannel#isVoiceBased()`
|
||||
|
||||
### BaseInteraction
|
||||
|
||||
Added `BaseInteraction#isRepliable()` to check whether a given interaction can be replied to.
|
||||
|
||||
### ClientApplication
|
||||
|
||||
Added support for role connection metadata.
|
||||
|
||||
### Collection
|
||||
|
||||
- Added `Collection#merge()` and `Collection#combineEntries()`.
|
||||
- New type: `ReadonlyCollection` which indicates an immutable `Collection`.
|
||||
|
||||
### Collector
|
||||
|
||||
A new `ignore` event has been added which is emitted whenever an element is not collected by the collector.
|
||||
|
||||
Component collector options now use the `ComponentType` enum values:
|
||||
|
||||
```js
|
||||
const { ComponentType } = require('discord.js'); // [!code ++]
|
||||
|
||||
const collector = interaction.channel.createMessageComponentCollector({
|
||||
filter: collectorFilter,
|
||||
componentType: 'BUTTON', // [!code --]
|
||||
componentType: ComponentType.Button, // [!code ++]
|
||||
time: 20_000,
|
||||
});
|
||||
```
|
||||
|
||||
### CommandInteraction
|
||||
|
||||
`CommandInteraction#commandGuildId` has been added which is the id of the guild the invoked application command is registered to.
|
||||
|
||||
### CommandInteractionOptionResolver
|
||||
|
||||
`CommandInteractionOptionResolver#getChannel()` now has a third parameter which narrows the channel type.
|
||||
|
||||
### Events
|
||||
|
||||
Added support for `guildAuditLogEntryCreate` event.
|
||||
|
||||
### ForumChannel
|
||||
|
||||
Added support for forum channels.
|
||||
|
||||
Added support for `ForumChannel#defaultForumLayout`.
|
||||
|
||||
### Guild
|
||||
|
||||
Added `Guild#setMFALevel()` which sets the guild's MFA level.
|
||||
|
||||
Added `Guild#maxVideoChannelUsers` which indicates the maximum number of video channel users.
|
||||
|
||||
Added `Guild#maxStageVideoChannelUsers` which indicates the maximum number of video channel users for stage channels.
|
||||
|
||||
Added `Guild#disableInvites()` which disables the guild's invites.
|
||||
|
||||
Added support for the `after` parameter in `Guild#fetchAuditLogs()`.
|
||||
|
||||
### GuildChannelManager
|
||||
|
||||
`videoQualityMode` may be used whilst creating a channel to initially set the camera video quality mode.
|
||||
|
||||
### GuildEmojiManager
|
||||
|
||||
Added `GuildEmojiManager#delete()` and `GuildEmojiManager#edit()` for managing existing guild emojis.
|
||||
|
||||
### GuildForumThreadManager
|
||||
|
||||
Added `GuildForumThreadManager` as manager for threads in forum channels.
|
||||
|
||||
### GuildMember
|
||||
|
||||
Added support for `GuildMember#flags`.
|
||||
|
||||
### GuildMembersChunk
|
||||
|
||||
This object now supports the `GuildMembersChunk#notFound` property.
|
||||
|
||||
### GuildMemberManager
|
||||
|
||||
Added `GuildMemberManager#fetchMe()` to fetch the client user in the guild.
|
||||
|
||||
Added `GuildMemberManager#addRole()` and `GuildMemberManager#removeRole()`. These methods allow a single addition or removal of a role respectively to a guild member, even if uncached.
|
||||
|
||||
### GuildTextThreadManager
|
||||
|
||||
Added `GuildTextThreadManager` as manager for threads in text channels and announcement channels.
|
||||
|
||||
### Message
|
||||
|
||||
`Message#position` has been added as an approximate position in a thread.
|
||||
|
||||
Added support for role subscription data.
|
||||
|
||||
### MessageReaction
|
||||
|
||||
Added `MessageReaction#react()` to make the client user react with the reaction the class belongs to.
|
||||
|
||||
### Role
|
||||
|
||||
Added support for role subscriptions.
|
||||
|
||||
Added support for `Role#tags#guildConnections`.
|
||||
|
||||
### StageChannel
|
||||
|
||||
Stage channels now allow messages to be sent in them, much like voice channels.
|
||||
|
||||
### Sticker
|
||||
|
||||
Added support for GIF stickers.
|
||||
|
||||
### ThreadMemberManager
|
||||
|
||||
The new `withMember` options returns the associated guild member with the thread member.
|
||||
|
||||
When fetching multiple thread members alongside `withMember`, paginated results will be returned. The `after` and `limit` option are supported in this scenario.
|
||||
|
||||
### Webhook
|
||||
|
||||
Added `Webhook#applicationId`.
|
||||
|
||||
Added the `threadName` property in `Webhook#send()` options which allows a webhook to create a post in a forum channel.
|
||||
|
||||
### WebSocketManager
|
||||
|
||||
discord.js uses `@discordjs/ws` internally
|
||||
107
apps/guide/content/docs/legacy/additional-info/collections.mdx
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
title: Collections
|
||||
---
|
||||
|
||||
discord.js comes with a utility class known as `Collection`.
|
||||
It extends JavaScript's native `Map` class, so it has all the `Map` features and more!
|
||||
|
||||
<Callout type="warn">
|
||||
If you're not familiar with `Map`, read [MDN's page on
|
||||
it](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) before continuing. You
|
||||
should be familiar with `Array`
|
||||
[methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) as well. We will
|
||||
also use some ES6 features, so read up [here](./es6-syntax) if you do not know what they are.
|
||||
</Callout>
|
||||
|
||||
A `Map` allows for an association between unique keys and their values.
|
||||
For example, how can you transform every value or filter the entries in a `Map` easily?
|
||||
This is the point of the `Collection` class!
|
||||
|
||||
## Array-like Methods
|
||||
|
||||
Many of the methods on `Collection` correspond to their namesake in `Array`. One of them is `find`:
|
||||
|
||||
```js
|
||||
// Assume we have an array of users and a collection of the same users.
|
||||
array.find((u) => u.discriminator === '1000'); // [!code word:find]
|
||||
collection.find((u) => u.discriminator === '1000');
|
||||
```
|
||||
|
||||
The interface of the callback function is very similar between the two.
|
||||
For arrays, callbacks usually pass the parameters `(value, index, array)`, where `value` is the value iterated to,
|
||||
`index` is the current index, and `array` is the array. For collections, you would have `(value, key, collection)`.
|
||||
Here, `value` is the same, but `key` is the key of the value, and `collection` is the collection itself instead.
|
||||
|
||||
Methods that follow this philosophy of staying close to the `Array` interface are as follows:
|
||||
|
||||
- `find`
|
||||
- `filter` - Note that this returns a `Collection` rather than an `Array`.
|
||||
- `map` - Yet this returns an `Array` of values instead of a `Collection`!
|
||||
- `every`
|
||||
- `some`
|
||||
- `reduce`
|
||||
- `concat`
|
||||
- `sort`
|
||||
|
||||
## Converting to Array
|
||||
|
||||
Since `Collection` extends `Map`, it is an [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols), and can be converted to an `Array` through either `Array.from()` or spread syntax (`...collection`).
|
||||
|
||||
```js
|
||||
// For values.
|
||||
Array.from(collection.values());
|
||||
[...collection.values()];
|
||||
|
||||
// For keys.
|
||||
Array.from(collection.keys());
|
||||
[...collection.keys()];
|
||||
|
||||
// For [key, value] pairs.
|
||||
Array.from(collection);
|
||||
[...collection];
|
||||
```
|
||||
|
||||
<Callout>
|
||||
Many people convert Collections to Arrays way too much!
|
||||
|
||||
This can lead to unnecessary and confusing code. Before you use `Array.from()` or similar, ask yourself if whatever you are trying to do can't be done with the given `Map` or `Collection` methods or with a for-of loop. Not being familiar with a new data structure should not mean you default to transforming it into the other.
|
||||
|
||||
There is usually a reason, why a `Map` or `Collection` is used. Most structures in Discord can be identified with an `id`, which lends itself well to `key -> value` associations like in `Map`s.
|
||||
|
||||
</Callout>
|
||||
|
||||
## Extra Utilities
|
||||
|
||||
Some methods are not from `Array` and are instead entirely new to standard JavaScript.
|
||||
|
||||
```js
|
||||
// A random value.
|
||||
collection.random();
|
||||
|
||||
// The first value.
|
||||
collection.first();
|
||||
|
||||
// The first 5 values.
|
||||
collection.first(5);
|
||||
|
||||
// Similar to `first`, but from the end.
|
||||
collection.last();
|
||||
collection.last(2);
|
||||
|
||||
// Removes anything that meets the condition from the collection.
|
||||
// Sort of like `filter`, but in-place.
|
||||
collection.sweep((user) => user.username === 'Bob');
|
||||
```
|
||||
|
||||
A more complicated method is `partition`, which splits a single Collection into two new Collections based on the provided function.
|
||||
You can think of it as two `filter`s, but done at the same time (and because of that much more performant):
|
||||
|
||||
```js
|
||||
// `bots` is a Collection of users where their `bot` property was true.
|
||||
// `humans` is a Collection where the property was false instead!
|
||||
const [bots, humans] = collection.partition((u) => u.bot); // [!code word:partition]
|
||||
|
||||
// Both return true.
|
||||
bots.every((b) => b.bot);
|
||||
humans.every((h) => !h.bot); // note the "not" ! operator
|
||||
```
|
||||
243
apps/guide/content/docs/legacy/additional-info/es6-syntax.mdx
Normal file
@@ -0,0 +1,243 @@
|
||||
---
|
||||
title: ES6 Syntax
|
||||
---
|
||||
|
||||
If you've used JavaScript for only a (relatively) small amount of time or don't have much experience with it, you might not be aware of what ES6 is and what beneficial features it includes. Since this is a guide primarily for Discord bots, we'll be using some discord.js code as an example of what you might have versus what you could do to benefit from ES6.
|
||||
|
||||
Here's the startup code we'll be using:
|
||||
|
||||
```js title="index.js" lineNumbers
|
||||
const { Client, Events, GatewayIntentBits } = require('discord.js'); // [!code word:const]
|
||||
const config = require('./config.json');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
// [!code word:=>]
|
||||
client.once(Events.ClientReady, () => {
|
||||
console.log('Ready!');
|
||||
});
|
||||
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const { commandName } = interaction;
|
||||
|
||||
if (commandName === 'ping') {
|
||||
interaction.reply('Pong.');
|
||||
} else if (commandName === 'beep') {
|
||||
interaction.reply('Boop.');
|
||||
} else if (commandName === 'server') {
|
||||
interaction.reply('Guild name: ' + interaction.guild.name + '\nTotal members: ' + interaction.guild.memberCount);
|
||||
} else if (commandName === 'user-info') {
|
||||
interaction.reply('Your username: ' + interaction.user.username + '\nYour ID: ' + interaction.user.id);
|
||||
}
|
||||
});
|
||||
|
||||
client.login(config.token);
|
||||
```
|
||||
|
||||
If you haven't noticed, this piece of code is already using a bit of ES6 here! The `const` keyword and arrow function declaration (`() => ...`) is ES6 syntax, and we recommend using it whenever possible.
|
||||
|
||||
As for the code above, there are a few places where things can be done better. Let's look at them.
|
||||
|
||||
## Template literals
|
||||
|
||||
If you check the code above, it's currently doing things like `'Guild name: ' + interaction.guild.name` and `'Your username: ' + interaction.user.username`, which is perfectly valid. It is a bit hard to read, though, and it's not too fun to constantly type out. Fortunately, there's a better alternative.
|
||||
|
||||
```js title="index.js" lineNumbers=19
|
||||
} else if (commandName === 'server') {
|
||||
interaction.reply('Guild name: ' + interaction.guild.name + '\nTotal members: ' + interaction.guild.memberCount); // [!code --]
|
||||
interaction.reply(`Guild name: ${interaction.guild.name}\nTotal members: ${interaction.guild.memberCount}`); // [!code ++]
|
||||
}
|
||||
else if (commandName === 'user-info') {
|
||||
interaction.reply('Your username: ' + interaction.user.username + '\nYour ID: ' + interaction.user.id); // [!code --]
|
||||
interaction.reply(`Your username: ${interaction.user.username}\nYour ID: ${interaction.user.id}`); // [!code ++]
|
||||
}
|
||||
```
|
||||
|
||||
Easier to read and write! The best of both worlds.
|
||||
|
||||
### Template literals vs string concatenation
|
||||
|
||||
If you've used other programming languages, you might be familiar with the term "string interpolation". Template literals would be JavaScript's implementation of string interpolation. If you're familiar with the heredoc syntax, it's very much like that; it allows for string interpolation, as well as multiline strings.
|
||||
|
||||
The example below won't go too much into detail about it, but if you're interested in reading more, you can [read about them on MDN](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals).
|
||||
|
||||
```js
|
||||
const username = 'Sanctuary';
|
||||
const password = 'pleasedonthackme';
|
||||
|
||||
function letsPretendThisDoesSomething() {
|
||||
return 'Yay for sample data.';
|
||||
}
|
||||
|
||||
console.log('Your username is: **' + username + '**.'); // [!code --:2]
|
||||
console.log('Your password is: **' + password + '**.');
|
||||
console.log(`Your username is: **${username}**.`); // [!code ++:2]
|
||||
console.log(`Your password is: **${password}**.`);
|
||||
|
||||
console.log('1 + 1 = ' + (1 + 1)); // [!code --]
|
||||
console.log(`1 + 1 = ${1 + 1}`); // [!code ++]
|
||||
|
||||
console.log("And here's a function call: " + letsPretendThisDoesSomething()); // [!code --]
|
||||
console.log(`And here's a function call: ${letsPretendThisDoesSomething()}`); // [!code ++]
|
||||
|
||||
console.log('Putting strings on new lines\n' + 'can be a bit painful\n' + 'with string concatenation.'); // [!code --]
|
||||
// [!code ++:5]
|
||||
console.log(`
|
||||
Putting strings on new lines
|
||||
is a breeze
|
||||
with template literals!
|
||||
`);
|
||||
```
|
||||
|
||||
<Callout>
|
||||
As you will notice, template literals will also render the white space inside them, including the indentation! There
|
||||
are ways around this, which we will discuss in another section.
|
||||
</Callout>
|
||||
|
||||
You can see how it makes things easier and more readable. In some cases, it can even make your code shorter! This one is something you'll want to take advantage of as much as possible.
|
||||
|
||||
## Arrow functions
|
||||
|
||||
Arrow functions are shorthand for regular functions, with the addition that they use a lexical `this` context inside of their own. If you don't know what the `this` keyword is referring to, don't worry about it; you'll learn more about it as you advance.
|
||||
|
||||
Here are some examples of ways you can benefit from arrow functions over regular functions:
|
||||
|
||||
```js
|
||||
// [!code --:3]
|
||||
client.once(Events.ClientReady, function () {
|
||||
console.log('Ready!');
|
||||
});
|
||||
client.once(Events.ClientReady, () => console.log('Ready!')); // [!code ++]
|
||||
|
||||
// [!code --:3]
|
||||
client.on(Events.TypingStart, function (typing) {
|
||||
console.log(typing.user.tag + ' started typing in #' + typing.channel.name);
|
||||
});
|
||||
client.on(Events.TypingStart, (typing) => console.log(`${typing.user.tag} started typing in #${typing.channel.name}`)); // [!code ++]
|
||||
|
||||
// [!code --:3]
|
||||
client.on(Events.MessageCreate, function (message) {
|
||||
console.log(message.author.tag + ' sent: ' + message.content);
|
||||
});
|
||||
client.on(Events.MessageCreate, (message) => console.log(`${message.author.tag} sent: ${message.content}`)); // [!code ++]
|
||||
|
||||
// [!code --:3]
|
||||
var doubleAge = function (age) {
|
||||
return 'Your age doubled is: ' + age * 2;
|
||||
};
|
||||
const doubleAge = (age) => `Your age doubled is: ${age * 2}`; // [!code ++]
|
||||
|
||||
// [!code --:4]
|
||||
var collectorFilter = function (m) {
|
||||
return m.content === 'I agree' && !m.author.bot;
|
||||
};
|
||||
var collector = message.createMessageCollector({ filter: collectorFilter, time: 15_000 });
|
||||
const collectorFilter = (m) => m.content === 'I agree' && !m.author.bot; // [!code ++:2]
|
||||
const collector = message.createMessageCollector({ filter: collectorFilter, time: 15_000 });
|
||||
```
|
||||
|
||||
There are a few important things you should note here:
|
||||
|
||||
- The parentheses around function parameters are optional when you have only one parameter but are required otherwise. If you feel like this will confuse you, it may be a good idea to use parentheses.
|
||||
- You can cleanly put what you need on a single line without curly braces.
|
||||
- Omitting curly braces will make arrow functions use **implicit return**, but only if you have a single-line expression. The `doubleAge` and `filter` variables are a good example of this.
|
||||
- Unlike the `function someFunc() { ... }` declaration, arrow functions cannot be used to create functions with such syntax. You can create a variable and give it an anonymous arrow function as the value, though (as seen with the `doubleAge` and `filter` variables).
|
||||
|
||||
We won't be covering the lexical `this` scope with arrow functions in here, but you can Google around if you're still curious. Again, if you aren't sure what `this` is or when you need it, reading about lexical `this` first may only confuse you.
|
||||
|
||||
## Destructuring
|
||||
|
||||
Destructuring is an easy way to extract items from an object or array. If you've never seen the syntax for it before, it can be a bit confusing, but it's straightforward to understand once explained!
|
||||
|
||||
### Object destructuring
|
||||
|
||||
Here's a common example where object destructuring would come in handy:
|
||||
|
||||
```js
|
||||
const config = require('./config.json');
|
||||
const prefix = config.prefix;
|
||||
const token = config.token;
|
||||
```
|
||||
|
||||
This code is a bit verbose and not the most fun to write out each time. Object destructuring simplifies this, making it easier to both read and write. Take a look:
|
||||
|
||||
```js
|
||||
const config = require('./config.json'); // [!code --:3]
|
||||
const prefix = config.prefix;
|
||||
const token = config.token;
|
||||
const { prefix, token } = require('./config.json'); // [!code ++]
|
||||
```
|
||||
|
||||
Object destructuring takes those properties from the object and stores them in variables. If the property doesn't exist, it'll still create a variable but with the value of `undefined`. So instead of using `config.token` in your `client.login()` method, you'd simply use `token`. And since destructuring creates a variable for each item, you don't even need that `const prefix = config.prefix` line. Pretty cool!
|
||||
|
||||
Additionally, you could do this for your commands:
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
const { commandName } = interaction;
|
||||
|
||||
if (commandName === 'ping') {
|
||||
// ping command here...
|
||||
} else if (commandName === 'beep') {
|
||||
// beep command here...
|
||||
}
|
||||
// other commands here...
|
||||
});
|
||||
```
|
||||
|
||||
The code is shorter and looks cleaner, but it shouldn't be necessary if you follow along with the [command handler](../app-creation/handling-commands) part of the guide.
|
||||
|
||||
You can also rename variables when destructuring, if necessary. A good example is when you're extracting a property with a name already being used or conflicts with a reserved keyword. The syntax is as follows:
|
||||
|
||||
```js
|
||||
// `default` is a reserved keyword
|
||||
const { default: defaultValue } = someObject;
|
||||
|
||||
console.log(defaultValue);
|
||||
// 'Some default value here'
|
||||
```
|
||||
|
||||
### Array destructuring
|
||||
|
||||
Array destructuring syntax is very similar to object destructuring, except that you use brackets instead of curly braces. In addition, since you're using it on an array, you destructure the items in the same order the array is. Without array destructuring, this is how you'd extract items from an array:
|
||||
|
||||
```js
|
||||
// assuming we're in a `profile` command and have an `args` variable
|
||||
const name = args[0];
|
||||
const age = args[1];
|
||||
const location = args[2];
|
||||
```
|
||||
|
||||
Like the first example with object destructuring, this is a bit verbose and not fun to write out. Array destructuring eases this pain.
|
||||
|
||||
```js
|
||||
const name = args[0]; // [!code --:3]
|
||||
const age = args[1];
|
||||
const location = args[2];
|
||||
const [name, age, location] = args; // [!code ++]
|
||||
```
|
||||
|
||||
A single line of code that makes things much cleaner! In some cases, you may not even need all the array's items (e.g., when using `string.match(regex)`). Array destructuring still allows you to operate in the same sense.
|
||||
|
||||
```js
|
||||
const [, username, id] = message.content.match(someRegex);
|
||||
```
|
||||
|
||||
In this snippet, we use a comma without providing a name for the item in the array we don't need. You can also give it a placeholder name (`_match` or similar) if you prefer, of course; it's entirely preference at that point.
|
||||
|
||||
<Callout>
|
||||
The underscore `_` prefix is a convention for unused variables. Some lint rules will error or warn if you define
|
||||
identifiers without using them in your code but ignore identifiers starting with `_`.
|
||||
</Callout>
|
||||
|
||||
## var, let, and const
|
||||
|
||||
Since there are many, many articles out there that can explain this part more in-depth, we'll only be giving you a TL;DR and an article link if you choose to read more about it.
|
||||
|
||||
1. The `var` keyword is what was (and can still be) used in JavaScript before `let` and `const` came to surface. There are many issues with `var`, though, such as it being function-scoped, hoisting related issues, and allowing redeclaration.
|
||||
2. The `let` keyword is essentially the new `var`; it addresses many of the issues `var` has, but its most significant factor would be that it's block-scoped and disallows redeclaration (_not_ reassignment).
|
||||
3. The `const` keyword is for giving variables a constant value that is unable to be reassigned. `const`, like `let`, is also block-scoped.
|
||||
|
||||
The general rule of thumb recommended by this guide is to use `const` wherever possible, `let` otherwise, and avoid using `var`. Here's a [helpful article](https://madhatted.com/2016/1/25/let-it-be) if you want to read more about this subject.
|
||||
BIN
apps/guide/content/docs/legacy/additional-info/images/search.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
apps/guide/content/docs/legacy/additional-info/images/send.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
3
apps/guide/content/docs/legacy/additional-info/meta.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pages": ["async-await", "collections", "es6-syntax", "notation", "rest-api"]
|
||||
}
|
||||
61
apps/guide/content/docs/legacy/additional-info/notation.mdx
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Understanding Notation
|
||||
---
|
||||
|
||||
Throughout the discord.js docs and when asking for help on the official server, you will run into many different kinds of notations. To help you understand the texts that you read, we will be going over some standard notations.
|
||||
|
||||
<Callout>
|
||||
Always keep in mind that notation is not always rigorous. There will be typos, misunderstandings, or contexts that
|
||||
will cause notation to differ from the usual meanings.
|
||||
</Callout>
|
||||
|
||||
## Classes
|
||||
|
||||
Some common notations refer to a class or the properties, methods, or events of a class. There are many variations on these notations, and they are very flexible depending on the person, so use your best judgment when reading them.
|
||||
|
||||
The notation `<Class>` means an instance of the `Class` class. For example, a snippet like `<BaseInteraction>.reply('Hello')` is asking you to replace `<BaseInteraction>` with some value that is an instance of `BaseInteraction`, e.g. `interaction.reply('Hello')`. It could also just be a placeholder, e.g., `<id>` would mean a placeholder for some ID.
|
||||
|
||||
The notation `Class#foo` can refer to the `foo` property, method, or event of the `Class` class. Which one the writer meant needs to be determined from context. For example:
|
||||
|
||||
- `BaseInteraction#user` means that you should refer to the `user` property on a `BaseInteraction`.
|
||||
- `TextChannel#send` means that you should refer to the `send` method on a `TextChannel`.
|
||||
- `Client#interactionCreate` means that you should refer to the `interactionCreate` event on a `Client`.
|
||||
|
||||
<Callout>
|
||||
Remember that this notation is not valid JavaScript; it is a shorthand to refer to a specific piece of code.
|
||||
</Callout>
|
||||
|
||||
Sometimes, the notation is extended, which can help you determine which one the writer meant. For example, `TextChannel#send(options)` is definitely a method of `TextChannel`, since it uses function notation. `Client#event:messageCreate` is an event since it says it is an event.
|
||||
|
||||
The vital thing to take away from this notation is that the `#` symbol signifies that the property, method, or event can only be accessed through an instance of the class. Unfortunately, many abuse this notation, e.g., `<Message>#send` or `Util#resolveColor`. `<Message>` is already an instance, so this makes no sense, and `resolveColor` is a static method–you should write it as `Util.resolveColor`. Always refer back to the docs if you are confused.
|
||||
|
||||
As an example, the documentation's search feature uses this notation.
|
||||
|
||||

|
||||
|
||||
Notice the use of the `.` operator for the static method, `Role.comparePositions` and the `#` notation for the method, `Role#comparePositionsTo`.
|
||||
|
||||
## Types
|
||||
|
||||
In the discord.js docs, there are type signatures everywhere, such as in properties, parameters, or return values. If you do not come from a statically typed language, you may not know what specific notations mean.
|
||||
|
||||
The symbol `*` means any type. For example, methods that return `*` mean that they can return anything, and a parameter of type `*` can be anything.
|
||||
|
||||
The symbol `?` means that the type is nullable. You can see it before or after the type (e.g. `?T` or `T?`). This symbol means that the value can be of the type `T` or `null`. An example is `GuildMember#nickname`; its type is `?string` since a member may or may not have a nickname.
|
||||
|
||||
The expression `T[]` means an array of `T`. You can sometimes see multiple brackets `[]`, indicating that the array is multi-dimensional, e.g., `string[][]`.
|
||||
|
||||
The expression `...T` signifies a rest parameter of type `T`. This means that the function can take any amount of arguments, and all those arguments must be of the type `T`.
|
||||
|
||||
The operator `|`, which can read as "or", creates a union type, e.g. `A|B|C`. Simply, it means the value can be of any one of the types given.
|
||||
|
||||
The angle brackets `<>` are used for generic types or parameterized types, signifying a type that uses another type(s). The notation looks like `A<B>` where `A` is the type and `B` is a type parameter. If this is hard to follow, it is enough to keep in mind that whenever you see `A<B>`, you can think of an `A` containing `B`. Examples:
|
||||
|
||||
- `Array<String>` means an array of strings.
|
||||
- `Promise<User>` means a `Promise` that contains a `User`.
|
||||
- `Array<Promise<User|GuildMember>>` would be an array of `Promise`s, each containing a `User` or a `GuildMember`.
|
||||
- `Collection<Snowflake, User>` would be a `Collection`, containing key-value pairs where the keys are `Snowflake`s, and the values are `User`s.
|
||||
|
||||

|
||||
|
||||
In this piece of the docs, you can see two type signatures, `string`, `MessagePayload`, or `MessageOptions`, and `Promise<(Message|Array<Message>)>`. The meaning of the word "or" here is the same as `|`.
|
||||
176
apps/guide/content/docs/legacy/additional-info/rest-api.mdx
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: REST APIs
|
||||
---
|
||||
|
||||
REST APIs are extremely popular on the web and allow you to freely grab a site's data if it has an available API over an HTTP connection.
|
||||
|
||||
## Making HTTP requests with Node
|
||||
|
||||
In these examples, we will be using [undici](https://www.npmjs.com/package/undici), an excellent library for making HTTP requests.
|
||||
|
||||
To install undici, run the following command:
|
||||
|
||||
```sh tab="npm"
|
||||
npm i install undici
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add undici
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add undici
|
||||
```
|
||||
|
||||
## Skeleton code
|
||||
|
||||
To start off, you will be using the following skeleton code. Since both the commands you will be adding in this section require an interaction with external APIs, you will defer the reply, so your application responds with a "thinking..." state. You can then edit the reply once you got the data you need:
|
||||
|
||||
```js title="rest-examples.js" lineNumbers
|
||||
const { Client, EmbedBuilder, Events, GatewayIntentBits } = require('discord.js');
|
||||
|
||||
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, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const { commandName } = interaction;
|
||||
await interaction.deferReply();
|
||||
// ...
|
||||
});
|
||||
|
||||
client.login('your-token-goes-here');
|
||||
```
|
||||
|
||||
<Callout>
|
||||
We're taking advantage of [destructuring](./es6-syntax#destructuring) in this tutorial to maintain readability.
|
||||
</Callout>
|
||||
|
||||
## Using undici
|
||||
|
||||
Undici is a Promise-based HTTP/1.1 client, written from scratch for Node.js. If you aren't already familiar with Promises, you should read up on them [here](./async-await).
|
||||
|
||||
In this tutorial, you will be making a bot with two API-based commands using the [random.cat](https://aws.random.cat) and [Urban Dictionary](https://www.urbandictionary.com) APIs.
|
||||
|
||||
On top of your file, import the library function you will be using:
|
||||
|
||||
```js
|
||||
const { request } = require('undici');
|
||||
```
|
||||
|
||||
### Random Cat
|
||||
|
||||
<Callout title="No more cats :(" type="error">
|
||||
Unfortunately, the `aws.random.cat` API doesn't work anymore. We will keep the example as-is until we find a better
|
||||
showcase!
|
||||
</Callout>
|
||||
|
||||
Random cat's API is available at [https://aws.random.cat/meow](https://aws.random.cat/meow) and returns a [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) response. To actually fetch data from the API, you're going to do the following:
|
||||
|
||||
```js
|
||||
const catResult = await request('https://aws.random.cat/meow');
|
||||
const { file } = await catResult.body.json();
|
||||
```
|
||||
|
||||
If you just add this code, it will seem like nothing happens. What you do not see, is that you are launching a request to the random.cat server, which responds some JSON data. The helper function parses the response data to a JavaScript object you can work with. The object will have a `file` property with the value of a link to a random cat image.
|
||||
|
||||
Next, you will implement this approach into an application command:
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
// ...
|
||||
if (commandName === 'cat') {
|
||||
const catResult = await request('https://aws.random.cat/meow');
|
||||
const { file } = await catResult.body.json();
|
||||
interaction.editReply({ files: [file] });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
So, here's what's happening in this code:
|
||||
|
||||
1. Your application sends a `GET` request to random.cat.
|
||||
2. random.cat sees the request and gets a random file url from their database.
|
||||
3. random.cat then sends that file's URL as a JSON object in a stringified form that contains a link to the image.
|
||||
4. undici receives the response and you parse the body to a JSON object.
|
||||
5. Your application then attaches the image and sends it in Discord.
|
||||
|
||||
### Urban Dictionary
|
||||
|
||||
Urban Dictionary's API is available at [https://api.urbandictionary.com/v0/define](https://api.urbandictionary.com/v0/define), accepts a `term` parameter, and returns a JSON response.
|
||||
|
||||
The following code will fetch data from this api:
|
||||
|
||||
```js
|
||||
// ...
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
// ...
|
||||
if (commandName === 'urban') {
|
||||
const term = interaction.options.getString('term');
|
||||
const query = new URLSearchParams({ term }); // [!code word:URLSearchParams]
|
||||
|
||||
const dictResult = await request(`https://api.urbandictionary.com/v0/define?${query}`);
|
||||
const { list } = await dictResult.body.json();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Here, you are using JavaScript's native [URLSearchParams class](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) to create a [query string](https://en.wikipedia.org/wiki/Query_string) for the URL so that the Urban Dictionary server can parse it and know what you want to look up.
|
||||
|
||||
If you were to do `/urban hello world`, then the URL would become https://api.urbandictionary.com/v0/define?term=hello%20world since the string `"hello world"` is encoded.
|
||||
|
||||
You can get the respective properties from the returned JSON. If you were to view it in your browser, it usually looks like a bunch of mumbo jumbo. If it doesn't, great! If it does, then you should get a JSON formatter/viewer. If you're using Chrome, [JSON Formatter](https://chrome.google.com/webstore/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa) is one of the more popular extensions. If you're not using Chrome, search for "JSON formatter/viewer <your browser>" and get one.
|
||||
|
||||
Now, if you look at the JSON, you can see that it has a `list` property, which is an array of objects containing various definitions for the term (maximum 10). Something you always want to do when making API-based commands is to handle the case when no results are available. So, if you throw a random term in there (e.g. `njaksdcas`) and then look at the response the `list` array should be empty. Now you are ready to start writing!
|
||||
|
||||
As explained above, you'll want to check if the API returned any answers for your query, and send back the definition if that's the case:
|
||||
|
||||
```js
|
||||
if (commandName === 'urban') {
|
||||
// ...
|
||||
if (!list.length) {
|
||||
return interaction.editReply(`No results found for **${term}**.`);
|
||||
}
|
||||
|
||||
interaction.editReply(`**${term}**: ${list[0].definition}`);
|
||||
}
|
||||
```
|
||||
|
||||
Here, you are only getting the first object from the array of objects called `list` and grabbing its `definition` property.
|
||||
|
||||
If you've followed the tutorial, you should have something like this:
|
||||
|
||||
Now, you can make it an [embed](../popular-topics/embeds) for easier formatting.
|
||||
|
||||
You can define the following helper function at the top of your file. In the code below, you can use this function to truncate the returned data and make sure the embed doesn't error, because field values exceed 1024 characters.
|
||||
|
||||
```js
|
||||
const trim = (str, max) => (str.length > max ? `${str.slice(0, max - 3)}...` : str);
|
||||
```
|
||||
|
||||
And here is how you can build the embed from the API data:
|
||||
|
||||
```js
|
||||
const [answer] = list;
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xefff00)
|
||||
.setTitle(answer.word)
|
||||
.setURL(answer.permalink)
|
||||
.addFields(
|
||||
{ name: 'Definition', value: trim(answer.definition, 1_024) },
|
||||
{ name: 'Example', value: trim(answer.example, 1_024) },
|
||||
{ name: 'Rating', value: `${answer.thumbs_up} thumbs up. ${answer.thumbs_down} thumbs down.` },
|
||||
);
|
||||
|
||||
interaction.editReply({ embeds: [embed] });
|
||||
```
|
||||
|
||||
<Callout>
|
||||
Check out display components for a newer approach to message formatting! You can read the [display
|
||||
components](../popular-topics/display-components) section of this guide to learn more about using them!
|
||||
</Callout>
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
title: Creating slash commands
|
||||
---
|
||||
|
||||
## Creating slash commands
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import { File, Folder, Files } from 'fumadocs-ui/components/files';
|
||||
|
||||
Discord allows developers to register [slash commands](https://discord.com/developers/docs/interactions/application-commands), which provide users a first-class way of interacting directly with your application.
|
||||
|
||||
Slash commands provide a huge number of benefits over manual message parsing, including:
|
||||
|
||||
- Integration with the Discord client interface.
|
||||
- Automatic command detection and parsing of the associated options/arguments.
|
||||
- Typed argument inputs for command options, e.g. "String", "User", or "Role".
|
||||
- Validated or dynamic choices for command options.
|
||||
- In-channel private responses (ephemeral messages).
|
||||
- Pop-up form-style inputs for capturing additional information.
|
||||
|
||||
...and many more!
|
||||
|
||||
## Before you continue
|
||||
|
||||
Assuming you've followed the guide so far, your project directory should look something like this:
|
||||
|
||||
<Files>
|
||||
<Folder name="discord-bot" defaultOpen>
|
||||
<Folder name="node_modules" defaultOpen />
|
||||
<File name="config.json" />
|
||||
<File name="index.js" />
|
||||
<File name="package-lock.json" />
|
||||
<File name="package.json" />
|
||||
</Folder>
|
||||
</Files>
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
|
||||
### Command Files
|
||||
|
||||
The individual command files, containing slash command definitions and functionality.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Handler
|
||||
|
||||
The [command handler](./handling-commands), dynamically reads the command files and executes commands.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Deployment
|
||||
|
||||
The command [deployment script](./deploying-commands) to register your slash commands with Discord.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
These steps can be followed in any order, but are all required to make your bot work. This page details step **1**. Make sure you also check out the other linked pages.
|
||||
|
||||
## Individual command files
|
||||
|
||||
Create a new folder named `commands` and a subfolder named `utility` inside it, which is where you'll store all of your utility command files. You'll be using the class to construct the command definitions.
|
||||
|
||||
At a minimum, the definition of a slash command must have a name and a description. Slash command names must be between 1-32 characters and contain no capital letters, spaces, or symbols other than `-` and `_`. Using the builder, a simple `ping` command definition would look like this:
|
||||
|
||||
```js
|
||||
new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!');
|
||||
```
|
||||
|
||||
A slash command also requires a function to run when the command is used, to respond to the interaction. Using an interaction response method confirms to Discord that your bot successfully received the interaction, and has responded to the user. Discord enforces this to ensure that all slash commands provide a good user experience (UX). Failing to respond will cause Discord to show that the command failed, even if your bot is performing other actions as a result.
|
||||
|
||||
The simplest way to acknowledge and respond to an interaction is the `interaction.reply()` method. Other methods of replying are covered on the [Response methods](../slash-commands/response-methods) page later in this section.
|
||||
|
||||
```js
|
||||
async execute(interaction) {
|
||||
await interaction.reply('Pong!')
|
||||
}
|
||||
```
|
||||
|
||||
Put these two together by creating a `ping.js` file in the `commands/utility` folder for your first command. Inside this file, you're going to define and export two items.
|
||||
|
||||
- The `data` property, which will provide the command definition shown above for registering to Discord.
|
||||
- The `execute` method, which will contain the functionality to run from our event handler when the command is used.
|
||||
|
||||
These are placed inside `module.exports` so they can be read by other files; namely the command loader and command deployment scripts mentioned earlier.
|
||||
|
||||
```js title="commands/utility/ping.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'),
|
||||
async execute(interaction) {
|
||||
await interaction.reply('Pong!');
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout>
|
||||
[`module.exports`](https://nodejs.org/api/modules.html#modules_module_exports) is how you export data in Node.js so that you can [`require()`](https://nodejs.org/api/modules.html#modules_require_id) it in other files.
|
||||
|
||||
If you need to access your client instance from inside a command file, you can access it via `interaction.client`. If you need to access external files, packages, etc., you should `require()` them at the top of the file.
|
||||
|
||||
</Callout>
|
||||
|
||||
That's it for your basic ping command. Below are examples of two more commands we're going to build upon throughout the guide, so create two more files for these before you continue reading.
|
||||
|
||||
```js tab="User" title="commands/utility/user.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder().setName('user').setDescription('Provides information about the user.'),
|
||||
async execute(interaction) {
|
||||
// interaction.user is the object representing the User who ran the command
|
||||
// interaction.member is the GuildMember object, which represents the user in the specific guild
|
||||
await interaction.reply(
|
||||
`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`,
|
||||
);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```js tab="Server" title="commands/utility/server.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder().setName('server').setDescription('Provides information about the server.'),
|
||||
async execute(interaction) {
|
||||
// interaction.guild is the object representing the Guild in which the command was run
|
||||
await interaction.reply(
|
||||
`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`,
|
||||
);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### Next steps
|
||||
|
||||
You can implement additional commands by creating new files within a dedicated subfolder in the `commands` folder, but these three are the ones we're going to use for the examples as we go on. For now let's move on to the code you'll need for command handling, to load the files and respond to incoming interactions.
|
||||
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: Registering Commands
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
|
||||
For fully functional slash commands, you need three important pieces of code:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
|
||||
### Command Files
|
||||
|
||||
The individual command files, containing [slash command](./creating-commands) definitions and functionality.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Handler
|
||||
|
||||
The [command handler](./handling-commands), dynamically reads the command files and executes commands.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Deployment
|
||||
|
||||
The command deployment script to register your slash commands with Discord.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
These steps can be followed in any order, but are all required to make your bot work. This page details step **3**. Make sure you also check out the other linked pages.
|
||||
|
||||
## Command registration
|
||||
|
||||
Slash commands can be registered in two ways; in one specific guild, or for every guild the bot is in. We're going to look at single-guild registration first, as this is a good way to develop and test your commands before a global deployment.
|
||||
|
||||
Your application will need the `applications.commands` scope authorized in a guild for any of its slash commands to appear, and to be able to register them in a specific guild without error.
|
||||
|
||||
Slash commands only need to be registered once, and updated when the definition (description, options etc) is changed. As there is a daily limit on command creations, it's not necessary nor desirable to connect a whole client to the gateway or do this on every `ready` event. As such, a standalone script using the lighter REST manager is preferred.
|
||||
|
||||
This script is intended to be run separately, only when you need to make changes to your slash command **definitions** - you're free to modify parts such as the execute function as much as you like without redeployment.
|
||||
|
||||
### Guild commands
|
||||
|
||||
Create a `deploy-commands.js` file in your project directory. This file will be used to register and update the slash commands for your bot application.
|
||||
|
||||
Add two more properties to your `config.json` file, which we'll need in the deployment script:
|
||||
|
||||
- `clientId`: Your application's client id ([Discord Developer Portal](https://discord.com/developers/applications) > "General Information" > application id)
|
||||
- `guildId`: Your development server's id ([Enable developer mode](https://support.discord.com/hc/en-us/articles/206346498) > Right-click the server title > "Copy ID")
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"token": "your-token-goes-here",
|
||||
// [!code ++:2]
|
||||
"clientId": "your-application-id-goes-here",
|
||||
"guildId": "your-server-id-goes-here"
|
||||
}
|
||||
```
|
||||
|
||||
With these defined, you can use the deployment script below:
|
||||
|
||||
```js title="deploy-commands.js" lineNumbers
|
||||
const { REST, Routes } = require('discord.js');
|
||||
const { clientId, guildId, token } = require('./config.json');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const commands = [];
|
||||
// Grab all the command folders from the commands directory you created earlier
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
// Grab all the command files from the commands directory you created earlier
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
|
||||
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
commands.push(command.data.toJSON());
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct and prepare an instance of the REST module
|
||||
const rest = new REST().setToken(token);
|
||||
|
||||
// and deploy your commands!
|
||||
(async () => {
|
||||
try {
|
||||
console.log(`Started refreshing ${commands.length} application (/) commands.`);
|
||||
|
||||
// [!code word:Guild]
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands });
|
||||
|
||||
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
Once you fill in these values, run `node deploy-commands.js` in your project directory to register your commands to the guild specified. If you see the success message, check for the commands in the server by typing `/`! If all goes well, you should be able to run them and see your bot's response in Discord!
|
||||
|
||||
### Global commands
|
||||
|
||||
Global application commands will be available in all the guilds your application has the `applications.commands` scope authorized in, and in direct messages by default.
|
||||
|
||||
To deploy global commands, you can use the same script from the guild commands section above and simply adjust the route in the script to `.applicationCommands(clientId)`
|
||||
|
||||
```js
|
||||
await rest.put(Routes.applicationCommands(clientId), { body: commands });
|
||||
```
|
||||
|
||||
### Where to deploy
|
||||
|
||||
<Callout>
|
||||
Guild-based deployment of commands is best suited for development and testing in your own personal server. Once you're satisfied that it's ready, deploy the command globally to publish it to all guilds that your bot is in.
|
||||
|
||||
You may wish to have a separate application and token in the Discord Dev Portal for your dev application, to avoid duplication between your guild-based commands and the global deployment.
|
||||
|
||||
</Callout>
|
||||
|
||||
#### Further reading
|
||||
|
||||
You've successfully sent a response to a slash command! However, this is only the most basic of command event and response functionality. Much more is available to enhance the user experience including:
|
||||
|
||||
<Cards>
|
||||
<Card title="Event Handling" href="./handling-events">
|
||||
Apply a similar dynamic, modular handling approach to client events.
|
||||
</Card>
|
||||
<Card title="Response Methods" href="../slash-commands/response-methods">
|
||||
Utilize different response methods for slash commands.
|
||||
</Card>
|
||||
<Card title="Advanced Command Creation" href="../slash-commands/advanced-creation">
|
||||
Expand on the command examples with additional, validated, option types.
|
||||
</Card>
|
||||
<Card title="Display Components" href="../popular-topics/display-components">
|
||||
Level up command responses with formatted content and display components.
|
||||
</Card>
|
||||
<Card title="Interactive Components" href="../interactive-components/action-rows">
|
||||
Enhance your commands with more input methods using Buttons, Select Menus, and Modals!
|
||||
</Card>
|
||||
</Cards>
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
title: Command Handling
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
|
||||
Unless your bot project is small, it's not a very good idea to have a single file with a giant `if`/`else if` chain for commands. If you want to implement features into your bot and make your development process a lot less painful, you'll want to implement a command handler. Let's get started on that!
|
||||
|
||||
For fully functional slash commands, you need three important pieces of code:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
|
||||
### Command Files
|
||||
|
||||
The individual command files, containing [slash command](./creating-commands) definitions and functionality.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Handler
|
||||
|
||||
The command handler, dynamically reads the command files and executes commands.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Deployment
|
||||
|
||||
The command [deployment script](./deploying-commands) to register your slash commands with Discord.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
These steps can be followed in any order, but are all required to make your bot work. This page details step **2**. Make sure you also check out the other linked pages.
|
||||
|
||||
## Loading command files
|
||||
|
||||
Now that your command files have been created, your bot needs to load these files on startup.
|
||||
|
||||
In your `index.js` file, make these additions to the base template:
|
||||
|
||||
```js title="index.js" lineNumbers
|
||||
// [!code ++:4]
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js');
|
||||
const { token } = require('./config.json');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.once(Events.ClientReady, (readyClient) => {
|
||||
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
||||
});
|
||||
|
||||
client.commands = new Collection(); // [!code ++]
|
||||
```
|
||||
|
||||
We recommend attaching a `.commands` property to your client instance so that you can access your commands in other files. The rest of the examples in this guide will follow this convention. For TypeScript users, we recommend extending the base Client class to add this property, [casting](https://www.typescripttutorial.net/typescript-tutorial/type-casting/), or [augmenting the module type](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation).
|
||||
|
||||
<Callout>
|
||||
- The [`fs`](https://nodejs.org/api/fs.html) module is Node's native file system module. `fs` is used to read the
|
||||
`commands` directory and identify our command files. - The [`path`](https://nodejs.org/api/path.html) module is Node's
|
||||
native path utility module. `path` helps construct paths to access files and directories. One of the advantages of the
|
||||
`path` module is that it automatically detects the operating system and uses the appropriate joiners. - The
|
||||
`Collection` class extends JavaScript's native
|
||||
[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) class, and includes more
|
||||
extensive, useful functionality. `Collection` is used to store and efficiently retrieve commands for execution.
|
||||
</Callout>
|
||||
|
||||
Next, using the modules imported above, dynamically retrieve your command files with a few more additions to the `index.js` file:
|
||||
|
||||
```js title="index.js" lineNumbers=12
|
||||
client.commands = new Collection();
|
||||
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
// Set a new item in the Collection with the key as the command name and the value as the exported module
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
First, [`path.join()`](https://nodejs.org/api/path.html#pathjoinpaths) helps to construct a path to the `commands` directory. The first [`fs.readdirSync()`](https://nodejs.org/api/fs.html#fs_fs_readdirsync_path_options) method then reads the path to the directory and returns an array of all the folder names it contains, currently `['utility']`. The second `fs.readdirSync()` method reads the path to this directory and returns an array of all the file names they contain, currently `['ping.js', 'server.js', 'user.js']`. To ensure only command files get processed, `Array.filter()` removes any non-JavaScript files from the array.
|
||||
|
||||
With the correct files identified, the last step is dynamically set each command into the `client.commands` Collection. For each file being loaded, check that it has at least the `data` and `execute` properties. This helps to prevent errors resulting from loading empty, unfinished, or otherwise incorrect command files while you're still developing.
|
||||
|
||||
## Receiving command interactions
|
||||
|
||||
You will receive an interaction for every slash command executed. To respond to a command, you need to create a listener for the `interactionCreate` event that will execute code when your application receives an interaction. Place the code below in the `index.js` file you created earlier.
|
||||
|
||||
```js title="index.js" lineNumbers=32
|
||||
// [!code ++:3]
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
console.log(interaction);
|
||||
});
|
||||
```
|
||||
|
||||
Not every interaction is a slash command (e.g. `MessageComponent` interactions). Make sure to only handle slash commands in this function by making use of the `BaseInteraction#isChatInputCommand` method to exit the handler if another type is encountered. This method also provides typeguarding for TypeScript users, narrowing the type from `BaseInteraction` to a `ChatInputCommandInteraction`.
|
||||
|
||||
```js title="index.js" lineNumbers=32
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return; // [!code ++]
|
||||
console.log(interaction);
|
||||
});
|
||||
```
|
||||
|
||||
## Executing commands
|
||||
|
||||
When your bot receives a `interactionCreate` event, the interaction object contains all the information you need to dynamically retrieve and execute your commands!
|
||||
|
||||
Let's take a look at the `ping` command again. Note the `execute()` function that will reply to the interaction with "Pong!".
|
||||
|
||||
```js title="commands/utility/ping.js"
|
||||
// [!code word:execute]
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'),
|
||||
async execute(interaction) {
|
||||
await interaction.reply('Pong!');
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
First, you need to get the matching command from the `client.commands` Collection based on the `interaction.commandName`. Your `Client` instance is always available via `interaction.client`. If no matching command is found, log an error to the console and ignore the event.
|
||||
|
||||
With the right command identified, all that's left to do is call the command's `.execute()` method and pass in the `interaction` variable as its argument. In case something goes wrong, catch and log any error to the console.
|
||||
|
||||
```js title="index.js" lineNumbers=32
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
// [!code ++:23]
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Next steps
|
||||
|
||||
Your command files are now loaded into your bot, and the event listener is prepared and ready to respond. In the next section, we cover the final step: a command deployment script you'll need to register your commands so they appear in the Discord client.
|
||||
215
apps/guide/content/docs/legacy/app-creation/handling-events.mdx
Normal file
@@ -0,0 +1,215 @@
|
||||
---
|
||||
title: Event Handling
|
||||
---
|
||||
|
||||
import { File, Folder, Files } from 'fumadocs-ui/components/files';
|
||||
|
||||
Node.js uses an event-driven architecture, making it possible to execute code when a specific event occurs. The discord.js library takes full advantage of this. You can visit the `Client` documentation to see the full list of events.
|
||||
|
||||
<Callout>
|
||||
This page assumes you've followed the guide up to this point, and created your `index.js` and individual slash
|
||||
commands according to those pages.
|
||||
</Callout>
|
||||
|
||||
At this point, your `index.js` file has listeners for two events: `ClientReady` and `InteractionCreate`.
|
||||
|
||||
```js title="index.js"
|
||||
// [!code word:ClientReady]
|
||||
client.once(Events.ClientReady, (readyClient) => {
|
||||
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
||||
|
||||
// [!code word:InteractionCreate]
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Currently, the event listeners are in the `index.js` file. `Client#ready`emits once when the `Client` becomes ready for use, and `Client#interactionCreate` emits whenever an interaction is received. Moving the event listener code into individual files is simple, and we'll be taking a similar approach to the command handler.
|
||||
|
||||
<Callout type="warn">
|
||||
You're only going to move these two events from `index.js`. The code for [loading command
|
||||
files](./handling-commands#loading-command-files) will stay here!
|
||||
</Callout>
|
||||
|
||||
## Individual event files
|
||||
|
||||
Your project directory should look something like this:
|
||||
|
||||
<Files>
|
||||
<Folder name="discord-bot" defaultOpen>
|
||||
<Folder name="commands" defaultOpen />
|
||||
<Folder name="node_modules" defaultOpen />
|
||||
<File name="config.json" />
|
||||
<File name="deploy-commands.js" />
|
||||
<File name="index.js" />
|
||||
<File name="package-lock.json" />
|
||||
<File name="package.json" />
|
||||
</Folder>
|
||||
</Files>
|
||||
|
||||
Create an `events` folder in the same directory. You can then move the code from your event listeners in `index.js` to separate files: `events/ready.js` and `events/interactionCreate.js`.
|
||||
|
||||
```js tab="Ready Handler" title="events/ready.js"
|
||||
const { Events } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.ClientReady,
|
||||
once: true,
|
||||
execute(client) {
|
||||
console.log(`Ready! Logged in as ${client.user.tag}`);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```js tab="Interaction Handler" title="events/interactionCreate.js"
|
||||
const { Events, MessageFlags } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```js title="index.js" tab="Main File (after removing events)"
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { Client, Collection, GatewayIntentBits } = require('discord.js');
|
||||
const { token } = require('./config.json');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.commands = new Collection();
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.login(token);
|
||||
```
|
||||
|
||||
The `name` property states which event this file is for, and the `once` property holds a boolean value that specifies if the event should run only once. You don't need to specify this in `interactionCreate.js` as the default behavior will be to run on every event instance. The `execute` function holds your event logic, which will be called by the event handler whenever the event emits.
|
||||
|
||||
## Reading event files
|
||||
|
||||
Next, let's write the code for dynamically retrieving all the event files in the `events` folder. We'll be taking a similar approach to our [command handler](./handling-commands). Place the new code highlighted below in your `index.js`.
|
||||
|
||||
`fs.readdirSync().filter()` returns an array of all the file names in the given directory and filters for only `.js` files, i.e. `['ready.js', 'interactionCreate.js']`.
|
||||
|
||||
```js title="index.js"
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { Client, Collection, GatewayIntentBits } = require('discord.js');
|
||||
const { token } = require('./config.json');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.commands = new Collection();
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [!code ++:12]
|
||||
const eventsPath = path.join(__dirname, 'events');
|
||||
const eventFiles = fs.readdirSync(eventsPath).filter((file) => file.endsWith('.js'));
|
||||
|
||||
for (const file of eventFiles) {
|
||||
const filePath = path.join(eventsPath, file);
|
||||
const event = require(filePath);
|
||||
if (event.once) {
|
||||
client.once(event.name, (...args) => event.execute(...args));
|
||||
} else {
|
||||
client.on(event.name, (...args) => event.execute(...args));
|
||||
}
|
||||
}
|
||||
|
||||
client.login(token);
|
||||
```
|
||||
|
||||
You'll notice the code looks very similar to the command loading above it - read the files in the events folder and load each one individually.
|
||||
|
||||
The `Client` class in discord.js extends the [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter) class. Therefore, the `client` object exposes the [`.on()`](https://nodejs.org/api/events.html#events_emitter_on_eventname_listener) and [`.once()`](https://nodejs.org/api/events.html#events_emitter_once_eventname_listener) methods that you can use to register event listeners. These methods take two arguments: the event name and a callback function. These are defined in your separate event files as `name` and `execute`.
|
||||
|
||||
The callback function passed takes argument(s) returned by its respective event, collects them in an `args` array using the `...` [rest parameter syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters), then calls `event.execute()` while passing in the `args` array using the `...` [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). They are used here because different events in discord.js have different numbers of arguments. The rest parameter collects these variable number of arguments into a single array, and the spread syntax then takes these elements and passes them to the `execute` function.
|
||||
|
||||
After this, listening for other events is as easy as creating a new file in the `events` folder. The event handler will automatically retrieve and register it whenever you restart your bot.
|
||||
|
||||
<Callout>
|
||||
In most cases, you can access your `client` instance in other files by obtaining it from one of the other discord.js
|
||||
structures, e.g. `interaction.client` in the `interactionCreate` event. You do not need to manually pass it to your
|
||||
events.
|
||||
</Callout>
|
||||
50
apps/guide/content/docs/legacy/app-creation/main-file.mdx
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: The Main File
|
||||
---
|
||||
|
||||
<Callout>
|
||||
This page assumes you've already prepared the [configuration files](../app-creation/project-setup#configuration-files)
|
||||
from the previous page. We're using the `config.json` approach, however feel free to substitute your own!
|
||||
</Callout>
|
||||
|
||||
## Creating the main file
|
||||
|
||||
Open your code editor and create a new file. We suggest that you save the file as `index.js`, but you may name it whatever you wish.
|
||||
|
||||
Here's the base code to get you started:
|
||||
|
||||
```js title="index.js"
|
||||
// Require the necessary discord.js classes
|
||||
const { Client, Events, GatewayIntentBits } = require('discord.js');
|
||||
const { token } = require('./config.json');
|
||||
|
||||
// Create a new client instance
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
// When the client is ready, run this code (only once).
|
||||
// The distinction between `client: Client<boolean>` and `readyClient: Client<true>` is important for TypeScript developers.
|
||||
// It makes some properties non-nullable.
|
||||
client.once(Events.ClientReady, (readyClient) => {
|
||||
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
||||
});
|
||||
|
||||
// Log in to Discord with your client's token
|
||||
client.login(token);
|
||||
```
|
||||
|
||||
This is how you create a client instance for your Discord bot and log in to Discord. The `GatewayIntentBits.Guilds` intents option is necessary for the discord.js client to work as you expect it to, as it ensures that the caches for guilds, channels, and roles are populated and available for internal use.
|
||||
|
||||
<Callout>The term "guild" is used by the Discord API and in discord.js to refer to a Discord server.</Callout>
|
||||
|
||||
Intents also define which events Discord should send to your bot, and you may wish to enable more than just the minimum. You can read more about the other intents in the [Intents topic](../popular-topics/intents).
|
||||
|
||||
## Running your application
|
||||
|
||||
Open your terminal and run `node index.js` to start the process. If you see "Ready!" after a few seconds, you're good to go! The next step is to start adding slash commands to develop your app's functionality.
|
||||
|
||||
<Callout>
|
||||
You can open your `package.json` file and edit the `"main": "index.js"` field to point to your main file. You can then run `node .` in your terminal to start the process!
|
||||
|
||||
After closing the process with <kbd>Ctrl</kbd> <kbd>C</kbd>, you can press the up arrow on your keyboard to bring up the latest commands you've run. Pressing up and then enter after closing the process is a quick way to start it up again.
|
||||
|
||||
</Callout>
|
||||
11
apps/guide/content/docs/legacy/app-creation/meta.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"title": "Creating Your App",
|
||||
"pages": [
|
||||
"project-setup",
|
||||
"main-file",
|
||||
"creating-commands",
|
||||
"handling-commands",
|
||||
"deploying-commands",
|
||||
"handling-events"
|
||||
]
|
||||
}
|
||||
101
apps/guide/content/docs/legacy/app-creation/project-setup.mdx
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: Project Setup
|
||||
---
|
||||
|
||||
## Configuration files
|
||||
|
||||
Once you [add your bot to a server](../preparations/adding-your-app), the next step is to start coding and get it online! Let's start by creating a config file for your client token and a main file for your bot application.
|
||||
|
||||
As explained in the ["What is a token, anyway?"](../preparations/app-setup#what-is-a-token-anyway) section, your token is essentially your bot's password, and you should protect it as best as possible. This can be done through a `config.json` file or by using environment variables.
|
||||
|
||||
Open your application in the [Discord Developer Portal](https://discord.com/developers/applications) and go to the "Bot" page to copy your token.
|
||||
|
||||
## Using `config.json`
|
||||
|
||||
Storing data in a `config.json` file is a common way of keeping your sensitive values safe. Create a `config.json` file in your project directory and paste in your token. You can access your token inside other files by using `require()`.
|
||||
|
||||
```json tab="Config" title="config.json"
|
||||
{
|
||||
"token": "your-token-goes-here"
|
||||
}
|
||||
```
|
||||
|
||||
```js tab="Usage"
|
||||
const { token } = require('./config.json');
|
||||
|
||||
console.log(token);
|
||||
```
|
||||
|
||||
<Callout title="Danger" type="error">
|
||||
If you're using Git, you should not commit files containing secrets. Read on to find out how to [exclude them from
|
||||
versioning by using `.gitignore`](#git-and-gitignore).
|
||||
</Callout>
|
||||
|
||||
## Using environment variables
|
||||
|
||||
Environment variables are, as the name suggets, values you can pass to your environment (e.g. terminal session, Docker container, node process). This has the benefit that you can keep your code the same for different execution contexts.
|
||||
|
||||
```txt title=".env"
|
||||
A=Hello World
|
||||
B=123
|
||||
DISCORD_TOKEN=MTI3NDMxMjA3PDQ3ODIxNzIzNg.G6uEbl.IpA3-9YeScYr9pu9K1utMlpP4p-KJwNxcIAbi8
|
||||
```
|
||||
|
||||
<Callout title="Danger" type="error">
|
||||
If you're using Git, you should not commit `.env` or other environment files containing secrets. Read on to find out
|
||||
how to [exclude them from versioning by using `.gitignore`](#git-and-gitignore).
|
||||
</Callout>
|
||||
|
||||
To use environment variables in Node.js, we recommend you use the command line interface flag `--env-file` to point to the `.env` file you want to use. Note that the file name `.env` is just a convention. You could for example have a `.env.development` and `.env.production` file with different values depending on the Discord application you want to run your code.
|
||||
|
||||
You can also read multiple environment files by using the `--env-file` flag multiple times.
|
||||
|
||||
```sh
|
||||
node --env-file=.env index.js
|
||||
```
|
||||
|
||||
<Callout>You don't need to pass any special flags when using Bun! Bun reads `.env` files automatically.</Callout>
|
||||
|
||||
The values you specify in `.env` files this way are exposed through the `process.env` global variable in any file. Note that values passed this way will always be strings. If you want to do calculations on environment numbers, you will have to parse them:
|
||||
|
||||
```js title="index.js"
|
||||
// [!code word:env]
|
||||
console.log(process.env.A);
|
||||
console.log(process.env.B + 9); // 1239 (this concatenates the number to the string!)
|
||||
console.log(Number(process.env.C) + 9); // 132
|
||||
console.log(process.env.DISCORD_TOKEN);
|
||||
```
|
||||
|
||||
## Online editors
|
||||
|
||||
While we generally do not recommend using online editors as hosting solutions, but rather invest in a proper virtual private server, these services do offer ways to keep your credentials safe as well! Please see the respective service's documentation and help articles for more information on how to keep sensitive values safe:
|
||||
|
||||
<Cards>
|
||||
<Card title="Glitch" href="https://help.glitch.com/s/article/Adding-Private-Data">
|
||||
Learn more about storing secrets in `.env` files using Glitch
|
||||
</Card>
|
||||
<Card title="Heroku" href="https://devcenter.heroku.com/articles/config-vars">
|
||||
Learn more about configuration variables in Heroku
|
||||
</Card>
|
||||
<Card title="Replit" href="https://docs.replit.com/replit-workspace/workspace-features/secrets#secrets">
|
||||
Learn more about secrets and environment variables in Replit
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Git and `.gitignore`
|
||||
|
||||
Git is a fantastic tool to keep track of your code changes and allows you to upload progress to services like [GitHub](https://github.com/), [GitLab](https://about.gitlab.com/), or [Bitbucket](https://bitbucket.org/product). While this is super useful to share code with other developers, it also bears the risk of uploading your configuration files with sensitive values!
|
||||
|
||||
You can specify files that Git should ignore in its versioning systems with a `.gitignore` file. Create a `.gitignore` file in your project directory and add the names of the files and folders you want to ignore. The following example ignores the `config.json` and `.env` files as well as the `node_modules` directory:
|
||||
|
||||
```txt title=".gitignore"
|
||||
node_modules
|
||||
.env
|
||||
config.json
|
||||
```
|
||||
|
||||
<Callout>
|
||||
`.gitignore` files can specify intricate patterns and help with your general development flow. Apart from keeping your
|
||||
credentials safe, you should exclude `node_modules` from version control as well, its contents can be restored from
|
||||
the entries in your `package.json` and `package-lock.json` files.
|
||||
</Callout>
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 22 KiB |
BIN
apps/guide/content/docs/legacy/images/branding/banner-small.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
apps/guide/content/docs/legacy/images/branding/banner.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
BIN
apps/guide/content/docs/legacy/images/branding/logo-blurple.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
apps/guide/content/docs/legacy/images/branding/logo-favicon.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
apps/guide/content/docs/legacy/images/branding/logo-small.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
apps/guide/content/docs/legacy/images/branding/logo.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,200 @@
|
||||
---
|
||||
title: Package scripts
|
||||
---
|
||||
|
||||
## Setting up package.json scripts
|
||||
|
||||
An easy way to run scripts like a script to start your bot, a script to lint your bot's files, or whatever scripts you use is by storing them in your `package.json` file. After you store these scripts in your `package.json` file, you can run the `start` script to start your bot or the `lint` script to lint your code for errors.
|
||||
|
||||
```sh tab="npm"
|
||||
npm run start
|
||||
npm run lint
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn run start
|
||||
yarn run lint
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm run start
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun run start
|
||||
bun run lint
|
||||
```
|
||||
|
||||
## Getting started
|
||||
|
||||
<Callout>
|
||||
Before getting started, you'll need to have a `package.json` file. If you don't have a `package.json` file yet, you can run the following command in the console to generate one.
|
||||
|
||||
```sh tab="npm"
|
||||
npm init -y
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn init -y
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm init
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun init -y
|
||||
```
|
||||
|
||||
</Callout>
|
||||
|
||||
If you haven't touched your `package.json` file yet (excluding installing dependencies), your `package.json` file should look similar to the following:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"name": "my-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "A Discord bot!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
```
|
||||
|
||||
Let's zoom in more. Below `main`, you'll see `scripts`. You can specify your scripts there. In this guide, we'll show how to start and lint your bot using a `package.json` script.
|
||||
|
||||
## Adding your first script
|
||||
|
||||
<Callout>
|
||||
We'll assume you have finished the [creating your app](../app-creation/project-setup) section of the guide. If you
|
||||
haven't, ensure to follow it first!
|
||||
</Callout>
|
||||
|
||||
Over at your `package.json` file, add the following line to the `scripts`:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"name": "my-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "A Discord bot!",
|
||||
"main": "index.js",
|
||||
"scripts": { // [!code focus:5]
|
||||
"test": "echo \"Error: no test specified\" && exit 1" // needs a comma // [!code --]
|
||||
"test": "echo \"Error: no test specified\" && exit 1", // [!code ++]
|
||||
"start": "node ." // [!code ++]
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The `node .` script will run the file you have specified at the `main` entry in your `package.json` file. If you don't
|
||||
have it set yet, make sure to select your bot's main file as `main`!
|
||||
</Callout>
|
||||
|
||||
Now, whenever you run the `start` script in your bot's directory, it will run the `node .` command.
|
||||
|
||||
```sh tab="npm"
|
||||
npm run start
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn run start
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm run start
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun run start
|
||||
```
|
||||
|
||||
Let's create another script to lint your code via the command line. Add the following line to your scripts:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"name": "my-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "A Discord bot!",
|
||||
"main": "index.js",
|
||||
"scripts": { // [!code focus:6]
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node ." // needs a comma // [!code --]
|
||||
"start": "node .", // [!code ++]
|
||||
"lint": "eslint ." // [!code ++]
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
```
|
||||
|
||||
Now, whenever you run the `lint` script, ESLint will lint your `index.js` file.
|
||||
|
||||
```sh tab="npm"
|
||||
npm run lint
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn run lint
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun run lint
|
||||
```
|
||||
|
||||
Your `package.json` file should now look similar to the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "A Discord bot!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node .",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
```
|
||||
|
||||
And that's it! You can always add more scripts now, running them with:
|
||||
|
||||
```sh tab="npm"
|
||||
npm run <script-name>
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn run <script-name>
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm run <script-name>
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun run <script-name>
|
||||
```
|
||||
|
||||
<Cards>
|
||||
<Card title="Package Scripts" href="https://docs.npmjs.com/cli/v7/using-npm/scripts">
|
||||
Package scripts allow some more configuration (like pre-, post- and lifecycle scripts) than we can cover in this
|
||||
guide. Check out the official documentation on for more information.
|
||||
</Card>
|
||||
</Cards>
|
||||
113
apps/guide/content/docs/legacy/improving-dev-environment/pm2.mdx
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: PM2
|
||||
---
|
||||
|
||||
PM2 is a process manager. It manages your applications' states, so you can start, stop, restart, and delete processes. It offers features such as monitoring running processes and setting up a "start with operating system" (be that Windows, Linux, or Mac) so your processes start when you boot your system.
|
||||
|
||||
## Installation
|
||||
|
||||
You can install PM2 via the following command:
|
||||
|
||||
```sh tab="npm"
|
||||
npm install --global pm2
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn global add pm2
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add --global pm2
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add --global pm2
|
||||
```
|
||||
|
||||
## Starting your app
|
||||
|
||||
After you install PM2, the easiest way you can start your app is by going to the directory your bot is in and then run the following:
|
||||
|
||||
```sh
|
||||
pm2 start your-app-name.js
|
||||
```
|
||||
|
||||
### Additional notes
|
||||
|
||||
The `pm2 start` script allows for more optional command-line arguments.
|
||||
|
||||
- `--name`: This allows you to set the name of your process when listing it up with `pm2 list` or `pm2 monit`:
|
||||
|
||||
```sh
|
||||
pm2 start your-app-name.js --name "Some cool name"
|
||||
```
|
||||
|
||||
- `--watch`: This option will automatically restart your process as soon as a file change is detected, which can be useful for development environments:
|
||||
|
||||
```bash
|
||||
pm2 start your-app-name.js --watch
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The `pm2 start` command can take more optional parameters, but only these two are relevant. If you want to see all the
|
||||
parameters available, you can check the documentation of pm2
|
||||
[here](https://pm2.keymetrics.io/docs/usage/pm2-doc-single-page/).
|
||||
</Callout>
|
||||
|
||||
Once the process launches with pm2, you can run `pm2 monit` to monitor all console outputs from the processes started by pm2. This accounts for any `console.log()` in your code or outputted errors.
|
||||
|
||||
In a similar fashion to how you start the process, running `pm2 stop` will stop the current process without removing it from PM2's interface:
|
||||
|
||||
```sh
|
||||
pm2 stop your-app-name.js
|
||||
```
|
||||
|
||||
## Setting up booting with your system
|
||||
|
||||
Perhaps one of the more useful features of PM2 is being able to boot up with your Operating System. This feature will ensure that your bot processes will always be started after an (unexpected) reboot (e.g., after a power outage).
|
||||
|
||||
The initial steps differ per OS. In this guide, we'll cover those for Windows and Linux/macOS.
|
||||
|
||||
### Initial steps for Windows
|
||||
|
||||
It is recommended to use `pm2-installer`. Follow the steps over at their [`GitHub`](https://github.com/jessety/pm2-installer).
|
||||
|
||||
### Initial steps for Linux/macOS
|
||||
|
||||
You'll need a start script, which you can get by running the following command:
|
||||
|
||||
```sh
|
||||
# Detects the available init system, generates the config, and enables startup system
|
||||
pm2 startup
|
||||
```
|
||||
|
||||
Or, if you want to specify your machine manually, select one of the options with the command:
|
||||
|
||||
```sh
|
||||
pm2 startup [ubuntu | ubuntu14 | ubuntu12 | centos | centos6 | arch | oracle | amazon | macos | darwin | freesd | systemd | systemv | upstart | launchd | rcd | openrc]
|
||||
```
|
||||
|
||||
The output of running one of the commands listed above will output a command for you to run with all environment variables and options configured.
|
||||
|
||||
**Example output for an Ubuntu user:**
|
||||
|
||||
```
|
||||
[PM2] You have to run this command as root. Execute the following command:
|
||||
sudo su -c "env PATH=$PATH:/home/user/.nvm/versions/node/v8.9/bin pm2 startup ubuntu -u user --hp /home/user
|
||||
```
|
||||
|
||||
After running that command, you can continue to the next step.
|
||||
|
||||
### Saving the current process list
|
||||
|
||||
To save the current process list so it will automatically get started after a restart, run the following command:
|
||||
|
||||
```sh
|
||||
pm2 save
|
||||
```
|
||||
|
||||
To disable this, you can run the following command:
|
||||
|
||||
```sh
|
||||
pm2 unstartup
|
||||
```
|
||||
36
apps/guide/content/docs/legacy/index.mdx
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Introduction
|
||||
---
|
||||
|
||||
import { GithubInfo } from 'fumadocs-ui/components/github-info';
|
||||
|
||||
<GithubInfo owner="discordjs" repo="discord.js" />
|
||||
|
||||
If you're reading this, it probably means you want to learn how to make a bot with discord.js. Awesome! You've come to the right place.
|
||||
This guide will teach you things such as:
|
||||
|
||||
- How to get a bot [up and running](./legacy/preparations/app-setup) from scratch;
|
||||
- How to properly [create](./legacy/app-creation/project-setup), [organize](./legacy/app-creation/handling-commands), and expand on your commands;
|
||||
- In-depth explanations and examples regarding popular topics (e.g. [components](./legacy/popular-topics/display-components) ,[reactions](./legacy/popular-topics/reactions), [embeds](./legacy/popular-topics/embeds), [canvas](./legacy/popular-topics/canvas));
|
||||
- Working with databases (e.g. [sequelize](./legacy/sequelize/) and [keyv](./legacy/keyv/keyv));
|
||||
- Getting started with [sharding](./legacy/sharding/);
|
||||
- And much more.
|
||||
|
||||
This guide will also cover subjects like common errors and how to solve them, keeping your code clean, setting up a proper development environment, etc.
|
||||
Sounds good? Great! Let's get started, then.
|
||||
|
||||
## Before you begin...
|
||||
|
||||
Alright, making a bot is cool and all, but there are some prerequisites to it. To create a bot with discord.js, you should have a fairly decent grasp of JavaScript itself.
|
||||
While you _can_ make a bot with very little JavaScript and programming knowledge, trying to do so without understanding the language first will only hinder you. You may get stuck on many uncomplicated issues, struggle with solutions to incredibly easy problems, and all-in-all end up frustrated. Sounds pretty annoying.
|
||||
|
||||
If you don't know JavaScript but would like to learn about it, here are a few links to help get you started:
|
||||
|
||||
- [Eloquent JavaScript, a free online book](http://eloquentjavascript.net/)
|
||||
- [JavaScript.info, a modern javascript tutorial](https://javascript.info/)
|
||||
- [Codecademy's interactive JavaScript course](https://www.codecademy.com/learn/introduction-to-javascript)
|
||||
- [Nodeschool, for both JavaScript and Node.js lessons](https://nodeschool.io/)
|
||||
- [MDN's JavaScript guide and full documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
||||
- [Google, your best friend](https://google.com)
|
||||
|
||||
Take your pick, learn some JavaScript, and once you feel like you're confident enough to make a bot, come back and get started!
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Context Menus
|
||||
---
|
||||
|
||||
Context Menus are application commands which appear when right clicking or tapping a user or a message, in the Apps submenu.
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [slash commands](../slash-commands/advanced-creation) section. Please carefully read
|
||||
those pages first so that you can understand the methods used in this section.
|
||||
</Callout>
|
||||
|
||||
## Registering context menu commands
|
||||
|
||||
To create a context menu command, use the `ContextMenuCommandBuilder` class. You can then set the type of the context menu (user or message) using the `setType()` method.
|
||||
|
||||
```js
|
||||
const { ContextMenuCommandBuilder, ApplicationCommandType } = require('discord.js');
|
||||
|
||||
const data = new ContextMenuCommandBuilder().setName('User Information').setType(ApplicationCommandType.User);
|
||||
```
|
||||
|
||||
## Receiving context menu command interactions
|
||||
|
||||
Context menus commands, just like slash commands, are received via an interaction. You can check if a given interaction is a context menu by invoking the `isContextMenuCommand()` method, or the `isMessageContextMenuCommand()` and `isUserContextMenuCommand()` methods to check for the specific type of context menu interaction:
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isUserContextMenuCommand()) return;
|
||||
console.log(interaction);
|
||||
});
|
||||
```
|
||||
|
||||
## Extracting data from context menus
|
||||
|
||||
For user context menus, you can get the targeted user by accessing the `targetUser` or `targetMember` property from the `UserContextMenuCommandInteraction`.
|
||||
|
||||
For message context menus, you can get the targeted message by accessing the `targetMessage` property from the `MessageContextMenuCommandInteraction`.
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isUserContextMenuCommand()) return;
|
||||
// Get the User's username from context menu
|
||||
const { username } = interaction.targetUser;
|
||||
console.log(username);
|
||||
});
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Context menu commands cannot have subcommands or any options.
|
||||
- Responding to context menu commands functions the same as slash commands. Refer to our [slash command responses](../slash-commands/response-methods) guide for more information.
|
||||
- Context menu command permissions also function the same as slash commands. Refer to our [slash command permissions](../slash-commands/permissions) guide for more information.
|
||||
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 25 KiB |
3
apps/guide/content/docs/legacy/interactions/meta.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Other Interactions"
|
||||
}
|
||||
192
apps/guide/content/docs/legacy/interactions/modals.mdx
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: Modals
|
||||
---
|
||||
|
||||
With modals you can create pop-up forms that allow users to provide you with formatted inputs through submissions. We'll cover how to create, show, and receive modal forms using discord.js!
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [interactions (slash commands) page](../slash-commands/advanced-creation). Please
|
||||
carefully read that section first, so that you can understand the methods used in this section.
|
||||
</Callout>
|
||||
|
||||
## Building and responding with modals
|
||||
|
||||
Unlike message components, modals aren't strictly components themselves. They're a callback structure used to respond to interactions.
|
||||
|
||||
<Callout>
|
||||
You can have a maximum of five `ActionRowBuilder`s per modal builder, and one `TextInputBuilder` within an
|
||||
`ActionRowBuilder`. Currently, you can only use `TextInputBuilder`s in modal action rows builders.
|
||||
</Callout>
|
||||
|
||||
To create a modal you construct a new `ModalBuilder`. You can then use the setters to add the custom id and title.
|
||||
|
||||
```js
|
||||
const { Events, ModalBuilder } = require('discord.js');
|
||||
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
if (interaction.commandName === 'ping') {
|
||||
const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
|
||||
|
||||
// TODO: Add components to modal...
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The custom id is a developer-defined string of up to 100 characters. Use this field to ensure you can uniquely define
|
||||
all incoming interactions from your modals!
|
||||
</Callout>
|
||||
|
||||
The next step is to add the input fields in which users responding can enter free-text. Adding inputs is similar to adding components to messages.
|
||||
|
||||
At the end, we then call `ChatInputCommandInteraction#showModal` to display the modal to the user.
|
||||
|
||||
<Callout type="warn">
|
||||
If you're using typescript you'll need to specify the type of components your action row holds. This can be done by specifying the generic parameter in `ActionRowBuilder`:
|
||||
|
||||
```diff
|
||||
- new ActionRowBuilder()
|
||||
+ new ActionRowBuilder<ModalActionRowComponentBuilder>()
|
||||
```
|
||||
|
||||
</Callout>
|
||||
|
||||
```js
|
||||
const { ActionRowBuilder, Events, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
|
||||
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
if (interaction.commandName === 'ping') {
|
||||
// Create the modal
|
||||
const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
|
||||
|
||||
// Add components to modal
|
||||
|
||||
// Create the text input components
|
||||
const favoriteColorInput = new TextInputBuilder()
|
||||
.setCustomId('favoriteColorInput')
|
||||
// The label is the prompt the user sees for this input
|
||||
.setLabel("What's your favorite color?")
|
||||
// Short means only a single line of text
|
||||
.setStyle(TextInputStyle.Short);
|
||||
|
||||
const hobbiesInput = new TextInputBuilder()
|
||||
.setCustomId('hobbiesInput')
|
||||
.setLabel("What's some of your favorite hobbies?")
|
||||
// Paragraph means multiple lines of text.
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
|
||||
// An action row only holds one text input,
|
||||
// so you need one action row per text input.
|
||||
const firstActionRow = new ActionRowBuilder().addComponents(favoriteColorInput);
|
||||
const secondActionRow = new ActionRowBuilder().addComponents(hobbiesInput);
|
||||
|
||||
// Add inputs to the modal
|
||||
modal.addComponents(firstActionRow, secondActionRow);
|
||||
|
||||
// Show the modal to the user
|
||||
await interaction.showModal(modal); // [!code word:showModal]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Restart your bot and invoke the `/ping` command again. You should see a popup form resembling the image below:
|
||||
|
||||

|
||||
|
||||
<Callout type="warn">
|
||||
Showing a modal must be the first response to an interaction. You cannot `defer()` or `deferUpdate()` then show a
|
||||
modal later.
|
||||
</Callout>
|
||||
|
||||
### Input styles
|
||||
|
||||
Currently there are two different input styles available:
|
||||
|
||||
- `Short`, a single-line text entry;
|
||||
- `Paragraph`, a multi-line text entry similar to the HTML `<textarea>`;
|
||||
|
||||
### Input properties
|
||||
|
||||
In addition to the `customId`, `label` and `style`, a text input can be customised in a number of ways to apply validation, prompt the user, or set default values via the `TextInputBuilder` methods:
|
||||
|
||||
```js
|
||||
const input = new TextInputBuilder()
|
||||
// set the maximum number of characters to allow
|
||||
.setMaxLength(1_000)
|
||||
// set the minimum number of characters required for submission
|
||||
.setMinLength(10)
|
||||
// set a placeholder string to prompt the user
|
||||
.setPlaceholder('Enter some text!')
|
||||
// set a default value to pre-fill the input
|
||||
.setValue('Default')
|
||||
// require a value in this input field
|
||||
.setRequired(true);
|
||||
```
|
||||
|
||||
## Receiving modal submissions
|
||||
|
||||
### Interaction collectors
|
||||
|
||||
Modal submissions can be collected within the scope of the interaction that showed it by utilising an `InteractionCollector`, or the `ChatInputCommandInteraction#awaitModalSubmit` promisified method. These both provide instances of the `ModalSubmitInteraction` class as collected items.
|
||||
|
||||
For a detailed guide on receiving message components via collectors, please refer to the [collectors guide](../popular-topics/collectors#interaction-collectors).
|
||||
|
||||
### The interactionCreate event
|
||||
|
||||
To receive a `ModalSubmitInteraction` event, attach an `Client#interactionCreate` event listener to your client and use the `BaseInteraction#isModalSubmit` type guard to make sure you only receive modals:
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isModalSubmit()) return;
|
||||
console.log(interaction);
|
||||
});
|
||||
```
|
||||
|
||||
## Responding to modal submissions
|
||||
|
||||
The `ModalSubmitInteraction` class provides the same methods as the `ChatInputCommandInteraction` class. These methods behave equally:
|
||||
|
||||
- `reply()`
|
||||
- `editReply()`
|
||||
- `deferReply()`
|
||||
- `fetchReply()`
|
||||
- `deleteReply()`
|
||||
- `followUp()`
|
||||
|
||||
If the modal was shown from a `ButtonInteraction` or `StringSelectMenuInteraction`, it will also provide these methods, which behave equally:
|
||||
|
||||
- `update()`
|
||||
- `deferUpdate()`
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isModalSubmit()) return;
|
||||
if (interaction.customId === 'myModal') {
|
||||
await interaction.reply({ content: 'Your submission was received successfully!' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Callout>
|
||||
If you're using typescript, you can use the `ModalSubmitInteraction#isFromMessage` typeguard, to make sure the
|
||||
received interaction was from a `MessageComponentInteraction`.
|
||||
</Callout>
|
||||
|
||||
## Extracting data from modal submissions
|
||||
|
||||
You'll most likely need to read the data sent by the user in the modal. You can do this by accessing the `ModalSubmitInteraction#fields`. From there you can call `ModalSubmitFields#getTextInputValue` with the custom id of the text input to get the value.
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isModalSubmit()) return;
|
||||
|
||||
// Get the data entered by the user
|
||||
const favoriteColor = interaction.fields.getTextInputValue('favoriteColorInput');
|
||||
const hobbies = interaction.fields.getTextInputValue('hobbiesInput');
|
||||
console.log({ favoriteColor, hobbies });
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Action rows
|
||||
---
|
||||
|
||||
Action rows are a layout component with five "slots" that can be filled with other components. At the time of writing this guide, buttons take up one slot and select menus take up five "slots". Accordingly, each `ActionRow` can hold up to 5 buttons or a single select menu. A message can have up to five rows without providing the `IsComponentsV2` message flag. If you want to place buttons or action rows into the message body, they have to be wrapped inside rows.
|
||||
|
||||
<Callout>
|
||||
Read our guide section on [display components](../popular-topics/display-components) if you want to learn more about
|
||||
those.
|
||||
</Callout>
|
||||
|
||||
## Building action rows
|
||||
|
||||
To create an action row, use the `ActionRowBuilder` class and the `ActionRowBuilder#addComponents` method to add buttons or a select menu.
|
||||
|
||||
```js
|
||||
const row = new ActionRowBuilder().addComponents(component);
|
||||
```
|
||||
|
||||
<Callout>
|
||||
If you're using TypeScript, you'll need to specify the type of components your action row holds. This can be done by specifying the component builder you will add to it using a generic parameter in `ActionRowBuilder`.
|
||||
|
||||
```js
|
||||
new ActionRowBuilder() // [!code --]
|
||||
new ActionRowBuilder<ButtonBuilder>() // [!code ++]
|
||||
```
|
||||
|
||||
</Callout>
|
||||
|
||||
## Sending action rows
|
||||
|
||||
Once one or many components are inside your row(s), send them in the `components` property of your `InteractionReplyOptions` (extends `BaseMessageOptions`).
|
||||
|
||||
```js
|
||||
const row = new ActionRowBuilder().addComponents(component);
|
||||
|
||||
await interaction.reply({ components: [row] });
|
||||
```
|
||||
|
||||
To learn how to create the buttons and select menus that will go inside your row, including more detailed examples on how you might use them, continue on to the other pages in this section.
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
title: Buttons
|
||||
---
|
||||
|
||||
The first type of interactive component we'll cover creating is a Button. Buttons are available in a variety of styles and can be used to provide permanent interfaces, temporary confirmation workflows, and other forms of additional interaction with your bot.
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [slash commands](../slash-commands/advanced-creation) section and [action
|
||||
rows](./action-rows) page. Please carefully read those pages first so that you can understand the methods used here.
|
||||
</Callout>
|
||||
|
||||
## Building buttons
|
||||
|
||||
Buttons are one of the `MessageComponent` classes, which can be sent via messages or interaction responses.
|
||||
|
||||
For this example, you're going to expand on the `ban` command that was previously covered on the [parsing options](../slash-commands/parsing-options) page with a confirmation workflow.
|
||||
|
||||
To create your buttons, use the `ButtonBuilder` class, defining at least the `customId`, `style` and `label`.
|
||||
|
||||
```js
|
||||
const { ButtonBuilder, ButtonStyle, SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
const target = interaction.options.getUser('target');
|
||||
const reason = interaction.options.getString('reason') ?? 'No reason provided';
|
||||
|
||||
const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Ban').setStyle(ButtonStyle.Danger);
|
||||
|
||||
const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The custom id is a developer-defined string of up to 100 characters. Use this field to ensure you can uniquely define
|
||||
all incoming interactions.
|
||||
</Callout>
|
||||
|
||||
## Sending buttons
|
||||
|
||||
To send your buttons, create an action row and add the buttons as components. Then, send the row in the `components` property of `InteractionReplyOptions` (extends `BaseMessageOptions`).
|
||||
|
||||
```js
|
||||
const { ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
const target = interaction.options.getUser('target');
|
||||
const reason = interaction.options.getString('reason') ?? 'No reason provided';
|
||||
|
||||
const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Ban').setStyle(ButtonStyle.Danger);
|
||||
|
||||
const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary);
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(cancel, confirm);
|
||||
|
||||
await interaction.reply({
|
||||
content: `Are you sure you want to ban ${target} for reason: ${reason}?`,
|
||||
components: [row],
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Restart your bot and then send the command to a channel your bot has access to. If all goes well, you should see something like this:
|
||||
|
||||
## Button styles
|
||||
|
||||
You'll notice in the above example that two different styles of buttons have been used, the grey Secondary style and the red Danger style. These were chosen specifically to support good UI/UX principles. In total, there are five button styles that can be used as appropriate to the action of the button:
|
||||
|
||||
- `Primary` style buttons are blue. These are suitable for most general purpose actions, where it's the primary or most significant action expected.
|
||||
- `Secondary` style buttons are grey. Use these for less important actions like the "Cancel" button in the example above.
|
||||
- `Success` style buttons are green. Similar to the Primary button, these are a good choice for "positive" confirmation actions.
|
||||
- `Danger` style buttons are red. Where the action being confirmed is "destructive", such a ban or delete, using a red button helps alert the user to the risk of the action.
|
||||
- `Link` style buttons are also grey, but are tagged with the "external link" symbol. These buttons will open the provided link in the browser without sending an interaction to the bot.
|
||||
|
||||
## Link buttons
|
||||
|
||||
Link buttons are a little different to the other styles. `Link` buttons **must** have a `url`, **cannot** have a `customId` and **do not** send an interaction event when clicked.
|
||||
|
||||
```js
|
||||
const button = new ButtonBuilder()
|
||||
.setLabel('discord.js docs')
|
||||
.setURL('https://discord.js.org') // [!code word:setURL]
|
||||
.setStyle(ButtonStyle.Link); // [!code word:Link]
|
||||
```
|
||||
|
||||
## Disabled buttons
|
||||
|
||||
If you want to prevent a button from being used, but not remove it from the message, you can disable it with the `ButtonBuilder#setDisabled` method:
|
||||
|
||||
```js
|
||||
const button = new ButtonBuilder()
|
||||
.setCustomId('disabled')
|
||||
.setLabel('Click me?')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(true); // [!code word:setDisabled]
|
||||
```
|
||||
|
||||
## Emoji buttons
|
||||
|
||||
If you want to use a guild emoji within a `ButtonBuilder`, you can use the `ButtonBuilder#setEmoji` method:
|
||||
|
||||
```js
|
||||
const button = new ButtonBuilder()
|
||||
.setCustomId('primary')
|
||||
.setLabel('Primary')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setEmoji('123456789012345678'); // [!code word:setEmoji]
|
||||
```
|
||||
|
||||
#### Next steps
|
||||
|
||||
<Callout>
|
||||
Buttons can also be set as an accessory in section components. Check the guide section on [display
|
||||
components](../popular-topics/display-components) if you want to find out more about this.
|
||||
</Callout>
|
||||
|
||||
That's everything you need to know about building and sending buttons! From here you can learn about the other type of message component, [select menus](./select-menus), or have your bot start listening to [component interactions](./interactions) from your buttons.
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,213 @@
|
||||
---
|
||||
title: Interactions
|
||||
---
|
||||
|
||||
## Component interactions
|
||||
|
||||
Every button click or select menu selection on a component sent by your bot fires an `interaction`, triggering the `Client#interactionCreate` event. How you decide to handle this will likely depend on the purpose of the components. Options include:
|
||||
|
||||
- Waiting for a single interaction via `awaitMessageComponent()` on a channel or a message.
|
||||
- Listening for multiple interactions over a period of time using an `InteractionCollector`.
|
||||
- Creating a permanent component handler in the `Client#interactionCreate` event.
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [slash commands](../slash-commands/advanced-creation) section, and assumes you have
|
||||
created either [buttons](./buttons) or [select menus](./select-menus) as detailed in this guide. Please carefully read
|
||||
those pages first so that you can understand the methods used here.
|
||||
</Callout>
|
||||
|
||||
## Responding to component interactions
|
||||
|
||||
As with all other interactions message components interactions require a response within 3 seconds, else Discord will treat them as failed.
|
||||
|
||||
Like slash commands, all types of message component interactions support the `reply()`, `deferReply()`, `editReply()` and `followUp()` methods, with the option for these responses to be ephemeral. These function identically to how they do for slash commands, so refer to the page on [slash command response methods](../slash-commands/response-methods) for information on those.
|
||||
|
||||
Component interactions also support two additional methods of response, detailed below and demonstrated in examples later on the page.
|
||||
|
||||
### Updates
|
||||
|
||||
Responding to a component interaction via the `update()` method acknowledges the interaction by editing the message on which the component was attached. This method should be preferred to calling `editReply()` on the original interaction which sent the components. Like `editReply()`, `update()` cannot be used to change the ephemeral state of a message.
|
||||
|
||||
Once `update()` has been called, future messages can be sent by calling `followUp()` or edits can be made by calling `editReply()` on the component interaction.
|
||||
|
||||
### Deferred updates
|
||||
|
||||
Responding to a component interaction via the `deferUpdate()` method acknowledges the interaction and resets the message state. This method can be used to suppress the need for further responses, however it's encouraged to provide meaningful feedback to users via an `update()` or ephemeral `reply()` at least.
|
||||
|
||||
Once `deferUpdate()` has been called, future messages can be sent by calling `followUp()` or edits can be made by calling `editReply()` on the component interaction.
|
||||
|
||||
## Awaiting components
|
||||
|
||||
If you followed our [buttons](./buttons) guide, the confirmation workflow for the `ban` command is a good example of a situation where your bot is expecting to receive a single response, from either the Confirm or Cancel button.
|
||||
|
||||
Begin by adding `withResponse` to the options, and then calling `Message#awaitMessageComponent` on the message. This method returns a [Promise](../additional-info/async-await) that resolves when any interaction passes its filter (if one is provided), or throws if none are received before the timeout. If this happens, remove the components and notify the user.
|
||||
|
||||
```js title="commands/moderation/ban.js"
|
||||
const response = await interaction.reply({
|
||||
content: `Are you sure you want to ban ${target.username} for reason: ${reason}?`,
|
||||
components: [row],
|
||||
// [!code ++]
|
||||
withResponse: true,
|
||||
});
|
||||
|
||||
// [!code ++:7]
|
||||
const collectorFilter = (i) => i.user.id === interaction.user.id;
|
||||
|
||||
try {
|
||||
const confirmation = await response.resource.message.awaitMessageComponent({ filter: collectorFilter, time: 60_000 });
|
||||
} catch {
|
||||
await interaction.editReply({ content: 'Confirmation not received within 1 minute, cancelling', components: [] });
|
||||
}
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The filter applied here ensures that only the user who triggered the original interaction can use the buttons.
|
||||
</Callout>
|
||||
|
||||
With the confirmation collected, check which button was clicked and perform the appropriate action.
|
||||
|
||||
```js
|
||||
const response = await interaction.reply({
|
||||
content: `Are you sure you want to ban ${target.username} for reason: ${reason}?`,
|
||||
components: [row],
|
||||
withResponse: true,
|
||||
});
|
||||
|
||||
const collectorFilter = (i) => i.user.id === interaction.user.id;
|
||||
try {
|
||||
// [!code focus:8] [!code word:customId]
|
||||
const confirmation = await response.resource.message.awaitMessageComponent({ filter: collectorFilter, time: 60_000 });
|
||||
|
||||
if (confirmation.customId === 'confirm') {
|
||||
await interaction.guild.members.ban(target);
|
||||
await confirmation.update({ content: `${target.username} has been banned for reason: ${reason}`, components: [] });
|
||||
} else if (confirmation.customId === 'cancel') {
|
||||
await confirmation.update({ content: 'Action cancelled', components: [] });
|
||||
}
|
||||
} catch {
|
||||
await interaction.editReply({ content: 'Confirmation not received within 1 minute, cancelling', components: [] });
|
||||
}
|
||||
```
|
||||
|
||||
## Component collectors
|
||||
|
||||
For situations where you want to collect multiple interactions, the Collector approach is better suited than awaiting singular interactions. Following on from the [select menus](./select-menus) guide, you're going to extend that example to use an `InteractionCollector` to listen for multiple `StringSelectMenuInteraction`.
|
||||
|
||||
Begin by adding `withResponse` to the options, and then calling `Message#createMessageComponentCollector` on this instance. This method returns an InteractionCollector that will fire its `InteractionCollector#collect` event whenever an interaction passes its filter (if one is provided).
|
||||
|
||||
In the `collect` event, each interaction is a `StringSelectMenuInteraction` thanks to the `componentType: ComponentType.StringSelect` option provided to the collector (and because that was the only type of component in the message). The selected value(s) are available via the `StringSelectMenuInteraction#values` property.
|
||||
|
||||
```js
|
||||
const response = await interaction.reply({
|
||||
content: 'Choose your starter!',
|
||||
components: [row],
|
||||
withResponse: true, // [!code ++]
|
||||
});
|
||||
|
||||
// [!code ++:9]
|
||||
const collector = response.resource.message.createMessageComponentCollector({
|
||||
componentType: ComponentType.StringSelect,
|
||||
time: 3_600_000, // 1 hour
|
||||
});
|
||||
|
||||
collector.on('collect', async (i) => {
|
||||
const selection = i.values[0];
|
||||
await i.reply(`${i.user} has selected ${selection}!`);
|
||||
});
|
||||
```
|
||||
|
||||
## The Client#interactionCreate event
|
||||
|
||||
Third and finally, you may wish to have a listener setup to respond to permanent button or select menu features of your guild. For this, returning to the `Client#interactionCreate` event is the best approach.
|
||||
|
||||
If your event handling has been setup in multiple files as per our [event handling](../app-creation/handling-events) guide, you should already have an `events/interactionCreate.js` file that looks something like this.
|
||||
|
||||
```js title="events/interactionCreate.js"
|
||||
const { Events } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(`Error executing ${interaction.commandName}`);
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The way this was previously set up returns from the `execute` function whenever it encounters an interaction that is not a `ChatInputCommandInteraction`, as shown on the highlighted line above. The first change that needs to be made is to invert this logic, without actually changing the functionality.
|
||||
|
||||
```js title="events/interactionCreate.js"
|
||||
const { Events } = require('discord.js');
|
||||
|
||||
// [!code focus:22]
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction) {
|
||||
if (!interaction.isChatInputCommand()) return; // [!code --]
|
||||
// [!code ++:15]
|
||||
if (interaction.isChatInputCommand()) {
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(`Error executing ${interaction.commandName}`);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Now that the logic is setup to run code when something _is_ a `ChatInputCommandInteraction`, rather than to stop and exit when it isn't, you can add handling for additional interaction types via simple `if...else` logic.
|
||||
|
||||
```js
|
||||
const { Events } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
// [!code focus]
|
||||
async execute(interaction) {
|
||||
// [!code focus]
|
||||
if (interaction.isChatInputCommand()) {
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(`Error executing ${interaction.commandName}`);
|
||||
console.error(error);
|
||||
}
|
||||
// [!code focus:5] [!code ++:5]
|
||||
} else if (interaction.isButton()) {
|
||||
// respond to the button
|
||||
} else if (interaction.isStringSelectMenu()) {
|
||||
// respond to the select menu
|
||||
}
|
||||
// [!code focus]
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pages": ["action-rows", "buttons", "select-menus", "interactions"]
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
---
|
||||
title: Select Menus
|
||||
---
|
||||
|
||||
Select menus are one of the `MessageComponent` classes, which can be sent via messages or interaction responses.
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [slash commands](../slash-commands/advanced-creation) section and [action
|
||||
rows](../interactive-components/action-rows) page. Please carefully read those pages first so that you can understand
|
||||
the methods used here.
|
||||
</Callout>
|
||||
|
||||
## Building string select menus
|
||||
|
||||
The "standard" and most customizable type of select menu is the string select menu. To create a string select menu, use the `StringSelectMenuBuilder` and `StringSelectMenuOptionBuilder` classes.
|
||||
|
||||
If you're a Pokémon fan, you've probably made a selection pretty similar to this example at some point in your life!
|
||||
|
||||
```js
|
||||
const { StringSelectMenuBuilder, StringSelectMenuOptionBuilder, SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId('starter')
|
||||
.setPlaceholder('Make a selection!')
|
||||
.addOptions(
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Bulbasaur')
|
||||
.setDescription('The dual-type Grass/Poison Seed Pokémon.')
|
||||
.setValue('bulbasaur'),
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Charmander')
|
||||
.setDescription('The Fire-type Lizard Pokémon.')
|
||||
.setValue('charmander'),
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Squirtle')
|
||||
.setDescription('The Water-type Tiny Turtle Pokémon.')
|
||||
.setValue('squirtle'),
|
||||
);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The custom id is a developer-defined string of up to 100 characters. Use this field to ensure you can uniquely define
|
||||
all incoming interactions from your select menus!
|
||||
</Callout>
|
||||
|
||||
## Sending select menus
|
||||
|
||||
To send your select menu, create an action row and add the buttons as components. Then, send the row in the `components` property of `InteractionReplyOptions` (extends `BaseMessageOptions`).
|
||||
|
||||
```js
|
||||
const {
|
||||
ActionRowBuilder,
|
||||
StringSelectMenuBuilder,
|
||||
StringSelectMenuOptionBuilder,
|
||||
SlashCommandBuilder,
|
||||
} = require('discord.js');
|
||||
// [!code focus:30]
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId('starter')
|
||||
.setPlaceholder('Make a selection!')
|
||||
.addOptions(
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Bulbasaur')
|
||||
.setDescription('The dual-type Grass/Poison Seed Pokémon.')
|
||||
.setValue('bulbasaur'),
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Charmander')
|
||||
.setDescription('The Fire-type Lizard Pokémon.')
|
||||
.setValue('charmander'),
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Squirtle')
|
||||
.setDescription('The Water-type Tiny Turtle Pokémon.')
|
||||
.setValue('squirtle'),
|
||||
);
|
||||
|
||||
// [!code ++:6]
|
||||
const row = new ActionRowBuilder().addComponents(select);
|
||||
|
||||
await interaction.reply({
|
||||
content: 'Choose your starter!',
|
||||
components: [row],
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout>Remember that if you have more than one select menu, each one will need its own action row.</Callout>
|
||||
|
||||
<Callout>
|
||||
You can also send action rows inside containers or between other components, when using the [display
|
||||
components](../popular-topics/display-components) system.
|
||||
</Callout>
|
||||
|
||||
### String select menu options
|
||||
|
||||
String select menu options are custom-defined by the user, as shown in the example above. At a minimum, each option must have it's `label` and `value` defined. The label is shown to the user, while the value is included in the interaction sent to the bot.
|
||||
|
||||
In addition to these, each option can be enhanced with a `description` or `emoji`, or can be set to be selected by default.
|
||||
|
||||
```js
|
||||
const select = new StringSelectMenuBuilder().setCustomId('select').addOptions(
|
||||
// [!code focus:6]
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Option')
|
||||
.setValue('option')
|
||||
.setDescription('A selectable option') // [!code word:setDescription]
|
||||
.setEmoji('123456789012345678') // [!code word:setEmoji]
|
||||
.setDefault(true), // [!code word:setDefault]
|
||||
);
|
||||
```
|
||||
|
||||
## Auto-populating select menus
|
||||
|
||||
Although the String select menu with its user-defined options is the most customizable, there are four other types of select menu that auto-populate with their corresponding Discord entities, much like slash command options. These are:
|
||||
|
||||
- `UserSelectMenuBuilder`
|
||||
- `RoleSelectMenuBuilder`
|
||||
- `MentionableSelectMenuBuilder`
|
||||
- `ChannelSelectMenuBuilder`
|
||||
|
||||
The `ChannelSelectMenuBuilder` can be configured to only show specific channel types using `ChannelSelectMenuBuilder#setChannelTypes`.
|
||||
|
||||
## Multi-selects
|
||||
|
||||
Where slash command options fall behind is in their single-select limitation on User, Role and Channel option types. Select menus can support this use case via `BaseSelectMenuBuilder#setMinValues` and `BaseSelectMenuBuilder#setMaxValues`. When these values are set, users can select multiple items, and the interaction will be sent with all selected values only when the user clicks outside the select menu.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
// [!code focus:5]
|
||||
const userSelect = new UserSelectMenuBuilder()
|
||||
.setCustomId('users')
|
||||
.setPlaceholder('Select multiple users.')
|
||||
.setMinValues(1) // [!code word:setMinValues]
|
||||
.setMaxValues(10); // [!code word:setMaxValues]
|
||||
|
||||
const row1 = new ActionRowBuilder().addComponents(userSelect);
|
||||
|
||||
await interaction.reply({
|
||||
content: 'Select users:',
|
||||
components: [row1],
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
194
apps/guide/content/docs/legacy/keyv/keyv.mdx
Normal file
@@ -0,0 +1,194 @@
|
||||
---
|
||||
title: Keyv
|
||||
---
|
||||
|
||||
[Keyv](https://www.npmjs.com/package/keyv) is a simple key-value store that works with multiple backends. It's fully scalable for [sharding](../sharding/) and supports JSON storage.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh tab="npm"
|
||||
npm install keyv
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add keyv
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add keyv
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add keyv
|
||||
```
|
||||
|
||||
Keyv requires an additional package depending on which persistent backend you would prefer to use. If you want to keep everything in memory, you can skip this part. Otherwise, install one of the below.
|
||||
|
||||
```sh tab="npm"
|
||||
npm install @keyv/redis
|
||||
npm install @keyv/mongo
|
||||
npm install @keyv/sqlite
|
||||
npm install @keyv/postgres
|
||||
npm install @keyv/mysql
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add @keyv/redis
|
||||
yarn add @keyv/mongo
|
||||
yarn add @keyv/sqlite
|
||||
yarn add @keyv/postgres
|
||||
yarn add @keyv/mysql
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add @keyv/redis
|
||||
pnpm add @keyv/mongo
|
||||
pnpm add @keyv/sqlite
|
||||
pnpm add @keyv/postgres
|
||||
pnpm add @keyv/mysql
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add @keyv/redis
|
||||
bun add @keyv/mongo
|
||||
bun add @keyv/sqlite
|
||||
bun add @keyv/postgres
|
||||
bun add @keyv/mysql
|
||||
```
|
||||
|
||||
Create an instance of Keyv once you've installed Keyv and any necessary drivers.
|
||||
|
||||
```js
|
||||
const { Keyv } = require('keyv');
|
||||
|
||||
// One of the following
|
||||
const keyv = new Keyv(); // for in-memory storage
|
||||
const keyv = new Keyv('redis://user:pass@localhost:6379');
|
||||
const keyv = new Keyv('mongodb://user:pass@localhost:27017/dbname');
|
||||
const keyv = new Keyv('sqlite://path/to/database.sqlite');
|
||||
const keyv = new Keyv('postgresql://user:pass@localhost:5432/dbname');
|
||||
const keyv = new Keyv('mysql://user:pass@localhost:3306/dbname');
|
||||
```
|
||||
|
||||
Make sure to handle connection errors.
|
||||
|
||||
```js
|
||||
keyv.on('error', (err) => console.error('Keyv connection error:', err));
|
||||
```
|
||||
|
||||
For a more detailed setup, check out the [Keyv readme](https://github.com/jaredwray/keyv/tree/main/packages/keyv).
|
||||
|
||||
## Usage
|
||||
|
||||
Keyv exposes a familiar [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)-like API. However, it only has `set`, `get`, `delete`, and `clear` methods. Additionally, instead of immediately returning data, these methods return [Promises](../additional-info/async-await) that resolve with the data.
|
||||
|
||||
```js
|
||||
(async () => {
|
||||
// true
|
||||
await keyv.set('foo', 'bar');
|
||||
|
||||
// bar
|
||||
await keyv.get('foo');
|
||||
|
||||
// undefined
|
||||
await keyv.clear();
|
||||
|
||||
// undefined
|
||||
await keyv.get('foo');
|
||||
})();
|
||||
```
|
||||
|
||||
## Application
|
||||
|
||||
Although Keyv can assist in any scenario where you need key-value data, we will focus on setting up a per-guild prefix configuration using [Sqlite](https://www.sqlite.org/).
|
||||
|
||||
<Callout>
|
||||
This section will still work with any provider supported by Keyv. We recommend PostgreSQL for larger applications.
|
||||
|
||||
Do note that "large" here should be interpreted as absolutely massive. Sqlite is used at scale by many companies and products you use every single day. The slight overhead should not be noticable for the application of a Discord bot at all unless you are dealing with super complicated queries or are using specific features that do not exist in sqlite.
|
||||
|
||||
You can find out if sqlite might be a good choice for your project (it very likely is) by reading [their own article](https://www.sqlite.org/whentouse.html) on the topic.
|
||||
|
||||
</Callout>
|
||||
|
||||
### Setup
|
||||
|
||||
```js
|
||||
const { Keyv } = require('keyv');
|
||||
const { Client, Events, GatewayIntentBits } = require('discord.js');
|
||||
const { globalPrefix, token } = require('./config.json');
|
||||
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent],
|
||||
});
|
||||
const prefixes = new Keyv('sqlite://path/to.sqlite');
|
||||
```
|
||||
|
||||
### Command handler
|
||||
|
||||
<Callout type="warn">
|
||||
This guide example is from a time where parsing the message content of Discord messages was the only option for
|
||||
commands. We have since moved on from this and recommend you do the same with [slash
|
||||
commands](../app-creation/handling-commands). It can still serve as an example for using databases in a more
|
||||
integrated example. We hope it can help you understand the core idea and apply it to your own use cases!
|
||||
</Callout>
|
||||
|
||||
```js
|
||||
client.on(Events.MessageCreate, async (message) => {
|
||||
if (message.author.bot) return;
|
||||
|
||||
let args;
|
||||
// handle messages in a guild
|
||||
if (message.guild) {
|
||||
let prefix;
|
||||
|
||||
if (message.content.startsWith(globalPrefix)) {
|
||||
prefix = globalPrefix;
|
||||
} else {
|
||||
// check the guild-level prefix
|
||||
const guildPrefix = await prefixes.get(message.guild.id);
|
||||
if (message.content.startsWith(guildPrefix)) prefix = guildPrefix;
|
||||
}
|
||||
|
||||
// if we found a prefix, setup args; otherwise, this isn't a command
|
||||
if (!prefix) return;
|
||||
args = message.content.slice(prefix.length).trim().split(/\s+/);
|
||||
} else {
|
||||
// handle DMs
|
||||
const slice = message.content.startsWith(globalPrefix) ? globalPrefix.length : 0;
|
||||
args = message.content.slice(slice).split(/\s+/);
|
||||
}
|
||||
|
||||
// get the first space-delimited argument after the prefix as the command
|
||||
const command = args.shift().toLowerCase();
|
||||
});
|
||||
```
|
||||
|
||||
### Prefix command
|
||||
|
||||
Now that you have a command handler, you can make a command to allow people to use your prefix system.
|
||||
|
||||
```js
|
||||
client.on(Events.MessageCreate, async (message) => {
|
||||
// ...
|
||||
if (command === 'prefix') {
|
||||
// if there's at least one argument, set the prefix
|
||||
if (args.length) {
|
||||
await prefixes.set(message.guild.id, args[0]);
|
||||
return message.channel.send(`Successfully set prefix to \`${args[0]}\``);
|
||||
}
|
||||
|
||||
return message.channel.send(`Prefix is \`${(await prefixes.get(message.guild.id)) || globalPrefix}\``);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Callout>
|
||||
You will probably want to set up additional validation, such as required permissions and maximum prefix length.
|
||||
</Callout>
|
||||
|
||||
## Next steps
|
||||
|
||||
Various other applications can use Keyv, such as guild settings; create another instance with a different [namespace](https://github.com/jaredwray/keyv/tree/main/packages/keyv#namespaces) for each setting. Additionally, it can be [extended](https://github.com/jaredwray/keyv/tree/main/packages/keyv#third-party-storage-adapters) to work with whatever storage backend you prefer.
|
||||
|
||||
Check out the [Keyv repository](https://github.com/jaredwray/keyv/tree/main/packages/keyv) for more information.
|
||||
32
apps/guide/content/docs/legacy/meta.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"pages": [
|
||||
"[MessageCircleQuestion][FAQ](/guide/legacy/popular-topics/faq)",
|
||||
"[ArrowDownToLine][Updating to v14](/guide/legacy/additional-info/changes-in-v14)",
|
||||
"[LibraryBig][Documentation](https://discord.js.org/docs)",
|
||||
"[Info][Introduction](/guide/legacy)",
|
||||
"---Setup---",
|
||||
"preparations",
|
||||
"---Your App---",
|
||||
"app-creation",
|
||||
"additional-features",
|
||||
"slash-commands",
|
||||
"interactions",
|
||||
"interactive-components",
|
||||
"popular-topics/display-components",
|
||||
"---More To Know---",
|
||||
"popular-topics",
|
||||
"improving-dev-environment",
|
||||
"additional-info",
|
||||
"miscellaneous",
|
||||
"---Persisting Data---",
|
||||
"...keyv",
|
||||
"sequelize",
|
||||
"---Advanced Concepts---",
|
||||
"...oauth2",
|
||||
"sharding"
|
||||
],
|
||||
"title": "Legacy Guide",
|
||||
"description": "The legacy discord.js guide",
|
||||
"icon": "Book",
|
||||
"root": true
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
title: Cache Customization
|
||||
---
|
||||
|
||||
Sometimes, you would like to be able to customize discord.js's caching behavior in order to reduce memory usage.
|
||||
To this end, discord.js provides you with two ways to do so:
|
||||
|
||||
1. Limiting the size of caches.
|
||||
2. Periodically removing old items from caches.
|
||||
|
||||
<Callout type="error">
|
||||
Customization of caching behavior is an advanced topic. It is very easy to introduce errors if your custom cache is
|
||||
not working as expected.
|
||||
</Callout>
|
||||
|
||||
## Limiting caches
|
||||
|
||||
When creating a new `Client`, you can limit the size of caches, which are specific to certain managers, using the `makeCache` option. Generally speaking, almost all your customizations can be done via the helper functions from the `Options` class.
|
||||
|
||||
Below is the default settings, which will limit message caches.
|
||||
|
||||
```js
|
||||
const { Client, Options } = require('discord.js');
|
||||
|
||||
const client = new Client({
|
||||
makeCache: Options.cacheWithLimits(Options.DefaultMakeCacheSettings),
|
||||
});
|
||||
```
|
||||
|
||||
To change the cache behaviors for a type of manager, add it on top of the default settings. For example, you can make caches for reactions limited to 0 items i.e. the client won't cache reactions at all:
|
||||
|
||||
```js
|
||||
const client = new Client({
|
||||
makeCache: Options.cacheWithLimits({
|
||||
...Options.DefaultMakeCacheSettings,
|
||||
ReactionManager: 0,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="error" title="Unsupported customization">
|
||||
As noted in the documentation, customizing `GuildManager`, `ChannelManager`, `GuildChannelManager`, `RoleManager`, or
|
||||
`PermissionOverwriteManager` is unsupported! Functionality will break with any kind of customization.
|
||||
</Callout>
|
||||
|
||||
We can further customize this by passing options to `LimitedCollection`, a special kind of collection that limits the number of items. In the example below, the client is configured so that:
|
||||
|
||||
1. Only 200 guild members maximum may be cached per `GuildMemberManager` (essentially, per guild).
|
||||
2. We never remove a member if it is the client. This is especially important, so that the client can function correctly in guilds.
|
||||
|
||||
```js
|
||||
const client = new Client({
|
||||
makeCache: Options.cacheWithLimits({
|
||||
...Options.DefaultMakeCacheSettings,
|
||||
ReactionManager: 0,
|
||||
GuildMemberManager: {
|
||||
maxSize: 200,
|
||||
keepOverLimit: (member) => member.id === member.client.user.id,
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
## Sweeping caches
|
||||
|
||||
In addition to limiting caches, you can also periodically sweep and remove old items from caches. When creating a new `Client`, you can customize the `sweepers` option.
|
||||
|
||||
Below is the default settings, which will occasionally sweep threads.
|
||||
|
||||
```js
|
||||
const { Client, Options } = require('discord.js');
|
||||
|
||||
const client = new Client({
|
||||
sweepers: Options.DefaultSweeperSettings,
|
||||
});
|
||||
```
|
||||
|
||||
To change the sweep behavior, you specify the type of cache to sweep (`SweeperKey`) and the options for sweeping (`SweepOptions`). If the type of cache has a lifetime associated with it, such as invites, messages, or threads, then you can set the `lifetime` option to sweep items older than specified. Otherwise, you can set the `filter` option for any type of cache, which will select the items to sweep.
|
||||
|
||||
```js
|
||||
const client = new Client({
|
||||
sweepers: {
|
||||
...Options.DefaultSweeperSettings,
|
||||
messages: {
|
||||
interval: 3_600, // Every hour.
|
||||
lifetime: 1_800, // Remove messages older than 30 minutes.
|
||||
},
|
||||
users: {
|
||||
interval: 3_600, // Every hour.
|
||||
filter: () => (user) => user.bot && user.id !== user.client.user.id, // Remove all bots.
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Callout title="Take some time to consider what you are changing and do some research about what that implies">
|
||||
Take a look at the documentation for which types of cache you can sweep. Also consider what exactly lifetime implies
|
||||
for invites, messages, and threads!
|
||||
</Callout>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 719 B |
BIN
apps/guide/content/docs/legacy/miscellaneous/images/winston.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
255
apps/guide/content/docs/legacy/miscellaneous/useful-packages.mdx
Normal file
@@ -0,0 +1,255 @@
|
||||
---
|
||||
title: Useful Packages
|
||||
---
|
||||
|
||||
Some of the examples in this section are far from complete. We encourage you to look at the official documentation for all packages you are intending to use in your code. We just want to point out some we found useful in our endeavours of developing Discord apps for years and whet your appetite to try and test some of them in your own code base!
|
||||
|
||||
## Day.js
|
||||
|
||||
<Callout>Official documentation: [https://day.js.org/](https://day.js.org/en)</Callout>
|
||||
|
||||
Day.js is a powerful package that parses, validates, manipulates, and displays dates and times in JavaScript.
|
||||
It allows you to quickly and easily format dates in any way you want or parse strings back into JavaScript Date objects.
|
||||
There are many plugins for it that allow you to work with durations and more.
|
||||
|
||||
For example if you wanted to ask your users to give you a date,
|
||||
you can use Day.js to turn it into a Date object you can use in your code:
|
||||
|
||||
```js
|
||||
const input = await interaction.channel.awaitMessages({
|
||||
filter: (m) => m.author.id === interaction.user.id,
|
||||
max: 1,
|
||||
time: 10e3,
|
||||
errors: ['time'],
|
||||
});
|
||||
const date = dayjs(input.first().content).toDate();
|
||||
```
|
||||
|
||||
Using the [duration plugin](https://day.js.org/docs/en/durations/durations), you could tell the user if the date is in the future or the past:
|
||||
|
||||
```js
|
||||
if (date.isValid()) {
|
||||
const now = dayjs();
|
||||
const duration = date - now;
|
||||
const formatted = dayjs.duration(duration, 'ms').format();
|
||||
|
||||
if (duration > 0) {
|
||||
interaction.reply(`The date you gave me is ${formatted} into the future.`);
|
||||
} else {
|
||||
interaction.reply(`The date you gave me is ${formatted} into the past.`);
|
||||
}
|
||||
} else {
|
||||
interaction.reply("You didn't give me a valid date.");
|
||||
}
|
||||
```
|
||||
|
||||
## ms
|
||||
|
||||
<Callout>Official documentation: [https://github.com/vercel/ms](https://github.com/vercel/ms)</Callout>
|
||||
|
||||
Ms is another tool for working with times in JavaScript. However, ms specializes on durations.
|
||||
It allows you to convert times in milliseconds into human-readable formats and vice versa.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
await interaction.reply("Send two messages and I'll tell you how far apart you sent them.");
|
||||
const messages = await interaction.channel.awaitMessages({
|
||||
filter: (m) => m.author.id === interaction.user.id,
|
||||
max: 2,
|
||||
time: 30e3,
|
||||
errors: ['time'],
|
||||
});
|
||||
|
||||
const difference = messages.last().createdTimestamp - messages.first().createdTimestamp;
|
||||
const formatted = ms(difference);
|
||||
|
||||
await interaction.followUp(`You sent the two messages ${formatted} apart.`);
|
||||
```
|
||||
|
||||
## common-tags
|
||||
|
||||
<Callout>
|
||||
Official documentation: [https://github.com/zspecza/common-tags](https://github.com/zspecza/common-tags)
|
||||
</Callout>
|
||||
|
||||
Common-tags is a library all about working with template literals.
|
||||
So far, you have probably only used them for interpolating variables into your strings, but they can do a whole lot more.
|
||||
If you've got time, you should check out [the MDN's documentation about _tagged literals_.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates).
|
||||
|
||||
Ever got annoyed your multi-line strings had nasty bits of indentation in them,
|
||||
but you did not want to remove the indentation in your source code?
|
||||
common-tags got you covered:
|
||||
|
||||
```js
|
||||
const packageName = 'common-tags';
|
||||
|
||||
if (someCondition) {
|
||||
const poem = stripIndents`
|
||||
I like ${packageName}.
|
||||
It makes my strings so pretty,
|
||||
you should use it too.
|
||||
`;
|
||||
|
||||
console.log(poem);
|
||||
}
|
||||
```
|
||||
|
||||
This will print your little poem like expected, but it will not have any tabs or other whitespace on the left.
|
||||
|
||||
But this is just the start! Another set of useful functions are the list-related functions:
|
||||
`inlineLists`, `commaLists`, etc.
|
||||
With those, you can easily interpolate arrays into your strings without them looking ugly:
|
||||
|
||||
```js
|
||||
const options = ['add', 'delete', 'edit'];
|
||||
|
||||
// -> Do you want me to add, delete or edit the channel?
|
||||
interaction.reply(oneLineCommaListsOr`
|
||||
Do you want me to ${options} the channel?
|
||||
`);
|
||||
```
|
||||
|
||||
Check the the documentation to find more useful functions.
|
||||
|
||||
## chalk
|
||||
|
||||
<Callout>Official documentation: [https://www.npmjs.com/package/chalk](https://www.npmjs.com/package/chalk)</Callout>
|
||||
|
||||
Chalk is not exactly useful for Discord bots themselves, but it will make your terminal output a lot prettier and organized.
|
||||
This package lets you color and style your `console.log`s in many different ways; No more simple white on black.
|
||||
|
||||
Let's say you want your error messages to be easily visible; Let us give them a nice red color:
|
||||
|
||||
```js
|
||||
console.error(chalk.redBright('FATAL ERROR'), 'Something really bad happened!');
|
||||
```
|
||||
|
||||

|
||||
|
||||
You can also chain multiple different multipliers.
|
||||
If you wanted to have green text, a grey background, and have it all underlined, that is possible:
|
||||
|
||||
```js
|
||||
console.log(chalk.green.bgBrightBlack.underline('This is so pretty.'));
|
||||
```
|
||||
|
||||

|
||||
|
||||
## pino
|
||||
|
||||
<Callout>Official documentation: [getpino.io](https://getpino.io)</Callout>
|
||||
|
||||
Pino is a Node.js logger with a very low overhead. But why does that even matter, if `console.log()` exists? Well, `console.log()` is quite slow and not very versatile. Whenever you make a call to `console.log()` your program halts and cannot do anything until the logging is finished.
|
||||
|
||||
To get started, install the package:
|
||||
|
||||
```sh tab="sh"
|
||||
npm install pino
|
||||
npm install -g pino-pretty
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add pino
|
||||
yarn global add pino-pretty
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add pino
|
||||
pnpm add --global pino-pretty
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add pino
|
||||
bun add --global pino-pretty
|
||||
```
|
||||
|
||||
Pino is highly configurable, so we heavily recommend you take a look at their documentation yourself.
|
||||
|
||||
To use the same logger across the project you can put the following code into it's own file, for example `logger.js` and import it when needed:
|
||||
|
||||
```js
|
||||
const pino = require('pino');
|
||||
const logger = pino();
|
||||
module.exports = logger;
|
||||
```
|
||||
|
||||
```js
|
||||
const { Client, Events, GatewayIntentBits } = require('discord.js');
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
const logger = require('./logger');
|
||||
|
||||
client.once(Events.ClientReady, () => logger.info('The bot is online'));
|
||||
client.on(Events.Debug, (info) => logger.debug(info));
|
||||
client.on(Events.Warn, (info) => logger.warn(info));
|
||||
client.on(Events.Error, (error) => logger.error(error));
|
||||
|
||||
client.login('your-token-goes-here');
|
||||
```
|
||||
|
||||
Pino logs in a json format, so other programs and services like log aggregators can easily parse and work with the output. This is very useful for production systems, but quite tedious to read during development. This is why you installed `pino-pretty` earlier. Instead of formatting the log output itself the developers recommended that you [pipe](<https://en.wikipedia.org/wiki/Pipeline_(Unix)>) the log output to other services instead. `pino-pretty` is a formatter you can use for easy-to-read logs during development.
|
||||
|
||||
We recommend you set `pino-pretty` up in a package script in your `package.json` file, rather than typing the pipeline out every time. Please read our [guide section on package scripts](../improving-dev-environment/package-json-scripts), if you are not sure what we're talking about here.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "A Discord bot!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node .",
|
||||
"lint": "eslint .",
|
||||
"dev": "node . | pino-pretty -i pid,hostname -t 'yyyy-mm-dd HH:MM:ss'" // [!code ++]
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
If you are using PowerShell, you have to use a package script for `pino-pretty`. PowerShell handles pipelines in a way
|
||||
that prevents logging. The command line interface is not affected.
|
||||
</Callout>
|
||||
|
||||
In the example above, further arguments are passed to `pino-pretty` to modify the generated output. `-i pid,hostname` hides these two elements from logged lines and `-t yyyy-mm-dd HH:MM:ss` formats the timestamp into an easy to use format. Try out what works for you! The official [pino-pretty documentation](https://github.com/pinojs/pino-pretty) explains all possible arguments.
|
||||
|
||||
To start your bot with prettified input you run the `dev` script via your package manager of choice:
|
||||
|
||||
```sh tab="npm"
|
||||
npm run dev
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun run dev
|
||||
```
|
||||
|
||||
Pino is very flexible, supports custom log levels, worker threads and many more features. Please check out the [official documentation](https://getpino.io) if you want to up your pino game! Below we show an alternative for a production setup. Using this code, you will be logging the raw json objects into a file, instead of printing to your console:
|
||||
|
||||
```js
|
||||
const pino = require('pino');
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination: './log.json' },
|
||||
});
|
||||
const logger = pino(transport);
|
||||
module.exports = logger;
|
||||
```
|
||||
|
||||
## i18next
|
||||
|
||||
<Callout>Official documentation: [https://www.i18next.com](https://www.i18next.com)</Callout>
|
||||
|
||||
i18next is an internationalization-framework for JavaScript. It is beneficial to translate your bot's user-facing messages into various languages based on the server it is used in.
|
||||
|
||||
Covering an entire use case example for internationalization would be out of this guide's scope and requires some more explanation as to how the system operates. Please refer to the official documentation linked above for an in-depth usage guide.
|
||||
BIN
apps/guide/content/docs/legacy/oauth2/images/add-redirects.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 46 KiB |
BIN
apps/guide/content/docs/legacy/oauth2/images/generate-url.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
apps/guide/content/docs/legacy/oauth2/images/oauth2-app-page.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
302
apps/guide/content/docs/legacy/oauth2/oauth2.mdx
Normal file
@@ -0,0 +1,302 @@
|
||||
---
|
||||
title: OAuth2
|
||||
---
|
||||
|
||||
OAuth2 enables application developers to build applications that utilize authentication and data from the Discord API. Developers can use this to create things such as web dashboards to display user info, fetch linked third-party accounts like Twitch or Steam, access users' guild information without actually being in the guild, and much more. OAuth2 can significantly extend the functionality of your bot if used correctly.
|
||||
|
||||
## A quick example
|
||||
|
||||
### Setting up a basic web server
|
||||
|
||||
Most of the time, websites use OAuth2 to get information about their users from an external service. In this example, we will use [`express`](https://expressjs.com/) to create a web server to use a user's Discord information to greet them. Start by creating three files: `config.json`, `index.js`, and `index.html`.
|
||||
|
||||
`config.json` will be used to store the client ID, client secret, and server port.
|
||||
|
||||
```json title="config.json" lineNumbers
|
||||
{
|
||||
"clientId": "",
|
||||
"clientSecret": "",
|
||||
"port": 53134
|
||||
}
|
||||
```
|
||||
|
||||
`index.js` will be used to start the server and handle requests. When someone visits the index page (`/`), an HTML file will be sent in response.
|
||||
|
||||
```js title="index.js" lineNumbers
|
||||
const express = require('express');
|
||||
const { port } = require('./config.json');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.get('/', (request, response) => {
|
||||
return response.sendFile('index.html', { root: '.' });
|
||||
});
|
||||
|
||||
app.listen(port, () => console.log(`App listening at http://localhost:${port}`));
|
||||
```
|
||||
|
||||
`index.html` will be used to display the user interface and OAuth data once logged in.
|
||||
|
||||
```html title="index.html" lineNumbers
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Discord OAuth2 App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="info">Hoi!</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
After running `npm i express`, you can start your server with `node index.js`. Once started, connect to `http://localhost:53134`, and you should see "Hoi!".
|
||||
|
||||
<Callout>
|
||||
Although we're using express, there are many other alternatives to handle a web server, such as:
|
||||
[fastify](https://www.fastify.io/), [koa](https://koajs.com/), and the [native Node.js http
|
||||
module](https://nodejs.org/api/http.html).
|
||||
</Callout>
|
||||
|
||||
### Getting an OAuth2 URL
|
||||
|
||||
Now that you have a web server up and running, it's time to get some information from Discord. Open [your Discord applications](https://discord.com/developers/applications/), create or select an application, and head over to the "OAuth2" page.
|
||||
|
||||

|
||||
|
||||
Take note of the `client id` and `client secret` fields. Copy these values into your `config.json` file; you'll need them later. For now, add a redirect URL to `http://localhost:53134` like so:
|
||||
|
||||

|
||||
|
||||
Once you've added your redirect URL, you will want to generate an OAuth2 URL. Lower down on the page, you can conveniently find an OAuth2 URL Generator provided by Discord. Use this to create a URL for yourself with the `identify` scope.
|
||||
|
||||

|
||||
|
||||
The `identify` scope will allow your application to get basic user information from Discord. You can find a list of all scopes [here](https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes).
|
||||
|
||||
### Implicit grant flow
|
||||
|
||||
You have your website, and you have a URL. Now you need to use those two things to get an access token. For basic applications like [SPAs](https://en.wikipedia.org/wiki/Single-page_application), getting an access token directly is enough. You can do so by changing the `response_type` in the URL to `token`. However, this means you will not get a refresh token, which means the user will have to explicitly re-authorize when this access token has expired.
|
||||
|
||||
After you change the `response_type`, you can test the URL right away. Visiting it in your browser, you will be directed to a page that looks like this:
|
||||
|
||||

|
||||
|
||||
You can see that by clicking `Authorize`, you allow the application to access your username and avatar. Once you click through, it will redirect you to your redirect URL with a [fragment identifier](https://en.wikipedia.org/wiki/Fragment_identifier) appended to it. You now have an access token and can make requests to Discord's API to get information on the user.
|
||||
|
||||
Modify `index.html` to add your OAuth2 URL and to take advantage of the access token if it exists. Even though [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) is for working with query strings, it can work here because the structure of the fragment follows that of a query string after removing the leading "#".
|
||||
|
||||
```html title="index.html" lineNumbers=11
|
||||
<div id="info">Hoi!</div>
|
||||
<a id="login" style="display: none;" href="your-oauth2-URL-here">Identify Yourself</a>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
const fragment = new URLSearchParams(window.location.hash.slice(1));
|
||||
const [accessToken, tokenType] = [fragment.get('access_token'), fragment.get('token_type')];
|
||||
|
||||
if (!accessToken) {
|
||||
return (document.getElementById('login').style.display = 'block');
|
||||
}
|
||||
|
||||
fetch('https://discord.com/api/users/@me', {
|
||||
headers: {
|
||||
authorization: `${tokenType} ${accessToken}`,
|
||||
},
|
||||
})
|
||||
.then((result) => result.json())
|
||||
.then((response) => {
|
||||
const { username, discriminator } = response;
|
||||
document.getElementById('info').innerText += ` ${username}#${discriminator}`;
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
Here you grab the access token and type from the URL if it's there and use it to get info on the user, which is then used to greet them. The response you get from the [`/api/users/@me` endpoint](https://discord.com/developers/docs/resources/user#get-current-user) is a [user object](https://discord.com/developers/docs/resources/user#user-object) and should look something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "123456789012345678",
|
||||
"username": "User",
|
||||
"discriminator": "0001",
|
||||
"avatar": "1cc0a3b14aec3499632225c708451d67",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
In the following sections, we'll go over various details of Discord and OAuth2.
|
||||
|
||||
## More details
|
||||
|
||||
### The state parameter
|
||||
|
||||
OAuth2's protocols provide a `state` parameter, which Discord supports. This parameter helps prevent [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) attacks and represents your application's state. The state should be generated per user and appended to the OAuth2 URL. For a basic example, you can use a randomly generated string encoded in Base64 as the state parameter.
|
||||
|
||||
```js
|
||||
function generateRandomString() {
|
||||
let randomString = '';
|
||||
const randomNumber = Math.floor(Math.random() * 10);
|
||||
|
||||
for (let i = 0; i < 20 + randomNumber; i++) {
|
||||
randomString += String.fromCharCode(33 + Math.floor(Math.random() * 94));
|
||||
}
|
||||
|
||||
return randomString;
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
// ...
|
||||
if (!accessToken) {
|
||||
const randomString = generateRandomString();
|
||||
localStorage.setItem('oauth-state', randomString);
|
||||
|
||||
document.getElementById('login').href += `&state=${btoa(randomString)}`;
|
||||
return (document.getElementById('login').style.display = 'block');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When you visit a URL with a `state` parameter appended to it and then click `Authorize`, you'll notice that after being redirected, the URL will also have the `state` parameter appended, which you should then check against what was stored. You can modify the script in your `index.html` file to handle this.
|
||||
|
||||
```html title="index.html"
|
||||
<script>
|
||||
// ... // [!code focus:15]
|
||||
const fragment = new URLSearchParams(window.location.hash.slice(1));
|
||||
const [accessToken, tokenType, state] = [
|
||||
fragment.get('access_token'),
|
||||
fragment.get('token_type'),
|
||||
fragment.get('state'),
|
||||
];
|
||||
|
||||
if (!accessToken) {
|
||||
// ...
|
||||
}
|
||||
|
||||
if (localStorage.getItem('oauth-state') !== atob(decodeURIComponent(state))) {
|
||||
return console.log('You may have been clickjacked!');
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
<Callout type="error" title="Don't forgo security for a tiny bit of convenience!" />
|
||||
|
||||
### Authorization code grant flow
|
||||
|
||||
What you did in the quick example was go through the `implicit grant` flow, which passed the access token straight to the user's browser. This flow is great and simple, but you don't get to refresh the token without the user, and it is less secure than going through the `authorization code grant` flow. This flow involves receiving an access code, which your server then exchanges for an access token. Notice that this way, the access token never actually reaches the user throughout the process.
|
||||
|
||||
Unlike the [implicit grant flow](#implicit-grant-flow), you need an OAuth2 URL where the `response_type` is `code`. After you change the `response_type`, try visiting the link and authorizing your application. You should notice that instead of a hash, the redirect URL now has a single query parameter appended to it, i.e. `?code=ACCESS_CODE`. Modify your `index.js` file to access the parameter from the URL if it exists. In express, you can use the `request` parameter's `query` property.
|
||||
|
||||
```js
|
||||
app.get('/', (request, response) => {
|
||||
console.log(`The access code is: ${request.query.code}`);
|
||||
return response.sendFile('index.html', { root: '.' });
|
||||
});
|
||||
```
|
||||
|
||||
Now you have to exchange this code with Discord for an access token. To do this, you need your `client_id` and `client_secret`. If you've forgotten these, head over to [your applications](https://discord.com/developers/applications) and get them. You can use [`undici`](https://www.npmjs.com/package/undici) to make requests to Discord.
|
||||
|
||||
To install undici, run the following command:
|
||||
|
||||
```sh tab="npm"
|
||||
npm install undici
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add undici
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add undici
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add undici
|
||||
```
|
||||
|
||||
Require `undici` and make your request.
|
||||
|
||||
<Callout>
|
||||
If you are used to the Fetch API and want to use that instead of how `undici` does it, instead of using
|
||||
`undici#request`, use `undici#fetch` with the same parameters as node-fetch.
|
||||
</Callout>
|
||||
|
||||
```js title="index.js"
|
||||
const { request } = require('undici'); // [!code ++]
|
||||
const express = require('express');
|
||||
const { port } = require('./config.json'); // [!code --]
|
||||
const { clientId, clientSecret, port } = require('./config.json'); // [!code ++]
|
||||
|
||||
const app = express();
|
||||
|
||||
app.get('/', async ({ query }, response) => {
|
||||
// [!code ++:28]
|
||||
const { code } = query;
|
||||
|
||||
if (code) {
|
||||
try {
|
||||
const tokenResponseData = await request('https://discord.com/api/oauth2/token', {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: `http://localhost:${port}`,
|
||||
scope: 'identify',
|
||||
}).toString(),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
});
|
||||
|
||||
const oauthData = await tokenResponseData.body.json();
|
||||
console.log(oauthData);
|
||||
} catch (error) {
|
||||
// NOTE: An unauthorized token will not throw an error
|
||||
// tokenResponseData.statusCode will be 401
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return response.sendFile('index.html', { root: '.' });
|
||||
});
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The content-type for the token URL must be `application/x-www-form-urlencoded`, which is why `URLSearchParams` is
|
||||
used.
|
||||
</Callout>
|
||||
|
||||
Now try visiting your OAuth2 URL and authorizing your application. Once you're redirected, you should see an [access token response](https://discord.com/developers/docs/topics/oauth2#authorization-code-grant-access-token-response) in your console.
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "an access token",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 604800,
|
||||
"refresh_token": "a refresh token",
|
||||
"scope": "identify"
|
||||
}
|
||||
```
|
||||
|
||||
With an access token and a refresh token, you can once again use the [`/api/users/@me` endpoint](https://discord.com/developers/docs/resources/user#get-current-user) to fetch the [user object](https://discord.com/developers/docs/resources/user#user-object).
|
||||
|
||||
```js
|
||||
const userResult = await request('https://discord.com/api/users/@me', {
|
||||
headers: {
|
||||
authorization: `${oauthData.token_type} ${oauthData.access_token}`,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(await userResult.body.json());
|
||||
```
|
||||
|
||||
<Callout
|
||||
type="error"
|
||||
title="To maintain security, store the access token server-side but associate it with a session ID that you generate for the user."
|
||||
/>
|
||||
|
||||
## Additional reading
|
||||
|
||||
[RFC 6759](https://tools.ietf.org/html/rfc6749)
|
||||
[Discord Docs for OAuth2](https://discord.com/developers/docs/topics/oauth2)
|
||||
98
apps/guide/content/docs/legacy/popular-topics/audit-logs.mdx
Normal 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).
|
||||
324
apps/guide/content/docs/legacy/popular-topics/canvas.mdx
Normal 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] });
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
<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);
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
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 --]
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
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);
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
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();
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
<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);
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
<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:
|
||||
|
||||

|
||||
|
||||
After adjustment:
|
||||
|
||||

|
||||
|
||||
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 --]
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
And that's it! We have covered the basics of image manipulation, text generation, and loading from a remote source.
|
||||
191
apps/guide/content/docs/legacy/popular-topics/collectors.mdx
Normal 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 👍 emoji–in the default skin tone specifically, so be wary of that. It will also check that the person who reacted shares the same id as the author of the original message that the collector was assigned to.
|
||||
|
||||
```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).
|
||||
@@ -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,
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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,
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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,
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
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,
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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,
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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,
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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,
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
230
apps/guide/content/docs/legacy/popular-topics/embeds.mdx
Normal 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)
|
||||
234
apps/guide/content/docs/legacy/popular-topics/errors.mdx
Normal 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`
|
||||
399
apps/guide/content/docs/legacy/popular-topics/faq.mdx
Normal 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>
|
||||
73
apps/guide/content/docs/legacy/popular-topics/formatters.mdx
Normal 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);
|
||||
```
|
||||
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 246 KiB |
|
After Width: | Height: | Size: 249 KiB |
|
After Width: | Height: | Size: 249 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 227 KiB |
BIN
apps/guide/content/docs/legacy/popular-topics/images/canvas.jpg
Normal file
|
After Width: | Height: | Size: 832 KiB |
|
After Width: | Height: | Size: 269 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 832 KiB |
80
apps/guide/content/docs/legacy/popular-topics/intents.mdx
Normal 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`).
|
||||
4
apps/guide/content/docs/legacy/popular-topics/meta.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"defaultOpen": true,
|
||||
"pages": ["!faq", "...", "!display-components"]
|
||||
}
|
||||
74
apps/guide/content/docs/legacy/popular-topics/partials.mdx
Normal 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>
|
||||
@@ -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>
|
||||
370
apps/guide/content/docs/legacy/popular-topics/permissions.mdx
Normal 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()`.
|
||||
327
apps/guide/content/docs/legacy/popular-topics/reactions.mdx
Normal 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>
|
||||
162
apps/guide/content/docs/legacy/popular-topics/threads.mdx
Normal 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!
|
||||
198
apps/guide/content/docs/legacy/popular-topics/webhooks.mdx
Normal 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.
|
||||
|
||||

|
||||
|
||||
If you already have created a webhook, the webhooks tab will look like this; you will need to click the `View Webhooks` button.
|
||||
|
||||

|
||||
|
||||
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 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]
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Adding your App
|
||||
---
|
||||
|
||||
After you set up a bot application, you'll notice that it's not in any servers yet. So how does that work?
|
||||
|
||||
Before you're able to see your bot in your own (or other) servers, you'll need to add it by creating and using a unique invite link using your bot application's client id.
|
||||
|
||||
## Bot invite links
|
||||
|
||||
The basic version of one such link looks like this:
|
||||
|
||||
```
|
||||
https://discord.com/api/oauth2/authorize?client_id=123456789012345678&permissions=0&scope=bot%20applications.commands
|
||||
```
|
||||
|
||||
The structure of the URL is quite simple:
|
||||
|
||||
- `https://discord.com/api/oauth2/authorize` is Discord's standard structure for authorizing an OAuth2 application (such as your bot application) for entry to a Discord server.
|
||||
- `client_id=...` is to specify _which_ application you want to authorize. You'll need to replace this part with your client's id to create a valid invite link.
|
||||
- `permissions=...` describes what permissions your bot will have on the server you are adding it to.
|
||||
- `scope=bot%20applications.commands` specifies that you want to add this application as a Discord bot, with the ability to create slash commands.
|
||||
|
||||
<Callout type="error">
|
||||
If you get an error message saying "Bot requires a code grant", head over to your application's settings and disable
|
||||
the "Require OAuth2 Code Grant" option. You shouldn't enable this option unless you know why you need to.
|
||||
</Callout>
|
||||
|
||||
## Creating and using your invite link
|
||||
|
||||
To create an invite link, head back to the [developer portal](https://discord.com/developers/applications/me) page under the "Applications" section, click on your bot application, and open the OAuth2 page.
|
||||
|
||||
In the sidebar, you'll find the OAuth2 URL generator. Select the `bot` and `applications.commands` options. Once you select the `bot` option, a list of permissions will appear, allowing you to configure the permissions your bot needs.
|
||||
|
||||
Grab the link via the "Copy" button and enter it in your browser. You should see something like this (with your bot's username and avatar):
|
||||
|
||||

|
||||
|
||||
Choose the server you want to add it to and click "Authorize". Do note that you'll need the "Manage Server" permission on a server to add your bot there. This should then present you a nice confirmation message:
|
||||
|
||||

|
||||
|
||||
Congratulations! You've successfully added your bot to your Discord server. It should show up in your server's member list somewhat like this:
|
||||
|
||||

|
||||
56
apps/guide/content/docs/legacy/preparations/app-setup.mdx
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Application Setup
|
||||
---
|
||||
|
||||
## Creating your bot
|
||||
|
||||
Now that you've installed Node, discord.js, and hopefully a linter, you're almost ready to start coding! The next step you need to take is setting up an actual Discord bot application via Discord's website.
|
||||
|
||||
It's effortless to create one. The steps you need to take are as follows:
|
||||
|
||||
1. Open the [Discord developer portal](https://discord.com/developers/applications) and log into your account.
|
||||
2. Click on the "New Application" button.
|
||||
3. Enter a name and confirm the pop-up window by clicking the "Create" button.
|
||||
|
||||
You should see a page like this:
|
||||
|
||||

|
||||
|
||||
You can edit your application's name, description, and avatar here. Once you've done that, then congratulations—you're now the proud owner of a shiny new Discord bot! You're not entirely done, though.
|
||||
|
||||
## Your bot's token
|
||||
|
||||
<Callout type="error" title="Important">
|
||||
**This section is critical**, so pay close attention. It explains what your bot token is, as well as the security
|
||||
aspects of it.
|
||||
</Callout>
|
||||
|
||||
After creating a bot user, you'll see a section like this:
|
||||
|
||||

|
||||
|
||||
In this panel, you can give your bot a snazzy avatar, set its username, and make it public or private. Your bot's token will be revealed when you press the "Reset Token" button and confirm. When we ask you to paste your bot's token somewhere, this is the value that you need to put in. If you happen to lose your bot's token at some point, you need to come back to this page and reset your bot's token again which will reveal the new token, invalidating all old ones.
|
||||
|
||||
### What is a token, anyway?
|
||||
|
||||
A token is essentially your bot's password; it's what your bot uses to login to Discord. With that said, **it is vital that you do not ever share this token with anybody, purposely or accidentally**. If someone does manage to get a hold of your bot's token, they can use your bot as if it were theirs—this means they can perform malicious acts with it.
|
||||
|
||||
Tokens look like this: `NzkyNzE1NDU0MTk2MDg4ODQy.X-hvzA.Ovy4MCQywSkoMRRclStW4xAYK7I` (don't worry, we immediately reset this token before even posting it here!). If it's any shorter and looks more like this: `kxbsDRU5UfAaiO7ar9GFMHSlmTwYaIYn`, you copied your client secret instead. Make sure to copy the token if you want your bot to work!
|
||||
|
||||
### Token leak scenario
|
||||
|
||||
Let's imagine that you have a bot on over 1,000 servers, and it took you many, many months of coding and patience to get it on that amount. Your bot's token gets leaked somewhere, and now someone else has it. That person can:
|
||||
|
||||
- Spam every server your bot is on;
|
||||
- DM spam as many users as possible;
|
||||
- Delete as many channels as possible;
|
||||
- Kick or ban as many server members as possible;
|
||||
- Make your bot leave all of the servers it has joined;
|
||||
|
||||
All that and much, much more. Sounds pretty terrible, right? So make sure to keep your bot's token as safe as possible!
|
||||
|
||||
<Callout type="error" title="Resetting Your Application Token">
|
||||
If your bot token has been compromised by committing it to a public repository, posting it in discord.js support etc.
|
||||
or otherwise see your bot's token in danger, return to this page and press "Reset Token". This will invalidate all old
|
||||
tokens belonging to your bot. Keep in mind that you will need to update your bot's token where you used it before.
|
||||
</Callout>
|
||||
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 115 KiB |
129
apps/guide/content/docs/legacy/preparations/installation.mdx
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
title: Installing Node.js and discord.js
|
||||
---
|
||||
|
||||
import { Grid2x2, Apple, Cpu, Zap, Package } from 'lucide-react';
|
||||
|
||||
## Installing Node.js
|
||||
|
||||
To use discord.js, you'll need to install the latest LTS version of [Node.js](https://nodejs.org/).
|
||||
|
||||
<Callout>
|
||||
To check if you already have Node installed on your machine \(e.g., if you're using a VPS\), run `node -v` in your
|
||||
terminal. It is recommended to use the latest LTS version of Node.
|
||||
</Callout>
|
||||
|
||||
<Cards>
|
||||
<Card icon={<Zap />} title="Volta" href="https://volta.sh/">
|
||||
Use volta to manage your node version cross-platform.
|
||||
</Card>
|
||||
<Card icon={<Package />} title="Fast Node Manager" href="https://github.com/Schniz/fnm">
|
||||
Use `Fast node Manager` to manage your node version cross-platform.
|
||||
</Card>
|
||||
<Card icon={<Grid2x2 />} title="Windows" href="https://nodejs.org/en/download">
|
||||
Download the latest version from the Node.js website, open the downloaded file, and follow the steps from the
|
||||
installer.
|
||||
</Card>
|
||||
<Card icon={<Apple />} title="macOS" href="https://nodejs.org/en/download">
|
||||
Download the latest version from the Node.js website, open the package installer, and follow the instructions.
|
||||
Alternatively you can use a package manager like Homebrew with the command `brew install node`
|
||||
</Card>
|
||||
<Card icon={<Cpu />} title="Linux" href="https://nodejs.org/en/download">
|
||||
Determine how you should install Node with out package manager of choice.
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Preparing the essentials
|
||||
|
||||
To use discord.js, you'll need to install it via npm \(Node's package manager\). npm comes with every Node installation, so you don't have to worry about installing that. However, before you install anything, you should set up a new project folder.
|
||||
|
||||
Navigate to a suitable place on your machine and create a new folder named `discord-bot` (or whatever you want). Next you'll need to open your terminal.
|
||||
|
||||
## Opening the terminal
|
||||
|
||||
<Callout>
|
||||
If you use [Visual Studio Code](https://code.visualstudio.com/), you can press <kbd>Ctrl</kbd>
|
||||
<kbd>`</kbd> (backtick) to open its integrated terminal.
|
||||
</Callout>
|
||||
|
||||
On Windows, either:
|
||||
|
||||
- Hold <kbd>Shift</kbd> and right-click inside your project directory and choose the "Open command window here" option
|
||||
- Press <kbd>Win</kbd> <kbd>R</kbd> and run `cmd.exe`, and then `cd` into your project directory
|
||||
|
||||
On macOS, either:
|
||||
|
||||
- Open Launchpad or Spotlight and search for "Terminal"
|
||||
- In your "Applications" folder, under "Utilities", open the Terminal app
|
||||
|
||||
On Linux, you can quickly open the terminal with <kbd>Ctrl</kbd> <kbd>Alt</kbd> <kbd>T</kbd>.
|
||||
|
||||
With the terminal open, run the `node -v` command to make sure you've successfully installed Node.js.
|
||||
|
||||
## Initiating a project folder
|
||||
|
||||
```sh tab="npm"
|
||||
npm init
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn init
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm init
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun init
|
||||
```
|
||||
|
||||
This is the next command you'll be running. This command creates a `package.json` file for you, which will keep track of the dependencies your project uses, as well as other info.
|
||||
|
||||
This command will ask you a sequence of questions–you should fill them out as you see fit. If you're not sure of something or want to skip it as a whole, leave it blank and press enter.
|
||||
|
||||
To get started quickly, you can run the following command to have it fill out everything for you.
|
||||
|
||||
```sh tab="npm"
|
||||
npm init -y
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn init -y
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm init
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun init -y
|
||||
```
|
||||
|
||||
Once you're done with that, you're ready to install discord.js!
|
||||
|
||||
## Installing discord.js
|
||||
|
||||
Now that you've installed Node.js and know how to open your console and run commands, you can finally install discord.js! Run the following command in your terminal:
|
||||
|
||||
```sh tab="npm"
|
||||
npm install discord.js
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add discord.js
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add discord.js
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add discord.js
|
||||
```
|
||||
|
||||
And that's it! With all the necessities installed, you're almost ready to start coding your bot.
|
||||
|
||||
## Installing a linter
|
||||
|
||||
While you are coding, it's possible to run into numerous syntax errors or code in an inconsistent style. You should [install a linter](./linter) to ease these troubles. While code editors generally can point out syntax errors, linters coerce your code into a specific style as defined by the configuration. While this is not required, it is advised.
|
||||