Skip to content

Ajouter une commande

1. Creer le fichier

Creer un fichier dans le dossier commands/ du module concerne :

src/modules/<module>/commands/<nom>.command.ts

2. Implementer l'interface SlashCommand

Chaque commande exporte un objet conforme a l'interface SlashCommand :

typescript
import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
import type { SlashCommand } from '../../../shared/types/command.js';

export const exampleCommand: SlashCommand = {
  data: new SlashCommandBuilder()
    .setName('example')
    .setDescription('Description de la commande'),

  async execute(interaction: ChatInputCommandInteraction) {
    await interaction.reply('Hello !');
  },
};

Interface

typescript
export interface SlashCommand {
  data:
    | SlashCommandBuilder
    | SlashCommandOptionsOnlyBuilder
    | SlashCommandSubcommandsOnlyBuilder
    | Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>;
  execute: (interaction: ChatInputCommandInteraction, client: Client) => Promise<void>;
  autocomplete?: (interaction: AutocompleteInteraction) => Promise<void>;
}

3. Exemple avec sous-commandes

typescript
export const myCommand: SlashCommand = {
  data: new SlashCommandBuilder()
    .setName('mymod')
    .setDescription('Module example')
    .addSubcommand((sub) =>
      sub.setName('action')
        .setDescription('Faire une action')
        .addStringOption((opt) =>
          opt.setName('param').setDescription('Parametre').setRequired(true),
        ),
    ),

  async execute(interaction: ChatInputCommandInteraction) {
    const sub = interaction.options.getSubcommand();

    if (sub === 'action') {
      const param = interaction.options.getString('param', true);
      await interaction.reply(`Action avec : ${param}`);
    }
  },
};

4. Permissions

Commande owner only

typescript
import { isOwner } from '../../../core/permissions.js';

async execute(interaction: ChatInputCommandInteraction) {
  if (!isOwner(interaction)) {
    await interaction.reply({
      content: '❌ Commande reservee au proprietaire du bot.',
      ephemeral: true,
    });
    return;
  }
  // ...
}

Commande moderateur

typescript
import { PermissionFlagsBits } from 'discord.js';

// Dans le builder :
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers)

5. Reponses longues (deferReply)

Pour les commandes qui prennent du temps (appels API, requetes DB complexes), utiliser deferReply :

typescript
async execute(interaction: ChatInputCommandInteraction) {
  await interaction.deferReply({ ephemeral: true });

  // ... traitement long ...

  await interaction.editReply('Resultat');
}

WARNING

Apres deferReply, utiliser editReply et followUp au lieu de reply. Discord donne 3 secondes pour la premiere reponse, 15 minutes apres un defer.

6. Reponses ephemerales

typescript
await interaction.reply({ content: 'Visible uniquement par toi.', ephemeral: true });

7. Enregistrer la commande

a. Importer dans register-commands.ts

typescript
// src/app/register-commands.ts
import { exampleCommand } from '../modules/<module>/commands/example.command.js';

const allCommands: SlashCommand[] = [
  // ... autres commandes ...
  exampleCommand,
];

b. Deployer sur Discord

bash
npm run register

Cette commande execute register-commands.ts en mode standalone, qui envoie les definitions de commandes a l'API Discord via REST.

Cooldown global

Toutes les commandes sont soumises a un cooldown global de 5 secondes par utilisateur, gere dans le dispatcher central (bootstrap.ts). Il n'est pas necessaire de l'implementer dans chaque commande.