From 776c604b3b4230365942eb8145a4fa07ccb2d440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Susa=C3=B1a?= Date: Wed, 27 Mar 2024 19:46:28 -0400 Subject: [PATCH] feat: Component Context (#166) * feat: component context --- src/client/base.ts | 2 + src/commands/applications/chatcontext.ts | 40 ++----- src/commands/applications/menucontext.ts | 32 ++---- src/commands/basecontex.ts | 33 ++++++ src/components/command.ts | 16 ++- src/components/componentcontext.ts | 133 +++++++++++++++++++++++ src/components/handler.ts | 14 ++- 7 files changed, 205 insertions(+), 65 deletions(-) create mode 100644 src/commands/basecontex.ts create mode 100644 src/components/componentcontext.ts diff --git a/src/client/base.ts b/src/client/base.ts index 4ecc16a..6791dfb 100644 --- a/src/client/base.ts +++ b/src/client/base.ts @@ -30,6 +30,7 @@ import { ComponentHandler, type ComponentHandlerLike } from '../components/handl import { LangsHandler, type LangsHandlerLike } from '../langs/handler'; import type { ChatInputCommandInteraction, + ComponentInteraction, Message, MessageCommandInteraction, UserCommandInteraction, @@ -284,6 +285,7 @@ export interface BaseClientOptions { | ChatInputCommandInteraction | UserCommandInteraction | MessageCommandInteraction + | ComponentInteraction | When, ) => {}; globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[]; diff --git a/src/commands/applications/chatcontext.ts b/src/commands/applications/chatcontext.ts index 4d28d19..0d0f2da 100644 --- a/src/commands/applications/chatcontext.ts +++ b/src/commands/applications/chatcontext.ts @@ -1,14 +1,4 @@ -import { - MenuCommandContext, - User, - type AllChannels, - type Guild, - type InferWithPrefix, - type MessageCommandInteraction, - type ReturnCache, - type UserCommandInteraction, - type WebhookMessage, -} from '../..'; +import type { AllChannels, Guild, InferWithPrefix, ReturnCache, WebhookMessage } from '../..'; import type { Client, WorkerClient } from '../../client'; import { MessageFlags, type If, type UnionToTuple, type When } from '../../common'; import type { InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest } from '../../common/types/write'; @@ -18,15 +8,20 @@ import { type GuildMember, type InteractionGuildMember, } from '../../structures'; +import { BaseContext } from '../basecontex'; import type { RegisteredMiddlewares } from '../decorators'; import type { OptionResolver } from '../optionresolver'; import type { Command, ContextOptions, OptionsRecord, SubCommand } from './chat'; import type { CommandMetadata, ExtendContext, GlobalMetadata, UsingClient } from './shared'; export interface CommandContext - extends ExtendContext {} + extends BaseContext, + ExtendContext {} -export class CommandContext { +export class CommandContext< + T extends OptionsRecord = {}, + M extends keyof RegisteredMiddlewares = never, +> extends BaseContext { message!: If; interaction!: If; @@ -38,6 +33,7 @@ export class CommandContext { return this.interaction?.member || ((this.message! as Message)?.member as any); } - - isChat(): this is CommandContext { - return this instanceof CommandContext; - } - - isMenu(): this is MenuCommandContext { - return this instanceof MenuCommandContext; - } - - isMenuUser(): this is MenuCommandContext { - return this instanceof MenuCommandContext && this.target instanceof User; - } - - isMenuMessage(): this is MenuCommandContext { - return this instanceof MenuCommandContext && this.target instanceof Message; - } } diff --git a/src/commands/applications/menucontext.ts b/src/commands/applications/menucontext.ts index 869f7e5..4a00fc6 100644 --- a/src/commands/applications/menucontext.ts +++ b/src/commands/applications/menucontext.ts @@ -1,4 +1,4 @@ -import { CommandContext, type ContextMenuCommand, type ReturnCache, type WebhookMessage } from '../..'; +import type { ContextMenuCommand, ReturnCache, WebhookMessage } from '../..'; import { ApplicationCommandType, MessageFlags, @@ -17,6 +17,7 @@ import { type MessageCommandInteraction, type UserCommandInteraction, } from '../../structures'; +import { BaseContext } from '../basecontex'; import type { RegisteredMiddlewares } from '../decorators'; import type { CommandMetadata, ExtendContext, GlobalMetadata, UsingClient } from './shared'; @@ -25,26 +26,25 @@ export type InteractionTarget = T extends MessageCommandInteraction ? Message export interface MenuCommandContext< T extends MessageCommandInteraction | UserCommandInteraction, M extends keyof RegisteredMiddlewares = never, -> extends ExtendContext {} +> extends BaseContext, + ExtendContext {} export class MenuCommandContext< T extends MessageCommandInteraction | UserCommandInteraction, M extends keyof RegisteredMiddlewares = never, -> { +> extends BaseContext { constructor( readonly client: UsingClient, readonly interaction: T, readonly shardId: number, readonly command: ContextMenuCommand, - ) {} + ) { + super(client); + } metadata: CommandMetadata> = {} as never; globalMetadata: GlobalMetadata = {}; - get proxy() { - return this.client.proxy; - } - // biome-ignore lint/suspicious/useGetterReturn: default don't exist. get target(): InteractionTarget { switch (this.interaction.data.type) { @@ -152,20 +152,4 @@ export class MenuCommandContext< get member() { return this.interaction.member; } - - isChat(): this is CommandContext { - return this instanceof CommandContext; - } - - isMenu(): this is MenuCommandContext { - return this instanceof MenuCommandContext; - } - - isMenuUser(): this is MenuCommandContext { - return this instanceof MenuCommandContext && this.target instanceof User; - } - - isMenuMessage(): this is MenuCommandContext { - return this instanceof MenuCommandContext && this.target instanceof Message; - } } diff --git a/src/commands/basecontex.ts b/src/commands/basecontex.ts new file mode 100644 index 0000000..ab866d1 --- /dev/null +++ b/src/commands/basecontex.ts @@ -0,0 +1,33 @@ +import { ComponentContext, type ComponentCommandInteractionMap } from '../components/componentcontext'; +import { Message, User, type MessageCommandInteraction, type UserCommandInteraction } from '../structures'; +import { CommandContext } from './applications/chatcontext'; +import { MenuCommandContext } from './applications/menucontext'; +import type { UsingClient } from './applications/shared'; + +export class BaseContext { + constructor(readonly client: UsingClient) {} + + get proxy() { + return this.client.proxy; + } + + isChat(): this is CommandContext { + return this instanceof CommandContext; + } + + isMenu(): this is MenuCommandContext { + return this instanceof MenuCommandContext; + } + + isMenuUser(): this is MenuCommandContext { + return this instanceof MenuCommandContext && this.target instanceof User; + } + + isMenuMessage(): this is MenuCommandContext { + return this instanceof MenuCommandContext && this.target instanceof Message; + } + + isComponent(): this is ComponentContext { + return this instanceof ComponentContext; + } +} diff --git a/src/components/command.ts b/src/components/command.ts index 9d69e80..8f96508 100644 --- a/src/components/command.ts +++ b/src/components/command.ts @@ -1,5 +1,6 @@ -import type { ComponentType } from 'discord-api-types/v10'; -import type { ComponentInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from '../structures'; +import { ComponentType } from 'discord-api-types/v10'; +import type { ModalSubmitInteraction } from '../structures'; +import type { ComponentCommandInteractionMap, ComponentContext } from './componentcontext'; export const InteractionCommandType = { COMPONENT: 0, @@ -12,9 +13,14 @@ export interface ComponentCommand { export abstract class ComponentCommand { type = InteractionCommandType.COMPONENT; - abstract componentType: ComponentType; - abstract filter(interaction: ComponentInteraction | StringSelectMenuInteraction): Promise | boolean; - abstract run(interaction: ComponentInteraction | StringSelectMenuInteraction): any; + abstract componentType: keyof ComponentCommandInteractionMap; + abstract filter(interaction: ComponentContext): Promise | boolean; + abstract run(interaction: ComponentContext): any; + + get cType(): number { + // @ts-expect-error + return ComponentType[this.componentType]; + } } export interface ModalCommand { diff --git a/src/components/componentcontext.ts b/src/components/componentcontext.ts new file mode 100644 index 0000000..a7dc3fc --- /dev/null +++ b/src/components/componentcontext.ts @@ -0,0 +1,133 @@ +import { MessageFlags } from 'discord-api-types/v10'; +import type { + AllChannels, + ButtonInteraction, + ChannelSelectMenuInteraction, + ComponentInteraction, + Guild, + GuildMember, + MentionableSelectMenuInteraction, + Message, + ReturnCache, + RoleSelectMenuInteraction, + StringSelectMenuInteraction, + UserSelectMenuInteraction, + WebhookMessage, +} from '..'; +import type { ExtendContext, UsingClient } from '../commands'; +import { BaseContext } from '../commands/basecontex'; +import type { InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest, When } from '../common'; + +export interface ComponentContext + extends BaseContext, + ExtendContext {} + +export class ComponentContext extends BaseContext { + constructor( + readonly client: UsingClient, + public interaction: ComponentCommandInteractionMap[Type] | ComponentInteraction, + ) { + super(client); + } + + get proxy() { + return this.client.proxy; + } + + get t() { + return this.client.langs!.get(this.interaction?.locale ?? this.client.langs?.defaultLang ?? 'en-US'); + } + + get customId() { + return this.interaction.customId; + } + + get write() { + return this.interaction.write; + } + + deferReply(ephemeral = false) { + return this.interaction.deferReply(ephemeral ? MessageFlags.Ephemeral : undefined); + } + + get editResponse() { + return this.interaction.editResponse; + } + + get update() { + return this.interaction.update; + } + + editOrReply( + body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest, + fetchReply?: FR, + ): Promise> { + return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply); + } + + deleteResponse() { + return this.interaction.deleteResponse(); + } + channel(mode?: 'rest' | 'flow'): Promise; + channel(mode?: 'cache'): ReturnCache; + channel(mode: 'cache' | 'rest' | 'flow' = 'cache') { + if (this.interaction?.channel && mode === 'cache') + return this.client.cache.adapter.isAsync ? Promise.resolve(this.interaction.channel) : this.interaction.channel; + return this.client.channels.fetch(this.channelId, mode === 'rest'); + } + + me(mode?: 'rest' | 'flow'): Promise; + me(mode?: 'cache'): ReturnCache; + me(mode: 'cache' | 'rest' | 'flow' = 'cache') { + if (!this.guildId) + return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve(); + switch (mode) { + case 'cache': + return this.client.cache.members?.get(this.client.botId, this.guildId); + default: + return this.client.members.fetch(this.guildId, this.client.botId, mode === 'rest'); + } + } + + guild(mode?: 'rest' | 'flow'): Promise | undefined>; + guild(mode?: 'cache'): ReturnCache | undefined>; + guild(mode: 'cache' | 'rest' | 'flow' = 'cache') { + if (!this.guildId) + return ( + mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve() + ) as any; + switch (mode) { + case 'cache': + return this.client.cache.guilds?.get(this.guildId); + default: + return this.client.guilds.fetch(this.guildId, mode === 'rest'); + } + } + + get guildId() { + return this.interaction.guildId; + } + + get channelId() { + return this.interaction.channelId!; + } + + get author() { + return this.interaction.user; + } + + get member() { + return this.interaction.member; + } +} + +export interface ComponentCommandInteractionMap { + ActionRow: never; + Button: ButtonInteraction; + StringSelect: StringSelectMenuInteraction; + TextInput: never; + UserSelect: UserSelectMenuInteraction; + RoleSelect: RoleSelectMenuInteraction; + MentioableSelect: MentionableSelectMenuInteraction; + ChannelSelect: ChannelSelectMenuInteraction; +} diff --git a/src/components/handler.ts b/src/components/handler.ts index c655b4d..a55ecda 100644 --- a/src/components/handler.ts +++ b/src/components/handler.ts @@ -4,6 +4,7 @@ import type { UsingClient } from '../commands'; import { BaseHandler, magicImport, type Logger, type OnFailCallback } from '../common'; import type { ComponentInteraction, ModalSubmitInteraction } from '../structures'; import { ComponentCommand, InteractionCommandType, ModalCommand } from './command'; +import { ComponentContext } from './componentcontext'; type COMPONENTS = { components: { match: string | string[] | RegExp; callback: ComponentCallback }[]; @@ -218,12 +219,13 @@ export class ComponentHandler extends BaseHandler { async executeComponent(interaction: ComponentInteraction) { for (const i of this.commands) { try { - if ( - i.type === InteractionCommandType.COMPONENT && - i.componentType === interaction.componentType && - (await i.filter(interaction)) - ) { - await i.run(interaction); + if (i.type === InteractionCommandType.COMPONENT && i.cType === interaction.componentType) { + const context = new ComponentContext(this.client, interaction); + const extended = this.client.options?.context?.(interaction) ?? {}; + Object.assign(context, extended); + // @ts-expect-error + if (!(await i.filter(interaction))) continue; + await i.run(context); break; } } catch (e) {