Manejo de comandos

A menos que tu proyecto bot sea pequeño, no es muy buena idea tener un único archivo con una cadena gigante if/else if o switch para los comandos. Si quieres implementar características en tu bot y hacer tu proceso de desarrollo mucho menos doloroso, querrás implementar un manejador de comandos. ¡Empecemos con ello!

Cargando archivos de comando

Ahora que tus archivos de comandos han sido creados, tu bot necesita cargar estos archivos al inicio.

En tu archivo index.js, haz estas adiciones a la plantilla base:

const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, Events, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');

const client = new Client({ 
	intents: [GatewayIntentBits.Guilds] 
});

client.commands = new Collection();
1
2
3
4
5
6
7
8
9
10

Recomendamos adjuntar una propiedad .commands a su instancia de cliente para que pueda acceder a sus comandos en otros archivos. El resto de los ejemplos de esta guía seguirán esta convención. Para los usuarios de TypeScript, recomendamos extender la clase Client base para añadir esta propiedad, castingopen in new window, o expandir los types de cierto móduloopen in new window.

CONSEJO

  • El módulo fsopen in new window es el módulo nativo del sistema de archivos de Node. fs se utiliza para leer el directorio commands e identificar nuestros archivos de comandos.
  • El módulo pathopen in new window es el módulo nativo de utilidad de rutas de Node. path ayuda a construir rutas para acceder a archivos y directorios. Una de las ventajas del módulo path es que detecta automáticamente el sistema operativo y utiliza los joiners apropiados.
  • La clase <DocsLink section="collection" path="class/Collection" /> extiende la clase nativa de JavaScript Mapopen in new window, e incluye una funcionalidad más extensa y útil. La clase Collection se utiliza para almacenar y recuperar eficientemente comandos para su ejecución.

A continuación, utilizando los módulos importados anteriormente, recupera dinámicamente tus archivos de comandos con algunas adiciones más al archivo index.js:

client.commands = new Collection();

const commandsPath = path.join(__dirname, 'commands');
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);
	// Establecer un nuevo elemento en la colección, siendo la llave el nombre del comando y el valor el módulo exportado.
	if ('data' in command && 'execute' in command) {
		client.commands.set(command.data.name, command);
	} else {
		console.log(`[ADVERTENCIA] El comando en ${filePath} le falta una propiedad "data" o "execute".`);
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Primero, path.join()open in new window ayuda a construir una ruta al directorio commands. A continuación, el método fs.readdirSync()open in new window lee la ruta al directorio y devuelve una lista con todos los nombres de archivos que contiene, actualmente ['ping.js', 'server.js', 'user.js']. Para garantizar que sólo se procesan los archivos de comandos, Array.filter() elimina cualquier archivo no JavaScript del array.

Una vez identificados los archivos correctos, el último paso es recorrer el array y colocar dinámicamente cada comando en la colección client.commands. Para cada archivo que se carga, comprueba que tiene al menos las propiedades data y execute. Esto ayuda a prevenir errores resultantes de la carga de archivos de comandos vacíos, inacabados o incorrectos mientras aún estás desarrollando.

Recibiendo comandos

Cada comando de barra es una interacción, así que para responder a un comando, necesitas crear un listener para el evento <DocsLink path="class/Client?scrollTo=e-interactionCreate" /> que ejecutará código cuando tu aplicación reciba una interacción. Coloca el siguiente código en el archivo index.js que creaste anteriormente.

client.on(Events.InteractionCreate, interaction => {
	console.log(interaction);
});
1
2
3

No todas las interacciones son comandos de barra (por ejemplo, las interacciones MessageComponent). Asegúrate de manejar sólo comandos de barra en esta función haciendo uso del método <DocsLink path="class/BaseInteraction?scrollTo=isChatInputCommand" /> para salir del manejador si se encuentra otro tipo. Este método también proporciona protección tipográfica para los usuarios de TypeScript, reduciendo el tipo de BaseInteraction a <DocsLink path="class/ChatInputCommandInteraction" />.

client.on(Events.InteractionCreate, interaction => {
	if (!interaction.isChatInputCommand()) return;
	console.log(interaction);
});
1
2
3
4

Ejecutando comandos

Cuando tu bot recibe un evento <DocsLink path="class/Client?scrollTo=e-interactionCreate" />, el objeto de interacción contiene toda la información que necesitas para recuperar y ejecutar dinámicamente tus comandos.

Veamos de nuevo el comando ping. Observa la función execute() que responderá a la interacción con "¡Pong!".

module.exports = {
	data: new SlashCommandBuilder()
		.setName('ping')
		.setDescription('Responde con Pong! 🏓'),
	async execute(interaction) {
		await interaction.reply('Pong! 🏓');
	},
};

1
2
3
4
5
6
7
8
9

En primer lugar, debe obtener el comando correspondiente de la colección client.commands basándose en interaction.commandName. Su instancia <DocsLink path="class/Client">Client``</DocsLink> está siempre disponible a través de interaction.client. Si no se encuentra ningún comando que coincida, registra un error en la consola e ignora el evento.

Con el comando correcto identificado, todo lo que queda por hacer es llamar al método .execute() del comando y pasar la variable interaction como parametro. En caso de que algo vaya mal, captura y registra cualquier error en la consola.

client.on(Events.InteractionCreate, async interaction => {
	if (!interaction.isChatInputCommand()) return;
	const command = interaction.client.commands.get(interaction.commandName);

	if (!command) {
		console.error(`No se ha encontrado ningún comando que coincida con ${interaction.commandName}.`);
		return;
	}

	await command.execute(interaction).catch(async error => {
		console.error(error);
		if (interaction.replied || interaction.deferred) {
			await interaction.followUp({ content: 'Se ha producido un error al ejecutar este comando!', ephemeral: true });
		} else {
			await interaction.reply({ content: 'Se ha producido un error al ejecutar este comando.', ephemeral: true });
		}
	});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Siguientes pasos

Tus archivos de comandos ya están cargados en tu bot, y el receptor de eventos está preparado y listo para responder. En la siguiente sección, cubriremos el paso final: un script de despliegue de comandos que necesitarás para registrar tus comandos y que aparezcan en el cliente de Discord.

Resultado final

Si quieres comparar tu código con el código que hemos construido hasta ahora, puedes revisarlo en el repositorio de GitHub aquí open in new window.

También incluye algunos comandos adicionales.