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

✨ Add ClientLoop lifecycle event and implement a continueous loop in the client

+81 -21
+3
src/core/Lifecycle.ts
··· 2 // Registries 3 RegistryPreCollect = "registry::preCollect", 4 RegistryPostCollect = "registry::postCollect", 5 } 6 7 type LifecycleEventCallback = () => void;
··· 2 // Registries 3 RegistryPreCollect = "registry::preCollect", 4 RegistryPostCollect = "registry::postCollect", 5 + 6 + // Client 7 + ClientLoop = "client::loop", 8 } 9 10 type LifecycleEventCallback = () => void;
+73 -21
src/core/VoidyClient.ts
··· 9 import { Registry } from "./Registry"; 10 import { RegistryManager } from "./RegistryManager"; 11 import type { Event } from "../loaders/EventLoader"; 12 13 export class VoidyClient extends Client { 14 public registryManager = new RegistryManager(); 15 16 public constructor(options: ClientOptions) { 17 super(options); ··· 22 ); 23 } 24 25 - public async start(token: string) { 26 // 1. Prepare and fetch registry manager cache 27 await this.registryManager.prepareRegistries(); 28 const cache = this.registryManager.getCache(); 29 30 - // 2. Showcase all loaded entities based on cache contents 31 - for (const [key, value] of Object.entries(cache)) { 32 - console.log(`[Voidy] Loaded ${value.length} ${key[0]?.toUpperCase() + key.substring(1)}`); 33 } 34 35 - // 3. Register event listeners 36 - await this.registerEventHandlers(cache.events); 37 38 - // 4. Log in 39 - await this.login(token); 40 41 - // 5. Register/Publish commands 42 - await this.registerCommands(cache.commands.flatMap(command => command.data.toJSON())); 43 } 44 45 - private async registerEventHandlers(events: Event[]) { 46 - console.log(`[Voidy] Registering ${events.length} event listeners: ${events.map(event => event.name).join(", ")}`); 47 48 - for (const event of events) { 49 - const execute = (...args: unknown[]) => event.execute(this, ...args); 50 51 - if (event.once) this.once(event.name, execute); 52 - else this.on(event.name, execute); 53 - } 54 } 55 56 - // @Todo: fix this type mess, if possible 57 - private async registerCommands(commands: (RESTPostAPIChatInputApplicationCommandsJSONBody | APIApplicationCommandSubcommandOption | APIApplicationCommandSubcommandGroupOption)[]) { 58 - console.info(`[Voidy] Registering ${commands.length} commands: ${commands.map(command => command.name).join(", ")}`); 59 60 - await this.application?.commands.set(commands as ApplicationCommandDataResolvable[]); 61 } 62 }
··· 9 import { Registry } from "./Registry"; 10 import { RegistryManager } from "./RegistryManager"; 11 import type { Event } from "../loaders/EventLoader"; 12 + import { Lifecycle, LifecycleEvents } from "./Lifecycle"; 13 14 export class VoidyClient extends Client { 15 public registryManager = new RegistryManager(); 16 + private intervalID?: NodeJS.Timeout | NodeJS.Timer; 17 18 public constructor(options: ClientOptions) { 19 super(options); ··· 24 ); 25 } 26 27 + /** 28 + * Register all provided events 29 + * @param events 30 + */ 31 + private async registerEventHandlers(events: Event[]) { 32 + console.log(`[Voidy] Registering ${events.length} event listeners: ${events.map(event => event.name).join(", ")}`); 33 + 34 + for (const event of events) { 35 + const execute = (...args: unknown[]) => event.execute(this, ...args); 36 + 37 + if (event.once) this.once(event.name, execute); 38 + else this.on(event.name, execute); 39 + } 40 + } 41 + 42 + /** 43 + * Register all provided commands to the global discord context 44 + * @param commands 45 + * @todo Fix this type mess, if possible 46 + */ 47 + private async registerCommands(commands: (RESTPostAPIChatInputApplicationCommandsJSONBody | APIApplicationCommandSubcommandOption | APIApplicationCommandSubcommandGroupOption)[]): Promise<void> { 48 + console.info(`[Voidy] Registering ${commands.length} commands: ${commands.map(command => command.name).join(", ")}`); 49 + 50 + await this.application?.commands.set(commands as ApplicationCommandDataResolvable[]); 51 + } 52 + 53 + /** 54 + * Refresh the registry manager and re-register relevant data 55 + * @param token 56 + */ 57 + private async refresh() { 58 // 1. Prepare and fetch registry manager cache 59 await this.registryManager.prepareRegistries(); 60 const cache = this.registryManager.getCache(); 61 62 + let cacheSize = 0; 63 + for (const cacheValue of Object.values(cache)) { 64 + cacheSize += cacheValue.length; 65 } 66 67 + // 2. Showcase number of loaded cache entities 68 + console.log(`[Voidy] Refreshed RegistryManager cache, with a total of ${cacheSize} items.`); 69 70 + // 3. Clear and re-register events 71 + const events = cache.events; 72 + this.removeAllListeners(); 73 + this.registerEventHandlers(events); 74 75 + // 4. Register all active commands 76 + const commands = cache.commands.flatMap(command => command.data.toJSON()); 77 + this.registerCommands(commands); 78 } 79 80 + /** 81 + * Runs reccurring tasks, doesn't loop by itself, though 82 + */ 83 + private async loop() { 84 + // Notifies the "client_loop" lifecycle event 85 + Lifecycle.notify(LifecycleEvents.ClientLoop); 86 + } 87 88 + /** 89 + * Starts the client loop, with a customizable interval 90 + * @param interval 91 + */ 92 + public startLoop(interval: number = 60 * 1000) { 93 + this.intervalID = setInterval(this.loop.bind(this), interval); 94 + } 95 96 + /** 97 + * Stops the client loop 98 + */ 99 + public stopLoop() { 100 + if (!this.intervalID) return; 101 + clearInterval(this.intervalID); 102 } 103 104 + /** 105 + * Launch the bot, additionally starts the client loop 106 + * @param token 107 + */ 108 + public async start(token: string) { 109 + await this.refresh(); 110 + await this.login(token); 111 112 + this.startLoop(); 113 } 114 }
+5
src/modules/core/module.ts
··· 1 import { ButtonLoader } from "../../loaders/ButtonLoader"; 2 import { CommandLoader } from "../../loaders/CommandLoader"; 3 import { EventLoader } from "../../loaders/EventLoader"; 4 import type { Module } from "../../loaders/ModuleLoader"; 5 6 export default { 7 name: "core",
··· 1 + import { Lifecycle, LifecycleEvents } from "../../core/Lifecycle"; 2 import { ButtonLoader } from "../../loaders/ButtonLoader"; 3 import { CommandLoader } from "../../loaders/CommandLoader"; 4 import { EventLoader } from "../../loaders/EventLoader"; 5 import type { Module } from "../../loaders/ModuleLoader"; 6 + 7 + Lifecycle.subscribe(LifecycleEvents.ClientLoop, () => { 8 + console.log("Wait what, wait what..."); 9 + }) 10 11 export default { 12 name: "core",