From 125ab779bb7c6fa5b65166483a44713e2a5f917f Mon Sep 17 00:00:00 2001 From: mustafakyia Date: Fri, 16 Jan 2026 23:35:23 +0300 Subject: [PATCH 01/11] refactor: implement class-based structure with hybrid command support --- package-lock.json | 47 ++----- src/classes/base/BaseClient.ts | 15 -- src/index.ts | 6 + src/structures/Argument.ts | 36 +++++ src/structures/Client.ts | 191 ++++++++++++++++++++++++++ src/structures/Command.ts | 43 ++++++ src/structures/Context.ts | 64 +++++++++ src/structures/Event.ts | 16 +++ src/{classes/base => utils}/Logger.ts | 0 9 files changed, 364 insertions(+), 54 deletions(-) delete mode 100644 src/classes/base/BaseClient.ts create mode 100644 src/structures/Argument.ts create mode 100644 src/structures/Client.ts create mode 100644 src/structures/Command.ts create mode 100644 src/structures/Context.ts create mode 100644 src/structures/Event.ts rename src/{classes/base => utils}/Logger.ts (100%) diff --git a/package-lock.json b/package-lock.json index ef734b5..a879393 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", @@ -63,7 +62,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=16.11.0" } @@ -73,7 +71,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "discord-api-types": "^0.38.33" }, @@ -89,7 +86,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", @@ -113,7 +109,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18" }, @@ -126,7 +121,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "discord-api-types": "^0.38.33" }, @@ -142,7 +136,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", @@ -166,7 +159,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18" }, @@ -1204,7 +1196,6 @@ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", "license": "MIT", - "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -1215,7 +1206,6 @@ "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" @@ -1229,7 +1219,6 @@ "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -1416,7 +1405,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=10" } @@ -1434,7 +1422,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=10" } @@ -1452,7 +1439,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1470,7 +1456,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1488,7 +1473,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1506,7 +1490,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1524,7 +1507,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1542,7 +1524,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -1560,7 +1541,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -1578,7 +1558,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -1596,7 +1575,6 @@ "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -1701,7 +1679,6 @@ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -1711,7 +1688,6 @@ "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", "license": "MIT", - "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -2324,7 +2300,6 @@ "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz", "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==", "license": "MIT", - "peer": true, "workspaces": [ "scripts/actions/documentation" ] @@ -2457,8 +2432,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-fifo": { "version": "1.3.2", @@ -2981,15 +2955,13 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lowercase-keys": { "version": "3.0.0", @@ -3018,8 +2990,7 @@ "version": "1.12.1", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/make-fetch-happen": { "version": "15.0.3", @@ -3550,6 +3521,7 @@ "integrity": "sha512-fGYb7z/cljC0Rjtbxh7mIe8vtF/M9TShLvniwc2rdcqNG3Z9g3nM01cr2kWRb1DZdbY4/kItvIsrV4uhaMifyQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "tsgolint": "bin/tsgolint.js" }, @@ -3672,6 +3644,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4260,15 +4233,13 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tuf-js": { "version": "4.1.0", @@ -4328,7 +4299,6 @@ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.17" } @@ -4451,7 +4421,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/src/classes/base/BaseClient.ts b/src/classes/base/BaseClient.ts deleted file mode 100644 index fb93305..0000000 --- a/src/classes/base/BaseClient.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Client, ClientOptions } from "discord.js"; -import { LoggerInstance, LogLevel } from "./Logger"; - -export interface BaseOptions extends ClientOptions { - logLevel?: LogLevel; -} - -export default class BaseClient extends Client { - readonly logger: LoggerInstance; - - constructor(opts: BaseOptions) { - super(opts); - this.logger = new LoggerInstance(opts.logLevel ?? "log"); - } -} diff --git a/src/index.ts b/src/index.ts index 6097fa3..b218a95 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,7 @@ +export * from "./structures/Client"; +export * from "./structures/Command"; +export * from "./structures/Context"; +export * from "./structures/Event"; +export * from "./structures/Argument"; +export * from "./utils/Logger"; export const version = "[VI]{{version}}[/VI]"; diff --git a/src/structures/Argument.ts b/src/structures/Argument.ts new file mode 100644 index 0000000..991aa20 --- /dev/null +++ b/src/structures/Argument.ts @@ -0,0 +1,36 @@ +import { + ApplicationCommandOptionData, + ApplicationCommandOptionType, +} from "discord.js"; + +export class Argument { + public readonly name: string; + public readonly description: string; + public readonly type: ApplicationCommandOptionType; + public readonly required: boolean; + public readonly choices?: { name: string; value: string | number }[]; + + constructor(data: { + name: string; + description: string; + type: ApplicationCommandOptionType; + required?: boolean; + choices?: { name: string; value: string | number }[]; + }) { + this.name = data.name; + this.description = data.description; + this.type = data.type; + this.required = data.required ?? false; + this.choices = data.choices; + } + + public toJSON(): ApplicationCommandOptionData { + return { + name: this.name, + description: this.description, + type: this.type, + required: this.required, + choices: this.choices, + } as ApplicationCommandOptionData; + } +} diff --git a/src/structures/Client.ts b/src/structures/Client.ts new file mode 100644 index 0000000..5c024ca --- /dev/null +++ b/src/structures/Client.ts @@ -0,0 +1,191 @@ +import { + Client as DiscordClient, + ClientOptions, + Collection, + Interaction, + Message, + REST, + Routes, +} from "discord.js"; +import { LoggerInstance, LogLevel } from "../utils/Logger"; +import { Command } from "./Command"; +import { Context } from "./Context"; +import { Event } from "./Event"; +import fs from "fs"; +import path from "path"; + +export interface ClientOptionsWithFramework extends ClientOptions { + logLevel?: LogLevel; + prefix?: string; + token?: string; +} + +export class Client extends DiscordClient { + public readonly logger: LoggerInstance; + public readonly commands: Collection; + public readonly aliases: Collection; + public readonly prefix: string; + + constructor(opts: ClientOptionsWithFramework) { + super(opts); + this.logger = new LoggerInstance(opts.logLevel ?? "log"); + this.commands = new Collection(); + this.aliases = new Collection(); + this.prefix = opts.prefix ?? "!"; + + if (opts.token) this.token = opts.token; + + this.on("messageCreate", this.handleMessage.bind(this)); + this.on("interactionCreate", this.handleInteraction.bind(this)); + + // Auto-load events + this.loadEvents(path.join(__dirname, "..", "events")).catch((error) => + this.logger.error("Error loading events:", error) + ); + } + + public async loadCommands(dir: string) { + const files = this.getFiles(dir); + for (const file of files) { + try { + delete require.cache[require.resolve(file)]; + const { default: CommandClass } = await require(file); + + if (!CommandClass || !(CommandClass.prototype instanceof Command)) { + continue; // Skip non-command files + } + + const command: Command = new CommandClass(this); + this.commands.set(command.name, command); + + for (const alias of command.aliases) { + this.aliases.set(alias, command.name); + } + + this.logger.debug(`Loaded command: ${command.name}`); + } catch (error) { + this.logger.error(`Error loading command ${file}:`, error); + } + } + this.logger.log(`Loaded ${this.commands.size} commands.`); + } + + public async loadEvents(dir: string) { + const files = this.getFiles(dir); + for (const file of files) { + try { + delete require.cache[require.resolve(file)]; + const { default: EventClass } = await require(file); + + if (!EventClass || !(EventClass.prototype instanceof Event)) { + continue; + } + + const event: Event = new EventClass(this); + if (event.once) { + this.once(event.name, (...args) => event.execute(...args)); + } else { + this.on(event.name, (...args) => event.execute(...args)); + } + + this.logger.debug(`Loaded event: ${event.name}`); + } catch (error) { + this.logger.error(`Error loading event ${file}:`, error); + } + } + } + + private getFiles(dir: string, fileList: string[] = []): string[] { + if (!fs.existsSync(dir)) return []; + const files = fs.readdirSync(dir); + for (const file of files) { + const filePath = path.join(dir, file); + if (fs.statSync(filePath).isDirectory()) { + this.getFiles(filePath, fileList); + } else if (file.endsWith(".ts") || file.endsWith(".js")) { + fileList.push(filePath); + } + } + return fileList; + } + + public async registerCommands() { + if (!this.token || !this.application) return; + + const slashCommands = this.commands + .filter((cmd) => cmd.supportsSlash) + .map((cmd) => ({ + name: cmd.name, + description: cmd.description, + options: cmd.options, + })); + + const rest = new REST({ version: "10" }).setToken(this.token); + + try { + this.logger.log( + `Started refreshing ${slashCommands.length} application (/) commands.` + ); + await rest.put(Routes.applicationCommands(this.application.id), { + body: slashCommands, + }); + this.logger.log(`Successfully reloaded application (/) commands.`); + } catch (error) { + this.logger.error("Failed to register commands:", error); + } + } + + private async handleMessage(message: Message) { + if (message.author.bot) return; + if (!message.content.startsWith(this.prefix)) return; + + const args = message.content.slice(this.prefix.length).trim().split(/ +/); + const commandName = args.shift()?.toLowerCase(); + + if (!commandName) return; + + const name = this.aliases.get(commandName) || commandName; + const command = this.commands.get(name); + + if (!command || !command.supportsPrefix) return; + + try { + const context = new Context(this, { message, args }); + await command.execute(context); + } catch (error) { + this.logger.error(`Error executing command ${command.name}:`, error); + await message.reply("There was an error trying to execute that command!"); + } + } + + private async handleInteraction(interaction: Interaction) { + if (!interaction.isCommand()) return; + + const command = this.commands.get(interaction.commandName); + if (!command || !command.supportsSlash) { + await interaction.reply({ + content: "Command not found or disabled.", + ephemeral: true, + }); + return; + } + + try { + const context = new Context(this, { interaction }); + await command.execute(context); + } catch (error) { + this.logger.error(`Error executing command ${command.name}:`, error); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } else { + await interaction.reply({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } + } + } +} diff --git a/src/structures/Command.ts b/src/structures/Command.ts new file mode 100644 index 0000000..71f0b78 --- /dev/null +++ b/src/structures/Command.ts @@ -0,0 +1,43 @@ +import { ApplicationCommandOptionData } from "discord.js"; +import { Context } from "./Context"; +import { Client } from "./Client"; +import { Argument } from "./Argument"; + +export interface CommandOptions { + name: string; + description: string; + aliases?: string[]; + options?: (ApplicationCommandOptionData | Argument)[]; + slash?: boolean; // Default true + prefix?: boolean; // Default true (if user enabled prefix generally) +} + +export abstract class Command { + public readonly client: Client; + public readonly name: string; + public readonly description: string; + public readonly aliases: string[]; + public readonly options: ApplicationCommandOptionData[]; + public readonly supportsSlash: boolean; + public readonly supportsPrefix: boolean; + + constructor(client: Client, options: CommandOptions) { + this.client = client; + this.name = options.name; + this.description = options.description; + this.aliases = options.aliases ?? []; + + // Handle Argument instances or raw data + this.options = (options.options ?? []).map((opt) => { + if (opt instanceof Argument) { + return opt.toJSON(); + } + return opt; + }); + + this.supportsSlash = options.slash ?? true; + this.supportsPrefix = options.prefix ?? true; + } + + public abstract execute(ctx: Context): Promise; +} diff --git a/src/structures/Context.ts b/src/structures/Context.ts new file mode 100644 index 0000000..32aa972 --- /dev/null +++ b/src/structures/Context.ts @@ -0,0 +1,64 @@ +import { + Message, + Guild, + User, + TextBasedChannel, + InteractionReplyOptions, + MessageReplyOptions, + CommandInteraction, +} from "discord.js"; +import { Client } from "./Client"; + +export type ReplyOptions = + | string + | MessageReplyOptions + | InteractionReplyOptions; + +export class Context { + public readonly client: Client; + public readonly interaction?: CommandInteraction; + public readonly message?: Message; + public readonly args: string[] = []; + + constructor( + client: Client, + data: { + interaction?: CommandInteraction; + message?: Message; + args?: string[]; + } + ) { + this.client = client; + this.interaction = data.interaction; + this.message = data.message; + if (data.args) this.args = data.args; + } + + public get author(): User { + return this.interaction?.user ?? this.message!.author; + } + + public get guild(): Guild | null { + return this.interaction?.guild ?? this.message!.guild; + } + + public get channel(): TextBasedChannel | null { + return (this.interaction?.channel ?? + this.message?.channel) as TextBasedChannel; + } + + public async reply(options: ReplyOptions): Promise { + if (this.interaction) { + if (this.interaction.replied || this.interaction.deferred) { + return this.interaction.followUp(options as InteractionReplyOptions); + } + return this.interaction.reply(options as InteractionReplyOptions); + } + return this.message!.reply(options as MessageReplyOptions); + } + + public async send(options: ReplyOptions): Promise { + const channel = this.channel as any; + return channel?.send(options); + } +} diff --git a/src/structures/Event.ts b/src/structures/Event.ts new file mode 100644 index 0000000..8c7aeef --- /dev/null +++ b/src/structures/Event.ts @@ -0,0 +1,16 @@ +import { ClientEvents } from "discord.js"; +import { Client } from "./Client"; + +export abstract class Event { + public readonly client: Client; + public readonly name: K; + public readonly once: boolean; + + constructor(client: Client, name: K, once: boolean = false) { + this.client = client; + this.name = name; + this.once = once; + } + + public abstract execute(...args: ClientEvents[K]): Promise | void; +} diff --git a/src/classes/base/Logger.ts b/src/utils/Logger.ts similarity index 100% rename from src/classes/base/Logger.ts rename to src/utils/Logger.ts From 08de20e94b289b1eceaa5915c5874b5cf8627572 Mon Sep 17 00:00:00 2001 From: vrdons Date: Sat, 17 Jan 2026 14:09:14 +0300 Subject: [PATCH 02/11] use fast-glob to see files --- package-lock.json | 283 +++++++++++++++++++++++++++++++++++++-- package.json | 3 +- src/structures/Client.ts | 26 +--- src/utils/Files.ts | 17 +++ tsconfig.json | 4 +- 5 files changed, 297 insertions(+), 36 deletions(-) create mode 100644 src/utils/Files.ts diff --git a/package-lock.json b/package-lock.json index a879393..35af29d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.1.0-alpha.2", "license": "Apache-2.0", "dependencies": { - "chalk": "^4.1.2" + "chalk": "^4.1.2", + "fast-glob": "^3.3.3" }, "devDependencies": { "@swc/cli": "^0.7.10", @@ -22,7 +23,7 @@ "typescript": "^5.9.3" }, "peerDependencies": { - "discord.js": ">=14.25.1" + "discord.js": "^14.25.1" } }, "node_modules/@borewit/text-codec": { @@ -41,6 +42,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", @@ -62,6 +64,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=16.11.0" } @@ -71,6 +74,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "discord-api-types": "^0.38.33" }, @@ -86,6 +90,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", @@ -109,6 +114,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18" }, @@ -121,6 +127,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "discord-api-types": "^0.38.33" }, @@ -136,6 +143,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", @@ -159,6 +167,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18" }, @@ -532,6 +541,41 @@ "node": ">= 10" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@npmcli/agent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", @@ -1196,6 +1240,7 @@ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", "license": "MIT", + "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -1206,6 +1251,7 @@ "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" @@ -1219,6 +1265,7 @@ "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -1405,6 +1452,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=10" } @@ -1422,6 +1470,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=10" } @@ -1439,6 +1488,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1456,6 +1506,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1473,6 +1524,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1490,6 +1542,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1507,6 +1560,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1524,6 +1578,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1541,6 +1596,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1558,6 +1614,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1575,6 +1632,7 @@ "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -1679,6 +1737,7 @@ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -1688,6 +1747,7 @@ "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", "license": "MIT", + "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -2026,6 +2086,18 @@ "balanced-match": "^1.0.0" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -2300,6 +2372,7 @@ "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz", "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==", "license": "MIT", + "peer": true, "workspaces": [ "scripts/actions/documentation" ] @@ -2432,7 +2505,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-fifo": { "version": "1.3.2", @@ -2441,6 +2515,31 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2514,6 +2613,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-versions": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", @@ -2584,6 +2695,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/glob/node_modules/minimatch": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", @@ -2834,6 +2957,36 @@ "node": ">= 12" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -2955,13 +3108,15 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lowercase-keys": { "version": "3.0.0", @@ -2990,7 +3145,8 @@ "version": "1.12.1", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/make-fetch-happen": { "version": "15.0.3", @@ -3022,6 +3178,40 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -3521,7 +3711,6 @@ "integrity": "sha512-fGYb7z/cljC0Rjtbxh7mIe8vtF/M9TShLvniwc2rdcqNG3Z9g3nM01cr2kWRb1DZdbY4/kItvIsrV4uhaMifyQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "tsgolint": "bin/tsgolint.js" }, @@ -3644,7 +3833,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3730,6 +3918,26 @@ "node": ">=10" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -3786,6 +3994,39 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4128,9 +4369,9 @@ } }, "node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz", + "integrity": "sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -4200,6 +4441,18 @@ "node": "^20.0.0 || >=22.0.0" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/token-types": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", @@ -4233,13 +4486,15 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tuf-js": { "version": "4.1.0", @@ -4299,6 +4554,7 @@ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.17" } @@ -4421,6 +4677,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 45aee4a..c60dd01 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "release:npm": "node scripts/actions/npm.js" }, "dependencies": { - "chalk": "^4.1.2" + "chalk": "^4.1.2", + "fast-glob": "^3.3.3" }, "devDependencies": { "@swc/cli": "^0.7.10", diff --git a/src/structures/Client.ts b/src/structures/Client.ts index 5c024ca..48e5a95 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -11,8 +11,8 @@ import { LoggerInstance, LogLevel } from "../utils/Logger"; import { Command } from "./Command"; import { Context } from "./Context"; import { Event } from "./Event"; -import fs from "fs"; import path from "path"; +import { getFiles, getProjectRoot } from "../utils/Files"; export interface ClientOptionsWithFramework extends ClientOptions { logLevel?: LogLevel; @@ -37,22 +37,22 @@ export class Client extends DiscordClient { this.on("messageCreate", this.handleMessage.bind(this)); this.on("interactionCreate", this.handleInteraction.bind(this)); + const eventsPath = path.join(getProjectRoot(), "events"); - // Auto-load events - this.loadEvents(path.join(__dirname, "..", "events")).catch((error) => + this.loadEvents(eventsPath).catch((error) => this.logger.error("Error loading events:", error) ); } public async loadCommands(dir: string) { - const files = this.getFiles(dir); + const files = getFiles(dir); for (const file of files) { try { delete require.cache[require.resolve(file)]; const { default: CommandClass } = await require(file); if (!CommandClass || !(CommandClass.prototype instanceof Command)) { - continue; // Skip non-command files + continue; } const command: Command = new CommandClass(this); @@ -71,7 +71,7 @@ export class Client extends DiscordClient { } public async loadEvents(dir: string) { - const files = this.getFiles(dir); + const files = getFiles(dir); for (const file of files) { try { delete require.cache[require.resolve(file)]; @@ -95,20 +95,6 @@ export class Client extends DiscordClient { } } - private getFiles(dir: string, fileList: string[] = []): string[] { - if (!fs.existsSync(dir)) return []; - const files = fs.readdirSync(dir); - for (const file of files) { - const filePath = path.join(dir, file); - if (fs.statSync(filePath).isDirectory()) { - this.getFiles(filePath, fileList); - } else if (file.endsWith(".ts") || file.endsWith(".js")) { - fileList.push(filePath); - } - } - return fileList; - } - public async registerCommands() { if (!this.token || !this.application) return; diff --git a/src/utils/Files.ts b/src/utils/Files.ts new file mode 100644 index 0000000..b2db2b2 --- /dev/null +++ b/src/utils/Files.ts @@ -0,0 +1,17 @@ +import FastGlob from "fast-glob"; +import path from "path"; + +export function getFiles(baseDir: string): string[] { + return FastGlob.sync(["**/*.ts", "**/*.js"], { + cwd: baseDir, + absolute: true, + ignore: ["**/*.d.ts", "node_modules/**", ".git/**"], + }); +} +export function getProjectRoot(): string { + if (!require.main?.filename) { + return process.cwd(); + } + + return path.dirname(require.main.filename); +} diff --git a/tsconfig.json b/tsconfig.json index cffc35f..572e823 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,8 @@ "outDir": "./dist", "rootDir": "./src", - "module": "CommonJS", - "moduleResolution": "Node", + "module": "nodenext", + "moduleResolution": "nodenext", "target": "esnext", From 9c9dfe11ed046f1404073e92ed0cf5dcb13379f1 Mon Sep 17 00:00:00 2001 From: vrdons Date: Sat, 17 Jan 2026 14:12:51 +0300 Subject: [PATCH 03/11] test: Add simple arox client tests --- examples/basic_client/index.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/basic_client/index.js diff --git a/examples/basic_client/index.js b/examples/basic_client/index.js new file mode 100644 index 0000000..80239ff --- /dev/null +++ b/examples/basic_client/index.js @@ -0,0 +1,3 @@ +const arox = require("../../dist/index"); + +const client = new arox.Client({ token: process.env.BOT_TOKEN }); From 08f37f057619e7a61c5f9ecd31be72d6e92ca655 Mon Sep 17 00:00:00 2001 From: vrdons Date: Sat, 17 Jan 2026 17:20:19 +0300 Subject: [PATCH 04/11] refactor event and command handling Co-authored-by: Fhyrox --- examples/basic_client/index.js | 13 ++- package-lock.json | 16 ++- package.json | 7 +- src/context.ts | 8 ++ src/handler/interaction.ts | 41 ++++++++ src/handler/message.ts | 39 ++++++++ src/structures/Client.ts | 178 ++++++++++----------------------- src/structures/Command.ts | 37 ++++--- src/structures/Context.ts | 15 +-- src/structures/Event.ts | 38 +++++-- src/utils/util.ts | 18 ++++ types/client.d.ts | 17 ++++ types/extra.d.ts | 1 + 13 files changed, 267 insertions(+), 161 deletions(-) create mode 100644 src/context.ts create mode 100644 src/handler/interaction.ts create mode 100644 src/handler/message.ts create mode 100644 src/utils/util.ts create mode 100644 types/client.d.ts create mode 100644 types/extra.d.ts diff --git a/examples/basic_client/index.js b/examples/basic_client/index.js index 80239ff..4108f76 100644 --- a/examples/basic_client/index.js +++ b/examples/basic_client/index.js @@ -1,3 +1,14 @@ const arox = require("../../dist/index"); -const client = new arox.Client({ token: process.env.BOT_TOKEN }); +const client = new arox.Client({ intents: 37376 }); + +new arox.Command( + { name: "arox", description: "Arox test command", slash: true, prefix: true }, + (interaction, message, args) => { + if (interaction) { + } + if (message) { + } + } +); +client.login(process.env.BOT_TOKEN); diff --git a/package-lock.json b/package-lock.json index 35af29d..bfb5ed0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,12 @@ "license": "Apache-2.0", "dependencies": { "chalk": "^4.1.2", - "fast-glob": "^3.3.3" + "fast-glob": "^3.3.3", + "lodash": "^4.17.21" }, "devDependencies": { "@swc/cli": "^0.7.10", + "@types/lodash": "^4.17.23", "@types/node": "^25.0.8", "husky": "^9.1.7", "libnpmpack": "^9.0.12", @@ -23,7 +25,7 @@ "typescript": "^5.9.3" }, "peerDependencies": { - "discord.js": "^14.25.1" + "discord.js": ">=14.25.1" } }, "node_modules/@borewit/text-codec": { @@ -1723,6 +1725,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz", @@ -3108,8 +3117,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", diff --git a/package.json b/package.json index c60dd01..714920c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,9 @@ "type": "commonjs", "main": "dist/index.js", "types": "dist/index.d.ts", + "imports": { + "#types/*": "./types/*" + }, "scripts": { "prepare": "node scripts/prepareHusky.js", "test": "echo \"Error: no test specified\" && exit 1", @@ -32,10 +35,12 @@ }, "dependencies": { "chalk": "^4.1.2", - "fast-glob": "^3.3.3" + "fast-glob": "^3.3.3", + "lodash": "^4.17.21" }, "devDependencies": { "@swc/cli": "^0.7.10", + "@types/lodash": "^4.17.23", "@types/node": "^25.0.8", "husky": "^9.1.7", "libnpmpack": "^9.0.12", diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..d5ca420 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,8 @@ +import { Client } from "./structures/Client"; + +// Birden fazla client olursa hata çıkartabilir ama aklıma gelen tek şey bu +export let currentClient: Client | null = null; + +export function setClient(client: Client) { + currentClient = client; +} diff --git a/src/handler/interaction.ts b/src/handler/interaction.ts new file mode 100644 index 0000000..053db23 --- /dev/null +++ b/src/handler/interaction.ts @@ -0,0 +1,41 @@ +import { Events } from "discord.js"; +import { Event } from "../structures/Event"; +import { Context } from "../structures/Context"; + +new Event( + Events.InteractionCreate, + async function (interaction) { + if (!interaction.isChatInputCommand()) return; + + const command = this.client.commands.get(interaction.commandName); + if (!command || !command.supportsSlash) { + await interaction.reply({ + content: "Command not found or disabled.", + ephemeral: true, + }); + return; + } + + try { + const context = new Context(this.client, { interaction }); + await command.execute(context); + } catch (error) { + this.client.logger.error( + `Error executing command ${command.name}:`, + error + ); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } else { + await interaction.reply({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } + } + }, + false +); diff --git a/src/handler/message.ts b/src/handler/message.ts new file mode 100644 index 0000000..30810d1 --- /dev/null +++ b/src/handler/message.ts @@ -0,0 +1,39 @@ +import { Events } from "discord.js"; +import { Event } from "../structures/Event"; +import { Context } from "../structures/Context"; + +new Event( + Events.MessageCreate, + async function (message) { + if (message.author.bot) return; + const prefix = this.client.options.prefix as string; + if (!message.content.startsWith(prefix)) return; + + const args = message.content.slice(prefix.length).trim().split(/ +/); + const commandName = args.shift()?.toLowerCase(); + if (!commandName) return; + + const commandAlias = this.client.aliases.findKey((cmd) => + cmd.has(commandName) + ); + let command = this.client.commands.get(commandAlias ?? commandName); + if (!command || !command.supportsSlash) { + await message.reply({ + content: "Command not found or disabled.", + allowedMentions: { repliedUser: false }, + }); + return; + } + + try { + const context = new Context(this.client, { message }); + await command.execute(context); + } catch (error) { + this.client.logger.error( + `Error executing command ${command.name}:`, + error + ); + } + }, + false +); diff --git a/src/structures/Client.ts b/src/structures/Client.ts index 48e5a95..a671b74 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -1,102 +1,82 @@ import { Client as DiscordClient, - ClientOptions, Collection, - Interaction, - Message, REST, Routes, + IntentsBitField, } from "discord.js"; -import { LoggerInstance, LogLevel } from "../utils/Logger"; +import { LoggerInstance } from "../utils/Logger"; import { Command } from "./Command"; -import { Context } from "./Context"; -import { Event } from "./Event"; import path from "path"; import { getFiles, getProjectRoot } from "../utils/Files"; - -export interface ClientOptionsWithFramework extends ClientOptions { - logLevel?: LogLevel; - prefix?: string; - token?: string; -} +import { FrameworkOptions, PrefixOptions } from "#types/client.js"; +import { merge } from "lodash"; +import { setClient } from "../context"; +import { getPrefix } from "../utils/util"; + +const defaultOpts: Omit = { + paths: { + events: "events", + commands: "commands", + }, +}; export class Client extends DiscordClient { public readonly logger: LoggerInstance; - public readonly commands: Collection; - public readonly aliases: Collection; - public readonly prefix: string; - - constructor(opts: ClientOptionsWithFramework) { - super(opts); - this.logger = new LoggerInstance(opts.logLevel ?? "log"); + public commands: Collection; + public aliases: Collection>; + declare public options: Omit & { + intents: IntentsBitField; + }; + + constructor(opts: FrameworkOptions) { + super(merge(defaultOpts, opts) as FrameworkOptions); + this.logger = new LoggerInstance(this.options.logLevel ?? "log"); this.commands = new Collection(); this.aliases = new Collection(); - this.prefix = opts.prefix ?? "!"; - - if (opts.token) this.token = opts.token; - - this.on("messageCreate", this.handleMessage.bind(this)); - this.on("interactionCreate", this.handleInteraction.bind(this)); - const eventsPath = path.join(getProjectRoot(), "events"); + this.options.prefix = getPrefix( + this.options.prefix ?? { enabled: false } + ) as PrefixOptions; + + if (this.options.paths?.events) { + this.loadFiles( + path.join(getProjectRoot(), this.options.paths?.events) + ).catch((error) => this.logger.error("Error loading events:", error)); + } - this.loadEvents(eventsPath).catch((error) => - this.logger.error("Error loading events:", error) - ); + setClient(this); + require("../handler/interaction"); } - public async loadCommands(dir: string) { + async loadFiles(dir: string) { const files = getFiles(dir); for (const file of files) { - try { - delete require.cache[require.resolve(file)]; - const { default: CommandClass } = await require(file); - - if (!CommandClass || !(CommandClass.prototype instanceof Command)) { - continue; - } - - const command: Command = new CommandClass(this); - this.commands.set(command.name, command); - - for (const alias of command.aliases) { - this.aliases.set(alias, command.name); - } - - this.logger.debug(`Loaded command: ${command.name}`); - } catch (error) { - this.logger.error(`Error loading command ${file}:`, error); - } + await this.loadFile(file); } - this.logger.log(`Loaded ${this.commands.size} commands.`); } - public async loadEvents(dir: string) { - const files = getFiles(dir); - for (const file of files) { - try { - delete require.cache[require.resolve(file)]; - const { default: EventClass } = await require(file); - - if (!EventClass || !(EventClass.prototype instanceof Event)) { - continue; - } - - const event: Event = new EventClass(this); - if (event.once) { - this.once(event.name, (...args) => event.execute(...args)); - } else { - this.on(event.name, (...args) => event.execute(...args)); - } + async loadFile(file: string) { + try { + delete require.cache[require.resolve(file)]; + setClient(this); - this.logger.debug(`Loaded event: ${event.name}`); - } catch (error) { - this.logger.error(`Error loading event ${file}:`, error); - } + await require(file); + } catch (error) { + this.logger.error(`Error loading file ${file}:`, error); } } public async registerCommands() { - if (!this.token || !this.application) return; + if (!this.token) { + this.logger.warn("registerCommands skipped: client token is not set."); + return; + } + if (!this.application) { + this.logger.warn( + "registerCommands skipped: client application is not ready." + ); + return; + } const slashCommands = this.commands .filter((cmd) => cmd.supportsSlash) @@ -120,58 +100,4 @@ export class Client extends DiscordClient { this.logger.error("Failed to register commands:", error); } } - - private async handleMessage(message: Message) { - if (message.author.bot) return; - if (!message.content.startsWith(this.prefix)) return; - - const args = message.content.slice(this.prefix.length).trim().split(/ +/); - const commandName = args.shift()?.toLowerCase(); - - if (!commandName) return; - - const name = this.aliases.get(commandName) || commandName; - const command = this.commands.get(name); - - if (!command || !command.supportsPrefix) return; - - try { - const context = new Context(this, { message, args }); - await command.execute(context); - } catch (error) { - this.logger.error(`Error executing command ${command.name}:`, error); - await message.reply("There was an error trying to execute that command!"); - } - } - - private async handleInteraction(interaction: Interaction) { - if (!interaction.isCommand()) return; - - const command = this.commands.get(interaction.commandName); - if (!command || !command.supportsSlash) { - await interaction.reply({ - content: "Command not found or disabled.", - ephemeral: true, - }); - return; - } - - try { - const context = new Context(this, { interaction }); - await command.execute(context); - } catch (error) { - this.logger.error(`Error executing command ${command.name}:`, error); - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ - content: "There was an error while executing this command!", - ephemeral: true, - }); - } else { - await interaction.reply({ - content: "There was an error while executing this command!", - ephemeral: true, - }); - } - } - } } diff --git a/src/structures/Command.ts b/src/structures/Command.ts index 71f0b78..2b9a1bf 100644 --- a/src/structures/Command.ts +++ b/src/structures/Command.ts @@ -2,18 +2,21 @@ import { ApplicationCommandOptionData } from "discord.js"; import { Context } from "./Context"; import { Client } from "./Client"; import { Argument } from "./Argument"; +import { currentClient } from "../context"; +import { MaybePromise } from "#types/extra.js"; export interface CommandOptions { name: string; description: string; aliases?: string[]; options?: (ApplicationCommandOptionData | Argument)[]; - slash?: boolean; // Default true - prefix?: boolean; // Default true (if user enabled prefix generally) + slash?: boolean; + prefix?: boolean; } export abstract class Command { public readonly client: Client; + public readonly name: string; public readonly description: string; public readonly aliases: string[]; @@ -21,23 +24,31 @@ export abstract class Command { public readonly supportsSlash: boolean; public readonly supportsPrefix: boolean; - constructor(client: Client, options: CommandOptions) { - this.client = client; + constructor( + options: CommandOptions, + public readonly execute: (ctx: Context) => MaybePromise + ) { + if (!currentClient) throw new Error("Client is not defined"); + + this.client = currentClient; + this.name = options.name; this.description = options.description; this.aliases = options.aliases ?? []; - // Handle Argument instances or raw data this.options = (options.options ?? []).map((opt) => { - if (opt instanceof Argument) { - return opt.toJSON(); - } - return opt; + return opt instanceof Argument ? opt.toJSON() : opt; }); - this.supportsSlash = options.slash ?? true; - this.supportsPrefix = options.prefix ?? true; - } + this.supportsSlash = options.slash ?? false; + this.supportsPrefix = options.prefix ?? false; - public abstract execute(ctx: Context): Promise; + process.nextTick(() => { + if (this.client.commands.has(this.name)) + throw new Error(`Command ${this.name} already registered to framework`); + this.client.commands.set(this.name, this); + + this.client.aliases.set(this.name, new Set(this.aliases)); + }); + } } diff --git a/src/structures/Context.ts b/src/structures/Context.ts index 32aa972..dff874e 100644 --- a/src/structures/Context.ts +++ b/src/structures/Context.ts @@ -18,20 +18,21 @@ export class Context { public readonly client: Client; public readonly interaction?: CommandInteraction; public readonly message?: Message; - public readonly args: string[] = []; + public readonly args: string[]; constructor( client: Client, - data: { - interaction?: CommandInteraction; - message?: Message; - args?: string[]; - } + data: + | { interaction: CommandInteraction; message?: never; args?: string[] } + | { message: Message; interaction?: never; args?: string[] } ) { + if (!data.interaction && !data.message) { + throw new Error("Context requires either interaction or message"); + } this.client = client; this.interaction = data.interaction; this.message = data.message; - if (data.args) this.args = data.args; + this.args = data.args ?? []; } public get author(): User { diff --git a/src/structures/Event.ts b/src/structures/Event.ts index 8c7aeef..cd97f61 100644 --- a/src/structures/Event.ts +++ b/src/structures/Event.ts @@ -1,16 +1,36 @@ import { ClientEvents } from "discord.js"; +import { MaybePromise } from "#types/extra.js"; +import { currentClient } from "../context"; import { Client } from "./Client"; -export abstract class Event { +type EventArgs = + K extends keyof ClientEvents ? ClientEvents[K] : any[]; + +export class Event { public readonly client: Client; - public readonly name: K; - public readonly once: boolean; - constructor(client: Client, name: K, once: boolean = false) { - this.client = client; - this.name = name; - this.once = once; - } + constructor( + public readonly name: K, + public readonly execute: ( + this: Event, + ...args: EventArgs + ) => MaybePromise, + public readonly once: boolean = false + ) { + if (!currentClient) throw new Error("Client is not defined"); + this.client = currentClient; - public abstract execute(...args: ClientEvents[K]): Promise | void; + if (!this.name) { + throw new Error(`${this.constructor.name}: Event name is missing!`); + } + process.nextTick(() => { + const listener = (...args: any[]) => + this.execute(...(args as EventArgs)); + if (this.once) { + this.client.once(this.name as string, listener); + } else { + this.client.on(this.name as string, listener); + } + }); + } } diff --git a/src/utils/util.ts b/src/utils/util.ts new file mode 100644 index 0000000..02382b5 --- /dev/null +++ b/src/utils/util.ts @@ -0,0 +1,18 @@ +export type PrefixOptions = + | { enabled: true; prefix: string } + | { enabled: false } + | string; + +export function getPrefix(opts: PrefixOptions): string | false { + if (typeof opts === "string") { + return opts; + } + + if (opts && typeof opts === "object") { + if (opts.enabled) { + return opts.prefix; + } + } + + return false; +} diff --git a/types/client.d.ts b/types/client.d.ts new file mode 100644 index 0000000..a30c2c5 --- /dev/null +++ b/types/client.d.ts @@ -0,0 +1,17 @@ +import { ClientOptions } from "discord.js"; + +export interface FrameworkPaths { + events?: string; + commands?: string; +} + +export type PrefixOptions = + | { enabled: true; prefix: string } + | { enabled: false } + | string; + +export interface FrameworkOptions extends ClientOptions { + logLevel?: LogLevel; + prefix?: PrefixOptions; + paths?: FrameworkPaths; +} diff --git a/types/extra.d.ts b/types/extra.d.ts new file mode 100644 index 0000000..5eb85a3 --- /dev/null +++ b/types/extra.d.ts @@ -0,0 +1 @@ +export type MaybePromise = Promise | T; From 84cc5d9cdaaa585237110d91b52b35504934cfde Mon Sep 17 00:00:00 2001 From: vrdons Date: Sat, 17 Jan 2026 17:27:50 +0300 Subject: [PATCH 05/11] fix minor issues --- src/structures/Client.ts | 5 ++++- src/structures/Context.ts | 27 +++++---------------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/structures/Client.ts b/src/structures/Client.ts index a671b74..b4b4f24 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -21,7 +21,9 @@ const defaultOpts: Omit = { }, }; -export class Client extends DiscordClient { +export class Client< + Ready extends boolean = boolean, +> extends DiscordClient { public readonly logger: LoggerInstance; public commands: Collection; public aliases: Collection>; @@ -46,6 +48,7 @@ export class Client extends DiscordClient { setClient(this); require("../handler/interaction"); + if (this.options.prefix) require("../handler/message"); } async loadFiles(dir: string) { diff --git a/src/structures/Context.ts b/src/structures/Context.ts index dff874e..bad77c9 100644 --- a/src/structures/Context.ts +++ b/src/structures/Context.ts @@ -2,7 +2,6 @@ import { Message, Guild, User, - TextBasedChannel, InteractionReplyOptions, MessageReplyOptions, CommandInteraction, @@ -35,31 +34,15 @@ export class Context { this.args = data.args ?? []; } - public get author(): User { - return this.interaction?.user ?? this.message!.author; + public get author(): User | null { + return this.interaction?.user ?? this.message?.author ?? null; } public get guild(): Guild | null { - return this.interaction?.guild ?? this.message!.guild; + return this.interaction?.guild ?? this.message?.guild ?? null; } - public get channel(): TextBasedChannel | null { - return (this.interaction?.channel ?? - this.message?.channel) as TextBasedChannel; - } - - public async reply(options: ReplyOptions): Promise { - if (this.interaction) { - if (this.interaction.replied || this.interaction.deferred) { - return this.interaction.followUp(options as InteractionReplyOptions); - } - return this.interaction.reply(options as InteractionReplyOptions); - } - return this.message!.reply(options as MessageReplyOptions); - } - - public async send(options: ReplyOptions): Promise { - const channel = this.channel as any; - return channel?.send(options); + public get channel() { + return this.interaction?.channel ?? this.message?.channel; } } From 92fa8562de9fc8c3f087682d15474df744ffd40d Mon Sep 17 00:00:00 2001 From: vrdons Date: Sat, 17 Jan 2026 21:32:13 +0300 Subject: [PATCH 06/11] use builder-like handler --- .gitignore | 4 ++ examples/basic_client/events/ready.js | 7 +++ examples/basic_client/index.js | 34 +++++++---- package-lock.json | 13 +++- package.json | 1 + src/handler/interaction.ts | 59 ++++++++----------- src/handler/message.ts | 60 +++++++++---------- src/handler/ready.ts | 7 +++ src/structures/Client.ts | 26 ++++---- src/structures/Command.ts | 67 ++++++++++++++------- src/structures/Context.ts | 85 +++++++++++++++------------ src/structures/Event.ts | 35 +++++++---- src/utils/Files.ts | 2 +- src/utils/Logger.ts | 2 +- src/utils/util.ts | 17 ++++-- types/client.d.ts | 2 + types/logger.d.ts | 1 + 17 files changed, 259 insertions(+), 163 deletions(-) create mode 100644 examples/basic_client/events/ready.js create mode 100644 src/handler/ready.ts create mode 100644 types/logger.d.ts diff --git a/.gitignore b/.gitignore index 9860f0c..aadd713 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ +# Our +env.sh + # Build extra dist + # Logs logs *.log diff --git a/examples/basic_client/events/ready.js b/examples/basic_client/events/ready.js new file mode 100644 index 0000000..c08fc96 --- /dev/null +++ b/examples/basic_client/events/ready.js @@ -0,0 +1,7 @@ +const { EventBuilder } = require("../../../dist"); + +new EventBuilder("clientReady", function () { + this.logger.log("Client connected!"); + this.logger.warn(`Current user: ${this.client.user.username}`); + this.logger.warn(`Current prefix: ${this.client.prefix ?? "none"}`); +}); diff --git a/examples/basic_client/index.js b/examples/basic_client/index.js index 4108f76..786243e 100644 --- a/examples/basic_client/index.js +++ b/examples/basic_client/index.js @@ -1,14 +1,26 @@ const arox = require("../../dist/index"); -const client = new arox.Client({ intents: 37376 }); - -new arox.Command( - { name: "arox", description: "Arox test command", slash: true, prefix: true }, - (interaction, message, args) => { - if (interaction) { - } - if (message) { - } - } -); +const client = new arox.Client({ + intents: 37376, + prefix: { enabled: true, prefix: "a!" }, + logLevel: "debug", +}); + +const command = new arox.CommandBuilder({ + name: "arox", + description: "Arox test command", + slash: true, + prefix: true, +}); + +command + .onMessage(function (ctx) { + const { message } = ctx; + message.reply("Çalışıyom ulan şurda rahat bırak beni"); + }) + .onInteraction(function (ctx) { + const { interaction } = ctx; + interaction.reply("Çalışıyom ulan şurda rahat bırak beni"); + }); + client.login(process.env.BOT_TOKEN); diff --git a/package-lock.json b/package-lock.json index bfb5ed0..d95144c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0-alpha.2", "license": "Apache-2.0", "dependencies": { + "@swc/helpers": "^0.5.18", "chalk": "^4.1.2", "fast-glob": "^3.3.3", "lodash": "^4.17.21" @@ -1628,6 +1629,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@swc/helpers": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@swc/types": { "version": "0.1.25", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", @@ -4501,8 +4511,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tuf-js": { "version": "4.1.0", diff --git a/package.json b/package.json index 714920c..19ffdd4 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "release:npm": "node scripts/actions/npm.js" }, "dependencies": { + "@swc/helpers": "^0.5.18", "chalk": "^4.1.2", "fast-glob": "^3.3.3", "lodash": "^4.17.21" diff --git a/src/handler/interaction.ts b/src/handler/interaction.ts index 053db23..2fb8355 100644 --- a/src/handler/interaction.ts +++ b/src/handler/interaction.ts @@ -1,41 +1,34 @@ import { Events } from "discord.js"; -import { Event } from "../structures/Event"; +import { EventBuilder } from "../structures/Event"; import { Context } from "../structures/Context"; -new Event( - Events.InteractionCreate, - async function (interaction) { - if (!interaction.isChatInputCommand()) return; +new EventBuilder(Events.InteractionCreate, false, async function (interaction) { + if (!interaction.isChatInputCommand()) return; - const command = this.client.commands.get(interaction.commandName); - if (!command || !command.supportsSlash) { + const command = this.client.commands.get(interaction.commandName); + if (!command || !command._supportsSlash) { + await interaction.reply({ + content: "Command not found or disabled.", + ephemeral: true, + }); + return; + } + + try { + const context = new Context(this.client, { interaction }); + if (command._onInteraction) await command._onInteraction(context.toJSON()); + } catch (error) { + this.client.logger.error(`Error executing command ${command.name}:`, error); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } else { await interaction.reply({ - content: "Command not found or disabled.", + content: "There was an error while executing this command!", ephemeral: true, }); - return; - } - - try { - const context = new Context(this.client, { interaction }); - await command.execute(context); - } catch (error) { - this.client.logger.error( - `Error executing command ${command.name}:`, - error - ); - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ - content: "There was an error while executing this command!", - ephemeral: true, - }); - } else { - await interaction.reply({ - content: "There was an error while executing this command!", - ephemeral: true, - }); - } } - }, - false -); + } +}); diff --git a/src/handler/message.ts b/src/handler/message.ts index 30810d1..b6d26fd 100644 --- a/src/handler/message.ts +++ b/src/handler/message.ts @@ -1,39 +1,37 @@ import { Events } from "discord.js"; -import { Event } from "../structures/Event"; +import { EventBuilder } from "../structures/Event"; import { Context } from "../structures/Context"; +import { deleteMessage } from "../utils/util"; -new Event( - Events.MessageCreate, - async function (message) { - if (message.author.bot) return; - const prefix = this.client.options.prefix as string; - if (!message.content.startsWith(prefix)) return; +new EventBuilder(Events.MessageCreate, false, async function (message) { + if (message.author.bot) return; + const prefix = this.client.prefix as string; + if (!message.content.startsWith(prefix)) return; - const args = message.content.slice(prefix.length).trim().split(/ +/); - const commandName = args.shift()?.toLowerCase(); - if (!commandName) return; + const args = message.content.slice(prefix.length).trim().split(/ +/); + const commandName = args.shift()?.toLowerCase(); + if (!commandName) return; - const commandAlias = this.client.aliases.findKey((cmd) => - cmd.has(commandName) - ); - let command = this.client.commands.get(commandAlias ?? commandName); - if (!command || !command.supportsSlash) { - await message.reply({ + const commandAlias = this.client.aliases.findKey((cmd) => + cmd.has(commandName) + ); + + let command = this.client.commands.get(commandAlias ?? commandName); + if (!command || !command._supportsPrefix) { + await message + .reply({ content: "Command not found or disabled.", allowedMentions: { repliedUser: false }, - }); - return; - } + }) + .then(deleteMessage); + return; + } - try { - const context = new Context(this.client, { message }); - await command.execute(context); - } catch (error) { - this.client.logger.error( - `Error executing command ${command.name}:`, - error - ); - } - }, - false -); + try { + const context = new Context(this.client, { message, args }); + this.logger.debug(`${context.author} used ${command.name}(message)`); + if (command._onMessage) await command._onMessage(context.toJSON()); + } catch (error) { + this.client.logger.error(`Error executing command ${command.name}:`, error); + } +}); diff --git a/src/handler/ready.ts b/src/handler/ready.ts new file mode 100644 index 0000000..999d456 --- /dev/null +++ b/src/handler/ready.ts @@ -0,0 +1,7 @@ +import { EventBuilder } from "../structures/Event"; + +new EventBuilder("clientReady").onExecute(function () { + if (this.client.options.autoRegiserCommands) { + this.client.registerCommands(); + } +}); diff --git a/src/structures/Client.ts b/src/structures/Client.ts index b4b4f24..3273c29 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -6,10 +6,10 @@ import { IntentsBitField, } from "discord.js"; import { LoggerInstance } from "../utils/Logger"; -import { Command } from "./Command"; +import { CommandBuilder } from "./Command"; import path from "path"; import { getFiles, getProjectRoot } from "../utils/Files"; -import { FrameworkOptions, PrefixOptions } from "#types/client.js"; +import { FrameworkOptions } from "#types/client.js"; import { merge } from "lodash"; import { setClient } from "../context"; import { getPrefix } from "../utils/util"; @@ -19,26 +19,27 @@ const defaultOpts: Omit = { events: "events", commands: "commands", }, + autoRegiserCommands: true, }; export class Client< Ready extends boolean = boolean, > extends DiscordClient { public readonly logger: LoggerInstance; - public commands: Collection; + public commands: Collection; public aliases: Collection>; + public readonly prefix: string | boolean; + declare public options: Omit & { intents: IntentsBitField; }; constructor(opts: FrameworkOptions) { - super(merge(defaultOpts, opts) as FrameworkOptions); + super(merge({}, defaultOpts, opts) as FrameworkOptions); this.logger = new LoggerInstance(this.options.logLevel ?? "log"); this.commands = new Collection(); this.aliases = new Collection(); - this.options.prefix = getPrefix( - this.options.prefix ?? { enabled: false } - ) as PrefixOptions; + this.prefix = getPrefix(this.options.prefix ?? { enabled: false }); if (this.options.paths?.events) { this.loadFiles( @@ -47,8 +48,9 @@ export class Client< } setClient(this); + require("../handler/ready"); require("../handler/interaction"); - if (this.options.prefix) require("../handler/message"); + if (this.prefix) require("../handler/message"); } async loadFiles(dir: string) { @@ -82,7 +84,7 @@ export class Client< } const slashCommands = this.commands - .filter((cmd) => cmd.supportsSlash) + .filter((cmd) => cmd._supportsSlash) .map((cmd) => ({ name: cmd.name, description: cmd.description, @@ -92,13 +94,15 @@ export class Client< const rest = new REST({ version: "10" }).setToken(this.token); try { - this.logger.log( + this.logger.debug( `Started refreshing ${slashCommands.length} application (/) commands.` ); await rest.put(Routes.applicationCommands(this.application.id), { body: slashCommands, }); - this.logger.log(`Successfully reloaded application (/) commands.`); + this.logger.warn( + `Loaded ${slashCommands.length} application (/) commands.` + ); } catch (error) { this.logger.error("Failed to register commands:", error); } diff --git a/src/structures/Command.ts b/src/structures/Command.ts index 2b9a1bf..7141183 100644 --- a/src/structures/Command.ts +++ b/src/structures/Command.ts @@ -1,36 +1,42 @@ -import { ApplicationCommandOptionData } from "discord.js"; +import { + ApplicationCommandOptionData, + ChatInputCommandInteraction, + Message, +} from "discord.js"; import { Context } from "./Context"; import { Client } from "./Client"; import { Argument } from "./Argument"; import { currentClient } from "../context"; import { MaybePromise } from "#types/extra.js"; +import { LoggerInstance } from "../utils/Logger"; export interface CommandOptions { name: string; description: string; aliases?: string[]; options?: (ApplicationCommandOptionData | Argument)[]; - slash?: boolean; - prefix?: boolean; } -export abstract class Command { +export class CommandBuilder { public readonly client: Client; + public readonly logger: LoggerInstance; public readonly name: string; public readonly description: string; public readonly aliases: string[]; public readonly options: ApplicationCommandOptionData[]; - public readonly supportsSlash: boolean; - public readonly supportsPrefix: boolean; - - constructor( - options: CommandOptions, - public readonly execute: (ctx: Context) => MaybePromise - ) { + public _supportsSlash: boolean; + public _supportsPrefix: boolean; + _onMessage?: ( + ctx: NonNullable["toJSON"]>> + ) => MaybePromise; + _onInteraction?: ( + ctx: NonNullable["toJSON"]>> + ) => MaybePromise; + constructor(options: CommandOptions) { if (!currentClient) throw new Error("Client is not defined"); - this.client = currentClient; + this.logger = currentClient.logger; this.name = options.name; this.description = options.description; @@ -39,16 +45,37 @@ export abstract class Command { this.options = (options.options ?? []).map((opt) => { return opt instanceof Argument ? opt.toJSON() : opt; }); + this._supportsPrefix = false; + this._supportsSlash = false; - this.supportsSlash = options.slash ?? false; - this.supportsPrefix = options.prefix ?? false; + if (this.client.commands.has(this.name)) + throw new Error(`Command ${this.name} already registered to framework`); + this.client.commands.set(this.name, this); + if (this.aliases.length > 0) { + this.client.aliases.set(this.name, new Set(this.aliases)); + } + this.logger.debug(`Loaded Command ${this.name}(${__filename})`); + } - process.nextTick(() => { - if (this.client.commands.has(this.name)) - throw new Error(`Command ${this.name} already registered to framework`); - this.client.commands.set(this.name, this); + onMessage( + func: ( + ctx: NonNullable["toJSON"]>> + ) => MaybePromise + ) { + this._supportsPrefix = true; + this._onMessage = func; + return this; + } - this.client.aliases.set(this.name, new Set(this.aliases)); - }); + onInteraction( + func: ( + ctx: NonNullable< + ReturnType["toJSON"]> + > + ) => MaybePromise + ) { + this._supportsSlash = true; + this._onInteraction = func; + return this; } } diff --git a/src/structures/Context.ts b/src/structures/Context.ts index bad77c9..d7484de 100644 --- a/src/structures/Context.ts +++ b/src/structures/Context.ts @@ -1,48 +1,61 @@ -import { - Message, - Guild, - User, - InteractionReplyOptions, - MessageReplyOptions, - CommandInteraction, -} from "discord.js"; -import { Client } from "./Client"; - -export type ReplyOptions = - | string - | MessageReplyOptions - | InteractionReplyOptions; - -export class Context { +import { Message, User, ChatInputCommandInteraction } from "discord.js"; +import { Client } from "../structures/Client"; + +type ContextPayload = + T extends ChatInputCommandInteraction + ? { interaction: T; args?: string[] } + : { message: T; args?: string[] }; + +export class Context { public readonly client: Client; - public readonly interaction?: CommandInteraction; - public readonly message?: Message; public readonly args: string[]; + public readonly data: T; - constructor( - client: Client, - data: - | { interaction: CommandInteraction; message?: never; args?: string[] } - | { message: Message; interaction?: never; args?: string[] } - ) { - if (!data.interaction && !data.message) { - throw new Error("Context requires either interaction or message"); - } + constructor(client: Client, payload: ContextPayload) { this.client = client; - this.interaction = data.interaction; - this.message = data.message; - this.args = data.args ?? []; + this.args = payload.args ?? []; + + if ("interaction" in payload) { + this.data = payload.interaction as T; + } else { + this.data = payload.message as T; + } } - public get author(): User | null { - return this.interaction?.user ?? this.message?.author ?? null; + public isInteraction(): this is Context { + return this.data instanceof ChatInputCommandInteraction; + } + + public isMessage(): this is Context { + return this.data instanceof Message; } - public get guild(): Guild | null { - return this.interaction?.guild ?? this.message?.guild ?? null; + public get author(): User | null { + if (this.isInteraction()) { + return this.data.user; + } + if (this.isMessage()) { + return this.data.author; + } + return null; } - public get channel() { - return this.interaction?.channel ?? this.message?.channel; + toJSON() { + const { data, args, author } = this; + + if (this.isInteraction()) { + return { + kind: "interaction" as const, + interaction: data, + author: author, + }; + } + + return { + kind: "message" as const, + message: data as Message, + args: args, + author: author, + }; } } diff --git a/src/structures/Event.ts b/src/structures/Event.ts index cd97f61..b7cf650 100644 --- a/src/structures/Event.ts +++ b/src/structures/Event.ts @@ -2,35 +2,44 @@ import { ClientEvents } from "discord.js"; import { MaybePromise } from "#types/extra.js"; import { currentClient } from "../context"; import { Client } from "./Client"; +import { LoggerInstance } from "../utils/Logger"; type EventArgs = K extends keyof ClientEvents ? ClientEvents[K] : any[]; -export class Event { +export class EventBuilder { public readonly client: Client; + public readonly logger: LoggerInstance; + private handler?: (...args: EventArgs) => MaybePromise; constructor( public readonly name: K, - public readonly execute: ( - this: Event, - ...args: EventArgs - ) => MaybePromise, - public readonly once: boolean = false + public readonly once: boolean = false, + _handler?: (...args: EventArgs) => MaybePromise ) { if (!currentClient) throw new Error("Client is not defined"); this.client = currentClient; + this.logger = currentClient.logger; + if (_handler) this.handler = _handler; + this.logger.debug(`Loaded Event ${this.name}(${__filename})`); - if (!this.name) { - throw new Error(`${this.constructor.name}: Event name is missing!`); - } process.nextTick(() => { - const listener = (...args: any[]) => - this.execute(...(args as EventArgs)); + const wrapper = async (...args: EventArgs) => { + if (this.handler) { + await this.handler(...args); + } + }; + if (this.once) { - this.client.once(this.name as string, listener); + this.client.once(this.name as string, wrapper); } else { - this.client.on(this.name as string, listener); + this.client.on(this.name as string, wrapper); } }); } + + public onExecute(func: (...args: EventArgs) => MaybePromise) { + this.handler = func; + return this; + } } diff --git a/src/utils/Files.ts b/src/utils/Files.ts index b2db2b2..0aed823 100644 --- a/src/utils/Files.ts +++ b/src/utils/Files.ts @@ -5,7 +5,7 @@ export function getFiles(baseDir: string): string[] { return FastGlob.sync(["**/*.ts", "**/*.js"], { cwd: baseDir, absolute: true, - ignore: ["**/*.d.ts", "node_modules/**", ".git/**"], + ignore: ["**/*.d.ts", "node_modules/**", ".git/**", "dist/**"], }); } export function getProjectRoot(): string { diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts index 6dc78ed..1734947 100644 --- a/src/utils/Logger.ts +++ b/src/utils/Logger.ts @@ -1,5 +1,5 @@ +import { LogLevel } from "#types/logger.js"; import chalk from "chalk"; -export type LogLevel = "debug" | "log" | "warn" | "error"; const LOG_LEVEL_PRIORITY: Record = { debug: 0, diff --git a/src/utils/util.ts b/src/utils/util.ts index 02382b5..f9c2a6b 100644 --- a/src/utils/util.ts +++ b/src/utils/util.ts @@ -1,8 +1,17 @@ -export type PrefixOptions = - | { enabled: true; prefix: string } - | { enabled: false } - | string; +import { PrefixOptions } from "#types/client.js"; +import { InteractionResponse, Message } from "discord.js"; +export function deleteMessage( + message: Message | InteractionResponse, + time = 15_000 +) { + return new Promise((r) => { + setTimeout(() => { + message.delete().catch(); + r(); + }, time); + }); +} export function getPrefix(opts: PrefixOptions): string | false { if (typeof opts === "string") { return opts; diff --git a/types/client.d.ts b/types/client.d.ts index a30c2c5..1274a4f 100644 --- a/types/client.d.ts +++ b/types/client.d.ts @@ -1,4 +1,5 @@ import { ClientOptions } from "discord.js"; +import { LogLevel } from "./logger"; export interface FrameworkPaths { events?: string; @@ -14,4 +15,5 @@ export interface FrameworkOptions extends ClientOptions { logLevel?: LogLevel; prefix?: PrefixOptions; paths?: FrameworkPaths; + autoRegiserCommands?: boolean; } diff --git a/types/logger.d.ts b/types/logger.d.ts new file mode 100644 index 0000000..7a7293f --- /dev/null +++ b/types/logger.d.ts @@ -0,0 +1 @@ +export type LogLevel = "debug" | "log" | "warn" | "error"; From 05db04eb68a6d704662c261c5616cea3c77e04d2 Mon Sep 17 00:00:00 2001 From: vrdons Date: Sat, 17 Jan 2026 21:36:39 +0300 Subject: [PATCH 07/11] move changelog.md to changelog branch --- .github/workflows/changelog.yml | 4 ++-- CHANGELOG.md | 34 --------------------------------- 2 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 524ff9c..885a05d 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -2,7 +2,7 @@ name: Generate Changelog on: schedule: - - cron: "0 3 * * 1" + - cron: "0 3 * * *" workflow_dispatch: jobs: @@ -37,4 +37,4 @@ jobs: git diff --cached --quiet && exit 0 git commit -m "Update changelog" - git push origin main + git push origin changelog diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index f11240c..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,34 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## Unreleased - -### Miscellaneous Tasks - -- Weekly update by github-actions[bot] (8e7f6b8) -- Update checkVersionExists script by vrdons (73eef8b) - -## [0.1.0-alpha.1] - 2026-01-15 - -### Bug Fixes - -- Npm build script by vrdons (6f235e3) - -### Features - -- Github Release by vrdons (80f4b1d) - -### Miscellaneous Tasks - -- Base template by vrdons (473e8a6) -- Dont ignore package-lock.json by vrdons (f6d2e9e) -- Add Error handling by vrdons (0525018) - -### Ci - -- Fix checking code by vrdons (310c4e1) -- Fix releasing github by vrdons (6b70d45) -- Move github-release to check by vrdons (9565461) - - From bffcd896f2ffeebb76cc8417e8b6d2dda0617dea Mon Sep 17 00:00:00 2001 From: vrdons Date: Sat, 17 Jan 2026 21:37:56 +0300 Subject: [PATCH 08/11] bump @arox/framework to 0.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d95144c..afa5e14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "framework", - "version": "0.1.0-alpha.2", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "framework", - "version": "0.1.0-alpha.2", + "version": "0.1.0", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.18", diff --git a/package.json b/package.json index 19ffdd4..1cdeb43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "framework", - "version": "0.1.0-alpha.2", + "version": "0.1.0", "description": "", "keywords": [ "discord.js", From d91d2cabdaf61a524f79a471ce37cd2a98de4cb1 Mon Sep 17 00:00:00 2001 From: vrdons Date: Sun, 18 Jan 2026 13:43:47 +0300 Subject: [PATCH 09/11] Fix issues, update logger Co-authored-by: Fhyrox Co-authored-by: Coderabbit --- .oxfmtrc.json | 3 +- .swcrc | 2 +- examples/basic_client/events/ready.js | 8 +- examples/basic_client/index.js | 16 +- package-lock.json | 85 ++---- package.json | 3 +- src/context.ts | 4 + src/events/interaction.ts | 39 +++ src/events/message.ts | 51 ++++ src/events/ready.ts | 8 + src/handler/interaction.ts | 34 --- src/handler/message.ts | 37 --- src/handler/ready.ts | 7 - src/index.ts | 3 +- src/structures/Client.ts | 28 +- src/structures/Command.ts | 70 ++++- src/structures/Event.ts | 68 +++-- src/utils/Files.ts | 12 +- src/utils/Logger.ts | 45 --- src/utils/logger/ILogger.ts | 93 ++++++ src/utils/logger/Logger.ts | 392 ++++++++++++++++++++++++++ src/utils/util.ts | 8 +- types/client.d.ts | 6 +- types/logger.d.ts | 1 - 24 files changed, 759 insertions(+), 264 deletions(-) create mode 100644 src/events/interaction.ts create mode 100644 src/events/message.ts create mode 100644 src/events/ready.ts delete mode 100644 src/handler/interaction.ts delete mode 100644 src/handler/message.ts delete mode 100644 src/handler/ready.ts delete mode 100644 src/utils/Logger.ts create mode 100644 src/utils/logger/ILogger.ts create mode 100644 src/utils/logger/Logger.ts delete mode 100644 types/logger.d.ts diff --git a/.oxfmtrc.json b/.oxfmtrc.json index eabeb37..f63218e 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -12,5 +12,6 @@ "jsxSingleQuote": false, "quoteProps": "as-needed", - "embeddedLanguageFormatting": "auto" + "embeddedLanguageFormatting": "auto", + "endOfLine": "lf" } diff --git a/.swcrc b/.swcrc index 296cd72..6497f33 100644 --- a/.swcrc +++ b/.swcrc @@ -13,5 +13,5 @@ "type": "commonjs" }, "sourceMaps": false, - "minify": true + "minify": false } diff --git a/examples/basic_client/events/ready.js b/examples/basic_client/events/ready.js index c08fc96..78b2120 100644 --- a/examples/basic_client/events/ready.js +++ b/examples/basic_client/events/ready.js @@ -1,7 +1,7 @@ const { EventBuilder } = require("../../../dist"); -new EventBuilder("clientReady", function () { - this.logger.log("Client connected!"); - this.logger.warn(`Current user: ${this.client.user.username}`); - this.logger.warn(`Current prefix: ${this.client.prefix ?? "none"}`); +new EventBuilder("clientReady", false, (context) => { + context.logger.log("Client connected!"); + context.logger.warn(`Current user: ${context.client.user.username}`); + context.logger.warn(`Current prefix: ${context.client.prefix ?? "none"}`); }); diff --git a/examples/basic_client/index.js b/examples/basic_client/index.js index 786243e..f62745f 100644 --- a/examples/basic_client/index.js +++ b/examples/basic_client/index.js @@ -3,24 +3,32 @@ const arox = require("../../dist/index"); const client = new arox.Client({ intents: 37376, prefix: { enabled: true, prefix: "a!" }, - logLevel: "debug", + logger: { + depth: 0, + }, + autoRegisterCommands: false, }); +arox.setClient(client); const command = new arox.CommandBuilder({ name: "arox", description: "Arox test command", slash: true, prefix: true, }); +arox.clearClient(); command .onMessage(function (ctx) { const { message } = ctx; - message.reply("Çalışıyom ulan şurda rahat bırak beni"); + void message.reply("Çalışıyom ulan şurda rahat bırak beni"); }) .onInteraction(function (ctx) { const { interaction } = ctx; - interaction.reply("Çalışıyom ulan şurda rahat bırak beni"); + void interaction.reply("Çalışıyom ulan şurda rahat bırak beni"); }); -client.login(process.env.BOT_TOKEN); +async function init() { + await client.login(process.env.BOT_TOKEN); +} +void init(); diff --git a/package-lock.json b/package-lock.json index afa5e14..d01e2cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,9 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { + "@sapphire/timestamp": "^1.0.5", "@swc/helpers": "^0.5.18", - "chalk": "^4.1.2", + "colorette": "^2.0.20", "fast-glob": "^3.3.3", "lodash": "^4.17.21" }, @@ -1274,6 +1275,16 @@ "npm": ">=7.0.0" } }, + "node_modules/@sapphire/timestamp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@sapphire/timestamp/-/timestamp-1.0.5.tgz", + "integrity": "sha512-oNwWyNdbt5wm4aYZvlHl1+64U3g0xrFmRIHsnER7RgMxNnp/wmAE4yTK2oUHeadg3t4V9iYctPAQCF+aINke4g==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@sigstore/bundle": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", @@ -1949,21 +1960,6 @@ "node": ">= 14" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/arch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", @@ -2204,22 +2200,6 @@ "node": ">=14.16" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -2240,22 +2220,10 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "license": "MIT" }, "node_modules/commander": { @@ -2775,15 +2743,6 @@ "dev": true, "license": "ISC" }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/hosted-git-info": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", @@ -4374,18 +4333,6 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tar": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz", diff --git a/package.json b/package.json index 1cdeb43..4160ea0 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,9 @@ "release:npm": "node scripts/actions/npm.js" }, "dependencies": { + "@sapphire/timestamp": "^1.0.5", "@swc/helpers": "^0.5.18", - "chalk": "^4.1.2", + "colorette": "^2.0.20", "fast-glob": "^3.3.3", "lodash": "^4.17.21" }, diff --git a/src/context.ts b/src/context.ts index d5ca420..d4eaf63 100644 --- a/src/context.ts +++ b/src/context.ts @@ -6,3 +6,7 @@ export let currentClient: Client | null = null; export function setClient(client: Client) { currentClient = client; } + +export function clearClient() { + currentClient = null; +} diff --git a/src/events/interaction.ts b/src/events/interaction.ts new file mode 100644 index 0000000..d23ecd5 --- /dev/null +++ b/src/events/interaction.ts @@ -0,0 +1,39 @@ +import { Events } from "discord.js"; +import { EventBuilder } from "../structures/Event"; +import { Context } from "../structures/Context"; + +new EventBuilder(Events.InteractionCreate, false).onExecute( + async function (context, interaction) { + if (!interaction.isChatInputCommand()) return; + + const command = context.client.commands.get(interaction.commandName); + if (!command || !command.supportsSlash) { + await interaction.reply({ + content: "Command not found or disabled.", + ephemeral: true, + }); + return; + } + + try { + const ctx = new Context(context.client, { interaction }); + if (command._onInteraction) await command._onInteraction(ctx.toJSON()); + } catch (error) { + this.client.logger.error( + `Error executing command ${command.name}:`, + error + ); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } else { + await interaction.reply({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } + } + } +); diff --git a/src/events/message.ts b/src/events/message.ts new file mode 100644 index 0000000..5c1ad4e --- /dev/null +++ b/src/events/message.ts @@ -0,0 +1,51 @@ +import { Events } from "discord.js"; +import { EventBuilder } from "../structures/Event"; +import { Context } from "../structures/Context"; +import { deleteMessage } from "../utils/util"; + +new EventBuilder( + Events.MessageCreate, + false, + async function (context, message) { + if (message.author.bot) return; + const prefix = context.client.prefix; + if ( + typeof prefix !== "string" || + prefix.length === 0 || + !message.content.startsWith(prefix) + ) + return; + + const args = message.content.slice(prefix.length).trim().split(/ +/); + const commandName = args.shift()?.toLowerCase(); + if (!commandName) return; + + const commandAlias = this.client.aliases.findKey((cmd) => + cmd.has(commandName) + ); + + let command = this.client.commands.get(commandAlias ?? commandName); + if (!command || !command.supportsPrefix) { + await message + .reply({ + content: "Command not found or disabled.", + allowedMentions: { repliedUser: false }, + }) + .then(deleteMessage); + return; + } + + try { + const context = new Context(this.client, { message, args }); + this.logger.debug( + `${context.author?.tag ?? "Unknown"} used ${command.name}(message)` + ); + if (command) await command.onMessageCallback(context.toJSON()); + } catch (error) { + this.client.logger.error( + `Error executing command ${command.name}:`, + error + ); + } + } +); diff --git a/src/events/ready.ts b/src/events/ready.ts new file mode 100644 index 0000000..b5433e2 --- /dev/null +++ b/src/events/ready.ts @@ -0,0 +1,8 @@ +import { Events } from "discord.js"; +import { EventBuilder } from "../structures/Event"; + +new EventBuilder(Events.ClientReady).onExecute(function () { + if (this.client.options.autoRegisterCommands) { + this.client.registerCommands(); + } +}); diff --git a/src/handler/interaction.ts b/src/handler/interaction.ts deleted file mode 100644 index 2fb8355..0000000 --- a/src/handler/interaction.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Events } from "discord.js"; -import { EventBuilder } from "../structures/Event"; -import { Context } from "../structures/Context"; - -new EventBuilder(Events.InteractionCreate, false, async function (interaction) { - if (!interaction.isChatInputCommand()) return; - - const command = this.client.commands.get(interaction.commandName); - if (!command || !command._supportsSlash) { - await interaction.reply({ - content: "Command not found or disabled.", - ephemeral: true, - }); - return; - } - - try { - const context = new Context(this.client, { interaction }); - if (command._onInteraction) await command._onInteraction(context.toJSON()); - } catch (error) { - this.client.logger.error(`Error executing command ${command.name}:`, error); - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ - content: "There was an error while executing this command!", - ephemeral: true, - }); - } else { - await interaction.reply({ - content: "There was an error while executing this command!", - ephemeral: true, - }); - } - } -}); diff --git a/src/handler/message.ts b/src/handler/message.ts deleted file mode 100644 index b6d26fd..0000000 --- a/src/handler/message.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Events } from "discord.js"; -import { EventBuilder } from "../structures/Event"; -import { Context } from "../structures/Context"; -import { deleteMessage } from "../utils/util"; - -new EventBuilder(Events.MessageCreate, false, async function (message) { - if (message.author.bot) return; - const prefix = this.client.prefix as string; - if (!message.content.startsWith(prefix)) return; - - const args = message.content.slice(prefix.length).trim().split(/ +/); - const commandName = args.shift()?.toLowerCase(); - if (!commandName) return; - - const commandAlias = this.client.aliases.findKey((cmd) => - cmd.has(commandName) - ); - - let command = this.client.commands.get(commandAlias ?? commandName); - if (!command || !command._supportsPrefix) { - await message - .reply({ - content: "Command not found or disabled.", - allowedMentions: { repliedUser: false }, - }) - .then(deleteMessage); - return; - } - - try { - const context = new Context(this.client, { message, args }); - this.logger.debug(`${context.author} used ${command.name}(message)`); - if (command._onMessage) await command._onMessage(context.toJSON()); - } catch (error) { - this.client.logger.error(`Error executing command ${command.name}:`, error); - } -}); diff --git a/src/handler/ready.ts b/src/handler/ready.ts deleted file mode 100644 index 999d456..0000000 --- a/src/handler/ready.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { EventBuilder } from "../structures/Event"; - -new EventBuilder("clientReady").onExecute(function () { - if (this.client.options.autoRegiserCommands) { - this.client.registerCommands(); - } -}); diff --git a/src/index.ts b/src/index.ts index b218a95..1e433b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,5 +3,6 @@ export * from "./structures/Command"; export * from "./structures/Context"; export * from "./structures/Event"; export * from "./structures/Argument"; -export * from "./utils/Logger"; +export * from "./utils/logger/Logger"; +export * from "./context"; export const version = "[VI]{{version}}[/VI]"; diff --git a/src/structures/Client.ts b/src/structures/Client.ts index 3273c29..d90f32e 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -5,30 +5,30 @@ import { Routes, IntentsBitField, } from "discord.js"; -import { LoggerInstance } from "../utils/Logger"; import { CommandBuilder } from "./Command"; import path from "path"; import { getFiles, getProjectRoot } from "../utils/Files"; import { FrameworkOptions } from "#types/client.js"; import { merge } from "lodash"; -import { setClient } from "../context"; +import { clearClient, setClient } from "../context"; import { getPrefix } from "../utils/util"; +import { Logger } from "../utils/logger/Logger"; const defaultOpts: Omit = { paths: { events: "events", commands: "commands", }, - autoRegiserCommands: true, + autoRegisterCommands: true, }; export class Client< Ready extends boolean = boolean, > extends DiscordClient { - public readonly logger: LoggerInstance; + public readonly logger: Logger; public commands: Collection; public aliases: Collection>; - public readonly prefix: string | boolean; + public readonly prefix: string | false; declare public options: Omit & { intents: IntentsBitField; @@ -36,7 +36,7 @@ export class Client< constructor(opts: FrameworkOptions) { super(merge({}, defaultOpts, opts) as FrameworkOptions); - this.logger = new LoggerInstance(this.options.logLevel ?? "log"); + this.logger = new Logger(opts.logger); this.commands = new Collection(); this.aliases = new Collection(); this.prefix = getPrefix(this.options.prefix ?? { enabled: false }); @@ -48,12 +48,17 @@ export class Client< } setClient(this); - require("../handler/ready"); - require("../handler/interaction"); - if (this.prefix) require("../handler/message"); + require("../events/ready"); + require("../events/interaction"); + if (this.prefix) require("../events/message"); + clearClient(); } async loadFiles(dir: string) { + if (!require("fs").existsSync(dir)) { + this.logger.warn(`Directory not found: ${dir}`); + return; + } const files = getFiles(dir); for (const file of files) { await this.loadFile(file); @@ -66,6 +71,7 @@ export class Client< setClient(this); await require(file); + clearClient(); } catch (error) { this.logger.error(`Error loading file ${file}:`, error); } @@ -84,7 +90,7 @@ export class Client< } const slashCommands = this.commands - .filter((cmd) => cmd._supportsSlash) + .filter((cmd) => cmd.supportsSlash) .map((cmd) => ({ name: cmd.name, description: cmd.description, @@ -100,7 +106,7 @@ export class Client< await rest.put(Routes.applicationCommands(this.application.id), { body: slashCommands, }); - this.logger.warn( + this.logger.info( `Loaded ${slashCommands.length} application (/) commands.` ); } catch (error) { diff --git a/src/structures/Command.ts b/src/structures/Command.ts index 7141183..27a42ed 100644 --- a/src/structures/Command.ts +++ b/src/structures/Command.ts @@ -8,35 +8,45 @@ import { Client } from "./Client"; import { Argument } from "./Argument"; import { currentClient } from "../context"; import { MaybePromise } from "#types/extra.js"; -import { LoggerInstance } from "../utils/Logger"; +import { Logger } from "../utils/logger/Logger"; export interface CommandOptions { name: string; description: string; aliases?: string[]; options?: (ApplicationCommandOptionData | Argument)[]; + slash?: boolean; + prefix?: boolean; } export class CommandBuilder { public readonly client: Client; - public readonly logger: LoggerInstance; + public readonly logger: Logger; public readonly name: string; public readonly description: string; public readonly aliases: string[]; public readonly options: ApplicationCommandOptionData[]; - public _supportsSlash: boolean; - public _supportsPrefix: boolean; - _onMessage?: ( + private _supportsSlash: boolean; + private _supportsPrefix: boolean; + public _onMessage?: ( ctx: NonNullable["toJSON"]>> ) => MaybePromise; - _onInteraction?: ( + public _onInteraction?: ( ctx: NonNullable["toJSON"]>> ) => MaybePromise; + + public get supportsSlash() { + return this._supportsSlash && this._onInteraction; + } + public get supportsPrefix() { + return this._supportsPrefix && this._onMessage; + } constructor(options: CommandOptions) { - if (!currentClient) throw new Error("Client is not defined"); - this.client = currentClient; - this.logger = currentClient.logger; + const client = currentClient; + if (!client) throw new Error("Client is not defined"); + this.client = client; + this.logger = client.logger; this.name = options.name; this.description = options.description; @@ -45,16 +55,48 @@ export class CommandBuilder { this.options = (options.options ?? []).map((opt) => { return opt instanceof Argument ? opt.toJSON() : opt; }); - this._supportsPrefix = false; - this._supportsSlash = false; + this._supportsPrefix = options.prefix ?? false; + this._supportsSlash = options.slash ?? false; + + if (!this._supportsPrefix && !this._supportsSlash) { + throw new Error( + `Command ${this.name} must support either slash or prefix commands.` + ); + } if (this.client.commands.has(this.name)) - throw new Error(`Command ${this.name} already registered to framework`); + throw new Error(`Command name "${this.name}" is already registered.`); + + const existingAliasOwner = this.client.aliases.findKey((aliases) => + aliases.has(this.name) + ); + if (existingAliasOwner) { + throw new Error( + `Command name "${this.name}" is already registered as an alias for command "${existingAliasOwner}".` + ); + } + + for (const alias of this.aliases) { + if (this.client.commands.has(alias)) { + throw new Error( + `Alias "${alias}" is already registered as a command name.` + ); + } + const conflictingCommand = this.client.aliases.findKey((aliases) => + aliases.has(alias) + ); + if (conflictingCommand) { + throw new Error( + `Alias "${alias}" is already registered as an alias for command "${conflictingCommand}".` + ); + } + } + this.client.commands.set(this.name, this); if (this.aliases.length > 0) { this.client.aliases.set(this.name, new Set(this.aliases)); } - this.logger.debug(`Loaded Command ${this.name}(${__filename})`); + this.logger.debug(`Loaded Command ${this.name}`); } onMessage( @@ -62,7 +104,6 @@ export class CommandBuilder { ctx: NonNullable["toJSON"]>> ) => MaybePromise ) { - this._supportsPrefix = true; this._onMessage = func; return this; } @@ -74,7 +115,6 @@ export class CommandBuilder { > ) => MaybePromise ) { - this._supportsSlash = true; this._onInteraction = func; return this; } diff --git a/src/structures/Event.ts b/src/structures/Event.ts index b7cf650..e97c11c 100644 --- a/src/structures/Event.ts +++ b/src/structures/Event.ts @@ -2,44 +2,64 @@ import { ClientEvents } from "discord.js"; import { MaybePromise } from "#types/extra.js"; import { currentClient } from "../context"; import { Client } from "./Client"; -import { LoggerInstance } from "../utils/Logger"; +import { Logger } from "../utils/logger/Logger"; -type EventArgs = - K extends keyof ClientEvents ? ClientEvents[K] : any[]; +type EventArgs = ClientEvents[K]; +type EventHandler = ( + context: EventBuilder, + ...args: EventArgs +) => MaybePromise; -export class EventBuilder { +export class EventBuilder { public readonly client: Client; - public readonly logger: LoggerInstance; - private handler?: (...args: EventArgs) => MaybePromise; + public readonly logger: Logger; + private handler?: EventHandler; + private bound = false; + + private readonly listener = async (...args: EventArgs) => { + if (!this.handler) return; + try { + await this.handler(this, ...args); + } catch (error) { + this.client.logger.error( + `Error executing event ${this.name} (${this.constructor.name}):`, + error + ); + } + }; constructor( public readonly name: K, public readonly once: boolean = false, - _handler?: (...args: EventArgs) => MaybePromise + _handler?: EventHandler ) { if (!currentClient) throw new Error("Client is not defined"); this.client = currentClient; this.logger = currentClient.logger; - if (_handler) this.handler = _handler; - this.logger.debug(`Loaded Event ${this.name}(${__filename})`); - - process.nextTick(() => { - const wrapper = async (...args: EventArgs) => { - if (this.handler) { - await this.handler(...args); - } - }; - - if (this.once) { - this.client.once(this.name as string, wrapper); - } else { - this.client.on(this.name as string, wrapper); - } - }); + + if (_handler) { + this.handler = _handler; + this.register(); + } + + this.logger.debug(`Loaded Event ${this.name} (${__filename})`); + } + + private register(): void { + if (this.bound || !this.handler) return; + + if (this.once) { + this.client.once(this.name as string, this.listener); + } else { + this.client.on(this.name as string, this.listener); + } + + this.bound = true; } - public onExecute(func: (...args: EventArgs) => MaybePromise) { + public onExecute(func: EventHandler) { this.handler = func; + this.register(); return this; } } diff --git a/src/utils/Files.ts b/src/utils/Files.ts index 0aed823..307d934 100644 --- a/src/utils/Files.ts +++ b/src/utils/Files.ts @@ -5,7 +5,17 @@ export function getFiles(baseDir: string): string[] { return FastGlob.sync(["**/*.ts", "**/*.js"], { cwd: baseDir, absolute: true, - ignore: ["**/*.d.ts", "node_modules/**", ".git/**", "dist/**"], + ignore: [ + "**/*.d.ts", + "node_modules/**", + ".git/**", + "dist/**", + "lib/**", + "out/**", + "build/**", + ".next/**", + "coverage/**", + ], }); } export function getProjectRoot(): string { diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts deleted file mode 100644 index 1734947..0000000 --- a/src/utils/Logger.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { LogLevel } from "#types/logger.js"; -import chalk from "chalk"; - -const LOG_LEVEL_PRIORITY: Record = { - debug: 0, - log: 1, - warn: 2, - error: 3, -}; - -export class LoggerInstance { - private level: LogLevel; - - constructor(level: LogLevel = "log") { - this.level = level; - } - - private shouldLog(level: LogLevel): boolean { - return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.level]; - } - - private get timestamp(): string { - return chalk.gray(`[${new Date().toISOString()}]`); - } - - log(...args: any[]) { - if (!this.shouldLog("log")) return; - console.log(this.timestamp, chalk.bgBlue.bold("LOG"), ...args); - } - - warn(...args: any[]) { - if (!this.shouldLog("warn")) return; - console.warn(this.timestamp, chalk.bgYellow.bold("WARN"), ...args); - } - - error(...args: any[]) { - if (!this.shouldLog("error")) return; - console.error(this.timestamp, chalk.bgRed.bold("ERROR"), ...args); - } - - debug(...args: any[]) { - if (!this.shouldLog("debug")) return; - console.debug(this.timestamp, chalk.bgMagenta.bold("DEBUG"), ...args); - } -} diff --git a/src/utils/logger/ILogger.ts b/src/utils/logger/ILogger.ts new file mode 100644 index 0000000..f88efd9 --- /dev/null +++ b/src/utils/logger/ILogger.ts @@ -0,0 +1,93 @@ +//https://github.com/sapphiredev/framework/tree/main/src/lib/utils/logger + +/** + * The logger levels for the {@link ILogger}. + */ +export enum LogLevel { + /** + * The lowest log level, used when calling {@link ILogger.trace}. + */ + Trace = 10, + + /** + * The debug level, used when calling {@link ILogger.debug}. + */ + Debug = 20, + + /** + * The info level, used when calling {@link ILogger.info}. + */ + Info = 30, + + /** + * The warning level, used when calling {@link ILogger.warn}. + */ + Warn = 40, + + /** + * The error level, used when calling {@link ILogger.error}. + */ + Error = 50, + + /** + * The critical level, used when calling {@link ILogger.fatal}. + */ + Fatal = 60, + + /** + * An unknown or uncategorized level. + */ + None = 100, +} + +export interface ILogger { + /** + * Checks whether a level is supported. + * @param level The level to check. + */ + has(level: LogLevel): boolean; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Trace} as level. + * @param values The values to log. + */ + trace(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Debug} as level. + * @param values The values to log. + */ + debug(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Info} as level. + * @param values The values to log. + */ + info(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Warn} as level. + * @param values The values to log. + */ + warn(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Error} as level. + * @param values The values to log. + */ + error(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Fatal} as level. + * @param values The values to log. + */ + fatal(...values: readonly unknown[]): void; + + /** + * Writes the log message given a level and the value(s). + * @param level The log level. + * @param values The values to log. + */ + write(level: LogLevel, ...values: readonly unknown[]): void; +} +export default {}; diff --git a/src/utils/logger/Logger.ts b/src/utils/logger/Logger.ts new file mode 100644 index 0000000..229f603 --- /dev/null +++ b/src/utils/logger/Logger.ts @@ -0,0 +1,392 @@ +//https://github.com/sapphiredev/plugins/tree/main/packages/logger +import { Console } from "console"; +import { inspect, type InspectOptions } from "util"; +import * as colorette from "colorette"; +import type { Color } from "colorette"; +import { Timestamp } from "@sapphire/timestamp"; +import type { ILogger } from "./ILogger"; +import { LogLevel } from "./ILogger"; + +export class Logger implements ILogger { + public level: LogLevel; + public readonly formats: Map; + public readonly join: string; + public readonly depth: number; + public readonly console: Console; + + private static instance: Logger | null = null; + private static readonly LOG_METHODS: ReadonlyMap = + new Map([ + [LogLevel.Trace, "trace"], + [LogLevel.Debug, "debug"], + [LogLevel.Info, "info"], + [LogLevel.Warn, "warn"], + [LogLevel.Error, "error"], + [LogLevel.Fatal, "error"], + ]); + + private static readonly DEFAULT_COLORS: ReadonlyMap = + new Map([ + [LogLevel.Trace, colorette.gray], + [LogLevel.Debug, colorette.magenta], + [LogLevel.Info, colorette.cyan], + [LogLevel.Warn, colorette.yellow], + [LogLevel.Error, colorette.red], + [LogLevel.Fatal, colorette.bgRed], + [LogLevel.None, colorette.white], + ]); + + private static readonly DEFAULT_NAMES: ReadonlyMap = + new Map([ + [LogLevel.Trace, "TRACE"], + [LogLevel.Debug, "DEBUG"], + [LogLevel.Info, "INFO"], + [LogLevel.Warn, "WARN"], + [LogLevel.Error, "ERROR"], + [LogLevel.Fatal, "FATAL"], + [LogLevel.None, ""], + ]); + + constructor(options: LoggerOptions = {}) { + this.level = options.level ?? LogLevel.Info; + this.console = new Console( + options.stdout ?? process.stdout, + options.stderr ?? process.stderr + ); + this.formats = this.createFormatMap( + options.format, + options.defaultFormat ?? options.format?.none ?? {} + ); + this.join = options.join ?? " "; + this.depth = options.depth ?? 0; + } + + static getInstance(): Logger { + if (!this.instance) { + this.instance = new Logger(); + } + return this.instance; + } + + static get stylize(): boolean { + return colorette.isColorSupported; + } + + setLevel(level: LogLevel): void { + this.level = level; + } + + has(level: LogLevel): boolean { + return level >= this.level; + } + + trace(...values: readonly unknown[]): void { + this.write(LogLevel.Trace, ...values); + } + + debug(...values: readonly unknown[]): void { + this.write(LogLevel.Debug, ...values); + } + + info(...values: readonly unknown[]): void { + this.write(LogLevel.Info, ...values); + } + + log(...values: readonly unknown[]): void { + this.write(LogLevel.Info, ...values); + } + + warn(...values: readonly unknown[]): void { + this.write(LogLevel.Warn, ...values); + } + + error(...values: readonly unknown[]): void { + this.write(LogLevel.Error, ...values); + } + + fatal(...values: readonly unknown[]): void { + this.write(LogLevel.Fatal, ...values); + } + + write(level: LogLevel, ...values: readonly unknown[]): void { + if (level < this.level) return; + + const method = Logger.LOG_METHODS.get(level); + const formatter = + this.formats.get(level) ?? this.formats.get(LogLevel.None)!; + const message = formatter.run(this.preprocess(values)); + + switch (method) { + case "trace": + this.console.trace(message); + break; + case "debug": + this.console.debug(message); + break; + case "info": + this.console.info(message); + break; + case "warn": + this.console.warn(message); + break; + case "error": + this.console.error(message); + break; + default: + this.console.log(message); + } + } + + protected preprocess(values: readonly unknown[]): string { + const inspectOptions: InspectOptions = { + colors: colorette.isColorSupported, + depth: this.depth, + }; + return values + .map((value) => + typeof value === "string" ? value : inspect(value, inspectOptions) + ) + .join(this.join); + } + + private createFormatMap( + options: LoggerFormatOptions = {}, + defaults: LoggerLevelOptions + ): Map { + const map = new Map(); + + for (const [level, color] of Logger.DEFAULT_COLORS) { + const name = Logger.DEFAULT_NAMES.get(level); + const levelOptions = + options[this.getLevelKey(level)] ?? + this.createDefaultLevel(defaults, color, name ?? ""); + map.set( + level, + levelOptions instanceof LoggerLevel + ? levelOptions + : new LoggerLevel(levelOptions) + ); + } + + return map; + } + + private createDefaultLevel( + defaults: LoggerLevelOptions, + color: Color, + name: string + ): LoggerLevel { + return new LoggerLevel({ + ...defaults, + timestamp: + defaults.timestamp === null ? null : { ...defaults.timestamp, color }, + infix: name.length ? `${color(name.padEnd(5, " "))} ` : "", + }); + } + + private getLevelKey(level: LogLevel): keyof LoggerFormatOptions { + const keys: Record = { + [LogLevel.Trace]: "trace", + [LogLevel.Debug]: "debug", + [LogLevel.Info]: "info", + [LogLevel.Warn]: "warn", + [LogLevel.Error]: "error", + [LogLevel.Fatal]: "fatal", + [LogLevel.None]: "none", + }; + return keys[level]; + } +} + +export class LoggerStyle { + public readonly style: Color; + + constructor(resolvable: LoggerStyleResolvable = {}) { + if (typeof resolvable === "function") { + this.style = resolvable; + } else { + const styles = this.buildStyleArray(resolvable); + this.style = this.combineStyles(styles); + } + } + + run(value: string | number): string { + return this.style(String(value)); + } + + private buildStyleArray(options: LoggerStyleOptions): Color[] { + const styles: Color[] = []; + + if (options.effects) { + styles.push(...options.effects.map((effect) => colorette[effect])); + } + + if (options.text) { + styles.push(colorette[options.text]); + } + + if (options.background) { + styles.push(colorette[options.background]); + } + + return styles; + } + + private combineStyles(styles: Color[]): Color { + if (styles.length === 0) return colorette.reset; + if (styles.length === 1) return styles[0]; + + return (text: string) => + styles.reduce((result, style) => style(result), text); + } +} + +export class LoggerTimestamp { + public timestamp: Timestamp; + public utc: boolean; + public color: LoggerStyle | null; + public formatter: LoggerTimestampFormatter; + + constructor(options: LoggerTimestampOptions = {}) { + this.timestamp = new Timestamp(options.pattern ?? "YYYY-MM-DD HH:mm:ss"); + this.utc = options.utc ?? false; + this.color = options.color === null ? null : new LoggerStyle(options.color); + this.formatter = options.formatter ?? ((timestamp) => `${timestamp} `); + } + + run(): string { + const date = new Date(); + + const result = this.utc + ? this.timestamp.displayUTC(date) + : this.timestamp.display(date); + + return this.formatter(this.color ? this.color.run(result) : result); + } +} + +export class LoggerLevel { + public timestamp: LoggerTimestamp | null; + public infix: string; + public message: LoggerStyle | null; + + constructor(options: LoggerLevelOptions = {}) { + this.timestamp = + options.timestamp === null + ? null + : new LoggerTimestamp(options.timestamp); + this.infix = options.infix ?? ""; + this.message = + options.message === null ? null : new LoggerStyle(options.message); + } + + run(content: string): string { + const prefix = (this.timestamp?.run() ?? "") + this.infix; + + if (prefix.length) { + const formatter = this.message + ? (line: string) => prefix + this.message?.run(line) + : (line: string) => prefix + line; + return content.split("\n").map(formatter).join("\n"); + } + + return this.message ? this.message.run(content) : content; + } +} + +export default Logger.getInstance(); + +export interface LoggerOptions { + stdout?: NodeJS.WritableStream; + stderr?: NodeJS.WritableStream; + defaultFormat?: LoggerLevelOptions; + format?: LoggerFormatOptions; + level?: LogLevel; + join?: string; + depth?: number; +} + +export interface LoggerFormatOptions { + trace?: LoggerLevelOptions; + debug?: LoggerLevelOptions; + info?: LoggerLevelOptions; + warn?: LoggerLevelOptions; + error?: LoggerLevelOptions; + fatal?: LoggerLevelOptions; + none?: LoggerLevelOptions; +} + +export interface LoggerLevelOptions { + timestamp?: LoggerTimestampOptions | null; + infix?: string; + message?: LoggerStyleResolvable | null; +} + +export interface LoggerTimestampOptions { + pattern?: string; + utc?: boolean; + color?: LoggerStyleResolvable | null; + formatter?: LoggerTimestampFormatter; +} + +export interface LoggerTimestampFormatter { + (timestamp: string): string; +} + +export interface LoggerStyleOptions { + effects?: LoggerStyleEffect[]; + text?: LoggerStyleText; + background?: LoggerStyleBackground; +} + +export type LoggerStyleResolvable = Color | LoggerStyleOptions; + +export enum LoggerStyleEffect { + Reset = "reset", + Bold = "bold", + Dim = "dim", + Italic = "italic", + Underline = "underline", + Inverse = "inverse", + Hidden = "hidden", + Strikethrough = "strikethrough", +} + +export enum LoggerStyleText { + Black = "black", + Red = "red", + Green = "green", + Yellow = "yellow", + Blue = "blue", + Magenta = "magenta", + Cyan = "cyan", + White = "white", + Gray = "gray", + BlackBright = "blackBright", + RedBright = "redBright", + GreenBright = "greenBright", + YellowBright = "yellowBright", + BlueBright = "blueBright", + MagentaBright = "magentaBright", + CyanBright = "cyanBright", + WhiteBright = "whiteBright", +} + +export enum LoggerStyleBackground { + Black = "bgBlack", + Red = "bgRed", + Green = "bgGreen", + Yellow = "bgYellow", + Blue = "bgBlue", + Magenta = "bgMagenta", + Cyan = "bgCyan", + White = "bgWhite", + BlackBright = "bgBlackBright", + RedBright = "bgRedBright", + GreenBright = "bgGreenBright", + YellowBright = "bgYellowBright", + BlueBright = "bgBlueBright", + MagentaBright = "bgMagentaBright", + CyanBright = "bgCyanBright", + WhiteBright = "bgWhiteBright", +} diff --git a/src/utils/util.ts b/src/utils/util.ts index f9c2a6b..99b31b3 100644 --- a/src/utils/util.ts +++ b/src/utils/util.ts @@ -7,7 +7,7 @@ export function deleteMessage( ) { return new Promise((r) => { setTimeout(() => { - message.delete().catch(); + message.delete().catch(() => {}); r(); }, time); }); @@ -17,10 +17,8 @@ export function getPrefix(opts: PrefixOptions): string | false { return opts; } - if (opts && typeof opts === "object") { - if (opts.enabled) { - return opts.prefix; - } + if (opts.enabled && opts.prefix) { + return opts.prefix; } return false; diff --git a/types/client.d.ts b/types/client.d.ts index 1274a4f..4e1055d 100644 --- a/types/client.d.ts +++ b/types/client.d.ts @@ -1,5 +1,5 @@ import { ClientOptions } from "discord.js"; -import { LogLevel } from "./logger"; +import { LoggerOptions } from "../src/utils/logger/Logger"; export interface FrameworkPaths { events?: string; @@ -12,8 +12,8 @@ export type PrefixOptions = | string; export interface FrameworkOptions extends ClientOptions { - logLevel?: LogLevel; + logger?: LoggerOptions; prefix?: PrefixOptions; paths?: FrameworkPaths; - autoRegiserCommands?: boolean; + autoRegisterCommands?: boolean; } diff --git a/types/logger.d.ts b/types/logger.d.ts deleted file mode 100644 index 7a7293f..0000000 --- a/types/logger.d.ts +++ /dev/null @@ -1 +0,0 @@ -export type LogLevel = "debug" | "log" | "warn" | "error"; From 758b9e7bfd0f7c39801985379402d19f534babab Mon Sep 17 00:00:00 2001 From: vrdons Date: Sun, 18 Jan 2026 14:00:22 +0300 Subject: [PATCH 10/11] Fix small mistakes :3 Co-authored-by: Coderabbit --- src/events/interaction.ts | 2 +- src/events/message.ts | 16 ++++++++-------- src/events/ready.ts | 6 +++--- src/structures/Client.ts | 17 ++++++++++------- src/structures/Event.ts | 2 +- src/utils/logger/ILogger.ts | 1 - src/utils/logger/Logger.ts | 2 +- 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/events/interaction.ts b/src/events/interaction.ts index d23ecd5..0f6832b 100644 --- a/src/events/interaction.ts +++ b/src/events/interaction.ts @@ -19,7 +19,7 @@ new EventBuilder(Events.InteractionCreate, false).onExecute( const ctx = new Context(context.client, { interaction }); if (command._onInteraction) await command._onInteraction(ctx.toJSON()); } catch (error) { - this.client.logger.error( + context.client.logger.error( `Error executing command ${command.name}:`, error ); diff --git a/src/events/message.ts b/src/events/message.ts index 5c1ad4e..7778084 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -20,12 +20,12 @@ new EventBuilder( const commandName = args.shift()?.toLowerCase(); if (!commandName) return; - const commandAlias = this.client.aliases.findKey((cmd) => + const commandAlias = context.client.aliases.findKey((cmd) => cmd.has(commandName) ); - let command = this.client.commands.get(commandAlias ?? commandName); - if (!command || !command.supportsPrefix) { + let command = context.client.commands.get(commandAlias ?? commandName); + if (!command || !command.supportsSlash) { await message .reply({ content: "Command not found or disabled.", @@ -36,13 +36,13 @@ new EventBuilder( } try { - const context = new Context(this.client, { message, args }); - this.logger.debug( - `${context.author?.tag ?? "Unknown"} used ${command.name}(message)` + const ctx = new Context(context.client, { message, args }); + context.logger.debug( + `${ctx.author?.tag ?? "Unknown"} used ${command.name}(message)` ); - if (command) await command.onMessageCallback(context.toJSON()); + if (command._onMessage) await command._onMessage(ctx.toJSON()); } catch (error) { - this.client.logger.error( + context.client.logger.error( `Error executing command ${command.name}:`, error ); diff --git a/src/events/ready.ts b/src/events/ready.ts index b5433e2..06317f5 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,8 +1,8 @@ import { Events } from "discord.js"; import { EventBuilder } from "../structures/Event"; -new EventBuilder(Events.ClientReady).onExecute(function () { - if (this.client.options.autoRegisterCommands) { - this.client.registerCommands(); +new EventBuilder(Events.ClientReady).onExecute(async function (context) { + if (context.client.options.autoRegisterCommands) { + await context.client.registerCommands(); } }); diff --git a/src/structures/Client.ts b/src/structures/Client.ts index d90f32e..c7ef552 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -48,10 +48,13 @@ export class Client< } setClient(this); - require("../events/ready"); - require("../events/interaction"); - if (this.prefix) require("../events/message"); - clearClient(); + try { + require("../events/ready"); + require("../events/interaction"); + if (this.prefix) require("../events/message"); + } finally { + clearClient(); + } } async loadFiles(dir: string) { @@ -69,11 +72,11 @@ export class Client< try { delete require.cache[require.resolve(file)]; setClient(this); - - await require(file); - clearClient(); + require(file); } catch (error) { this.logger.error(`Error loading file ${file}:`, error); + } finally { + clearClient(); } } diff --git a/src/structures/Event.ts b/src/structures/Event.ts index e97c11c..af5aa98 100644 --- a/src/structures/Event.ts +++ b/src/structures/Event.ts @@ -42,7 +42,7 @@ export class EventBuilder { this.register(); } - this.logger.debug(`Loaded Event ${this.name} (${__filename})`); + this.logger.debug(`Loaded Event ${String(this.name)}`); } private register(): void { diff --git a/src/utils/logger/ILogger.ts b/src/utils/logger/ILogger.ts index f88efd9..8356b14 100644 --- a/src/utils/logger/ILogger.ts +++ b/src/utils/logger/ILogger.ts @@ -90,4 +90,3 @@ export interface ILogger { */ write(level: LogLevel, ...values: readonly unknown[]): void; } -export default {}; diff --git a/src/utils/logger/Logger.ts b/src/utils/logger/Logger.ts index 229f603..037f4ad 100644 --- a/src/utils/logger/Logger.ts +++ b/src/utils/logger/Logger.ts @@ -58,7 +58,7 @@ export class Logger implements ILogger { options.defaultFormat ?? options.format?.none ?? {} ); this.join = options.join ?? " "; - this.depth = options.depth ?? 0; + this.depth = options.depth ?? 2; } static getInstance(): Logger { From 9c161658ea769bac1c160dd5e8d93bb5ce897cef Mon Sep 17 00:00:00 2001 From: vrdons Date: Sun, 18 Jan 2026 14:05:46 +0300 Subject: [PATCH 11/11] fix wrong commands be listed Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/events/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events/message.ts b/src/events/message.ts index 7778084..7b92263 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -25,7 +25,7 @@ new EventBuilder( ); let command = context.client.commands.get(commandAlias ?? commandName); - if (!command || !command.supportsSlash) { + if (!command || !command.supportsPrefix) { await message .reply({ content: "Command not found or disabled.",