fix: actually fix default props in components

This commit is contained in:
MARCROCK22 2024-06-06 17:36:29 +00:00
parent 9a942e8674
commit eef42a9a9f
3 changed files with 327 additions and 333 deletions

View File

@ -19,7 +19,7 @@ export abstract class ComponentCommand {
middlewares: (keyof RegisteredMiddlewares)[] = []; middlewares: (keyof RegisteredMiddlewares)[] = [];
props: ExtraProps = {}; props!: ExtraProps;
get cType(): number { get cType(): number {
return ComponentType[this.componentType]; return ComponentType[this.componentType];

View File

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

View File

@ -13,7 +13,7 @@ export abstract class ModalCommand {
middlewares: (keyof RegisteredMiddlewares)[] = []; middlewares: (keyof RegisteredMiddlewares)[] = [];
props: ExtraProps = {}; props!: ExtraProps;
onAfterRun?(context: ModalContext, error: unknown | undefined): any; onAfterRun?(context: ModalContext, error: unknown | undefined): any;
onRunError?(context: ModalContext, error: unknown): any; onRunError?(context: ModalContext, error: unknown): any;