A powerful and extendable Discord bot, with it's own module system :3
thevoid.cafe/projects/voidy
1import { Glob } from "bun";
2import { basename, dirname } from "node:path";
3import type { Resource, ModuleDef } from "../types/Resource";
4
5interface ModuleEntry {
6 module: ModuleDef;
7 dir: string;
8}
9
10export async function scanModules(path: string): Promise<ModuleEntry[]> {
11 const glob = new Glob("*/module.ts");
12 const entries: ModuleEntry[] = [];
13
14 for await (const file of glob.scan(path)) {
15 try {
16 const fullPath = `${path}/${file}`;
17 const imported = await import(fullPath);
18 const mod = imported.default;
19 if (!mod?.id || !mod?.name || !mod?.description) continue;
20
21 entries.push({
22 module: { id: mod.id, name: mod.name, description: mod.description },
23 dir: dirname(fullPath),
24 });
25 } catch {
26 continue;
27 }
28 }
29
30 return entries;
31}
32
33const CONVENTIONAL_DIRS = ["commands", "events", "buttons", "modals"];
34
35export async function scanResources(
36 module: ModuleDef,
37 dir: string,
38 additionalDirs: string[] = [],
39): Promise<Resource[]> {
40 const resources: Resource[] = [];
41 const glob = new Glob("**/*.ts");
42 const dirs = [...CONVENTIONAL_DIRS, ...additionalDirs];
43
44 for (const conventionalDir of dirs) {
45 const scanDir = `${dir}/${conventionalDir}`;
46 const inferredType = basename(conventionalDir).replace(/s$/, ""); // "commands" → "command"
47
48 try {
49 for await (const file of glob.scan(scanDir)) {
50 try {
51 const fullPath = `${scanDir}/${file}`;
52 const imported = await import(fullPath);
53 const exported = imported.default;
54 if (!exported) continue;
55
56 const resource: Resource = {
57 ...exported,
58 type: exported.type ?? inferredType,
59 origin: module.id,
60 createdAt: new Date(),
61 };
62
63 resources.push(resource);
64 } catch {
65 continue;
66 }
67 }
68 } catch {
69 // Directory doesn't exist — skip
70 }
71 }
72
73 return resources;
74}