+2
README.md
+2
README.md
+76
-12
bun.lock
+76
-12
bun.lock
···
4
4
"workspaces": {
5
5
"": {
6
6
"name": "voidydiscord",
7
+
"devDependencies": {
8
+
"oxfmt": "^0.17.0",
9
+
},
7
10
},
8
11
"packages/bot": {
9
12
"name": "@voidy/bot",
10
13
"version": "0.1.0",
11
14
"dependencies": {
12
15
"@voidy/framework": "workspace:*",
13
-
"discord.js": "^14.21.0",
16
+
"discord.js": "^14.25.1",
17
+
"mongoose": "^9.0.1",
14
18
},
15
19
"devDependencies": {
16
20
"@types/bun": "latest",
17
21
},
18
22
"peerDependencies": {
19
-
"typescript": "^5",
23
+
"typescript": "^5.9.2",
20
24
},
21
25
},
22
26
"packages/framework": {
23
27
"name": "@voidy/framework",
24
28
"version": "0.1.0",
25
29
"dependencies": {
26
-
"discord.js": "^14.21.0",
30
+
"discord.js": "^14.25.1",
27
31
},
28
32
"devDependencies": {
29
33
"@types/bun": "latest",
30
34
},
31
35
"peerDependencies": {
32
-
"typescript": "^5",
36
+
"typescript": "^5.9.2",
33
37
},
34
38
},
35
39
},
36
40
"packages": {
37
-
"@discordjs/builders": ["@discordjs/builders@1.11.2", "", { "dependencies": { "@discordjs/formatters": "^0.6.1", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.1", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A=="],
41
+
"@discordjs/builders": ["@discordjs/builders@1.13.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w=="],
38
42
39
43
"@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
40
44
41
-
"@discordjs/formatters": ["@discordjs/formatters@0.6.1", "", { "dependencies": { "discord-api-types": "^0.38.1" } }, "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg=="],
45
+
"@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="],
42
46
43
-
"@discordjs/rest": ["@discordjs/rest@2.5.1", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw=="],
47
+
"@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="],
44
48
45
-
"@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
49
+
"@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="],
46
50
47
51
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
48
52
53
+
"@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.0", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-ZHzx7Z3rdlWL1mECydvpryWN/ETXJiCxdgQKTAH+djzIPe77HdnSizKBDi1TVDXZjXyOj2IqEG/vPw71ULF06w=="],
54
+
55
+
"@oxfmt/darwin-arm64": ["@oxfmt/darwin-arm64@0.17.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OMv0tOb+xiwSZKjYbM6TwMSP5QwFJlBGQmEsk98QJ30sHhdyC//0UvGKuR0KZuzZW4E0+k0rHDmos1Z5DmBEkA=="],
56
+
57
+
"@oxfmt/darwin-x64": ["@oxfmt/darwin-x64@0.17.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-trzidyzryKIdL/cLCYU9IwprgJegVBUrz1rqzOMe5is+qdgH/RxTCvhYUNFzxRHpil3g4QUYd2Ja831tc5Nehg=="],
58
+
59
+
"@oxfmt/linux-arm64-gnu": ["@oxfmt/linux-arm64-gnu@0.17.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-KlwzidgvHznbUaaglZT1goTS30osTV553pfbKve9B1PyTDkluNDfm/polOaf3SVLN7wL/NNLFZRMupvJ1eJXAw=="],
60
+
61
+
"@oxfmt/linux-arm64-musl": ["@oxfmt/linux-arm64-musl@0.17.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-+tbYJTocF4BNLaQQbc/xrBWTNgiU6zmYeF4NvRDxuuQjDOnmUZPn0EED3PZBRJyg4/YllhplHDo8x+gfcb9G3A=="],
62
+
63
+
"@oxfmt/linux-x64-gnu": ["@oxfmt/linux-x64-gnu@0.17.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pEmv7zJIw2HpnA4Tn1xrfJNGi2wOH2+usT14Pkvf/c5DdB+pOir6k/5jzfe70+V3nEtmtV9Lm+spndN/y6+X7A=="],
64
+
65
+
"@oxfmt/linux-x64-musl": ["@oxfmt/linux-x64-musl@0.17.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+DrFSCZWyFdtEAWR5xIBTV8GX0RA9iB+y7ZlJPRAXrNG8TdBY9vc7/MIGolIgrkMPK4mGMn07YG/qEyPY+iKaw=="],
66
+
67
+
"@oxfmt/win32-arm64": ["@oxfmt/win32-arm64@0.17.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-FoUZRR7mVpTYIaY/qz2BYwzqMnL+HsUxmMWAIy6nl29UEkDgxNygULJ4rIGY4/Axne41fhtldLrSGBOpwNm3jA=="],
68
+
69
+
"@oxfmt/win32-x64": ["@oxfmt/win32-x64@0.17.0", "", { "os": "win32", "cpu": "x64" }, "sha512-fBIcUpHmCwf3leWlo0cYwLb9Pd2mzxQlZYJX9dD9nylPvsxOnsy9fmsaflpj34O0JbQJN3Y0SRkoaCcHHlxFww=="],
70
+
49
71
"@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
50
72
51
73
"@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
52
74
53
75
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
54
76
55
-
"@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
77
+
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
56
78
57
79
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
80
+
81
+
"@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="],
82
+
83
+
"@types/whatwg-url": ["@types/whatwg-url@13.0.0", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q=="],
58
84
59
85
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
60
86
···
64
90
65
91
"@voidy/framework": ["@voidy/framework@workspace:packages/framework"],
66
92
67
-
"bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
93
+
"bson": ["bson@7.0.0", "", {}, "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw=="],
68
94
69
-
"discord-api-types": ["discord-api-types@0.38.18", "", {}, "sha512-ygenySjZKUaBf5JT8BNhZSxLzwpwdp41O0wVroOTu/N2DxFH7dxYTZUSnFJ6v+/2F3BMcnD47PC47u4aLOLxrQ=="],
95
+
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
96
+
97
+
"discord-api-types": ["discord-api-types@0.38.37", "", {}, "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w=="],
70
98
71
-
"discord.js": ["discord.js@14.21.0", "", { "dependencies": { "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.1", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-U5w41cEmcnSfwKYlLv5RJjB8Joa+QJyRwIJz5i/eg+v2Qvv6EYpCRhN9I2Rlf0900LuqSDg8edakUATrDZQncQ=="],
99
+
"discord.js": ["discord.js@14.25.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="],
72
100
73
101
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
102
+
103
+
"kareem": ["kareem@3.0.0", "", {}, "sha512-RKhaOBSPN8L7y4yAgNhDT2602G5FD6QbOIISbjN9D6mjHPeqeg7K+EB5IGSU5o81/X2Gzm3ICnAvQW3x3OP8HA=="],
74
104
75
105
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
76
106
···
78
108
79
109
"magic-bytes.js": ["magic-bytes.js@1.12.1", "", {}, "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA=="],
80
110
111
+
"memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="],
112
+
113
+
"mongodb": ["mongodb@7.0.0", "", { "dependencies": { "@mongodb-js/saslprep": "^1.3.0", "bson": "^7.0.0", "mongodb-connection-string-url": "^7.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.806.0", "@mongodb-js/zstd": "^7.0.0", "gcp-metadata": "^7.0.1", "kerberos": "^7.0.0", "mongodb-client-encryption": ">=7.0.0 <7.1.0", "snappy": "^7.3.2", "socks": "^2.8.6" }, "optionalPeers": ["@aws-sdk/credential-providers", "@mongodb-js/zstd", "gcp-metadata", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg=="],
114
+
115
+
"mongodb-connection-string-url": ["mongodb-connection-string-url@7.0.0", "", { "dependencies": { "@types/whatwg-url": "^13.0.0", "whatwg-url": "^14.1.0" } }, "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og=="],
116
+
117
+
"mongoose": ["mongoose@9.0.1", "", { "dependencies": { "kareem": "3.0.0", "mongodb": "~7.0", "mpath": "0.9.0", "mquery": "6.0.0", "ms": "2.1.3", "sift": "17.1.3" } }, "sha512-aHPfQx2YX5UwAmMVud7OD4lIz9AEO4jI+oDnRh3lPZq9lrKTiHmOzszVffDMyQHXvrf4NXsJ34kpmAhyYAZGbw=="],
118
+
119
+
"mpath": ["mpath@0.9.0", "", {}, "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="],
120
+
121
+
"mquery": ["mquery@6.0.0", "", {}, "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw=="],
122
+
123
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
124
+
125
+
"oxfmt": ["oxfmt@0.17.0", "", { "optionalDependencies": { "@oxfmt/darwin-arm64": "0.17.0", "@oxfmt/darwin-x64": "0.17.0", "@oxfmt/linux-arm64-gnu": "0.17.0", "@oxfmt/linux-arm64-musl": "0.17.0", "@oxfmt/linux-x64-gnu": "0.17.0", "@oxfmt/linux-x64-musl": "0.17.0", "@oxfmt/win32-arm64": "0.17.0", "@oxfmt/win32-x64": "0.17.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-12Rmq2ub61rUZ3Pqnsvmo99rRQ6hQJwQsjnFnbvXYLMrlIsWT6SFVsrjAkBBrkXXSHv8ePIpKQ0nZph5KDrOqw=="],
126
+
127
+
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
128
+
129
+
"sift": ["sift@17.1.3", "", {}, "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="],
130
+
131
+
"sparse-bitfield": ["sparse-bitfield@3.0.3", "", { "dependencies": { "memory-pager": "^1.0.2" } }, "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ=="],
132
+
133
+
"tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="],
134
+
81
135
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
82
136
83
137
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
···
88
142
89
143
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
90
144
145
+
"webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
146
+
147
+
"whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="],
148
+
91
149
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
92
150
93
151
"@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
94
152
95
153
"@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
154
+
155
+
"@discordjs/ws/@discordjs/rest": ["@discordjs/rest@2.5.1", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw=="],
156
+
157
+
"@discordjs/ws/@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
158
+
159
+
"@discordjs/ws/discord-api-types": ["discord-api-types@0.38.18", "", {}, "sha512-ygenySjZKUaBf5JT8BNhZSxLzwpwdp41O0wVroOTu/N2DxFH7dxYTZUSnFJ6v+/2F3BMcnD47PC47u4aLOLxrQ=="],
96
160
}
97
161
}
+3
package.json
+3
package.json
+10
packages/bot/docker-compose.yml
+10
packages/bot/docker-compose.yml
···
1
+
# Don't change these development defaults, use a docker-compose.override.yml file instead :)
2
+
services:
3
+
mongo:
4
+
image: mongo:latest
5
+
container_name: voidy_db
6
+
ports:
7
+
- "27017:27017"
8
+
environment:
9
+
MONGO_INITDB_ROOT_USERNAME: voidy
10
+
MONGO_INITDB_ROOT_PASSWORD: voidy
+19
-18
packages/bot/package.json
+19
-18
packages/bot/package.json
···
1
1
{
2
-
"name": "@voidy/bot",
3
-
"version": "0.1.0",
4
-
"module": "src/index.ts",
5
-
"type": "module",
6
-
"private": true,
7
-
"scripts": {
8
-
"dev": "bun --watch ."
9
-
},
10
-
"devDependencies": {
11
-
"@types/bun": "latest"
12
-
},
13
-
"peerDependencies": {
14
-
"typescript": "^5"
15
-
},
16
-
"dependencies": {
17
-
"@voidy/framework": "workspace:*",
18
-
"discord.js": "^14.21.0"
19
-
}
2
+
"name": "@voidy/bot",
3
+
"version": "0.1.0",
4
+
"module": "src/index.ts",
5
+
"type": "module",
6
+
"private": true,
7
+
"scripts": {
8
+
"dev": "bun --watch ."
9
+
},
10
+
"devDependencies": {
11
+
"@types/bun": "latest"
12
+
},
13
+
"peerDependencies": {
14
+
"typescript": "^5.9.2"
15
+
},
16
+
"dependencies": {
17
+
"@voidy/framework": "workspace:*",
18
+
"discord.js": "^14.25.1",
19
+
"mongoose": "^9.0.1"
20
+
}
20
21
}
+12
-3
packages/bot/src/index.ts
+12
-3
packages/bot/src/index.ts
···
1
-
import { GatewayIntentBits } from "discord.js"
1
+
import { GatewayIntentBits } from "discord.js";
2
2
import { VoidyClient } from "@voidy/framework";
3
+
import { connect } from "mongoose";
3
4
4
5
// Client initialization with intents and stuff...
5
6
const client = new VoidyClient({
6
-
intents: [GatewayIntentBits.Guilds],
7
-
})
7
+
intents: [GatewayIntentBits.Guilds],
8
+
});
9
+
10
+
// Database URI validation and connection check
11
+
if (!Bun.env.DB_URI) throw new Error("[Voidy] Missing database URI");
12
+
await connect(Bun.env.DB_URI).then(() => {
13
+
console.log("Connected to database");
14
+
}).catch((error) => {
15
+
console.error("Failed to connect to database:", error);
16
+
});
8
17
9
18
// Token validation and client start
10
19
if (!Bun.env.BOT_TOKEN) throw new Error("[Voidy] Missing bot token");
+20
-23
packages/bot/src/modules/core/commands/api/httpcat.ts
+20
-23
packages/bot/src/modules/core/commands/api/httpcat.ts
···
2
2
import type { Command } from "@voidy/framework";
3
3
4
4
export default {
5
-
id: "api.httpcat",
6
-
data: new SlashCommandSubcommandBuilder()
7
-
.setName("httpcat")
8
-
.setDescription("Display a cat from the https://http.cat API.")
9
-
.addStringOption(option => option
10
-
.setName("code")
11
-
.setDescription("The desired HTTP status code.")
12
-
.setRequired(true)
13
-
)
14
-
.addBooleanOption(option => option
15
-
.setName("ephemeral")
16
-
.setDescription("Whether to publicly share the bot response")
17
-
),
5
+
id: "api.httpcat",
6
+
data: new SlashCommandSubcommandBuilder()
7
+
.setName("httpcat")
8
+
.setDescription("Display a cat from the https://http.cat API.")
9
+
.addStringOption((option) =>
10
+
option.setName("code").setDescription("The desired HTTP status code.").setRequired(true),
11
+
)
12
+
.addBooleanOption((option) =>
13
+
option.setName("ephemeral").setDescription("Whether to publicly share the bot response"),
14
+
),
18
15
19
-
execute: async (interaction, _client) => {
20
-
const { options } = interaction;
16
+
execute: async (interaction, _client) => {
17
+
const { options } = interaction;
21
18
22
-
const httpCode = options.getString("code");
23
-
const ephemeral = options.getBoolean("ephemeral") ?? true;
19
+
const httpCode = options.getString("code");
20
+
const ephemeral = options.getBoolean("ephemeral") ?? true;
24
21
25
-
await interaction.reply({
26
-
files: [`https://http.cat/${httpCode}.jpg`],
27
-
flags: ephemeral ? [MessageFlags.Ephemeral] : []
28
-
});
29
-
}
30
-
} as Command
22
+
await interaction.reply({
23
+
files: [`https://http.cat/${httpCode}.jpg`],
24
+
flags: ephemeral ? [MessageFlags.Ephemeral] : [],
25
+
});
26
+
},
27
+
} as Command;
+11
-11
packages/bot/src/modules/core/commands/ping.ts
+11
-11
packages/bot/src/modules/core/commands/ping.ts
···
2
2
import type { Command } from "@voidy/framework";
3
3
4
4
export default {
5
-
id: "ping",
6
-
data: new SlashCommandBuilder()
7
-
.setName("ping")
8
-
.setDescription("View the websocket ping between Discord and the Bot."),
5
+
id: "ping",
6
+
data: new SlashCommandBuilder()
7
+
.setName("ping")
8
+
.setDescription("View the websocket ping between Discord and the Bot."),
9
9
10
-
execute: async (interaction, client) => {
11
-
await interaction.reply({
12
-
content: `The current websocket ping is at ${client.ws.ping}`,
13
-
flags: [MessageFlags.Ephemeral]
14
-
});
15
-
}
16
-
} as Command
10
+
execute: async (interaction, client) => {
11
+
await interaction.reply({
12
+
content: `The current websocket ping is at ${client.ws.ping}`,
13
+
flags: [MessageFlags.Ephemeral],
14
+
});
15
+
},
16
+
} as Command;
+46
-44
packages/bot/src/modules/core/events/interactionCreate.ts
+46
-44
packages/bot/src/modules/core/events/interactionCreate.ts
···
1
1
import { Events, MessageFlags, type Interaction } from "discord.js";
2
2
import {
3
-
ChatInputCommandHandler,
4
-
ButtonHandler,
5
-
type VoidyClient,
6
-
type Event
3
+
ChatInputCommandHandler,
4
+
ButtonHandler,
5
+
type VoidyClient,
6
+
type Event,
7
7
} from "@voidy/framework";
8
8
9
9
export default {
10
-
id: "interactionCreate",
11
-
name: Events.InteractionCreate,
12
-
execute: async (client: VoidyClient, interaction: Interaction) => {
13
-
if (interaction.isChatInputCommand() && interaction.isCommand()) {
14
-
// Set the top-level command name
15
-
let commandId = interaction.commandName;
10
+
id: "interactionCreate",
11
+
name: Events.InteractionCreate,
12
+
execute: async (client: VoidyClient, interaction: Interaction) => {
13
+
if (interaction.isChatInputCommand() && interaction.isCommand()) {
14
+
// Set the top-level command name
15
+
let commandId = interaction.commandName;
16
16
17
-
// Try to get a subgroup first
18
-
const subgroup = interaction.options.getSubcommandGroup(false);
19
-
if (subgroup) commandId += `.${subgroup}`;
17
+
// Try to get a subgroup first
18
+
const subgroup = interaction.options.getSubcommandGroup(false);
19
+
if (subgroup) commandId += `.${subgroup}`;
20
20
21
-
// Then subcommand (or subcommand in a group)
22
-
const subcommand = interaction.options.getSubcommand(false);
23
-
if (subcommand) commandId += `.${subcommand}`;
21
+
// Then subcommand (or subcommand in a group)
22
+
const subcommand = interaction.options.getSubcommand(false);
23
+
if (subcommand) commandId += `.${subcommand}`;
24
24
25
-
const command = client.commands.get(commandId);
25
+
const command = client.commands.get(commandId);
26
26
27
-
if (!command) return interaction.reply({
28
-
content: `Sorry, but the command ${interaction.commandName} could not be located in my command cache >:3`,
29
-
flags: [MessageFlags.Ephemeral]
30
-
});
27
+
if (!command)
28
+
return interaction.reply({
29
+
content: `Sorry, but the command ${interaction.commandName} could not be located in my command cache >:3`,
30
+
flags: [MessageFlags.Ephemeral],
31
+
});
31
32
32
-
ChatInputCommandHandler.invoke(interaction, command, client);
33
-
} else if (interaction.isButton()) {
34
-
// Filter the client button cache to locate the invoked button
35
-
const button = client.buttons.get(interaction.customId);
33
+
ChatInputCommandHandler.invoke(interaction, command, client);
34
+
} else if (interaction.isButton()) {
35
+
// Filter the client button cache to locate the invoked button
36
+
const button = client.buttons.get(interaction.customId);
36
37
37
-
if (!button) return interaction.reply({
38
-
content: `Sorry, but the button ${interaction.customId} could not be located in my button cache >:3`,
39
-
flags: [MessageFlags.Ephemeral]
40
-
});
38
+
if (!button)
39
+
return interaction.reply({
40
+
content: `Sorry, but the button ${interaction.customId} could not be located in my button cache >:3`,
41
+
flags: [MessageFlags.Ephemeral],
42
+
});
41
43
42
-
ButtonHandler.invoke(interaction, button, client);
43
-
} else {
44
-
let dmChannel = interaction.user.dmChannel;
44
+
ButtonHandler.invoke(interaction, button, client);
45
+
} else {
46
+
let dmChannel = interaction.user.dmChannel;
45
47
46
-
// Attempt DM channel creation, if not found.
47
-
if (!dmChannel) {
48
-
dmChannel = await interaction.user.createDM();
49
-
}
48
+
// Attempt DM channel creation, if not found.
49
+
if (!dmChannel) {
50
+
dmChannel = await interaction.user.createDM();
51
+
}
50
52
51
-
// If the DM channel is still not available, give up.
52
-
if (!dmChannel || !dmChannel.isSendable()) return;
53
+
// If the DM channel is still not available, give up.
54
+
if (!dmChannel || !dmChannel.isSendable()) return;
53
55
54
-
dmChannel.send({
55
-
content: `Sorry, but your last interaction wasn't successful and has been logged as an error case, for debugging purposes.\n\nIf you have any additional information to share with us, please communicate with the bot within this DM channel, and we will get in contact.\n\nThank you for understanding, and have a great day :3`,
56
-
})
57
-
}
58
-
}
59
-
} as Event
56
+
dmChannel.send({
57
+
content: `Sorry, but your last interaction wasn't successful and has been logged as an error case, for debugging purposes.\n\nIf you have any additional information to share with us, please communicate with the bot within this DM channel, and we will get in contact.\n\nThank you for understanding, and have a great day :3`,
58
+
});
59
+
}
60
+
},
61
+
} as Event;
+10
-10
packages/bot/src/modules/core/events/ready.ts
+10
-10
packages/bot/src/modules/core/events/ready.ts
···
2
2
import { ActivityType, Events } from "discord.js";
3
3
4
4
export default {
5
-
id: "ready",
6
-
name: Events.ClientReady,
7
-
once: true,
8
-
execute: async (client: VoidyClient) => {
9
-
console.log("THE BOT IS READY >:3");
5
+
id: "clientReady",
6
+
name: Events.ClientReady,
7
+
once: true,
8
+
execute: async (client: VoidyClient) => {
9
+
console.log("THE BOT IS READY >:3");
10
10
11
-
client.user?.setActivity({
12
-
name: `${client.guilds.cache.size} guilds :3`,
13
-
type: ActivityType.Watching
14
-
});
15
-
}
11
+
client.user?.setActivity({
12
+
name: `${client.guilds.cache.size} guilds :3`,
13
+
type: ActivityType.Watching,
14
+
});
15
+
},
16
16
} as Event;
+15
-20
packages/bot/src/modules/core/module.ts
+15
-20
packages/bot/src/modules/core/module.ts
···
1
-
import {
2
-
ButtonLoader,
3
-
CommandLoader,
4
-
EventLoader,
5
-
type Module
6
-
} from "@voidy/framework";
1
+
import { CommandLoader, EventLoader, type Module } from "@voidy/framework";
7
2
8
3
export default {
9
-
id: "core",
10
-
name: "Core",
11
-
description: "The core feature set of the bot, required for command handling to work.",
12
-
author: "jokiller230",
4
+
id: "core",
5
+
name: "Core",
6
+
description: "Initializes the bot and registers all commands.",
7
+
author: "jokiller230",
13
8
14
-
exports: [
15
-
{
16
-
source: `${import.meta.dir}/events`,
17
-
loader: EventLoader,
18
-
},
19
-
{
20
-
source: `${import.meta.dir}/commands`,
21
-
loader: CommandLoader,
22
-
}
23
-
]
9
+
exports: [
10
+
{
11
+
source: `${import.meta.dir}/events`,
12
+
loader: EventLoader,
13
+
},
14
+
{
15
+
source: `${import.meta.dir}/commands`,
16
+
loader: CommandLoader,
17
+
},
18
+
],
24
19
} as Module;
+54
packages/bot/src/modules/user/commands/consent/grant.ts
+54
packages/bot/src/modules/user/commands/consent/grant.ts
···
1
+
import { MessageFlags, SlashCommandBuilder } from "discord.js";
2
+
import type { Command } from "@voidy/framework";
3
+
import { UserConfig } from "../../schemas/UserConfig";
4
+
5
+
export default {
6
+
id: "user.consent.update",
7
+
data: new SlashCommandBuilder()
8
+
.setName("Update Consent")
9
+
.setDescription("Update your user consent choices.")
10
+
.addStringOption((option) =>
11
+
option
12
+
.setName("category")
13
+
.setDescription("Choose which category of consent to update.")
14
+
.setRequired(true)
15
+
.addChoices(
16
+
{ name: "Storage", value: "storage" },
17
+
{ name: "Statistics", value: "statistics" },
18
+
),
19
+
)
20
+
.addBooleanOption((option) =>
21
+
option
22
+
.setName("consent")
23
+
.setDescription("Choose whether to grant or revoke consent.")
24
+
.setRequired(true),
25
+
),
26
+
27
+
execute: async (interaction, _client) => {
28
+
const { options } = interaction;
29
+
const category = options.getString("category", true);
30
+
const consent = options.getBoolean("consent", true);
31
+
32
+
let userConfig = await UserConfig.findById(interaction.user.id);
33
+
if (!userConfig) {
34
+
userConfig = new UserConfig({ id: interaction.user.id });
35
+
}
36
+
37
+
switch (category) {
38
+
case "storage":
39
+
userConfig.consent.storage = consent;
40
+
break;
41
+
case "statistics":
42
+
userConfig.consent.statistics = consent;
43
+
break;
44
+
default:
45
+
console.error(`Invalid category: ${category}`);
46
+
}
47
+
48
+
await userConfig.save();
49
+
await interaction.reply({
50
+
content: `Updated consent for \`${category}\` to \`${consent ? "granted" : "revoked"}\`.`,
51
+
flags: [MessageFlags.Ephemeral],
52
+
});
53
+
},
54
+
} as Command;
+14
packages/bot/src/modules/user/module.ts
+14
packages/bot/src/modules/user/module.ts
···
1
+
import { CommandLoader } from "@voidy/framework";
2
+
3
+
export default {
4
+
id: "user",
5
+
name: "User Management Services",
6
+
description:
7
+
"Provides various resources for working with users, like global preferences and consent statements.",
8
+
exports: [
9
+
{
10
+
src: `${import.meta.dir}/commands`,
11
+
loader: CommandLoader,
12
+
},
13
+
],
14
+
};
+12
packages/bot/src/modules/user/schemas/UserConfig.ts
+12
packages/bot/src/modules/user/schemas/UserConfig.ts
···
1
+
import { Schema, model } from "mongoose";
2
+
3
+
export const UserConfig = model(
4
+
"UserConfig",
5
+
new Schema({
6
+
id: { type: String, required: true, primary: true },
7
+
consent: { type: Object, required: true, default: {
8
+
storage: { type: Boolean, required: true, default: true },
9
+
statistics: { type: Boolean, required: true, default: true },
10
+
} },
11
+
}),
12
+
);
+2
-4
packages/bot/tsconfig.json
+2
-4
packages/bot/tsconfig.json
+20
-20
packages/framework/package.json
+20
-20
packages/framework/package.json
···
1
1
{
2
-
"name": "@voidy/framework",
3
-
"version": "0.1.0",
4
-
"module": "src/index.ts",
5
-
"type": "module",
6
-
"private": true,
7
-
"exports": {
8
-
".": {
9
-
"import": "./src/index.ts",
10
-
"require": "./src/index.cjs"
11
-
}
12
-
},
13
-
"devDependencies": {
14
-
"@types/bun": "latest"
15
-
},
16
-
"peerDependencies": {
17
-
"typescript": "^5"
18
-
},
19
-
"dependencies": {
20
-
"discord.js": "^14.21.0"
21
-
}
2
+
"name": "@voidy/framework",
3
+
"version": "0.1.0",
4
+
"module": "src/index.ts",
5
+
"type": "module",
6
+
"private": true,
7
+
"exports": {
8
+
".": {
9
+
"import": "./src/index.ts",
10
+
"require": "./src/index.cjs"
11
+
}
12
+
},
13
+
"devDependencies": {
14
+
"@types/bun": "latest"
15
+
},
16
+
"peerDependencies": {
17
+
"typescript": "^5.9.2"
18
+
},
19
+
"dependencies": {
20
+
"discord.js": "^14.25.1"
21
+
}
22
22
}
+53
-50
packages/framework/src/core/Loader.ts
+53
-50
packages/framework/src/core/Loader.ts
···
7
7
// Loader Definition
8
8
//===============================================
9
9
interface ILoader<T extends object> {
10
-
id: string
11
-
cache: T[]
12
-
source: string
10
+
id: string;
11
+
cache: T[];
12
+
source: string;
13
13
14
-
collect: () => Promise<ThisType<this>>
15
-
validate: (data: Partial<T>) => Promise<T | null>
16
-
getJSON: () => T[]
14
+
collect: () => Promise<ThisType<this>>;
15
+
validate: (data: Partial<T>) => Promise<T | null>;
16
+
getJSON: () => T[];
17
17
}
18
18
19
19
//===============================================
20
20
// Loader Implementation
21
21
//===============================================
22
22
export abstract class Loader<T extends object> implements ILoader<T> {
23
-
public abstract id: string;
24
-
public cache: T[] = [];
25
-
public source: string;
23
+
public abstract id: string;
24
+
public cache: T[] = [];
25
+
public source: string;
26
26
27
-
public constructor(source: string) {
28
-
if (!source) throw new Error("Class of type Loader was initialized without the *required* source parameter.");
27
+
public constructor(source: string) {
28
+
if (!source)
29
+
throw new Error(
30
+
"Class of type Loader was initialized without the *required* source parameter.",
31
+
);
29
32
30
-
this.source = source;
31
-
}
33
+
this.source = source;
34
+
}
32
35
33
-
/**
34
-
* Recursively collects data from a directory based on the path specificed in dataSource property.
35
-
*/
36
-
public async collect() {
37
-
const glob = new Glob(`**/**.ts`);
38
-
const iterator = glob.scan(this.source);
36
+
/**
37
+
* Recursively collects data from a directory based on the path specificed in dataSource property.
38
+
*/
39
+
public async collect() {
40
+
const glob = new Glob(`**/**.ts`);
41
+
const iterator = glob.scan(this.source);
39
42
40
-
try {
41
-
for await (const path of iterator) {
42
-
let moduleDefault: T | null;
43
+
try {
44
+
for await (const path of iterator) {
45
+
let moduleDefault: T | null;
43
46
44
-
try {
45
-
const module = (await import(`${this.source}/${path}`));
46
-
moduleDefault = module.default;
47
+
try {
48
+
const module = await import(`${this.source}/${path}`);
49
+
moduleDefault = module.default;
47
50
48
-
if (!moduleDefault) continue;
49
-
} catch {
50
-
continue;
51
-
}
51
+
if (!moduleDefault) continue;
52
+
} catch {
53
+
continue;
54
+
}
52
55
53
-
const final = await this.validate(moduleDefault);
54
-
if (!final) continue;
56
+
const final = await this.validate(moduleDefault);
57
+
if (!final) continue;
55
58
56
-
this.cache.push(final);
57
-
}
58
-
} catch {
59
-
console.error(`[Voidy] Specified loader target ${this.source} doesn't exist. Skipping...`);
60
-
return this;
61
-
}
59
+
this.cache.push(final);
60
+
}
61
+
} catch {
62
+
console.error(`[Voidy] Specified loader target ${this.source} doesn't exist. Skipping...`);
63
+
return this;
64
+
}
62
65
63
-
return this;
64
-
}
66
+
return this;
67
+
}
65
68
66
-
/**
67
-
* Validates a singular element during data collection, and returns whatever should be written to the cache.
68
-
*/
69
-
public abstract validate(data: Partial<T>): Promise<T | null>;
69
+
/**
70
+
* Validates a singular element during data collection, and returns whatever should be written to the cache.
71
+
*/
72
+
public abstract validate(data: Partial<T>): Promise<T | null>;
70
73
71
-
/**
72
-
* Returns the JSON-ified contents of the loader cache
73
-
*/
74
-
public getJSON() {
75
-
return this.cache;
76
-
}
77
-
};
74
+
/**
75
+
* Returns the JSON-ified contents of the loader cache
76
+
*/
77
+
public getJSON() {
78
+
return this.cache;
79
+
}
80
+
}
+45
-45
packages/framework/src/core/ModuleManager.ts
+45
-45
packages/framework/src/core/ModuleManager.ts
···
13
13
// ModuleManager Implementation
14
14
//===============================================
15
15
export class ModuleManager {
16
-
private cache = new Map<string, Map<string, unknown>>();
16
+
private cache = new Map<string, Map<string, unknown>>();
17
17
18
-
// Module Loading
19
-
//==============================
20
-
async loadModules(path: string) {
21
-
const moduleLoader = new ModuleLoader(path);
22
-
const modules = (await moduleLoader.collect()).getJSON();
18
+
// Module Loading
19
+
//==============================
20
+
async loadModules(path: string) {
21
+
const moduleLoader = new ModuleLoader(path);
22
+
const modules = (await moduleLoader.collect()).getJSON();
23
23
24
-
for (const module of modules) {
25
-
await this.prepareModule(module);
26
-
}
27
-
}
24
+
for (const module of modules) {
25
+
await this.prepareModule(module);
26
+
}
27
+
}
28
28
29
-
async prepareModule(module: Module) {
30
-
for (const exp of module.exports) {
31
-
const loader = new exp.loader(exp.source);
32
-
const data = (await loader.collect()).getJSON();
29
+
async prepareModule(module: Module) {
30
+
for (const exp of module.exports) {
31
+
const loader = new exp.loader(exp.source);
32
+
const data = (await loader.collect()).getJSON();
33
33
34
-
for (const item of data) {
35
-
this.set(loader.id, (item as any).id, item);
36
-
}
37
-
}
38
-
}
34
+
for (const item of data) {
35
+
this.set(loader.id, (item as any).id, item);
36
+
}
37
+
}
38
+
}
39
39
40
-
// Core API
41
-
//==============================
42
-
set<T>(type: string, id: string, value: T) {
43
-
if (!this.cache.has(type)) this.cache.set(type, new Map());
44
-
(this.cache.get(type) as CacheMap<T>).set(id, value);
45
-
}
40
+
// Core API
41
+
//==============================
42
+
set<T>(type: string, id: string, value: T) {
43
+
if (!this.cache.has(type)) this.cache.set(type, new Map());
44
+
(this.cache.get(type) as CacheMap<T>).set(id, value);
45
+
}
46
46
47
-
get<T>(type: string, id: string): T | undefined {
48
-
return (this.cache.get(type) as CacheMap<T>)?.get(id);
49
-
}
47
+
get<T>(type: string, id: string): T | undefined {
48
+
return (this.cache.get(type) as CacheMap<T>)?.get(id);
49
+
}
50
50
51
-
getAll<T>(type: string): CacheMap<T> {
52
-
return (this.cache.get(type) as CacheMap<T>) ?? new Map();
53
-
}
51
+
getAll<T>(type: string): CacheMap<T> {
52
+
return (this.cache.get(type) as CacheMap<T>) ?? new Map();
53
+
}
54
54
55
-
// Typed Accessors
56
-
//==============================
57
-
get modules(): CacheMap<Module> {
58
-
return this.getAll<Module>("module");
59
-
}
55
+
// Typed Accessors
56
+
//==============================
57
+
get modules(): CacheMap<Module> {
58
+
return this.getAll<Module>("module");
59
+
}
60
60
61
-
get commands(): CacheMap<Command> {
62
-
return this.getAll<Command>("command");
63
-
}
61
+
get commands(): CacheMap<Command> {
62
+
return this.getAll<Command>("command");
63
+
}
64
64
65
-
get buttons(): CacheMap<Button> {
66
-
return this.getAll<Button>("button");
67
-
}
65
+
get buttons(): CacheMap<Button> {
66
+
return this.getAll<Button>("button");
67
+
}
68
68
69
-
get events(): CacheMap<Event> {
70
-
return this.getAll<Event>("event");
71
-
}
69
+
get events(): CacheMap<Event> {
70
+
return this.getAll<Event>("event");
71
+
}
72
72
}
+95
-91
packages/framework/src/core/VoidyClient.ts
+95
-91
packages/framework/src/core/VoidyClient.ts
···
2
2
// Imports
3
3
//===============================================
4
4
import {
5
-
type ClientOptions,
6
-
SlashCommandSubcommandGroupBuilder,
7
-
SlashCommandSubcommandBuilder,
8
-
SlashCommandBuilder,
9
-
Client,
5
+
type ClientOptions,
6
+
SlashCommandSubcommandGroupBuilder,
7
+
SlashCommandSubcommandBuilder,
8
+
SlashCommandBuilder,
9
+
Client,
10
+
Events,
10
11
} from "discord.js";
11
12
import { ModuleManager, type CacheMap } from "./ModuleManager";
12
13
import type { Command } from "./types/Command";
···
17
18
// VoidyClient Implementation
18
19
//===============================================
19
20
export class VoidyClient extends Client {
20
-
public moduleManager = new ModuleManager();
21
+
public moduleManager = new ModuleManager();
21
22
22
-
public constructor(options: ClientOptions) {
23
-
super(options);
24
-
}
23
+
public constructor(options: ClientOptions) {
24
+
super(options);
25
+
}
25
26
26
-
/**
27
-
* Launches the bot
28
-
* @param token - The Discord application bot token.
29
-
* @param modulesPath - Where the bot should search for modules.
30
-
*/
31
-
public async start(token: string, modulesPath: string) {
32
-
// Load modules and register events
33
-
await this.moduleManager.loadModules(modulesPath);
34
-
await this.registerEvents();
27
+
/**
28
+
* Launches the bot
29
+
* @param token - The Discord application bot token.
30
+
* @param modulesPath - Where the bot should search for modules.
31
+
*/
32
+
public async start(token: string, modulesPath: string) {
33
+
// Load modules and register events
34
+
await this.moduleManager.loadModules(modulesPath);
35
+
await this.registerEvents();
35
36
36
-
// Register commands on ready event
37
-
this.on("ready", this.registerCommands);
37
+
// Register commands on ready event
38
+
this.on(Events.ClientReady, this.registerCommands);
38
39
39
-
// Login using the bot token
40
-
await this.login(token);
41
-
}
40
+
// Login using the bot token
41
+
await this.login(token);
42
+
}
42
43
43
-
/**
44
-
* Registers all cached events
45
-
* @param events
46
-
*/
47
-
private async registerEvents() {
48
-
const events = this.moduleManager.events;
44
+
/**
45
+
* Registers all cached events
46
+
* @param events
47
+
*/
48
+
private async registerEvents() {
49
+
const events = this.moduleManager.events;
49
50
50
-
for (const [_id, event] of events) {
51
-
const execute = (...args: unknown[]) => event.execute(this, ...args);
51
+
for (const [_id, event] of events) {
52
+
const execute = (...args: unknown[]) => event.execute(this, ...args);
52
53
53
-
if (event.once) this.once(event.name, execute);
54
-
else this.on(event.name, execute);
55
-
}
56
-
}
54
+
if (event.once) this.once(event.name, execute);
55
+
else this.on(event.name, execute);
56
+
}
57
+
}
57
58
58
-
/**
59
-
* Registers all provided commands globally
60
-
* @param commands
61
-
*/
62
-
private async registerCommands(): Promise<void> {
63
-
const topLevelCommands = new Map<string, SlashCommandBuilder>();
59
+
/**
60
+
* Registers all provided commands globally
61
+
* @param commands
62
+
*/
63
+
private async registerCommands(): Promise<void> {
64
+
const topLevelCommands = new Map<string, SlashCommandBuilder>();
64
65
65
-
for (const cmd of this.moduleManager.commands.values()) {
66
-
const parts = cmd.id.split("."); // ["music", "set", "channel"]
67
-
const command = parts[0];
68
-
const subcommand = parts[1];
69
-
const subgroupcommand = parts[2];
66
+
for (const cmd of this.moduleManager.commands.values()) {
67
+
const parts = cmd.id.split("."); // ["music", "set", "channel"]
68
+
const command = parts[0];
69
+
const subcommand = parts[1];
70
+
const subgroupcommand = parts[2];
70
71
71
-
if (!command) continue;
72
+
if (!command) continue;
72
73
73
-
// Ensure top-level builder exists
74
-
if (!topLevelCommands.has(command)) {
75
-
const topCommand = (
76
-
this.moduleManager.commands.get(command)?.data as SlashCommandBuilder
77
-
) ?? new SlashCommandBuilder().setName(command).setDescription("...");
74
+
// Ensure top-level builder exists
75
+
if (!topLevelCommands.has(command)) {
76
+
const topCommand =
77
+
(this.moduleManager.commands.get(command)?.data as SlashCommandBuilder) ??
78
+
new SlashCommandBuilder().setName(command).setDescription("...");
78
79
79
-
topLevelCommands.set(command, topCommand);
80
-
}
80
+
topLevelCommands.set(command, topCommand);
81
+
}
81
82
82
-
const parent = topLevelCommands.get(command)!;
83
+
const parent = topLevelCommands.get(command)!;
83
84
84
-
if (subcommand && !subgroupcommand) {
85
-
// It's a subcommand
86
-
parent.addSubcommand(cmd.data as SlashCommandSubcommandBuilder);
87
-
} else if (subcommand && subgroupcommand) {
88
-
// It's a subgroup command
89
-
let group = parent.options.find(
90
-
(o): o is SlashCommandSubcommandGroupBuilder => o instanceof SlashCommandSubcommandGroupBuilder && o.name === subcommand
91
-
);
85
+
if (subcommand && !subgroupcommand) {
86
+
// It's a subcommand
87
+
parent.addSubcommand(cmd.data as SlashCommandSubcommandBuilder);
88
+
} else if (subcommand && subgroupcommand) {
89
+
// It's a subgroup command
90
+
let group = parent.options.find(
91
+
(o): o is SlashCommandSubcommandGroupBuilder =>
92
+
o instanceof SlashCommandSubcommandGroupBuilder && o.name === subcommand,
93
+
);
92
94
93
-
if (!group) {
94
-
group = new SlashCommandSubcommandGroupBuilder().setName(subcommand).setDescription("...");
95
-
parent.addSubcommandGroup(group);
96
-
}
95
+
if (!group) {
96
+
group = new SlashCommandSubcommandGroupBuilder()
97
+
.setName(subcommand)
98
+
.setDescription("...");
99
+
parent.addSubcommandGroup(group);
100
+
}
97
101
98
-
group.addSubcommand(cmd.data as SlashCommandSubcommandBuilder);
99
-
}
100
-
}
102
+
group.addSubcommand(cmd.data as SlashCommandSubcommandBuilder);
103
+
}
104
+
}
101
105
102
-
// Finally convert assembled top-level commands to JSON and register them
103
-
await this.application?.commands.set([...topLevelCommands.values()].map(c => c.toJSON()));
104
-
}
106
+
// Finally convert assembled top-level commands to JSON and register them
107
+
await this.application?.commands.set([...topLevelCommands.values()].map((c) => c.toJSON()));
108
+
}
105
109
106
-
/**
107
-
* Returns all cached commands
108
-
*/
109
-
get commands(): CacheMap<Command> {
110
-
return this.moduleManager.commands;
111
-
}
110
+
/**
111
+
* Returns all cached commands
112
+
*/
113
+
get commands(): CacheMap<Command> {
114
+
return this.moduleManager.commands;
115
+
}
112
116
113
-
/**
114
-
* Returns all cached events
115
-
*/
116
-
get events(): CacheMap<Event> {
117
-
return this.moduleManager.events;
118
-
}
117
+
/**
118
+
* Returns all cached events
119
+
*/
120
+
get events(): CacheMap<Event> {
121
+
return this.moduleManager.events;
122
+
}
119
123
120
-
/**
121
-
* Returns all cached buttons
122
-
*/
123
-
get buttons(): CacheMap<Button> {
124
-
return this.moduleManager.buttons;
125
-
}
124
+
/**
125
+
* Returns all cached buttons
126
+
*/
127
+
get buttons(): CacheMap<Button> {
128
+
return this.moduleManager.buttons;
129
+
}
126
130
}
+1
-3
packages/framework/src/core/types/Button.ts
+1
-3
packages/framework/src/core/types/Button.ts
···
9
9
// Button Definition
10
10
//===============================================
11
11
export interface Button extends Resource {
12
-
execute: (
13
-
interaction: ButtonInteraction, client: VoidyClient
14
-
) => Promise<void>
12
+
execute: (interaction: ButtonInteraction, client: VoidyClient) => Promise<void>;
15
13
}
+6
-8
packages/framework/src/core/types/Command.ts
+6
-8
packages/framework/src/core/types/Command.ts
···
2
2
// Imports
3
3
//===============================================
4
4
import type {
5
-
SlashCommandSubcommandGroupBuilder,
6
-
SlashCommandSubcommandBuilder,
7
-
ChatInputCommandInteraction,
8
-
SlashCommandBuilder,
5
+
SlashCommandSubcommandGroupBuilder,
6
+
SlashCommandSubcommandBuilder,
7
+
ChatInputCommandInteraction,
8
+
SlashCommandBuilder,
9
9
} from "discord.js";
10
10
import type { VoidyClient } from "../VoidyClient";
11
11
import type { Resource } from "./Resource";
···
14
14
// Command Definition
15
15
//===============================================
16
16
export interface Command extends Resource {
17
-
data: SlashCommandBuilder | SlashCommandSubcommandBuilder | SlashCommandSubcommandGroupBuilder,
18
-
execute: (
19
-
interaction: ChatInputCommandInteraction, client: VoidyClient
20
-
) => Promise<void>
17
+
data: SlashCommandBuilder | SlashCommandSubcommandBuilder | SlashCommandSubcommandGroupBuilder;
18
+
execute: (interaction: ChatInputCommandInteraction, client: VoidyClient) => Promise<void>;
21
19
}
+3
-3
packages/framework/src/core/types/Event.ts
+3
-3
packages/framework/src/core/types/Event.ts
···
9
9
// Event Definition
10
10
//===============================================
11
11
export interface Event extends Resource {
12
-
name: keyof ClientEvents,
13
-
once?: boolean,
14
-
execute: (client: VoidyClient, ...args: unknown[]) => void,
12
+
name: keyof ClientEvents;
13
+
once?: boolean;
14
+
execute: (client: VoidyClient, ...args: unknown[]) => void;
15
15
}
+6
-6
packages/framework/src/core/types/Module.ts
+6
-6
packages/framework/src/core/types/Module.ts
···
8
8
// ModuleExportsItem Definition
9
9
//===============================================
10
10
export interface ModuleExportsItem<T extends object> {
11
-
source: string
12
-
loader: new (...args: ConstructorParameters<typeof Loader<T>>) => Loader<T>
11
+
source: string;
12
+
loader: new (...args: ConstructorParameters<typeof Loader<T>>) => Loader<T>;
13
13
}
14
14
15
15
//===============================================
16
16
// Module Definition
17
17
//===============================================
18
18
export interface Module extends Resource {
19
-
name: string
20
-
description: string
21
-
author: string
22
-
exports: ModuleExportsItem<object>[]
19
+
name: string;
20
+
description: string;
21
+
author: string;
22
+
exports: ModuleExportsItem<object>[];
23
23
}
+1
-1
packages/framework/src/core/types/Resource.ts
+1
-1
packages/framework/src/core/types/Resource.ts
+3
-3
packages/framework/src/handlers/ButtonHandler.ts
+3
-3
packages/framework/src/handlers/ButtonHandler.ts
···
3
3
import type { VoidyClient } from "../core/VoidyClient";
4
4
5
5
export class ButtonHandler {
6
-
public static invoke(interaction: ButtonInteraction, payload: Button, client: VoidyClient): void {
7
-
payload.execute(interaction, client);
8
-
}
6
+
public static invoke(interaction: ButtonInteraction, payload: Button, client: VoidyClient): void {
7
+
payload.execute(interaction, client);
8
+
}
9
9
}
+7
-3
packages/framework/src/handlers/CommandHandler.ts
+7
-3
packages/framework/src/handlers/CommandHandler.ts
···
3
3
import type { VoidyClient } from "../core/VoidyClient";
4
4
5
5
export class ChatInputCommandHandler {
6
-
public static invoke(interaction: ChatInputCommandInteraction, payload: Command, client: VoidyClient): void {
7
-
payload.execute(interaction, client);
8
-
}
6
+
public static invoke(
7
+
interaction: ChatInputCommandInteraction,
8
+
payload: Command,
9
+
client: VoidyClient,
10
+
): void {
11
+
payload.execute(interaction, client);
12
+
}
9
13
}
+5
-5
packages/framework/src/loaders/ButtonLoader.ts
+5
-5
packages/framework/src/loaders/ButtonLoader.ts
···
8
8
// ButtonLoader Implementation
9
9
//===============================================
10
10
export class ButtonLoader extends Loader<Button> {
11
-
public id = "button";
12
-
public async validate(data: Partial<Button>) {
13
-
if (!data.id || !data.execute) return null;
14
-
return data as Button;
15
-
}
11
+
public id = "button";
12
+
public async validate(data: Partial<Button>) {
13
+
if (!data.id || !data.execute) return null;
14
+
return data as Button;
15
+
}
16
16
}
+5
-5
packages/framework/src/loaders/CommandLoader.ts
+5
-5
packages/framework/src/loaders/CommandLoader.ts
···
8
8
// CommandLoader Implementation
9
9
//===============================================
10
10
export class CommandLoader extends Loader<Command> {
11
-
public id = "command";
12
-
public async validate(data: Partial<Command>) {
13
-
if (!data.id || !data.data || !data.execute) return null;
14
-
return data as Command;
15
-
}
11
+
public id = "command";
12
+
public async validate(data: Partial<Command>) {
13
+
if (!data.id || !data.data || !data.execute) return null;
14
+
return data as Command;
15
+
}
16
16
}
+5
-5
packages/framework/src/loaders/EventLoader.ts
+5
-5
packages/framework/src/loaders/EventLoader.ts
···
8
8
// EventLoader Implemenation
9
9
//===============================================
10
10
export class EventLoader extends Loader<Event> {
11
-
public id = "event";
12
-
public async validate(data: Partial<Event>) {
13
-
if (!data.id || !data.name || !data.execute) return null;
14
-
return data as Event;
15
-
}
11
+
public id = "event";
12
+
public async validate(data: Partial<Event>) {
13
+
if (!data.id || !data.name || !data.execute) return null;
14
+
return data as Event;
15
+
}
16
16
}
+6
-12
packages/framework/src/loaders/ModuleLoader.ts
+6
-12
packages/framework/src/loaders/ModuleLoader.ts
···
2
2
// Imports
3
3
//===============================================
4
4
import type { Module } from "../core/types/Module";
5
-
import { Loader } from "../core/Loader"
5
+
import { Loader } from "../core/Loader";
6
6
7
7
//===============================================
8
8
// ModuleLoader Implementation
9
9
//===============================================
10
10
export class ModuleLoader extends Loader<Module> {
11
-
public id = "module";
12
-
public async validate(data: Partial<Module>) {
13
-
if (
14
-
!data.id ||
15
-
!data.name ||
16
-
!data.description ||
17
-
!data.author ||
18
-
!data.exports
19
-
) return null;
11
+
public id = "module";
12
+
public async validate(data: Partial<Module>) {
13
+
if (!data.id || !data.name || !data.description || !data.author || !data.exports) return null;
20
14
21
-
return data as Module;
22
-
}
15
+
return data as Module;
16
+
}
23
17
}
+10
-12
packages/framework/tsconfig.json
+10
-12
packages/framework/tsconfig.json
···
1
1
{
2
-
"extends": "../../tsconfig.json",
3
-
"compilerOptions": {
4
-
"declaration": true,
5
-
"declarationDir": "dist/types",
6
-
"outDir": "dist",
7
-
"strict": true,
8
-
"esModuleInterop": true,
9
-
"composite": true
10
-
},
11
-
"include": [
12
-
"src"
13
-
]
2
+
"extends": "../../tsconfig.json",
3
+
"compilerOptions": {
4
+
"declaration": true,
5
+
"declarationDir": "dist/types",
6
+
"outDir": "dist",
7
+
"strict": true,
8
+
"esModuleInterop": true,
9
+
"composite": true,
10
+
},
11
+
"include": ["src"],
14
12
}