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 2 // Registries 3 3 RegistryPreCollect = "registry::preCollect", 4 4 RegistryPostCollect = "registry::postCollect", 5 + 6 + // Client 7 + ClientLoop = "client::loop", 5 8 } 6 9 7 10 type LifecycleEventCallback = () => void;
+73 -21
src/core/VoidyClient.ts
··· 9 9 import { Registry } from "./Registry"; 10 10 import { RegistryManager } from "./RegistryManager"; 11 11 import type { Event } from "../loaders/EventLoader"; 12 + import { Lifecycle, LifecycleEvents } from "./Lifecycle"; 12 13 13 14 export class VoidyClient extends Client { 14 15 public registryManager = new RegistryManager(); 16 + private intervalID?: NodeJS.Timeout | NodeJS.Timer; 15 17 16 18 public constructor(options: ClientOptions) { 17 19 super(options); ··· 22 24 ); 23 25 } 24 26 25 - public async start(token: string) { 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() { 26 58 // 1. Prepare and fetch registry manager cache 27 59 await this.registryManager.prepareRegistries(); 28 60 const cache = this.registryManager.getCache(); 29 61 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)}`); 62 + let cacheSize = 0; 63 + for (const cacheValue of Object.values(cache)) { 64 + cacheSize += cacheValue.length; 33 65 } 34 66 35 - // 3. Register event listeners 36 - await this.registerEventHandlers(cache.events); 67 + // 2. Showcase number of loaded cache entities 68 + console.log(`[Voidy] Refreshed RegistryManager cache, with a total of ${cacheSize} items.`); 37 69 38 - // 4. Log in 39 - await this.login(token); 70 + // 3. Clear and re-register events 71 + const events = cache.events; 72 + this.removeAllListeners(); 73 + this.registerEventHandlers(events); 40 74 41 - // 5. Register/Publish commands 42 - await this.registerCommands(cache.commands.flatMap(command => command.data.toJSON())); 75 + // 4. Register all active commands 76 + const commands = cache.commands.flatMap(command => command.data.toJSON()); 77 + this.registerCommands(commands); 43 78 } 44 79 45 - private async registerEventHandlers(events: Event[]) { 46 - console.log(`[Voidy] Registering ${events.length} event listeners: ${events.map(event => event.name).join(", ")}`); 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 + } 47 87 48 - for (const event of events) { 49 - const execute = (...args: unknown[]) => event.execute(this, ...args); 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 + } 50 95 51 - if (event.once) this.once(event.name, execute); 52 - else this.on(event.name, execute); 53 - } 96 + /** 97 + * Stops the client loop 98 + */ 99 + public stopLoop() { 100 + if (!this.intervalID) return; 101 + clearInterval(this.intervalID); 54 102 } 55 103 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(", ")}`); 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); 59 111 60 - await this.application?.commands.set(commands as ApplicationCommandDataResolvable[]); 112 + this.startLoop(); 61 113 } 62 114 }
+5
src/modules/core/module.ts
··· 1 + import { Lifecycle, LifecycleEvents } from "../../core/Lifecycle"; 1 2 import { ButtonLoader } from "../../loaders/ButtonLoader"; 2 3 import { CommandLoader } from "../../loaders/CommandLoader"; 3 4 import { EventLoader } from "../../loaders/EventLoader"; 4 5 import type { Module } from "../../loaders/ModuleLoader"; 6 + 7 + Lifecycle.subscribe(LifecycleEvents.ClientLoop, () => { 8 + console.log("Wait what, wait what..."); 9 + }) 5 10 6 11 export default { 7 12 name: "core",