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

* feat: initial attempt at porting legacy guide

* feat: completion of legacy guide backport

* chore: lockfile shenanigans

* fix: handle svgs

* fix: replace svg with mermaid integration

* chore: format

* chore: remove unnecssary bullet

* chore: cleanup code highlights

* chore: explicit return

* chore: move display components after interactive components in sidebar

* chore: voice

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

* feat: subguide-based accent styles

* chore: don't list faq twice

* chore: mention display components in interactive components

* fix: remove unoccs/order rule from guide

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

* refactor: use `<kbd>`

* refactor: more kbd use

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

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

* chore: fix typos

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

* chore: fix typos

* chore: fix links regarding secret stores across coding platforms

* chore: fix typo

* chore: link node method directly

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

* chore: typos

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

* chore: typo

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

* fix: prevent v14 changes from being listed twice

* chore: prefer relative links

* chore: missed link conversion

* chore: missed link conversion

* chore: fix link

* chore: remove legacy code highlight markers

* chore: rephrase and extend contributing guidelines

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

* chore: move introduction in sidebar

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

* fix: replace outdated link

* fix: update voice dependencies

* chore: update node install instructions

* fix: list in missing access callout

* chore: match bun env file format

* chore: restore ffmpeg disclaimer

* fix: lockfile conflict

* chore: action row typo

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

* chore: no longer use at-next for pino

---------

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

View File

@@ -0,0 +1,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.

View File

@@ -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.