From 906aa0d8686b05d68450ccf6d80e6e77e8463f29 Mon Sep 17 00:00:00 2001 From: didinele Date: Thu, 27 Jun 2024 20:33:19 +0300 Subject: [PATCH] feat: command handler registration --- .../src/command-framework/CommandHandler.ts | 102 +++++++++--------- .../src/command-framework/ICommandHandler.ts | 66 +++++++++++- packages/core/src/index.ts | 9 +- .../{singletons => util}/DependencyManager.ts | 2 +- packages/core/src/{singletons => util}/Env.ts | 0 5 files changed, 123 insertions(+), 56 deletions(-) rename packages/core/src/{singletons => util}/DependencyManager.ts (97%) rename packages/core/src/{singletons => util}/Env.ts (100%) diff --git a/packages/core/src/command-framework/CommandHandler.ts b/packages/core/src/command-framework/CommandHandler.ts index 18f7e630..d1ab058d 100644 --- a/packages/core/src/command-framework/CommandHandler.ts +++ b/packages/core/src/command-framework/CommandHandler.ts @@ -3,12 +3,8 @@ import { InteractionType, type APIApplicationCommandAutocompleteInteraction, type APIApplicationCommandInteraction, - type APIApplicationCommandInteractionDataIntegerOption, - type APIApplicationCommandInteractionDataNumberOption, - type APIApplicationCommandInteractionDataStringOption, type APIInteraction, - type APIMessageComponentInteraction, - type APIModalSubmitInteraction, + type RESTPostAPIApplicationCommandsJSONBody, } from '@discordjs/core'; import { InteractionOptionResolver } from '@sapphire/discord-utilities'; import { inject, injectable } from 'inversify'; @@ -17,56 +13,23 @@ import type { Logger } from 'pino'; import type { IDataManager } from '../applicationData/IDataManager.js'; import { INJECTION_TOKENS } from '../container.js'; import type { Incident } from '../db.js'; -import { ICommandHandler } from './ICommandHandler.js'; - -/** - * @internal - */ -interface ResolvedCommandIdentifier { - root: ApplicationCommandIdentifier; - subcommand?: ApplicationCommandIdentifier; -} - -/** - * The identifier of a command. - * `name:group:subcommand` - */ -export type ApplicationCommandIdentifier = `${string}:${string}:${string}`; - -/** - * Callback responsible for handling application commands. - */ -export type ApplicationCommandHandler = ( - interaction: APIApplicationCommandInteraction, - options: InteractionOptionResolver, -) => Promise; - -/** - * Callback responsible for handling components. - */ -export type ComponentHandler = (interaction: APIMessageComponentInteraction, args: string[]) => Promise; - -// [command]:argName -export type AutocompleteIdentifier = `${ApplicationCommandIdentifier}:${string}`; - -/** - * Callback responsible for handling autocompletes. - */ -export type AutocompleteHandler = ( - interaction: APIApplicationCommandAutocompleteInteraction, - option: - | APIApplicationCommandInteractionDataIntegerOption - | APIApplicationCommandInteractionDataNumberOption - | APIApplicationCommandInteractionDataStringOption, -) => Promise; - -/** - * Callback responsible for handling modals. - */ -export type ModalHandler = (interaction: APIModalSubmitInteraction, args: string[]) => Promise; +import type { Env } from '../util/Env.js'; +import { + ICommandHandler, + type ApplicationCommandHandler, + type ApplicationCommandIdentifier, + type AutocompleteHandler, + type AutocompleteIdentifier, + type ComponentHandler, + type ModalHandler, + type RegisterOptions, + type ResolvedCommandIdentifier, +} from './ICommandHandler.js'; @injectable() export class CommandHandler extends ICommandHandler { + readonly #interactions: RESTPostAPIApplicationCommandsJSONBody[] = []; + readonly #handlers = { applicationCommands: new Map(), components: new Map(), @@ -77,11 +40,16 @@ export class CommandHandler extends ICommandHandler { public constructor( private readonly api: API, private readonly database: IDataManager, + private readonly env: Env, @inject(INJECTION_TOKENS.logger) private readonly logger: Logger, ) { super(); } + public async deployCommands(): Promise { + await this.api.applicationCommands.bulkOverwriteGlobalCommands(this.env.discordClientId, this.#interactions); + } + public async handle(interaction: APIInteraction): Promise { if (!interaction.guild_id) { const incident = await this.database.createIncident(new Error('Interaction not in guild')); @@ -181,6 +149,36 @@ export class CommandHandler extends ICommandHandler { } } + public register(options: RegisterOptions): void { + if (options.interactions) { + this.#interactions.push(...options.interactions); + } + + if (options.applicationCommands?.length) { + for (const [identifier, handler] of options.applicationCommands) { + this.#handlers.applicationCommands.set(identifier, handler); + } + } + + if (options.components?.length) { + for (const [name, handler] of options.components) { + this.#handlers.components.set(name, handler); + } + } + + if (options.autocomplete?.length) { + for (const [identifier, handler] of options.autocomplete) { + this.#handlers.autocomplete.set(identifier, handler); + } + } + + if (options.modals?.length) { + for (const [name, handler] of options.modals) { + this.#handlers.modals.set(name, handler); + } + } + } + /** * Resolves the command identifier from the interaction. * diff --git a/packages/core/src/command-framework/ICommandHandler.ts b/packages/core/src/command-framework/ICommandHandler.ts index 11c84517..d0622fd7 100644 --- a/packages/core/src/command-framework/ICommandHandler.ts +++ b/packages/core/src/command-framework/ICommandHandler.ts @@ -1,5 +1,69 @@ -import type { APIInteraction } from '@discordjs/core'; +import type { + APIApplicationCommandAutocompleteInteraction, + APIApplicationCommandInteraction, + APIApplicationCommandInteractionDataIntegerOption, + APIApplicationCommandInteractionDataNumberOption, + APIApplicationCommandInteractionDataStringOption, + APIInteraction, + APIMessageComponentInteraction, + APIModalSubmitInteraction, + RESTPostAPIApplicationCommandsJSONBody, +} from '@discordjs/core'; +import type { InteractionOptionResolver } from '@sapphire/discord-utilities'; + +export interface ResolvedCommandIdentifier { + root: ApplicationCommandIdentifier; + subcommand?: ApplicationCommandIdentifier; +} + +/** + * The identifier of a command. + * `name:group:subcommand` + */ +export type ApplicationCommandIdentifier = `${string}:${string}:${string}`; + +/** + * Callback responsible for handling application commands. + */ +export type ApplicationCommandHandler = ( + interaction: APIApplicationCommandInteraction, + options: InteractionOptionResolver, +) => Promise; + +/** + * Callback responsible for handling components. + */ +export type ComponentHandler = (interaction: APIMessageComponentInteraction, args: string[]) => Promise; + +// [command]:argName +export type AutocompleteIdentifier = `${ApplicationCommandIdentifier}:${string}`; + +/** + * Callback responsible for handling autocompletes. + */ +export type AutocompleteHandler = ( + interaction: APIApplicationCommandAutocompleteInteraction, + option: + | APIApplicationCommandInteractionDataIntegerOption + | APIApplicationCommandInteractionDataNumberOption + | APIApplicationCommandInteractionDataStringOption, +) => Promise; + +/** + * Callback responsible for handling modals. + */ +export type ModalHandler = (interaction: APIModalSubmitInteraction, args: string[]) => Promise; + +export interface RegisterOptions { + applicationCommands?: [ApplicationCommandIdentifier, ApplicationCommandHandler][]; + autocomplete?: [AutocompleteIdentifier, AutocompleteHandler][]; + components?: [string, ComponentHandler][]; + interactions?: RESTPostAPIApplicationCommandsJSONBody[]; + modals?: [string, ModalHandler][]; +} export abstract class ICommandHandler { public abstract handle(interaction: APIInteraction): Promise; + public abstract register(options: RegisterOptions): void; + public abstract deployCommands(): Promise; } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9d3d6500..a1a23e40 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -14,10 +14,15 @@ export * from './cache/entities/ICacheEntity.js'; export * from './cache/CacheFactory.js'; export * from './cache/ICache.js'; -export * from './singletons/DependencyManager.js'; -export * from './singletons/Env.js'; +// Deliberately don't export impl +export * from './command-framework/ICommandHandler.js'; + +// Deliberately don't export impl +export * from './experiments/IExperimentHandler.js'; +export * from './util/DependencyManager.js'; export * from './util/encode.js'; +export * from './util/Env.js'; export * from './util/parseRelativeTime.js'; export * from './util/PermissionsBitField.js'; export * from './util/promiseAllObject.js'; diff --git a/packages/core/src/singletons/DependencyManager.ts b/packages/core/src/util/DependencyManager.ts similarity index 97% rename from packages/core/src/singletons/DependencyManager.ts rename to packages/core/src/util/DependencyManager.ts index 14ceacc5..450c4a42 100644 --- a/packages/core/src/singletons/DependencyManager.ts +++ b/packages/core/src/util/DependencyManager.ts @@ -9,8 +9,8 @@ import { GuildCacheEntity, type CachedGuild } from '../cache/entities/GuildCache import type { ICacheEntity } from '../cache/entities/ICacheEntity.js'; import { INJECTION_TOKENS, globalContainer } from '../container.js'; import type { DB } from '../db.js'; -import type { TransportOptions } from '../util/loggingTransport.js'; import { Env } from './Env.js'; +import type { TransportOptions } from './loggingTransport.js'; // no proper ESM support const { diff --git a/packages/core/src/singletons/Env.ts b/packages/core/src/util/Env.ts similarity index 100% rename from packages/core/src/singletons/Env.ts rename to packages/core/src/util/Env.ts