diff --git a/src/interactions/client.ts b/src/interactions/client.ts index 36fae6bf..b6ed5055 100644 --- a/src/interactions/client.ts +++ b/src/interactions/client.ts @@ -32,6 +32,7 @@ import { Message } from '../structures/message.ts' import { MessageComponentInteraction } from '../structures/messageComponents.ts' import { AutocompleteInteraction } from '../structures/autocompleteInteraction.ts' import { ModalSubmitInteraction } from '../structures/modalSubmitInteraction.ts' +import { MessageComponentType } from '../types/messageComponents.ts' export type ApplicationCommandHandlerCallback = ( interaction: ApplicationCommandInteraction @@ -51,9 +52,7 @@ export type { ApplicationCommandHandlerCallback as SlashCommandHandlerCallback } export type { ApplicationCommandHandler as SlashCommandHandler } export type AutocompleteHandlerCallback = (d: AutocompleteInteraction) => any -export type ComponentInteractionCallback = ( - d: MessageComponentInteraction -) => any +export type ComponentInteractionCallback = (d: T) => any export interface AutocompleteHandler { cmd: string @@ -63,9 +62,11 @@ export interface AutocompleteHandler { handler: AutocompleteHandlerCallback } -export interface ComponentInteractionHandler { +// deno-lint-ignore no-explicit-any +export interface ComponentInteractionHandler { customID: string - handler: ComponentInteractionCallback + handler: ComponentInteractionCallback + type: 'button' | 'modal' } /** Options for InteractionsClient */ @@ -410,7 +411,7 @@ export class InteractionsClient extends HarmonyEventEmitter e.components).flat() ].find((e) => { - return i.customID === e.customID + if (i.customID !== e.customID) return false + + if (i.isMessageComponent() === true) { + return ( + e.type === 'button' && + i.data.component_type === MessageComponentType.BUTTON + ) + } + + return false + }) + } + + /** Get Handler for an modal submit Interaction. */ + private _getModalSubmitHandler( + i: ModalSubmitInteraction + ): ComponentInteractionHandler | undefined { + return [ + ...this.componentHandlers, + ...this.modules.map((e) => e.components).flat() + ].find((e) => { + if (i.customID !== e.customID) return false + + if (e.type === 'modal' && i.isModalSubmit() === true) { + return true + } + + return false }) } @@ -453,7 +481,24 @@ export class InteractionsClient extends HarmonyEventEmitter e.components).flat() - ].find((e) => e.customID === '*') + ].find((e) => e.customID === '*' && e.type === 'button') + + try { + await handle?.handler(interaction) + } catch (e) { + await this.emit('interactionError', e as Error) + } + return + } + + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (interaction.isModalSubmit()) { + const handle = + this._getModalSubmitHandler(interaction) ?? + [ + ...this.componentHandlers, + ...this.modules.map((e) => e.components).flat() + ].find((e) => e.customID === '*' && e.type === 'modal') try { await handle?.handler(interaction) diff --git a/src/interactions/decorators.ts b/src/interactions/decorators.ts index 1fb9dc4a..0274203d 100644 --- a/src/interactions/decorators.ts +++ b/src/interactions/decorators.ts @@ -12,6 +12,8 @@ import { ApplicationCommandsModule } from './commandModule.ts' import { ApplicationCommandInteraction } from '../structures/applicationCommand.ts' import { GatewayIntents } from '../types/gateway.ts' import { ApplicationCommandType } from '../types/applicationCommand.ts' +import { MessageComponentInteraction } from '../structures/messageComponents.ts'; +import { ModalSubmitInteraction } from '../structures/modalSubmitInteraction.ts'; /** Type extension that adds the `_decoratedAppCmd` list. */ interface DecoratedAppExt { @@ -50,10 +52,10 @@ type AutocompleteDecorator = ( desc: TypedPropertyDescriptor ) => void -type MessageComponentDecorator = ( +type MessageComponentDecorator = ( client: ApplicationCommandClientExt, prop: string, - desc: TypedPropertyDescriptor + desc: TypedPropertyDescriptor> ) => void /** @@ -378,11 +380,29 @@ export function userContextMenu(name?: string): ApplicationCommandDecorator { } } -export function messageComponent(customID?: string): MessageComponentDecorator { +/** + * Decorator to create a Button message component interaction handler. + * + * Example: + * ```ts + * class MyClient extends Client { + * // ... + * + * @messageComponent("custom_id") + * buttonHandler(i: MessageComponentInteraction) { + * // ... + * } + * } + * ``` + * + * First argument that is `name` is optional and can be + * inferred from method name. + */ +export function messageComponent(customID?: string): MessageComponentDecorator { return function ( client: ApplicationCommandClientExt, prop: string, - desc: TypedPropertyDescriptor + desc: TypedPropertyDescriptor> ) { if (client._decoratedComponents === undefined) client._decoratedComponents = [] @@ -391,7 +411,45 @@ export function messageComponent(customID?: string): MessageComponentDecorator { } else client._decoratedComponents.push({ customID: customID ?? prop, - handler: desc.value + handler: desc.value, + type: 'button' + }) + } +} + +/** + * Decorator to create a Modal submit interaction handler. + * + * Example: + * ```ts + * class MyClient extends Client { + * // ... + * + * @modalHandler("custom_id") + * modalSubmit(i: ModalSubmitInteraction) { + * // ... + * } + * } + * ``` + * + * First argument that is `name` is optional and can be + * inferred from method name. + */ +export function modalHandler(customID?: string): MessageComponentDecorator { + return function ( + client: ApplicationCommandClientExt, + prop: string, + desc: TypedPropertyDescriptor> + ) { + if (client._decoratedComponents === undefined) + client._decoratedComponents = [] + if (typeof desc.value !== 'function') { + throw new Error('@modalHandler decorator requires a function') + } else + client._decoratedComponents.push({ + customID: customID ?? prop, + handler: desc.value, + type: 'modal' }) } } diff --git a/test/slash.ts b/test/slash.ts index a22450fe..1ee23949 100644 --- a/test/slash.ts +++ b/test/slash.ts @@ -5,7 +5,9 @@ import { slash, messageComponent, MessageComponentInteraction, - SlashCommandInteraction + SlashCommandInteraction, + modalHandler, + ModalSubmitInteraction } from '../mod.ts' import { ApplicationCommandInteraction } from '../src/structures/applicationCommand.ts' import { ApplicationCommandOptionType as Type } from '../src/types/applicationCommand.ts' @@ -80,6 +82,10 @@ export class MyClient extends Client { { name: 'test3', description: 'Test command with a message component decorators.' + }, + { + name: 'test4', + description: 'Test command with a modal decorators.' } ], GUILD @@ -87,6 +93,31 @@ export class MyClient extends Client { this.interactions.commands.bulkEdit([]) } + @slash() test4(d: SlashCommandInteraction): void { + d.showModal({ + title: 'Test', + customID: 'modal_id', + components: [ + { + type: 1, + components: [ + { + type: 4, + customID: 'text_field_id', + placeholder: 'Test', + label: 'Test', + style: 1 + } + ] + } + ] + }) + } + + @modalHandler('modal_id') modal(d: ModalSubmitInteraction): void { + d.reply(JSON.stringify(d.data.components)) + } + @slash() test3(d: SlashCommandInteraction): void { d.reply({ components: [