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
|
## Architecture
|
||||||
|
|
||||||
- `src/framework/types/command.ts`: strict command schema
|
- `src/types/command.ts`: strict command schema
|
||||||
- `src/framework/commands/defineCommand.ts`: default command completion
|
- `src/core/commands/defineCommand.ts`: default command completion
|
||||||
- `src/framework/commands/registry.ts`: trigger/name mapping generated from i18n dictionaries
|
- `src/core/commands/registry.ts`: trigger/name mapping generated from i18n dictionaries
|
||||||
- `src/framework/commands/argParser.ts`: prefix/slash args parsing from schema
|
- `src/core/commands/argParser.ts`: prefix/slash args parsing from schema
|
||||||
- `src/framework/execution/CommandExecutor.ts`: unified pipeline (permissions/cooldown/execute)
|
- `src/core/execution/CommandExecutor.ts`: unified pipeline (permissions/cooldown/execute)
|
||||||
- `src/framework/handlers/prefixHandler.ts`: prefix entrypoint
|
- `src/handlers/prefixHandler.ts`: prefix entrypoint
|
||||||
- `src/framework/handlers/slashHandler.ts`: slash entrypoint
|
- `src/handlers/slashHandler.ts`: slash entrypoint
|
||||||
- `src/database/presence/presenceStore.ts`: PostgreSQL presence storage
|
- `src/database/presence/presenceStore.ts`: PostgreSQL presence storage
|
||||||
- `src/types/presenceTypes.ts`: shared presence types/validation
|
- `src/types/presenceTypes.ts`: shared presence types/validation
|
||||||
- `src/i18n/*.json`: external i18n dictionaries
|
- `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
|
## Adding A Command
|
||||||
|
|
||||||
1. Create a command object in `src/commands/...`
|
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`
|
3. Add command to `src/commands/index.ts`
|
||||||
4. If `AUTO_DEPLOY_SLASH=true`, restart the bot to sync slash commands automatically
|
4. If `AUTO_DEPLOY_SLASH=true`, restart the bot to sync slash commands automatically
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import { PermissionFlagsBits } from "discord.js";
|
import { PermissionFlagsBits } from "discord.js";
|
||||||
|
|
||||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||||
import { createMemberMessageExecute } from "./memberMessagePanel.js";
|
import { createMemberMessageExecute } from "./memberMessagePanel.js";
|
||||||
|
|
||||||
/** Commande `goodbye` — ouvre le panneau de configuration des messages 'goodbye'. */
|
/** Commande `goodbye` — ouvre le panneau de configuration des messages 'goodbye'. */
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
import { EmbedBuilder } from "discord.js";
|
import { EmbedBuilder } from "discord.js";
|
||||||
|
|
||||||
import { buildPrefixUsage, buildSlashUsage, resolvePrefixTrigger, resolveSlashName } from "../framework/commands/usage.js";
|
import { buildPrefixUsage, buildSlashUsage, resolvePrefixTrigger, resolveSlashName } from "../core/commands/usage.js";
|
||||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||||
import type { BotCommand, CommandExecutionContext } from "../types/command.js";
|
import type { BotCommand, CommandExecutionContext } from "../types/command.js";
|
||||||
|
|
||||||
const categoryName = (command: BotCommand): string => command.meta.category;
|
const categoryName = (command: BotCommand): string => command.meta.category;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Envoie une réponse de type `kissing` ciblant un utilisateur mentionné.
|
* Envoie une réponse de type `kissing` ciblant un utilisateur mentionné.
|
||||||
* Utilise un seul argument `user` de type `user`.
|
* 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. */
|
/** Commande `kiss` — envoie un message de type `kiss` vers la cible. */
|
||||||
export const kissCommand = defineCommand({
|
export const kissCommand = defineCommand({
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ import {
|
|||||||
type Message,
|
type Message,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
|
|
||||||
import { env } from "../framework/config/env.js";
|
import { env } from "../config/env.js";
|
||||||
import { I18nService } from "../i18n/index.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 { getMemberMessageStore } from "../database/memberMessages/memberMessageStore.js";
|
||||||
import {
|
import {
|
||||||
MEMBER_MESSAGE_RENDER_TYPES,
|
MEMBER_MESSAGE_RENDER_TYPES,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Répond avec un message court contenant la latence websocket du bot.
|
* Répond avec un message court contenant la latence websocket du bot.
|
||||||
*/
|
*/
|
||||||
import { MessageFlags } from "discord.js";
|
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. */
|
/** Commande `ping` — affiche la latence du bot. */
|
||||||
export const pingCommand = defineCommand({
|
export const pingCommand = defineCommand({
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import {
|
|||||||
type Client,
|
type Client,
|
||||||
type Message,
|
type Message,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||||
import { env } from "../framework/config/env.js";
|
import { env } from "../config/env.js";
|
||||||
import type {
|
import type {
|
||||||
DiscordPresenceStatus,
|
DiscordPresenceStatus,
|
||||||
PresenceActivityTypeValue,
|
PresenceActivityTypeValue,
|
||||||
@@ -38,7 +38,7 @@ import {
|
|||||||
containsPresenceTemplateVariables,
|
containsPresenceTemplateVariables,
|
||||||
getPresenceTemplateHelpText,
|
getPresenceTemplateHelpText,
|
||||||
renderPresenceTemplate,
|
renderPresenceTemplate,
|
||||||
} from "../framework/presence/presenceTemplateVariables.js";
|
} from "../services/presence/presenceTemplateVariables.js";
|
||||||
import {
|
import {
|
||||||
PRESENCE_ACTIVITY_TYPES,
|
PRESENCE_ACTIVITY_TYPES,
|
||||||
PRESENCE_STATUSES,
|
PRESENCE_STATUSES,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import { PermissionFlagsBits } from "discord.js";
|
import { PermissionFlagsBits } from "discord.js";
|
||||||
|
|
||||||
import { defineCommand } from "../framework/commands/defineCommand.js";
|
import { defineCommand } from "../core/commands/defineCommand.js";
|
||||||
import { createMemberMessageExecute } from "./memberMessagePanel.js";
|
import { createMemberMessageExecute } from "./memberMessagePanel.js";
|
||||||
|
|
||||||
/** Commande `welcome` — ouvre le panneau de configuration des messages 'welcome'. */
|
/** Commande `welcome` — ouvre le panneau de configuration des messages 'welcome'. */
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { config as loadEnv } from "dotenv";
|
import { config as loadEnv } from "dotenv";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SUPPORTED_LANGS } from "../../types/command.js";
|
import { SUPPORTED_LANGS } from "../types/command.js";
|
||||||
|
|
||||||
loadEnv();
|
loadEnv();
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Pool } from "pg";
|
import { Pool } from "pg";
|
||||||
|
|
||||||
import { env } from "../../framework/config/env.js";
|
import { env } from "../../config/env.js";
|
||||||
import type {
|
import type {
|
||||||
MemberMessageConfig,
|
MemberMessageConfig,
|
||||||
MemberMessageKind,
|
MemberMessageKind,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Pool } from "pg";
|
import { Pool } from "pg";
|
||||||
|
|
||||||
import { env } from "../../framework/config/env.js";
|
import { env } from "../../config/env.js";
|
||||||
import type {
|
import type {
|
||||||
PresenceActivityTypeValue,
|
PresenceActivityTypeValue,
|
||||||
PresenceRow,
|
PresenceRow,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Events, type Client } from "discord.js";
|
import { Events, type Client } from "discord.js";
|
||||||
import type { I18nService } from "../i18n/index.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
|
* Enregistre le listener `guildMemberAdd` et déclenche l'envoi d'un message
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Events, type Client } from "discord.js";
|
import { Events, type Client } from "discord.js";
|
||||||
import type { I18nService } from "../i18n/index.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
|
* 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 { registerGuildCreate } from "./guildCreate.js";
|
||||||
import { registerGuildDelete } from "./guildDelete.js";
|
import { registerGuildDelete } from "./guildDelete.js";
|
||||||
import { registerClientReady } from "./ready.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.
|
* Regroupe l'enregistrement des événements Discord les plus courants.
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import { Events, type Client } from "discord.js";
|
import { Events, type Client } from "discord.js";
|
||||||
import { deployApplicationCommands } from "../framework/commands/deploy.js";
|
import { deployApplicationCommands } from "../core/commands/deploy.js";
|
||||||
import { env } from "../framework/config/env.js";
|
import { env } from "../config/env.js";
|
||||||
import { restorePresenceFromStorage } from "../commands/presence.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";
|
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 { BotCommand, SupportedLang } from "../types/command.js";
|
||||||
import type { PrefixHandlerDeps } from "../types/handlers.js";
|
import type { PrefixHandlerDeps } from "../types/handlers.js";
|
||||||
|
|
||||||
import { parsePrefixArgs } from "../framework/commands/argParser.js";
|
import { parsePrefixArgs } from "../core/commands/argParser.js";
|
||||||
import { buildPrefixUsage } from "../framework/commands/usage.js";
|
import { buildPrefixUsage } from "../core/commands/usage.js";
|
||||||
import {
|
import {
|
||||||
buildCommandExecutionContext,
|
buildCommandExecutionContext,
|
||||||
createTranslator,
|
createTranslator,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
|
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
|
||||||
import type { SlashHandlerDeps } from "../types/handlers.js";
|
import type { SlashHandlerDeps } from "../types/handlers.js";
|
||||||
|
|
||||||
import { parseSlashArgs } from "../framework/commands/argParser.js";
|
import { parseSlashArgs } from "../core/commands/argParser.js";
|
||||||
import { buildSlashUsage } from "../framework/commands/usage.js";
|
import { buildSlashUsage } from "../core/commands/usage.js";
|
||||||
import {
|
import {
|
||||||
buildCommandExecutionContext,
|
buildCommandExecutionContext,
|
||||||
createTranslator,
|
createTranslator,
|
||||||
|
|||||||
+3
-3
@@ -13,9 +13,9 @@ import { Client, GatewayIntentBits } from "discord.js";
|
|||||||
import { commandList } from "./commands/index.js";
|
import { commandList } from "./commands/index.js";
|
||||||
import { shutdownPresenceRuntime } from "./commands/presence.js";
|
import { shutdownPresenceRuntime } from "./commands/presence.js";
|
||||||
import { registerEvents } from "./events/index.js";
|
import { registerEvents } from "./events/index.js";
|
||||||
import { CommandRegistry } from "./framework/commands/registry.js";
|
import { CommandRegistry } from "./core/commands/registry.js";
|
||||||
import { env } from "./framework/config/env.js";
|
import { env } from "./config/env.js";
|
||||||
import { CommandExecutor } from "./framework/execution/CommandExecutor.js";
|
import { CommandExecutor } from "./core/execution/CommandExecutor.js";
|
||||||
import { createPrefixHandler } from "./handlers/prefixHandler.js";
|
import { createPrefixHandler } from "./handlers/prefixHandler.js";
|
||||||
import { createSlashHandler } from "./handlers/slashHandler.js";
|
import { createSlashHandler } from "./handlers/slashHandler.js";
|
||||||
import { I18nService } from "./i18n/index.js";
|
import { I18nService } from "./i18n/index.js";
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import type { Client } from "discord.js";
|
import type { Client } from "discord.js";
|
||||||
|
|
||||||
import { env } from "../config/env.js";
|
import { env } from "../../config/env.js";
|
||||||
import { hasTemplateVariable, renderTemplate } from "../utils/templateVariables.js";
|
import { hasTemplateVariable, renderTemplate } from "../../utils/templateVariables.js";
|
||||||
import { sanitizeActivityText } from "../../types/presenceTypes.js";
|
import { sanitizeActivityText } from "../../types/presenceTypes.js";
|
||||||
|
|
||||||
export const PRESENCE_TEMPLATE_REFRESH_INTERVAL_MS = 60_000;
|
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";
|
import type { I18nService } from "../i18n/index.js";
|
||||||
|
|
||||||
export interface DeployCommandsOptions {
|
export interface DeployCommandsOptions {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { CommandRegistry } from "../framework/commands/registry.js";
|
import type { CommandRegistry } from "../core/commands/registry.js";
|
||||||
import type { CommandExecutor } from "../framework/execution/CommandExecutor.js";
|
import type { CommandExecutor } from "../core/execution/CommandExecutor.js";
|
||||||
import type { I18nService } from "../i18n/index.js";
|
import type { I18nService } from "../i18n/index.js";
|
||||||
import type {
|
import type {
|
||||||
BotCommand,
|
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;
|
const createTemplateTokenRegex = (): RegExp => /\{\{([a-zA-Z0-9_]+)\}\}/g;
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import test from "node:test";
|
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", () => {
|
test("tokenizePrefixInput parse les quotes simples et doubles", () => {
|
||||||
const tokens = tokenizePrefixInput('"hello world" test \'foo bar\'');
|
const tokens = tokenizePrefixInput('"hello world" test \'foo bar\'');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import test from "node:test";
|
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", () => {
|
test("defineCommand refuse un argument requis apres un optionnel", () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
extractTemplateVariables,
|
extractTemplateVariables,
|
||||||
hasTemplateVariable,
|
hasTemplateVariable,
|
||||||
renderTemplate,
|
renderTemplate,
|
||||||
} from "../src/framework/utils/templateVariables.js";
|
} from "../src/utils/templateVariables.js";
|
||||||
|
|
||||||
test("renderTemplate remplace les variables et applique les alias", () => {
|
test("renderTemplate remplace les variables et applique les alias", () => {
|
||||||
const output = renderTemplate(
|
const output = renderTemplate(
|
||||||
|
|||||||
Reference in New Issue
Block a user