···11+// Core
22+export * from "./core/Loader";
33+export * from "./core/ModuleManager";
44+export * from "./core/VoidyClient";
55+66+// Types
77+export * from "./core/types/Button";
88+export * from "./core/types/Command";
99+export * from "./core/types/Event";
1010+export * from "./core/types/Module";
1111+export * from "./core/types/Resource";
1212+1313+// Handlers
1414+export * from "./handlers/ButtonHandler";
1515+export * from "./handlers/CommandHandler";
1616+1717+// Loaders
1818+export * from "./loaders/ButtonLoader";
1919+export * from "./loaders/CommandLoader";
2020+export * from "./loaders/EventLoader";
2121+export * from "./loaders/ModuleLoader";
+16
packages/framework/src/loaders/ButtonLoader.ts
···11+//===============================================
22+// Imports
33+//===============================================
44+import type { Button } from "../core/types/Button";
55+import { Loader } from "../core/Loader";
66+77+//===============================================
88+// ButtonLoader Implementation
99+//===============================================
1010+export class ButtonLoader extends Loader<Button> {
1111+ public id = "button";
1212+ public async validate(data: Partial<Button>) {
1313+ if (!data.id || !data.execute) return null;
1414+ return data as Button;
1515+ }
1616+}
+16
packages/framework/src/loaders/CommandLoader.ts
···11+//===============================================
22+// Imports
33+//===============================================
44+import type { Command } from "../core/types/Command";
55+import { Loader } from "../core/Loader";
66+77+//===============================================
88+// CommandLoader Implementation
99+//===============================================
1010+export class CommandLoader extends Loader<Command> {
1111+ public id = "command";
1212+ public async validate(data: Partial<Command>) {
1313+ if (!data.id || !data.data || !data.execute) return null;
1414+ return data as Command;
1515+ }
1616+}
+16
packages/framework/src/loaders/EventLoader.ts
···11+//===============================================
22+// Imports
33+//===============================================
44+import type { Event } from "../core/types/Event";
55+import { Loader } from "../core/Loader";
66+77+//===============================================
88+// EventLoader Implemenation
99+//===============================================
1010+export class EventLoader extends Loader<Event> {
1111+ public id = "event";
1212+ public async validate(data: Partial<Event>) {
1313+ if (!data.id || !data.name || !data.execute) return null;
1414+ return data as Event;
1515+ }
1616+}
+23
packages/framework/src/loaders/ModuleLoader.ts
···11+//===============================================
22+// Imports
33+//===============================================
44+import type { Module } from "../core/types/Module";
55+import { Loader } from "../core/Loader"
66+77+//===============================================
88+// ModuleLoader Implementation
99+//===============================================
1010+export class ModuleLoader extends Loader<Module> {
1111+ public id = "module";
1212+ public async validate(data: Partial<Module>) {
1313+ if (
1414+ !data.id ||
1515+ !data.name ||
1616+ !data.description ||
1717+ !data.author ||
1818+ !data.exports
1919+ ) return null;
2020+2121+ return data as Module;
2222+ }
2323+}
···11+//===============================================
22+// Imports
33+//===============================================
14import { Glob } from "bun";
2533-interface ILoader<T> {
66+//===============================================
77+// Loader Definition
88+//===============================================
99+interface ILoader<T extends object> {
410 id: string
511 cache: T[]
612 source: string
···1016 getJSON: () => T[]
1117}
12181313-export class Loader<T extends object> implements ILoader<T> {
1414- public id = "loader";
1919+//===============================================
2020+// Loader Implementation
2121+//===============================================
2222+export abstract class Loader<T extends object> implements ILoader<T> {
2323+ public abstract id: string;
1524 public cache: T[] = [];
1616- public source;
2525+ public source: string;
17261827 public constructor(source: string) {
1928 if (!source) throw new Error("Class of type Loader was initialized without the *required* source parameter.");
···5766 /**
5867 * Validates a singular element during data collection, and returns whatever should be written to the cache.
5968 */
6060- public async validate(data: Partial<T>): Promise<T | null> {
6161- return null;
6262- }
6969+ public abstract validate(data: Partial<T>): Promise<T | null>;
63706471 /**
6572 * Returns the JSON-ified contents of the loader cache
-83
src/core/Registry.ts
···11-import { ButtonLoader, type Button } from "../loaders/ButtonLoader"
22-import { CommandLoader, type Command } from "../loaders/CommandLoader"
33-import { EventLoader, type Event } from "../loaders/EventLoader"
44-import { ModuleLoader, type Module } from "../loaders/ModuleLoader"
55-66-export enum RegistryCacheKey {
77- Events = "events",
88- Modules = "modules",
99- Buttons = "buttons",
1010- Commands = "commands",
1111-}
1212-1313-export type RegistryCache = {
1414- events: Event[],
1515- modules: Module[],
1616- buttons: Button[],
1717- commands: Command[],
1818- [x: string]: object[],
1919-};
2020-2121-export interface IRegistry {
2222- id: string
2323- active: boolean
2424- dataSource: string
2525-2626- cache: RegistryCache;
2727-2828- collectModules: () => Promise<void>
2929- processModules: () => Promise<void>
3030-3131- activate: () => Promise<void>
3232- deactivate: () => Promise<void>
3333-}
3434-3535-export class Registry implements IRegistry {
3636- public id: string;
3737- public active = false;
3838- public dataSource: string;
3939-4040- // Initialize cache stores
4141- public cache: RegistryCache = {
4242- events: [],
4343- modules: [],
4444- buttons: [],
4545- commands: [],
4646- }
4747-4848- public constructor(id: string, dataSource: string) {
4949- this.id = id;
5050- this.dataSource = dataSource;
5151- }
5252-5353- /** Collect modules from specified dataSource directory */
5454- public async collectModules() {
5555- // Collect modules and bundle their JSON contents into an array.
5656- const moduleLoader = new ModuleLoader(this.dataSource);
5757- const modules = (await moduleLoader.collect()).getJSON();
5858-5959- // Merge all modules into the store.
6060- this.cache.modules = modules;
6161- }
6262-6363- /** Process exports of all collected modules */
6464- public async processModules() {
6565- for (const module of this.cache.modules) {
6666- for (const item of module.exports) {
6767- const loader = new item.loader(item.source);
6868- await loader.collect();
6969-7070- // Mape loader output to correct cache key
7171- this.cache[`${loader.id}s`] = [...loader.getJSON()];
7272- }
7373- }
7474- }
7575-7676- public async activate() {
7777- this.active = true;
7878- }
7979-8080- public async deactivate() {
8181- this.active = false;
8282- }
8383-}
-62
src/core/RegistryManager.ts
···11-import { Registry, type RegistryCache } from "./Registry"
22-33-interface IRegistryManager {
44- addRegistry: (newRegistry: Registry) => boolean
55- getRegistry: (registryID: string) => Registry | null
66-77- prepareRegistries: () => void
88- getCache: () => RegistryCache
99-}
1010-1111-export class RegistryManager implements IRegistryManager {
1212- private registries = new Map<string, Registry>();
1313-1414- public addRegistry(newRegistry: Registry) {
1515- // Append registry to registries array if ID is unique,
1616- // else return false.
1717- if (this.registries.get(newRegistry.id)) return false;
1818- this.registries.set(newRegistry.id, newRegistry);
1919-2020- // The registry was added successfully, therefore return true.
2121- return true;
2222- }
2323-2424- public getRegistry(registryID: string) {
2525- const registry = this.registries.get(registryID);
2626-2727- if (!registry) return null;
2828- return registry;
2929- }
3030-3131- public async prepareRegistries() {
3232- for (const registry of this.registries.values()) {
3333- // 1. Collecting required registry data
3434- console.info(`[Voidy] Collecting registry data: ${registry.dataSource}`);
3535- await registry.collectModules();
3636-3737- // 2. Processing collected registry modules and their exports
3838- console.info(`[Voidy] Processing registry data: ${registry.dataSource}`);
3939- await registry.processModules();
4040-4141- // 3. Activating registry
4242- console.info(`[Voidy] Activating registry: ${registry.dataSource}`);
4343- await registry.activate();
4444- }
4545- }
4646-4747- public getCache() {
4848- let cache: RegistryCache = {
4949- events: [],
5050- modules: [],
5151- commands: [],
5252- buttons: [],
5353- };
5454-5555- // Combine all registry caches
5656- for (const registry of this.registries.values()) {
5757- if (registry.active) cache = { ...cache, ...registry.cache };
5858- }
5959-6060- return cache;
6161- }
6262-}
-114
src/core/VoidyClient.ts
···11-import {
22- type RESTPostAPIChatInputApplicationCommandsJSONBody,
33- type APIApplicationCommandSubcommandGroupOption,
44- type APIApplicationCommandSubcommandOption,
55- type ApplicationCommandDataResolvable,
66- type ClientOptions,
77- Client,
88-} from "discord.js";
99-import { Registry } from "./Registry";
1010-import { RegistryManager } from "./RegistryManager";
1111-import type { Event } from "../loaders/EventLoader";
1212-import { Lifecycle, LifecycleEvents } from "./Lifecycle";
1313-1414-export class VoidyClient extends Client {
1515- public registryManager = new RegistryManager();
1616- private intervalID?: NodeJS.Timeout | NodeJS.Timer;
1717-1818- public constructor(options: ClientOptions) {
1919- super(options);
2020-2121- // Add the core registry to our registry manager
2222- this.registryManager.addRegistry(
2323- new Registry('core', `${process.cwd()}/src/modules`)
2424- );
2525- }
2626-2727- /**
2828- * Register all provided events
2929- * @param events
3030- */
3131- private async registerEventHandlers(events: Event[]) {
3232- console.log(`[Voidy] Registering ${events.length} event listeners: ${events.map(event => event.name).join(", ")}`);
3333-3434- for (const event of events) {
3535- const execute = (...args: unknown[]) => event.execute(this, ...args);
3636-3737- if (event.once) this.once(event.name, execute);
3838- else this.on(event.name, execute);
3939- }
4040- }
4141-4242- /**
4343- * Register all provided commands to the global discord context
4444- * @param commands
4545- * @todo Fix this type mess, if possible
4646- */
4747- private async registerCommands(commands: (RESTPostAPIChatInputApplicationCommandsJSONBody | APIApplicationCommandSubcommandOption | APIApplicationCommandSubcommandGroupOption)[]): Promise<void> {
4848- console.info(`[Voidy] Registering ${commands.length} commands: ${commands.map(command => command.name).join(", ")}`);
4949-5050- await this.application?.commands.set(commands as ApplicationCommandDataResolvable[]);
5151- }
5252-5353- /**
5454- * Refresh the registry manager and re-register relevant data
5555- * @param token
5656- */
5757- private async refresh() {
5858- // 1. Prepare and fetch registry manager cache
5959- await this.registryManager.prepareRegistries();
6060- const cache = this.registryManager.getCache();
6161-6262- let cacheSize = 0;
6363- for (const cacheValue of Object.values(cache)) {
6464- cacheSize += cacheValue.length;
6565- }
6666-6767- // 2. Showcase number of loaded cache entities
6868- console.log(`[Voidy] Refreshed RegistryManager cache, with a total of ${cacheSize} items.`);
6969-7070- // 3. Clear and re-register events
7171- const events = cache.events;
7272- this.removeAllListeners();
7373- this.registerEventHandlers(events);
7474-7575- // 4. Register all active commands
7676- const commands = cache.commands.flatMap(command => command.data.toJSON());
7777- this.registerCommands(commands);
7878- }
7979-8080- /**
8181- * Runs reccurring tasks, doesn't loop by itself, though
8282- */
8383- private async loop() {
8484- // Notifies the "client_loop" lifecycle event
8585- Lifecycle.notify(LifecycleEvents.ClientLoop);
8686- }
8787-8888- /**
8989- * Starts the client loop, with a customizable interval
9090- * @param interval
9191- */
9292- public startLoop(interval: number = 60 * 1000) {
9393- this.intervalID = setInterval(this.loop.bind(this), interval);
9494- }
9595-9696- /**
9797- * Stops the client loop
9898- */
9999- public stopLoop() {
100100- if (!this.intervalID) return;
101101- clearInterval(this.intervalID);
102102- }
103103-104104- /**
105105- * Launch the bot, additionally starts the client loop
106106- * @param token
107107- */
108108- public async start(token: string) {
109109- await this.refresh();
110110- await this.login(token);
111111-112112- this.startLoop();
113113- }
114114-}
···11import { MessageFlags, SlashCommandBuilder } from "discord.js";
22-import type { Command } from "../../../loaders/CommandLoader";
22+import type { Command } from "voidy-framework";
3344export default {
55+ id: "ping",
56 data: new SlashCommandBuilder()
67 .setName("ping")
78 .setDescription("View the websocket ping between Discord and the Bot."),