Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/commands/docs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/commands/guides/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const loadChoices = async (): Promise<void> => {

await loadChoices();

export default createCommand(
export const guidesCommand = createCommand(
{
name: 'guides',
description: 'Get a guide on a specific subject',
Expand Down
2 changes: 1 addition & 1 deletion src/commands/ping.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createCommand } from './index.js';

export default createCommand(
export const pingCommand = createCommand(
{
name: 'ping',
description: 'Replies with Pong!',
Expand Down
2 changes: 1 addition & 1 deletion src/commands/tips/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ const contextMenuCommands = Array.from(subjectChoices).map(([key, value]) =>
)
);

export default [slashCommand, ...contextMenuCommands];
export const tipsCommands = [slashCommand, ...contextMenuCommands];
2 changes: 1 addition & 1 deletion src/events/has-var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const hasVarDeclaration = (code: string, language: string): boolean => {
}
};

export default createEvent(
export const hasVarEvent = createEvent(
{
name: Events.MessageCreate,
once: false,
Expand Down
6 changes: 2 additions & 4 deletions src/events/interaction-create.ts
Original file line number Diff line number Diff line change
@@ -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,
},
Expand Down
2 changes: 1 addition & 1 deletion src/events/just-ask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down
2 changes: 1 addition & 1 deletion src/events/ready.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
13 changes: 4 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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);
134 changes: 35 additions & 99 deletions src/util/loaders.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(question) What i don't understand, why do you import the commands and events here, just to manipulate them, and export them?

Original file line number Diff line number Diff line change
@@ -1,114 +1,27 @@
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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(suggestion) i recommend making a barrel file in /commands called index.ts which exports everything. then you don't need to import eveything in here, but instead import everything inside the command dir barrel file.

then you can do

// /command/index.ts
import { pingCommand } from "./ping";
// ... rest

export const commands = [pingCommand, tipsCommands, guidesCommand, docsCommands].flat();

Here you can also then manually remove a command if it should not be used.

Same thing with events then.

and then in this file you would just do

import { commands } from "@/commands"
// ... rest of code
const loadCommands = (): Map<string, Command> => {
// no filter needed, since we know from the import that it only has the neseccary ones
  return new Map(commands.map((command) => [command.data.name, command]));
};

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<T> = (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 <T>(
dir: PathLike,
predicate: StructurePredicate<T>,
recursive = true
): Promise<T[]> => {
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<Map<string, Command>> => {
const commands = await loadStructures<Command>(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<DiscordEvent[]> => {
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<string, Command>
): Promise<void> => {
Expand All @@ -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<void> => {
export const registerEvents = async (client: Client, events: DiscordEvent[]): Promise<void> => {
// Loop through all events
for (const event of events) {
console.log(`Loading event: ${event.name}`);
Expand All @@ -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<string, Command> => {
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(suggestion) as mentioned in my other comment, use a barrel file. in the barrel file you prepare the data as you need it.
so the loadCommands function then becomes this in the barrel file

export const commands = new Map([pingCommand, tipsCommands, guidesCommand, docsCommands].flat().map((command) => [command.data.name, command]));