APRK01 commited on
Commit
3c7e34b
·
0 Parent(s):

Initial commit: WSB Discord Bot

Browse files
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ node_modules/
2
+ .env
3
+ data/
package-lock.json ADDED
@@ -0,0 +1,777 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "wsb-bot",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "wsb-bot",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "better-sqlite3": "^11.7.0",
12
+ "discord.js": "^14.16.0",
13
+ "dotenv": "^16.4.0"
14
+ }
15
+ },
16
+ "node_modules/@discordjs/builders": {
17
+ "version": "1.13.1",
18
+ "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz",
19
+ "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==",
20
+ "license": "Apache-2.0",
21
+ "dependencies": {
22
+ "@discordjs/formatters": "^0.6.2",
23
+ "@discordjs/util": "^1.2.0",
24
+ "@sapphire/shapeshift": "^4.0.0",
25
+ "discord-api-types": "^0.38.33",
26
+ "fast-deep-equal": "^3.1.3",
27
+ "ts-mixer": "^6.0.4",
28
+ "tslib": "^2.6.3"
29
+ },
30
+ "engines": {
31
+ "node": ">=16.11.0"
32
+ },
33
+ "funding": {
34
+ "url": "https://github.com/discordjs/discord.js?sponsor"
35
+ }
36
+ },
37
+ "node_modules/@discordjs/collection": {
38
+ "version": "1.5.3",
39
+ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
40
+ "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
41
+ "license": "Apache-2.0",
42
+ "engines": {
43
+ "node": ">=16.11.0"
44
+ }
45
+ },
46
+ "node_modules/@discordjs/formatters": {
47
+ "version": "0.6.2",
48
+ "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
49
+ "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
50
+ "license": "Apache-2.0",
51
+ "dependencies": {
52
+ "discord-api-types": "^0.38.33"
53
+ },
54
+ "engines": {
55
+ "node": ">=16.11.0"
56
+ },
57
+ "funding": {
58
+ "url": "https://github.com/discordjs/discord.js?sponsor"
59
+ }
60
+ },
61
+ "node_modules/@discordjs/rest": {
62
+ "version": "2.6.0",
63
+ "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz",
64
+ "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==",
65
+ "license": "Apache-2.0",
66
+ "dependencies": {
67
+ "@discordjs/collection": "^2.1.1",
68
+ "@discordjs/util": "^1.1.1",
69
+ "@sapphire/async-queue": "^1.5.3",
70
+ "@sapphire/snowflake": "^3.5.3",
71
+ "@vladfrangu/async_event_emitter": "^2.4.6",
72
+ "discord-api-types": "^0.38.16",
73
+ "magic-bytes.js": "^1.10.0",
74
+ "tslib": "^2.6.3",
75
+ "undici": "6.21.3"
76
+ },
77
+ "engines": {
78
+ "node": ">=18"
79
+ },
80
+ "funding": {
81
+ "url": "https://github.com/discordjs/discord.js?sponsor"
82
+ }
83
+ },
84
+ "node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
85
+ "version": "2.1.1",
86
+ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
87
+ "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
88
+ "license": "Apache-2.0",
89
+ "engines": {
90
+ "node": ">=18"
91
+ },
92
+ "funding": {
93
+ "url": "https://github.com/discordjs/discord.js?sponsor"
94
+ }
95
+ },
96
+ "node_modules/@discordjs/util": {
97
+ "version": "1.2.0",
98
+ "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
99
+ "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
100
+ "license": "Apache-2.0",
101
+ "dependencies": {
102
+ "discord-api-types": "^0.38.33"
103
+ },
104
+ "engines": {
105
+ "node": ">=18"
106
+ },
107
+ "funding": {
108
+ "url": "https://github.com/discordjs/discord.js?sponsor"
109
+ }
110
+ },
111
+ "node_modules/@discordjs/ws": {
112
+ "version": "1.2.3",
113
+ "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
114
+ "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
115
+ "license": "Apache-2.0",
116
+ "dependencies": {
117
+ "@discordjs/collection": "^2.1.0",
118
+ "@discordjs/rest": "^2.5.1",
119
+ "@discordjs/util": "^1.1.0",
120
+ "@sapphire/async-queue": "^1.5.2",
121
+ "@types/ws": "^8.5.10",
122
+ "@vladfrangu/async_event_emitter": "^2.2.4",
123
+ "discord-api-types": "^0.38.1",
124
+ "tslib": "^2.6.2",
125
+ "ws": "^8.17.0"
126
+ },
127
+ "engines": {
128
+ "node": ">=16.11.0"
129
+ },
130
+ "funding": {
131
+ "url": "https://github.com/discordjs/discord.js?sponsor"
132
+ }
133
+ },
134
+ "node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
135
+ "version": "2.1.1",
136
+ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
137
+ "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
138
+ "license": "Apache-2.0",
139
+ "engines": {
140
+ "node": ">=18"
141
+ },
142
+ "funding": {
143
+ "url": "https://github.com/discordjs/discord.js?sponsor"
144
+ }
145
+ },
146
+ "node_modules/@sapphire/async-queue": {
147
+ "version": "1.5.5",
148
+ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
149
+ "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
150
+ "license": "MIT",
151
+ "engines": {
152
+ "node": ">=v14.0.0",
153
+ "npm": ">=7.0.0"
154
+ }
155
+ },
156
+ "node_modules/@sapphire/shapeshift": {
157
+ "version": "4.0.0",
158
+ "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
159
+ "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
160
+ "license": "MIT",
161
+ "dependencies": {
162
+ "fast-deep-equal": "^3.1.3",
163
+ "lodash": "^4.17.21"
164
+ },
165
+ "engines": {
166
+ "node": ">=v16"
167
+ }
168
+ },
169
+ "node_modules/@sapphire/snowflake": {
170
+ "version": "3.5.3",
171
+ "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
172
+ "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
173
+ "license": "MIT",
174
+ "engines": {
175
+ "node": ">=v14.0.0",
176
+ "npm": ">=7.0.0"
177
+ }
178
+ },
179
+ "node_modules/@types/node": {
180
+ "version": "25.3.3",
181
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
182
+ "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==",
183
+ "license": "MIT",
184
+ "dependencies": {
185
+ "undici-types": "~7.18.0"
186
+ }
187
+ },
188
+ "node_modules/@types/ws": {
189
+ "version": "8.18.1",
190
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
191
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
192
+ "license": "MIT",
193
+ "dependencies": {
194
+ "@types/node": "*"
195
+ }
196
+ },
197
+ "node_modules/@vladfrangu/async_event_emitter": {
198
+ "version": "2.4.7",
199
+ "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
200
+ "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
201
+ "license": "MIT",
202
+ "engines": {
203
+ "node": ">=v14.0.0",
204
+ "npm": ">=7.0.0"
205
+ }
206
+ },
207
+ "node_modules/base64-js": {
208
+ "version": "1.5.1",
209
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
210
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
211
+ "funding": [
212
+ {
213
+ "type": "github",
214
+ "url": "https://github.com/sponsors/feross"
215
+ },
216
+ {
217
+ "type": "patreon",
218
+ "url": "https://www.patreon.com/feross"
219
+ },
220
+ {
221
+ "type": "consulting",
222
+ "url": "https://feross.org/support"
223
+ }
224
+ ],
225
+ "license": "MIT"
226
+ },
227
+ "node_modules/better-sqlite3": {
228
+ "version": "11.10.0",
229
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
230
+ "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
231
+ "hasInstallScript": true,
232
+ "license": "MIT",
233
+ "dependencies": {
234
+ "bindings": "^1.5.0",
235
+ "prebuild-install": "^7.1.1"
236
+ }
237
+ },
238
+ "node_modules/bindings": {
239
+ "version": "1.5.0",
240
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
241
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
242
+ "license": "MIT",
243
+ "dependencies": {
244
+ "file-uri-to-path": "1.0.0"
245
+ }
246
+ },
247
+ "node_modules/bl": {
248
+ "version": "4.1.0",
249
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
250
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
251
+ "license": "MIT",
252
+ "dependencies": {
253
+ "buffer": "^5.5.0",
254
+ "inherits": "^2.0.4",
255
+ "readable-stream": "^3.4.0"
256
+ }
257
+ },
258
+ "node_modules/buffer": {
259
+ "version": "5.7.1",
260
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
261
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
262
+ "funding": [
263
+ {
264
+ "type": "github",
265
+ "url": "https://github.com/sponsors/feross"
266
+ },
267
+ {
268
+ "type": "patreon",
269
+ "url": "https://www.patreon.com/feross"
270
+ },
271
+ {
272
+ "type": "consulting",
273
+ "url": "https://feross.org/support"
274
+ }
275
+ ],
276
+ "license": "MIT",
277
+ "dependencies": {
278
+ "base64-js": "^1.3.1",
279
+ "ieee754": "^1.1.13"
280
+ }
281
+ },
282
+ "node_modules/chownr": {
283
+ "version": "1.1.4",
284
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
285
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
286
+ "license": "ISC"
287
+ },
288
+ "node_modules/decompress-response": {
289
+ "version": "6.0.0",
290
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
291
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
292
+ "license": "MIT",
293
+ "dependencies": {
294
+ "mimic-response": "^3.1.0"
295
+ },
296
+ "engines": {
297
+ "node": ">=10"
298
+ },
299
+ "funding": {
300
+ "url": "https://github.com/sponsors/sindresorhus"
301
+ }
302
+ },
303
+ "node_modules/deep-extend": {
304
+ "version": "0.6.0",
305
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
306
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
307
+ "license": "MIT",
308
+ "engines": {
309
+ "node": ">=4.0.0"
310
+ }
311
+ },
312
+ "node_modules/detect-libc": {
313
+ "version": "2.1.2",
314
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
315
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
316
+ "license": "Apache-2.0",
317
+ "engines": {
318
+ "node": ">=8"
319
+ }
320
+ },
321
+ "node_modules/discord-api-types": {
322
+ "version": "0.38.40",
323
+ "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.40.tgz",
324
+ "integrity": "sha512-P/His8cotqZgQqrt+hzrocp9L8RhQQz1GkrCnC9TMJ8Uw2q0tg8YyqJyGULxhXn/8kxHETN4IppmOv+P2m82lQ==",
325
+ "license": "MIT",
326
+ "workspaces": [
327
+ "scripts/actions/documentation"
328
+ ]
329
+ },
330
+ "node_modules/discord.js": {
331
+ "version": "14.25.1",
332
+ "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz",
333
+ "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
334
+ "license": "Apache-2.0",
335
+ "dependencies": {
336
+ "@discordjs/builders": "^1.13.0",
337
+ "@discordjs/collection": "1.5.3",
338
+ "@discordjs/formatters": "^0.6.2",
339
+ "@discordjs/rest": "^2.6.0",
340
+ "@discordjs/util": "^1.2.0",
341
+ "@discordjs/ws": "^1.2.3",
342
+ "@sapphire/snowflake": "3.5.3",
343
+ "discord-api-types": "^0.38.33",
344
+ "fast-deep-equal": "3.1.3",
345
+ "lodash.snakecase": "4.1.1",
346
+ "magic-bytes.js": "^1.10.0",
347
+ "tslib": "^2.6.3",
348
+ "undici": "6.21.3"
349
+ },
350
+ "engines": {
351
+ "node": ">=18"
352
+ },
353
+ "funding": {
354
+ "url": "https://github.com/discordjs/discord.js?sponsor"
355
+ }
356
+ },
357
+ "node_modules/dotenv": {
358
+ "version": "16.6.1",
359
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
360
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
361
+ "license": "BSD-2-Clause",
362
+ "engines": {
363
+ "node": ">=12"
364
+ },
365
+ "funding": {
366
+ "url": "https://dotenvx.com"
367
+ }
368
+ },
369
+ "node_modules/end-of-stream": {
370
+ "version": "1.4.5",
371
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
372
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
373
+ "license": "MIT",
374
+ "dependencies": {
375
+ "once": "^1.4.0"
376
+ }
377
+ },
378
+ "node_modules/expand-template": {
379
+ "version": "2.0.3",
380
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
381
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
382
+ "license": "(MIT OR WTFPL)",
383
+ "engines": {
384
+ "node": ">=6"
385
+ }
386
+ },
387
+ "node_modules/fast-deep-equal": {
388
+ "version": "3.1.3",
389
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
390
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
391
+ "license": "MIT"
392
+ },
393
+ "node_modules/file-uri-to-path": {
394
+ "version": "1.0.0",
395
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
396
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
397
+ "license": "MIT"
398
+ },
399
+ "node_modules/fs-constants": {
400
+ "version": "1.0.0",
401
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
402
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
403
+ "license": "MIT"
404
+ },
405
+ "node_modules/github-from-package": {
406
+ "version": "0.0.0",
407
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
408
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
409
+ "license": "MIT"
410
+ },
411
+ "node_modules/ieee754": {
412
+ "version": "1.2.1",
413
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
414
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
415
+ "funding": [
416
+ {
417
+ "type": "github",
418
+ "url": "https://github.com/sponsors/feross"
419
+ },
420
+ {
421
+ "type": "patreon",
422
+ "url": "https://www.patreon.com/feross"
423
+ },
424
+ {
425
+ "type": "consulting",
426
+ "url": "https://feross.org/support"
427
+ }
428
+ ],
429
+ "license": "BSD-3-Clause"
430
+ },
431
+ "node_modules/inherits": {
432
+ "version": "2.0.4",
433
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
434
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
435
+ "license": "ISC"
436
+ },
437
+ "node_modules/ini": {
438
+ "version": "1.3.8",
439
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
440
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
441
+ "license": "ISC"
442
+ },
443
+ "node_modules/lodash": {
444
+ "version": "4.17.23",
445
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
446
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
447
+ "license": "MIT"
448
+ },
449
+ "node_modules/lodash.snakecase": {
450
+ "version": "4.1.1",
451
+ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
452
+ "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
453
+ "license": "MIT"
454
+ },
455
+ "node_modules/magic-bytes.js": {
456
+ "version": "1.13.0",
457
+ "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz",
458
+ "integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==",
459
+ "license": "MIT"
460
+ },
461
+ "node_modules/mimic-response": {
462
+ "version": "3.1.0",
463
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
464
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
465
+ "license": "MIT",
466
+ "engines": {
467
+ "node": ">=10"
468
+ },
469
+ "funding": {
470
+ "url": "https://github.com/sponsors/sindresorhus"
471
+ }
472
+ },
473
+ "node_modules/minimist": {
474
+ "version": "1.2.8",
475
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
476
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
477
+ "license": "MIT",
478
+ "funding": {
479
+ "url": "https://github.com/sponsors/ljharb"
480
+ }
481
+ },
482
+ "node_modules/mkdirp-classic": {
483
+ "version": "0.5.3",
484
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
485
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
486
+ "license": "MIT"
487
+ },
488
+ "node_modules/napi-build-utils": {
489
+ "version": "2.0.0",
490
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
491
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
492
+ "license": "MIT"
493
+ },
494
+ "node_modules/node-abi": {
495
+ "version": "3.87.0",
496
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
497
+ "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
498
+ "license": "MIT",
499
+ "dependencies": {
500
+ "semver": "^7.3.5"
501
+ },
502
+ "engines": {
503
+ "node": ">=10"
504
+ }
505
+ },
506
+ "node_modules/once": {
507
+ "version": "1.4.0",
508
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
509
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
510
+ "license": "ISC",
511
+ "dependencies": {
512
+ "wrappy": "1"
513
+ }
514
+ },
515
+ "node_modules/prebuild-install": {
516
+ "version": "7.1.3",
517
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
518
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
519
+ "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
520
+ "license": "MIT",
521
+ "dependencies": {
522
+ "detect-libc": "^2.0.0",
523
+ "expand-template": "^2.0.3",
524
+ "github-from-package": "0.0.0",
525
+ "minimist": "^1.2.3",
526
+ "mkdirp-classic": "^0.5.3",
527
+ "napi-build-utils": "^2.0.0",
528
+ "node-abi": "^3.3.0",
529
+ "pump": "^3.0.0",
530
+ "rc": "^1.2.7",
531
+ "simple-get": "^4.0.0",
532
+ "tar-fs": "^2.0.0",
533
+ "tunnel-agent": "^0.6.0"
534
+ },
535
+ "bin": {
536
+ "prebuild-install": "bin.js"
537
+ },
538
+ "engines": {
539
+ "node": ">=10"
540
+ }
541
+ },
542
+ "node_modules/pump": {
543
+ "version": "3.0.4",
544
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
545
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
546
+ "license": "MIT",
547
+ "dependencies": {
548
+ "end-of-stream": "^1.1.0",
549
+ "once": "^1.3.1"
550
+ }
551
+ },
552
+ "node_modules/rc": {
553
+ "version": "1.2.8",
554
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
555
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
556
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
557
+ "dependencies": {
558
+ "deep-extend": "^0.6.0",
559
+ "ini": "~1.3.0",
560
+ "minimist": "^1.2.0",
561
+ "strip-json-comments": "~2.0.1"
562
+ },
563
+ "bin": {
564
+ "rc": "cli.js"
565
+ }
566
+ },
567
+ "node_modules/readable-stream": {
568
+ "version": "3.6.2",
569
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
570
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
571
+ "license": "MIT",
572
+ "dependencies": {
573
+ "inherits": "^2.0.3",
574
+ "string_decoder": "^1.1.1",
575
+ "util-deprecate": "^1.0.1"
576
+ },
577
+ "engines": {
578
+ "node": ">= 6"
579
+ }
580
+ },
581
+ "node_modules/safe-buffer": {
582
+ "version": "5.2.1",
583
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
584
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
585
+ "funding": [
586
+ {
587
+ "type": "github",
588
+ "url": "https://github.com/sponsors/feross"
589
+ },
590
+ {
591
+ "type": "patreon",
592
+ "url": "https://www.patreon.com/feross"
593
+ },
594
+ {
595
+ "type": "consulting",
596
+ "url": "https://feross.org/support"
597
+ }
598
+ ],
599
+ "license": "MIT"
600
+ },
601
+ "node_modules/semver": {
602
+ "version": "7.7.4",
603
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
604
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
605
+ "license": "ISC",
606
+ "bin": {
607
+ "semver": "bin/semver.js"
608
+ },
609
+ "engines": {
610
+ "node": ">=10"
611
+ }
612
+ },
613
+ "node_modules/simple-concat": {
614
+ "version": "1.0.1",
615
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
616
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
617
+ "funding": [
618
+ {
619
+ "type": "github",
620
+ "url": "https://github.com/sponsors/feross"
621
+ },
622
+ {
623
+ "type": "patreon",
624
+ "url": "https://www.patreon.com/feross"
625
+ },
626
+ {
627
+ "type": "consulting",
628
+ "url": "https://feross.org/support"
629
+ }
630
+ ],
631
+ "license": "MIT"
632
+ },
633
+ "node_modules/simple-get": {
634
+ "version": "4.0.1",
635
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
636
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
637
+ "funding": [
638
+ {
639
+ "type": "github",
640
+ "url": "https://github.com/sponsors/feross"
641
+ },
642
+ {
643
+ "type": "patreon",
644
+ "url": "https://www.patreon.com/feross"
645
+ },
646
+ {
647
+ "type": "consulting",
648
+ "url": "https://feross.org/support"
649
+ }
650
+ ],
651
+ "license": "MIT",
652
+ "dependencies": {
653
+ "decompress-response": "^6.0.0",
654
+ "once": "^1.3.1",
655
+ "simple-concat": "^1.0.0"
656
+ }
657
+ },
658
+ "node_modules/string_decoder": {
659
+ "version": "1.3.0",
660
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
661
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
662
+ "license": "MIT",
663
+ "dependencies": {
664
+ "safe-buffer": "~5.2.0"
665
+ }
666
+ },
667
+ "node_modules/strip-json-comments": {
668
+ "version": "2.0.1",
669
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
670
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
671
+ "license": "MIT",
672
+ "engines": {
673
+ "node": ">=0.10.0"
674
+ }
675
+ },
676
+ "node_modules/tar-fs": {
677
+ "version": "2.1.4",
678
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
679
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
680
+ "license": "MIT",
681
+ "dependencies": {
682
+ "chownr": "^1.1.1",
683
+ "mkdirp-classic": "^0.5.2",
684
+ "pump": "^3.0.0",
685
+ "tar-stream": "^2.1.4"
686
+ }
687
+ },
688
+ "node_modules/tar-stream": {
689
+ "version": "2.2.0",
690
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
691
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
692
+ "license": "MIT",
693
+ "dependencies": {
694
+ "bl": "^4.0.3",
695
+ "end-of-stream": "^1.4.1",
696
+ "fs-constants": "^1.0.0",
697
+ "inherits": "^2.0.3",
698
+ "readable-stream": "^3.1.1"
699
+ },
700
+ "engines": {
701
+ "node": ">=6"
702
+ }
703
+ },
704
+ "node_modules/ts-mixer": {
705
+ "version": "6.0.4",
706
+ "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
707
+ "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
708
+ "license": "MIT"
709
+ },
710
+ "node_modules/tslib": {
711
+ "version": "2.8.1",
712
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
713
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
714
+ "license": "0BSD"
715
+ },
716
+ "node_modules/tunnel-agent": {
717
+ "version": "0.6.0",
718
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
719
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
720
+ "license": "Apache-2.0",
721
+ "dependencies": {
722
+ "safe-buffer": "^5.0.1"
723
+ },
724
+ "engines": {
725
+ "node": "*"
726
+ }
727
+ },
728
+ "node_modules/undici": {
729
+ "version": "6.21.3",
730
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
731
+ "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
732
+ "license": "MIT",
733
+ "engines": {
734
+ "node": ">=18.17"
735
+ }
736
+ },
737
+ "node_modules/undici-types": {
738
+ "version": "7.18.2",
739
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
740
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
741
+ "license": "MIT"
742
+ },
743
+ "node_modules/util-deprecate": {
744
+ "version": "1.0.2",
745
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
746
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
747
+ "license": "MIT"
748
+ },
749
+ "node_modules/wrappy": {
750
+ "version": "1.0.2",
751
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
752
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
753
+ "license": "ISC"
754
+ },
755
+ "node_modules/ws": {
756
+ "version": "8.19.0",
757
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
758
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
759
+ "license": "MIT",
760
+ "engines": {
761
+ "node": ">=10.0.0"
762
+ },
763
+ "peerDependencies": {
764
+ "bufferutil": "^4.0.1",
765
+ "utf-8-validate": ">=5.0.2"
766
+ },
767
+ "peerDependenciesMeta": {
768
+ "bufferutil": {
769
+ "optional": true
770
+ },
771
+ "utf-8-validate": {
772
+ "optional": true
773
+ }
774
+ }
775
+ }
776
+ }
777
+ }
package.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "wsb-bot",
3
+ "version": "1.0.0",
4
+ "description": "WSB — Wyvern Softworks Bot",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "start": "node src/index.js"
8
+ },
9
+ "dependencies": {
10
+ "better-sqlite3": "^11.7.0",
11
+ "discord.js": "^14.16.0",
12
+ "dotenv": "^16.4.0"
13
+ }
14
+ }
render.yaml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ - type: web
3
+ name: wsb-bot
4
+ runtime: node
5
+ buildCommand: npm install
6
+ startCommand: npm start
7
+ envVars:
8
+ - key: BOT_TOKEN
9
+ sync: false
10
+ - key: OWNER_ID
11
+ sync: false
12
+ - key: GUILD_ID
13
+ sync: false
14
+ - key: NODE_VERSION
15
+ value: 20
src/commands/backupLayout.js ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { createEmbed } = require('../utils/embeds');
2
+ const { Colors } = require('../config');
3
+
4
+ module.exports = {
5
+ name: 'backup layout',
6
+ async execute(client, message) {
7
+ const guildId = process.env.GUILD_ID;
8
+ const guild = await client.guilds.fetch(guildId);
9
+
10
+ // Fetch all channels and roles
11
+ await guild.channels.fetch();
12
+ await guild.roles.fetch();
13
+
14
+ const backup = {
15
+ guild: {
16
+ name: guild.name,
17
+ id: guild.id,
18
+ backupDate: new Date().toISOString(),
19
+ },
20
+ roles: guild.roles.cache
21
+ .filter(r => r.name !== '@everyone')
22
+ .sort((a, b) => b.position - a.position)
23
+ .map(r => ({
24
+ name: r.name,
25
+ color: r.hexColor,
26
+ position: r.position,
27
+ hoist: r.hoist,
28
+ managed: r.managed,
29
+ permissions: r.permissions.toArray(),
30
+ })),
31
+ categories: guild.channels.cache
32
+ .filter(c => c.type === 4) // GuildCategory
33
+ .sort((a, b) => a.position - b.position)
34
+ .map(cat => ({
35
+ name: cat.name,
36
+ position: cat.position,
37
+ channels: guild.channels.cache
38
+ .filter(c => c.parentId === cat.id)
39
+ .sort((a, b) => a.position - b.position)
40
+ .map(ch => ({
41
+ name: ch.name,
42
+ type: ch.type === 2 ? 'voice' : 'text',
43
+ position: ch.position,
44
+ })),
45
+ })),
46
+ };
47
+
48
+ const json = JSON.stringify(backup, null, 2);
49
+ const buffer = Buffer.from(json, 'utf-8');
50
+
51
+ const embed = createEmbed({
52
+ title: '💾 Server Backup',
53
+ description: [
54
+ `**Guild:** ${guild.name}`,
55
+ `**Roles:** ${backup.roles.length}`,
56
+ `**Categories:** ${backup.categories.length}`,
57
+ `**Total Channels:** ${backup.categories.reduce((sum, c) => sum + c.channels.length, 0)}`,
58
+ ].join('\n'),
59
+ color: Colors.SUCCESS,
60
+ });
61
+
62
+ await message.reply({
63
+ embeds: [embed],
64
+ files: [{ attachment: buffer, name: `backup-${guild.name.replace(/\s+/g, '-')}-${Date.now()}.json` }],
65
+ });
66
+ },
67
+ };
src/commands/createChannels.js ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { ChannelType, PermissionFlagsBits } = require('discord.js');
2
+ const { Categories } = require('../config');
3
+ const { stmts } = require('../database');
4
+ const { successEmbed, infoEmbed } = require('../utils/embeds');
5
+
6
+ module.exports = {
7
+ name: 'create channels',
8
+ async execute(client, message) {
9
+ const guildId = process.env.GUILD_ID;
10
+ const guild = await client.guilds.fetch(guildId);
11
+
12
+ await message.reply({ embeds: [infoEmbed('Creating Channels', 'Setting up categories and channels...')] });
13
+
14
+ const results = [];
15
+
16
+ // Build a role map for permission overwrites
17
+ const roleMap = new Map();
18
+ roleMap.set('everyone', guild.roles.everyone);
19
+ guild.roles.cache.forEach(r => roleMap.set(r.name, r));
20
+
21
+ for (const catDef of Categories) {
22
+ // Check if category already exists
23
+ let category = guild.channels.cache.find(
24
+ c => c.type === ChannelType.GuildCategory && c.name === catDef.name
25
+ );
26
+
27
+ if (!category) {
28
+ // Build category-level permission overwrites
29
+ const catOverwrites = buildCategoryOverwrites(catDef, roleMap);
30
+
31
+ category = await guild.channels.create({
32
+ name: catDef.name,
33
+ type: ChannelType.GuildCategory,
34
+ permissionOverwrites: catOverwrites,
35
+ reason: 'WSB Setup',
36
+ });
37
+ results.push(`📁 **${catDef.name}** — category created`);
38
+ } else {
39
+ results.push(`📁 **${catDef.name}** — already exists`);
40
+ }
41
+
42
+ // Create child channels
43
+ for (const chDef of catDef.channels) {
44
+ const existing = guild.channels.cache.find(
45
+ c => c.name === chDef.name && c.parentId === category.id
46
+ );
47
+ if (existing) {
48
+ // Store channel ID in bot state for later reference
49
+ stmts.setState.run(`channel_${chDef.name}`, existing.id);
50
+ results.push(` └ **${chDef.name}** — already exists`);
51
+ continue;
52
+ }
53
+
54
+ const channelType = chDef.type === 'voice'
55
+ ? ChannelType.GuildVoice
56
+ : ChannelType.GuildText;
57
+
58
+ // Build channel-specific permission overwrites
59
+ const channelOverwrites = buildChannelOverwrites(catDef, chDef, roleMap);
60
+
61
+ const channel = await guild.channels.create({
62
+ name: chDef.name,
63
+ type: channelType,
64
+ parent: category.id,
65
+ permissionOverwrites: channelOverwrites,
66
+ reason: 'WSB Setup',
67
+ });
68
+
69
+ // Store in bot state
70
+ stmts.setState.run(`channel_${chDef.name}`, channel.id);
71
+
72
+ results.push(` └ **${chDef.name}** — created`);
73
+ }
74
+ }
75
+
76
+ await message.reply({
77
+ embeds: [successEmbed('Channels Created', results.join('\n'))],
78
+ });
79
+ },
80
+ };
81
+
82
+ /**
83
+ * Build permission overwrites for a category.
84
+ */
85
+ function buildCategoryOverwrites(catDef, roleMap) {
86
+ const overrideMap = catDef.permOverrides(roleMap);
87
+ const overwrites = [];
88
+
89
+ for (const [roleName, perms] of Object.entries(overrideMap)) {
90
+ const role = roleName === 'everyone' ? roleMap.get('everyone') : roleMap.get(roleName);
91
+ if (!role) continue;
92
+
93
+ const allow = [];
94
+ const deny = [];
95
+
96
+ if (perms.view === true) allow.push(PermissionFlagsBits.ViewChannel);
97
+ if (perms.view === false) deny.push(PermissionFlagsBits.ViewChannel);
98
+ if (perms.send === true) allow.push(PermissionFlagsBits.SendMessages);
99
+ if (perms.send === false) deny.push(PermissionFlagsBits.SendMessages);
100
+
101
+ overwrites.push({ id: role.id || role, allow, deny });
102
+ }
103
+
104
+ return overwrites;
105
+ }
106
+
107
+ /**
108
+ * Build channel-specific overwrites, handling readOnly, staffOnly, noEmbeds, and special flags.
109
+ */
110
+ function buildChannelOverwrites(catDef, chDef, roleMap) {
111
+ const overrideMap = catDef.permOverrides(roleMap);
112
+ const overwrites = [];
113
+
114
+ for (const [roleName, perms] of Object.entries(overrideMap)) {
115
+ const role = roleName === 'everyone' ? roleMap.get('everyone') : roleMap.get(roleName);
116
+ if (!role) continue;
117
+
118
+ const allow = [];
119
+ const deny = [];
120
+
121
+ // Public channels: @everyone can view (overrides category deny)
122
+ if (chDef.public && roleName === 'everyone') {
123
+ allow.push(PermissionFlagsBits.ViewChannel);
124
+ allow.push(PermissionFlagsBits.ReadMessageHistory);
125
+ allow.push(PermissionFlagsBits.AddReactions);
126
+ } else {
127
+ if (perms.view === true) allow.push(PermissionFlagsBits.ViewChannel);
128
+ if (perms.view === false) deny.push(PermissionFlagsBits.ViewChannel);
129
+ }
130
+
131
+ // hideFromVerified: deny view for @@ Verified (verify channel after verification)
132
+ if (chDef.hideFromVerified && roleName === '@@ Verified') {
133
+ // Override: verified users cannot see this channel
134
+ const viewIdx = allow.indexOf(PermissionFlagsBits.ViewChannel);
135
+ if (viewIdx !== -1) allow.splice(viewIdx, 1);
136
+ deny.push(PermissionFlagsBits.ViewChannel);
137
+ }
138
+
139
+ if (chDef.ownerOnly) {
140
+ // Only Owner and Co-Owner can send
141
+ if (['@@ Owner', '@@ Co-Owner'].includes(roleName)) {
142
+ allow.push(PermissionFlagsBits.SendMessages);
143
+ } else {
144
+ deny.push(PermissionFlagsBits.SendMessages);
145
+ }
146
+ } else if (chDef.readOnly) {
147
+ // Only Staff and Owner can send in read-only channels
148
+ if (['@@ Staff', '@@ Owner', '@@ Co-Owner'].includes(roleName)) {
149
+ if (perms.send === true) allow.push(PermissionFlagsBits.SendMessages);
150
+ } else {
151
+ deny.push(PermissionFlagsBits.SendMessages);
152
+ }
153
+ } else if (chDef.staffOnly) {
154
+ // Only Staff and Owner can see/send
155
+ if (['@@ Staff', '@@ Owner', '@@ Co-Owner'].includes(roleName)) {
156
+ allow.push(PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages);
157
+ } else {
158
+ deny.push(PermissionFlagsBits.ViewChannel);
159
+ }
160
+ } else if (chDef.special === 'community-content') {
161
+ // Known + Helper + Staff + Owner can send; everyone else view-only
162
+ if (['@@ Known', '@@ Helper', '@@ Staff', '@@ Owner', '@@ Co-Owner'].includes(roleName)) {
163
+ allow.push(PermissionFlagsBits.SendMessages);
164
+ } else {
165
+ if (perms.view === true) {
166
+ // Can view but not send
167
+ deny.push(PermissionFlagsBits.SendMessages);
168
+ }
169
+ }
170
+ } else {
171
+ if (perms.send === true) allow.push(PermissionFlagsBits.SendMessages);
172
+ if (perms.send === false) deny.push(PermissionFlagsBits.SendMessages);
173
+ }
174
+
175
+ // noEmbeds: block attachments, embeds, and external emojis (text-only channel)
176
+ if (chDef.noEmbeds && !['@@ Staff', '@@ Owner', '@@ Co-Owner'].includes(roleName)) {
177
+ deny.push(PermissionFlagsBits.AttachFiles);
178
+ deny.push(PermissionFlagsBits.EmbedLinks);
179
+ deny.push(PermissionFlagsBits.UseExternalEmojis);
180
+ }
181
+
182
+ // Add reactions for verify and ticket channels
183
+ if (chDef.special === 'verify' || chDef.special === 'ticket') {
184
+ allow.push(PermissionFlagsBits.AddReactions);
185
+ allow.push(PermissionFlagsBits.ReadMessageHistory);
186
+ }
187
+
188
+ overwrites.push({ id: role.id || role, allow, deny });
189
+ }
190
+
191
+ // For community-content, add Known and Helper overwrites if they aren't in the category overrides
192
+ if (chDef.special === 'community-content') {
193
+ for (const extraRole of ['@@ Known', '@@ Helper']) {
194
+ if (!overrideMap[extraRole]) {
195
+ const role = roleMap.get(extraRole);
196
+ if (role) {
197
+ overwrites.push({
198
+ id: role.id,
199
+ allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages],
200
+ deny: [],
201
+ });
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ return overwrites;
208
+ }
src/commands/createRoles.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { PermissionsBitField } = require('discord.js');
2
+ const { Roles } = require('../config');
3
+ const { successEmbed, infoEmbed, errorEmbed } = require('../utils/embeds');
4
+
5
+ module.exports = {
6
+ name: 'create roles',
7
+ async execute(client, message) {
8
+ const guildId = process.env.GUILD_ID;
9
+ const guild = await client.guilds.fetch(guildId);
10
+
11
+ await message.reply({ embeds: [infoEmbed('Creating Roles', 'Setting up role hierarchy...')] });
12
+
13
+ const createdRoles = [];
14
+
15
+ // Create roles in reverse order so the first role ends up highest
16
+ for (let i = Roles.length - 1; i >= 0; i--) {
17
+ const roleDef = Roles[i];
18
+
19
+ // Check if role already exists
20
+ const existing = guild.roles.cache.find(r => r.name === roleDef.name);
21
+ if (existing) {
22
+ createdRoles.unshift(`⚠️ **${roleDef.name}** — already exists`);
23
+ continue;
24
+ }
25
+
26
+ // Build permissions
27
+ const permissions = new PermissionsBitField();
28
+ for (const perm of roleDef.permissions) {
29
+ permissions.add(perm);
30
+ }
31
+
32
+ try {
33
+ const role = await guild.roles.create({
34
+ name: roleDef.name,
35
+ color: parseInt(roleDef.color.replace('#', ''), 16),
36
+ permissions,
37
+ hoist: roleDef.hoist,
38
+ mentionable: roleDef.mentionable,
39
+ reason: 'WSB Setup',
40
+ });
41
+ createdRoles.unshift(`✅ **${role.name}** — created`);
42
+ } catch (err) {
43
+ createdRoles.unshift(`❌ **${roleDef.name}** — failed: ${err.message}`);
44
+ console.error(`[CreateRoles] Failed to create ${roleDef.name}:`, err.message);
45
+ }
46
+ }
47
+
48
+ // Reorder roles to match hierarchy (Owner highest)
49
+ const roleOrder = [];
50
+ for (const roleDef of Roles) {
51
+ const role = guild.roles.cache.find(r => r.name === roleDef.name);
52
+ if (role) roleOrder.push(role);
53
+ }
54
+
55
+ // Set positions (higher index = higher position)
56
+ const botRole = guild.members.me.roles.highest;
57
+ let position = botRole.position - 1;
58
+
59
+ for (const role of roleOrder) {
60
+ try {
61
+ await role.setPosition(Math.max(position, 1));
62
+ position--;
63
+ } catch (err) {
64
+ // May fail if bot role isn't high enough
65
+ console.warn(`Could not reposition ${role.name}: ${err.message}`);
66
+ }
67
+ }
68
+
69
+ await message.reply({
70
+ embeds: [successEmbed('Roles Created', createdRoles.join('\n'))],
71
+ });
72
+ },
73
+ };
src/commands/permissionAudit.js ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { ChannelType, PermissionFlagsBits } = require('discord.js');
2
+ const { createEmbed } = require('../utils/embeds');
3
+ const { Colors } = require('../config');
4
+
5
+ module.exports = {
6
+ name: 'permission audit',
7
+ async execute(client, message) {
8
+ const guildId = process.env.GUILD_ID;
9
+ const guild = await client.guilds.fetch(guildId);
10
+ await guild.channels.fetch();
11
+
12
+ const issues = [];
13
+ const report = [];
14
+
15
+ const categories = guild.channels.cache
16
+ .filter(c => c.type === ChannelType.GuildCategory)
17
+ .sort((a, b) => a.position - b.position);
18
+
19
+ for (const [, category] of categories) {
20
+ const catReport = [`📁 **${category.name}**`];
21
+ const children = guild.channels.cache
22
+ .filter(c => c.parentId === category.id)
23
+ .sort((a, b) => a.position - b.position);
24
+
25
+ for (const [, channel] of children) {
26
+ const everyone = channel.permissionOverwrites.cache.get(guild.roles.everyone.id);
27
+ const everyoneView = everyone?.deny?.has(PermissionFlagsBits.ViewChannel) ? '🔒' : '👁️';
28
+ const everyoneSend = everyone?.deny?.has(PermissionFlagsBits.SendMessages) ? '🔇' : '💬';
29
+
30
+ catReport.push(` └ ${channel.name} — ${everyoneView} ${everyoneSend}`);
31
+
32
+ // Check for potential issues
33
+ if (!everyone) {
34
+ issues.push(`⚠️ ${channel.name} — no @everyone override set`);
35
+ }
36
+ }
37
+
38
+ report.push(catReport.join('\n'));
39
+ }
40
+
41
+ // Legend
42
+ const legend = '```\n👁️ = @everyone can view\n🔒 = @everyone cannot view\n💬 = @everyone can send\n🔇 = @everyone cannot send\n```';
43
+
44
+ const embed = createEmbed({
45
+ title: '🛡️ Permission Audit',
46
+ description: legend + '\n\n' + report.join('\n\n'),
47
+ color: Colors.INFO,
48
+ });
49
+
50
+ const parts = [];
51
+ const embedStr = legend + '\n\n' + report.join('\n\n');
52
+
53
+ // Discord embed limit is 4096 characters
54
+ if (embedStr.length > 4000) {
55
+ // Send as file instead
56
+ const buffer = Buffer.from(report.join('\n\n'), 'utf-8');
57
+ await message.reply({
58
+ embeds: [createEmbed({
59
+ title: '🛡️ Permission Audit',
60
+ description: `${legend}\n\nFull report attached (too long for embed).${issues.length ? '\n\n**Issues Found:**\n' + issues.join('\n') : '\n\n✅ No issues found.'}`,
61
+ color: Colors.INFO,
62
+ })],
63
+ files: [{ attachment: buffer, name: 'permission-audit.txt' }],
64
+ });
65
+ } else {
66
+ const desc = embedStr + (issues.length ? '\n\n**Issues Found:**\n' + issues.join('\n') : '\n\n✅ No issues found.');
67
+ await message.reply({
68
+ embeds: [createEmbed({ title: '🛡️ Permission Audit', description: desc, color: Colors.INFO })],
69
+ });
70
+ }
71
+ },
72
+ };
src/commands/postDisclaimer.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ const { sendDisclaimerEmbed } = require('../systems/embeds');
2
+ const { successEmbed } = require('../utils/embeds');
3
+
4
+ module.exports = {
5
+ name: 'post disclaimer',
6
+ async execute(client, message) {
7
+ await sendDisclaimerEmbed(client);
8
+ await message.reply({ embeds: [successEmbed('Disclaimer Posted', 'Disclaimer embed sent to ⚠️・disclaimer')] });
9
+ },
10
+ };
src/commands/postRules.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ const { sendRulesEmbed } = require('../systems/embeds');
2
+ const { successEmbed } = require('../utils/embeds');
3
+
4
+ module.exports = {
5
+ name: 'post rules',
6
+ async execute(client, message) {
7
+ await sendRulesEmbed(client);
8
+ await message.reply({ embeds: [successEmbed('Rules Posted', 'Rules embed sent to 📜・rules')] });
9
+ },
10
+ };
src/commands/setup.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const createRoles = require('./createRoles');
2
+ const createChannels = require('./createChannels');
3
+ const { sendVerificationEmbed } = require('../systems/verification');
4
+ const { sendTicketEmbed } = require('../systems/tickets');
5
+ const { sendDisclaimerEmbed, sendRulesEmbed } = require('../systems/embeds');
6
+ const { successEmbed, infoEmbed, createEmbed } = require('../utils/embeds');
7
+ const { Colors } = require('../config');
8
+
9
+ module.exports = {
10
+ name: 'setup server',
11
+ async execute(client, message) {
12
+ const startEmbed = createEmbed({
13
+ title: '🚀 Server Setup',
14
+ description: [
15
+ '> Starting full server setup...',
16
+ '',
17
+ '```',
18
+ 'Phase 1: Creating Roles',
19
+ 'Phase 2: Creating Channels',
20
+ 'Phase 3: Rules & Disclaimer Embeds',
21
+ 'Phase 4: Verification System',
22
+ 'Phase 5: Ticket System',
23
+ '```',
24
+ ].join('\n'),
25
+ color: Colors.PRIMARY,
26
+ });
27
+ await message.reply({ embeds: [startEmbed] });
28
+
29
+ // Phase 1: Roles
30
+ await message.reply({ embeds: [infoEmbed('Phase 1/5', 'Creating roles...')] });
31
+ await createRoles.execute(client, message);
32
+
33
+ // Phase 2: Channels
34
+ await message.reply({ embeds: [infoEmbed('Phase 2/5', 'Creating channels...')] });
35
+ await createChannels.execute(client, message);
36
+
37
+ // Phase 3: Rules & Disclaimer
38
+ await message.reply({ embeds: [infoEmbed('Phase 3/5', 'Posting rules & disclaimer embeds...')] });
39
+ try {
40
+ await sendRulesEmbed(client);
41
+ await message.reply({ embeds: [successEmbed('Rules', 'Rules embed sent to 📜・rules')] });
42
+ } catch (err) {
43
+ await message.reply({ content: `⚠️ Rules embed issue: ${err.message}` });
44
+ }
45
+ try {
46
+ await sendDisclaimerEmbed(client);
47
+ await message.reply({ embeds: [successEmbed('Disclaimer', 'Disclaimer embed sent to ⚠️・disclaimer')] });
48
+ } catch (err) {
49
+ await message.reply({ content: `⚠️ Disclaimer embed issue: ${err.message}` });
50
+ }
51
+
52
+ // Phase 4: Verification
53
+ await message.reply({ embeds: [infoEmbed('Phase 4/5', 'Setting up verification...')] });
54
+ try {
55
+ await sendVerificationEmbed(client);
56
+ await message.reply({ embeds: [successEmbed('Verification', 'Verification embed sent to ✅・verify')] });
57
+ } catch (err) {
58
+ await message.reply({ content: `⚠️ Verification setup issue: ${err.message}` });
59
+ }
60
+
61
+ // Phase 5: Tickets
62
+ await message.reply({ embeds: [infoEmbed('Phase 5/5', 'Setting up ticket system...')] });
63
+ try {
64
+ await sendTicketEmbed(client);
65
+ await message.reply({ embeds: [successEmbed('Tickets', 'Ticket embed sent to 🎫・open-ticket')] });
66
+ } catch (err) {
67
+ await message.reply({ content: `⚠️ Ticket setup issue: ${err.message}` });
68
+ }
69
+
70
+ // Done
71
+ const completeEmbed = createEmbed({
72
+ title: '✅ Setup Complete',
73
+ description: [
74
+ '> **Wyvern Softworks** server is ready!',
75
+ '',
76
+ '```',
77
+ '✓ Roles created and ordered',
78
+ '✓ All categories & channels set up',
79
+ '✓ Rules & disclaimer posted',
80
+ '✓ Verification system active',
81
+ '✓ Ticket system active',
82
+ '✓ Booster auto-role enabled',
83
+ '✓ Welcome system enabled',
84
+ '```',
85
+ ].join('\n'),
86
+ color: Colors.SUCCESS,
87
+ });
88
+ await message.reply({ embeds: [completeEmbed] });
89
+ },
90
+ };
src/commands/shutdown.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { createEmbed } = require('../utils/embeds');
2
+ const { Colors } = require('../config');
3
+ const { db } = require('../database');
4
+
5
+ module.exports = {
6
+ name: 'shutdown bot',
7
+ async execute(client, message) {
8
+ const embed = createEmbed({
9
+ title: '🔌 Shutting Down',
10
+ description: 'WSB is going offline. Goodbye!',
11
+ color: Colors.ACCENT,
12
+ });
13
+ await message.reply({ embeds: [embed] });
14
+
15
+ // Close database
16
+ db.close();
17
+
18
+ // Destroy client and exit
19
+ client.destroy();
20
+ process.exit(0);
21
+ },
22
+ };
src/commands/ticketStats.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { createEmbed } = require('../utils/embeds');
2
+ const { Colors } = require('../config');
3
+ const { stmts } = require('../database');
4
+
5
+ module.exports = {
6
+ name: 'ticket stats',
7
+ async execute(client, message) {
8
+ const stats = stmts.ticketStats.get();
9
+
10
+ const embed = createEmbed({
11
+ title: '🎫 Ticket Statistics',
12
+ description: '> Overview of all support tickets',
13
+ color: Colors.PRIMARY,
14
+ fields: [
15
+ { name: '📊 Total', value: `\`${stats.total || 0}\``, inline: true },
16
+ { name: '🟢 Open', value: `\`${stats.open_count || 0}\``, inline: true },
17
+ { name: '🔴 Closed', value: `\`${stats.closed_count || 0}\``, inline: true },
18
+ { name: '🗑️ Deleted', value: `\`${stats.deleted_count || 0}\``, inline: true },
19
+ ],
20
+ });
21
+
22
+ await message.reply({ embeds: [embed] });
23
+ },
24
+ };
src/config.js ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { PermissionFlagsBits } = require('discord.js');
2
+
3
+ // ── Theme Colors ──────────────────────────────────────────────
4
+ const Colors = {
5
+ PRIMARY: 0x9b59b6, // Purple
6
+ ACCENT: 0xe74c3c, // Red
7
+ DARK: 0x0d0d0d, // Near-black
8
+ SUCCESS: 0x2ecc71, // Green
9
+ WARNING: 0xf39c12, // Orange
10
+ INFO: 0x3498db, // Blue
11
+ MUTED: 0x2c2f33, // Dark grey
12
+ };
13
+
14
+ // ── Role Definitions (top → bottom hierarchy) ─────────────────
15
+ const Roles = [
16
+ {
17
+ name: '@@ Owner',
18
+ color: '#9b59b6',
19
+ permissions: [PermissionFlagsBits.Administrator],
20
+ hoist: true,
21
+ mentionable: true,
22
+ },
23
+ {
24
+ name: '@@ Co-Owner',
25
+ color: '#e74c3c',
26
+ permissions: [PermissionFlagsBits.Administrator],
27
+ hoist: true,
28
+ mentionable: true,
29
+ },
30
+ {
31
+ name: '@@ Staff',
32
+ color: '#e67e22',
33
+ permissions: [
34
+ PermissionFlagsBits.KickMembers,
35
+ PermissionFlagsBits.BanMembers,
36
+ PermissionFlagsBits.ManageMessages,
37
+ PermissionFlagsBits.MuteMembers,
38
+ PermissionFlagsBits.DeafenMembers,
39
+ PermissionFlagsBits.MoveMembers,
40
+ PermissionFlagsBits.ManageNicknames,
41
+ PermissionFlagsBits.ViewChannel,
42
+ PermissionFlagsBits.SendMessages,
43
+ PermissionFlagsBits.ManageChannels,
44
+ ],
45
+ hoist: true,
46
+ mentionable: true,
47
+ },
48
+ {
49
+ name: '@@ Helper',
50
+ color: '#1abc9c',
51
+ permissions: [],
52
+ hoist: true,
53
+ mentionable: true,
54
+ },
55
+ {
56
+ name: '@@ Known',
57
+ color: '#9b59b6',
58
+ permissions: [],
59
+ hoist: true,
60
+ mentionable: true,
61
+ },
62
+ {
63
+ name: '@@ Booster',
64
+ color: '#f47fff',
65
+ permissions: [],
66
+ hoist: true,
67
+ mentionable: true,
68
+ },
69
+ {
70
+ name: '@@ Verified',
71
+ color: '#2ecc71',
72
+ permissions: [],
73
+ hoist: false,
74
+ mentionable: true,
75
+ },
76
+ {
77
+ name: '@@ Buyer',
78
+ color: '#3498db',
79
+ permissions: [],
80
+ hoist: true,
81
+ mentionable: true,
82
+ },
83
+ ];
84
+
85
+ // ── Channel / Category Layout ─────────────────────────────────
86
+ // Permission key legend:
87
+ // VIEW = ViewChannel
88
+ // SEND = SendMessages
89
+ // noEmbeds = deny EmbedLinks + AttachFiles (text-only channel)
90
+ //
91
+ // PUBLIC channels (visible to @everyone without Verified):
92
+ // 📜・rules, ✅・verify, ⚠️・disclaimer
93
+ // EVERYTHING else requires @@ Verified to see.
94
+
95
+ const Categories = [
96
+ {
97
+ name: '📌・INFORMATION',
98
+ channels: [
99
+ { name: '📜・rules', type: 'text', readOnly: true, public: true },
100
+ { name: '⚠️・disclaimer', type: 'text', readOnly: true, public: true },
101
+ { name: '✅・verify', type: 'text', special: 'verify', public: true, hideFromVerified: true },
102
+ { name: '📢・announcements', type: 'text', readOnly: true },
103
+ { name: '⚡・updates', type: 'text' },
104
+ { name: '📊・polls', type: 'text', ownerOnly: true },
105
+ ],
106
+ permOverrides: () => ({
107
+ everyone: { view: false, send: false },
108
+ '@@ Verified': { view: true, send: true },
109
+ '@@ Booster': { view: true, send: true },
110
+ '@@ Staff': { view: true, send: true },
111
+ '@@ Owner': { view: true, send: true },
112
+ }),
113
+ },
114
+ {
115
+ name: '💬・COMMUNITY',
116
+ channels: [
117
+ { name: '💬・general', type: 'text', noEmbeds: true },
118
+ { name: '🎨・media', type: 'text' },
119
+ { name: '🎧・voice-chat', type: 'voice' },
120
+ ],
121
+ permOverrides: () => ({
122
+ everyone: { view: false, send: false },
123
+ '@@ Verified': { view: true, send: true },
124
+ '@@ Booster': { view: true, send: true },
125
+ '@@ Staff': { view: true, send: true },
126
+ '@@ Owner': { view: true, send: true },
127
+ }),
128
+ },
129
+ {
130
+ name: '🎫・SUPPORT & TICKETS',
131
+ channels: [
132
+ { name: '🎫・open-ticket', type: 'text', special: 'ticket' },
133
+ { name: '📂・ticket-logs', type: 'text', staffOnly: true },
134
+ ],
135
+ permOverrides: () => ({
136
+ everyone: { view: false, send: false },
137
+ '@@ Verified': { view: true, send: false },
138
+ '@@ Booster': { view: true, send: false },
139
+ '@@ Staff': { view: true, send: true },
140
+ '@@ Owner': { view: true, send: true },
141
+ }),
142
+ },
143
+ {
144
+ name: '🤖・DEVELOPMENT',
145
+ channels: [
146
+ { name: '🤖・offsets', type: 'text' },
147
+ { name: '🤖・request-offsets', type: 'text' },
148
+ { name: '🤖・offset-dumpers', type: 'text' },
149
+ { name: '🧠・dev-chat', type: 'text' },
150
+ ],
151
+ permOverrides: () => ({
152
+ everyone: { view: false, send: false },
153
+ '@@ Verified': { view: true, send: false },
154
+ '@@ Booster': { view: true, send: false },
155
+ '@@ Staff': { view: true, send: true },
156
+ '@@ Owner': { view: true, send: true },
157
+ }),
158
+ },
159
+ {
160
+ name: '🌐・RESOURCES',
161
+ channels: [
162
+ { name: '🌐・resources', type: 'text' },
163
+ { name: '🌐・free-assets', type: 'text' },
164
+ { name: '🌐・scripts', type: 'text' },
165
+ { name: '🌐・drivers', type: 'text' },
166
+ { name: '🌐・community-content', type: 'text', special: 'community-content' },
167
+ ],
168
+ permOverrides: () => ({
169
+ everyone: { view: false, send: false },
170
+ '@@ Verified': { view: true, send: false },
171
+ '@@ Booster': { view: true, send: false },
172
+ '@@ Staff': { view: true, send: true },
173
+ '@@ Owner': { view: true, send: true },
174
+ }),
175
+ },
176
+ {
177
+ name: '💜・BOOSTER ZONE',
178
+ channels: [
179
+ { name: '💜・booster-chat', type: 'text' },
180
+ { name: '💎・booster-rewards', type: 'text' },
181
+ { name: '🚀・booster-updates', type: 'text' },
182
+ ],
183
+ permOverrides: () => ({
184
+ everyone: { view: false, send: false },
185
+ '@@ Booster': { view: true, send: true },
186
+ '@@ Owner': { view: true, send: true },
187
+ '@@ Staff': { view: true, send: true },
188
+ }),
189
+ },
190
+ {
191
+ name: '🛡️・STAFF ONLY',
192
+ channels: [
193
+ { name: '🛡️・staff-chat', type: 'text' },
194
+ { name: '📁・staff-logs', type: 'text', special: 'staff-logs' },
195
+ { name: '⚙️・bot-control', type: 'text' },
196
+ ],
197
+ permOverrides: () => ({
198
+ everyone: { view: false, send: false },
199
+ '@@ Staff': { view: true, send: true },
200
+ '@@ Owner': { view: true, send: true },
201
+ }),
202
+ },
203
+ ];
204
+
205
+ module.exports = { Colors, Roles, Categories };
src/database.js ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const path = require('path');
2
+ const Database = require('better-sqlite3');
3
+ const fs = require('fs');
4
+
5
+ const DATA_DIR = path.join(__dirname, '..', 'data');
6
+ if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
7
+
8
+ const db = new Database(path.join(DATA_DIR, 'wsb.db'));
9
+
10
+ // Enable WAL mode for better concurrency
11
+ db.pragma('journal_mode = WAL');
12
+
13
+ // ── Schema ────────────────────────────────────────────────────
14
+ db.exec(`
15
+ CREATE TABLE IF NOT EXISTS tickets (
16
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+ user_id TEXT NOT NULL,
18
+ username TEXT NOT NULL,
19
+ channel_id TEXT,
20
+ status TEXT DEFAULT 'open',
21
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
22
+ closed_at DATETIME
23
+ );
24
+
25
+ CREATE TABLE IF NOT EXISTS verification_log (
26
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
27
+ user_id TEXT NOT NULL,
28
+ username TEXT NOT NULL,
29
+ action TEXT NOT NULL,
30
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
31
+ );
32
+
33
+ CREATE TABLE IF NOT EXISTS bot_state (
34
+ key TEXT PRIMARY KEY,
35
+ value TEXT NOT NULL
36
+ );
37
+ `);
38
+
39
+ // ── Prepared Statements ───────────────────────────────────────
40
+ const stmts = {
41
+ // Tickets
42
+ createTicket: db.prepare('INSERT INTO tickets (user_id, username, channel_id) VALUES (?, ?, ?)'),
43
+ closeTicket: db.prepare('UPDATE tickets SET status = ?, closed_at = CURRENT_TIMESTAMP WHERE channel_id = ?'),
44
+ getTicket: db.prepare('SELECT * FROM tickets WHERE channel_id = ?'),
45
+ getOpenTickets: db.prepare('SELECT * FROM tickets WHERE status = ?'),
46
+ getUserTicket: db.prepare('SELECT * FROM tickets WHERE user_id = ? AND status = ?'),
47
+ ticketStats: db.prepare(`
48
+ SELECT
49
+ COUNT(*) as total,
50
+ SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open_count,
51
+ SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed_count,
52
+ SUM(CASE WHEN status = 'deleted' THEN 1 ELSE 0 END) as deleted_count
53
+ FROM tickets
54
+ `),
55
+
56
+ // Verification log
57
+ logVerification: db.prepare('INSERT INTO verification_log (user_id, username, action) VALUES (?, ?, ?)'),
58
+
59
+ // Bot state (key-value)
60
+ setState: db.prepare('INSERT OR REPLACE INTO bot_state (key, value) VALUES (?, ?)'),
61
+ getState: db.prepare('SELECT value FROM bot_state WHERE key = ?'),
62
+ delState: db.prepare('DELETE FROM bot_state WHERE key = ?'),
63
+ };
64
+
65
+ module.exports = { db, stmts };
src/events/guildMemberAdd.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ const { handleMemberJoin, handleMemberLeave } = require('../systems/welcome');
2
+
3
+ module.exports = {
4
+ name: 'guildMemberAdd',
5
+ async execute(client, member) {
6
+ await handleMemberJoin(member, client);
7
+ },
8
+ };
src/events/guildMemberRemove.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ const { handleMemberLeave } = require('../systems/welcome');
2
+
3
+ module.exports = {
4
+ name: 'guildMemberRemove',
5
+ async execute(client, member) {
6
+ await handleMemberLeave(member, client);
7
+ },
8
+ };
src/events/guildMemberUpdate.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ const { handleBoostChange } = require('../systems/booster');
2
+
3
+ module.exports = {
4
+ name: 'guildMemberUpdate',
5
+ async execute(client, oldMember, newMember) {
6
+ await handleBoostChange(oldMember, newMember, client);
7
+ },
8
+ };
src/events/interactionCreate.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { handleTicketButton } = require('../systems/tickets');
2
+ const { handleDropButton } = require('../systems/drops');
3
+
4
+ module.exports = {
5
+ name: 'interactionCreate',
6
+ async execute(client, interaction) {
7
+ if (!interaction.isButton()) return;
8
+
9
+ // Try drop buttons first (DM interactions)
10
+ const dropHandled = await handleDropButton(interaction);
11
+ if (dropHandled) return;
12
+
13
+ // Then ticket buttons (server interactions)
14
+ await handleTicketButton(interaction, client);
15
+ },
16
+ };
src/events/messageCreate.js ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { ChannelType } = require('discord.js');
2
+ const { errorEmbed, infoEmbed } = require('../utils/embeds');
3
+ const { logCommand } = require('../systems/logger');
4
+ const { startDropSession, hasSession, getPrompt, handleDropMessage } = require('../systems/drops');
5
+
6
+ // Import command handlers
7
+ const setup = require('../commands/setup');
8
+ const createRoles = require('../commands/createRoles');
9
+ const createChannels = require('../commands/createChannels');
10
+ const backupLayout = require('../commands/backupLayout');
11
+ const shutdown = require('../commands/shutdown');
12
+ const ticketStats = require('../commands/ticketStats');
13
+ const permissionAudit = require('../commands/permissionAudit');
14
+ const postRules = require('../commands/postRules');
15
+ const postDisclaimer = require('../commands/postDisclaimer');
16
+
17
+ const OWNER_ID = process.env.OWNER_ID;
18
+
19
+ // Command registry
20
+ const commands = {
21
+ 'setup server': setup,
22
+ 'create roles': createRoles,
23
+ 'create channels': createChannels,
24
+ 'backup layout': backupLayout,
25
+ 'shutdown bot': shutdown,
26
+ 'ticket stats': ticketStats,
27
+ 'permission audit': permissionAudit,
28
+ 'post rules': postRules,
29
+ 'post disclaimer': postDisclaimer,
30
+ };
31
+
32
+ module.exports = {
33
+ name: 'messageCreate',
34
+ async execute(client, message) {
35
+ // ── SECURITY: Only accept DMs from the Owner ──
36
+ if (message.author.bot) return;
37
+ if (message.channel.type !== ChannelType.DM) return;
38
+ if (message.author.id !== OWNER_ID) return;
39
+
40
+ const content = message.content.trim().toLowerCase();
41
+
42
+ // ── Handle active drop session first ──
43
+ if (hasSession(message.author.id)) {
44
+ try {
45
+ const handled = await handleDropMessage(message);
46
+ if (handled) return;
47
+ } catch (err) {
48
+ console.error('[Drop Error]', err);
49
+ await message.reply({ content: `❌ Drop error: ${err.message}` }).catch(() => { });
50
+ return;
51
+ }
52
+ }
53
+
54
+ // ── Start a new drop ──
55
+ if (content === 'drop') {
56
+ await logCommand(client, 'drop');
57
+ const session = startDropSession(message.author.id);
58
+ await message.reply(getPrompt(session));
59
+ return;
60
+ }
61
+
62
+ // Show help
63
+ if (content === 'help') {
64
+ const allCommands = [...Object.keys(commands), 'drop'];
65
+ const embed = infoEmbed('WSB Commands', [
66
+ 'All commands are **DM-only** and **Owner-only**.\n',
67
+ ...allCommands.map(cmd => `\`${cmd}\``),
68
+ ].join('\n'));
69
+ return message.reply({ embeds: [embed] });
70
+ }
71
+
72
+ // Find matching command
73
+ const handler = commands[content];
74
+ if (!handler) {
75
+ if (content.startsWith('setup') || content.startsWith('create') ||
76
+ content.startsWith('backup') || content.startsWith('shutdown') ||
77
+ content.startsWith('ticket') || content.startsWith('permission') ||
78
+ content.startsWith('post') || content.startsWith('drop')) {
79
+ return message.reply({ embeds: [errorEmbed('Unknown Command', `Did you mean one of these?\n${[...Object.keys(commands), 'drop'].map(c => `\`${c}\``).join('\n')}`)] });
80
+ }
81
+ return; // Silently ignore non-command messages
82
+ }
83
+
84
+ try {
85
+ await logCommand(client, content);
86
+ await handler.execute(client, message);
87
+ } catch (err) {
88
+ console.error(`[Command Error] ${content}:`, err);
89
+ await message.reply({ embeds: [errorEmbed('Error', `Command failed: \`${err.message}\``)] }).catch(() => { });
90
+ }
91
+ },
92
+ };
src/events/messageReactionAdd.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { handleVerifyReaction } = require('../systems/verification');
2
+
3
+ module.exports = {
4
+ name: 'messageReactionAdd',
5
+ async execute(client, reaction, user) {
6
+ if (user.bot) return;
7
+
8
+ // Handle partial reactions (uncached messages)
9
+ if (reaction.partial) {
10
+ try { await reaction.fetch(); } catch { return; }
11
+ }
12
+ if (reaction.message.partial) {
13
+ try { await reaction.message.fetch(); } catch { return; }
14
+ }
15
+
16
+ // Handle verification
17
+ await handleVerifyReaction(reaction, user, client);
18
+ },
19
+ };
src/events/messageReactionRemove.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { handleVerifyReactionRemove } = require('../systems/verification');
2
+
3
+ module.exports = {
4
+ name: 'messageReactionRemove',
5
+ async execute(client, reaction, user) {
6
+ if (user.bot) return;
7
+
8
+ if (reaction.partial) {
9
+ try { await reaction.fetch(); } catch { return; }
10
+ }
11
+ if (reaction.message.partial) {
12
+ try { await reaction.message.fetch(); } catch { return; }
13
+ }
14
+
15
+ await handleVerifyReactionRemove(reaction, user, client);
16
+ },
17
+ };
src/events/ready.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { ActivityType } = require('discord.js');
2
+
3
+ module.exports = {
4
+ name: 'clientReady',
5
+ once: true,
6
+ execute(client) {
7
+ console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
8
+ console.log(` 🟣 WSB — Wyvern Softworks Bot`);
9
+ console.log(` ✅ Logged in as ${client.user.tag}`);
10
+ console.log(` 🏠 Serving ${client.guilds.cache.size} guild(s)`);
11
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
12
+
13
+ client.user.setPresence({
14
+ activities: [{ name: 'Wyvern Softworks', type: ActivityType.Watching }],
15
+ status: 'dnd',
16
+ });
17
+ },
18
+ };
src/index.js ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('dotenv').config();
2
+
3
+ const {
4
+ Client,
5
+ GatewayIntentBits,
6
+ Partials,
7
+ } = require('discord.js');
8
+
9
+ // ── Validate Environment ──────────────────────────────────────
10
+ const required = ['BOT_TOKEN', 'OWNER_ID', 'GUILD_ID'];
11
+ for (const key of required) {
12
+ if (!process.env[key] || process.env[key].includes('YOUR_')) {
13
+ console.error(`❌ Missing or placeholder env var: ${key}`);
14
+ console.error(' Please fill in your .env file before starting.');
15
+ process.exit(1);
16
+ }
17
+ }
18
+
19
+ // ── Create Client ─────────────────────────────────────────────
20
+ const client = new Client({
21
+ intents: [
22
+ GatewayIntentBits.Guilds,
23
+ GatewayIntentBits.GuildMembers,
24
+ GatewayIntentBits.GuildMessages,
25
+ GatewayIntentBits.GuildMessageReactions,
26
+ GatewayIntentBits.DirectMessages,
27
+ GatewayIntentBits.MessageContent,
28
+ GatewayIntentBits.GuildPresences,
29
+ ],
30
+ partials: [
31
+ Partials.Message,
32
+ Partials.Channel,
33
+ Partials.Reaction,
34
+ Partials.User,
35
+ Partials.GuildMember,
36
+ ],
37
+ });
38
+
39
+ // ── Load Event Handlers ───────────────────────────────────────
40
+ const events = [
41
+ require('./events/ready'),
42
+ require('./events/messageCreate'),
43
+ require('./events/messageReactionAdd'),
44
+ require('./events/messageReactionRemove'),
45
+ require('./events/guildMemberUpdate'),
46
+ require('./events/guildMemberAdd'),
47
+ require('./events/guildMemberRemove'),
48
+ require('./events/interactionCreate'),
49
+ ];
50
+
51
+ for (const event of events) {
52
+ if (event.once) {
53
+ client.once(event.name, (...args) => event.execute(client, ...args));
54
+ } else {
55
+ client.on(event.name, (...args) => event.execute(client, ...args));
56
+ }
57
+ }
58
+
59
+ // ── Error Handling ────────────────────────────────────────────
60
+ client.on('error', (err) => {
61
+ console.error('[Client Error]', err);
62
+ });
63
+
64
+ process.on('unhandledRejection', (err) => {
65
+ console.error('[Unhandled Rejection]', err);
66
+ });
67
+
68
+ process.on('uncaughtException', (err) => {
69
+ console.error('[Uncaught Exception]', err);
70
+ process.exit(1);
71
+ });
72
+
73
+ // ── Keep-Alive HTTP Server (for Render / Glitch + UptimeRobot) ─
74
+ const http = require('http');
75
+ const PORT = process.env.PORT || 3000;
76
+
77
+ http.createServer((req, res) => {
78
+ res.writeHead(200, { 'Content-Type': 'application/json' });
79
+ res.end(JSON.stringify({
80
+ status: 'alive',
81
+ bot: client.user?.tag || 'starting...',
82
+ uptime: Math.floor(process.uptime()) + 's',
83
+ }));
84
+ }).listen(PORT, () => {
85
+ console.log(` 🌐 Keep-alive server on port ${PORT}`);
86
+ });
87
+
88
+ // ── Login ─────────────────────────────────────────────────────
89
+ client.login(process.env.BOT_TOKEN);
src/systems/booster.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { logRoleChange } = require('./logger');
2
+
3
+ /**
4
+ * Detect server boost changes via guildMemberUpdate.
5
+ * If a member starts boosting → add Booster role.
6
+ * If a member stops boosting → remove Booster role.
7
+ */
8
+ async function handleBoostChange(oldMember, newMember, client) {
9
+ const wasBoosting = oldMember.premiumSince !== null;
10
+ const isBoosting = newMember.premiumSince !== null;
11
+
12
+ if (wasBoosting === isBoosting) return; // No change
13
+
14
+ const boosterRole = newMember.guild.roles.cache.find(r => r.name === '@@ Booster');
15
+ if (!boosterRole) return;
16
+
17
+ if (!wasBoosting && isBoosting) {
18
+ // Started boosting
19
+ await newMember.roles.add(boosterRole).catch(() => { });
20
+ await logRoleChange(client, {
21
+ user: newMember.user,
22
+ role: 'Booster',
23
+ action: 'added',
24
+ });
25
+ } else if (wasBoosting && !isBoosting) {
26
+ // Stopped boosting
27
+ await newMember.roles.remove(boosterRole).catch(() => { });
28
+ await logRoleChange(client, {
29
+ user: newMember.user,
30
+ role: 'Booster',
31
+ action: 'removed',
32
+ });
33
+ }
34
+ }
35
+
36
+ module.exports = { handleBoostChange };
src/systems/drops.js ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const {
2
+ ActionRowBuilder,
3
+ ButtonBuilder,
4
+ ButtonStyle,
5
+ StringSelectMenuBuilder,
6
+ } = require('discord.js');
7
+ const { createEmbed } = require('../utils/embeds');
8
+ const { Colors } = require('../config');
9
+
10
+ // Active drop sessions: Map<userId, session>
11
+ const activeSessions = new Map();
12
+
13
+ const STEPS = ['title', 'file', 'warnings', 'status', 'about', 'image', 'preview'];
14
+
15
+ /**
16
+ * Start a new drop session for the owner.
17
+ */
18
+ function startDropSession(userId) {
19
+ const session = {
20
+ step: 'title',
21
+ title: null,
22
+ file: null, // { url, name, size }
23
+ warnings: null,
24
+ status: null, // 'checked' or 'unchecked'
25
+ about: null,
26
+ image: null, // url or null
27
+ channelId: null,
28
+ };
29
+ activeSessions.set(userId, session);
30
+ return session;
31
+ }
32
+
33
+ /**
34
+ * Check if a user has an active drop session.
35
+ */
36
+ function hasSession(userId) {
37
+ return activeSessions.has(userId);
38
+ }
39
+
40
+ /**
41
+ * Get the current prompt message for the active step.
42
+ */
43
+ function getPrompt(session) {
44
+ switch (session.step) {
45
+ case 'title':
46
+ return {
47
+ embeds: [createEmbed({
48
+ title: '📦 New Drop — Step 1/6',
49
+ description: '> **What is the title for this drop?**\n\nType the title below.',
50
+ color: Colors.PRIMARY,
51
+ footer: 'Type "cancel" at any time to abort',
52
+ })],
53
+ };
54
+
55
+ case 'file':
56
+ return {
57
+ embeds: [createEmbed({
58
+ title: '📦 New Drop — Step 2/6',
59
+ description: '> **Upload the file for this drop.**\n\nAttach the file to your next message.',
60
+ color: Colors.PRIMARY,
61
+ footer: `Title: ${session.title}`,
62
+ })],
63
+ };
64
+
65
+ case 'warnings':
66
+ return {
67
+ embeds: [createEmbed({
68
+ title: '📦 New Drop — Step 3/6',
69
+ description: '> **Any warnings for this drop?**\n\nType warnings below, or type `none` to skip.',
70
+ color: Colors.WARNING,
71
+ footer: `Title: ${session.title}`,
72
+ })],
73
+ };
74
+
75
+ case 'status':
76
+ return {
77
+ embeds: [createEmbed({
78
+ title: '📦 New Drop — Step 4/6',
79
+ description: '> **Is this source checked or unchecked?**\n\nClick a button below.',
80
+ color: Colors.PRIMARY,
81
+ footer: `Title: ${session.title}`,
82
+ })],
83
+ components: [new ActionRowBuilder().addComponents(
84
+ new ButtonBuilder()
85
+ .setCustomId('drop_checked')
86
+ .setLabel('✅ Checked')
87
+ .setStyle(ButtonStyle.Success),
88
+ new ButtonBuilder()
89
+ .setCustomId('drop_unchecked')
90
+ .setLabel('⚠️ Unchecked')
91
+ .setStyle(ButtonStyle.Danger),
92
+ )],
93
+ };
94
+
95
+ case 'about':
96
+ return {
97
+ embeds: [createEmbed({
98
+ title: '📦 New Drop — Step 5/6',
99
+ description: '> **Describe this source.**\n\nWrite a short description about what this is.',
100
+ color: Colors.PRIMARY,
101
+ footer: `Title: ${session.title}`,
102
+ })],
103
+ };
104
+
105
+ case 'image':
106
+ return {
107
+ embeds: [createEmbed({
108
+ title: '📦 New Drop — Step 6/6',
109
+ description: '> **Reference image?**\n\nUpload an image or type `none` to skip.',
110
+ color: Colors.PRIMARY,
111
+ footer: `Title: ${session.title}`,
112
+ })],
113
+ };
114
+
115
+ default:
116
+ return null;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Build the final preview embed for the drop.
122
+ */
123
+ function buildDropEmbed(session) {
124
+ const statusIcon = session.status === 'checked' ? '✅' : '⚠️';
125
+ const statusText = session.status === 'checked' ? 'Verified / Checked' : 'Unchecked — Use at your own risk';
126
+
127
+ const lines = [
128
+ `${statusIcon} **${statusText}**`,
129
+ '',
130
+ '> **About**',
131
+ `> ${session.about}`,
132
+ '',
133
+ ];
134
+
135
+ if (session.warnings && session.warnings.toLowerCase() !== 'none') {
136
+ lines.push('⚠️ **Warnings**');
137
+ lines.push(`> ${session.warnings}`);
138
+ lines.push('');
139
+ }
140
+
141
+ lines.push('```');
142
+ lines.push(`📁 File: ${session.file.name}`);
143
+ lines.push(`📊 Size: ${formatBytes(session.file.size)}`);
144
+ lines.push(`🔎 Status: ${statusText}`);
145
+ lines.push('```');
146
+ lines.push('');
147
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
148
+ lines.push('*Wyvern Softworks — Educational Use Only*');
149
+
150
+ const embedData = {
151
+ title: `📦 ${session.title}`,
152
+ description: lines.join('\n'),
153
+ color: session.status === 'checked' ? Colors.SUCCESS : Colors.WARNING,
154
+ footer: 'Wyvern Softworks • ' + new Date().toLocaleDateString('en-US', { day: 'numeric', month: 'short', year: 'numeric' }),
155
+ };
156
+
157
+ if (session.image) {
158
+ embedData.image = session.image;
159
+ }
160
+
161
+ return createEmbed(embedData);
162
+ }
163
+
164
+ /**
165
+ * Build the download button row for a drop.
166
+ */
167
+ function buildDownloadButton(session) {
168
+ return new ActionRowBuilder().addComponents(
169
+ new ButtonBuilder()
170
+ .setLabel('📥 Download')
171
+ .setStyle(ButtonStyle.Link)
172
+ .setURL(session.file.url),
173
+ );
174
+ }
175
+
176
+ /**
177
+ * Handle a message in an active drop session.
178
+ * Returns true if handled, false if not.
179
+ */
180
+ async function handleDropMessage(message) {
181
+ const userId = message.author.id;
182
+ const session = activeSessions.get(userId);
183
+ if (!session) return false;
184
+
185
+ const content = message.content.trim();
186
+
187
+ // Cancel
188
+ if (content.toLowerCase() === 'cancel') {
189
+ activeSessions.delete(userId);
190
+ await message.reply({ embeds: [createEmbed({ title: '❌ Drop Cancelled', color: Colors.ACCENT })] });
191
+ return true;
192
+ }
193
+
194
+ switch (session.step) {
195
+ case 'title':
196
+ session.title = content;
197
+ session.step = 'file';
198
+ await message.reply(getPrompt(session));
199
+ return true;
200
+
201
+ case 'file':
202
+ if (message.attachments.size === 0) {
203
+ await message.reply({ content: '❌ Please attach a file.' });
204
+ return true;
205
+ }
206
+ const att = message.attachments.first();
207
+ session.file = { url: att.url, name: att.name, size: att.size };
208
+ session.step = 'warnings';
209
+ await message.reply(getPrompt(session));
210
+ return true;
211
+
212
+ case 'warnings':
213
+ session.warnings = content.toLowerCase() === 'none' ? null : content;
214
+ session.step = 'status';
215
+ await message.reply(getPrompt(session));
216
+ return true;
217
+
218
+ case 'about':
219
+ session.about = content;
220
+ session.step = 'image';
221
+ await message.reply(getPrompt(session));
222
+ return true;
223
+
224
+ case 'image':
225
+ if (content.toLowerCase() === 'none') {
226
+ session.image = null;
227
+ } else if (message.attachments.size > 0) {
228
+ session.image = message.attachments.first().url;
229
+ } else {
230
+ session.image = null;
231
+ }
232
+
233
+ // Show preview
234
+ session.step = 'preview';
235
+ const previewEmbed = buildDropEmbed(session);
236
+ const row = new ActionRowBuilder().addComponents(
237
+ new ButtonBuilder()
238
+ .setCustomId('drop_approve')
239
+ .setLabel('✅ Approve & Post')
240
+ .setStyle(ButtonStyle.Success),
241
+ new ButtonBuilder()
242
+ .setCustomId('drop_cancel')
243
+ .setLabel('❌ Cancel')
244
+ .setStyle(ButtonStyle.Danger),
245
+ );
246
+
247
+ await message.reply({
248
+ content: '**📋 Preview — This is how the drop will look:**',
249
+ embeds: [previewEmbed],
250
+ components: [buildDownloadButton(session), row],
251
+ });
252
+ return true;
253
+
254
+ case 'channel':
255
+ // User provides channel ID to post in
256
+ const channelId = content.replace(/[<#>]/g, '');
257
+ session.channelId = channelId;
258
+
259
+ try {
260
+ const guild = message.client.guilds.cache.first();
261
+ const channel = await guild.channels.fetch(channelId);
262
+ if (!channel) throw new Error('Channel not found');
263
+
264
+ const dropEmbed = buildDropEmbed(session);
265
+ const downloadRow = buildDownloadButton(session);
266
+ await channel.send({
267
+ embeds: [dropEmbed],
268
+ components: [downloadRow],
269
+ });
270
+
271
+ activeSessions.delete(userId);
272
+ await message.reply({
273
+ embeds: [createEmbed({
274
+ title: '✅ Drop Posted!',
275
+ description: `Successfully posted to <#${channelId}>`,
276
+ color: Colors.SUCCESS,
277
+ })],
278
+ });
279
+ } catch (err) {
280
+ await message.reply({ content: `❌ Failed to post: ${err.message}\nPlease send a valid channel ID.` });
281
+ }
282
+ return true;
283
+
284
+ default:
285
+ return false;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Handle drop button interactions (status select, approve/cancel).
291
+ */
292
+ async function handleDropButton(interaction) {
293
+ const userId = interaction.user.id;
294
+ const session = activeSessions.get(userId);
295
+ if (!session) return false;
296
+
297
+ const { customId } = interaction;
298
+
299
+ if (customId === 'drop_checked' || customId === 'drop_unchecked') {
300
+ session.status = customId === 'drop_checked' ? 'checked' : 'unchecked';
301
+ session.step = 'about';
302
+ await interaction.update({
303
+ embeds: [createEmbed({
304
+ title: `${customId === 'drop_checked' ? '✅' : '⚠️'} Status: ${session.status}`,
305
+ color: customId === 'drop_checked' ? Colors.SUCCESS : Colors.WARNING,
306
+ })], components: []
307
+ });
308
+ await interaction.followUp(getPrompt(session));
309
+ return true;
310
+ }
311
+
312
+ if (customId === 'drop_approve') {
313
+ session.step = 'channel';
314
+ await interaction.update({
315
+ embeds: [interaction.message.embeds[0]],
316
+ components: [],
317
+ });
318
+ await interaction.followUp({
319
+ embeds: [createEmbed({
320
+ title: '📤 Where to post?',
321
+ description: '> Send the **channel ID** where this drop should be posted.\n\nYou can right-click a channel → Copy Channel ID.',
322
+ color: Colors.INFO,
323
+ })],
324
+ });
325
+ return true;
326
+ }
327
+
328
+ if (customId === 'drop_cancel') {
329
+ activeSessions.delete(userId);
330
+ await interaction.update({
331
+ embeds: [createEmbed({ title: '❌ Drop Cancelled', color: Colors.ACCENT })],
332
+ components: [],
333
+ });
334
+ return true;
335
+ }
336
+
337
+ return false;
338
+ }
339
+
340
+ /**
341
+ * Format bytes to human readable string.
342
+ */
343
+ function formatBytes(bytes) {
344
+ if (bytes === 0) return '0 B';
345
+ const k = 1024;
346
+ const sizes = ['B', 'KB', 'MB', 'GB'];
347
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
348
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
349
+ }
350
+
351
+ module.exports = {
352
+ startDropSession,
353
+ hasSession,
354
+ getPrompt,
355
+ handleDropMessage,
356
+ handleDropButton,
357
+ buildDropEmbed,
358
+ };
src/systems/embeds.js ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { createEmbed } = require('../utils/embeds');
2
+ const { Colors } = require('../config');
3
+ const { stmts } = require('../database');
4
+
5
+ /**
6
+ * Send the disclaimer embed to ⚠️・disclaimer channel.
7
+ */
8
+ async function sendDisclaimerEmbed(client) {
9
+ const row = stmts.getState.get('channel_⚠️・disclaimer');
10
+ if (!row) throw new Error('Disclaimer channel not found in bot state.');
11
+
12
+ const channel = await client.channels.fetch(row.value);
13
+ if (!channel) throw new Error('Could not fetch disclaimer channel.');
14
+
15
+ // Fetch the @@ Owner role for a proper mention
16
+ const guild = channel.guild;
17
+ const ownerRole = guild.roles.cache.find(r => r.name === '@@ Owner');
18
+ const ownerMention = ownerRole ? `<@&${ownerRole.id}>` : '**Server Owner**';
19
+
20
+ const embed = createEmbed({
21
+ title: '🔴 Disclaimer 🔴',
22
+ description: [
23
+ '> **Wyvern Softworks** and all associated contributors do **NOT** endorse or support any form of cheating, unlawful activity, or malicious conduct.\n',
24
+
25
+ '```',
26
+ 'Our server provides source code and resources for',
27
+ 'penetration testing tools and security research,',
28
+ 'intended strictly for educational purposes and',
29
+ 'authorized testing in controlled environments.',
30
+ '```\n',
31
+
32
+ '> All materials shared within this server are provided **as-is** for learning and development purposes only. Users are solely responsible for how they use the resources provided.\n',
33
+
34
+ '**By joining this server, you acknowledge and agree that:**',
35
+ '• You will use all resources **legally and ethically**',
36
+ '• You will **not** use any tools for unauthorized access',
37
+ '• You accept full responsibility for your own actions',
38
+ '• Wyvern Softworks bears **no liability** for misuse\n',
39
+
40
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
41
+ '> **Wyvern Softworks** is not affiliated with any game studio, publisher, or platform.',
42
+ `> For legal inquiries or DMCA notices, contact the server ${ownerMention}.`,
43
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
44
+ ].join('\n'),
45
+ color: 0xe74c3c,
46
+ footer: 'Wyvern Softworks — Read before proceeding',
47
+ });
48
+
49
+ await channel.send({ embeds: [embed] });
50
+ }
51
+
52
+ /**
53
+ * Send the rules embed to 📜・rules channel.
54
+ */
55
+ async function sendRulesEmbed(client) {
56
+ const row = stmts.getState.get('channel_📜・rules');
57
+ if (!row) throw new Error('Rules channel not found in bot state.');
58
+
59
+ const channel = await client.channels.fetch(row.value);
60
+ if (!channel) throw new Error('Could not fetch rules channel.');
61
+
62
+ const embed = createEmbed({
63
+ title: '📜 Server Rules',
64
+ description: [
65
+ '> Welcome to **Wyvern Softworks**. To maintain a safe and productive community, all members must follow these rules.\n',
66
+
67
+ '**1.** 🤝 **Respect Everyone** — Treat all members with respect. No harassment, hate speech, racism, sexism, or personal attacks.',
68
+ '**2.** 🚫 **No Spam** — Do not spam messages, emojis, images, or links in any channel.',
69
+ '**3.** 🔇 **No NSFW Content** — Absolutely no NSFW, gore, or disturbing content anywhere in the server.',
70
+ '**4.** 📛 **Appropriate Names** — Keep your username and nickname clean and appropriate.',
71
+ '**5.** 📢 **No Self-Promotion** — Do not advertise other servers, products, or services without explicit permission from Staff.',
72
+ '**6.** 🧠 **Use Channels Correctly** — Post content in the appropriate channels. Off-topic messages will be removed.',
73
+ '**7.** 🔒 **No Leaking** — Do not share, redistribute, or leak any paid or exclusive content from this server.',
74
+ '**8.** ⚖️ **No Illegal Activity** — Do not share, promote, or discuss anything that violates local or international law.',
75
+ '**9.** 🛡️ **No Cheating Encouragement** — We provide pentesting tools for educational purposes only. Do not encourage or discuss using tools for cheating in live environments.',
76
+ '**10.** 🤖 **No Alt Accounts** — Using alternate accounts to bypass bans or restrictions will result in a permanent ban.',
77
+ '**11.** 👮 **Listen to Staff** — Staff decisions are final. If you disagree, open a ticket to appeal.',
78
+ '**12.** 🎫 **Ticket System** — Use the ticket system for support. Do not DM staff directly unless asked.',
79
+ '**13.** 🔊 **Voice Chat Etiquette** — No mic spamming, soundboards, or disruptive audio in voice channels.',
80
+ '**14.** 💬 **English Only** — Please communicate in English in all public channels.',
81
+ '**15.** 📎 **No Malicious Files** — Do not upload malware, viruses, IP loggers, or any harmful files.',
82
+ '**16.** 📖 **Follow Discord ToS** — All members must abide by Discord\'s Terms of Service and Community Guidelines at all times.\n',
83
+
84
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
85
+ '> **Violating any of the above rules may result in a mute, kick, or permanent ban at the discretion of Staff.**\n',
86
+
87
+ '🔗 **Discord Terms of Service**',
88
+ '[Terms of Service](https://discord.com/terms)',
89
+ '[Community Guidelines](https://discord.com/guidelines)',
90
+ '[Privacy Policy](https://discord.com/privacy)',
91
+ ].join('\n'),
92
+ color: 0x9b59b6,
93
+ footer: 'Wyvern Softworks — Last updated ' + new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' }),
94
+ });
95
+
96
+ await channel.send({ embeds: [embed] });
97
+ }
98
+
99
+ module.exports = { sendDisclaimerEmbed, sendRulesEmbed };
src/systems/logger.js ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { stmts } = require('../database');
2
+ const { logEmbed } = require('../utils/embeds');
3
+ const { Colors } = require('../config');
4
+
5
+ /**
6
+ * Log an action to the staff-logs channel.
7
+ */
8
+ async function log(client, { title, description, fields = [], color = Colors.MUTED }) {
9
+ try {
10
+ const row = stmts.getState.get('channel_staff-logs');
11
+ if (!row) return;
12
+
13
+ const channelId = row.value;
14
+ const channel = await client.channels.fetch(channelId).catch(() => null);
15
+ if (!channel) return;
16
+
17
+ const embed = logEmbed({ title, description, color, fields });
18
+ await channel.send({ embeds: [embed] });
19
+ } catch (err) {
20
+ console.error('[Logger] Failed to log:', err.message);
21
+ }
22
+ }
23
+
24
+ // Convenience wrappers
25
+ async function logVerification(client, user, action) {
26
+ stmts.logVerification.run(user.id, user.tag, action);
27
+ await log(client, {
28
+ title: '🔐 Verification',
29
+ description: `**${user.tag}** (${user.id})`,
30
+ fields: [{ name: 'Action', value: action, inline: true }],
31
+ color: action === 'verified' ? Colors.SUCCESS : Colors.WARNING,
32
+ });
33
+ }
34
+
35
+ async function logTicket(client, { user, action, channelName }) {
36
+ await log(client, {
37
+ title: '🎫 Ticket',
38
+ description: `**${user.tag}** (${user.id})`,
39
+ fields: [
40
+ { name: 'Action', value: action, inline: true },
41
+ { name: 'Channel', value: channelName || 'N/A', inline: true },
42
+ ],
43
+ color: Colors.INFO,
44
+ });
45
+ }
46
+
47
+ async function logCommand(client, command) {
48
+ await log(client, {
49
+ title: '⚙️ Command Executed',
50
+ description: `\`${command}\``,
51
+ fields: [{ name: 'Executed by', value: 'Owner (DM)', inline: true }],
52
+ color: Colors.PRIMARY,
53
+ });
54
+ }
55
+
56
+ async function logRoleChange(client, { user, role, action }) {
57
+ await log(client, {
58
+ title: '👤 Role Change',
59
+ description: `**${user.tag}** (${user.id})`,
60
+ fields: [
61
+ { name: 'Role', value: role, inline: true },
62
+ { name: 'Action', value: action, inline: true },
63
+ ],
64
+ color: action === 'added' ? Colors.SUCCESS : Colors.WARNING,
65
+ });
66
+ }
67
+
68
+ module.exports = { log, logVerification, logTicket, logCommand, logRoleChange };
src/systems/tickets.js ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const {
2
+ ChannelType,
3
+ PermissionFlagsBits,
4
+ ActionRowBuilder,
5
+ ButtonBuilder,
6
+ ButtonStyle,
7
+ } = require('discord.js');
8
+ const { createEmbed } = require('../utils/embeds');
9
+ const { Colors } = require('../config');
10
+ const { stmts } = require('../database');
11
+ const { logTicket } = require('./logger');
12
+
13
+ /**
14
+ * Send the ticket embed with a "Create a Ticket" button to 🎫・open-ticket channel.
15
+ */
16
+ async function sendTicketEmbed(client) {
17
+ const row = stmts.getState.get('channel_🎫・open-ticket');
18
+ if (!row) throw new Error('Ticket channel not found in bot state.');
19
+
20
+ const channel = await client.channels.fetch(row.value);
21
+ if (!channel) throw new Error('Could not fetch ticket channel.');
22
+
23
+ const embed = createEmbed({
24
+ title: '🎫 Support Tickets',
25
+ description: [
26
+ '> Need help? Open a support ticket!',
27
+ '',
28
+ 'Click the button below to create a private ticket.',
29
+ '',
30
+ '```',
31
+ '• A private channel will be created for you',
32
+ '• Staff will assist you as soon as possible',
33
+ '• Only you and staff can see the ticket',
34
+ '```',
35
+ ].join('\n'),
36
+ color: Colors.PRIMARY,
37
+ });
38
+
39
+ const actionRow = new ActionRowBuilder().addComponents(
40
+ new ButtonBuilder()
41
+ .setCustomId('ticket_create')
42
+ .setLabel('Create a Ticket')
43
+ .setEmoji('🎫')
44
+ .setStyle(ButtonStyle.Primary),
45
+ );
46
+
47
+ const msg = await channel.send({ embeds: [embed], components: [actionRow] });
48
+
49
+ stmts.setState.run('ticket_message_id', msg.id);
50
+ stmts.setState.run('ticket_channel_id', channel.id);
51
+
52
+ return msg;
53
+ }
54
+
55
+ /**
56
+ * Create a ticket channel for a user.
57
+ */
58
+ async function createTicket(guild, user, client) {
59
+ // Check if user already has an open ticket
60
+ const existing = stmts.getUserTicket.get(user.id, 'open');
61
+ if (existing) return null;
62
+
63
+ const staffRole = guild.roles.cache.find(r => r.name === '@@ Staff');
64
+ const ownerRole = guild.roles.cache.find(r => r.name === '@@ Owner');
65
+
66
+ // Find the SUPPORT & TICKETS category
67
+ const category = guild.channels.cache.find(
68
+ c => c.type === ChannelType.GuildCategory && c.name.includes('SUPPORT')
69
+ );
70
+
71
+ const channelName = `ticket-${user.username.toLowerCase().replace(/[^a-z0-9]/g, '')}`;
72
+
73
+ const overwrites = [
74
+ { id: guild.id, deny: [PermissionFlagsBits.ViewChannel] },
75
+ { id: user.id, allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ReadMessageHistory] },
76
+ ];
77
+ if (staffRole) overwrites.push({ id: staffRole.id, allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ReadMessageHistory, PermissionFlagsBits.ManageMessages] });
78
+ if (ownerRole) overwrites.push({ id: ownerRole.id, allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ReadMessageHistory, PermissionFlagsBits.ManageMessages] });
79
+
80
+ const ticketChannel = await guild.channels.create({
81
+ name: channelName,
82
+ type: ChannelType.GuildText,
83
+ parent: category?.id,
84
+ permissionOverwrites: overwrites,
85
+ });
86
+
87
+ // Save to database
88
+ stmts.createTicket.run(user.id, user.tag, ticketChannel.id);
89
+
90
+ // Send welcome embed with action buttons
91
+ const embed = createEmbed({
92
+ title: '🎫 Ticket Opened',
93
+ description: [
94
+ `Welcome <@${user.id}>!`,
95
+ '',
96
+ 'A staff member will be with you shortly.',
97
+ 'Please describe your issue below.',
98
+ '',
99
+ '> Use the buttons to manage this ticket.',
100
+ ].join('\n'),
101
+ color: Colors.PRIMARY,
102
+ });
103
+
104
+ const row = new ActionRowBuilder().addComponents(
105
+ new ButtonBuilder()
106
+ .setCustomId('ticket_close')
107
+ .setLabel('Close Ticket')
108
+ .setEmoji('🔒')
109
+ .setStyle(ButtonStyle.Secondary),
110
+ new ButtonBuilder()
111
+ .setCustomId('ticket_transcript')
112
+ .setLabel('Transcript')
113
+ .setEmoji('📄')
114
+ .setStyle(ButtonStyle.Primary),
115
+ new ButtonBuilder()
116
+ .setCustomId('ticket_delete')
117
+ .setLabel('Delete Ticket')
118
+ .setEmoji('🗑️')
119
+ .setStyle(ButtonStyle.Danger),
120
+ );
121
+
122
+ await ticketChannel.send({ embeds: [embed], components: [row] });
123
+ await logTicket(client, { user, action: 'opened', channelName });
124
+
125
+ return ticketChannel;
126
+ }
127
+
128
+ /**
129
+ * Generate a transcript of a ticket channel.
130
+ */
131
+ async function generateTranscript(channel) {
132
+ const messages = [];
133
+ let lastId;
134
+
135
+ // Fetch all messages (paginated)
136
+ while (true) {
137
+ const batch = await channel.messages.fetch({ limit: 100, ...(lastId ? { before: lastId } : {}) });
138
+ if (batch.size === 0) break;
139
+ messages.push(...batch.values());
140
+ lastId = batch.last().id;
141
+ }
142
+
143
+ messages.reverse();
144
+
145
+ const lines = messages.map(m => {
146
+ const time = m.createdAt.toISOString().replace('T', ' ').slice(0, 19);
147
+ return `[${time}] ${m.author.tag}: ${m.content || '(embed/attachment)'}`;
148
+ });
149
+
150
+ return lines.join('\n') || '(no messages)';
151
+ }
152
+
153
+ /**
154
+ * Handle ticket button interactions.
155
+ */
156
+ async function handleTicketButton(interaction, client) {
157
+ const { customId, channel, guild, member } = interaction;
158
+
159
+ // Handle "Create a Ticket" button
160
+ if (customId === 'ticket_create') {
161
+ await interaction.deferReply({ ephemeral: true });
162
+ const user = interaction.user;
163
+ const ticketChannel = await createTicket(guild, user, client);
164
+
165
+ if (!ticketChannel) {
166
+ await interaction.editReply({ content: '❌ You already have an open ticket.' });
167
+ } else {
168
+ await interaction.editReply({ content: `✅ Ticket created: <#${ticketChannel.id}>` });
169
+ }
170
+ return true;
171
+ }
172
+
173
+ if (!['ticket_close', 'ticket_delete', 'ticket_transcript'].includes(customId)) return false;
174
+
175
+ const ticket = stmts.getTicket.get(channel.id);
176
+ if (!ticket) {
177
+ await interaction.reply({ content: '❌ This is not a ticket channel.', ephemeral: true });
178
+ return true;
179
+ }
180
+
181
+ // Permission check: only staff, owner, or ticket creator
182
+ const isStaff = member.roles.cache.some(r => ['@@ Staff', '@@ Owner', '@@ Co-Owner'].includes(r.name));
183
+ const isCreator = ticket.user_id === member.id;
184
+ if (!isStaff && !isCreator) {
185
+ await interaction.reply({ content: '❌ You do not have permission.', ephemeral: true });
186
+ return true;
187
+ }
188
+
189
+ if (customId === 'ticket_transcript') {
190
+ await interaction.deferReply({ ephemeral: true });
191
+ const transcript = await generateTranscript(channel);
192
+ const buffer = Buffer.from(transcript, 'utf-8');
193
+ await interaction.editReply({
194
+ content: '📄 Transcript generated.',
195
+ files: [{ attachment: buffer, name: `transcript-${channel.name}.txt` }],
196
+ });
197
+ return true;
198
+ }
199
+
200
+ if (customId === 'ticket_close') {
201
+ stmts.closeTicket.run('closed', channel.id);
202
+
203
+ // Save transcript to ticket-logs
204
+ const transcript = await generateTranscript(channel);
205
+ const logsRow = stmts.getState.get('channel_📂・ticket-logs');
206
+ if (logsRow) {
207
+ const logsChannel = await client.channels.fetch(logsRow.value).catch(() => null);
208
+ if (logsChannel) {
209
+ const embed = createEmbed({
210
+ title: '📂 Ticket Closed',
211
+ description: `**Ticket:** ${channel.name}\n**User:** <@${ticket.user_id}>\n**Closed by:** <@${member.id}>`,
212
+ color: Colors.WARNING,
213
+ });
214
+ const buffer = Buffer.from(transcript, 'utf-8');
215
+ await logsChannel.send({
216
+ embeds: [embed],
217
+ files: [{ attachment: buffer, name: `transcript-${channel.name}.txt` }],
218
+ });
219
+ }
220
+ }
221
+
222
+ await logTicket(client, { user: { tag: ticket.username, id: ticket.user_id }, action: 'closed', channelName: channel.name });
223
+
224
+ // Delete the channel after a short delay
225
+ const closeEmbed = createEmbed({
226
+ title: '🔒 Ticket Closed',
227
+ description: 'This ticket has been closed. The channel will be deleted in 5 seconds.',
228
+ color: Colors.WARNING,
229
+ });
230
+ await interaction.reply({ embeds: [closeEmbed] });
231
+ setTimeout(() => channel.delete().catch(() => { }), 5000);
232
+ return true;
233
+ }
234
+
235
+ if (customId === 'ticket_delete') {
236
+ stmts.closeTicket.run('deleted', channel.id);
237
+ await logTicket(client, { user: { tag: ticket.username, id: ticket.user_id }, action: 'deleted', channelName: channel.name });
238
+ await interaction.reply({ content: '🗑️ Deleting ticket...' });
239
+ setTimeout(() => channel.delete().catch(() => { }), 1000);
240
+ return true;
241
+ }
242
+
243
+ return false;
244
+ }
245
+
246
+ module.exports = { sendTicketEmbed, createTicket, handleTicketButton, generateTranscript };
src/systems/verification.js ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { createEmbed } = require('../utils/embeds');
2
+ const { Colors } = require('../config');
3
+ const { stmts } = require('../database');
4
+ const { logVerification } = require('./logger');
5
+
6
+ /**
7
+ * Send the verification embed to the ✅・verify channel.
8
+ */
9
+ async function sendVerificationEmbed(client) {
10
+ const row = stmts.getState.get('channel_✅・verify');
11
+ if (!row) throw new Error('Verify channel not found in bot state.');
12
+
13
+ const channel = await client.channels.fetch(row.value);
14
+ if (!channel) throw new Error('Could not fetch verify channel.');
15
+
16
+ const embed = createEmbed({
17
+ title: '✅ Verification',
18
+ description: [
19
+ '> Welcome to **Wyvern Softworks**!',
20
+ '',
21
+ 'React with ✅ below to verify yourself and gain access to the server.',
22
+ '',
23
+ '```',
24
+ '• You will receive the Verified role',
25
+ '• Removing your reaction will remove the role',
26
+ '```',
27
+ ].join('\n'),
28
+ color: Colors.SUCCESS,
29
+ });
30
+
31
+ const msg = await channel.send({ embeds: [embed] });
32
+ await msg.react('✅');
33
+
34
+ // Store the message ID so we can listen for reactions
35
+ stmts.setState.run('verify_message_id', msg.id);
36
+ stmts.setState.run('verify_channel_id', channel.id);
37
+
38
+ return msg;
39
+ }
40
+
41
+ /**
42
+ * Handle verification reaction add.
43
+ */
44
+ async function handleVerifyReaction(reaction, user, client) {
45
+ const verifyMsgId = stmts.getState.get('verify_message_id')?.value;
46
+ if (!verifyMsgId || reaction.message.id !== verifyMsgId) return false;
47
+ if (reaction.emoji.name !== '✅') return false;
48
+
49
+ const guild = reaction.message.guild;
50
+ const member = await guild.members.fetch(user.id).catch(() => null);
51
+ if (!member) return false;
52
+
53
+ const role = guild.roles.cache.find(r => r.name === '@@ Verified');
54
+ if (!role) return false;
55
+
56
+ await member.roles.add(role);
57
+ await logVerification(client, user, 'verified');
58
+ return true;
59
+ }
60
+
61
+ /**
62
+ * Handle verification reaction remove.
63
+ */
64
+ async function handleVerifyReactionRemove(reaction, user, client) {
65
+ const verifyMsgId = stmts.getState.get('verify_message_id')?.value;
66
+ if (!verifyMsgId || reaction.message.id !== verifyMsgId) return false;
67
+ if (reaction.emoji.name !== '✅') return false;
68
+
69
+ const guild = reaction.message.guild;
70
+ const member = await guild.members.fetch(user.id).catch(() => null);
71
+ if (!member) return false;
72
+
73
+ const role = guild.roles.cache.find(r => r.name === '@@ Verified');
74
+ if (!role) return false;
75
+
76
+ await member.roles.remove(role);
77
+ await logVerification(client, user, 'unverified');
78
+ return true;
79
+ }
80
+
81
+ module.exports = { sendVerificationEmbed, handleVerifyReaction, handleVerifyReactionRemove };
src/systems/welcome.js ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { createEmbed } = require('../utils/embeds');
2
+ const { Colors } = require('../config');
3
+
4
+ /**
5
+ * Handle new member join — send a welcome embed via DM and optionally in a channel.
6
+ */
7
+ async function handleMemberJoin(member, client) {
8
+ // Build welcome DM embed
9
+ const embed = createEmbed({
10
+ title: '🟣 Welcome to Wyvern Softworks',
11
+ description: [
12
+ `Hey **${member.user.username}**, welcome to the server!\n`,
13
+
14
+ '> Before you get started, here\'s what you need to do:\n',
15
+
16
+ '**1.** Read the 📜・rules and ⚠️・disclaimer channels',
17
+ '**2.** Head to ✅・verify and react to get verified',
18
+ '**3.** Once verified, the full server will unlock\n',
19
+
20
+ '```',
21
+ '🔒 Most channels are locked until verification',
22
+ '🎫 Need help? Open a ticket after verifying',
23
+ '💜 Boost the server for exclusive perks',
24
+ '```\n',
25
+
26
+ '> Enjoy your stay — **Wyvern Softworks** 🐉',
27
+ ].join('\n'),
28
+ color: Colors.PRIMARY,
29
+ footer: 'WSB — Wyvern Softworks Bot',
30
+ });
31
+
32
+ // Try to send DM
33
+ try {
34
+ await member.send({ embeds: [embed] });
35
+ } catch {
36
+ // User has DMs disabled — silently ignore
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Handle member leave (optional logging).
42
+ */
43
+ async function handleMemberLeave(member, client) {
44
+ const { log } = require('./logger');
45
+ await log(client, {
46
+ title: '👋 Member Left',
47
+ description: `**${member.user.tag}** (${member.id}) has left the server.`,
48
+ color: Colors.WARNING,
49
+ });
50
+ }
51
+
52
+ module.exports = { handleMemberJoin, handleMemberLeave };
src/utils/embeds.js ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { EmbedBuilder } = require('discord.js');
2
+ const { Colors } = require('../config');
3
+
4
+ /**
5
+ * Build a themed embed with consistent WSB branding.
6
+ */
7
+ function createEmbed({
8
+ title = null,
9
+ description = null,
10
+ color = Colors.PRIMARY,
11
+ fields = [],
12
+ thumbnail = null,
13
+ footer = 'WSB — Wyvern Softworks Bot',
14
+ timestamp = true,
15
+ } = {}) {
16
+ const embed = new EmbedBuilder().setColor(color);
17
+
18
+ if (title) embed.setTitle(title);
19
+ if (description) embed.setDescription(description);
20
+ if (thumbnail) embed.setThumbnail(thumbnail);
21
+ if (fields.length) embed.addFields(fields);
22
+ if (footer) embed.setFooter({ text: footer });
23
+ if (timestamp) embed.setTimestamp();
24
+
25
+ return embed;
26
+ }
27
+
28
+ function successEmbed(title, description) {
29
+ return createEmbed({ title: `✅ ${title}`, description, color: Colors.SUCCESS });
30
+ }
31
+
32
+ function errorEmbed(title, description) {
33
+ return createEmbed({ title: `❌ ${title}`, description, color: Colors.ACCENT });
34
+ }
35
+
36
+ function infoEmbed(title, description) {
37
+ return createEmbed({ title: `ℹ️ ${title}`, description, color: Colors.INFO });
38
+ }
39
+
40
+ function warnEmbed(title, description) {
41
+ return createEmbed({ title: `⚠️ ${title}`, description, color: Colors.WARNING });
42
+ }
43
+
44
+ function logEmbed({ title, description, color = Colors.MUTED, fields = [] }) {
45
+ return createEmbed({ title, description, color, fields });
46
+ }
47
+
48
+ module.exports = { createEmbed, successEmbed, errorEmbed, infoEmbed, warnEmbed, logEmbed };
src/utils/permissions.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { PermissionFlagsBits } = require('discord.js');
2
+
3
+ /**
4
+ * Build permission overwrite objects for a channel from the config shorthand.
5
+ * @param {Object} overrides - { roleName: { view, send, connect, speak } }
6
+ * @param {Map} roleMap - Map<roleName, Role> (includes 'everyone' → @everyone)
7
+ * @returns {Array} Array of permission overwrite objects for channel creation
8
+ */
9
+ function buildOverwrites(overrides, roleMap) {
10
+ const result = [];
11
+
12
+ for (const [roleName, perms] of Object.entries(overrides)) {
13
+ const role = roleMap.get(roleName === 'everyone' ? 'everyone' : roleName);
14
+ if (!role) continue;
15
+
16
+ const allow = [];
17
+ const deny = [];
18
+
19
+ // View
20
+ if (perms.view === true) allow.push(PermissionFlagsBits.ViewChannel);
21
+ if (perms.view === false) deny.push(PermissionFlagsBits.ViewChannel);
22
+
23
+ // Send
24
+ if (perms.send === true) allow.push(PermissionFlagsBits.SendMessages);
25
+ if (perms.send === false) deny.push(PermissionFlagsBits.SendMessages);
26
+
27
+ // Connect (voice)
28
+ if (perms.connect === true) allow.push(PermissionFlagsBits.Connect);
29
+ if (perms.connect === false) deny.push(PermissionFlagsBits.Connect);
30
+
31
+ // Speak (voice)
32
+ if (perms.speak === true) allow.push(PermissionFlagsBits.Speak);
33
+ if (perms.speak === false) deny.push(PermissionFlagsBits.Speak);
34
+
35
+ // React
36
+ if (perms.react === true) allow.push(PermissionFlagsBits.AddReactions);
37
+ if (perms.react === false) deny.push(PermissionFlagsBits.AddReactions);
38
+
39
+ // Read history
40
+ if (perms.readHistory === true) allow.push(PermissionFlagsBits.ReadMessageHistory);
41
+ if (perms.readHistory === false) deny.push(PermissionFlagsBits.ReadMessageHistory);
42
+
43
+ result.push({
44
+ id: role.id || role,
45
+ allow,
46
+ deny,
47
+ });
48
+ }
49
+
50
+ return result;
51
+ }
52
+
53
+ module.exports = { buildOverwrites };