feat: ajouter la documentation JSDoc pour les commandes et événements, et réorganiser l'enregistrement des événements

This commit is contained in:
Puechberty Arthur
2026-04-12 22:31:56 +02:00
parent fc0daa671d
commit a777ea18e0
19 changed files with 402 additions and 59 deletions
+86
View File
@@ -0,0 +1,86 @@
# Structure du projet
Objectif
--------
Décrire l'organisation recommandée du dépôt et les conventions à suivre.
Organisation proposée
---------------------
- `package.json`, `tsconfig.json`, `README.md`, `Dockerfile`, `docker-compose.yml` — fichiers racine et scripts.
- `dist/` — sortie build (doit être ignoré par git).
- `locales/` — fichiers de traduction (`en.json`, `es.json`, `fr.json`).
- `STRUCTURE.md` — ce fichier : documentation structure/conventions.
Arborescence `src/` (principale)
--------------------------------
- `src/index.ts` — point d'entrée, boot du bot (initialisation, appel des enregistreurs d'événements).
- `src/commands/` — définitions de commandes (meta, args, examples, `execute`). Les commandes doivent rester minces et déléguer la logique lourde.
- `core/` — commandes fondamentales (ex: `help`).
- `fun/` — petites commandes stateless (ex: `kiss`).
- `utility/` — commandes qui orchestrent des services (ex: `presence`, `welcome`, `goodbye`).
- `src/events/` — fonctions d'enregistrement d'événements Discord (ex: `registerMemberMessageEvents`). Favoriser des fonctions `registerX(client, i18n)` simples.
- `src/framework/` — bibliothèque interne, responsables techniques réutilisables :
- `commands/` — helpers pour définir/parse/registry des commandes (`defineCommand`, `usage`, etc.).
- `i18n/``I18nService` et ressources locales.
- `memberMessages/``panel.ts` (UI/panels), `sender.ts`, `store.ts`, `types.ts` (extraction recommandée depuis `commands/utility/memberMessagePanel.ts`).
- `presence/``manager.ts` (timers, rotation, apply/save), `store.ts`, `templateVariables.ts` (extraction recommandée depuis `commands/utility/presence.ts`).
- `execution/`, `handlers/`, `config/`, `types/` — autres utilitaires et adaptateurs.
- `src/utils/` — helpers orthogonaux (ex: `templateVariables.ts`).
- `src/scripts/` — scripts réutilisables (ex: `deployCommands.ts`).
- `tests/` — tests unitaires; organiser pour refléter la logique testée (stores, managers, utils).
Conventions et règles simples
----------------------------
- Commandes : `defineCommand({...})` doit exposer uniquement la configuration + un `execute` qui fait appel à des services/managers. Eviter grosses fonctions de logique métier dans les fichiers de commandes.
- Services / managers : code testable, découplé de Discord.js; n'exposer que des fonctions pures ou des adaptateurs (injections de `client` uniquement dans l'adaptateur).
- UI / panels : centraliser sous `framework/memberMessages` et exposer des factories (ex: `createMemberMessageExecute(kind)` reste en commande mais la UI est extraite).
- Events : chaque fichier exporte une fonction `registerX(client, i18n)` ; centraliser l'appel dans `src/events/index.ts`.
- Tests : cibler les managers et stores en priorité; mocks/fixtures pour les adaptateurs Discord.
Plan de migration (prioritaire)
------------------------------
1. Extraire `memberMessagePanel.ts``src/framework/memberMessages/panel.ts` et mettre à jour `welcome.ts`/`goodbye.ts` pour utiliser l'API extraite. (À FAIRE)
2. Extraire la logique `presence` (timers, rotation, apply) → `src/framework/presence/manager.ts`, laisser `presence` command comme wrapper léger. (À FAIRE)
3. Scinder les événements et centraliser dans `src/events/` — un fichier par événement + `src/events/index.ts`. (FAIT)
4. Documenter les conventions (ce fichier) et ouvrir PRs petites et ciblées. (PARTIELLEMENT FAIT)
Modifications récentes appliquées
--------------------------------
- Scission de `src/events/` en fichiers par événement :
- `messageCreate.ts`, `interactionCreate.ts`, `guildMemberAdd.ts`, `guildMemberRemove.ts`, `guildCreate.ts`, `guildDelete.ts`, `ready.ts`.
- `src/events/index.ts` centralise l'enregistrement via `registerEvents(client, i18n, handlers, registry)`.
- `ready` : la logique de restauration de présence et de déploiement des slash commands a été déplacée de `src/index.ts` vers `src/events/ready.ts`.
- Signatures des handlers mises à jour pour être fortement typées : `onPrefixMessage(message: Message)` et `onSlashInteraction(interaction: ChatInputCommandInteraction)`.
- Documentation (JSDoc / commentaires) ajoutée dans plusieurs fichiers de `src/commands/` et `src/events/`.
Conventions actualisées
----------------------
- Un fichier par événement Discord, nommé exactement comme l'événement (ex: `guildMemberAdd.ts`).
- `src/events/index.ts` expose `registerEvents(...)` qui reçoit :
- `client`, `i18n`, `handlers` ({ `onPrefixMessage`, `onSlashInteraction` }) et `registry`.
- Les commandes restent des wrappers légers : configuration + `execute()` qui délègue aux services/managers dans `src/framework/`.
Prochaines étapes recommandées
------------------------------
- Extraire `memberMessagePanel.ts` vers `src/framework/memberMessages/panel.ts` (priorité haute).
- Extraire la logique `presence` (timers, rotation) vers `src/framework/presence/manager.ts`.
- Ajouter un petit `README.md` de démarrage (build & run) et tenir ce fichier `STRUCTURE.md` à jour après chaque PR.
- Lancer `npm run build` et les tests pour valider les changements et corriger les erreurs de typage éventuelles.
Comment travailler ensemble
-------------------------
- Proposez ici les modifications souhaitées (renommage, dossiers additionnels, conventions strictes). Je peux appliquer chaque changement en petites PRs (déplacement de fichiers + correction d'import).
- Pour chaque modification, indiquer l'objectif et le résultat attendu. Je ferai les edits et lancerai les tests/build.
Historique
---------
- Créé le 12 avril 2026 — version initiale.
+12
View File
@@ -1,3 +1,12 @@
/**
* Commande `help`
*
* Construit un embed d'aide global listant les commandes par catégorie, ou
* affiche les détails pour une commande spécifique si un argument est fourni.
*
* Export:
* - `helpCommand`: BotCommand (défini via `defineCommand`)
*/
import { EmbedBuilder } from "discord.js";
import { buildPrefixUsage, buildSlashUsage, resolvePrefixTrigger, resolveSlashName } from "../../framework/commands/usage.js";
@@ -113,6 +122,9 @@ const buildCommandDetailsEmbed = (ctx: CommandExecutionContext, command: BotComm
.setFooter({ text: ctx.ct("embed.footer", { source: command.meta.category }) });
};
/**
* Commande `help` — renvoie un embed d'aide global ou les détails d'une commande.
*/
export const helpCommand = defineCommand({
meta: {
name: "help",
+7
View File
@@ -1,5 +1,12 @@
/**
* Commande `kiss` (fun)
*
* Envoie une réponse de type `kissing` ciblant un utilisateur mentionné.
* Utilise un seul argument `user` de type `user`.
*/
import { defineCommand } from "../../framework/commands/defineCommand.js";
/** Commande `kiss` — envoie un message de type `kiss` vers la cible. */
export const kissCommand = defineCommand({
meta: {
name: "kiss",
+7
View File
@@ -1,3 +1,9 @@
/**
* Liste des commandes exportées par le module `commands`.
*
* Ce fichier centralise l'ordre par défaut des commandes et permet de
* récupérer facilement la liste pour l'enregistrement (registry/dispatch).
*/
import { helpCommand } from "./core/help.js";
import { kissCommand } from "./fun/kiss.js";
import { advancedCommand } from "./utility/advanced.js";
@@ -8,6 +14,7 @@ import { welcomeCommand } from "./utility/welcome.js";
import type { BotCommand } from "../framework/types/command.js";
/** CommandList: tableau ordonné des commandes disponibles. */
export const commandList: BotCommand[] = [
kissCommand,
pingCommand,
+10
View File
@@ -1,3 +1,10 @@
/**
* Commande `advanced` (utility)
*
* Exemple de commande démontrant la gestion de plusieurs types d'arguments
* (string, int, user, number, boolean, channel, role) et l'utilisation d'un
* template de réponse configurable via `commandText.responses.summary`.
*/
import { PermissionFlagsBits } from "discord.js";
import { defineCommand } from "../../framework/commands/defineCommand.js";
@@ -24,6 +31,9 @@ const toDisplayValue = (value: unknown, formatter?: (id: string) => string): str
return String(value);
};
/**
* Commande `advanced` — affiche un résumé formaté des arguments fournis.
*/
export const advancedCommand = defineCommand({
meta: {
name: "advanced",
+7
View File
@@ -1,8 +1,15 @@
/**
* Commande `goodbye` (utility)
*
* Wrapper léger qui utilise la factory `createMemberMessageExecute` pour
* afficher un panneau de configuration des messages d'au revoir.
*/
import { PermissionFlagsBits } from "discord.js";
import { defineCommand } from "../../framework/commands/defineCommand.js";
import { createMemberMessageExecute } from "./memberMessagePanel.js";
/** Commande `goodbye` — ouvre le panneau de configuration des messages 'goodbye'. */
export const goodbyeCommand = defineCommand({
meta: {
name: "goodbye",
@@ -1,3 +1,14 @@
/**
* Panneau de configuration pour les messages de bienvenue / départ
*
* Contient la logique UI (Components v2), collectors et sessions pour afficher
* et gérer un panneau interactif permettant de configurer les messages
* d'accueil et d'au revoir par guild.
*
* Export:
* - `createMemberMessageExecute(kind)` : factory utilisée par les commandes
* `welcome` et `goodbye` pour attacher le panneau.
*/
import {
ActionRowBuilder,
ButtonBuilder,
@@ -203,6 +214,12 @@ const testFeedbackKey = (reason: string): string => {
}
};
/**
* Factory qui crée l'exécuteur de commande pour un `MemberMessageKind` donné.
*
* Le handler retourné gère l'affichage du panneau, la collecte d'interactions
* et la persistance de la configuration via `memberMessageStore`.
*/
export const createMemberMessageExecute = (kind: MemberMessageKind) => {
return async (ctx: CommandExecutionContext): Promise<void> => {
if (!ctx.guild) {
+6
View File
@@ -1,6 +1,12 @@
/**
* Commande `ping` (utility)
*
* Répond avec un message court contenant la latence websocket du bot.
*/
import { MessageFlags } from "discord.js";
import { defineCommand } from "../../framework/commands/defineCommand.js";
/** Commande `ping` — affiche la latence du bot. */
export const pingCommand = defineCommand({
meta: {
name: "ping",
+17
View File
@@ -1,3 +1,11 @@
/**
* Module `presence` — gestion des présences du bot
*
* Contient la logique de timers, rotation d'activités, rendu de templates
* et l'UI panel d'administration. Le module expose des helpers pour restaurer
* l'état (`restorePresenceFromStorage`), arrêter les timers (`shutdownPresenceRuntime`)
* et la commande `presence` qui affiche le panneau.
*/
import {
ActivityType,
ActionRowBuilder,
@@ -96,6 +104,9 @@ const resolveRuntimeState = (client: Client): PresenceRuntimeState => {
return next;
};
/**
* Stoppe toutes les tâches runtime liées aux présences et libère les sessions.
*/
export const shutdownPresenceRuntime = (): void => {
for (const runtimeState of presenceRuntimeByBotId.values()) {
clearRuntimeTimers(runtimeState);
@@ -395,6 +406,9 @@ const persistAndApplyPresence = async (
await savePresenceState(ctx.client, state);
};
/**
* Charge et applique l'état de présence depuis le stockage pour un client.
*/
export const restorePresenceFromStorage = async (client: Client): Promise<void> => {
const state = await loadPresenceState(client);
const runtimeState = resolveRuntimeState(client);
@@ -403,6 +417,9 @@ export const restorePresenceFromStorage = async (client: Client): Promise<void>
syncDynamicPresenceTimers(client, state, runtimeState);
};
/**
* Commande `presence` — ouvre le panneau de gestion de la présence pour le bot.
*/
export const presenceCommand = defineCommand({
meta: {
name: "presence",
+7
View File
@@ -1,8 +1,15 @@
/**
* Commande `welcome` (utility)
*
* Wrapper léger qui utilise la factory `createMemberMessageExecute` pour
* afficher un panneau de configuration des messages d'accueil.
*/
import { PermissionFlagsBits } from "discord.js";
import { defineCommand } from "../../framework/commands/defineCommand.js";
import { createMemberMessageExecute } from "./memberMessagePanel.js";
/** Commande `welcome` — ouvre le panneau de configuration des messages 'welcome'. */
export const welcomeCommand = defineCommand({
meta: {
name: "welcome",
+12
View File
@@ -0,0 +1,12 @@
import { Events, type Client } from "discord.js";
/**
* Enregistre le listener `guildCreate` (bot ajouté à un serveur).
*
* Action minimale: log pour monitoring ; peut être étendu (initialisation de configs, etc.).
*/
export const registerGuildCreate = (client: Client): void => {
client.on(Events.GuildCreate, (guild) => {
console.log(`[event:guildCreate] joined guild ${guild.id} (${guild.name})`);
});
};
+12
View File
@@ -0,0 +1,12 @@
import { Events, type Client } from "discord.js";
/**
* Enregistre le listener `guildDelete` (bot retiré d'un serveur).
*
* Action minimale: log pour monitoring ; peut être étendu (cleanup, etc.).
*/
export const registerGuildDelete = (client: Client): void => {
client.on(Events.GuildDelete, (guild) => {
console.log(`[event:guildDelete] left guild ${guild.id} (${guild.name})`);
});
};
+21
View File
@@ -0,0 +1,21 @@
import { Events, type Client } from "discord.js";
import type { I18nService } from "../framework/i18n/I18nService.js";
import { dispatchMemberMessage } from "../framework/memberMessages/memberMessageSender.js";
/**
* Enregistre le listener `guildMemberAdd` et déclenche l'envoi d'un message
* de bienvenue via `dispatchMemberMessage`.
*/
export const registerGuildMemberAdd = (client: Client, i18n: I18nService): void => {
client.on(Events.GuildMemberAdd, (member) => {
void dispatchMemberMessage({
client,
i18n,
guild: member.guild,
user: member.user,
kind: "welcome",
}).catch((error) => {
console.error("[event:guildMemberAdd] failed to send welcome message", error);
});
});
};
@@ -1,21 +1,12 @@
import { Events, type Client } from "discord.js";
import type { I18nService } from "../framework/i18n/I18nService.js";
import { dispatchMemberMessage } from "../framework/memberMessages/memberMessageSender.js";
export const registerMemberMessageEvents = (client: Client, i18n: I18nService): void => {
client.on(Events.GuildMemberAdd, (member) => {
void dispatchMemberMessage({
client,
i18n,
guild: member.guild,
user: member.user,
kind: "welcome",
}).catch((error) => {
console.error("[event:guildMemberAdd] failed to send welcome message", error);
});
});
/**
* Enregistre le listener `guildMemberRemove` et déclenche l'envoi d'un message
* d'au revoir via `dispatchMemberMessage`.
*/
export const registerGuildMemberRemove = (client: Client, i18n: I18nService): void => {
client.on(Events.GuildMemberRemove, (member) => {
void dispatchMemberMessage({
client,
+37
View File
@@ -0,0 +1,37 @@
import type { Client, Message, ChatInputCommandInteraction } from "discord.js";
import type { I18nService } from "../framework/i18n/I18nService.js";
import { registerMessageCreate } from "./messageCreate.js";
import { registerInteractionCreate } from "./interactionCreate.js";
import { registerGuildMemberAdd } from "./guildMemberAdd.js";
import { registerGuildMemberRemove } from "./guildMemberRemove.js";
import { registerGuildCreate } from "./guildCreate.js";
import { registerGuildDelete } from "./guildDelete.js";
import { registerClientReady } from "./ready.js";
import type { CommandRegistry } from "../framework/commands/registry.js";
/**
* Regroupe l'enregistrement des événements Discord les plus courants.
*
* @param client - instance du Client Discord
* @param i18n - instance de service i18n
* @param handlers - objets contenant les handlers pour message/interaction
*/
export const registerEvents = (
client: Client,
i18n: I18nService,
handlers: { onPrefixMessage: (m: Message) => Promise<void>; onSlashInteraction: (i: ChatInputCommandInteraction) => Promise<void> },
registry: CommandRegistry,
): void => {
registerMessageCreate(client, handlers.onPrefixMessage);
registerInteractionCreate(client, handlers.onSlashInteraction);
registerGuildMemberAdd(client, i18n);
registerGuildMemberRemove(client, i18n);
registerGuildCreate(client);
registerGuildDelete(client);
// Ready: tâches à exécuter au démarrage du client
registerClientReady(client, registry, i18n);
};
+32
View File
@@ -0,0 +1,32 @@
import { Events, type Client, type ChatInputCommandInteraction } from "discord.js";
/**
* Enregistre le listener `interactionCreate` pour les commandes slash (chat input).
*
* @param client - instance du Client Discord
* @param onSlashInteraction - fonction à appeler pour traiter les interactions slash
*/
export const registerInteractionCreate = (
client: Client,
onSlashInteraction: (interaction: ChatInputCommandInteraction) => Promise<void>,
): void => {
client.on(Events.InteractionCreate, (interaction) => {
// On ne traite que les `ChatInputCommand` (slash)
if (!interaction || typeof (interaction as any).isChatInputCommand !== "function") {
return;
}
try {
if (!(interaction as any).isChatInputCommand()) {
return;
}
} catch {
return;
}
// Cast sécurisé par le test `isChatInputCommand()` effectué cidessus
void onSlashInteraction(interaction as ChatInputCommandInteraction).catch((error) => {
console.error("[event:interactionCreate] handler failed", error);
});
});
};
+15
View File
@@ -0,0 +1,15 @@
import { Events, type Client, type Message } from "discord.js";
/**
* Enregistre le listener `messageCreate` en déléguant au handler fourni.
*
* @param client - instance du Client Discord
* @param onPrefixMessage - fonction à appeler pour traiter les messages (préfixe)
*/
export const registerMessageCreate = (client: Client, onPrefixMessage: (message: Message) => Promise<void>): void => {
client.on(Events.MessageCreate, (message: Message) => {
void onPrefixMessage(message).catch((error) => {
console.error("[event:messageCreate] handler failed", error);
});
});
};
+37
View File
@@ -0,0 +1,37 @@
import { Events, type Client } from "discord.js";
import { deployApplicationCommands } from "../framework/commands/deploy.js";
import { env } from "../framework/config/env.js";
import { restorePresenceFromStorage } from "../commands/utility/presence.js";
import type { CommandRegistry } from "../framework/commands/registry.js";
import type { I18nService } from "../framework/i18n/I18nService.js";
/**
* Attache le listener `ready` et exécute les tâches post-démarrage:
* - restauration de la présence
* - (optionnel) déploiement des commandes slash
*/
export const registerClientReady = (client: Client, registry: CommandRegistry, i18n: I18nService): void => {
client.once(Events.ClientReady, async () => {
console.log(`[ready] logged as ${client.user?.tag ?? "unknown"}`);
try {
await restorePresenceFromStorage(client);
} catch (error) {
console.error("[ready] failed to restore bot presence", error);
}
if (env.AUTO_DEPLOY_SLASH) {
try {
const result = await deployApplicationCommands({
token: env.DISCORD_TOKEN,
clientId: env.DISCORD_CLIENT_ID,
registry,
i18n,
...(env.DEV_GUILD_ID ? { guildId: env.DEV_GUILD_ID } : {}),
});
console.log(`[ready] slash sync done (${result.scope}, ${result.count} commands)`);
} catch (error) {
console.error("[ready] slash sync failed", error);
}
}
});
};
+55 -45
View File
@@ -1,9 +1,18 @@
import { Client, Events, GatewayIntentBits } from "discord.js";
/**
* Entrypoint du bot — bootstrap et configuration runtime.
*
* Rôles principaux:
* - initialiser les stores et ressources (presence, member messages)
* - configurer le client Discord (intents)
* - construire i18n, registry et executor
* - attacher les handlers (prefix/slash) et listeners d'événements
* - restaurer la présence et (optionnel) déployer les commandes slash
*/
import { Client, GatewayIntentBits } from "discord.js";
import { commandList } from "./commands/index.js";
import { restorePresenceFromStorage, shutdownPresenceRuntime } from "./commands/utility/presence.js";
import { registerMemberMessageEvents } from "./events/memberMessageEvents.js";
import { deployApplicationCommands } from "./framework/commands/deploy.js";
import { shutdownPresenceRuntime } from "./commands/utility/presence.js";
import { registerEvents } from "./events/index.js";
import { CommandRegistry } from "./framework/commands/registry.js";
import { env } from "./framework/config/env.js";
import { CommandExecutor } from "./framework/execution/CommandExecutor.js";
@@ -16,16 +25,31 @@ import {
} from "./framework/memberMessages/memberMessageStore.js";
import { initPresenceStore, shutdownPresenceStore } from "./framework/presence/presenceStore.js";
/**
* Attache des handlers pour un arrêt gracieux du process.
*
* Actions effectuées lors du shutdown:
* - arrêt des timers/runtime (présence)
* - fermeture des stores (member messages / presence)
* - sortie du process
*/
const bindGracefulShutdown = (): void => {
const shutdown = async (signal: string): Promise<void> => {
console.log(`[shutdown] ${signal}`);
// Stoppe les timers et runtime liés à la présence
shutdownPresenceRuntime();
// Ferme proprement le store des messages de membre
await shutdownMemberMessageStore().catch((error) => {
console.error("[shutdown] member message store close failed", error);
});
// Ferme proprement le store de présence
await shutdownPresenceStore().catch((error) => {
console.error("[shutdown] presence store close failed", error);
});
process.exit(0);
};
@@ -38,11 +62,27 @@ const bindGracefulShutdown = (): void => {
});
};
/**
* Sequence d'initialisation principale.
*
* Etapes:
* 1. initialiser les stores (presence, member messages)
* 2. attacher shutdown handler
* 3. configurer la gestion d'erreurs process
* 4. créer le client Discord et les services (i18n, registry, executor)
* 5. construire les handlers prefix/slash et attacher les listeners
* 6. enregistrer events métier (member messages)
* 7. sur ready: restaurer la présence et déployer les slash commands si activé
*/
const bootstrap = async (): Promise<void> => {
// Initialisation des stores persistants utilisés par le bot
await initPresenceStore();
await initMemberMessageStore();
// Prépare la gestion d'un arrêt gracieux (SIGINT / SIGTERM)
bindGracefulShutdown();
// Log des erreurs non catchées pour debugging
process.on("unhandledRejection", (reason) => {
console.error("[process] unhandled rejection", reason);
});
@@ -51,6 +91,7 @@ const bootstrap = async (): Promise<void> => {
console.error("[process] uncaught exception", error);
});
// Création du client Discord avec les intents nécessaires
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
@@ -60,10 +101,13 @@ const bootstrap = async (): Promise<void> => {
],
});
// Services transverses: i18n, registry des commandes et executor
const i18n = new I18nService(env.DEFAULT_LANG);
const registry = new CommandRegistry(commandList, i18n);
const executor = new CommandExecutor();
// Création des handlers : ces factories retournent une fonction utilitaire
// qui prend un Message / Interaction et exécute la logique (parse args, permissions, etc.)
const onPrefixMessage = createPrefixHandler({
registry,
i18n,
@@ -80,53 +124,19 @@ const bootstrap = async (): Promise<void> => {
defaultLang: env.DEFAULT_LANG,
});
client.on(Events.MessageCreate, (message) => {
void onPrefixMessage(message).catch((error) => {
console.error("[event:messageCreate] handler failed", error);
});
});
client.on(Events.InteractionCreate, (interaction) => {
if (!interaction.isChatInputCommand()) {
return;
}
void onSlashInteraction(interaction).catch((error) => {
console.error("[event:interactionCreate] handler failed", error);
});
});
registerMemberMessageEvents(client, i18n);
client.once(Events.ClientReady, async () => {
console.log(`[ready] logged as ${client.user?.tag ?? "unknown"}`);
try {
await restorePresenceFromStorage(client);
} catch (error) {
console.error("[ready] failed to restore bot presence", error);
}
if (env.AUTO_DEPLOY_SLASH) {
try {
const result = await deployApplicationCommands({
token: env.DISCORD_TOKEN,
clientId: env.DISCORD_CLIENT_ID,
registry,
i18n,
...(env.DEV_GUILD_ID ? { guildId: env.DEV_GUILD_ID } : {}),
});
console.log(`[ready] slash sync done (${result.scope}, ${result.count} commands)`);
} catch (error) {
console.error("[ready] slash sync failed", error);
}
}
});
// Enregistre les événements principaux (séparés par fichier)
// `registerEvents` inclut désormais l'enregistrement du listener `ready`.
registerEvents(client, i18n, { onPrefixMessage, onSlashInteraction }, registry);
// Connexion du client au gateway
await client.login(env.DISCORD_TOKEN);
};
// Démarre la séquence d'initialisation et gère les erreurs fatales
bootstrap().catch(async (error) => {
console.error("[boot] fatal error", error);
// Nettoyage partiel en cas d'erreur critique pendant le boot
shutdownPresenceRuntime();
await shutdownMemberMessageStore().catch((closeError) => {
console.error("[boot] failed to close member message store", closeError);