diff --git a/.changeset/curly-dots-burn.md b/.changeset/curly-dots-burn.md new file mode 100644 index 00000000..82d30b40 --- /dev/null +++ b/.changeset/curly-dots-burn.md @@ -0,0 +1,5 @@ +--- +"@buape/carbon": patch +--- + +feat: make components automatically registered diff --git a/apps/cloudo/src/commands/testing/button.ts b/apps/cloudo/src/commands/testing/button.ts index e5f6cf61..f434617a 100644 --- a/apps/cloudo/src/commands/testing/button.ts +++ b/apps/cloudo/src/commands/testing/button.ts @@ -12,7 +12,6 @@ export default class ButtonCommand extends Command { name = "button" description = "A simple command with a button!" defer = true - components = [new PingButton(), new Link()] async run(interaction: CommandInteraction) { await interaction.reply({ diff --git a/apps/cloudo/src/commands/testing/every_select.ts b/apps/cloudo/src/commands/testing/every_select.ts index ce0923e1..fa70fa8c 100644 --- a/apps/cloudo/src/commands/testing/every_select.ts +++ b/apps/cloudo/src/commands/testing/every_select.ts @@ -18,13 +18,6 @@ export default class SelectCommand extends Command { name = "every_select" description = "Send every select menu" defer = true - components = [ - new StringSelect(), - new RoleSelect(), - new MentionableSelect(), - new ChannelSelect(), - new UserSelect() - ] async run(interaction: CommandInteraction) { interaction.reply({ diff --git a/apps/rocko/src/commands/testing/button.ts b/apps/rocko/src/commands/testing/button.ts index e5f6cf61..f434617a 100644 --- a/apps/rocko/src/commands/testing/button.ts +++ b/apps/rocko/src/commands/testing/button.ts @@ -12,7 +12,6 @@ export default class ButtonCommand extends Command { name = "button" description = "A simple command with a button!" defer = true - components = [new PingButton(), new Link()] async run(interaction: CommandInteraction) { await interaction.reply({ diff --git a/apps/rocko/src/commands/testing/every_select.ts b/apps/rocko/src/commands/testing/every_select.ts index ce0923e1..be308acd 100644 --- a/apps/rocko/src/commands/testing/every_select.ts +++ b/apps/rocko/src/commands/testing/every_select.ts @@ -18,24 +18,18 @@ export default class SelectCommand extends Command { name = "every_select" description = "Send every select menu" defer = true - components = [ - new StringSelect(), - new RoleSelect(), - new MentionableSelect(), - new ChannelSelect(), - new UserSelect() - ] async run(interaction: CommandInteraction) { + const row = new Row() + row.addComponent(new StringSelect()) + row.addComponent(new RoleSelect()) + row.addComponent(new MentionableSelect()) + row.addComponent(new ChannelSelect()) + row.addComponent(new UserSelect()) + interaction.reply({ content: "Select menus! <:caughtIn4k:1145473115703496816>", - components: [ - new Row([new StringSelect()]), - new Row([new RoleSelect()]), - new Row([new MentionableSelect()]), - new Row([new ChannelSelect()]), - new Row([new UserSelect()]) - ] + components: [row] }) } } diff --git a/packages/carbon/src/abstracts/BaseCommand.ts b/packages/carbon/src/abstracts/BaseCommand.ts index 51410231..b23cf91b 100644 --- a/packages/carbon/src/abstracts/BaseCommand.ts +++ b/packages/carbon/src/abstracts/BaseCommand.ts @@ -2,11 +2,7 @@ import { ApplicationCommandType, type RESTPostAPIApplicationCommandsJSONBody } from "discord-api-types/v10" -import { - ApplicationIntegrationType, - type BaseComponent, - InteractionContextType -} from "../index.js" +import { ApplicationIntegrationType, InteractionContextType } from "../index.js" /** * Represents the base data of a command that the user creates @@ -50,12 +46,6 @@ export abstract class BaseCommand { InteractionContextType.PrivateChannel ] - /** - * All the components that the command is able to use. - * You mount these here so the handler can access them - */ - components?: BaseComponent[] = [] - /** * All the paginators that the command is able to use. * You mount these here so the handler can access them diff --git a/packages/carbon/src/abstracts/BaseInteraction.ts b/packages/carbon/src/abstracts/BaseInteraction.ts index 7b7a0519..9e8b9482 100644 --- a/packages/carbon/src/abstracts/BaseInteraction.ts +++ b/packages/carbon/src/abstracts/BaseInteraction.ts @@ -36,6 +36,10 @@ export type InteractionReplyOptions = { * The files to send in the interaction */ files?: InteractionFileData[] + /** + * Whether the interaction should be ephemeral + */ + ephemeral?: boolean } /** @@ -80,12 +84,15 @@ export abstract class BaseInteraction extends Base { */ _deferred = false - constructor(client: Client, data: T) { + private defaultEphemeral = false + + constructor(client: Client, data: T, defaults: { ephemeral?: boolean } = {}) { super(client) this.rawData = data this.type = data.type this.userId = this.rawData.user?.id || this.rawData.member?.user.id || undefined + if (defaults.ephemeral) this.defaultEphemeral = defaults.ephemeral } get message(): Message | null { @@ -125,6 +132,11 @@ export abstract class BaseInteraction extends Base { data: InteractionReplyData, options: InteractionReplyOptions = {} ) { + data.components?.map((row) => { + row.components.map((component) => { + this.client.componentHandler.registerComponent(component) + }) + }) if (this._deferred) { await this.client.rest.patch( Routes.webhookMessage( @@ -148,7 +160,8 @@ export abstract class BaseInteraction extends Base { type: InteractionResponseType.ChannelMessageWithSource, data: { ...data, - components: data.components?.map((row) => row.serialize()) + components: data.components?.map((row) => row.serialize()), + ephemeral: options.ephemeral ?? this.defaultEphemeral } }, files: options.files @@ -169,7 +182,8 @@ export abstract class BaseInteraction extends Base { Routes.interactionCallback(this.rawData.id, this.rawData.token), { body: { - type: InteractionResponseType.DeferredChannelMessageWithSource + type: InteractionResponseType.DeferredChannelMessageWithSource, + ephemeral: this.defaultEphemeral } } ) diff --git a/packages/carbon/src/classes/Row.ts b/packages/carbon/src/classes/Row.ts index df9c96ce..56577d01 100644 --- a/packages/carbon/src/classes/Row.ts +++ b/packages/carbon/src/classes/Row.ts @@ -4,10 +4,10 @@ export class Row { /** * The components in the action row */ - components: BaseComponent[] + components: BaseComponent[] = [] - constructor(components: BaseComponent[]) { - this.components = components + constructor(components?: BaseComponent[]) { + if (components) this.components = components } /** diff --git a/packages/carbon/src/internals/CommandHandler.ts b/packages/carbon/src/internals/CommandHandler.ts index 7918f9b6..ea1abe61 100644 --- a/packages/carbon/src/internals/CommandHandler.ts +++ b/packages/carbon/src/internals/CommandHandler.ts @@ -76,6 +76,10 @@ export class CommandHandler extends Base { throw new Error("Command is not a valid command type") } + /** + * Handle a command interaction + * @internal + */ async handleCommandInteraction( rawInteraction: APIApplicationCommandInteraction ) { diff --git a/packages/carbon/src/internals/ComponentHandler.ts b/packages/carbon/src/internals/ComponentHandler.ts index 0591d867..b1a8961e 100644 --- a/packages/carbon/src/internals/ComponentHandler.ts +++ b/packages/carbon/src/internals/ComponentHandler.ts @@ -19,11 +19,22 @@ import { StringSelectMenuInteraction } from "./StringSelectMenuInteraction.js" import { UserSelectMenuInteraction } from "./UserSelectMenuInteraction.js" export class ComponentHandler extends Base { + components: BaseComponent[] = [] + /** + * Register a component with the handler + * @internal + */ + registerComponent(component: BaseComponent) { + if (!this.components.find((x) => x.customId === component.customId)) { + this.components.push(component) + } + } + /** + * Handle an interaction + * @internal + */ async handleInteraction(data: APIMessageComponentInteraction) { - const allComponents = this.client.commands - .filter((x) => x.components && x.components.length > 0) - .flatMap((x) => x.components) as BaseComponent[] - const component = allComponents.find( + const component = this.components.find( (x) => x.customId === data.data.custom_id && x.type === data.data.component_type