mirror of
https://github.com/arthur-pbty/flint.git
synced 2026-08-01 20:29:03 +02:00
feat: ajouter la documentation JSDoc pour les commandes et événements, et réorganiser l'enregistrement des événements
This commit is contained in:
@@ -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.
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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})`);
|
||||
});
|
||||
};
|
||||
@@ -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})`);
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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é ci‑dessus
|
||||
void onSlashInteraction(interaction as ChatInputCommandInteraction).catch((error) => {
|
||||
console.error("[event:interactionCreate] handler failed", error);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user