seyfert
Version:
The most advanced framework for discord bots
291 lines (290 loc) • 11.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComponentHandler = void 0;
const collection_1 = require("../collection");
const commands_1 = require("../commands");
const common_1 = require("../common");
const componentcommand_1 = require("./componentcommand");
const modalcommand_1 = require("./modalcommand");
class ComponentHandler extends common_1.BaseHandler {
client;
onFail = err => this.logger.warn('<Client>.components.onFail', err);
values = new Map();
// 10 minutes of timeout by default, because discord doesnt send an event when the user cancels the modal
modals = new collection_1.LimitedCollection({ expire: 60e3 * 10 });
commands = [];
filter = (path) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts'));
constructor(logger, client) {
super(logger);
this.client = client;
}
createMatchCallback(match) {
if (typeof match === 'string')
return str => str === match;
if (Array.isArray(match))
return str => match.includes(str);
return str => match.test(str);
}
createComponentCollector(messageId, channelId, guildId, options = {}, components = []) {
this.values.set(messageId, {
messageId,
channelId,
guildId,
options,
components,
idle: options.idle && options.idle > 0
? setTimeout(() => {
const old = this.clearValue(messageId);
if (!old)
return;
options.onStop?.('idle', () => {
this.createComponentCollector(messageId, channelId, guildId, options, old.components);
});
}, options.idle)
: undefined,
timeout: options.timeout && options.timeout > 0
? setTimeout(() => {
const old = this.clearValue(messageId);
if (!old)
return;
options.onStop?.('timeout', () => {
this.createComponentCollector(messageId, channelId, guildId, options, old.components);
});
}, options.timeout)
: undefined,
__run: (customId, callback) => {
if (this.values.has(messageId)) {
this.values.get(messageId).components.push({
callback,
match: this.createMatchCallback(customId),
});
}
},
});
return {
//@ts-expect-error generic
run: this.values.get(messageId).__run,
stop: (reason) => {
const old = this.clearValue(messageId);
if (!old)
return;
options.onStop?.(reason, () => {
this.createComponentCollector(messageId, channelId, guildId, options, old.components);
});
},
};
}
async onComponent(id, interaction) {
const row = this.values.get(id);
const component = row?.components?.find(x => x.match(interaction.customId));
if (!component)
return;
if (row.options?.filter) {
if (!(await row.options.filter(interaction)))
return row.options.onPass?.(interaction);
}
row.idle?.refresh();
await component.callback(interaction, reason => {
this.clearValue(id);
row.options?.onStop?.(reason ?? 'stop', () => {
this.createComponentCollector(row.messageId, row.channelId, row.guildId, row.options, row.components);
});
}, () => {
this.resetTimeouts(id);
});
}
hasComponent(id, customId) {
return this.values.get(id)?.components?.some(x => x.match(customId));
}
resetTimeouts(id) {
const listener = this.values.get(id);
if (listener) {
listener.timeout?.refresh();
listener.idle?.refresh();
}
}
hasModal(interaction) {
return this.modals.has(interaction.user.id);
}
onModalSubmit(interaction) {
setImmediate(() => this.modals.delete(interaction.user.id));
return this.modals.get(interaction.user.id)?.(interaction);
}
deleteValue(id, reason) {
const component = this.clearValue(id);
if (!component)
return;
component.options?.onStop?.(reason, () => {
this.createComponentCollector(component.messageId, component.channelId, component.guildId, component.options, component.components);
});
}
clearValue(id) {
const component = this.values.get(id);
if (!component)
return;
clearTimeout(component.timeout);
clearTimeout(component.idle);
this.values.delete(id);
return component;
}
stablishDefaults(component) {
component.props ??= this.client.options.commands?.defaults?.props ?? {};
const is = component instanceof modalcommand_1.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;
}
set(instances) {
for (const i of instances) {
let component;
try {
component = this.callback(i);
if (!component)
continue;
}
catch (e) {
this.logger.warn(e, i);
continue;
}
this.stablishDefaults(component);
this.commands.push(component);
}
}
async load(componentsDir) {
const paths = await this.loadFilesK(await this.getFiles(componentsDir));
for (const { components, file } of paths.map(x => ({ components: this.onFile(x.file), file: x }))) {
if (!components)
continue;
for (const value of components) {
let component;
try {
component = this.callback(value);
if (!component)
continue;
}
catch (e) {
if (e instanceof Error && e.message.includes('is not a constructor')) {
this.logger.warn(`${file.path
.split(process.cwd())
.slice(1)
.join(process.cwd())} doesn't export the class by \`export default <ComponentCommand>\``);
}
else
this.logger.warn(e, value);
continue;
}
if (!(component instanceof modalcommand_1.ModalCommand || component instanceof componentcommand_1.ComponentCommand))
continue;
this.stablishDefaults(component);
component.__filePath = file.path;
this.commands.push(component);
}
}
}
async reload(path) {
if (!this.client.components)
return;
if ((0, common_1.isCloudfareWorker)()) {
throw new Error('Reload in cloudfare worker is not supported');
}
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 (0, common_1.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 execute(i, context) {
try {
const resultRunGlobalMiddlewares = await commands_1.BaseCommand.__runMiddlewares(context, (context.client.options?.globalMiddlewares ?? []), true);
if (resultRunGlobalMiddlewares.pass) {
return;
}
if ('error' in resultRunGlobalMiddlewares) {
return i.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error');
}
const resultRunMiddlewares = await commands_1.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 (e) {
// supress error
this.logger.error(e);
}
}
}
async executeComponent(context) {
for (const i of this.commands) {
try {
if (i.type === componentcommand_1.InteractionCommandType.COMPONENT &&
i.cType === context.interaction.componentType &&
(await i.filter(context))) {
context.command = i;
await this.execute(i, context);
}
}
catch (e) {
await this.onFail(e);
}
}
}
async executeModal(context) {
for (const i of this.commands) {
try {
if (i.type === componentcommand_1.InteractionCommandType.MODAL && (await i.filter(context))) {
context.command = i;
await this.execute(i, context);
}
}
catch (e) {
await this.onFail(e);
}
}
}
onFile(file) {
return file.default ? [file.default] : undefined;
}
callback(file) {
return new file();
}
}
exports.ComponentHandler = ComponentHandler;