From 8e6a395e59dff7afa9373d52d4fefad886d09003 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 18:43:55 +0300 Subject: [PATCH 01/15] feat: Switch from dynamic import to a more explicit approach --- src/commands/docs/index.ts | 2 +- src/commands/guides/index.ts | 2 +- src/commands/ping.ts | 2 +- src/commands/tips/index.ts | 2 +- src/events/has-var.ts | 2 +- src/events/interaction-create.ts | 6 +- src/events/just-ask.ts | 2 +- src/events/ready.ts | 2 +- src/index.ts | 13 +-- src/util/loaders.ts | 134 ++++++++----------------------- 10 files changed, 48 insertions(+), 119 deletions(-) diff --git a/src/commands/docs/index.ts b/src/commands/docs/index.ts index 03074f0..b4b5046 100644 --- a/src/commands/docs/index.ts +++ b/src/commands/docs/index.ts @@ -2,7 +2,7 @@ import { ApplicationCommandOptionType } from 'discord.js'; import { createCommands } from '../index.js'; import { type DocProvider, docProviders, executeDocCommand } from './providers.js'; -export default createCommands( +export const docsCommands = createCommands( Object.entries(docProviders).map(([providerKey, providerConfig]) => ({ data: { name: providerKey, diff --git a/src/commands/guides/index.ts b/src/commands/guides/index.ts index f09bc3c..b4db47d 100644 --- a/src/commands/guides/index.ts +++ b/src/commands/guides/index.ts @@ -18,7 +18,7 @@ const loadChoices = async (): Promise => { await loadChoices(); -export default createCommand( +export const guidesCommand = createCommand( { name: 'guides', description: 'Get a guide on a specific subject', diff --git a/src/commands/ping.ts b/src/commands/ping.ts index bffa048..5739a00 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -1,6 +1,6 @@ import { createCommand } from './index.js'; -export default createCommand( +export const pingCommand = createCommand( { name: 'ping', description: 'Replies with Pong!', diff --git a/src/commands/tips/index.ts b/src/commands/tips/index.ts index 3f8e534..be188b6 100644 --- a/src/commands/tips/index.ts +++ b/src/commands/tips/index.ts @@ -92,4 +92,4 @@ const contextMenuCommands = Array.from(subjectChoices).map(([key, value]) => ) ); -export default [slashCommand, ...contextMenuCommands]; +export const tipsCommands = [slashCommand, ...contextMenuCommands]; diff --git a/src/events/has-var.ts b/src/events/has-var.ts index 05ee17d..2f2e627 100644 --- a/src/events/has-var.ts +++ b/src/events/has-var.ts @@ -37,7 +37,7 @@ const hasVarDeclaration = (code: string, language: string): boolean => { } }; -export default createEvent( +export const hasVarEvent = createEvent( { name: Events.MessageCreate, once: false, diff --git a/src/events/interaction-create.ts b/src/events/interaction-create.ts index af4aa6e..7887a02 100644 --- a/src/events/interaction-create.ts +++ b/src/events/interaction-create.ts @@ -1,10 +1,8 @@ import { Events } from 'discord.js'; -import { getCommands } from '../util/loaders.js'; +import { commands } from '../util/loaders.js'; import { createEvent } from './index.js'; -const commands = await getCommands(new URL('../commands/', import.meta.url)); - -export default createEvent( +export const interactionCreateEvent = createEvent( { name: Events.InteractionCreate, }, diff --git a/src/events/just-ask.ts b/src/events/just-ask.ts index 7942d92..f71b12e 100644 --- a/src/events/just-ask.ts +++ b/src/events/just-ask.ts @@ -33,7 +33,7 @@ const [response] = await loadMarkdownOptions<{ name: string }>( const { canRun, reset } = rateLimit(10 * MINUTE); -export default createEvent( +export const justAskEvent = createEvent( { name: Events.MessageCreate, }, diff --git a/src/events/ready.ts b/src/events/ready.ts index a84ff3c..c0eb24b 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,7 +1,7 @@ import { Events } from 'discord.js'; import { createEvent } from './index.js'; -export default createEvent( +export const readyEvent = createEvent( { name: Events.ClientReady, once: true, diff --git a/src/index.ts b/src/index.ts index d6fb793..8c5b255 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { ActivityType, Client, GatewayIntentBits } from 'discord.js'; import { config } from './env.js'; -import { getCommands, getEvents, loadCommands, loadEvents } from './util/loaders.js'; +import { commands, events, registerCommands, registerEvents } from './util/loaders.js'; // Create a new client instance const client = new Client({ @@ -22,13 +22,8 @@ const client = new Client({ }, }); -// Load events and commands -const events = await getEvents(new URL('events/', import.meta.url)); -const commands = await getCommands(new URL('commands/', import.meta.url)); - -// use path utils etc to get the paths - -await loadEvents(client, events); -await loadCommands(client, commands); +// Register events and commands +await registerEvents(client, events); +await registerCommands(client, commands); void client.login(config.discord.token); diff --git a/src/util/loaders.ts b/src/util/loaders.ts index 2904e6d..d99666c 100644 --- a/src/util/loaders.ts +++ b/src/util/loaders.ts @@ -1,8 +1,14 @@ -import type { PathLike } from 'node:fs'; -import { readdir, stat } from 'node:fs/promises'; import type { Client } from 'discord.js'; +import { docsCommands } from '../commands/docs/index.js'; +import { guidesCommand } from '../commands/guides/index.js'; import { type Command, predicate as commandPredicate } from '../commands/index.js'; +import { pingCommand } from '../commands/ping.js'; +import { tipsCommands } from '../commands/tips/index.js'; +import { hasVarEvent } from '../events/has-var.js'; import { type DiscordEvent, predicate as eventPredicate } from '../events/index.js'; +import { interactionCreateEvent } from '../events/interaction-create.js'; +import { justAskEvent } from '../events/just-ask.js'; +import { readyEvent } from '../events/ready.js'; /** * A predicate to check if the structure is valid @@ -10,105 +16,12 @@ import { type DiscordEvent, predicate as eventPredicate } from '../events/index. export type StructurePredicate = (structure: unknown) => structure is T; /** - * Loads all structures in the provided directory - * - * @param dir - The directory to load the structures from - * @param predicate - The predicate to check if the structure is valid - * @param recursive- Whether to load structures recursively - * @returns - */ -export const loadStructures = async ( - dir: PathLike, - predicate: StructurePredicate, - recursive = true -): Promise => { - const statDir = await stat(dir); - - if (!statDir.isDirectory()) { - throw new Error(`The path ${dir} is not a directory`); - } - - // Get all files in the directory - const files = await readdir(dir); - - // Create an empty array to store the structures - const structures: T[] = []; - - // Loop through all files in the directory - for (const file of files) { - const fileUrl = new URL(`${dir}/${file}`, import.meta.url); - - // Get the stats of the file - const fileStat = await stat(fileUrl); - - // If the file is a directory and recursive is true, load the structures in the directory - if (fileStat.isDirectory() && recursive) { - structures.push(...(await loadStructures(fileUrl, predicate, recursive))); - continue; - } - - // If the file is index.js or the file does not end with .js, skip it - if ( - // file === 'index.js' || - // file === 'index.ts' || - !file.endsWith('.js') && - !file.endsWith('.ts') - ) { - continue; - } - - // Import the structure from the file - const { default: structure } = await import(fileUrl.href); - - // If the structure is an array, loop through all structures in the array and check if they are valid - // If the structure is not an array, check if it is valid - if (Array.isArray(structure)) { - for (const str of structure) { - if (predicate(str)) { - structures.push(str); - } - } - } else if (predicate(structure)) { - structures.push(structure); - } - } - return structures; -}; - -/** - * Gets all the commands in the provided directory - * - * @param dir - The directory to load the commands from - * @param recursive - Whether to load commands recursively - * @returns A map of command names to commands - */ -export const getCommands = async ( - dir: PathLike, - recursive = true -): Promise> => { - const commands = await loadStructures(dir, commandPredicate, recursive); - - return new Map(commands.map((command) => [command.data.name, command])); -}; - -/** - * Gets all the events in the provided directory - * - * @param dir - The directory to load the events from - * @param recursive - Whether to load events recursively - * @returns An array of events - */ -export const getEvents = async (dir: PathLike, recursive = true): Promise => { - return loadStructures(dir, eventPredicate, recursive); -}; - -/** - * Loads commands to the Discord API + * Register commands to the Discord API * * @param client - The Discord client * @param commands - A map of command names to commands */ -export const loadCommands = async ( +export const registerCommands = async ( client: Client, commands: Map ): Promise => { @@ -128,12 +41,12 @@ export const loadCommands = async ( }; /** - * Loads events to the Discord client + * Register events to the Discord client * * @param client - The Discord client * @param events - An array of events */ -export const loadEvents = async (client: Client, events: DiscordEvent[]): Promise => { +export const registerEvents = async (client: Client, events: DiscordEvent[]): Promise => { // Loop through all events for (const event of events) { console.log(`Loading event: ${event.name}`); @@ -148,3 +61,26 @@ export const loadEvents = async (client: Client, events: DiscordEvent[]): Promis }); } }; + +/** + * + * @returns An array of events + */ +const loadEvents = (): DiscordEvent[] => { + const events = [hasVarEvent, readyEvent, justAskEvent, interactionCreateEvent].filter( + eventPredicate + ); + return events as DiscordEvent[]; +}; + +/** + * + * @returns A map of command names to commands + */ +const loadCommands = (): Map => { + const commands = [pingCommand, tipsCommands, guidesCommand, docsCommands].flat(); + return new Map(commands.filter(commandPredicate).map((command) => [command.data.name, command])); +}; + +export const commands = loadCommands(); +export const events = loadEvents(); From eff689d62b239d9f6282d2286a63b8943e2e027d Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 18:53:54 +0300 Subject: [PATCH 02/15] fix: Fix deploy.ts to use the exported commands Map --- src/util/deploy.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/util/deploy.ts b/src/util/deploy.ts index 349c2fc..14c00b6 100644 --- a/src/util/deploy.ts +++ b/src/util/deploy.ts @@ -1,12 +1,9 @@ -// deploy.ts -import { URL } from 'node:url'; import { API } from '@discordjs/core/http-only'; import { REST, type RESTPutAPIApplicationCommandsResult } from 'discord.js'; import { config } from '../env.js'; -import { getCommands } from './loaders.js'; +import { commands } from './loaders.js'; export async function deployCommands(): Promise { - const commands = await getCommands(new URL('../commands/', import.meta.url)); const commandData = [...commands.values()].map((command) => command.data); const rest = new REST({ version: '10' }).setToken(config.discord.token); From 9e558f4b5f99266f485cf437542aac403299f258 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:22:11 +0300 Subject: [PATCH 03/15] feat: Add typecheck script and remove zod --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9bf5758..16de36f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "format": "biome format --write .", "check": "biome check .", "check:fix": "biome check --write .", + "typecheck": "tsc --noEmit", "test": "node --test", "prepare": "husky", "pre-commit": "lint-staged" @@ -35,8 +36,7 @@ "lint-staged": "^16.2.1", "tsup": "^8.5.0", "tsx": "^4.20.6", - "typescript": "^5.9.2", - "zod": "^4.1.11" + "typescript": "^5.9.2" }, "lint-staged": { "*.{ts,js}": [ From 6660944cfafe180c20bd4b4d5eaffcf9410e8bbb Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:22:38 +0300 Subject: [PATCH 04/15] feat: remove zod --- pnpm-lock.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c60c2b5..6e0aa3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,9 +36,6 @@ importers: typescript: specifier: ^5.9.2 version: 5.9.2 - zod: - specifier: ^4.1.11 - version: 4.1.11 packages: @@ -970,9 +967,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - zod@4.1.11: - resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} - snapshots: '@biomejs/biome@2.2.4': @@ -1798,5 +1792,3 @@ snapshots: ws@8.18.3: {} yaml@2.8.1: {} - - zod@4.1.11: {} From 4cd0d16df963fb10daac22c3221efbf9b8e79d98 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:23:42 +0300 Subject: [PATCH 05/15] feat: create a file for event types --- src/events/types.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/events/types.ts diff --git a/src/events/types.ts b/src/events/types.ts new file mode 100644 index 0000000..ad6db08 --- /dev/null +++ b/src/events/types.ts @@ -0,0 +1,7 @@ +import type { ClientEvents } from 'discord.js'; + +export type DiscordEvent = { + name: T; + once?: boolean; + execute: (...args: ClientEvents[T]) => Promise | void; +}; From ce1c40fe3bebdab32856f347c1b383ad741ba8ff Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:24:33 +0300 Subject: [PATCH 06/15] feat: create event utils --- src/util/events.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/util/events.ts diff --git a/src/util/events.ts b/src/util/events.ts new file mode 100644 index 0000000..3636182 --- /dev/null +++ b/src/util/events.ts @@ -0,0 +1,37 @@ +import type { Client, ClientEvents } from 'discord.js'; +import type { DiscordEvent } from '../events/types.js'; + +export const createEvent = ( + data: { + name: T; + once?: boolean; + }, + execute: (...args: ClientEvents[T]) => Promise | void +): DiscordEvent => { + return { ...data, execute } satisfies DiscordEvent; +}; + +export const createEvents = ( + events: Array<{ + data: { + name: T; + once?: boolean; + }; + execute: (...args: ClientEvents[T]) => Promise | void; + }> +): DiscordEvent[] => { + return events.map(({ data, execute }) => createEvent(data, execute)); +}; + +export const registerEvents = async (client: Client, events: DiscordEvent[]): Promise => { + for (const event of events) { + console.log(`Loading event: ${event.name}`); + client[event.once ? 'once' : 'on'](event.name, async (...args) => { + try { + await event.execute(...args); + } catch (error) { + console.error(`Error executing event ${event.name}:`, error); + } + }); + } +}; From 9847659b5700eddf837c95f639e2ce955106036b Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:24:57 +0300 Subject: [PATCH 07/15] feat: add events barrel export --- src/events/index.ts | 92 ++++++--------------------------------------- 1 file changed, 12 insertions(+), 80 deletions(-) diff --git a/src/events/index.ts b/src/events/index.ts index 9f4f72b..f4c5fd0 100644 --- a/src/events/index.ts +++ b/src/events/index.ts @@ -1,80 +1,12 @@ -import type { ClientEvents } from 'discord.js'; -import z from 'zod'; -import type { StructurePredicate } from '../util/loaders.js'; - -/** - * Defines the structure of an Event - */ -export type DiscordEvent = { - /** - * The name of the event to listen to - */ - name: T; - /** - * Whether the event should be listened to only once - * - * @default false - */ - once?: boolean; - /** - * The function to execute when the event is triggered - * - * @param args - The arguments passed by the event - */ - execute: (...args: ClientEvents[T]) => Promise | void; - - __isEvent__: true; -}; - -/** - * Defines the schema for an event - */ -export const schema = z.object({ - name: z.string(), - once: z.boolean().optional().default(false), - execute: z.function(), - __isEvent__: z.literal(true), -}); - -/** - * Defines the predicate to check if an object is a valid Event type. - */ -export const predicate: StructurePredicate = (obj: unknown): obj is DiscordEvent => - schema.safeParse(obj).success; - -/** - * - * Creates an event object - * - * @param data - The event data - * @param execute - The function to execute when the event is triggered - * @returns - */ -export const createEvent = ( - data: { - name: T; - once?: boolean; - }, - execute: (...args: ClientEvents[T]) => Promise | void -): DiscordEvent => { - return { ...data, execute, __isEvent__: true } satisfies DiscordEvent; -}; - -/** - * - * Creates multiple events - * - * @param events - An array of event data and execute functions - * @returns - */ -export const createEvents = ( - events: Array<{ - data: { - name: T; - once?: boolean; - }; - execute: (...args: ClientEvents[T]) => Promise | void; - }> -): DiscordEvent[] => { - return events.map(({ data, execute }) => createEvent(data, execute)); -}; +import { hasVarEvent } from './has-var.js'; +import { interactionCreateEvent } from './interaction-create.js'; +import { justAskEvent } from './just-ask.js'; +import { readyEvent } from './ready.js'; +import type { DiscordEvent } from './types.js'; + +export const events = [ + readyEvent, + justAskEvent, + hasVarEvent, + interactionCreateEvent, +] as DiscordEvent[]; From a09150dab9d808ed1d7d961e0c10133c6ec329a0 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:25:21 +0300 Subject: [PATCH 08/15] feat: add commands type --- src/commands/types.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/commands/types.ts diff --git a/src/commands/types.ts b/src/commands/types.ts new file mode 100644 index 0000000..b377ad0 --- /dev/null +++ b/src/commands/types.ts @@ -0,0 +1,6 @@ +import type { CommandInteraction, RESTPostAPIApplicationCommandsJSONBody } from 'discord.js'; + +export type Command = { + data: RESTPostAPIApplicationCommandsJSONBody; + execute: (interaction: CommandInteraction) => Promise | void; +}; From 29ad0859e2bce4f4af81257f139fc5d68976c07b Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:25:40 +0300 Subject: [PATCH 09/15] feat: add commands utils --- src/util/commands.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/util/commands.ts diff --git a/src/util/commands.ts b/src/util/commands.ts new file mode 100644 index 0000000..219f806 --- /dev/null +++ b/src/util/commands.ts @@ -0,0 +1,39 @@ +import type { + Client, + CommandInteraction, + RESTPostAPIApplicationCommandsJSONBody, +} from 'discord.js'; +import type { Command } from '../commands/types.js'; + +export const createCommand = ( + data: RESTPostAPIApplicationCommandsJSONBody, + execute: (interaction: CommandInteraction) => Promise | void +): Command => { + return { data, execute } satisfies Command; +}; + +export const createCommands = ( + commands: Array<{ + data: RESTPostAPIApplicationCommandsJSONBody; + execute: (interaction: CommandInteraction) => Promise | void; + }> +): Command[] => { + return commands.map(({ data, execute }) => createCommand(data, execute)); +}; + +export const registerCommands = async ( + client: Client, + commands: Map +): Promise => { + const commandArray = Array.from(commands.values()).map((cmd) => cmd.data); + + try { + await client.application?.commands.set(commandArray); + commandArray.forEach((cmd) => { + console.log(`Registered command: ${cmd.type}, ${cmd.name}`); + }); + console.log(`Registered ${commandArray.length} commands globally.`); + } catch (error) { + console.error('Error registering commands:', error); + } +}; From 07356b6785d4b2a41f6378ba7642730f3dbf20b0 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:26:15 +0300 Subject: [PATCH 10/15] feat: add commands barrel export --- src/commands/index.ts | 72 ++++++------------------------------------- 1 file changed, 9 insertions(+), 63 deletions(-) diff --git a/src/commands/index.ts b/src/commands/index.ts index f95a9e6..dc97772 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,63 +1,9 @@ -import type { CommandInteraction, RESTPostAPIApplicationCommandsJSONBody } from 'discord.js'; -import { z } from 'zod'; -import type { StructurePredicate } from '../util/loaders.js'; - -export type Command = { - /** - * The data for the command - */ - data: RESTPostAPIApplicationCommandsJSONBody; - /** - * The function execute when the command is called - * - * @param interaction - The interaction that triggered the command - */ - execute: (interaction: CommandInteraction) => Promise | void; - - __isCommand__: true; -}; - -/** - * Defines a schema for a command - */ -export const schema = z.object({ - data: z.custom(), - execute: z.function(), - __isCommand__: z.literal(true), -}); - -/** - * Defines the predicate to check if an object is a Command - */ -export const predicate: StructurePredicate = (obj: unknown): obj is Command => - schema.safeParse(obj).success; - -/** - * - * Creates a command object - * - * @param data - The command data - * @param execute - The function to execute when the command is called - * @returns - */ -export const createCommand = ( - data: RESTPostAPIApplicationCommandsJSONBody, - execute: (interaction: CommandInteraction) => Promise | void -): Command => { - return { data, execute, __isCommand__: true } satisfies Command; -}; - -/** - * Creates multiple commands - * - * @param commands - An array of command data and execute functions - * @returns - */ -export const createCommands = ( - commands: Array<{ - data: RESTPostAPIApplicationCommandsJSONBody; - execute: (interaction: CommandInteraction) => Promise | void; - }> -): Command[] => { - return commands.map(({ data, execute }) => createCommand(data, execute)); -}; +import { docsCommands } from './docs/index.js'; +import { guidesCommand } from './guides/index.js'; +import { pingCommand } from './ping.js'; +import { tipsCommands } from './tips/index.js'; +import type { Command } from './types.js'; + +export const commands = new Map( + [pingCommand, guidesCommand, docsCommands, tipsCommands].flat().map((cmd) => [cmd.data.name, cmd]) +); From 18d612e847d16b9732701557decd254711440c57 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:27:05 +0300 Subject: [PATCH 11/15] refactor: adjust imports paths --- src/commands/docs/index.ts | 2 +- src/commands/guides/index.ts | 2 +- src/commands/ping.ts | 2 +- src/commands/tips/index.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/docs/index.ts b/src/commands/docs/index.ts index b4b5046..f120800 100644 --- a/src/commands/docs/index.ts +++ b/src/commands/docs/index.ts @@ -1,5 +1,5 @@ import { ApplicationCommandOptionType } from 'discord.js'; -import { createCommands } from '../index.js'; +import { createCommands } from '../../util/commands.js'; import { type DocProvider, docProviders, executeDocCommand } from './providers.js'; export const docsCommands = createCommands( diff --git a/src/commands/guides/index.ts b/src/commands/guides/index.ts index b4db47d..a02ca39 100644 --- a/src/commands/guides/index.ts +++ b/src/commands/guides/index.ts @@ -1,7 +1,7 @@ import { ApplicationCommandOptionType, ApplicationCommandType, MessageFlags } from 'discord.js'; import { logToChannel } from '../../util/channel-logging.js'; +import { createCommand } from '../../util/commands.js'; import { loadMarkdownOptions } from '../../util/markdown.js'; -import { createCommand } from '../index.js'; const subjectsDir = new URL('./subjects/', import.meta.url); const subjectChoices = new Map(); diff --git a/src/commands/ping.ts b/src/commands/ping.ts index 5739a00..d0657d3 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -1,4 +1,4 @@ -import { createCommand } from './index.js'; +import { createCommand } from '../util/commands.js'; export const pingCommand = createCommand( { diff --git a/src/commands/tips/index.ts b/src/commands/tips/index.ts index be188b6..0970fd1 100644 --- a/src/commands/tips/index.ts +++ b/src/commands/tips/index.ts @@ -1,7 +1,7 @@ import { ApplicationCommandOptionType, ApplicationCommandType, MessageFlags } from 'discord.js'; import { logToChannel } from '../../util/channel-logging.js'; +import { createCommand } from '../../util/commands.js'; import { loadMarkdownOptions } from '../../util/markdown.js'; -import { createCommand } from '../index.js'; const subjectsDir = new URL('./subjects/', import.meta.url); const subjectChoices = new Map(); From 8f6ddba85b064fed2f4332b5197eac11c0865f1f Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:27:53 +0300 Subject: [PATCH 12/15] refactor: adjust events import paths --- src/events/has-var.ts | 3 +-- src/events/interaction-create.ts | 4 ++-- src/events/just-ask.ts | 2 +- src/events/ready.ts | 2 +- src/util/deploy.ts | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/events/has-var.ts b/src/events/has-var.ts index 2f2e627..951ee1a 100644 --- a/src/events/has-var.ts +++ b/src/events/has-var.ts @@ -1,9 +1,9 @@ import { Events } from 'discord.js'; import ts from 'typescript'; import { MINUTE } from '../constants/time.js'; +import { createEvent } from '../util/events.js'; import { codeBlockRegex } from '../util/message.js'; import { rateLimit } from '../util/rate-limit.js'; -import { createEvent } from './index.js'; const { canRun, reset } = rateLimit(5 * MINUTE); @@ -64,7 +64,6 @@ export const hasVarEvent = createEvent( } } } - return; } ); diff --git a/src/events/interaction-create.ts b/src/events/interaction-create.ts index 7887a02..98a82c5 100644 --- a/src/events/interaction-create.ts +++ b/src/events/interaction-create.ts @@ -1,6 +1,6 @@ import { Events } from 'discord.js'; -import { commands } from '../util/loaders.js'; -import { createEvent } from './index.js'; +import { commands } from '../commands/index.js'; +import { createEvent } from '../util/events.js'; export const interactionCreateEvent = createEvent( { diff --git a/src/events/just-ask.ts b/src/events/just-ask.ts index f71b12e..8029c52 100644 --- a/src/events/just-ask.ts +++ b/src/events/just-ask.ts @@ -1,8 +1,8 @@ import { Events } from 'discord.js'; import { MINUTE } from '../constants/time.js'; +import { createEvent } from '../util/events.js'; import { loadMarkdownOptions } from '../util/markdown.js'; import { rateLimit } from '../util/rate-limit.js'; -import { createEvent } from './index.js'; // Subject patterns (who) const reSubject = `(?:(?:any|some|no|every)(?:one|body)|people|folks|peeps|who)`; diff --git a/src/events/ready.ts b/src/events/ready.ts index c0eb24b..9b5bbcb 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,5 +1,5 @@ import { Events } from 'discord.js'; -import { createEvent } from './index.js'; +import { createEvent } from '../util/events.js'; export const readyEvent = createEvent( { diff --git a/src/util/deploy.ts b/src/util/deploy.ts index 14c00b6..9a089a3 100644 --- a/src/util/deploy.ts +++ b/src/util/deploy.ts @@ -1,7 +1,7 @@ import { API } from '@discordjs/core/http-only'; import { REST, type RESTPutAPIApplicationCommandsResult } from 'discord.js'; +import { commands } from '../commands/index.js'; import { config } from '../env.js'; -import { commands } from './loaders.js'; export async function deployCommands(): Promise { const commandData = [...commands.values()].map((command) => command.data); From 8a9fc4c94f8465817603f450f2e3b6ae3c947246 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:29:11 +0300 Subject: [PATCH 13/15] refactor: update index.ts to accomodate barrel exports and utils --- src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 8c5b255..af17536 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,9 @@ import { ActivityType, Client, GatewayIntentBits } from 'discord.js'; +import { commands } from './commands/index.js'; import { config } from './env.js'; -import { commands, events, registerCommands, registerEvents } from './util/loaders.js'; +import { events } from './events/index.js'; +import { registerCommands } from './util/commands.js'; +import { registerEvents } from './util/events.js'; // Create a new client instance const client = new Client({ From 1baab2172a002163dbf9ac9e3a25ecbcc6147fca Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:30:01 +0300 Subject: [PATCH 14/15] fix: fix bug in rate limiter --- src/util/rate-limit.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/util/rate-limit.ts b/src/util/rate-limit.ts index 95750ed..f2d25b3 100644 --- a/src/util/rate-limit.ts +++ b/src/util/rate-limit.ts @@ -22,8 +22,7 @@ export const rateLimit = (ms: number) => { */ const canRun = () => { const now = Date.now(); - if (now - last >= ms) { - last = now; + if (last === 0 || now - last >= ms) { return true; } return false; From c18073992bd51b4132ae73d5bd1d95c2f99b8eb2 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Sun, 5 Oct 2025 20:30:32 +0300 Subject: [PATCH 15/15] feat: remove loaders files, logic moved to other files --- src/util/loaders.ts | 86 --------------------------------------------- 1 file changed, 86 deletions(-) delete mode 100644 src/util/loaders.ts diff --git a/src/util/loaders.ts b/src/util/loaders.ts deleted file mode 100644 index d99666c..0000000 --- a/src/util/loaders.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { Client } from 'discord.js'; -import { docsCommands } from '../commands/docs/index.js'; -import { guidesCommand } from '../commands/guides/index.js'; -import { type Command, predicate as commandPredicate } from '../commands/index.js'; -import { pingCommand } from '../commands/ping.js'; -import { tipsCommands } from '../commands/tips/index.js'; -import { hasVarEvent } from '../events/has-var.js'; -import { type DiscordEvent, predicate as eventPredicate } from '../events/index.js'; -import { interactionCreateEvent } from '../events/interaction-create.js'; -import { justAskEvent } from '../events/just-ask.js'; -import { readyEvent } from '../events/ready.js'; - -/** - * A predicate to check if the structure is valid - */ -export type StructurePredicate = (structure: unknown) => structure is T; - -/** - * Register commands to the Discord API - * - * @param client - The Discord client - * @param commands - A map of command names to commands - */ -export const registerCommands = async ( - client: Client, - commands: Map -): Promise => { - // Convert the commands map to an array of command data - const commandArray = Array.from(commands.values()).map((cmd) => cmd.data); - - try { - // Register commands globally - await client.application?.commands.set(commandArray); - commandArray.forEach((cmd) => { - console.log(`Registered command: ${cmd.type}, ${cmd.name}`); - }); - console.log(`Registered ${commandArray.length} commands globally.`); - } catch (error) { - console.error('Error registering commands:', error); - } -}; - -/** - * Register events to the Discord client - * - * @param client - The Discord client - * @param events - An array of events - */ -export const registerEvents = async (client: Client, events: DiscordEvent[]): Promise => { - // Loop through all events - for (const event of events) { - console.log(`Loading event: ${event.name}`); - // Register the event - // If the event should be registered once, use "once", otherwise use "on" - client[event.once ? 'once' : 'on'](event.name, async (...args) => { - try { - await event.execute(...args); - } catch (error) { - console.error(`Error executing event ${event.name}:`, error); - } - }); - } -}; - -/** - * - * @returns An array of events - */ -const loadEvents = (): DiscordEvent[] => { - const events = [hasVarEvent, readyEvent, justAskEvent, interactionCreateEvent].filter( - eventPredicate - ); - return events as DiscordEvent[]; -}; - -/** - * - * @returns A map of command names to commands - */ -const loadCommands = (): Map => { - const commands = [pingCommand, tipsCommands, guidesCommand, docsCommands].flat(); - return new Map(commands.filter(commandPredicate).map((command) => [command.data.name, command])); -}; - -export const commands = loadCommands(); -export const events = loadEvents();