feat: experimental custom handlers

This commit is contained in:
MARCROCK22 2024-03-26 14:39:04 -04:00
parent 168aab508a
commit 9b40239b55
14 changed files with 165 additions and 82 deletions

View File

@ -4,7 +4,7 @@ import type { Adapter } from '../cache';
import { Cache, MemoryAdapter } from '../cache'; import { Cache, MemoryAdapter } from '../cache';
import type { RegisteredMiddlewares } from '../commands'; import type { RegisteredMiddlewares } from '../commands';
import type { InferWithPrefix, MiddlewareContext } from '../commands/applications/shared'; import type { InferWithPrefix, MiddlewareContext } from '../commands/applications/shared';
import { CommandHandler } from '../commands/handler'; import { CommandHandler, type CommandHandlerLike } from '../commands/handler';
import { import {
ChannelShorter, ChannelShorter,
EmojiShorter, EmojiShorter,
@ -25,8 +25,8 @@ import {
} from '../common'; } from '../common';
import type { DeepPartial, IntentStrings, OmitInsert, When } from '../common/types/util'; import type { DeepPartial, IntentStrings, OmitInsert, When } from '../common/types/util';
import { ComponentHandler } from '../components/handler'; import { ComponentHandler, type ComponentHandlerLike } from '../components/handler';
import { LangsHandler } from '../langs/handler'; import { LangsHandler, type LangsHandlerLike } from '../langs/handler';
import type { import type {
ChatInputCommandInteraction, ChatInputCommandInteraction,
Message, Message,
@ -55,9 +55,9 @@ export class BaseClient {
name: '[Seyfert]', name: '[Seyfert]',
}); });
langs = new LangsHandler(this.logger); langs?: LangsHandlerLike = new LangsHandler(this.logger);
commands = new CommandHandler(this.logger, this); commands?: CommandHandlerLike = new CommandHandler(this.logger, this);
components = new ComponentHandler(this.logger, this); components?: ComponentHandlerLike = new ComponentHandler(this.logger, this);
private _applicationId?: string; private _applicationId?: string;
private _botId?: string; private _botId?: string;
@ -99,7 +99,7 @@ export class BaseClient {
return new Router(this.rest).createProxy(); return new Router(this.rest).createProxy();
} }
setServices({ rest, cache, langs, middlewares }: ServicesOptions) { setServices({ rest, cache, langs, middlewares, handlers }: ServicesOptions) {
if (rest) { if (rest) {
this.rest = rest; this.rest = rest;
} }
@ -111,13 +111,24 @@ export class BaseClient {
this, this,
); );
} }
if (langs) {
if (langs.default) this.langs.defaultLang = langs.default;
if (langs.aliases) this.langs.aliases = Object.entries(langs.aliases);
}
if (middlewares) { if (middlewares) {
this.middlewares = middlewares; this.middlewares = middlewares;
} }
if (handlers) {
if ('components' in handlers) {
this.components = handlers.components;
}
if ('commands' in handlers) {
this.commands = handlers.commands;
}
if ('langs' in handlers) {
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[]) { protected async execute(..._options: unknown[]) {
@ -170,7 +181,7 @@ export class BaseClient {
applicationId ??= await this.getRC().then(x => x.applicationId ?? this.applicationId); applicationId ??= await this.getRC().then(x => x.applicationId ?? this.applicationId);
BaseClient.assertString(applicationId, 'applicationId is not a string'); BaseClient.assertString(applicationId, 'applicationId is not a string');
const commands = this.commands.values.map(x => x.toJSON()); const commands = this.commands!.values.map(x => x.toJSON());
const filter = filterSplit(commands, command => !command.guild_id); const filter = filterSplit(commands, command => !command.guild_id);
await this.proxy.applications(applicationId).commands.put({ await this.proxy.applications(applicationId).commands.put({
@ -197,7 +208,7 @@ export class BaseClient {
async loadCommands(dir?: string) { async loadCommands(dir?: string) {
dir ??= await this.getRC().then(x => x.commands); dir ??= await this.getRC().then(x => x.commands);
if (dir) { if (dir && this.commands) {
await this.commands.load(dir, this); await this.commands.load(dir, this);
this.logger.info('CommandHandler loaded'); this.logger.info('CommandHandler loaded');
} }
@ -205,7 +216,7 @@ export class BaseClient {
async loadComponents(dir?: string) { async loadComponents(dir?: string) {
dir ??= await this.getRC().then(x => x.components); dir ??= await this.getRC().then(x => x.components);
if (dir) { if (dir && this.components) {
await this.components.load(dir); await this.components.load(dir);
this.logger.info('ComponentHandler loaded'); this.logger.info('ComponentHandler loaded');
} }
@ -213,14 +224,14 @@ export class BaseClient {
async loadLangs(dir?: string) { async loadLangs(dir?: string) {
dir ??= await this.getRC().then(x => x.langs); dir ??= await this.getRC().then(x => x.langs);
if (dir) { if (dir && this.langs) {
await this.langs.load(dir); await this.langs.load(dir);
this.logger.info('LangsHandler loaded'); this.logger.info('LangsHandler loaded');
} }
} }
t(locale: string) { t(locale: string) {
return this.langs.get(locale); return this.langs!.get(locale);
} }
async getRC< async getRC<
@ -310,4 +321,9 @@ export interface ServicesOptions {
aliases?: Record<string, LocaleString[]>; aliases?: Record<string, LocaleString[]>;
}; };
middlewares?: Record<string, MiddlewareContext>; middlewares?: Record<string, MiddlewareContext>;
handlers?: {
components?: ComponentHandlerLike;
commands?: CommandHandlerLike;
langs?: LangsHandlerLike;
};
} }

View File

@ -1,5 +1,5 @@
import { parentPort, workerData } from 'node:worker_threads'; import { parentPort, workerData } from 'node:worker_threads';
import type { Command, CommandContext, Message, SubCommand } from '..'; import type { Command, CommandContext, EventHandlerLike, Message, SubCommand } from '..';
import { import {
GatewayIntentBits, GatewayIntentBits,
type DeepPartial, type DeepPartial,
@ -22,7 +22,7 @@ import { onMessageCreate } from './onmessagecreate';
export class Client<Ready extends boolean = boolean> extends BaseClient { export class Client<Ready extends boolean = boolean> extends BaseClient {
private __handleGuilds?: Set<string> = new Set(); private __handleGuilds?: Set<string> = new Set();
gateway!: ShardManager; gateway!: ShardManager;
events = new EventHandler(this.logger); events?: EventHandlerLike = new EventHandler(this.logger);
me!: If<Ready, ClientUser>; me!: If<Ready, ClientUser>;
declare options: ClientOptions | undefined; declare options: ClientOptions | undefined;
memberUpdateHandler = new MemberUpdateHandler(); memberUpdateHandler = new MemberUpdateHandler();
@ -37,6 +37,9 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
...rest ...rest
}: ServicesOptions & { }: ServicesOptions & {
gateway?: ShardManager; gateway?: ShardManager;
handlers?: ServicesOptions['handlers'] & {
events?: EventHandlerLike;
};
}) { }) {
super.setServices(rest); super.setServices(rest);
if (gateway) { if (gateway) {
@ -48,11 +51,14 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
}; };
this.gateway = gateway; this.gateway = gateway;
} }
if (rest.handlers && 'events' in rest.handlers) {
this.events = rest.handlers.events;
}
} }
async loadEvents(dir?: string) { async loadEvents(dir?: string) {
dir ??= await this.getRC().then(x => x.events); dir ??= await this.getRC().then(x => x.events);
if (dir) { if (dir && this.events) {
await this.events.load(dir); await this.events.load(dir);
this.logger.info('EventHandler loaded'); this.logger.info('EventHandler loaded');
} }
@ -114,21 +120,21 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
} }
protected async onPacket(shardId: number, packet: GatewayDispatchPayload) { protected async onPacket(shardId: number, packet: GatewayDispatchPayload) {
await this.events.runEvent('RAW', this, packet, shardId); await this.events?.runEvent('RAW', this, packet, shardId);
switch (packet.t) { switch (packet.t) {
//// Cases where we must obtain the old data before updating //// Cases where we must obtain the old data before updating
case 'GUILD_MEMBER_UPDATE': case 'GUILD_MEMBER_UPDATE':
if (!this.memberUpdateHandler.check(packet.d)) { if (!this.memberUpdateHandler.check(packet.d)) {
return; return;
} }
await this.events.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(packet.t, packet, this as Client<true>, shardId);
await this.cache.onPacket(packet); await this.cache.onPacket(packet);
break; break;
case 'PRESENCE_UPDATE': case 'PRESENCE_UPDATE':
if (!this.presenceUpdateHandler.check(packet.d as any)) { if (!this.presenceUpdateHandler.check(packet.d as any)) {
return; return;
} }
await this.events.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(packet.t, packet, this as Client<true>, shardId);
await this.cache.onPacket(packet); await this.cache.onPacket(packet);
break; break;
//rest of the events //rest of the events
@ -153,7 +159,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
!((this.gateway.options.intents & GatewayIntentBits.Guilds) === GatewayIntentBits.Guilds) !((this.gateway.options.intents & GatewayIntentBits.Guilds) === GatewayIntentBits.Guilds)
) { ) {
if ([...this.gateway.values()].every(shard => shard.data.session_id)) { if ([...this.gateway.values()].every(shard => shard.data.session_id)) {
await this.events.runEvent('BOT_READY', this, this.me, -1); await this.events?.runEvent('BOT_READY', this, this.me, -1);
} }
delete this.__handleGuilds; delete this.__handleGuilds;
} }
@ -163,7 +169,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
if (this.__handleGuilds?.has(packet.d.id)) { if (this.__handleGuilds?.has(packet.d.id)) {
this.__handleGuilds.delete(packet.d.id); this.__handleGuilds.delete(packet.d.id);
if (!this.__handleGuilds.size && [...this.gateway.values()].every(shard => shard.data.session_id)) { if (!this.__handleGuilds.size && [...this.gateway.values()].every(shard => shard.data.session_id)) {
await this.events.runEvent('BOT_READY', this, this.me, -1); await this.events?.runEvent('BOT_READY', this, this.me, -1);
} }
if (!this.__handleGuilds.size) delete this.__handleGuilds; if (!this.__handleGuilds.size) delete this.__handleGuilds;
return; return;
@ -171,7 +177,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
break; break;
} }
} }
await this.events.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(packet.t, packet, this as Client<true>, shardId);
break; break;
} }
} }

View File

@ -1,11 +1,11 @@
import { ApplicationCommandType, InteractionType, type APIInteraction } from 'discord-api-types/v10'; import { ApplicationCommandType, InteractionType, type APIInteraction } from 'discord-api-types/v10';
import { import {
CommandContext, CommandContext,
type ContextOptionsResolved,
MenuCommandContext, MenuCommandContext,
OptionResolver, OptionResolver,
type Command, type Command,
type ContextMenuCommand, type ContextMenuCommand,
type ContextOptionsResolved,
} from '../commands'; } from '../commands';
import type { import type {
ChatInputCommandInteraction, ChatInputCommandInteraction,
@ -28,7 +28,7 @@ export async function onInteractionCreate(
switch (body.type) { switch (body.type) {
case InteractionType.ApplicationCommandAutocomplete: case InteractionType.ApplicationCommandAutocomplete:
{ {
const parentCommand = self.commands.values.find(x => { const parentCommand = self.commands?.values.find(x => {
if (x.guild_id && !x.guild_id.includes(body.data.guild_id ?? '')) { if (x.guild_id && !x.guild_id.includes(body.data.guild_id ?? '')) {
return false; return false;
} }
@ -76,7 +76,7 @@ export async function onInteractionCreate(
case ApplicationCommandType.Message: case ApplicationCommandType.Message:
case ApplicationCommandType.User: case ApplicationCommandType.User:
{ {
const command = self.commands.values.find(x => { const command = self.commands?.values.find(x => {
if (x.guild_id && !x.guild_id.includes(body.data.guild_id ?? '')) { if (x.guild_id && !x.guild_id.includes(body.data.guild_id ?? '')) {
return false; return false;
} }
@ -135,7 +135,7 @@ export async function onInteractionCreate(
case ApplicationCommandType.ChatInput: case ApplicationCommandType.ChatInput:
{ {
const packetData = body.data; const packetData = body.data;
const parentCommand = self.commands.values.find(x => { const parentCommand = self.commands?.values.find(x => {
if (x.guild_id && !x.guild_id.includes(packetData.guild_id ?? '')) { if (x.guild_id && !x.guild_id.includes(packetData.guild_id ?? '')) {
return false; return false;
} }
@ -206,20 +206,20 @@ export async function onInteractionCreate(
case InteractionType.ModalSubmit: case InteractionType.ModalSubmit:
{ {
const interaction = BaseInteraction.from(self, body, __reply) as ModalSubmitInteraction; const interaction = BaseInteraction.from(self, body, __reply) as ModalSubmitInteraction;
if (self.components.hasModal(interaction)) { if (self.components?.hasModal(interaction)) {
await self.components.onModalSubmit(interaction); await self.components.onModalSubmit(interaction);
} else { } else {
await self.components.executeModal(interaction); await self.components?.executeModal(interaction);
} }
} }
break; break;
case InteractionType.MessageComponent: case InteractionType.MessageComponent:
{ {
const interaction = BaseInteraction.from(self, body, __reply) as ComponentInteraction; const interaction = BaseInteraction.from(self, body, __reply) as ComponentInteraction;
if (self.components.hasComponent(body.message.id, interaction.customId)) { if (self.components?.hasComponent(body.message.id, interaction.customId)) {
await self.components.onComponent(body.message.id, interaction); await self.components.onComponent(body.message.id, interaction);
} else { } else {
await self.components.executeComponent(interaction); await self.components?.executeComponent(interaction);
} }
} }
break; break;

View File

@ -34,7 +34,7 @@ function getCommandFromContent(
const parentName = commandRaw[0]; const parentName = commandRaw[0];
const groupName = commandRaw.length === 3 ? commandRaw[1] : undefined; const groupName = commandRaw.length === 3 ? commandRaw[1] : undefined;
const subcommandName = groupName ? commandRaw[2] : commandRaw[1]; const subcommandName = groupName ? commandRaw[2] : commandRaw[1];
const parent = self.commands.values.find(x => x.name === parentName); const parent = self.commands!.values.find(x => x.name === parentName);
const fullCommandName = `${parentName}${ const fullCommandName = `${parentName}${
groupName ? ` ${groupName} ${subcommandName}` : `${subcommandName ? ` ${subcommandName}` : ''}` groupName ? ` ${groupName} ${subcommandName}` : `${subcommandName ? ` ${subcommandName}` : ''}`
}`; }`;
@ -80,7 +80,10 @@ export async function onMessageCreate(
const content = message.content.slice(prefix.length).trimStart(); const content = message.content.slice(prefix.length).trimStart();
const { fullCommandName, command, parent } = getCommandFromContent( const { fullCommandName, command, parent } = getCommandFromContent(
content.split(' ').filter(x => x).slice(0, 3), content
.split(' ')
.filter(x => x)
.slice(0, 3),
self, self,
); );

View File

@ -5,7 +5,7 @@ import type { Cache } from '../cache';
import { WorkerAdapter } from '../cache'; import { WorkerAdapter } from '../cache';
import type { GatewayDispatchPayload, GatewaySendPayload, When } from '../common'; import type { GatewayDispatchPayload, GatewaySendPayload, When } from '../common';
import { GatewayIntentBits, LogLevels, Logger, type DeepPartial } from '../common'; import { GatewayIntentBits, LogLevels, Logger, type DeepPartial } from '../common';
import { EventHandler } from '../events'; import { EventHandler, type EventHandlerLike } from '../events';
import { ClientUser } from '../structures'; import { ClientUser } from '../structures';
import { Shard, type ShardManagerOptions, type WorkerData } from '../websocket'; import { Shard, type ShardManagerOptions, type WorkerData } from '../websocket';
import type { import type {
@ -21,7 +21,7 @@ import type {
WorkerStart, WorkerStart,
} from '../websocket/discord/worker'; } from '../websocket/discord/worker';
import type { ManagerMessages } from '../websocket/discord/workermanager'; import type { ManagerMessages } from '../websocket/discord/workermanager';
import type { BaseClientOptions, StartOptions } from './base'; import type { BaseClientOptions, ServicesOptions, StartOptions } from './base';
import { BaseClient } from './base'; import { BaseClient } from './base';
import type { Client } from './client'; import type { Client } from './client';
import { onInteractionCreate } from './oninteractioncreate'; import { onInteractionCreate } from './oninteractioncreate';
@ -46,7 +46,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
name: `[Worker #${workerData.workerId}]`, name: `[Worker #${workerData.workerId}]`,
}); });
events = new EventHandler(this.logger); events?: EventHandlerLike = new EventHandler(this.logger);
me!: When<Ready, ClientUser>; me!: When<Ready, ClientUser>;
promises = new Map<string, { resolve: (value: any) => void; timeout: NodeJS.Timeout }>(); promises = new Map<string, { resolve: (value: any) => void; timeout: NodeJS.Timeout }>();
@ -98,6 +98,19 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
return acc / this.shards.size; return acc / this.shards.size;
} }
setServices({
...rest
}: ServicesOptions & {
handlers?: ServicesOptions['handlers'] & {
events?: EventHandlerLike;
};
}) {
super.setServices(rest);
if (rest.handlers && 'events' in rest.handlers) {
this.events = rest.handlers.events;
}
}
async start(options: Omit<DeepPartial<StartOptions>, 'httpConnection' | 'token' | 'connection'> = {}) { async start(options: Omit<DeepPartial<StartOptions>, 'httpConnection' | 'token' | 'connection'> = {}) {
await super.start(options); await super.start(options);
await this.loadEvents(options.eventsDir); await this.loadEvents(options.eventsDir);
@ -106,7 +119,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
async loadEvents(dir?: string) { async loadEvents(dir?: string) {
dir ??= await this.getRC().then(x => x.events); dir ??= await this.getRC().then(x => x.events);
if (dir) { if (dir && this.events) {
await this.events.load(dir); await this.events.load(dir);
this.logger.info('EventHandler loaded'); this.logger.info('EventHandler loaded');
} }
@ -221,7 +234,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
} }
break; break;
case 'BOT_READY': case 'BOT_READY':
await this.events.runEvent('BOT_READY', this, this.me, -1); await this.events?.runEvent('BOT_READY', this, this.me, -1);
break; break;
case 'API_RESPONSE': case 'API_RESPONSE':
{ {
@ -302,14 +315,14 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
} }
protected async onPacket(packet: GatewayDispatchPayload, shardId: number) { protected async onPacket(packet: GatewayDispatchPayload, shardId: number) {
await this.events.execute('RAW', packet, this as WorkerClient<true>, shardId); await this.events?.execute('RAW', packet, this as WorkerClient<true>, shardId);
switch (packet.t) { switch (packet.t) {
case 'GUILD_MEMBER_UPDATE': case 'GUILD_MEMBER_UPDATE':
await this.events.execute(packet.t, packet, this as WorkerClient<true>, shardId); await this.events?.execute(packet.t, packet, this as WorkerClient<true>, shardId);
await this.cache.onPacket(packet); await this.cache.onPacket(packet);
break; break;
case 'PRESENCE_UPDATE': case 'PRESENCE_UPDATE':
await this.events.execute(packet.t, packet, this as WorkerClient<true>, shardId); await this.events?.execute(packet.t, packet, this as WorkerClient<true>, shardId);
await this.cache.onPacket(packet); await this.cache.onPacket(packet);
break; break;
//rest of the events //rest of the events
@ -332,7 +345,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
type: 'WORKER_READY', type: 'WORKER_READY',
workerId: this.workerId, workerId: this.workerId,
} as WorkerReady); } as WorkerReady);
await this.events.runEvent('WORKER_READY', this, this.me, -1); await this.events?.runEvent('WORKER_READY', this, this.me, -1);
} }
delete this.__handleGuilds; delete this.__handleGuilds;
} }
@ -352,14 +365,14 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
type: 'WORKER_READY', type: 'WORKER_READY',
workerId: this.workerId, workerId: this.workerId,
} as WorkerReady); } as WorkerReady);
await this.events.runEvent('WORKER_READY', this, this.me, -1); await this.events?.runEvent('WORKER_READY', this, this.me, -1);
} }
if (!this.__handleGuilds.size) delete this.__handleGuilds; if (!this.__handleGuilds.size) delete this.__handleGuilds;
return; return;
} }
} }
} }
await this.events.execute(packet.t, packet, this, shardId); await this.events?.execute(packet.t, packet, this, shardId);
} }
break; break;
} }

View File

@ -54,7 +54,7 @@ export class CommandContext<T extends OptionsRecord = {}, M extends keyof Regist
} }
get t() { get t() {
return this.client.langs.get(this.interaction?.locale ?? this.client.langs.defaultLang ?? 'en-US'); return this.client.langs!.get(this.interaction?.locale ?? this.client.langs!.defaultLang ?? 'en-US');
} }
get fullCommandName() { get fullCommandName() {

View File

@ -60,7 +60,7 @@ export class MenuCommandContext<
} }
get t() { get t() {
return this.client.langs.get(this.interaction.locale); return this.client.langs!.get(this.interaction.locale ?? this.client.langs!.defaultLang ?? 'en-US');
} }
get fullCommandName() { get fullCommandName() {

View File

@ -5,6 +5,11 @@ import { Command, SubCommand } from './applications/chat';
import { ContextMenuCommand } from './applications/menu'; import { ContextMenuCommand } from './applications/menu';
import type { UsingClient } from './applications/shared'; import type { UsingClient } from './applications/shared';
export interface CommandHandlerLike {
values: CommandHandler['values'];
load: CommandHandler['load'];
}
export class CommandHandler extends BaseHandler { export class CommandHandler extends BaseHandler {
values: (Command | ContextMenuCommand)[] = []; values: (Command | ContextMenuCommand)[] = [];
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'));
@ -122,20 +127,20 @@ export class CommandHandler extends BaseHandler {
if (command.__t) { if (command.__t) {
command.name_localizations = {}; command.name_localizations = {};
command.description_localizations = {}; command.description_localizations = {};
for (const locale of Object.keys(client.langs.values)) { for (const locale of Object.keys(client.langs!.values)) {
const locales = this.client.langs.aliases.find(x => x[0] === locale)?.[1] ?? []; const locales = this.client.langs!.aliases.find(x => x[0] === locale)?.[1] ?? [];
if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString); if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString);
if (command.__t.name) { if (command.__t.name) {
for (const i of locales) { for (const i of locales) {
const valueName = client.langs.getKey(locale, command.__t.name!); const valueName = client.langs!.getKey(locale, command.__t.name!);
if (valueName) command.name_localizations[i] = valueName; if (valueName) command.name_localizations[i] = valueName;
} }
} }
if (command.__t.description) { if (command.__t.description) {
for (const i of locales) { for (const i of locales) {
const valueKey = client.langs.getKey(locale, command.__t.description!); const valueKey = client.langs!.getKey(locale, command.__t.description!);
if (valueKey) command.description_localizations[i] = valueKey; if (valueKey) command.description_localizations[i] = valueKey;
} }
} }
@ -146,20 +151,20 @@ export class CommandHandler extends BaseHandler {
if (options instanceof SubCommand || !options.locales) continue; if (options instanceof SubCommand || !options.locales) continue;
options.name_localizations = {}; options.name_localizations = {};
options.description_localizations = {}; options.description_localizations = {};
for (const locale of Object.keys(client.langs.values)) { for (const locale of Object.keys(client.langs!.values)) {
const locales = this.client.langs.aliases.find(x => x[0] === locale)?.[1] ?? []; const locales = this.client.langs!.aliases.find(x => x[0] === locale)?.[1] ?? [];
if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString); if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString);
if (options.locales.name) { if (options.locales.name) {
for (const i of locales) { for (const i of locales) {
const valueName = client.langs.getKey(locale, options.locales.name!); const valueName = client.langs!.getKey(locale, options.locales.name!);
if (valueName) options.name_localizations[i] = valueName; if (valueName) options.name_localizations[i] = valueName;
} }
} }
if (options.locales.description) { if (options.locales.description) {
for (const i of locales) { for (const i of locales) {
const valueKey = client.langs.getKey(locale, options.locales.description!); const valueKey = client.langs!.getKey(locale, options.locales.description!);
if (valueKey) options.description_localizations[i] = valueKey; if (valueKey) options.description_localizations[i] = valueKey;
} }
} }
@ -168,8 +173,8 @@ export class CommandHandler extends BaseHandler {
if (command instanceof Command && command.__tGroups) { if (command instanceof Command && command.__tGroups) {
command.groups = {}; command.groups = {};
for (const locale of Object.keys(client.langs.values)) { for (const locale of Object.keys(client.langs!.values)) {
const locales = this.client.langs.aliases.find(x => x[0] === locale)?.[1] ?? []; const locales = this.client.langs!.aliases.find(x => x[0] === locale)?.[1] ?? [];
if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString); if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString);
for (const group in command.__tGroups) { for (const group in command.__tGroups) {
command.groups[group] ??= { command.groups[group] ??= {
@ -180,7 +185,7 @@ export class CommandHandler extends BaseHandler {
if (command.__tGroups[group].name) { if (command.__tGroups[group].name) {
for (const i of locales) { for (const i of locales) {
const valueName = client.langs.getKey(locale, command.__tGroups[group].name!); const valueName = client.langs!.getKey(locale, command.__tGroups[group].name!);
if (valueName) { if (valueName) {
command.groups[group].name!.push([i, valueName]); command.groups[group].name!.push([i, valueName]);
} }
@ -189,7 +194,7 @@ export class CommandHandler extends BaseHandler {
if (command.__tGroups[group].description) { if (command.__tGroups[group].description) {
for (const i of locales) { for (const i of locales) {
const valueKey = client.langs.getKey(locale, command.__tGroups[group].description!); const valueKey = client.langs!.getKey(locale, command.__tGroups[group].description!);
if (valueKey) { if (valueKey) {
command.groups[group].description!.push([i, valueKey]); command.groups[group].description!.push([i, valueKey]);
} }

View File

@ -50,7 +50,7 @@ export class MessageShorter extends BaseShorter {
.messages(messageId) .messages(messageId)
.delete({ reason }) .delete({ reason })
.then(() => { .then(() => {
return this.client.components.onMessageDelete(messageId); return this.client.components?.onMessageDelete(messageId);
}); });
} }
fetch(messageId: string, channelId: string) { fetch(messageId: string, channelId: string) {

View File

@ -14,8 +14,28 @@ type COMPONENTS = {
__run: (customId: string | string[] | RegExp, callback: ComponentCallback) => any; __run: (customId: string | string[] | RegExp, callback: ComponentCallback) => any;
}; };
export interface ComponentHandlerLike {
readonly values: Map<string, COMPONENTS>;
readonly commands: (ComponentCommand | ModalCommand)[];
readonly modals: Map<string, ModalSubmitCallback> | LimitedCollection<string, ModalSubmitCallback>;
createComponentCollector: ComponentHandler['createComponentCollector'];
hasModal: ComponentHandler['hasModal'];
onModalSubmit: ComponentHandler['onModalSubmit'];
executeModal: ComponentHandler['executeModal'];
hasComponent: ComponentHandler['hasComponent'];
executeComponent: ComponentHandler['executeComponent'];
onComponent: ComponentHandler['onComponent'];
load: ComponentHandler['load'];
onMessageDelete: ComponentHandler['onMessageDelete'];
}
export class ComponentHandler extends BaseHandler { export class ComponentHandler extends BaseHandler {
protected onFail?: OnFailCallback; onFail?: OnFailCallback;
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 });
@ -99,11 +119,13 @@ export class ComponentHandler extends BaseHandler {
} }
hasComponent(id: string, customId: string) { hasComponent(id: string, customId: string) {
return this.values.get(id)?.components?.some(x => { return (
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
);
} }
resetTimeouts(id: string) { resetTimeouts(id: string) {
@ -164,6 +186,7 @@ export class ComponentHandler extends BaseHandler {
} }
async reload(path: string) { async reload(path: string) {
if (!this.client.components) return;
const component = this.client.components.commands.find( const component = this.client.components.commands.find(
x => x =>
x.__filePath?.endsWith(`${path}.js`) || x.__filePath?.endsWith(`${path}.js`) ||
@ -184,6 +207,7 @@ export class ComponentHandler extends BaseHandler {
} }
async reloadAll() { async reloadAll() {
if (!this.client.components) return;
for (const i of this.client.components.commands) { for (const i of this.client.components.commands) {
if (!i.__filePath) return this.logger.warn('Unknown command dont have __filePath property', i); if (!i.__filePath) return this.logger.warn('Unknown command dont have __filePath property', i);
await this.reload(i.__filePath); await this.reload(i.__filePath);

View File

@ -19,6 +19,13 @@ type EventValue = MakeRequired<ClientEvent, '__filePath'> & { fired?: boolean };
type GatewayEvents = Uppercase<SnakeCase<keyof ClientEvents>>; type GatewayEvents = Uppercase<SnakeCase<keyof ClientEvents>>;
export interface EventHandlerLike {
runEvent: EventHandler['runEvent'];
execute: EventHandler['execute'];
load: EventHandler['load'];
values: EventHandler['values'];
}
export class EventHandler extends BaseHandler { export class EventHandler extends BaseHandler {
protected onFail: OnFailCallback = err => this.logger.warn('<Client>.events.OnFail', err); protected onFail: OnFailCallback = err => this.logger.warn('<Client>.events.OnFail', err);
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'));
@ -54,7 +61,7 @@ export class EventHandler extends BaseHandler {
case 'MESSAGE_CREATE': case 'MESSAGE_CREATE':
{ {
const { d: data } = args[0] as GatewayMessageCreateDispatch; const { d: data } = args[0] as GatewayMessageCreateDispatch;
if (args[1].components.values.has(data.interaction?.id ?? data.id)) { if (args[1].components?.values.has(data.interaction?.id ?? data.id)) {
args[1].components.values.get(data.interaction?.id ?? data.id)!.messageId = data.id; args[1].components.values.get(data.interaction?.id ?? data.id)!.messageId = data.id;
} }
} }
@ -62,20 +69,20 @@ export class EventHandler extends BaseHandler {
case 'MESSAGE_DELETE': case 'MESSAGE_DELETE':
{ {
const { d: data } = args[0] as GatewayMessageDeleteDispatch; const { d: data } = args[0] as GatewayMessageDeleteDispatch;
const value = [...args[1].components.values].find(x => x[1].messageId === data.id); const value = [...(args[1].components?.values ?? [])].find(x => x[1].messageId === data.id);
if (value) { if (value) {
args[1].components.onMessageDelete(value[0]); args[1].components!.onMessageDelete(value[0]);
} }
} }
break; break;
case 'MESSAGE_DELETE_BULK': case 'MESSAGE_DELETE_BULK':
{ {
const { d: data } = args[0] as GatewayMessageDeleteBulkDispatch; const { d: data } = args[0] as GatewayMessageDeleteBulkDispatch;
const values = [...args[1].components.values]; const values = [...(args[1].components?.values ?? [])];
data.ids.forEach(id => { data.ids.forEach(id => {
const value = values.find(x => x[1].messageId === id); const value = values.find(x => x[1].messageId === id);
if (value) { if (value) {
args[1].components.onMessageDelete(value[0]); args[1].components!.onMessageDelete(value[0]);
} }
}); });
} }

View File

@ -1,6 +1,15 @@
import { BaseHandler, type Locale, type LocaleString } from '../common'; import { BaseHandler, type Locale, type LocaleString } from '../common';
import { LangRouter } from './router'; import { LangRouter } from './router';
export interface LangsHandlerLike {
getKey: LangsHandler['getKey'];
load: LangsHandler['load'];
values: LangsHandler['values'];
aliases: LangsHandler['aliases'];
get: LangsHandler['get'];
defaultLang?: LangsHandler['defaultLang'];
}
export class LangsHandler extends BaseHandler { export class LangsHandler extends BaseHandler {
values: Partial<Record<string, any>> = {}; values: Partial<Record<string, any>> = {};
protected filter = (path: string) => protected filter = (path: string) =>

View File

@ -388,7 +388,7 @@ export class Interaction<
.webhooks(this.applicationId)(this.token) .webhooks(this.applicationId)(this.token)
.messages(messageId) .messages(messageId)
.delete() .delete()
.then(() => this.client.components.onMessageDelete(messageId === '@original' ? this.id : messageId)); .then(() => this.client.components?.onMessageDelete(messageId === '@original' ? this.id : messageId));
} }
async createResponse({ files, ...body }: MessageWebhookCreateBodyRequest) { async createResponse({ files, ...body }: MessageWebhookCreateBodyRequest) {

View File

@ -51,7 +51,7 @@ export class BaseMessage extends DiscordBase {
} }
createComponentCollector(options?: ListenerOptions) { createComponentCollector(options?: ListenerOptions) {
return this.client.components.createComponentCollector(this.id, options); return this.client.components!.createComponentCollector(this.id, options);
} }
get url() { get url() {