APRK01 commited on
Commit ·
3c7e34b
0
Parent(s):
Initial commit: WSB Discord Bot
Browse files- .gitignore +3 -0
- package-lock.json +777 -0
- package.json +14 -0
- render.yaml +15 -0
- src/commands/backupLayout.js +67 -0
- src/commands/createChannels.js +208 -0
- src/commands/createRoles.js +73 -0
- src/commands/permissionAudit.js +72 -0
- src/commands/postDisclaimer.js +10 -0
- src/commands/postRules.js +10 -0
- src/commands/setup.js +90 -0
- src/commands/shutdown.js +22 -0
- src/commands/ticketStats.js +24 -0
- src/config.js +205 -0
- src/database.js +65 -0
- src/events/guildMemberAdd.js +8 -0
- src/events/guildMemberRemove.js +8 -0
- src/events/guildMemberUpdate.js +8 -0
- src/events/interactionCreate.js +16 -0
- src/events/messageCreate.js +92 -0
- src/events/messageReactionAdd.js +19 -0
- src/events/messageReactionRemove.js +17 -0
- src/events/ready.js +18 -0
- src/index.js +89 -0
- src/systems/booster.js +36 -0
- src/systems/drops.js +358 -0
- src/systems/embeds.js +99 -0
- src/systems/logger.js +68 -0
- src/systems/tickets.js +246 -0
- src/systems/verification.js +81 -0
- src/systems/welcome.js +52 -0
- src/utils/embeds.js +48 -0
- src/utils/permissions.js +53 -0
.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 };
|