From eef42a9a9ff477e533dafda4f1f46db87e9effe8 Mon Sep 17 00:00:00 2001 From: MARCROCK22 Date: Thu, 6 Jun 2024 17:36:29 +0000 Subject: [PATCH] fix: actually fix default props in components --- src/components/componentcommand.ts | 2 +- src/components/handler.ts | 656 ++++++++++++++--------------- src/components/modalcommand.ts | 2 +- 3 files changed, 327 insertions(+), 333 deletions(-) diff --git a/src/components/componentcommand.ts b/src/components/componentcommand.ts index ab9a1c7..1d4b250 100644 --- a/src/components/componentcommand.ts +++ b/src/components/componentcommand.ts @@ -19,7 +19,7 @@ export abstract class ComponentCommand { middlewares: (keyof RegisteredMiddlewares)[] = []; - props: ExtraProps = {}; + props!: ExtraProps; get cType(): number { return ComponentType[this.componentType]; diff --git a/src/components/handler.ts b/src/components/handler.ts index b482357..88211da 100644 --- a/src/components/handler.ts +++ b/src/components/handler.ts @@ -1,331 +1,325 @@ -import type { ComponentCallback, ListenerOptions, ModalSubmitCallback } from '../builders/types'; -import { LimitedCollection } from '../collection'; -import { BaseCommand, type RegisteredMiddlewares, type UsingClient } from '../commands'; -import { BaseHandler, magicImport, type Logger, type OnFailCallback } from '../common'; -import type { ComponentInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from '../structures'; -import { ComponentCommand, InteractionCommandType } from './componentcommand'; -import type { ComponentContext } from './componentcontext'; -import { ModalCommand } from './modalcommand'; -import type { ModalContext } from './modalcontext'; - -type COMPONENTS = { - components: { match: string | string[] | RegExp; callback: ComponentCallback }[]; - options?: ListenerOptions; - messageId?: string; - idle?: NodeJS.Timeout; - timeout?: NodeJS.Timeout; - __run: (customId: string | string[] | RegExp, callback: ComponentCallback) => any; -}; - -export class ComponentHandler extends BaseHandler { - onFail: OnFailCallback = err => this.logger.warn('.components.onFail', err); - readonly values = new Map(); - // 10 minutes timeout, because discord dont send an event when the user cancel the modal - readonly modals = new LimitedCollection({ expire: 60e3 * 10 }); - readonly commands: (ComponentCommand | ModalCommand)[] = []; - protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts')); - - constructor( - logger: Logger, - protected client: UsingClient, - ) { - super(logger); - } - - createComponentCollector( - messageId: string, - options: ListenerOptions = {}, - ): { - run< - T extends ComponentInteraction | StringSelectMenuInteraction = ComponentInteraction | StringSelectMenuInteraction, - >(customId: string | string[] | RegExp, callback: ComponentCallback): any; - stop(reason?: string): any; - } { - this.values.set(messageId, { - components: [], - options, - idle: - options.idle && options.idle > 0 - ? setTimeout(() => { - this.deleteValue(messageId); - options.onStop?.('idle', () => { - this.createComponentCollector(messageId, options); - }); - }, options.idle) - : undefined, - timeout: - options.timeout && options.timeout > 0 - ? setTimeout(() => { - this.deleteValue(messageId); - options.onStop?.('timeout', () => { - this.createComponentCollector(messageId, options); - }); - }, options.timeout) - : undefined, - __run: (customId, callback) => { - if (this.values.has(messageId)) { - this.values.get(messageId)!.components.push({ - callback, - match: customId, - }); - } - }, - }); - - return { - //@ts-expect-error generic - run: this.values.get(messageId)!.__run, - stop: (reason?: string) => { - this.deleteValue(messageId); - options.onStop?.(reason, () => { - this.createComponentCollector(messageId, options); - }); - }, - }; - } - - async onComponent(id: string, interaction: ComponentInteraction) { - const row = this.values.get(id)!; - const component = row?.components?.find(x => { - if (typeof x.match === 'string') return x.match === interaction.customId; - if (Array.isArray(x.match)) return x.match.includes(interaction.customId); - return interaction.customId.match(x.match); - }); - if (!component) return; - if (row.options?.filter) { - if (!(await row.options.filter(interaction))) return; - } - row.idle?.refresh(); - await component.callback( - interaction, - reason => { - row.options?.onStop?.(reason ?? 'stop'); - this.deleteValue(id); - }, - () => { - this.resetTimeouts(id); - }, - ); - } - - hasComponent(id: string, customId: string) { - return ( - this.values.get(id)?.components?.some(x => { - if (typeof x.match === 'string') return x.match === customId; - if (Array.isArray(x.match)) return x.match.includes(customId); - return customId.match(x.match); - }) ?? false - ); - } - - resetTimeouts(id: string) { - const listener = this.values.get(id); - if (listener) { - listener.timeout?.refresh(); - listener.idle?.refresh(); - } - } - - hasModal(interaction: ModalSubmitInteraction) { - return this.modals.has(interaction.user.id); - } - - onModalSubmit(interaction: ModalSubmitInteraction) { - setImmediate(() => this.modals.delete(interaction.user.id)); - return this.modals.get(interaction.user.id)?.(interaction); - } - - deleteValue(id: string, reason?: string) { - const component = this.values.get(id); - if (component) { - if (reason !== undefined) component.options?.onStop?.(reason); - clearTimeout(component.timeout); - clearTimeout(component.idle); - this.values.delete(id); - } - } - - onMessageDelete(id: string) { - this.deleteValue(id, 'messageDelete'); - } - - async load(componentsDir: string, instances?: { new (): ModalCommand | ComponentCommand }[]) { - const paths = - instances?.map(x => { - const i = new x(); - return { file: x, path: i.__filePath ?? '*' }; - }) ?? (await this.loadFilesK<{ new (): ModalCommand | ComponentCommand }>(await this.getFiles(componentsDir))); - - for (const value of paths) { - let component; - try { - component = this.callback(value.file); - if (!component) continue; - } catch (e) { - if (e instanceof Error && e.message.includes('is not a constructor')) { - this.logger.warn( - `${value.path - .split(process.cwd()) - .slice(1) - .join(process.cwd())} doesn't export the class by \`export default \``, - ); - } else this.logger.warn(e, value); - continue; - } - if (!(component instanceof ModalCommand || component instanceof ComponentCommand)) continue; - component.props ??= this.client.options.commands?.defaults?.props ?? {}; - if (component instanceof ModalCommand) { - component.onInternalError ??= this.client.options?.modals?.defaults?.onInternalError; - component.onMiddlewaresError ??= this.client.options?.modals?.defaults?.onMiddlewaresError; - component.onRunError ??= this.client.options?.modals?.defaults?.onRunError; - component.onAfterRun ??= this.client.options?.modals?.defaults?.onAfterRun; - } else { - component.onInternalError ??= this.client.options?.components?.defaults?.onInternalError; - component.onMiddlewaresError ??= this.client.options?.components?.defaults?.onMiddlewaresError; - component.onRunError ??= this.client.options?.components?.defaults?.onRunError; - component.onAfterRun ??= this.client.options?.components?.defaults?.onAfterRun; - } - component.__filePath = value.path; - this.commands.push(component); - } - } - - async reload(path: string) { - if (!this.client.components) return; - const component = this.client.components.commands.find( - x => - x.__filePath?.endsWith(`${path}.js`) || - x.__filePath?.endsWith(`${path}.ts`) || - x.__filePath?.endsWith(path) || - x.__filePath === path, - ); - if (!component?.__filePath) return null; - delete require.cache[component.__filePath]; - const index = this.client.components.commands.findIndex(x => x.__filePath === component.__filePath!); - if (index === -1) return null; - this.client.components.commands.splice(index, 1); - const imported = await magicImport(component.__filePath).then(x => x.default ?? x); - const command = new imported(); - command.__filePath = component.__filePath; - this.client.components.commands.push(command); - return imported; - } - - async reloadAll(stopIfFail = true) { - for (const i of this.commands) { - try { - await this.reload(i.__filePath ?? ''); - } catch (e) { - if (stopIfFail) { - throw e; - } - } - } - } - - async executeComponent(context: ComponentContext) { - for (const i of this.commands) { - try { - if ( - i.type === InteractionCommandType.COMPONENT && - i.cType === context.interaction.componentType && - (await i.filter(context)) - ) { - context.command = i; - try { - const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares( - context, - (context.client.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares, - true, - ); - if (resultRunGlobalMiddlewares.pass) { - return; - } - if ('error' in resultRunGlobalMiddlewares) { - return i.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error'); - } - - const resultRunMiddlewares = await BaseCommand.__runMiddlewares(context, i.middlewares, false); - if (resultRunMiddlewares.pass) { - return; - } - if ('error' in resultRunMiddlewares) { - return i.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error'); - } - - try { - await i.run(context); - await i.onAfterRun?.(context, undefined); - } catch (error) { - await i.onRunError?.(context, error); - await i.onAfterRun?.(context, error); - } - } catch (error) { - try { - await i.onInternalError?.(this.client, error); - } catch { - // supress error - } - } - break; - } - } catch (e) { - await this.onFail(e); - } - } - } - - async executeModal(context: ModalContext) { - for (const i of this.commands) { - try { - if (i.type === InteractionCommandType.MODAL && (await i.filter(context))) { - context.command = i; - try { - const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares( - context, - (context.client.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares, - true, - ); - if (resultRunGlobalMiddlewares.pass) { - return; - } - if ('error' in resultRunGlobalMiddlewares) { - return i.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error'); - } - - const resultRunMiddlewares = await BaseCommand.__runMiddlewares(context, i.middlewares, false); - if (resultRunMiddlewares.pass) { - return; - } - if ('error' in resultRunMiddlewares) { - return i.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error'); - } - - try { - await i.run(context); - await i.onAfterRun?.(context, undefined); - } catch (error) { - await i.onRunError?.(context, error); - await i.onAfterRun?.(context, error); - } - } catch (error) { - try { - await i.onInternalError?.(this.client, error); - } catch { - // supress error - } - } - break; - } - } catch (e) { - await this.onFail(e); - } - } - } - - setHandlers({ callback }: { callback: ComponentHandler['callback'] }) { - this.callback = callback; - } - - callback = (file: { new (): ModalCommand | ComponentCommand }): ModalCommand | ComponentCommand | false => new file(); -} +import type { ComponentCallback, ListenerOptions, ModalSubmitCallback } from '../builders/types'; +import { LimitedCollection } from '../collection'; +import { BaseCommand, type RegisteredMiddlewares, type UsingClient } from '../commands'; +import { BaseHandler, magicImport, type Logger, type OnFailCallback } from '../common'; +import type { ComponentInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from '../structures'; +import { ComponentCommand, InteractionCommandType } from './componentcommand'; +import type { ComponentContext } from './componentcontext'; +import { ModalCommand } from './modalcommand'; +import type { ModalContext } from './modalcontext'; + +type COMPONENTS = { + components: { match: string | string[] | RegExp; callback: ComponentCallback }[]; + options?: ListenerOptions; + messageId?: string; + idle?: NodeJS.Timeout; + timeout?: NodeJS.Timeout; + __run: (customId: string | string[] | RegExp, callback: ComponentCallback) => any; +}; + +export class ComponentHandler extends BaseHandler { + onFail: OnFailCallback = err => this.logger.warn('.components.onFail', err); + readonly values = new Map(); + // 10 minutes timeout, because discord dont send an event when the user cancel the modal + readonly modals = new LimitedCollection({ expire: 60e3 * 10 }); + readonly commands: (ComponentCommand | ModalCommand)[] = []; + protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts')); + + constructor( + logger: Logger, + protected client: UsingClient, + ) { + super(logger); + } + + createComponentCollector( + messageId: string, + options: ListenerOptions = {}, + ): { + run< + T extends ComponentInteraction | StringSelectMenuInteraction = ComponentInteraction | StringSelectMenuInteraction, + >(customId: string | string[] | RegExp, callback: ComponentCallback): any; + stop(reason?: string): any; + } { + this.values.set(messageId, { + components: [], + options, + idle: + options.idle && options.idle > 0 + ? setTimeout(() => { + this.deleteValue(messageId); + options.onStop?.('idle', () => { + this.createComponentCollector(messageId, options); + }); + }, options.idle) + : undefined, + timeout: + options.timeout && options.timeout > 0 + ? setTimeout(() => { + this.deleteValue(messageId); + options.onStop?.('timeout', () => { + this.createComponentCollector(messageId, options); + }); + }, options.timeout) + : undefined, + __run: (customId, callback) => { + if (this.values.has(messageId)) { + this.values.get(messageId)!.components.push({ + callback, + match: customId, + }); + } + }, + }); + + return { + //@ts-expect-error generic + run: this.values.get(messageId)!.__run, + stop: (reason?: string) => { + this.deleteValue(messageId); + options.onStop?.(reason, () => { + this.createComponentCollector(messageId, options); + }); + }, + }; + } + + async onComponent(id: string, interaction: ComponentInteraction) { + const row = this.values.get(id)!; + const component = row?.components?.find(x => { + if (typeof x.match === 'string') return x.match === interaction.customId; + if (Array.isArray(x.match)) return x.match.includes(interaction.customId); + return interaction.customId.match(x.match); + }); + if (!component) return; + if (row.options?.filter) { + if (!(await row.options.filter(interaction))) return; + } + row.idle?.refresh(); + await component.callback( + interaction, + reason => { + row.options?.onStop?.(reason ?? 'stop'); + this.deleteValue(id); + }, + () => { + this.resetTimeouts(id); + }, + ); + } + + hasComponent(id: string, customId: string) { + return ( + this.values.get(id)?.components?.some(x => { + if (typeof x.match === 'string') return x.match === customId; + if (Array.isArray(x.match)) return x.match.includes(customId); + return customId.match(x.match); + }) ?? false + ); + } + + resetTimeouts(id: string) { + const listener = this.values.get(id); + if (listener) { + listener.timeout?.refresh(); + listener.idle?.refresh(); + } + } + + hasModal(interaction: ModalSubmitInteraction) { + return this.modals.has(interaction.user.id); + } + + onModalSubmit(interaction: ModalSubmitInteraction) { + setImmediate(() => this.modals.delete(interaction.user.id)); + return this.modals.get(interaction.user.id)?.(interaction); + } + + deleteValue(id: string, reason?: string) { + const component = this.values.get(id); + if (component) { + if (reason !== undefined) component.options?.onStop?.(reason); + clearTimeout(component.timeout); + clearTimeout(component.idle); + this.values.delete(id); + } + } + + onMessageDelete(id: string) { + this.deleteValue(id, 'messageDelete'); + } + + async load(componentsDir: string, instances?: { new (): ModalCommand | ComponentCommand }[]) { + const paths = + instances?.map(x => { + const i = new x(); + return { file: x, path: i.__filePath ?? '*' }; + }) ?? (await this.loadFilesK<{ new (): ModalCommand | ComponentCommand }>(await this.getFiles(componentsDir))); + + for (const value of paths) { + let component; + try { + component = this.callback(value.file); + if (!component) continue; + } catch (e) { + if (e instanceof Error && e.message.includes('is not a constructor')) { + this.logger.warn( + `${value.path + .split(process.cwd()) + .slice(1) + .join(process.cwd())} doesn't export the class by \`export default \``, + ); + } else this.logger.warn(e, value); + continue; + } + if (!(component instanceof ModalCommand || component instanceof ComponentCommand)) continue; + component.props ??= this.client.options.commands?.defaults?.props ?? {}; + const is = component instanceof ModalCommand ? 'modals' : 'components'; + component.onInternalError ??= this.client.options?.[is]?.defaults?.onInternalError; + component.onMiddlewaresError ??= this.client.options?.[is]?.defaults?.onMiddlewaresError; + component.onRunError ??= this.client.options?.[is]?.defaults?.onRunError; + component.onAfterRun ??= this.client.options?.[is]?.defaults?.onAfterRun; + component.__filePath = value.path; + this.commands.push(component); + } + } + + async reload(path: string) { + if (!this.client.components) return; + const component = this.client.components.commands.find( + x => + x.__filePath?.endsWith(`${path}.js`) || + x.__filePath?.endsWith(`${path}.ts`) || + x.__filePath?.endsWith(path) || + x.__filePath === path, + ); + if (!component?.__filePath) return null; + delete require.cache[component.__filePath]; + const index = this.client.components.commands.findIndex(x => x.__filePath === component.__filePath!); + if (index === -1) return null; + this.client.components.commands.splice(index, 1); + const imported = await magicImport(component.__filePath).then(x => x.default ?? x); + const command = new imported(); + command.__filePath = component.__filePath; + this.client.components.commands.push(command); + return imported; + } + + async reloadAll(stopIfFail = true) { + for (const i of this.commands) { + try { + await this.reload(i.__filePath ?? ''); + } catch (e) { + if (stopIfFail) { + throw e; + } + } + } + } + + async executeComponent(context: ComponentContext) { + for (const i of this.commands) { + try { + if ( + i.type === InteractionCommandType.COMPONENT && + i.cType === context.interaction.componentType && + (await i.filter(context)) + ) { + context.command = i; + try { + const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares( + context, + (context.client.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares, + true, + ); + if (resultRunGlobalMiddlewares.pass) { + return; + } + if ('error' in resultRunGlobalMiddlewares) { + return i.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error'); + } + + const resultRunMiddlewares = await BaseCommand.__runMiddlewares(context, i.middlewares, false); + if (resultRunMiddlewares.pass) { + return; + } + if ('error' in resultRunMiddlewares) { + return i.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error'); + } + + try { + await i.run(context); + await i.onAfterRun?.(context, undefined); + } catch (error) { + await i.onRunError?.(context, error); + await i.onAfterRun?.(context, error); + } + } catch (error) { + try { + await i.onInternalError?.(this.client, error); + } catch { + // supress error + } + } + break; + } + } catch (e) { + await this.onFail(e); + } + } + } + + async executeModal(context: ModalContext) { + for (const i of this.commands) { + try { + if (i.type === InteractionCommandType.MODAL && (await i.filter(context))) { + context.command = i; + try { + const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares( + context, + (context.client.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares, + true, + ); + if (resultRunGlobalMiddlewares.pass) { + return; + } + if ('error' in resultRunGlobalMiddlewares) { + return i.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error'); + } + + const resultRunMiddlewares = await BaseCommand.__runMiddlewares(context, i.middlewares, false); + if (resultRunMiddlewares.pass) { + return; + } + if ('error' in resultRunMiddlewares) { + return i.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error'); + } + + try { + await i.run(context); + await i.onAfterRun?.(context, undefined); + } catch (error) { + await i.onRunError?.(context, error); + await i.onAfterRun?.(context, error); + } + } catch (error) { + try { + await i.onInternalError?.(this.client, error); + } catch { + // supress error + } + } + break; + } + } catch (e) { + await this.onFail(e); + } + } + } + + setHandlers({ callback }: { callback: ComponentHandler['callback'] }) { + this.callback = callback; + } + + callback = (file: { new (): ModalCommand | ComponentCommand }): ModalCommand | ComponentCommand | false => new file(); +} diff --git a/src/components/modalcommand.ts b/src/components/modalcommand.ts index 781c529..2f28be3 100644 --- a/src/components/modalcommand.ts +++ b/src/components/modalcommand.ts @@ -13,7 +13,7 @@ export abstract class ModalCommand { middlewares: (keyof RegisteredMiddlewares)[] = []; - props: ExtraProps = {}; + props!: ExtraProps; onAfterRun?(context: ModalContext, error: unknown | undefined): any; onRunError?(context: ModalContext, error: unknown): any;