diff --git a/src/client/base.ts b/src/client/base.ts index 99e8c41..fb45eb4 100644 --- a/src/client/base.ts +++ b/src/client/base.ts @@ -1,474 +1,482 @@ -import { join } from 'node:path'; -import { ApiHandler, Router } from '../api'; -import type { Adapter } from '../cache'; -import { Cache, MemoryAdapter } from '../cache'; -import type { Command, CommandContext, OnOptionsReturnObject, RegisteredMiddlewares, UsingClient } from '../commands'; -import { IgnoreCommand, type InferWithPrefix, type MiddlewareContext } from '../commands/applications/shared'; -import { CommandHandler } from '../commands/handler'; -import { - ChannelShorter, - EmojiShorter, - GuildShorter, - InteractionShorter, - LogLevels, - Logger, - MemberShorter, - MergeOptions, - MessageShorter, - ReactionShorter, - RoleShorter, - TemplateShorter, - ThreadShorter, - UsersShorter, - WebhookShorter, - filterSplit, - magicImport, - type MakeRequired, -} from '../common'; - -import type { LocaleString, RESTPostAPIChannelMessageJSONBody } from 'discord-api-types/rest/v10'; -import type { Awaitable, DeepPartial, IntentStrings, OmitInsert, PermissionStrings, When } from '../common/types/util'; -import { ComponentHandler } from '../components/handler'; -import { LangsHandler } from '../langs/handler'; -import type { - ChatInputCommandInteraction, - ComponentInteraction, - Message, - MessageCommandInteraction, - ModalSubmitInteraction, - UserCommandInteraction, -} from '../structures'; -import type { ComponentCommand, ComponentContext, ModalCommand, ModalContext } from '../components'; -import { promises } from 'node:fs'; - -export class BaseClient { - rest!: ApiHandler; - cache!: Cache; - - users = new UsersShorter(this); - channels = new ChannelShorter(this); - guilds = new GuildShorter(this); - messages = new MessageShorter(this); - members = new MemberShorter(this); - webhooks = new WebhookShorter(this); - templates = new TemplateShorter(this); - roles = new RoleShorter(this); - reactions = new ReactionShorter(this); - emojis = new EmojiShorter(this); - threads = new ThreadShorter(this); - interactions = new InteractionShorter(this); - - debugger?: Logger; - - logger = new Logger({ - name: '[Seyfert]', - }); - - langs? = new LangsHandler(this.logger); - commands? = new CommandHandler(this.logger, this); - components? = new ComponentHandler(this.logger, this); - - private _applicationId?: string; - private _botId?: string; - middlewares?: Record; - - protected static assertString(value: unknown, message?: string): asserts value is string { - if (!(typeof value === 'string' && value !== '')) { - throw new Error(message ?? 'Value is not a string'); - } - } - - protected static getBotIdFromToken(token: string): string { - return Buffer.from(token.split('.')[0], 'base64').toString('ascii'); - } - - options: BaseClientOptions; - - /**@internal */ - static _seyfertConfig?: InternalRuntimeConfigHTTP | InternalRuntimeConfig; - - constructor(options?: BaseClientOptions) { - this.options = MergeOptions( - { - commands: { - defaults: { - onRunError(context: CommandContext, error: unknown): any { - context.client.logger.fatal(`${context.command.name}.`, context.author.id, error); - }, - onOptionsError(context: CommandContext<{}, never>, metadata: OnOptionsReturnObject): any { - context.client.logger.fatal(`${context.command.name}.`, context.author.id, metadata); - }, - onMiddlewaresError(context: CommandContext<{}, never>, error: string): any { - context.client.logger.fatal(`${context.command.name}.`, context.author.id, error); - }, - onBotPermissionsFail(context: CommandContext<{}, never>, permissions: PermissionStrings): any { - context.client.logger.fatal( - `${context.command.name}.`, - context.author.id, - permissions, - ); - }, - onPermissionsFail(context: CommandContext<{}, never>, permissions: PermissionStrings): any { - context.client.logger.fatal( - `${context.command.name}.`, - context.author.id, - permissions, - ); - }, - onInternalError(client: UsingClient, command: Command, error?: unknown): any { - client.logger.fatal(`${command.name}.`, error); - }, - }, - }, - components: { - defaults: { - onRunError(context: ComponentContext, error: unknown): any { - context.client.logger.fatal('ComponentCommand.', context.author.id, error); - }, - onMiddlewaresError(context: ComponentContext, error: string): any { - context.client.logger.fatal('ComponentCommand.', context.author.id, error); - }, - onInternalError(client: UsingClient, error?: unknown): any { - client.logger.fatal(error); - }, - }, - }, - modals: { - defaults: { - onRunError(context: ModalContext, error: unknown): any { - context.client.logger.fatal('ComponentCommand.', context.author.id, error); - }, - onMiddlewaresError(context: ModalContext, error: string): any { - context.client.logger.fatal('ComponentCommand.', context.author.id, error); - }, - onInternalError(client: UsingClient, error?: unknown): any { - client.logger.fatal(error); - }, - }, - }, - } satisfies BaseClientOptions, - options, - ); - } - - set botId(id: string) { - this._botId = id; - } - - get botId() { - return this._botId ?? BaseClient.getBotIdFromToken(this.rest.options.token); - } - - set applicationId(id: string) { - this._applicationId = id; - } - - get applicationId() { - return this._applicationId ?? this.botId; - } - - get proxy() { - return new Router(this.rest).createProxy(); - } - - setServices({ rest, cache, langs, middlewares, handlers }: ServicesOptions) { - if (rest) { - this.rest = rest; - } - if (cache) { - this.cache = new Cache( - this.cache?.intents ?? 0, - cache?.adapter ?? this.cache?.adapter ?? new MemoryAdapter(), - cache.disabledCache ?? this.cache?.disabledCache ?? [], - this, - ); - } - if (middlewares) { - this.middlewares = middlewares; - } - if (handlers) { - if ('components' in handlers) { - if (!handlers.components) { - this.components = undefined; - } else if (typeof handlers.components === 'function') { - this.components ??= new ComponentHandler(this.logger, this); - this.components.setHandlers({ callback: handlers.components }); - } else { - this.components = handlers.components; - } - } - if ('commands' in handlers) { - if (!handlers.commands) { - this.commands = undefined; - } else if (typeof handlers.commands === 'object') { - this.commands ??= new CommandHandler(this.logger, this); - this.commands.setHandlers(handlers.commands); - } else { - this.commands = handlers.commands; - } - } - if ('langs' in handlers) { - if (!handlers.langs) { - this.langs = undefined; - } else if (typeof handlers.langs === 'function') { - this.langs ??= new LangsHandler(this.logger); - this.langs.setHandlers({ callback: handlers.langs }); - } else { - this.langs = handlers.langs; - } - } - } - if (langs) { - if (langs.default) this.langs!.defaultLang = langs.default; - if (langs.aliases) this.langs!.aliases = Object.entries(langs.aliases); - } - } - - protected async execute(..._options: unknown[]) { - if ((await this.getRC()).debug) { - this.debugger = new Logger({ - name: '[Debug]', - logLevel: LogLevels.Debug, - }); - } - } - - async start( - options: Pick, 'langsDir' | 'commandsDir' | 'connection' | 'token' | 'componentsDir'> = { - token: undefined, - langsDir: undefined, - commandsDir: undefined, - connection: undefined, - componentsDir: undefined, - }, - ) { - await this.loadLangs(options.langsDir); - await this.loadCommands(options.commandsDir); - await this.loadComponents(options.componentsDir); - - const { token: tokenRC } = await this.getRC(); - const token = options?.token ?? tokenRC; - - if (!this.rest) { - BaseClient.assertString(token, 'token is not a string'); - this.rest = new ApiHandler({ - token, - baseUrl: 'api/v10', - domain: 'https://discord.com', - debug: (await this.getRC()).debug, - }); - } - - if (this.cache) { - this.cache.__setClient(this); - } else { - this.cache = new Cache(0, new MemoryAdapter(), [], this); - } - } - - protected async onPacket(..._packet: unknown[]) { - throw new Error('Function not implemented'); - } - - shouldUploadCommands(cachePath: string) { - return this.commands!.shouldUpload(cachePath).then(async should => { - if (should) await promises.writeFile(cachePath, JSON.stringify(this.commands!.values.map(x => x.toJSON()))); - return should; - }); - } - - async uploadCommands(applicationId?: string) { - applicationId ??= await this.getRC().then(x => x.applicationId ?? this.applicationId); - BaseClient.assertString(applicationId, 'applicationId is not a string'); - - const commands = this.commands!.values; - const filter = filterSplit(commands, command => !command.guildId); - - await this.proxy.applications(applicationId).commands.put({ - body: filter.expect.filter(cmd => !('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash).map(x => x.toJSON()), - }); - - const guilds = new Set(); - - for (const command of filter.never) { - for (const guild_id of command.guildId!) { - guilds.add(guild_id); - } - } - - for (const guild of guilds) { - await this.proxy - .applications(applicationId) - .guilds(guild) - .commands.put({ - body: filter.never - .filter(cmd => cmd.guildId?.includes(guild) && (!('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash)) - .map(x => x.toJSON()), - }); - } - } - - async loadCommands(dir?: string) { - dir ??= await this.getRC().then(x => x.commands); - if (dir && this.commands) { - await this.commands.load(dir, this); - this.logger.info('CommandHandler loaded'); - } - } - - async loadComponents(dir?: string) { - dir ??= await this.getRC().then(x => x.components); - if (dir && this.components) { - await this.components.load(dir); - this.logger.info('ComponentHandler loaded'); - } - } - - async loadLangs(dir?: string) { - dir ??= await this.getRC().then(x => x.langs); - if (dir && this.langs) { - await this.langs.load(dir); - this.logger.info('LangsHandler loaded'); - } - } - - t(locale: string) { - return this.langs!.get(locale); - } - - async getRC< - T extends InternalRuntimeConfigHTTP | InternalRuntimeConfig = InternalRuntimeConfigHTTP | InternalRuntimeConfig, - >() { - const seyfertConfig = (BaseClient._seyfertConfig || - (await this.options.getRC?.()) || - (await magicImport(join(process.cwd(), 'seyfert.config.js')).then(x => x.default ?? x))) as T; - - const { locations, debug, ...env } = seyfertConfig; - - const obj = { - debug: !!debug, - ...env, - templates: locations.templates ? join(process.cwd(), locations.base, locations.templates) : undefined, - langs: locations.langs ? join(process.cwd(), locations.output, locations.langs) : undefined, - events: - 'events' in locations && locations.events ? join(process.cwd(), locations.output, locations.events) : undefined, - components: locations.components ? join(process.cwd(), locations.output, locations.components) : undefined, - commands: locations.commands ? join(process.cwd(), locations.output, locations.commands) : undefined, - base: join(process.cwd(), locations.base), - output: join(process.cwd(), locations.output), - }; - - BaseClient._seyfertConfig = seyfertConfig; - - return obj; - } -} - -export interface BaseClientOptions { - context?: ( - interaction: - | ChatInputCommandInteraction - | UserCommandInteraction - | MessageCommandInteraction - | ComponentInteraction - | ModalSubmitInteraction - | When, - ) => {}; - globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[]; - commands?: { - defaults?: { - onRunError?: Command['onRunError']; - onPermissionsFail?: Command['onPermissionsFail']; - onBotPermissionsFail?: Command['onBotPermissionsFail']; - onInternalError?: Command['onInternalError']; - onMiddlewaresError?: Command['onMiddlewaresError']; - onOptionsError?: Command['onOptionsError']; - onAfterRun?: Command['onAfterRun']; - }; - }; - components?: { - defaults?: { - onRunError?: ComponentCommand['onRunError']; - onInternalError?: ComponentCommand['onInternalError']; - onMiddlewaresError?: ComponentCommand['onMiddlewaresError']; - onAfterRun?: ComponentCommand['onAfterRun']; - }; - }; - modals?: { - defaults?: { - onRunError?: ModalCommand['onRunError']; - onInternalError?: ModalCommand['onInternalError']; - onMiddlewaresError?: ModalCommand['onMiddlewaresError']; - onAfterRun?: ModalCommand['onAfterRun']; - }; - }; - allowedMentions?: Omit, 'parse'> & { - parse?: ('everyone' | 'roles' | 'users')[]; //nice types, d-api - }; - getRC?(): Awaitable; -} - -export interface StartOptions { - eventsDir: string; - langsDir: string; - commandsDir: string; - componentsDir: string; - connection: { intents: number }; - httpConnection: { - publicKey: string; - port: number; - useUWS: boolean; - }; - token: string; -} - -interface RC extends Variables { - debug?: boolean; - locations: { - base: string; - output: string; - commands?: string; - langs?: string; - templates?: string; - events?: string; - components?: string; - }; -} - -export interface Variables { - token: string; - intents?: number; - applicationId?: string; - port?: number; - publicKey?: string; -} - -export type InternalRuntimeConfigHTTP = Omit< - MakeRequired, - 'intents' | 'locations' -> & { locations: Omit }; -export type RuntimeConfigHTTP = Omit, 'intents' | 'locations'> & { - locations: Omit; -}; - -export type InternalRuntimeConfig = Omit, 'publicKey' | 'port'>; -export type RuntimeConfig = OmitInsert< - InternalRuntimeConfig, - 'intents', - { intents?: IntentStrings | number[] | number } ->; - -export interface ServicesOptions { - rest?: ApiHandler; - cache?: { adapter?: Adapter; disabledCache?: Cache['disabledCache'] }; - langs?: { - default?: string; - aliases?: Record; - }; - middlewares?: Record; - handlers?: { - components?: ComponentHandler | ComponentHandler['callback']; - commands?: CommandHandler | Parameters[0]; - langs?: LangsHandler | LangsHandler['callback']; - }; -} +import { join } from 'node:path'; +import { ApiHandler, Router } from '../api'; +import type { Adapter } from '../cache'; +import { Cache, MemoryAdapter } from '../cache'; +import type { + Command, + CommandContext, + ExtraProps, + OnOptionsReturnObject, + RegisteredMiddlewares, + UsingClient, +} from '../commands'; +import { IgnoreCommand, type InferWithPrefix, type MiddlewareContext } from '../commands/applications/shared'; +import { CommandHandler } from '../commands/handler'; +import { + ChannelShorter, + EmojiShorter, + GuildShorter, + InteractionShorter, + LogLevels, + Logger, + MemberShorter, + MergeOptions, + MessageShorter, + ReactionShorter, + RoleShorter, + TemplateShorter, + ThreadShorter, + UsersShorter, + WebhookShorter, + filterSplit, + magicImport, + type MakeRequired, +} from '../common'; + +import type { LocaleString, RESTPostAPIChannelMessageJSONBody } from 'discord-api-types/rest/v10'; +import type { Awaitable, DeepPartial, IntentStrings, OmitInsert, PermissionStrings, When } from '../common/types/util'; +import { ComponentHandler } from '../components/handler'; +import { LangsHandler } from '../langs/handler'; +import type { + ChatInputCommandInteraction, + ComponentInteraction, + Message, + MessageCommandInteraction, + ModalSubmitInteraction, + UserCommandInteraction, +} from '../structures'; +import type { ComponentCommand, ComponentContext, ModalCommand, ModalContext } from '../components'; +import { promises } from 'node:fs'; + +export class BaseClient { + rest!: ApiHandler; + cache!: Cache; + + users = new UsersShorter(this); + channels = new ChannelShorter(this); + guilds = new GuildShorter(this); + messages = new MessageShorter(this); + members = new MemberShorter(this); + webhooks = new WebhookShorter(this); + templates = new TemplateShorter(this); + roles = new RoleShorter(this); + reactions = new ReactionShorter(this); + emojis = new EmojiShorter(this); + threads = new ThreadShorter(this); + interactions = new InteractionShorter(this); + + debugger?: Logger; + + logger = new Logger({ + name: '[Seyfert]', + }); + + langs? = new LangsHandler(this.logger); + commands? = new CommandHandler(this.logger, this); + components? = new ComponentHandler(this.logger, this); + + private _applicationId?: string; + private _botId?: string; + middlewares?: Record; + + protected static assertString(value: unknown, message?: string): asserts value is string { + if (!(typeof value === 'string' && value !== '')) { + throw new Error(message ?? 'Value is not a string'); + } + } + + protected static getBotIdFromToken(token: string): string { + return Buffer.from(token.split('.')[0], 'base64').toString('ascii'); + } + + options: BaseClientOptions; + + /**@internal */ + static _seyfertConfig?: InternalRuntimeConfigHTTP | InternalRuntimeConfig; + + constructor(options?: BaseClientOptions) { + this.options = MergeOptions( + { + commands: { + defaults: { + onRunError(context: CommandContext, error: unknown): any { + context.client.logger.fatal(`${context.command.name}.`, context.author.id, error); + }, + onOptionsError(context: CommandContext<{}, never>, metadata: OnOptionsReturnObject): any { + context.client.logger.fatal(`${context.command.name}.`, context.author.id, metadata); + }, + onMiddlewaresError(context: CommandContext<{}, never>, error: string): any { + context.client.logger.fatal(`${context.command.name}.`, context.author.id, error); + }, + onBotPermissionsFail(context: CommandContext<{}, never>, permissions: PermissionStrings): any { + context.client.logger.fatal( + `${context.command.name}.`, + context.author.id, + permissions, + ); + }, + onPermissionsFail(context: CommandContext<{}, never>, permissions: PermissionStrings): any { + context.client.logger.fatal( + `${context.command.name}.`, + context.author.id, + permissions, + ); + }, + onInternalError(client: UsingClient, command: Command, error?: unknown): any { + client.logger.fatal(`${command.name}.`, error); + }, + }, + }, + components: { + defaults: { + onRunError(context: ComponentContext, error: unknown): any { + context.client.logger.fatal('ComponentCommand.', context.author.id, error); + }, + onMiddlewaresError(context: ComponentContext, error: string): any { + context.client.logger.fatal('ComponentCommand.', context.author.id, error); + }, + onInternalError(client: UsingClient, error?: unknown): any { + client.logger.fatal(error); + }, + }, + }, + modals: { + defaults: { + onRunError(context: ModalContext, error: unknown): any { + context.client.logger.fatal('ComponentCommand.', context.author.id, error); + }, + onMiddlewaresError(context: ModalContext, error: string): any { + context.client.logger.fatal('ComponentCommand.', context.author.id, error); + }, + onInternalError(client: UsingClient, error?: unknown): any { + client.logger.fatal(error); + }, + }, + }, + } satisfies BaseClientOptions, + options, + ); + } + + set botId(id: string) { + this._botId = id; + } + + get botId() { + return this._botId ?? BaseClient.getBotIdFromToken(this.rest.options.token); + } + + set applicationId(id: string) { + this._applicationId = id; + } + + get applicationId() { + return this._applicationId ?? this.botId; + } + + get proxy() { + return new Router(this.rest).createProxy(); + } + + setServices({ rest, cache, langs, middlewares, handlers }: ServicesOptions) { + if (rest) { + this.rest = rest; + } + if (cache) { + this.cache = new Cache( + this.cache?.intents ?? 0, + cache?.adapter ?? this.cache?.adapter ?? new MemoryAdapter(), + cache.disabledCache ?? this.cache?.disabledCache ?? [], + this, + ); + } + if (middlewares) { + this.middlewares = middlewares; + } + if (handlers) { + if ('components' in handlers) { + if (!handlers.components) { + this.components = undefined; + } else if (typeof handlers.components === 'function') { + this.components ??= new ComponentHandler(this.logger, this); + this.components.setHandlers({ callback: handlers.components }); + } else { + this.components = handlers.components; + } + } + if ('commands' in handlers) { + if (!handlers.commands) { + this.commands = undefined; + } else if (typeof handlers.commands === 'object') { + this.commands ??= new CommandHandler(this.logger, this); + this.commands.setHandlers(handlers.commands); + } else { + this.commands = handlers.commands; + } + } + if ('langs' in handlers) { + if (!handlers.langs) { + this.langs = undefined; + } else if (typeof handlers.langs === 'function') { + this.langs ??= new LangsHandler(this.logger); + this.langs.setHandlers({ callback: handlers.langs }); + } else { + this.langs = handlers.langs; + } + } + } + if (langs) { + if (langs.default) this.langs!.defaultLang = langs.default; + if (langs.aliases) this.langs!.aliases = Object.entries(langs.aliases); + } + } + + protected async execute(..._options: unknown[]) { + if ((await this.getRC()).debug) { + this.debugger = new Logger({ + name: '[Debug]', + logLevel: LogLevels.Debug, + }); + } + } + + async start( + options: Pick, 'langsDir' | 'commandsDir' | 'connection' | 'token' | 'componentsDir'> = { + token: undefined, + langsDir: undefined, + commandsDir: undefined, + connection: undefined, + componentsDir: undefined, + }, + ) { + await this.loadLangs(options.langsDir); + await this.loadCommands(options.commandsDir); + await this.loadComponents(options.componentsDir); + + const { token: tokenRC } = await this.getRC(); + const token = options?.token ?? tokenRC; + + if (!this.rest) { + BaseClient.assertString(token, 'token is not a string'); + this.rest = new ApiHandler({ + token, + baseUrl: 'api/v10', + domain: 'https://discord.com', + debug: (await this.getRC()).debug, + }); + } + + if (this.cache) { + this.cache.__setClient(this); + } else { + this.cache = new Cache(0, new MemoryAdapter(), [], this); + } + } + + protected async onPacket(..._packet: unknown[]) { + throw new Error('Function not implemented'); + } + + shouldUploadCommands(cachePath: string) { + return this.commands!.shouldUpload(cachePath).then(async should => { + if (should) await promises.writeFile(cachePath, JSON.stringify(this.commands!.values.map(x => x.toJSON()))); + return should; + }); + } + + async uploadCommands(applicationId?: string) { + applicationId ??= await this.getRC().then(x => x.applicationId ?? this.applicationId); + BaseClient.assertString(applicationId, 'applicationId is not a string'); + + const commands = this.commands!.values; + const filter = filterSplit(commands, command => !command.guildId); + + await this.proxy.applications(applicationId).commands.put({ + body: filter.expect.filter(cmd => !('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash).map(x => x.toJSON()), + }); + + const guilds = new Set(); + + for (const command of filter.never) { + for (const guild_id of command.guildId!) { + guilds.add(guild_id); + } + } + + for (const guild of guilds) { + await this.proxy + .applications(applicationId) + .guilds(guild) + .commands.put({ + body: filter.never + .filter(cmd => cmd.guildId?.includes(guild) && (!('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash)) + .map(x => x.toJSON()), + }); + } + } + + async loadCommands(dir?: string) { + dir ??= await this.getRC().then(x => x.commands); + if (dir && this.commands) { + await this.commands.load(dir, this); + this.logger.info('CommandHandler loaded'); + } + } + + async loadComponents(dir?: string) { + dir ??= await this.getRC().then(x => x.components); + if (dir && this.components) { + await this.components.load(dir); + this.logger.info('ComponentHandler loaded'); + } + } + + async loadLangs(dir?: string) { + dir ??= await this.getRC().then(x => x.langs); + if (dir && this.langs) { + await this.langs.load(dir); + this.logger.info('LangsHandler loaded'); + } + } + + t(locale: string) { + return this.langs!.get(locale); + } + + async getRC< + T extends InternalRuntimeConfigHTTP | InternalRuntimeConfig = InternalRuntimeConfigHTTP | InternalRuntimeConfig, + >() { + const seyfertConfig = (BaseClient._seyfertConfig || + (await this.options.getRC?.()) || + (await magicImport(join(process.cwd(), 'seyfert.config.js')).then(x => x.default ?? x))) as T; + + const { locations, debug, ...env } = seyfertConfig; + + const obj = { + debug: !!debug, + ...env, + templates: locations.templates ? join(process.cwd(), locations.base, locations.templates) : undefined, + langs: locations.langs ? join(process.cwd(), locations.output, locations.langs) : undefined, + events: + 'events' in locations && locations.events ? join(process.cwd(), locations.output, locations.events) : undefined, + components: locations.components ? join(process.cwd(), locations.output, locations.components) : undefined, + commands: locations.commands ? join(process.cwd(), locations.output, locations.commands) : undefined, + base: join(process.cwd(), locations.base), + output: join(process.cwd(), locations.output), + }; + + BaseClient._seyfertConfig = seyfertConfig; + + return obj; + } +} + +export interface BaseClientOptions { + context?: ( + interaction: + | ChatInputCommandInteraction + | UserCommandInteraction + | MessageCommandInteraction + | ComponentInteraction + | ModalSubmitInteraction + | When, + ) => {}; + globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[]; + commands?: { + defaults?: { + onRunError?: Command['onRunError']; + onPermissionsFail?: Command['onPermissionsFail']; + onBotPermissionsFail?: Command['onBotPermissionsFail']; + onInternalError?: Command['onInternalError']; + onMiddlewaresError?: Command['onMiddlewaresError']; + onOptionsError?: Command['onOptionsError']; + onAfterRun?: Command['onAfterRun']; + props?: ExtraProps; + }; + }; + components?: { + defaults?: { + onRunError?: ComponentCommand['onRunError']; + onInternalError?: ComponentCommand['onInternalError']; + onMiddlewaresError?: ComponentCommand['onMiddlewaresError']; + onAfterRun?: ComponentCommand['onAfterRun']; + }; + }; + modals?: { + defaults?: { + onRunError?: ModalCommand['onRunError']; + onInternalError?: ModalCommand['onInternalError']; + onMiddlewaresError?: ModalCommand['onMiddlewaresError']; + onAfterRun?: ModalCommand['onAfterRun']; + }; + }; + allowedMentions?: Omit, 'parse'> & { + parse?: ('everyone' | 'roles' | 'users')[]; //nice types, d-api + }; + getRC?(): Awaitable; +} + +export interface StartOptions { + eventsDir: string; + langsDir: string; + commandsDir: string; + componentsDir: string; + connection: { intents: number }; + httpConnection: { + publicKey: string; + port: number; + useUWS: boolean; + }; + token: string; +} + +interface RC extends Variables { + debug?: boolean; + locations: { + base: string; + output: string; + commands?: string; + langs?: string; + templates?: string; + events?: string; + components?: string; + }; +} + +export interface Variables { + token: string; + intents?: number; + applicationId?: string; + port?: number; + publicKey?: string; +} + +export type InternalRuntimeConfigHTTP = Omit< + MakeRequired, + 'intents' | 'locations' +> & { locations: Omit }; +export type RuntimeConfigHTTP = Omit, 'intents' | 'locations'> & { + locations: Omit; +}; + +export type InternalRuntimeConfig = Omit, 'publicKey' | 'port'>; +export type RuntimeConfig = OmitInsert< + InternalRuntimeConfig, + 'intents', + { intents?: IntentStrings | number[] | number } +>; + +export interface ServicesOptions { + rest?: ApiHandler; + cache?: { adapter?: Adapter; disabledCache?: Cache['disabledCache'] }; + langs?: { + default?: string; + aliases?: Record; + }; + middlewares?: Record; + handlers?: { + components?: ComponentHandler | ComponentHandler['callback']; + commands?: CommandHandler | Parameters[0]; + langs?: LangsHandler | LangsHandler['callback']; + }; +} diff --git a/src/commands/applications/chat.ts b/src/commands/applications/chat.ts index 54260c6..81c73d1 100644 --- a/src/commands/applications/chat.ts +++ b/src/commands/applications/chat.ts @@ -24,6 +24,7 @@ import type { OptionResolver } from '../optionresolver'; import type { CommandContext } from './chatcontext'; import type { DefaultLocale, + ExtraProps, IgnoreCommand, OKFunction, OnOptionsReturnObject, @@ -138,6 +139,8 @@ export class BaseCommand { aliases?: string[]; + props: ExtraProps = {}; + /** @internal */ async __runOptions( ctx: CommandContext<{}, never>, diff --git a/src/commands/applications/menu.ts b/src/commands/applications/menu.ts index 4b42d71..2589cf7 100644 --- a/src/commands/applications/menu.ts +++ b/src/commands/applications/menu.ts @@ -7,7 +7,7 @@ import type { import { magicImport, type PermissionStrings } from '../../common'; import type { RegisteredMiddlewares } from '../decorators'; import type { MenuCommandContext } from './menucontext'; -import type { UsingClient } from './shared'; +import type { ExtraProps, UsingClient } from './shared'; export abstract class ContextMenuCommand { middlewares: (keyof RegisteredMiddlewares)[] = []; @@ -28,6 +28,8 @@ export abstract class ContextMenuCommand { name_localizations?: Partial>; description_localizations?: Partial>; + props: ExtraProps = {}; + toJSON() { return { name: this.name, diff --git a/src/commands/applications/shared.ts b/src/commands/applications/shared.ts index 56d00c2..96207d5 100644 --- a/src/commands/applications/shared.ts +++ b/src/commands/applications/shared.ts @@ -13,6 +13,7 @@ export type InferWithPrefix = InternalOptions extends { withPrefix: infer P } ? export interface GlobalMetadata {} export interface DefaultLocale {} export interface ExtendContext {} +export interface ExtraProps {} export interface UsingClient extends BaseClient {} export type ParseClient = T; export interface InternalOptions {} diff --git a/src/commands/decorators.ts b/src/commands/decorators.ts index c4c72e8..a0540dc 100644 --- a/src/commands/decorators.ts +++ b/src/commands/decorators.ts @@ -7,7 +7,7 @@ import { } from 'discord-api-types/v10'; import type { FlatObjectKeys, PermissionStrings } from '../common'; import type { CommandOption, OptionsRecord, SubCommand } from './applications/chat'; -import type { DefaultLocale, IgnoreCommand, MiddlewareContext } from './applications/shared'; +import type { DefaultLocale, ExtraProps, IgnoreCommand, MiddlewareContext } from './applications/shared'; export interface RegisteredMiddlewares {} @@ -23,6 +23,7 @@ type DeclareOptions = contexts?: (keyof typeof InteractionContextType)[]; ignore?: IgnoreCommand; aliases?: string[]; + props?: ExtraProps; } | (Omit< { @@ -34,6 +35,7 @@ type DeclareOptions = nsfw?: boolean; integrationTypes?: (keyof typeof ApplicationIntegrationType)[]; contexts?: (keyof typeof InteractionContextType)[]; + props?: ExtraProps; }, 'type' | 'description' > & { @@ -157,6 +159,7 @@ export function Declare(declare: DeclareOptions) { class extends target { name = declare.name; nsfw = declare.nsfw; + props = declare.props; contexts = declare.contexts?.map(i => InteractionContextType[i]) ?? Object.values(InteractionContextType).filter(x => typeof x === 'number'); diff --git a/src/commands/handler.ts b/src/commands/handler.ts index 5da093d..46fe422 100644 --- a/src/commands/handler.ts +++ b/src/commands/handler.ts @@ -214,6 +214,8 @@ export class CommandHandler extends BaseHandler { commandInstance.onRunError ??= client.options?.commands?.defaults?.onRunError; commandInstance.__filePath = command.path; commandInstance.options ??= [] as NonNullable; + console.log(commandInstance, commandInstance.props); + commandInstance.props ??= client.options.commands?.defaults?.props ?? {}; if (commandInstance.__autoload) { //@AutoLoad const options = await this.getFiles(dirname(command.path)); @@ -239,35 +241,36 @@ export class CommandHandler extends BaseHandler { option.onMiddlewaresError = option.onMiddlewaresError?.bind(option) ?? commandInstance.onMiddlewaresError?.bind(commandInstance) ?? - this.client.options?.commands?.defaults?.onMiddlewaresError; + this.client.options.commands?.defaults?.onMiddlewaresError; option.onRunError = option.onRunError?.bind(option) ?? commandInstance.onRunError?.bind(commandInstance) ?? - this.client.options?.commands?.defaults?.onRunError; + this.client.options.commands?.defaults?.onRunError; option.onOptionsError = option.onOptionsError?.bind(option) ?? commandInstance.onOptionsError?.bind(commandInstance) ?? - this.client.options?.commands?.defaults?.onOptionsError; + this.client.options.commands?.defaults?.onOptionsError; option.onInternalError = option.onInternalError?.bind(option) ?? commandInstance.onInternalError?.bind(commandInstance) ?? - this.client.options?.commands?.defaults?.onInternalError; + this.client.options.commands?.defaults?.onInternalError; option.onAfterRun = option.onAfterRun?.bind(option) ?? commandInstance.onAfterRun?.bind(commandInstance) ?? - this.client.options?.commands?.defaults?.onAfterRun; + this.client.options.commands?.defaults?.onAfterRun; option.onBotPermissionsFail = option.onBotPermissionsFail?.bind(option) ?? commandInstance.onBotPermissionsFail?.bind(commandInstance) ?? - this.client.options?.commands?.defaults?.onBotPermissionsFail; + this.client.options.commands?.defaults?.onBotPermissionsFail; option.onPermissionsFail = option.onPermissionsFail?.bind(option) ?? commandInstance.onPermissionsFail?.bind(commandInstance) ?? - this.client.options?.commands?.defaults?.onPermissionsFail; + this.client.options.commands?.defaults?.onPermissionsFail; option.botPermissions ??= commandInstance.botPermissions; option.defaultMemberPermissions ??= commandInstance.defaultMemberPermissions; option.contexts ??= commandInstance.contexts; option.integrationTypes ??= commandInstance.integrationTypes; + option.props ??= commandInstance.props; } } diff --git a/src/components/componentcommand.ts b/src/components/componentcommand.ts index e74df0b..ab9a1c7 100644 --- a/src/components/componentcommand.ts +++ b/src/components/componentcommand.ts @@ -1,30 +1,32 @@ -import { ComponentType } from 'discord-api-types/v10'; -import type { ContextComponentCommandInteractionMap, ComponentContext } from './componentcontext'; -import type { RegisteredMiddlewares, UsingClient } from '../commands'; - -export const InteractionCommandType = { - COMPONENT: 0, - MODAL: 1, -} as const; - -export interface ComponentCommand { - __filePath?: string; -} - -export abstract class ComponentCommand { - type = InteractionCommandType.COMPONENT; - abstract componentType: keyof ContextComponentCommandInteractionMap; - abstract filter(context: ComponentContext): Promise | boolean; - abstract run(context: ComponentContext): any; - - get cType(): number { - return ComponentType[this.componentType]; - } - - onAfterRun?(context: ComponentContext, error: unknown | undefined): any; - onRunError?(context: ComponentContext, error: unknown): any; - onMiddlewaresError?(context: ComponentContext, error: string): any; - onInternalError?(client: UsingClient, error?: unknown): any; - - middlewares: (keyof RegisteredMiddlewares)[] = []; -} +import { ComponentType } from 'discord-api-types/v10'; +import type { ContextComponentCommandInteractionMap, ComponentContext } from './componentcontext'; +import type { ExtraProps, RegisteredMiddlewares, UsingClient } from '../commands'; + +export const InteractionCommandType = { + COMPONENT: 0, + MODAL: 1, +} as const; + +export interface ComponentCommand { + __filePath?: string; +} + +export abstract class ComponentCommand { + type = InteractionCommandType.COMPONENT; + abstract componentType: keyof ContextComponentCommandInteractionMap; + abstract filter(context: ComponentContext): Promise | boolean; + abstract run(context: ComponentContext): any; + + middlewares: (keyof RegisteredMiddlewares)[] = []; + + props: ExtraProps = {}; + + get cType(): number { + return ComponentType[this.componentType]; + } + + onAfterRun?(context: ComponentContext, error: unknown | undefined): any; + onRunError?(context: ComponentContext, error: unknown): any; + onMiddlewaresError?(context: ComponentContext, error: string): any; + onInternalError?(client: UsingClient, error?: unknown): any; +} diff --git a/src/components/componentcontext.ts b/src/components/componentcontext.ts index c7bf26f..fba0f83 100644 --- a/src/components/componentcontext.ts +++ b/src/components/componentcontext.ts @@ -1,243 +1,243 @@ -import { ComponentType, MessageFlags } from 'discord-api-types/v10'; -import type { - AllChannels, - ButtonInteraction, - ChannelSelectMenuInteraction, - ComponentCommand, - Guild, - GuildMember, - MentionableSelectMenuInteraction, - Message, - ReturnCache, - RoleSelectMenuInteraction, - StringSelectMenuInteraction, - UserSelectMenuInteraction, - WebhookMessage, -} from '..'; -import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands'; -import { BaseContext } from '../commands/basecontext'; -import type { - ComponentInteractionMessageUpdate, - InteractionCreateBodyRequest, - InteractionMessageUpdateBodyRequest, - ModalCreateBodyRequest, - UnionToTuple, - When, -} from '../common'; - -export interface ComponentContext< - Type extends keyof ContextComponentCommandInteractionMap = keyof ContextComponentCommandInteractionMap, -> extends BaseContext, - ExtendContext {} - -/** - * Represents a context for interacting with components in a Discord bot. - * @template Type - The type of component interaction. - */ -export class ComponentContext< - Type extends keyof ContextComponentCommandInteractionMap, - M extends keyof RegisteredMiddlewares = never, -> extends BaseContext { - /** - * Creates a new instance of the ComponentContext class. - * @param client - The UsingClient instance. - * @param interaction - The component interaction object. - */ - constructor( - readonly client: UsingClient, - public interaction: ContextComponentCommandInteractionMap[Type], - ) { - super(client); - } - - command?: ComponentCommand; - metadata: CommandMetadata> = {} as never; - globalMetadata: GlobalMetadata = {}; - - /** - * Gets the language object for the interaction's locale. - */ - get t() { - return this.client.t(this.interaction?.locale ?? this.client.langs?.defaultLang ?? 'en-US'); - } - - /** - * Gets the custom ID of the interaction. - */ - get customId() { - return this.interaction.customId; - } - - /** - * Writes a response to the interaction. - * @param body - The body of the response. - * @param fetchReply - Whether to fetch the reply or not. - */ - write(body: InteractionCreateBodyRequest, fetchReply?: FR) { - return this.interaction.write(body, fetchReply); - } - - /** - * Defers the reply to the interaction. - * @param ephemeral - Whether the reply should be ephemeral or not. - */ - deferReply(ephemeral = false) { - return this.interaction.deferReply(ephemeral ? MessageFlags.Ephemeral : undefined); - } - - /** - * Edits the response of the interaction. - * @param body - The updated body of the response. - */ - editResponse(body: InteractionMessageUpdateBodyRequest) { - return this.interaction.editResponse(body); - } - - /** - * Updates the interaction with new data. - * @param body - The updated body of the interaction. - */ - update(body: ComponentInteractionMessageUpdate) { - return this.interaction.update(body); - } - - /** - * Edits the response or replies to the interaction. - * @param body - The body of the response or updated body of the interaction. - * @param fetchReply - Whether to fetch the reply or not. - */ - editOrReply( - body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest, - fetchReply?: FR, - ): Promise> { - return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply); - } - - /** - * Deletes the response of the interaction. - * @returns A promise that resolves when the response is deleted. - */ - deleteResponse() { - return this.interaction.deleteResponse(); - } - - modal(body: ModalCreateBodyRequest) { - return this.interaction.modal(body); - } - - /** - * Gets the channel of the interaction. - * @param mode - The mode to fetch the channel. - * @returns A promise that resolves to the channel. - */ - 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'); - } - - /** - * Gets the bot member in the guild of the interaction. - * @param mode - The mode to fetch the member. - * @returns A promise that resolves to the bot member. - */ - 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'); - } - } - - /** - * Gets the guild of the interaction. - * @param mode - The mode to fetch the guild. - * @returns A promise that resolves to the guild. - */ - 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'); - } - } - - /** - * Gets the ID of the guild of the interaction. - */ - get guildId() { - return this.interaction.guildId; - } - - /** - * Gets the ID of the channel of the interaction. - */ - get channelId() { - return this.interaction.channelId!; - } - - /** - * Gets the author of the interaction. - */ - get author() { - return this.interaction.user; - } - - /** - * Gets the member of the interaction. - */ - get member() { - return this.interaction.member; - } - - isComponent(): this is ComponentContext { - return true; - } - - isButton(): this is ComponentContext<'Button'> { - return this.interaction.data.componentType === ComponentType.Button; - } - - isChannelSelectMenu(): this is ComponentContext<'ChannelSelect'> { - return this.interaction.componentType === ComponentType.ChannelSelect; - } - - isRoleSelectMenu(): this is ComponentContext<'RoleSelect'> { - return this.interaction.componentType === ComponentType.RoleSelect; - } - - isMentionableSelectMenu(): this is ComponentContext<'MentionableSelect'> { - return this.interaction.componentType === ComponentType.MentionableSelect; - } - - isUserSelectMenu(): this is ComponentContext<'UserSelect'> { - return this.interaction.componentType === ComponentType.UserSelect; - } - - isStringSelectMenu(): this is ComponentContext<'StringSelect'> { - return this.interaction.componentType === ComponentType.StringSelect; - } -} - -export interface ContextComponentCommandInteractionMap { - Button: ButtonInteraction; - StringSelect: StringSelectMenuInteraction; - UserSelect: UserSelectMenuInteraction; - RoleSelect: RoleSelectMenuInteraction; - MentionableSelect: MentionableSelectMenuInteraction; - ChannelSelect: ChannelSelectMenuInteraction; -} +import { ComponentType, MessageFlags } from 'discord-api-types/v10'; +import type { + AllChannels, + ButtonInteraction, + ChannelSelectMenuInteraction, + ComponentCommand, + Guild, + GuildMember, + MentionableSelectMenuInteraction, + Message, + ReturnCache, + RoleSelectMenuInteraction, + StringSelectMenuInteraction, + UserSelectMenuInteraction, + WebhookMessage, +} from '..'; +import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands'; +import { BaseContext } from '../commands/basecontext'; +import type { + ComponentInteractionMessageUpdate, + InteractionCreateBodyRequest, + InteractionMessageUpdateBodyRequest, + ModalCreateBodyRequest, + UnionToTuple, + When, +} from '../common'; + +export interface ComponentContext< + Type extends keyof ContextComponentCommandInteractionMap = keyof ContextComponentCommandInteractionMap, +> extends BaseContext, + ExtendContext {} + +/** + * Represents a context for interacting with components in a Discord bot. + * @template Type - The type of component interaction. + */ +export class ComponentContext< + Type extends keyof ContextComponentCommandInteractionMap, + M extends keyof RegisteredMiddlewares = never, +> extends BaseContext { + /** + * Creates a new instance of the ComponentContext class. + * @param client - The UsingClient instance. + * @param interaction - The component interaction object. + */ + constructor( + readonly client: UsingClient, + public interaction: ContextComponentCommandInteractionMap[Type], + ) { + super(client); + } + + command!: ComponentCommand; + metadata: CommandMetadata> = {} as never; + globalMetadata: GlobalMetadata = {}; + + /** + * Gets the language object for the interaction's locale. + */ + get t() { + return this.client.t(this.interaction?.locale ?? this.client.langs?.defaultLang ?? 'en-US'); + } + + /** + * Gets the custom ID of the interaction. + */ + get customId() { + return this.interaction.customId; + } + + /** + * Writes a response to the interaction. + * @param body - The body of the response. + * @param fetchReply - Whether to fetch the reply or not. + */ + write(body: InteractionCreateBodyRequest, fetchReply?: FR) { + return this.interaction.write(body, fetchReply); + } + + /** + * Defers the reply to the interaction. + * @param ephemeral - Whether the reply should be ephemeral or not. + */ + deferReply(ephemeral = false) { + return this.interaction.deferReply(ephemeral ? MessageFlags.Ephemeral : undefined); + } + + /** + * Edits the response of the interaction. + * @param body - The updated body of the response. + */ + editResponse(body: InteractionMessageUpdateBodyRequest) { + return this.interaction.editResponse(body); + } + + /** + * Updates the interaction with new data. + * @param body - The updated body of the interaction. + */ + update(body: ComponentInteractionMessageUpdate) { + return this.interaction.update(body); + } + + /** + * Edits the response or replies to the interaction. + * @param body - The body of the response or updated body of the interaction. + * @param fetchReply - Whether to fetch the reply or not. + */ + editOrReply( + body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest, + fetchReply?: FR, + ): Promise> { + return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply); + } + + /** + * Deletes the response of the interaction. + * @returns A promise that resolves when the response is deleted. + */ + deleteResponse() { + return this.interaction.deleteResponse(); + } + + modal(body: ModalCreateBodyRequest) { + return this.interaction.modal(body); + } + + /** + * Gets the channel of the interaction. + * @param mode - The mode to fetch the channel. + * @returns A promise that resolves to the channel. + */ + 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'); + } + + /** + * Gets the bot member in the guild of the interaction. + * @param mode - The mode to fetch the member. + * @returns A promise that resolves to the bot member. + */ + 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'); + } + } + + /** + * Gets the guild of the interaction. + * @param mode - The mode to fetch the guild. + * @returns A promise that resolves to the guild. + */ + 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'); + } + } + + /** + * Gets the ID of the guild of the interaction. + */ + get guildId() { + return this.interaction.guildId; + } + + /** + * Gets the ID of the channel of the interaction. + */ + get channelId() { + return this.interaction.channelId!; + } + + /** + * Gets the author of the interaction. + */ + get author() { + return this.interaction.user; + } + + /** + * Gets the member of the interaction. + */ + get member() { + return this.interaction.member; + } + + isComponent(): this is ComponentContext { + return true; + } + + isButton(): this is ComponentContext<'Button'> { + return this.interaction.data.componentType === ComponentType.Button; + } + + isChannelSelectMenu(): this is ComponentContext<'ChannelSelect'> { + return this.interaction.componentType === ComponentType.ChannelSelect; + } + + isRoleSelectMenu(): this is ComponentContext<'RoleSelect'> { + return this.interaction.componentType === ComponentType.RoleSelect; + } + + isMentionableSelectMenu(): this is ComponentContext<'MentionableSelect'> { + return this.interaction.componentType === ComponentType.MentionableSelect; + } + + isUserSelectMenu(): this is ComponentContext<'UserSelect'> { + return this.interaction.componentType === ComponentType.UserSelect; + } + + isStringSelectMenu(): this is ComponentContext<'StringSelect'> { + return this.interaction.componentType === ComponentType.StringSelect; + } +} + +export interface ContextComponentCommandInteractionMap { + Button: ButtonInteraction; + StringSelect: StringSelectMenuInteraction; + UserSelect: UserSelectMenuInteraction; + RoleSelect: RoleSelectMenuInteraction; + MentionableSelect: MentionableSelectMenuInteraction; + ChannelSelect: ChannelSelectMenuInteraction; +} diff --git a/src/components/modalcommand.ts b/src/components/modalcommand.ts index d1accb2..781c529 100644 --- a/src/components/modalcommand.ts +++ b/src/components/modalcommand.ts @@ -1,20 +1,22 @@ -import type { RegisteredMiddlewares, UsingClient } from '../commands'; -import { InteractionCommandType } from './componentcommand'; -import type { ModalContext } from './modalcontext'; - -export interface ModalCommand { - __filePath?: string; -} - -export abstract class ModalCommand { - type = InteractionCommandType.MODAL; - abstract filter(context: ModalContext): Promise | boolean; - abstract run(context: ModalContext): any; - - middlewares: (keyof RegisteredMiddlewares)[] = []; - - onAfterRun?(context: ModalContext, error: unknown | undefined): any; - onRunError?(context: ModalContext, error: unknown): any; - onMiddlewaresError?(context: ModalContext, error: string): any; - onInternalError?(client: UsingClient, error?: unknown): any; -} +import type { ExtraProps, RegisteredMiddlewares, UsingClient } from '../commands'; +import { InteractionCommandType } from './componentcommand'; +import type { ModalContext } from './modalcontext'; + +export interface ModalCommand { + __filePath?: string; +} + +export abstract class ModalCommand { + type = InteractionCommandType.MODAL; + abstract filter(context: ModalContext): Promise | boolean; + abstract run(context: ModalContext): any; + + middlewares: (keyof RegisteredMiddlewares)[] = []; + + props: ExtraProps = {}; + + onAfterRun?(context: ModalContext, error: unknown | undefined): any; + onRunError?(context: ModalContext, error: unknown): any; + onMiddlewaresError?(context: ModalContext, error: string): any; + onInternalError?(client: UsingClient, error?: unknown): any; +} diff --git a/src/components/modalcontext.ts b/src/components/modalcontext.ts index af3f1a3..e72498e 100644 --- a/src/components/modalcontext.ts +++ b/src/components/modalcontext.ts @@ -1,192 +1,192 @@ -import { MessageFlags } from 'discord-api-types/v10'; -import type { - AllChannels, - Guild, - GuildMember, - Message, - ModalCommand, - ModalSubmitInteraction, - ReturnCache, - WebhookMessage, -} from '..'; -import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands'; -import { BaseContext } from '../commands/basecontext'; -import type { - InteractionCreateBodyRequest, - InteractionMessageUpdateBodyRequest, - ModalCreateBodyRequest, - UnionToTuple, - When, -} from '../common'; - -export interface ModalContext extends BaseContext, ExtendContext {} - -/** - * Represents a context for interacting with components in a Discord bot. - * @template Type - The type of component interaction. - */ -export class ModalContext extends BaseContext { - /** - * Creates a new instance of the ComponentContext class. - * @param client - The UsingClient instance. - * @param interaction - The component interaction object. - */ - constructor( - readonly client: UsingClient, - public interaction: ModalSubmitInteraction, - ) { - super(client); - } - - command?: ModalCommand; - metadata: CommandMetadata> = {} as never; - globalMetadata: GlobalMetadata = {}; - - get customId() { - return this.interaction.customId; - } - - get components() { - return this.interaction.components; - } - - /** - * Gets the language object for the interaction's locale. - */ - get t() { - return this.client.t(this.interaction?.locale ?? this.client.langs?.defaultLang ?? 'en-US'); - } - - /** - * Writes a response to the interaction. - * @param body - The body of the response. - * @param fetchReply - Whether to fetch the reply or not. - */ - write(body: InteractionCreateBodyRequest, fetchReply?: FR) { - return this.interaction.write(body, fetchReply); - } - - /** - * Defers the reply to the interaction. - * @param ephemeral - Whether the reply should be ephemeral or not. - */ - deferReply(ephemeral = false) { - return this.interaction.deferReply(ephemeral ? MessageFlags.Ephemeral : undefined); - } - - /** - * Edits the response of the interaction. - * @param body - The updated body of the response. - */ - editResponse(body: InteractionMessageUpdateBodyRequest) { - return this.interaction.editResponse(body); - } - - /** - * Edits the response or replies to the interaction. - * @param body - The body of the response or updated body of the interaction. - * @param fetchReply - Whether to fetch the reply or not. - */ - editOrReply( - body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest, - fetchReply?: FR, - ): Promise> { - return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply); - } - - /** - * Deletes the response of the interaction. - * @returns A promise that resolves when the response is deleted. - */ - deleteResponse() { - return this.interaction.deleteResponse(); - } - - modal(body: ModalCreateBodyRequest) { - //@ts-expect-error - return this.interaction.modal(body); - } - - /** - * Gets the channel of the interaction. - * @param mode - The mode to fetch the channel. - * @returns A promise that resolves to the channel. - */ - 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'); - } - - /** - * Gets the bot member in the guild of the interaction. - * @param mode - The mode to fetch the member. - * @returns A promise that resolves to the bot member. - */ - 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'); - } - } - - /** - * Gets the guild of the interaction. - * @param mode - The mode to fetch the guild. - * @returns A promise that resolves to the guild. - */ - 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'); - } - } - - /** - * Gets the ID of the guild of the interaction. - */ - get guildId() { - return this.interaction.guildId; - } - - /** - * Gets the ID of the channel of the interaction. - */ - get channelId() { - return this.interaction.channelId!; - } - - /** - * Gets the author of the interaction. - */ - get author() { - return this.interaction.user; - } - - /** - * Gets the member of the interaction. - */ - get member() { - return this.interaction.member; - } - - isModal(): this is ModalContext { - return true; - } -} +import { MessageFlags } from 'discord-api-types/v10'; +import type { + AllChannels, + Guild, + GuildMember, + Message, + ModalCommand, + ModalSubmitInteraction, + ReturnCache, + WebhookMessage, +} from '..'; +import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands'; +import { BaseContext } from '../commands/basecontext'; +import type { + InteractionCreateBodyRequest, + InteractionMessageUpdateBodyRequest, + ModalCreateBodyRequest, + UnionToTuple, + When, +} from '../common'; + +export interface ModalContext extends BaseContext, ExtendContext {} + +/** + * Represents a context for interacting with components in a Discord bot. + * @template Type - The type of component interaction. + */ +export class ModalContext extends BaseContext { + /** + * Creates a new instance of the ComponentContext class. + * @param client - The UsingClient instance. + * @param interaction - The component interaction object. + */ + constructor( + readonly client: UsingClient, + public interaction: ModalSubmitInteraction, + ) { + super(client); + } + + command!: ModalCommand; + metadata: CommandMetadata> = {} as never; + globalMetadata: GlobalMetadata = {}; + + get customId() { + return this.interaction.customId; + } + + get components() { + return this.interaction.components; + } + + /** + * Gets the language object for the interaction's locale. + */ + get t() { + return this.client.t(this.interaction?.locale ?? this.client.langs?.defaultLang ?? 'en-US'); + } + + /** + * Writes a response to the interaction. + * @param body - The body of the response. + * @param fetchReply - Whether to fetch the reply or not. + */ + write(body: InteractionCreateBodyRequest, fetchReply?: FR) { + return this.interaction.write(body, fetchReply); + } + + /** + * Defers the reply to the interaction. + * @param ephemeral - Whether the reply should be ephemeral or not. + */ + deferReply(ephemeral = false) { + return this.interaction.deferReply(ephemeral ? MessageFlags.Ephemeral : undefined); + } + + /** + * Edits the response of the interaction. + * @param body - The updated body of the response. + */ + editResponse(body: InteractionMessageUpdateBodyRequest) { + return this.interaction.editResponse(body); + } + + /** + * Edits the response or replies to the interaction. + * @param body - The body of the response or updated body of the interaction. + * @param fetchReply - Whether to fetch the reply or not. + */ + editOrReply( + body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest, + fetchReply?: FR, + ): Promise> { + return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply); + } + + /** + * Deletes the response of the interaction. + * @returns A promise that resolves when the response is deleted. + */ + deleteResponse() { + return this.interaction.deleteResponse(); + } + + modal(body: ModalCreateBodyRequest) { + //@ts-expect-error + return this.interaction.modal(body); + } + + /** + * Gets the channel of the interaction. + * @param mode - The mode to fetch the channel. + * @returns A promise that resolves to the channel. + */ + 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'); + } + + /** + * Gets the bot member in the guild of the interaction. + * @param mode - The mode to fetch the member. + * @returns A promise that resolves to the bot member. + */ + 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'); + } + } + + /** + * Gets the guild of the interaction. + * @param mode - The mode to fetch the guild. + * @returns A promise that resolves to the guild. + */ + 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'); + } + } + + /** + * Gets the ID of the guild of the interaction. + */ + get guildId() { + return this.interaction.guildId; + } + + /** + * Gets the ID of the channel of the interaction. + */ + get channelId() { + return this.interaction.channelId!; + } + + /** + * Gets the author of the interaction. + */ + get author() { + return this.interaction.user; + } + + /** + * Gets the member of the interaction. + */ + get member() { + return this.interaction.member; + } + + isModal(): this is ModalContext { + return true; + } +}