Commandes
Ce guide explique comment créer des commandes slash Discord dans vos modules. Les commandes ne sont disponibles que si le module est activé sur le serveur où elles sont utilisées.
Créer une commande
TIP
Définissez les noms et descriptions des commandes en anglais par défaut — ils servent de valeur de repli quand aucune traduction n'est disponible pour la locale du serveur. Dans ce bot, seules les descriptions sont localisées via setDescriptionLocalizations() ; les noms de commandes restent en anglais.
// src/modules/salut/commands/bonjour.command.ts
import { SlashCommandBuilder } from "discord.js";
import { declareCommand } from "#lib/command.js";
export default declareCommand({
data: new SlashCommandBuilder()
.setName("bonjour")
.setDescription("Dit bonjour !"),
async execute(interaction) {
await interaction.reply(`Bonjour, ${interaction.user.username} !`);
},
});Interface Command
interface Command {
data:
| SlashCommandBuilder
| SlashCommandSubcommandBuilder
| SlashCommandSubcommandGroupBuilder
| SlashCommandSubcommandsOnlyBuilder
| SlashCommandOptionsOnlyBuilder;
execute: (
interaction: ChatInputCommandInteraction,
config: ConfigProvider
) => Promise<void>;
complete?: (
interaction: AutocompleteInteraction,
config: ConfigProvider
) => Promise<void>;
}| Champ | Requis | Description |
|---|---|---|
data | Oui | Définition de la commande slash (nom, description, options) |
execute | Oui | Appelé quand un utilisateur exécute la commande |
complete | Non | Appelé quand un utilisateur tape dans une option à autocomplétion |
Accès à la configuration
Le paramètre config est un ConfigProvider qui donne accès à la configuration du module (voir Configuration). Il est toujours injecté — même pour les modules sans schéma de configuration.
NOTE
Dans les handlers complete() (autocomplétion), le config injecté provient actuellement du module Cœur plutôt que du module de la commande. Les valeurs de configuration spécifiques au module ne sont donc pas disponibles pendant l'autocomplétion — seule la configuration du module Cœur est accessible.
async execute(interaction, config) {
const salon = config.get("canalLog");
const max = config.get("avertissementsMax");
// ...
}Options et sous-commandes
Options de base
data: new SlashCommandBuilder()
.setName("saluer")
.setDescription("Saluer quelqu'un")
.addUserOption((option) =>
option.setName("cible").setDescription("Qui saluer").setRequired(true)
)
.addStringOption((option) =>
option
.setName("message")
.setDescription("Message personnalisé")
.setMaxLength(200)
);Autocomplétion
export default declareCommand({
data: new SlashCommandBuilder()
.setName("couleur")
.setDescription("Choisir une couleur")
.addStringOption((option) =>
option
.setName("couleur")
.setDescription("Choisissez une couleur")
.setAutocomplete(true)
.setRequired(true)
),
async execute(interaction, config) {
const couleur = interaction.options.getString("couleur", true);
await interaction.reply(`Vous avez choisi ${couleur} !`);
},
async complete(interaction, config) {
const couleurs = ["rouge", "vert", "bleu", "jaune", "violet"];
const saisie = interaction.options.getFocused().toLowerCase();
const filtrees = couleurs.filter((c) => c.startsWith(saisie));
await interaction.respond(filtrees.map((c) => ({ name: c, value: c })));
},
});Sous-commandes
const builder = new SlashCommandBuilder()
.setName("config")
.setDescription("Commandes de configuration")
.addSubcommand((sub) =>
sub.setName("voir").setDescription("Voir la configuration")
)
.addSubcommand((sub) =>
sub
.setName("definir")
.setDescription("Définir une valeur")
.addStringOption((opt) =>
opt.setName("cle").setDescription("Clé de config").setRequired(true)
)
.addStringOption((opt) =>
opt
.setName("valeur")
.setDescription("Valeur de config")
.setRequired(true)
)
);Menus contextuels
Une commande peut aussi être un menu contextuel de message ou d'utilisateur (clic droit → Applications). Déclarez son type d'interaction en second paramètre de type ; le dispatcher la route par nom et par type, un menu contextuel peut donc porter le même nom qu'une commande slash.
export default declareCommand<MaConfig, MessageContextMenuCommandInteraction>({
data: new ContextMenuCommandBuilder()
.setName("Report message")
.setNameLocalizations({ fr: "Signaler le message" })
.setType(ApplicationCommandType.Message),
async execute(interaction, config) {
const message = interaction.targetMessage;
// ...
},
});Enregistrement et propagation
Dans le module
// src/modules/salut/salut.module.ts
import bonjourCommand from "./commands/bonjour.command.js";
export default defineModule({
onLoad(_client, registry) {
registry.register(bonjourCommand);
},
});Développement vs Production
| Aspect | Développement (NODE_ENV=development) | Production |
|---|---|---|
| Commandes du cœur | Enregistrées sur DEV_GUILD_ID (instantané) | Globales (~1h de propagation) |
| Commandes des modules | Resynchronisées à chaque démarrage sur DEV_GUILD_ID pour les modules activés | Enregistrées par serveur, uniquement en cas de changement de version |
| Propagation | Instantanée (PUT unique) | Différée (globale) ou à la demande (par serveur) |
Le système de versionnage en production utilise le champ activatedVersion dans l'enregistrement ModuleActivation en base de données. Les commandes sont ré-enregistrées sur un serveur uniquement lorsque la version déclarée du module diffère de la version stockée. Cela évite des appels API inutiles à chaque redémarrage.
DEV_GUILD_IDest requis en mode développement. Voir.env.example.
Commandes du cœur
OmniBot fournit deux commandes intégrées dans le module Cœur (toujours actif, non désinstallable) :
| Commande | Permission | Description |
|---|---|---|
/modules | Administrateur | Liste tous les modules avec boutons d'activation/désactivation |
/config <module> | Administrateur | Ouvre le panneau de configuration interactif d'un module |
Localisation
Les descriptions des commandes slash peuvent être traduites par locale en utilisant les méthodes de localisation intégrées de Discord :
data: new SlashCommandBuilder()
.setName("hello")
.setDescription("Says hello!")
.setDescriptionLocalizations({ fr: "Dit bonjour !" }),Dans ce bot, seules les descriptions sont localisées. Les noms de commandes restent en anglais.
Pour les chaînes spécifiques au module dans les réponses de commandes, utilisez config.t() pour chercher les traductions dans vos fichiers i18n :
async execute(interaction, config) {
await interaction.reply(config.t("salutation", { user: interaction.user.username }));
}La locale du serveur est configurée via /config core > locale. Voir Configuration → Localisation pour les détails sur la mise en place des fichiers de traduction.
Bonnes pratiques
- Utilisez reply, deferReply ou editReply de manière appropriée — différée pour les opérations de plus de 3 secondes
- Utilisez les réponses éphémères pour les réponses propres à un utilisateur (
flags: MessageFlags.Ephemeral) - Gérez les erreurs — enveloppez les opérations risquées dans try/catch et répondez avec un message convivial
- Validez les valeurs des options — utilisez la validation intégrée du builder (min/max length, min/max value, etc.)