mirror of
https://github.com/arthur-pbty/flint.git
synced 2026-08-01 20:29:03 +02:00
feat: implement command definition, registration, and execution framework
- Add `defineCommand` function to validate and normalize command inputs. - Introduce `deployApplicationCommands` for deploying slash commands to Discord. - Create `CommandRegistry` class for managing command registration and retrieval. - Implement `buildSlashPayload` to construct slash command payloads with localization support. - Add usage utilities for prefix and slash commands. - Develop `CommandExecutor` to handle command execution with permission checks and cooldown management. - Create member message rendering service for welcome and leave messages with customizable templates. - Introduce presence template variables for dynamic bot status updates. - Implement template variable utilities for rendering and extracting variables from strings.
This commit is contained in:
@@ -79,13 +79,13 @@ An example with two bot services is available in `docker-compose.multi-bot.examp
|
||||
|
||||
## Architecture
|
||||
|
||||
- `src/framework/types/command.ts`: strict command schema
|
||||
- `src/framework/commands/defineCommand.ts`: default command completion
|
||||
- `src/framework/commands/registry.ts`: trigger/name mapping generated from i18n dictionaries
|
||||
- `src/framework/commands/argParser.ts`: prefix/slash args parsing from schema
|
||||
- `src/framework/execution/CommandExecutor.ts`: unified pipeline (permissions/cooldown/execute)
|
||||
- `src/framework/handlers/prefixHandler.ts`: prefix entrypoint
|
||||
- `src/framework/handlers/slashHandler.ts`: slash entrypoint
|
||||
- `src/types/command.ts`: strict command schema
|
||||
- `src/core/commands/defineCommand.ts`: default command completion
|
||||
- `src/core/commands/registry.ts`: trigger/name mapping generated from i18n dictionaries
|
||||
- `src/core/commands/argParser.ts`: prefix/slash args parsing from schema
|
||||
- `src/core/execution/CommandExecutor.ts`: unified pipeline (permissions/cooldown/execute)
|
||||
- `src/handlers/prefixHandler.ts`: prefix entrypoint
|
||||
- `src/handlers/slashHandler.ts`: slash entrypoint
|
||||
- `src/database/presence/presenceStore.ts`: PostgreSQL presence storage
|
||||
- `src/types/presenceTypes.ts`: shared presence types/validation
|
||||
- `src/i18n/*.json`: external i18n dictionaries
|
||||
@@ -102,6 +102,6 @@ An example with two bot services is available in `docker-compose.multi-bot.examp
|
||||
## Adding A Command
|
||||
|
||||
1. Create a command object in `src/commands/...`
|
||||
2. Follow the schema in `src/framework/types/command.ts`
|
||||
2. Follow the schema in `src/types/command.ts`
|
||||
3. Add command to `src/commands/index.ts`
|
||||
4. If `AUTO_DEPLOY_SLASH=true`, restart the bot to sync slash commands automatically
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
import { PermissionFlagsBits } from "discord.js";
|
||||
|
||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
||||
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||
import { createMemberMessageExecute } from "./memberMessagePanel.js";
|
||||
|
||||
/** Commande `goodbye` — ouvre le panneau de configuration des messages 'goodbye'. */
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
*/
|
||||
import { EmbedBuilder } from "discord.js";
|
||||
|
||||
import { buildPrefixUsage, buildSlashUsage, resolvePrefixTrigger, resolveSlashName } from "../framework/commands/usage.js";
|
||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
||||
import { buildPrefixUsage, buildSlashUsage, resolvePrefixTrigger, resolveSlashName } from "../core/commands/usage.js";
|
||||
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||
import type { BotCommand, CommandExecutionContext } from "../types/command.js";
|
||||
|
||||
const categoryName = (command: BotCommand): string => command.meta.category;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 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";
|
||||
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||
|
||||
/** Commande `kiss` — envoie un message de type `kiss` vers la cible. */
|
||||
export const kissCommand = defineCommand({
|
||||
|
||||
@@ -22,9 +22,9 @@ import {
|
||||
type Message,
|
||||
} from "discord.js";
|
||||
|
||||
import { env } from "../framework/config/env.js";
|
||||
import { env } from "../config/env.js";
|
||||
import { I18nService } from "../i18n/index.js";
|
||||
import { dispatchMemberMessage } from "../framework/memberMessages/memberMessageSender.js";
|
||||
import { dispatchMemberMessage } from "../services/memberMessages/memberMessageSender.js";
|
||||
import { getMemberMessageStore } from "../database/memberMessages/memberMessageStore.js";
|
||||
import {
|
||||
MEMBER_MESSAGE_RENDER_TYPES,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Répond avec un message court contenant la latence websocket du bot.
|
||||
*/
|
||||
import { MessageFlags } from "discord.js";
|
||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
||||
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||
|
||||
/** Commande `ping` — affiche la latence du bot. */
|
||||
export const pingCommand = defineCommand({
|
||||
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
type Client,
|
||||
type Message,
|
||||
} from "discord.js";
|
||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
||||
import { env } from "../framework/config/env.js";
|
||||
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||
import { env } from "../config/env.js";
|
||||
import type {
|
||||
DiscordPresenceStatus,
|
||||
PresenceActivityTypeValue,
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
containsPresenceTemplateVariables,
|
||||
getPresenceTemplateHelpText,
|
||||
renderPresenceTemplate,
|
||||
} from "../framework/presence/presenceTemplateVariables.js";
|
||||
} from "../services/presence/presenceTemplateVariables.js";
|
||||
import {
|
||||
PRESENCE_ACTIVITY_TYPES,
|
||||
PRESENCE_STATUSES,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
import { PermissionFlagsBits } from "discord.js";
|
||||
|
||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
||||
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||
import { createMemberMessageExecute } from "./memberMessagePanel.js";
|
||||
|
||||
/** Commande `welcome` — ouvre le panneau de configuration des messages 'welcome'. */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { config as loadEnv } from "dotenv";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SUPPORTED_LANGS } from "../../types/command.js";
|
||||
import { SUPPORTED_LANGS } from "../types/command.js";
|
||||
|
||||
loadEnv();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Pool } from "pg";
|
||||
|
||||
import { env } from "../../framework/config/env.js";
|
||||
import { env } from "../../config/env.js";
|
||||
import type {
|
||||
MemberMessageConfig,
|
||||
MemberMessageKind,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Pool } from "pg";
|
||||
|
||||
import { env } from "../../framework/config/env.js";
|
||||
import { env } from "../../config/env.js";
|
||||
import type {
|
||||
PresenceActivityTypeValue,
|
||||
PresenceRow,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Events, type Client } from "discord.js";
|
||||
import type { I18nService } from "../i18n/index.js";
|
||||
import { dispatchMemberMessage } from "../framework/memberMessages/memberMessageSender.js";
|
||||
import { dispatchMemberMessage } from "../services/memberMessages/memberMessageSender.js";
|
||||
|
||||
/**
|
||||
* Enregistre le listener `guildMemberAdd` et déclenche l'envoi d'un message
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Events, type Client } from "discord.js";
|
||||
import type { I18nService } from "../i18n/index.js";
|
||||
import { dispatchMemberMessage } from "../framework/memberMessages/memberMessageSender.js";
|
||||
import { dispatchMemberMessage } from "../services/memberMessages/memberMessageSender.js";
|
||||
|
||||
/**
|
||||
* Enregistre le listener `guildMemberRemove` et déclenche l'envoi d'un message
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ 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";
|
||||
import type { CommandRegistry } from "../core/commands/registry.js";
|
||||
|
||||
/**
|
||||
* Regroupe l'enregistrement des événements Discord les plus courants.
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import { Events, type Client } from "discord.js";
|
||||
import { deployApplicationCommands } from "../framework/commands/deploy.js";
|
||||
import { env } from "../framework/config/env.js";
|
||||
import { deployApplicationCommands } from "../core/commands/deploy.js";
|
||||
import { env } from "../config/env.js";
|
||||
import { restorePresenceFromStorage } from "../commands/presence.js";
|
||||
import type { CommandRegistry } from "../framework/commands/registry.js";
|
||||
import type { CommandRegistry } from "../core/commands/registry.js";
|
||||
import type { I18nService } from "../i18n/index.js";
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { Message } from "discord.js";
|
||||
import type { BotCommand, SupportedLang } from "../types/command.js";
|
||||
import type { PrefixHandlerDeps } from "../types/handlers.js";
|
||||
|
||||
import { parsePrefixArgs } from "../framework/commands/argParser.js";
|
||||
import { buildPrefixUsage } from "../framework/commands/usage.js";
|
||||
import { parsePrefixArgs } from "../core/commands/argParser.js";
|
||||
import { buildPrefixUsage } from "../core/commands/usage.js";
|
||||
import {
|
||||
buildCommandExecutionContext,
|
||||
createTranslator,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
|
||||
import type { SlashHandlerDeps } from "../types/handlers.js";
|
||||
|
||||
import { parseSlashArgs } from "../framework/commands/argParser.js";
|
||||
import { buildSlashUsage } from "../framework/commands/usage.js";
|
||||
import { parseSlashArgs } from "../core/commands/argParser.js";
|
||||
import { buildSlashUsage } from "../core/commands/usage.js";
|
||||
import {
|
||||
buildCommandExecutionContext,
|
||||
createTranslator,
|
||||
|
||||
+3
-3
@@ -13,9 +13,9 @@ import { Client, GatewayIntentBits } from "discord.js";
|
||||
import { commandList } from "./commands/index.js";
|
||||
import { shutdownPresenceRuntime } from "./commands/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";
|
||||
import { CommandRegistry } from "./core/commands/registry.js";
|
||||
import { env } from "./config/env.js";
|
||||
import { CommandExecutor } from "./core/execution/CommandExecutor.js";
|
||||
import { createPrefixHandler } from "./handlers/prefixHandler.js";
|
||||
import { createSlashHandler } from "./handlers/slashHandler.js";
|
||||
import { I18nService } from "./i18n/index.js";
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import type { Client } from "discord.js";
|
||||
|
||||
import { env } from "../config/env.js";
|
||||
import { hasTemplateVariable, renderTemplate } from "../utils/templateVariables.js";
|
||||
import { env } from "../../config/env.js";
|
||||
import { hasTemplateVariable, renderTemplate } from "../../utils/templateVariables.js";
|
||||
import { sanitizeActivityText } from "../../types/presenceTypes.js";
|
||||
|
||||
export const PRESENCE_TEMPLATE_REFRESH_INTERVAL_MS = 60_000;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { CommandRegistry } from "../framework/commands/registry.js";
|
||||
import type { CommandRegistry } from "../core/commands/registry.js";
|
||||
import type { I18nService } from "../i18n/index.js";
|
||||
|
||||
export interface DeployCommandsOptions {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CommandRegistry } from "../framework/commands/registry.js";
|
||||
import type { CommandExecutor } from "../framework/execution/CommandExecutor.js";
|
||||
import type { CommandRegistry } from "../core/commands/registry.js";
|
||||
import type { CommandExecutor } from "../core/execution/CommandExecutor.js";
|
||||
import type { I18nService } from "../i18n/index.js";
|
||||
import type {
|
||||
BotCommand,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TemplateRenderOptions } from "../../types/templateVariables.js";
|
||||
import type { TemplateRenderOptions } from "../types/templateVariables.js";
|
||||
|
||||
const createTemplateTokenRegex = (): RegExp => /\{\{([a-zA-Z0-9_]+)\}\}/g;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { tokenizePrefixInput } from "../src/framework/commands/argParser.js";
|
||||
import { tokenizePrefixInput } from "../src/core/commands/argParser.js";
|
||||
|
||||
test("tokenizePrefixInput parse les quotes simples et doubles", () => {
|
||||
const tokens = tokenizePrefixInput('"hello world" test \'foo bar\'');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { defineCommand } from "../src/framework/commands/defineCommand.js";
|
||||
import { defineCommand } from "../src/core/commands/defineCommand.js";
|
||||
|
||||
test("defineCommand refuse un argument requis apres un optionnel", () => {
|
||||
assert.throws(() => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
extractTemplateVariables,
|
||||
hasTemplateVariable,
|
||||
renderTemplate,
|
||||
} from "../src/framework/utils/templateVariables.js";
|
||||
} from "../src/utils/templateVariables.js";
|
||||
|
||||
test("renderTemplate remplace les variables et applique les alias", () => {
|
||||
const output = renderTemplate(
|
||||
|
||||
Reference in New Issue
Block a user