A powerful and extendable Discord bot, with it's own module system :3 thevoid.cafe/projects/voidy

✨ Implement core Loader concept and example EventLoader

+80 -8
+2
docs/architecture/top-level.md
··· 12 12 13 13 Each loader additionally implements an asynchronous `collect` method for initial data collection. 14 14 15 + Additionally, each loader implements their own asynchronous `validate` method, which is invoked within `collect`, to validate the contents of a file, before adding it to the Loader store. 16 + 15 17 Finally, loaders provide various means of exporting data in supported formats, through methods like `getJSON`, `getCSV` and more... 16 18 17 19 ### Event loader
+48 -6
src/core/Loader.ts
··· 1 - export interface ILoader { 2 - store: object[]; 1 + import { Glob } from "bun"; 2 + 3 + export interface ILoader<T> { 4 + dataSource: string, 5 + store: T[]; 3 6 4 7 collect: () => Promise<ThisType<this>>, 5 - getJSON: () => object, 8 + validate: (data: Partial<T>) => Promise<T | null>, 9 + getJSON: () => T[], 6 10 } 7 11 8 - export class Loader implements ILoader { 9 - public store = []; 12 + export class Loader<T extends object> implements ILoader<T> { 13 + public dataSource; 14 + public store: T[] = []; 15 + 16 + public constructor(dataSource: string) { 17 + if (!dataSource) throw new Error("Class of type Loader was initialized without the *required* dataSource parameter."); 18 + 19 + this.dataSource = dataSource; 20 + } 10 21 22 + /** 23 + * Recursively collects data from a directory based on the directory specificed in dataSource property. 24 + */ 11 25 public async collect() { 26 + const glob = new Glob(`**.ts`); 27 + const iterator = glob.scan(this.dataSource); 28 + 29 + for await (const path of iterator) { 30 + let moduleDefault: T | null; 31 + 32 + try { 33 + const module = (await import(`${this.dataSource}/${path}`)); 34 + moduleDefault = module.default; 35 + 36 + if (!moduleDefault) continue; 37 + } catch { 38 + continue; 39 + } 40 + 41 + const final = await this.validate(moduleDefault); 42 + if (!final) continue; 43 + 44 + this.store.push(final); 45 + } 46 + 12 47 return this; 13 48 } 14 49 50 + /** 51 + * Validates a singular element during data collection, and returns whatever should be written to the store. 52 + */ 53 + public async validate(data: Partial<T>): Promise<T | null> { 54 + return null; 55 + } 56 + 15 57 public getJSON() { 16 - return {}; 58 + return this.store; 17 59 } 18 60 };
+10
src/events/ready.ts
··· 1 + import { Events } from "discord.js"; 2 + import type { IEvent } from "../loaders/EventLoader"; 3 + 4 + export default { 5 + name: Events.ClientReady, 6 + once: false, 7 + execute: () => { 8 + 9 + } 10 + } as IEvent;
+5 -2
src/index.ts
··· 1 1 import { GatewayIntentBits } from "discord.js" 2 2 import { VoidyClient } from "./core/VoidyClient" 3 + import { EventLoader } from "./loaders/EventLoader"; 4 + import { join } from "node:path"; 3 5 4 6 // Client initialization with intents and stuff... 5 7 const client = new VoidyClient({ ··· 10 12 if (!Bun.env.BOT_TOKEN) throw new Error("[Voidy] Missing bot token"); 11 13 client.start(Bun.env.BOT_TOKEN); 12 14 13 - // @Todo: Remove after core registry implementation is complete 14 - console.log(client.registries[0]); 15 + // @Todo: Remove after event and command loader implementation is complete 16 + const eventLoader = await new EventLoader(join(__dirname, "events")).collect(); 17 + console.log(eventLoader);
+15
src/loaders/EventLoader.ts
··· 1 + import { Events } from "discord.js"; 2 + import { Loader } from "../core/Loader"; 3 + 4 + export interface IEvent { 5 + name: Events, 6 + once?: boolean, 7 + execute: () => void, 8 + } 9 + 10 + export class EventLoader extends Loader<IEvent> { 11 + public override async validate(data: Partial<IEvent>) { 12 + if (!data.name || !data.execute) return null; 13 + return data as IEvent; 14 + } 15 + }