From fbcbbc66f612ebaa358417c803cbbb5bd342497e Mon Sep 17 00:00:00 2001 From: Aiden Date: Sat, 3 Aug 2024 16:30:24 -0400 Subject: [PATCH] Minor Rewrite - Move from json config to .env - Update packages - Add Prettier - Cleanup tsconfig - index.ts -> main.ts - Use Discord attachments - Add maximum upload size - Formatting --- .env.example | 17 + .github/readme.md | 35 +- .gitignore | 2 +- config/config.example.json | 23 -- eslint.config.js | 14 +- package.json | 28 +- pnpm-lock.yaml | 417 ++++++++++++------------ prettier.config.js | 12 + src/commands/admin.ts | 129 ++++---- src/commands/badge.ts | 622 ++++++++++++++++++------------------ src/handler/autocomplete.ts | 28 +- src/handler/button.ts | 22 +- src/handler/command.ts | 34 +- src/index.ts | 53 --- src/lib/bucket.ts | 73 +++-- src/lib/checkDomain.ts | 21 -- src/lib/indexer.ts | 14 +- src/lib/mongo.ts | 170 +++++----- src/lib/pushCommands.ts | 28 +- src/lib/verification.ts | 112 ++++--- src/main.ts | 52 +++ src/types/badge.d.ts | 6 +- src/types/button.d.ts | 4 +- src/types/command.d.ts | 14 +- src/types/config.d.ts | 19 -- src/types/entry.d.ts | 8 +- tsconfig.json | 27 +- 27 files changed, 969 insertions(+), 1015 deletions(-) create mode 100644 .env.example delete mode 100644 config/config.example.json create mode 100644 prettier.config.js delete mode 100644 src/index.ts delete mode 100644 src/lib/checkDomain.ts create mode 100644 src/main.ts delete mode 100644 src/types/config.d.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6d7bef1 --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +DISCORD_TOKEN="" +MONGO_DB="mongodb://localhost:27017" +CLIENT_ID="" +DATABASE_NAME="" +COLLECTION_NAME="" +MAX_BADGES="5" +EXTRA_BOOST_BADGES="5" +MAX_BADGE_SIZE="5242880" +PROMPT_CHANNEL="" +VERIFIER_ROLE="" +BUCKET_ENDPOINT="" +BUCKET_PORT="443" +BUCKET_SSL="true" +BUCKET_ACCESS_KEY="" +BUCKET_SECRET_KEY="" +BUCKET_NAME="" +BUCKET_DOMAIN="https://s3.example.com" diff --git a/.github/readme.md b/.github/readme.md index 1e2fbb4..996626b 100644 --- a/.github/readme.md +++ b/.github/readme.md @@ -9,23 +9,24 @@ - `application.commands` (To create commands) 2. Add the bot to your server 3. Clone this repository (`git clone https://github.com/WolfPlugs/GiBBy`) -4. Set configs in `config/config.json` (copy `config.example.json` and rename the copy to `config.json`) - - `DiscordToken`: The bot's Discord token - - `MongoDB`: The MongoDB connection string (default: `mongodb://localhost:27017`) - - `ClientId`: The bot's Discord ID - - `DatabaseName`: The name of the MongoDB database - - `CollectionName`: The collection name of the MongoDB database - - `MaxBadges`: The maximum badges a user can have (default: `5`) - - `ExtraBoostBadges`: The amount of badges added to booster's allowance (default: `5`) - - `PromptChannel`: The channel to send the badge requests to - - `VerifierRole`: The role ID of people who can approve requests - - `Domains`: An array of whitelisted domains for badges - - `BucketEndpoint`: The URL to upload to the bucket - - `BucketPort`: The port to upload to the bucket. (default: `443`) - - `BucketAccessKey`: The bucket's access key - - `BucketSecretKey`: The bucket's secret key - - `BucketName`: The bucket's name - - `BucketDomain`: The bucket's public facing URL +4. Set configs in `.env` (copy `.env.example` and rename the copy to `.env`) + - `DISCORD_TOKEN`: The bot's Discord token + - `MONGO_DB`: The MongoDB connection string (default: `mongodb://localhost:27017`) + - `CLIENT_ID`: The bot's Discord ID + - `DATABASE_NAME`: The name of the MongoDB database + - `COLLECTION_NAME`: The collection name of the MongoDB database + - `MAX_BADGES`: The maximum badges a user can have (default: `5`) + - `EXTRA_BOOST_BADGES`: The amount of badges added to booster's allowance (default: `5`) + - `MAX_BADGE_SIZE`: The maximum badge size, in binary bytes. (default: `5242880` 5mb) + - `PROMPT_CHANNEL`: The channel to send the badge requests to + - `VERIFIER_ROLE`: The role ID of people who can approve requests + - `BUCKET_ENDPOINT`: The URL to upload to the bucket + - `BUCKET_PORT`: The port to upload to the bucket. (default: `443`) + - `BUCKET_SSL`: Whether or not to use SSL. (default: `true`) + - `BUCKET_ACCESS_KEY`: The bucket's access key + - `BUCKET_SECRET_KEY`: The bucket's secret key + - `BUCKET_NAME`: The bucket's name + - `BUCKET_DOMAIN`: The bucket's public facing URL. (default: `https://s3.example.com`) 5. Install packages using a node package manager (I suggest [PNPM](https://pnpm.io/)): `pnpm i` 6. Build: `pnpm build` 7. Run: `pnpm start` diff --git a/.gitignore b/.gitignore index a6aab84..a0d218e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules dist -config/config.json \ No newline at end of file +.env \ No newline at end of file diff --git a/config/config.example.json b/config/config.example.json deleted file mode 100644 index 64f0a27..0000000 --- a/config/config.example.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "DiscordToken": "BotToken", - "MongoDB": "mongodb://localhost:27017", - "ClientID": "Bot ID", - "DatabaseName": "wehDB", - "CollectionName": "database", - "MaxBadges": 5, - "ExtraBoostBadges": 5, - "PromptChannel": "ChannelID", - "VerifierRole": "554356800", - "Domains": [ - "media.discordapp.net", - "cdn.discordapp.com", - "tenor.com" - ], - "BucketEndpoint": "weh.r2.dev", - "BucketPort": 443, - "BucketSSL": true, - "BucketAccessKey": "SuperCoolKey", - "BucketSecretKey": "SuperSecretKey", - "BucketName": "WehBucket", - "BucketDomain": "https://bucket.weh.com" -} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index 9e80b13..ba7a05c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,25 +1,23 @@ // @ts-check -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; export default tseslint.config( eslint.configs.recommended, - {ignores:["dist/"]}, + { ignores: ["dist/", "*.config.js"] }, ...tseslint.configs.strictTypeChecked, ...tseslint.configs.stylisticTypeChecked, { - languageOptions: { parserOptions: { project: true, tsconfigRootDir: import.meta.dirname, }, }, - rules:{ + rules: { "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/restrict-template-expressions": "warn", "@typescript-eslint/prefer-for-of": "off" - } + }, }, -); \ No newline at end of file +); diff --git a/package.json b/package.json index e595818..e85b242 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,25 @@ { "type": "module", "scripts": { - "dev": "tsx src/index.ts", - "start": "node dist/src/index.js", + "dev": "tsx --env-file=.env src/main.ts", + "start": "node --env-file=.env dist/main.js", "build": "tsc", - "lint": "eslint \"**/*.ts\" ." + "lint": "eslint" }, "devDependencies": { - "@eslint/js": "^9.4.0", + "@eslint/js": "^9.8.0", "@types/eslint__js": "^8.42.3", - "@types/node": "^20.14.2", - "eslint": "^9.4.0", + "@types/node": "^22.1.0", + "eslint": "^9.8.0", + "prettier": "^3.3.3", + "tsx": "^4.16.5", "typescript": "^5.4.5", - "typescript-eslint": "^7.13.0" + "typescript-eslint": "^8.0.0" }, "dependencies": { - "@discordjs/rest": "^2.3.0", "discord.js": "^14.15.3", - "minio": "^8.0.0", - "mongodb": "^6.7.0" + "minio": "^8.0.1", + "mongodb": "^6.8.0" }, - "optionalDependencies": { - "tsx": "^4.15.4" - }, - "packageManager": "pnpm@9.1.4" -} \ No newline at end of file + "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eac8076..a00d159 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,41 +8,40 @@ importers: .: dependencies: - '@discordjs/rest': - specifier: ^2.3.0 - version: 2.3.0 discord.js: specifier: ^14.15.3 version: 14.15.3 minio: - specifier: ^8.0.0 - version: 8.0.0 + specifier: ^8.0.1 + version: 8.0.1 mongodb: - specifier: ^6.7.0 - version: 6.7.0 - optionalDependencies: - tsx: - specifier: ^4.15.4 - version: 4.15.4 + specifier: ^6.8.0 + version: 6.8.0 devDependencies: '@eslint/js': - specifier: ^9.4.0 - version: 9.4.0 + specifier: ^9.8.0 + version: 9.8.0 '@types/eslint__js': specifier: ^8.42.3 version: 8.42.3 '@types/node': - specifier: ^20.14.2 - version: 20.14.2 + specifier: ^22.1.0 + version: 22.1.0 eslint: - specifier: ^9.4.0 - version: 9.4.0 + specifier: ^9.8.0 + version: 9.8.0 + prettier: + specifier: ^3.3.3 + version: 3.3.3 + tsx: + specifier: ^4.16.5 + version: 4.16.5 typescript: specifier: ^5.4.5 version: 5.4.5 typescript-eslint: - specifier: ^7.13.0 - version: 7.13.0(eslint@9.4.0)(typescript@5.4.5) + specifier: ^8.0.0 + version: 8.0.0(eslint@9.8.0)(typescript@5.4.5) packages: @@ -218,20 +217,20 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.10.1': - resolution: {integrity: sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==} + '@eslint-community/regexpp@4.11.0': + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.15.1': - resolution: {integrity: sha512-K4gzNq+yymn/EVsXYmf+SBcBro8MTf+aXJZUphM96CdzUEr+ClGDvAbpmaEK+cGVigVXIgs9gNmvHAlrzzY5JQ==} + '@eslint/config-array@0.17.1': + resolution: {integrity: sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.1.0': resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.4.0': - resolution: {integrity: sha512-fdI7VJjP3Rvc70lC4xkFXHB0fiPeojiL1PxVG6t1ZvXQrarj893PweuBTujxDUFk0Fxj4R7PIIAZ/aiiyZPZcg==} + '@eslint/js@9.8.0': + resolution: {integrity: sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': @@ -246,8 +245,8 @@ packages: resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} engines: {node: '>=18.18'} - '@mongodb-js/saslprep@1.1.7': - resolution: {integrity: sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==} + '@mongodb-js/saslprep@1.1.8': + resolution: {integrity: sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -261,8 +260,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@sapphire/async-queue@1.5.2': - resolution: {integrity: sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==} + '@sapphire/async-queue@1.5.3': + resolution: {integrity: sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} '@sapphire/shapeshift@3.9.7': @@ -273,8 +272,8 @@ packages: resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - '@types/eslint@8.56.10': - resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + '@types/eslint@9.6.0': + resolution: {integrity: sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==} '@types/eslint__js@8.42.3': resolution: {integrity: sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==} @@ -285,8 +284,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.14.2': - resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} + '@types/node@22.1.0': + resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} '@types/webidl-conversions@7.0.3': resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} @@ -294,69 +293,68 @@ packages: '@types/whatwg-url@11.0.5': resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==} - '@types/ws@8.5.10': - resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + '@types/ws@8.5.12': + resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} - '@typescript-eslint/eslint-plugin@7.13.0': - resolution: {integrity: sha512-FX1X6AF0w8MdVFLSdqwqN/me2hyhuQg4ykN6ZpVhh1ij/80pTvDKclX1sZB9iqex8SjQfVhwMKs3JtnnMLzG9w==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/eslint-plugin@8.0.0': + resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/parser@7.13.0': - resolution: {integrity: sha512-EjMfl69KOS9awXXe83iRN7oIEXy9yYdqWfqdrFAYAAr6syP8eLEFI7ZE4939antx2mNgPRW/o1ybm2SFYkbTVA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/parser@8.0.0': + resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/scope-manager@7.13.0': - resolution: {integrity: sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.0.0': + resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@7.13.0': - resolution: {integrity: sha512-xMEtMzxq9eRkZy48XuxlBFzpVMDurUAfDu5Rz16GouAtXm0TaAoTFzqWUFPPuQYXI/CDaH/Bgx/fk/84t/Bc9A==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/type-utils@8.0.0': + resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/types@7.13.0': - resolution: {integrity: sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.0.0': + resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@7.13.0': - resolution: {integrity: sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/typescript-estree@8.0.0': + resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/utils@7.13.0': - resolution: {integrity: sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/utils@8.0.0': + resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@7.13.0': - resolution: {integrity: sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@8.0.0': + resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vladfrangu/async_event_emitter@2.2.4': - resolution: {integrity: sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==} + '@vladfrangu/async_event_emitter@2.4.5': + resolution: {integrity: sha512-J7T3gUr3Wz0l7Ni1f9upgBZ7+J22/Q1B7dl0X6fG+fTsD+H+31DIosMHj4Um1dWQwqbcQ3oQf+YS2foYkDc9cQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} '@zxing/text-encoding@0.9.0': @@ -367,8 +365,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} hasBin: true @@ -416,8 +414,8 @@ packages: browser-or-node@2.1.1: resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} - bson@6.7.0: - resolution: {integrity: sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==} + bson@6.8.0: + resolution: {integrity: sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==} engines: {node: '>=16.20.1'} buffer-crc32@1.0.0: @@ -450,8 +448,8 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -498,8 +496,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-scope@8.0.1: - resolution: {integrity: sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==} + eslint-scope@8.0.2: + resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: @@ -510,17 +508,17 @@ packages: resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.4.0: - resolution: {integrity: sha512-sjc7Y8cUD1IlwYcTS9qPSvGjAC8Ne9LctpxKKu3x/1IC9bnOg98Zy6GxEJUfr1NojMgVPlyANXYns8oE2c1TAA==} + eslint@9.8.0: + resolution: {integrity: sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true - espree@10.0.1: - resolution: {integrity: sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==} + espree@10.1.0: + resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -551,8 +549,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-xml-parser@4.4.0: - resolution: {integrity: sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==} + fast-xml-parser@4.4.1: + resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} hasBin: true fastq@1.17.1: @@ -596,8 +594,8 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} + get-tsconfig@4.7.6: + resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -756,19 +754,19 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minio@8.0.0: - resolution: {integrity: sha512-GkM/lk+Gzwd4fAQvLlB+cy3NV3PRADe0tNXnH9JD5BmdAHKIp+5vypptbjdkU85xWBIQsa2xK35GpXjmYXBBYA==} + minio@8.0.1: + resolution: {integrity: sha512-FzDO6yGnqLtm8sp3mXafWtiRUOslJSSg/aI0v9YbN5vjw5KLoODKAROCyi766NIvTSxcfHBrbhCSGk1A+MOzDg==} engines: {node: ^16 || ^18 || >=20} mongodb-connection-string-url@3.0.1: resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} - mongodb@6.7.0: - resolution: {integrity: sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==} + mongodb@6.8.0: + resolution: {integrity: sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==} engines: {node: '>=16.20.1'} peerDependencies: '@aws-sdk/credential-providers': ^3.188.0 @@ -840,6 +838,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -875,8 +878,8 @@ packages: sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} - semver@7.6.2: - resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true @@ -957,11 +960,8 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - - tsx@4.15.4: - resolution: {integrity: sha512-d++FLCwJLrXaBFtRcqdPBzu6FiVOJ2j+UsvUZPtoTrnYtCGU5CEW7iHXtNZfA2fcRTvJFWPqA6SWBuB0GSva9w==} + tsx@4.16.5: + resolution: {integrity: sha512-ArsiAQHEW2iGaqZ8fTA1nX0a+lN5mNTyuGRRO6OW3H/Yno1y9/t1f9YOI1Cfoqz63VAthn++ZYcbDP7jPflc+A==} engines: {node: '>=18.0.0'} hasBin: true @@ -969,11 +969,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@7.13.0: - resolution: {integrity: sha512-upO0AXxyBwJ4BbiC6CRgAJKtGYha2zw4m1g7TIVPSonwYEuf7vCicw3syjS1OxdDMTz96sZIXl3Jx3vWJLLKFw==} - engines: {node: ^18.18.0 || >=20.0.0} + typescript-eslint@8.0.0: + resolution: {integrity: sha512-yQWBJutWL1PmpmDddIOl9/Mi6vZjqNCjqSGBMQ4vsc2Aiodk0SnbQQWPXbSy0HNuKCuGkw1+u4aQ2mO40TdhDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: @@ -984,8 +983,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.13.0: + resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} undici@6.13.0: resolution: {integrity: sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw==} @@ -1024,8 +1023,8 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - ws@8.17.0: - resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1072,12 +1071,12 @@ snapshots: dependencies: '@discordjs/collection': 2.1.0 '@discordjs/util': 1.1.0 - '@sapphire/async-queue': 1.5.2 + '@sapphire/async-queue': 1.5.3 '@sapphire/snowflake': 3.5.3 - '@vladfrangu/async_event_emitter': 2.2.4 + '@vladfrangu/async_event_emitter': 2.4.5 discord-api-types: 0.37.83 magic-bytes.js: 1.10.0 - tslib: 2.6.3 + tslib: 2.6.2 undici: 6.13.0 '@discordjs/util@1.1.0': {} @@ -1087,12 +1086,12 @@ snapshots: '@discordjs/collection': 2.1.0 '@discordjs/rest': 2.3.0 '@discordjs/util': 1.1.0 - '@sapphire/async-queue': 1.5.2 - '@types/ws': 8.5.10 - '@vladfrangu/async_event_emitter': 2.2.4 + '@sapphire/async-queue': 1.5.3 + '@types/ws': 8.5.12 + '@vladfrangu/async_event_emitter': 2.4.5 discord-api-types: 0.37.83 tslib: 2.6.2 - ws: 8.17.0 + ws: 8.18.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -1166,17 +1165,17 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@9.4.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.8.0)': dependencies: - eslint: 9.4.0 + eslint: 9.8.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.10.1': {} + '@eslint-community/regexpp@4.11.0': {} - '@eslint/config-array@0.15.1': + '@eslint/config-array@0.17.1': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.5 + debug: 4.3.6 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -1184,8 +1183,8 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.5 - espree: 10.0.1 + debug: 4.3.6 + espree: 10.1.0 globals: 14.0.0 ignore: 5.3.1 import-fresh: 3.3.0 @@ -1195,7 +1194,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.4.0': {} + '@eslint/js@9.8.0': {} '@eslint/object-schema@2.1.4': {} @@ -1203,7 +1202,7 @@ snapshots: '@humanwhocodes/retry@0.3.0': {} - '@mongodb-js/saslprep@1.1.7': + '@mongodb-js/saslprep@1.1.8': dependencies: sparse-bitfield: 3.0.3 @@ -1219,7 +1218,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@sapphire/async-queue@1.5.2': {} + '@sapphire/async-queue@1.5.3': {} '@sapphire/shapeshift@3.9.7': dependencies: @@ -1228,22 +1227,22 @@ snapshots: '@sapphire/snowflake@3.5.3': {} - '@types/eslint@8.56.10': + '@types/eslint@9.6.0': dependencies: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 '@types/eslint__js@8.42.3': dependencies: - '@types/eslint': 8.56.10 + '@types/eslint': 9.6.0 '@types/estree@1.0.5': {} '@types/json-schema@7.0.15': {} - '@types/node@20.14.2': + '@types/node@22.1.0': dependencies: - undici-types: 5.26.5 + undici-types: 6.13.0 '@types/webidl-conversions@7.0.3': {} @@ -1251,19 +1250,19 @@ snapshots: dependencies: '@types/webidl-conversions': 7.0.3 - '@types/ws@8.5.10': + '@types/ws@8.5.12': dependencies: - '@types/node': 20.14.2 + '@types/node': 22.1.0 - '@typescript-eslint/eslint-plugin@7.13.0(@typescript-eslint/parser@7.13.0(eslint@9.4.0)(typescript@5.4.5))(eslint@9.4.0)(typescript@5.4.5)': + '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.4.5))(eslint@9.8.0)(typescript@5.4.5)': dependencies: - '@eslint-community/regexpp': 4.10.1 - '@typescript-eslint/parser': 7.13.0(eslint@9.4.0)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 7.13.0 - '@typescript-eslint/type-utils': 7.13.0(eslint@9.4.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.13.0(eslint@9.4.0)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.13.0 - eslint: 9.4.0 + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 8.0.0(eslint@9.8.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 8.0.0 + '@typescript-eslint/type-utils': 8.0.0(eslint@9.8.0)(typescript@5.4.5) + '@typescript-eslint/utils': 8.0.0(eslint@9.8.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 8.0.0 + eslint: 9.8.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 @@ -1273,79 +1272,79 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.13.0(eslint@9.4.0)(typescript@5.4.5)': + '@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/scope-manager': 7.13.0 - '@typescript-eslint/types': 7.13.0 - '@typescript-eslint/typescript-estree': 7.13.0(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.13.0 - debug: 4.3.5 - eslint: 9.4.0 + '@typescript-eslint/scope-manager': 8.0.0 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 8.0.0 + debug: 4.3.6 + eslint: 9.8.0 optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.13.0': + '@typescript-eslint/scope-manager@8.0.0': dependencies: - '@typescript-eslint/types': 7.13.0 - '@typescript-eslint/visitor-keys': 7.13.0 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/visitor-keys': 8.0.0 - '@typescript-eslint/type-utils@7.13.0(eslint@9.4.0)(typescript@5.4.5)': + '@typescript-eslint/type-utils@8.0.0(eslint@9.8.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/typescript-estree': 7.13.0(typescript@5.4.5) - '@typescript-eslint/utils': 7.13.0(eslint@9.4.0)(typescript@5.4.5) - debug: 4.3.5 - eslint: 9.4.0 + '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.4.5) + '@typescript-eslint/utils': 8.0.0(eslint@9.8.0)(typescript@5.4.5) + debug: 4.3.6 ts-api-utils: 1.3.0(typescript@5.4.5) optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: + - eslint - supports-color - '@typescript-eslint/types@7.13.0': {} + '@typescript-eslint/types@8.0.0': {} - '@typescript-eslint/typescript-estree@7.13.0(typescript@5.4.5)': + '@typescript-eslint/typescript-estree@8.0.0(typescript@5.4.5)': dependencies: - '@typescript-eslint/types': 7.13.0 - '@typescript-eslint/visitor-keys': 7.13.0 - debug: 4.3.5 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/visitor-keys': 8.0.0 + debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 - minimatch: 9.0.4 - semver: 7.6.2 + minimatch: 9.0.5 + semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.4.5) optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.13.0(eslint@9.4.0)(typescript@5.4.5)': + '@typescript-eslint/utils@8.0.0(eslint@9.8.0)(typescript@5.4.5)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.4.0) - '@typescript-eslint/scope-manager': 7.13.0 - '@typescript-eslint/types': 7.13.0 - '@typescript-eslint/typescript-estree': 7.13.0(typescript@5.4.5) - eslint: 9.4.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) + '@typescript-eslint/scope-manager': 8.0.0 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.4.5) + eslint: 9.8.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@7.13.0': + '@typescript-eslint/visitor-keys@8.0.0': dependencies: - '@typescript-eslint/types': 7.13.0 + '@typescript-eslint/types': 8.0.0 eslint-visitor-keys: 3.4.3 - '@vladfrangu/async_event_emitter@2.2.4': {} + '@vladfrangu/async_event_emitter@2.4.5': {} '@zxing/text-encoding@0.9.0': optional: true - acorn-jsx@5.3.2(acorn@8.11.3): + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: - acorn: 8.11.3 + acorn: 8.12.1 - acorn@8.11.3: {} + acorn@8.12.1: {} ajv@6.12.6: dependencies: @@ -1391,7 +1390,7 @@ snapshots: browser-or-node@2.1.1: {} - bson@6.7.0: {} + bson@6.8.0: {} buffer-crc32@1.0.0: {} @@ -1424,7 +1423,7 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.3.5: + debug@4.3.6: dependencies: ms: 2.1.2 @@ -1493,11 +1492,10 @@ snapshots: '@esbuild/win32-arm64': 0.21.5 '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - optional: true escape-string-regexp@4.0.0: {} - eslint-scope@8.0.1: + eslint-scope@8.0.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 @@ -1506,25 +1504,25 @@ snapshots: eslint-visitor-keys@4.0.0: {} - eslint@9.4.0: + eslint@9.8.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.4.0) - '@eslint-community/regexpp': 4.10.1 - '@eslint/config-array': 0.15.1 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) + '@eslint-community/regexpp': 4.11.0 + '@eslint/config-array': 0.17.1 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.4.0 + '@eslint/js': 9.8.0 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.3.0 '@nodelib/fs.walk': 1.2.8 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.5 + debug: 4.3.6 escape-string-regexp: 4.0.0 - eslint-scope: 8.0.1 + eslint-scope: 8.0.2 eslint-visitor-keys: 4.0.0 - espree: 10.0.1 - esquery: 1.5.0 + espree: 10.1.0 + esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 @@ -1545,13 +1543,13 @@ snapshots: transitivePeerDependencies: - supports-color - espree@10.0.1: + espree@10.1.0: dependencies: - acorn: 8.11.3 - acorn-jsx: 5.3.2(acorn@8.11.3) + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) eslint-visitor-keys: 4.0.0 - esquery@1.5.0: + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -1579,7 +1577,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-xml-parser@4.4.0: + fast-xml-parser@4.4.1: dependencies: strnum: 1.0.5 @@ -1626,10 +1624,9 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 - get-tsconfig@4.7.5: + get-tsconfig@4.7.6: dependencies: resolve-pkg-maps: 1.0.0 - optional: true glob-parent@5.1.2: dependencies: @@ -1764,18 +1761,18 @@ snapshots: dependencies: brace-expansion: 1.1.11 - minimatch@9.0.4: + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 - minio@8.0.0: + minio@8.0.1: dependencies: async: 3.2.5 block-stream2: 2.1.0 browser-or-node: 2.1.1 buffer-crc32: 1.0.0 eventemitter3: 5.0.1 - fast-xml-parser: 4.4.0 + fast-xml-parser: 4.4.1 ipaddr.js: 2.2.0 lodash: 4.17.21 mime-types: 2.1.35 @@ -1790,10 +1787,10 @@ snapshots: '@types/whatwg-url': 11.0.5 whatwg-url: 13.0.0 - mongodb@6.7.0: + mongodb@6.8.0: dependencies: - '@mongodb-js/saslprep': 1.1.7 - bson: 6.7.0 + '@mongodb-js/saslprep': 1.1.8 + bson: 6.8.0 mongodb-connection-string-url: 3.0.1 ms@2.1.2: {} @@ -1833,6 +1830,8 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.3.3: {} + punycode@2.3.1: {} query-string@7.1.3: @@ -1852,8 +1851,7 @@ snapshots: resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: - optional: true + resolve-pkg-maps@1.0.0: {} reusify@1.0.4: {} @@ -1865,7 +1863,7 @@ snapshots: sax@1.4.1: {} - semver@7.6.2: {} + semver@7.6.3: {} set-function-length@1.2.2: dependencies: @@ -1936,34 +1934,31 @@ snapshots: tslib@2.6.2: {} - tslib@2.6.3: {} - - tsx@4.15.4: + tsx@4.16.5: dependencies: esbuild: 0.21.5 - get-tsconfig: 4.7.5 + get-tsconfig: 4.7.6 optionalDependencies: fsevents: 2.3.3 - optional: true type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - typescript-eslint@7.13.0(eslint@9.4.0)(typescript@5.4.5): + typescript-eslint@8.0.0(eslint@9.8.0)(typescript@5.4.5): dependencies: - '@typescript-eslint/eslint-plugin': 7.13.0(@typescript-eslint/parser@7.13.0(eslint@9.4.0)(typescript@5.4.5))(eslint@9.4.0)(typescript@5.4.5) - '@typescript-eslint/parser': 7.13.0(eslint@9.4.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.13.0(eslint@9.4.0)(typescript@5.4.5) - eslint: 9.4.0 + '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.4.5))(eslint@9.8.0)(typescript@5.4.5) + '@typescript-eslint/parser': 8.0.0(eslint@9.8.0)(typescript@5.4.5) + '@typescript-eslint/utils': 8.0.0(eslint@9.8.0)(typescript@5.4.5) optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: + - eslint - supports-color typescript@5.4.5: {} - undici-types@5.26.5: {} + undici-types@6.13.0: {} undici@6.13.0: {} @@ -2008,7 +2003,7 @@ snapshots: word-wrap@1.2.5: {} - ws@8.17.0: {} + ws@8.18.0: {} xml2js@0.5.0: dependencies: diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..6eee882 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,12 @@ +// @ts-check +/** @type {import("prettier").Config} */ +export default { + tabWidth: 4, + useTabs: true, + semi: true, + singleQuote: false, + trailingComma: "all", + bracketSpacing: true, + arrowParens: "always", + endOfLine: "lf", +}; diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 5d0e7d2..5fe7818 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -1,78 +1,75 @@ import { - type ChatInputCommandInteraction, - SlashCommandBuilder, - type AutocompleteInteraction, + type ChatInputCommandInteraction, + SlashCommandBuilder, + type AutocompleteInteraction, } from "discord.js"; import { badgeExists, deleteBadge, getBadges } from "../lib/mongo.js"; -import untypedConfig from "../../config/config.json" with { type: "json" }; -import type { Config } from "../types/config.js"; -const { VerifierRole } = untypedConfig as Config; import type { Badge } from "../types/badge.js"; export const data = new SlashCommandBuilder() - .setName("admin") - .setDescription("List all a user's badges") - .addSubcommand((subcommand) => - subcommand - .addUserOption((option) => - option - .setName("user") - .setDescription("The user to delete the badge form") - .setRequired(true), - ) - .setName("delete") - .setDescription("Delete a badge") - .addStringOption((option) => - option - .setName("name") - .setDescription("The name of the badge") - .setRequired(true) - .setAutocomplete(true), - ), - ) + .setName("admin") + .setDescription("List all a user's badges") + .addSubcommand((subcommand) => + subcommand + .addUserOption((option) => + option + .setName("user") + .setDescription("The user to delete the badge form") + .setRequired(true), + ) + .setName("delete") + .setDescription("Delete a badge") + .addStringOption((option) => + option + .setName("name") + .setDescription("The name of the badge") + .setRequired(true) + .setAutocomplete(true), + ), + ); export async function execute(interaction: ChatInputCommandInteraction) { - if (interaction.inCachedGuild()) { - if (interaction.member.roles.cache.has(VerifierRole)) { - const selectedUser = interaction.options.getUser("user")!; // User will be defined as it is required by command - if (interaction.options.getSubcommand() === "delete") { - const name = interaction.options.getString("name")!; - if (await badgeExists(selectedUser.id, name, "all")) { - await deleteBadge(selectedUser.id, name).then(async () => { - await interaction.reply({ - content: `Deleted badge "${name}" from ${selectedUser.username}`, - ephemeral: true, - }); - try { - return await selectedUser.send({ - content: `Your badge "${name}" has been deleted by an admin.`, - }); - } catch (e) { - console.error(e) - return; - } - }); - } - } - } else { - await interaction.reply({ - content: "You are not authorized to use this command", - ephemeral: true, - }); - } - } + if (interaction.inCachedGuild()) { + if (interaction.member.roles.cache.has(process.env["VERIFIER_ROLE"]!)) { + const selectedUser = interaction.options.getUser("user")!; // User will be defined as it is required by command + if (interaction.options.getSubcommand() === "delete") { + const name = interaction.options.getString("name")!; + if (await badgeExists(selectedUser.id, name, "all")) { + await deleteBadge(selectedUser.id, name).then(async () => { + await interaction.reply({ + content: `Deleted badge "${name}" from ${selectedUser.username}`, + ephemeral: true, + }); + try { + return await selectedUser.send({ + content: `Your badge "${name}" has been deleted by an admin.`, + }); + } catch (e) { + console.error(e); + return; + } + }); + } + } + } else { + await interaction.reply({ + content: "You are not authorized to use this command", + ephemeral: true, + }); + } + } } export async function autocomplete(interaction: AutocompleteInteraction) { - const options: string[] = []; - const focus = interaction.options.getFocused(); - ( - await getBadges(interaction.options.get("user")!.value as string, "all") - ).forEach((badge: Badge) => { - options.push(badge.name); - }); - const filtered = options.filter((option) => option.startsWith(focus)); - await interaction.respond( - filtered.map((option) => ({ name: option, value: option })), - ); + const options: string[] = []; + const focus = interaction.options.getFocused(); + ( + await getBadges(interaction.options.get("user")!.value as string, "all") + ).forEach((badge: Badge) => { + options.push(badge.name); + }); + const filtered = options.filter((option) => option.startsWith(focus)); + await interaction.respond( + filtered.map((option) => ({ name: option, value: option })), + ); } diff --git a/src/commands/badge.ts b/src/commands/badge.ts index 3b34362..860000a 100644 --- a/src/commands/badge.ts +++ b/src/commands/badge.ts @@ -1,341 +1,351 @@ import { - SlashCommandBuilder, - type ChatInputCommandInteraction, - type ButtonInteraction, - type AutocompleteInteraction, - EmbedBuilder, - ButtonBuilder, - ButtonStyle, - ActionRowBuilder, - GuildMember, + SlashCommandBuilder, + type ChatInputCommandInteraction, + type ButtonInteraction, + type AutocompleteInteraction, + EmbedBuilder, + ButtonBuilder, + ButtonStyle, + ActionRowBuilder, + GuildMember, } from "discord.js"; import { - canMakeNewBadge, - pendBadge, - isBlocked, - deleteBadge, - badgeExists, - getBadges, - approveBadge, - blockUser, - unblockUser, + canMakeNewBadge, + pendBadge, + isBlocked, + deleteBadge, + badgeExists, + getBadges, + approveBadge, + blockUser, + unblockUser, } from "../lib/mongo.js"; -import untypedConfig from "../../config/config.json" with { type: "json" }; import { fireVerification } from "../lib/verification.js"; -import { isAllowedDomain } from "../lib/checkDomain.js"; import { Badge } from "../types/badge.js"; -import { Config } from "../types/config.js"; - -const { MaxBadges, ExtraBoostBadges, VerifierRole } = untypedConfig as Config; export const data = new SlashCommandBuilder() - .setName("badge") - .setDescription("Manage your badges") - .addSubcommand((subcommand) => - subcommand - .setName("create") - .setDescription("Add a new badge") - .addStringOption((option) => - option - .setName("name") - .setDescription("The name of the badge") - .setRequired(true), - ) - .addStringOption((option) => - option - .setName("url") - .setDescription("The image URL of the badge") - .setRequired(true), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName("delete") - .setDescription("Delete a badge") - .addStringOption((option) => - option - .setName("name") - .setDescription("The name of the badge") - .setRequired(true) - .setAutocomplete(true), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName("list") - .setDescription("List a user's badges (defaults to you)") - .addUserOption((option) => - option - .setName("user") - .setDescription("Optional user to check badges for") - .setRequired(false), - ), - ); + .setName("badge") + .setDescription("Manage your badges") + .addSubcommand((subcommand) => + subcommand + .setName("create") + .setDescription("Add a new badge") + .addStringOption((option) => + option + .setName("name") + .setDescription("The name of the badge") + .setRequired(true), + ) + .addAttachmentOption((option) => + option + .setName("image") + .setDescription("The image for the badge") + .setRequired(true), + ), + ) + .addSubcommand((subcommand) => + subcommand + .setName("delete") + .setDescription("Delete a badge") + .addStringOption((option) => + option + .setName("name") + .setDescription("The name of the badge") + .setRequired(true) + .setAutocomplete(true), + ), + ) + .addSubcommand((subcommand) => + subcommand + .setName("list") + .setDescription("List a user's badges (defaults to you)") + .addUserOption((option) => + option + .setName("user") + .setDescription("Optional user to check badges for") + .setRequired(false), + ), + ); export async function execute( - interaction: ChatInputCommandInteraction, + interaction: ChatInputCommandInteraction, ): Promise { - const id = interaction.user.id; + const id = interaction.user.id; - //SECTION - NEW BADGE + //SECTION - NEW BADGE - if (interaction.options.getSubcommand() === "create") { - if (await isBlocked(id)) { - await interaction.reply({ - content: - "You are blocked from making new badges! Please contact an admin if you believe this is a mistake.", - ephemeral: true, - }); - return; - } - if (!(await canMakeNewBadge(interaction.member as GuildMember))) { - await interaction.reply({ - content: `You already have ${ - MaxBadges.toString() + - ((interaction.member as GuildMember).premiumSince - ? ExtraBoostBadges - : 0).toString() - } or more badges! (This includes pending badges!)`, - ephemeral: true, - }); - return; - } - // These WILL exist because they are required by the slash command - const name = interaction.options.getString("name")!; - const url = interaction.options.getString("url")!; + if (interaction.options.getSubcommand() === "create") { + if (await isBlocked(id)) { + await interaction.reply({ + content: + "You are blocked from making new badges! Please contact an admin if you believe this is a mistake.", + ephemeral: true, + }); + return; + } + if (!(await canMakeNewBadge(interaction.member as GuildMember))) { + await interaction.reply({ + content: `You already have ${( + Number(process.env["MAX_BADGES"]!) + + ((interaction.member as GuildMember).premiumSince + ? Number(process.env["EXTRA_BOOST_BADGES"]!) + : 0) + ).toString()} or more badges! (This includes pending badges!)`, + ephemeral: true, + }); + return; + } + // These WILL exist because they are required by the slash command + const name = interaction.options.getString("name")!; + const image = interaction.options.getAttachment("image")!; - if (/<:(.*):(.*)>/.test(name)) { - await interaction.reply({ - content: - "Custom emojis will not appear in the badge name, and are thus blocked.", - ephemeral: true, - }); - return; - } + if (image.size > Number(process.env["MAX_BADGE_SIZE"])) { + await interaction.reply({ + content: `The image you have attached is over ${(Number(process.env["MAX_BADGE_SIZE"]) / 1048576).toString()}MB, please try to make it smaller!`, + ephemeral: true, + }); + return; + } + if (/<:(.*):(.*)>/.test(name)) { + await interaction.reply({ + content: + "Custom emojis will not appear in the badge name, and are thus blocked.", + ephemeral: true, + }); + return; + } - if (!(await isAllowedDomain(url))) { - await interaction.reply({ - content: - "This is not a whitelisted domain or it is improperly formatted", - ephemeral: true, - }); - return; - } - if (await badgeExists(id, name, "all")) { - await interaction.reply({ - content: "You already have a badge with that name!", - ephemeral: true, - }); - return; - } + if (await badgeExists(id, name, "all")) { + await interaction.reply({ + content: "You already have a badge with that name!", + ephemeral: true, + }); + return; + } - await pendBadge(id, { - name, - badge: url, - }).then(async () => { - await interaction.reply({ - content: "Badge is now pending approval!", - ephemeral: true, - }); - await fireVerification(interaction); - }); - return; - } + await pendBadge(id, { + name, + badge: image.url, + }).then(async () => { + await interaction.reply({ + content: "Badge is now pending approval!", + ephemeral: true, + }); + await fireVerification(interaction); + }); + return; + } - //SECTION - DELETE BADGE + //SECTION - DELETE BADGE - if (interaction.options.getSubcommand() === "delete") { - const name = interaction.options.getString("name")!; - if (await badgeExists(id, name, "active")) { - await deleteBadge(id, name).then(async () => { - await interaction.reply({ - content: "Badge deleted!", - ephemeral: true, - }); - }); - return; - } - await interaction.reply({ - content: "You do not have an active badge with that name!", - ephemeral: true, - }); - return; - } - //SECTION - LIST BADGES + if (interaction.options.getSubcommand() === "delete") { + const name = interaction.options.getString("name")!; + if (await badgeExists(id, name, "active")) { + await deleteBadge(id, name).then(async () => { + await interaction.reply({ + content: "Badge deleted!", + ephemeral: true, + }); + }); + return; + } + await interaction.reply({ + content: "You do not have an active badge with that name!", + ephemeral: true, + }); + return; + } + //SECTION - LIST BADGES - if (interaction.options.getSubcommand() === "list") { - let user = interaction.user; - if (interaction.options.getUser("user")) { - user = interaction.options.getUser("user")!; - } - const badges = await getBadges(user.id, "all"); - const returnEmbed = new EmbedBuilder() - .setTitle(`${user.username}'s Badge Overview`) - .setDescription(`${user.username} has ${badges.length.toString()} badges`) - .setColor("#FF0000"); + if (interaction.options.getSubcommand() === "list") { + let user = interaction.user; + if (interaction.options.getUser("user")) { + user = interaction.options.getUser("user")!; + } + const badges = await getBadges(user.id, "all"); + const returnEmbed = new EmbedBuilder() + .setTitle(`${user.username}'s Badge Overview`) + .setDescription( + `${user.username} has ${badges.length.toString()} badges`, + ) + .setColor("#FF0000"); - for (const badge of badges) { - returnEmbed.addFields({ - name: `${badge.name}${ - badge.pending ? " `(Pending Approval)`" : "" - }`, - value: badge.badge, - }); - } + for (const badge of badges) { + returnEmbed.addFields({ + name: `${badge.name}${ + badge.pending ? " `(Pending Approval)`" : "" + }`, + value: badge.badge, + }); + } - await interaction.reply({ - embeds: [returnEmbed], - ephemeral: true, - }); - } + await interaction.reply({ + embeds: [returnEmbed], + ephemeral: true, + }); + } } const blockActionRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId("verify.block") - .setStyle(ButtonStyle.Danger) - .setEmoji("⛔") - .setLabel("Block User"), + new ButtonBuilder() + .setCustomId("verify.block") + .setStyle(ButtonStyle.Danger) + .setEmoji("⛔") + .setLabel("Block User"), ); const unblockButtonRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId("verify.unblock") - .setStyle(ButtonStyle.Danger) - .setEmoji("🔑") - .setLabel("Unblock User"), + new ButtonBuilder() + .setCustomId("verify.unblock") + .setStyle(ButtonStyle.Danger) + .setEmoji("🔑") + .setLabel("Unblock User"), ); export const buttons = [ - { - id: "verify.accept", - execute: async (interaction: ButtonInteraction) => { - if (interaction.inCachedGuild()) { - if (interaction.member.roles.cache.has(VerifierRole)) { - const newEmbed = EmbedBuilder.from( - interaction.message.embeds.at(0)!, - ) - .setFooter({ - text: `Approved by ${interaction.user.username}`, - }) - .setColor("#00FF00"); - await interaction.update({ - embeds: [newEmbed], - components: [], - }); - await approveBadge( - interaction.message.mentions.users.at(0)!.id, - interaction.message.embeds.at(0)!.fields[0]!.value, - ); - } else { - await interaction.reply({ - content: "You cannot do that!", - ephemeral: true, - }); - } - } - }, - }, - { - id: "verify.deny", - execute: async (interaction: ButtonInteraction) => { - const originalPrompter = interaction.message.mentions.users.at(0)!; - if (interaction.inCachedGuild()) { - if ( - interaction.member.roles.cache.has(VerifierRole) || - originalPrompter == interaction.user - ) { - const newEmbed = EmbedBuilder.from( - interaction.message.embeds.at(0)!, - ) - .setFooter({ - text: `Denied by ${interaction.user.username}`, - }) - .setColor("#FF0000"); - await interaction.update({ - embeds: [newEmbed], - components: [blockActionRow], - }); - await deleteBadge( - originalPrompter.id, - interaction.message.embeds.at(0)!.fields[0]!.value, - ); - } else { - await interaction.reply({ - content: "You cannot do that!", - ephemeral: true, - }); - } - } - }, - }, - { - id: "verify.block", - execute: async (interaction: ButtonInteraction) => { - if (interaction.inCachedGuild()) { - if (interaction.member.roles.cache.has(VerifierRole)) { - await blockUser( - interaction.message.mentions.users.at(0)!.id, - ); - const newEmbed = EmbedBuilder.from( - interaction.message.embeds.at(0)!, - ) - .setFooter({ - text: `Blocked by ${interaction.user.username}`, - }) - .setColor("#000000"); - await interaction.update({ - embeds: [newEmbed], - components: [unblockButtonRow], - }); - } else { - await interaction.reply({ - content: "You cannot do that!", - ephemeral: true, - }); - } - } - }, - }, - { - id: "verify.unblock", - execute: async (interaction: ButtonInteraction) => { - if (interaction.inCachedGuild()) { - if (interaction.member.roles.cache.has(VerifierRole)) { - await unblockUser( - interaction.message.mentions.users.at(0)!.id, - ); - const newEmbed = EmbedBuilder.from( - interaction.message.embeds.at(0)!, - ) - .setFooter({ - text: `Unblocked by ${interaction.user.username}`, - }) - .setColor("#FF0000"); - await interaction.update({ - embeds: [newEmbed], - components: [blockActionRow], - }); - } else { - await interaction.reply({ - content: "You cannot do that!", - ephemeral: true, - }); - } - } - }, - }, + { + id: "verify.accept", + execute: async (interaction: ButtonInteraction) => { + if (interaction.inCachedGuild()) { + if ( + interaction.member.roles.cache.has( + process.env["VERIFIER_ROLE"]!, + ) + ) { + const newEmbed = EmbedBuilder.from( + interaction.message.embeds.at(0)!, + ) + .setFooter({ + text: `Approved by ${interaction.user.username}`, + }) + .setColor("#00FF00"); + await interaction.update({ + embeds: [newEmbed], + components: [], + }); + await approveBadge( + interaction.message.mentions.users.at(0)!.id, + interaction.message.embeds.at(0)!.fields[0]!.value, + ); + } else { + await interaction.reply({ + content: "You cannot do that!", + ephemeral: true, + }); + } + } + }, + }, + { + id: "verify.deny", + execute: async (interaction: ButtonInteraction) => { + const originalPrompter = interaction.message.mentions.users.at(0)!; + if (interaction.inCachedGuild()) { + if ( + interaction.member.roles.cache.has( + process.env["VERIFIER_ROLE"]!, + ) || + originalPrompter == interaction.user + ) { + const newEmbed = EmbedBuilder.from( + interaction.message.embeds.at(0)!, + ) + .setFooter({ + text: `Denied by ${interaction.user.username}`, + }) + .setColor("#FF0000"); + await interaction.update({ + embeds: [newEmbed], + components: [blockActionRow], + }); + await deleteBadge( + originalPrompter.id, + interaction.message.embeds.at(0)!.fields[0]!.value, + ); + } else { + await interaction.reply({ + content: "You cannot do that!", + ephemeral: true, + }); + } + } + }, + }, + { + id: "verify.block", + execute: async (interaction: ButtonInteraction) => { + if (interaction.inCachedGuild()) { + if ( + interaction.member.roles.cache.has( + process.env["VERIFIER_ROLE"]!, + ) + ) { + await blockUser( + interaction.message.mentions.users.at(0)!.id, + ); + const newEmbed = EmbedBuilder.from( + interaction.message.embeds.at(0)!, + ) + .setFooter({ + text: `Blocked by ${interaction.user.username}`, + }) + .setColor("#000000"); + await interaction.update({ + embeds: [newEmbed], + components: [unblockButtonRow], + }); + } else { + await interaction.reply({ + content: "You cannot do that!", + ephemeral: true, + }); + } + } + }, + }, + { + id: "verify.unblock", + execute: async (interaction: ButtonInteraction) => { + if (interaction.inCachedGuild()) { + if ( + interaction.member.roles.cache.has( + process.env["VERIFIER_ROLE"]!, + ) + ) { + await unblockUser( + interaction.message.mentions.users.at(0)!.id, + ); + const newEmbed = EmbedBuilder.from( + interaction.message.embeds.at(0)!, + ) + .setFooter({ + text: `Unblocked by ${interaction.user.username}`, + }) + .setColor("#FF0000"); + await interaction.update({ + embeds: [newEmbed], + components: [blockActionRow], + }); + } else { + await interaction.reply({ + content: "You cannot do that!", + ephemeral: true, + }); + } + } + }, + }, ]; export async function autocomplete(interaction: AutocompleteInteraction) { - const options: string[] = []; - const focus = interaction.options.getFocused(); - (await getBadges(interaction.user.id, "all")).forEach((badge: Badge) => { - options.push(badge.name); - }); - const filtered = options.filter((option) => option.startsWith(focus)); - await interaction.respond( - filtered.map((option) => ({ name: option, value: option })), - ); + const options: string[] = []; + const focus = interaction.options.getFocused(); + (await getBadges(interaction.user.id, "all")).forEach((badge: Badge) => { + options.push(badge.name); + }); + const filtered = options.filter((option) => option.startsWith(focus)); + await interaction.respond( + filtered.map((option) => ({ name: option, value: option })), + ); } diff --git a/src/handler/autocomplete.ts b/src/handler/autocomplete.ts index 2c6ebe2..ad68691 100644 --- a/src/handler/autocomplete.ts +++ b/src/handler/autocomplete.ts @@ -2,19 +2,19 @@ import type { AutocompleteInteraction } from "discord.js"; import { Command } from "../types/command.js"; export async function handleAutocomplete( - interaction: AutocompleteInteraction, - commands: Map, + interaction: AutocompleteInteraction, + commands: Map, ) { - const command = commands.get(interaction.commandName); - if (!command) { - console.error( - `[Autocomplete Handler]: Command ${interaction.commandName} not found`, - ); - } else if (command.autocomplete) { - try { - await command.autocomplete(interaction); - } catch (error) { - console.error(error); - } - } + const command = commands.get(interaction.commandName); + if (!command) { + console.error( + `[Autocomplete Handler]: Command ${interaction.commandName} not found`, + ); + } else if (command.autocomplete) { + try { + await command.autocomplete(interaction); + } catch (error) { + console.error(error); + } + } } diff --git a/src/handler/button.ts b/src/handler/button.ts index 45e8284..fb9df8c 100644 --- a/src/handler/button.ts +++ b/src/handler/button.ts @@ -2,17 +2,17 @@ import type { ButtonInteraction } from "discord.js"; import type { Command } from "../types/command.js"; export async function handleButton( - interaction: ButtonInteraction, - commands: Map, + interaction: ButtonInteraction, + commands: Map, ) { - for (const command of commands) { - const buttons = command[1].buttons; - if (!buttons) continue; + for (const command of commands) { + const buttons = command[1].buttons; + if (!buttons) continue; - for (let i = 0; i < buttons.length; ++i) { - if (buttons[i]?.id === interaction.customId) { - await buttons[i]?.execute(interaction); - } - } - } + for (let i = 0; i < buttons.length; ++i) { + if (buttons[i]?.id === interaction.customId) { + await buttons[i]?.execute(interaction); + } + } + } } diff --git a/src/handler/command.ts b/src/handler/command.ts index 7f9119e..97f47db 100644 --- a/src/handler/command.ts +++ b/src/handler/command.ts @@ -2,21 +2,23 @@ import type { ChatInputCommandInteraction } from "discord.js"; import type { Command } from "../types/command.js"; export async function handleCommand( - interaction: ChatInputCommandInteraction, - commands: Map, + interaction: ChatInputCommandInteraction, + commands: Map, ) { - try { - await commands.get(interaction.commandName)?.execute(interaction); - } catch (error:unknown) { - console.error(error); - try { - await interaction.reply({ - content: `There was an error while executing this command! Debug: \`${(error as Error).message}\``, - ephemeral: true, - }); - } catch (e){ - console.error("Caught error executing command, but it was already replied to!") - console.error(error) - } - } + try { + await commands.get(interaction.commandName)?.execute(interaction); + } catch (error: unknown) { + console.error(error); + try { + await interaction.reply({ + content: `There was an error while executing this command! Debug: \`${(error as Error).message}\``, + ephemeral: true, + }); + } catch (e) { + console.error( + "Caught error executing command, but it was already replied to!", + ); + console.error(e); + } + } } diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 1f6f301..0000000 --- a/src/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - Client, - GatewayIntentBits, - Events, - type ChatInputCommandInteraction, -} from "discord.js"; -import { destroy } from "./lib/mongo.js"; -import untypedConfig from "../config/config.json" with { type: "json" }; -import type { Config } from "./types/config.js"; -const { DiscordToken } = untypedConfig as Config; - -const client = new Client({ - intents: [GatewayIntentBits.Guilds], -}); - -client.once(Events.ClientReady, () => { - console.log("Connected to Discord!"); -}); - -import { indexCommands } from "./lib/indexer.js"; -await indexCommands(); -import { pushCommands } from "./lib/pushCommands.js"; -await pushCommands(); -import { commands } from "./lib/indexer.js"; -import { handleCommand } from "./handler/command.js"; -import { handleButton } from "./handler/button.js"; -import { handleAutocomplete } from "./handler/autocomplete.js"; - -client.on(Events.InteractionCreate, (interaction) => { - if (interaction.isCommand()) { - void handleCommand( - interaction as ChatInputCommandInteraction, - commands, - ); - } else if (interaction.isButton()) { - void handleButton(interaction, commands); - } else if (interaction.isAutocomplete()) { - void handleAutocomplete(interaction, commands); - } -}); - -client.on(Events.Error, (error) => {console.error(error)}); -client.on(Events.Warn, (warning) => {console.warn(warning)}); -client.on(Events.Invalidated, () => { - (async ()=>{ - console.log("Session Invalidated - Stopping Client"); - await client.destroy(); - await destroy(); - process.exit(1); - }); -}); - -await client.login(DiscordToken); diff --git a/src/lib/bucket.ts b/src/lib/bucket.ts index d0b3564..40cd895 100644 --- a/src/lib/bucket.ts +++ b/src/lib/bucket.ts @@ -1,43 +1,50 @@ import { Client } from "minio"; import { createHash } from "crypto"; -import untypedConfig from "../../config/config.json" with { type: "json" }; -import { Config } from "../types/config.js"; - -const { BucketEndpoint, BucketPort, BucketAccessKey, BucketSecretKey, BucketSSL, BucketDomain, BucketName } = untypedConfig as Config; - const minioClient = new Client({ - endPoint: BucketEndpoint, - port: BucketPort, - useSSL:BucketSSL, - accessKey: BucketAccessKey, - secretKey: BucketSecretKey + endPoint: process.env["BUCKET_ENDPOINT"]!, + port: Number(process.env["BUCKET_PORT"]), + useSSL: process.env["BUCKET_SSL"] === "true", + accessKey: process.env["BUCKET_ACCESS_KEY"]!, + secretKey: process.env["BUCKET_SECRET_KEY"]!, }); -function mimeToExt(extension:string|null){ - switch(extension){ - case "image/webp": return ".webp"; - case "image/png": return ".png"; - case "image/jpeg": return ".jpg" - case "image/gif": return ".gif" - case "image/apng": return ".apng" - default: throw(new TypeError("Unacceptable MIME")) - } +function mimeToExt(extension: string | null) { + switch (extension) { + case "image/webp": + return ".webp"; + case "image/png": + return ".png"; + case "image/jpeg": + return ".jpg"; + case "image/gif": + return ".gif"; + case "image/apng": + return ".apng"; + default: + throw new TypeError("Unacceptable MIME"); + } } -export async function BucketUpload(url:string):Promise{ - const name = createHash("sha1").update((Math.random()+1).toString(36).substring(2)+url).digest("hex") - return await fetch(url).then(async (data)=>{ - const ext = mimeToExt(data.headers.get("Content-Type")) - await minioClient.putObject(BucketName, name+ext, Buffer.from(await data.arrayBuffer())) // Don't ask me how this buffer thing works, I don't know yet - return `${BucketDomain}/${name+ext}` - }) +export async function BucketUpload(url: string): Promise { + const name = createHash("sha1") + .update((Math.random() + 1).toString(36).substring(2) + url) + .digest("hex"); + return await fetch(url).then(async (data) => { + const ext = mimeToExt(data.headers.get("Content-Type")); + await minioClient.putObject( + process.env["BUCKET_NAME"]!, + name + ext, + Buffer.from(await data.arrayBuffer()), + ); // Don't ask me how this buffer thing works, I don't know yet + return `${process.env["BUCKET_DOMAIN"]!}/${name + ext}`; + }); } -export async function BucketDelete(url:string){ - const obj = url.split(`${BucketDomain}/`)[1] - if(!obj){ - throw new URIError("URL does not seem to contain an object") - } - await minioClient.removeObject(BucketName, obj) -} \ No newline at end of file +export async function BucketDelete(url: string) { + const obj = url.split(`${process.env["BUCKET_DOMAIN"]!}/`)[1]; + if (!obj) { + throw new URIError("URL does not seem to contain an object"); + } + await minioClient.removeObject(process.env["BUCKET_NAME"]!, obj); +} diff --git a/src/lib/checkDomain.ts b/src/lib/checkDomain.ts deleted file mode 100644 index fe3c1e8..0000000 --- a/src/lib/checkDomain.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Config } from "../types/config.js"; -import untypedConfig from "../../config/config.json" with { type: "json" }; - -const { Domains } = untypedConfig as Config; - -export async function isAllowedDomain(domain: string): Promise { - let out = false; - if (Domains.some((v) => domain.includes(v))) { - await fetch(domain) - .then((r) => { - out = r.ok; - }) - .catch((error:unknown) => { - console.warn(error); - out = false; - }); - } else { - out = false; - } - return out; -} diff --git a/src/lib/indexer.ts b/src/lib/indexer.ts index 568ac99..78919be 100644 --- a/src/lib/indexer.ts +++ b/src/lib/indexer.ts @@ -6,11 +6,11 @@ export const commandData: SlashCommandOptionsOnlyBuilder[] = []; export const commands = new Map(); export async function indexCommands() { - for (const file of commandFiles) { - const command = (await import( - `../commands/${file}.js`.replace(".ts", "") - )) as Command; - commandData.push(command.data); - commands.set(command.data.name, command); - } + for (const file of commandFiles) { + const command = (await import( + `../commands/${file}.js`.replace(".ts", "") + )) as Command; + commandData.push(command.data); + commands.set(command.data.name, command); + } } diff --git a/src/lib/mongo.ts b/src/lib/mongo.ts index 5752650..dd53106 100644 --- a/src/lib/mongo.ts +++ b/src/lib/mongo.ts @@ -1,144 +1,142 @@ -import untypedConfig from "../../config/config.json" with { type: "json" }; import { MongoClient, Collection } from "mongodb"; import type { Badge } from "../types/badge.d.js"; import { Entry } from "../types/entry.js"; import { GuildMember } from "discord.js"; -import { Config } from "../types/config.js"; import { BucketDelete, BucketUpload } from "./bucket.js"; -const { CollectionName, DatabaseName, MongoDB, MaxBadges, ExtraBoostBadges, BucketDomain } = untypedConfig as Config; - -const client = new MongoClient(MongoDB); +const client = new MongoClient(process.env["MONGO_DB"]!); async function connect(): Promise { - await client.connect(); - const collection: Collection = client - .db(DatabaseName) - .collection(CollectionName); - return collection; + await client.connect(); + const collection: Collection = client + .db(process.env["DATABASE_NAME"]) + .collection(process.env["COLLECTION_NAME"]!); + return collection; } const mongo = await connect(); export async function destroy(): Promise { - await client.close(); + await client.close(); } -export async function getEntry(userId: string): Promise { - let entry = (await mongo.findOne({ userId })) as Entry; - // I don't remember the exact logic here and don't really wanna rework it - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (entry === null) { - await mongo.insertOne({ - userId, - badges: [], - blocked: false, - }); - entry = (await mongo.findOne({ userId })) as Entry; - } - return entry; +async function getEntry(userId: string): Promise { + let entry = (await mongo.findOne({ userId })) as Entry; + // I don't remember the exact logic here and don't really wanna rework it + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (entry === null) { + await mongo.insertOne({ + userId, + badges: [], + blocked: false, + }); + entry = (await mongo.findOne({ userId })) as Entry; + } + return entry; } // GETTERS export async function isBlocked(userId: string): Promise { - return (await getEntry(userId)).blocked; + return (await getEntry(userId)).blocked; } export async function getBadges( - userId: string, - only: "all" | "pending" | "active", + userId: string, + only: "all" | "pending" | "active", ) { - const allBadges = (await getEntry(userId)).badges; - const returnArray: Badge[] = []; - switch (only) { - case "all": - return allBadges; - case "active": - allBadges.forEach((badge) => { - if (!badge.pending) returnArray.push(badge); - }); - return returnArray; - case "pending": - allBadges.forEach((badge) => { - if (badge.pending) returnArray.push(badge); - }); - return returnArray; - } + const allBadges = (await getEntry(userId)).badges; + const returnArray: Badge[] = []; + switch (only) { + case "all": + return allBadges; + case "active": + allBadges.forEach((badge) => { + if (!badge.pending) returnArray.push(badge); + }); + return returnArray; + case "pending": + allBadges.forEach((badge) => { + if (badge.pending) returnArray.push(badge); + }); + return returnArray; + } } export async function getBadge( - userId: string, - name: string, + userId: string, + name: string, ): Promise { - const foundBadge = (await getEntry(userId)).badges.find( - (badge) => badge.name === name, - ); - return foundBadge; + const foundBadge = (await getEntry(userId)).badges.find( + (badge) => badge.name === name, + ); + return foundBadge; } export async function canMakeNewBadge(user: GuildMember): Promise { - const entry = await getEntry(user.id); - return !( - entry.badges.length >= - MaxBadges + (user.premiumSince ? ExtraBoostBadges : 0) - ); + const entry = await getEntry(user.id); + return !( + entry.badges.length >= + Number(process.env["MAX_BADGES"]) + + (user.premiumSince ? Number(process.env["EXTRA_BOOST_BADGES"]) : 0) + ); } // SETTERS export async function pendBadge(userId: string, badge: Badge): Promise { - badge.pending = true; - // @ts-expect-error - Possibly https://jira.mongodb.org/browse/NODE-5995 - await mongo.updateOne({ userId }, { $push: { badges: badge } }); + badge.pending = true; + // @ts-expect-error - Possibly https://jira.mongodb.org/browse/NODE-5995 + await mongo.updateOne({ userId }, { $push: { badges: badge } }); } export async function approveBadge( - userId: string, - name: string, + userId: string, + name: string, ): Promise { - const badge = await getBadge(userId,name) - if(!badge){ - throw new Error("Badge is not pending") - } - const s3Url = await BucketUpload(badge.badge) - if(!s3Url){ - throw new Error("S3 upload failed") - } - await mongo.updateOne( - { userId, "badges.name": name }, - { $set: { "badges.$.pending": false, "badges.$.badge": s3Url } }, - ); + const badge = await getBadge(userId, name); + if (!badge) { + throw new Error("Badge is not pending"); + } + const s3Url = await BucketUpload(badge.badge); + if (!s3Url) { + throw new Error("S3 upload failed"); + } + await mongo.updateOne( + { userId, "badges.name": name }, + { $set: { "badges.$.pending": false, "badges.$.badge": s3Url } }, + ); } export async function blockUser(userId: string): Promise { - await mongo.updateOne({ userId }, { $set: { blocked: true } }); + await mongo.updateOne({ userId }, { $set: { blocked: true } }); } export async function unblockUser(userId: string): Promise { - await mongo.updateOne({ userId }, { $set: { blocked: false } }); + await mongo.updateOne({ userId }, { $set: { blocked: false } }); } // DELETE FUNCTIONS export async function deleteBadge(userId: string, name: string): Promise { - const badge = await getBadge(userId,name) - if(!badge){ - throw new Error("Badge does not exist") - } - if(badge.badge.includes(BucketDomain)){ // Badges that have not been approved will not be in the bucket - await BucketDelete(badge.badge) - } - // @ts-expect-error - Possibly https://jira.mongodb.org/browse/NODE-5995 - await mongo.updateOne({ userId }, { $pull: { badges: { name } } }); + const badge = await getBadge(userId, name); + if (!badge) { + throw new Error("Badge does not exist"); + } + if (badge.badge.includes(process.env["BUCKET_DOMAIN"]!)) { + // Badges that have not been approved will not be in the bucket + await BucketDelete(badge.badge); + } + // @ts-expect-error - Possibly https://jira.mongodb.org/browse/NODE-5995 + await mongo.updateOne({ userId }, { $pull: { badges: { name } } }); } // OTHER export async function badgeExists( - userId: string, - name: string, - only: "all" | "pending" | "active", + userId: string, + name: string, + only: "all" | "pending" | "active", ): Promise { - return (await getBadges(userId, only)).some((badge) => badge.name === name); + return (await getBadges(userId, only)).some((badge) => badge.name === name); } diff --git a/src/lib/pushCommands.ts b/src/lib/pushCommands.ts index e03b726..60a8253 100644 --- a/src/lib/pushCommands.ts +++ b/src/lib/pushCommands.ts @@ -1,20 +1,20 @@ import { commandData } from "./indexer.js"; import { REST, Routes } from "discord.js"; -import untypedConfig from "../../config/config.json" with { type: "json" }; -import type { Config } from "../types/config.js"; - -const { DiscordToken, ClientID } = untypedConfig as Config; - -const restAPI = new REST({ version: "10" }).setToken(DiscordToken); +const restAPI = new REST({ version: "10" }).setToken( + process.env["DISCORD_TOKEN"]!, +); export async function pushCommands(): Promise { - try { - console.log(`Pushing ${commandData.length.toString()} commands...`); - await restAPI.put(Routes.applicationCommands(ClientID), { - body: commandData, - }); - } catch (error) { - console.error(error); - } + try { + console.log(`Pushing ${commandData.length.toString()} commands...`); + await restAPI.put( + Routes.applicationCommands(process.env["CLIENT_ID"]!), + { + body: commandData, + }, + ); + } catch (error) { + console.error(error); + } } diff --git a/src/lib/verification.ts b/src/lib/verification.ts index 2cc03ab..559907b 100644 --- a/src/lib/verification.ts +++ b/src/lib/verification.ts @@ -1,67 +1,65 @@ -import untypedConfig from "../../config/config.json" with { type: "json" }; -import { Config } from "../types/config.js"; - -const { PromptChannel } = untypedConfig as Config; import { - EmbedBuilder, - ChatInputCommandInteraction, - TextChannel, - ButtonBuilder, - ButtonStyle, - ActionRowBuilder, + EmbedBuilder, + ChatInputCommandInteraction, + TextChannel, + ButtonBuilder, + ButtonStyle, + ActionRowBuilder, } from "discord.js"; export async function fireVerification(data: ChatInputCommandInteraction) { - const user = data.user; - const badgeName = data.options.getString("name")!; - const badgeURL = data.options.getString("url")!; + const user = data.user; + const badgeName = data.options.getString("name")!; + const badgeImage = data.options.getAttachment("image")!; - const acceptButton = new ButtonBuilder() - .setCustomId("verify.accept") - .setStyle(ButtonStyle.Success) - .setEmoji("✅"); + const acceptButton = new ButtonBuilder() + .setCustomId("verify.accept") + .setStyle(ButtonStyle.Success) + .setEmoji("✅"); - const denyButton = new ButtonBuilder() - .setCustomId("verify.deny") - .setStyle(ButtonStyle.Danger) - .setEmoji("✖️"); + const denyButton = new ButtonBuilder() + .setCustomId("verify.deny") + .setStyle(ButtonStyle.Danger) + .setEmoji("✖️"); - const embed = new EmbedBuilder() - .setAuthor({ - name: user.username, - iconURL: user.displayAvatarURL(), - }) - .setImage(data.options.getString("url")) - .addFields({ - name: "Badge Name", - value: badgeName, - }) - .addFields({ - name: "URL:", - value: badgeURL, - }) - .setTimestamp(Date.now()) - .setColor("#FFA500"); - if (data.options.getSubcommand() === "create") { - embed.setTitle(`${user.username}'s Badge Request:`); - acceptButton.setLabel("Approve Badge Creation"); - denyButton.setLabel("Deny Badge Creation"); - } else { - embed.setTitle(`${user.username}'s Badge Change:`); - acceptButton.setLabel("Approve Badge URL Change"); - denyButton.setLabel("Deny Badge URL Change"); - } + const embed = new EmbedBuilder() + .setAuthor({ + name: user.username, + iconURL: user.displayAvatarURL(), + }) + .setImage(badgeImage.url) + .addFields({ + name: "Badge Name", + value: badgeName, + }) + .addFields({ + name: "URL:", + value: badgeImage.url, + }) + .setTimestamp(Date.now()) + .setColor("#FFA500"); + if (data.options.getSubcommand() === "create") { + embed.setTitle(`${user.username}'s Badge Request:`); + acceptButton.setLabel("Approve Badge Creation"); + denyButton.setLabel("Deny Badge Creation"); + } else { + embed.setTitle(`${user.username}'s Badge Change:`); + acceptButton.setLabel("Approve Badge URL Change"); + denyButton.setLabel("Deny Badge URL Change"); + } - const buttons = new ActionRowBuilder().addComponents( - acceptButton, - denyButton, - ); + const buttons = new ActionRowBuilder().addComponents( + acceptButton, + denyButton, + ); - await ( - data.client.channels.cache.get(PromptChannel) as TextChannel - ).send({ - content: `<@${user.id}>`, - embeds: [embed], - components: [buttons], - }); + await ( + data.client.channels.cache.get( + process.env["PROMPT_CHANNEL"]!, + ) as TextChannel + ).send({ + content: `<@${user.id}>`, + embeds: [embed], + components: [buttons], + }); } diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..b48c341 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,52 @@ +import { + Client, + GatewayIntentBits, + Events, + type ChatInputCommandInteraction, +} from "discord.js"; +import { destroy } from "./lib/mongo.js"; + +const client = new Client({ + intents: [GatewayIntentBits.Guilds], +}); + +client.once(Events.ClientReady, () => { + console.log("Connected to Discord!"); +}); + +import { indexCommands } from "./lib/indexer.js"; +await indexCommands(); +import { pushCommands } from "./lib/pushCommands.js"; +await pushCommands(); +import { commands } from "./lib/indexer.js"; +import { handleCommand } from "./handler/command.js"; +import { handleButton } from "./handler/button.js"; +import { handleAutocomplete } from "./handler/autocomplete.js"; + +client.on(Events.InteractionCreate, (interaction) => { + if (interaction.isCommand()) { + void handleCommand( + interaction as ChatInputCommandInteraction, + commands, + ); + } else if (interaction.isButton()) { + void handleButton(interaction, commands); + } else if (interaction.isAutocomplete()) { + void handleAutocomplete(interaction, commands); + } +}); + +client.on(Events.Error, (error) => { + console.error(error); +}); +client.on(Events.Warn, (warning) => { + console.warn(warning); +}); +client.on(Events.Invalidated, () => { + console.log("Session Invalidated - Stopping Client"); + void destroy().then(async () => { + await client.destroy(); + }); +}); + +await client.login(process.env["DISCORD_TOKEN"]); diff --git a/src/types/badge.d.ts b/src/types/badge.d.ts index d7e459f..d43b5a3 100644 --- a/src/types/badge.d.ts +++ b/src/types/badge.d.ts @@ -1,5 +1,5 @@ export interface Badge { - name: string; - badge: string; - pending?: boolean; + name: string; + badge: string; + pending?: boolean; } diff --git a/src/types/button.d.ts b/src/types/button.d.ts index 3b6fc64..a685b16 100644 --- a/src/types/button.d.ts +++ b/src/types/button.d.ts @@ -1,6 +1,6 @@ import type { ButtonInteraction } from "discord.js"; export interface Button { - id: string; - execute: (interaction: ButtonInteraction) => Promise; + id: string; + execute: (interaction: ButtonInteraction) => Promise; } diff --git a/src/types/command.d.ts b/src/types/command.d.ts index 2f511c4..791e29e 100644 --- a/src/types/command.d.ts +++ b/src/types/command.d.ts @@ -1,14 +1,14 @@ import type { - AutocompleteInteraction, - ChatInputCommandInteraction, - SlashCommandOptionsOnlyBuilder, + AutocompleteInteraction, + ChatInputCommandInteraction, + SlashCommandOptionsOnlyBuilder, } from "discord.js"; import type { Button } from "./button.js"; export interface Command { - data: SlashCommandOptionsOnlyBuilder; - execute: (interaction: ChatInputCommandInteraction) => Promise; - buttons?: Button[]; - autocomplete?: (interaction: AutocompleteInteraction) => Promise; + data: SlashCommandOptionsOnlyBuilder; + execute: (interaction: ChatInputCommandInteraction) => Promise; + buttons?: Button[]; + autocomplete?: (interaction: AutocompleteInteraction) => Promise; } diff --git a/src/types/config.d.ts b/src/types/config.d.ts deleted file mode 100644 index 0782d3f..0000000 --- a/src/types/config.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface Config { - DiscordToken: string; - MongoDB: string; - ClientID: string; - DatabaseName: string; - CollectionName: string; - MaxBadges: number; - ExtraBoostBadges: number; - PromptChannel: string; - VerifierRole: string; - Domains: string[]; - BucketEndpoint: string; - BucketPort: number; - BucketSSL: boolean - BucketAccessKey: string; - BucketSecretKey: string; - BucketName: string; - BucketDomain: string; -} diff --git a/src/types/entry.d.ts b/src/types/entry.d.ts index 92ee054..e075b48 100644 --- a/src/types/entry.d.ts +++ b/src/types/entry.d.ts @@ -2,8 +2,8 @@ import type { ObjectId } from "mongodb"; import type { Badge } from "./badge.d.ts"; export interface Entry { - _id?: ObjectId; - userId: string; - badges: Badge[]; - blocked: boolean; + _id?: ObjectId; + userId: string; + badges: Badge[]; + blocked: boolean; } diff --git a/tsconfig.json b/tsconfig.json index 4582650..4d544ae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,44 +1,29 @@ { "compilerOptions": { - "allowUnreachableCode": false, - "allowUnusedLabels": false, - "alwaysStrict": true, "exactOptionalPropertyTypes": true, - "noImplicitAny": true, "noImplicitOverride": true, "noImplicitReturns": true, - "noImplicitThis": true, "noPropertyAccessFromIndexSignature": true, "noUncheckedIndexedAccess": true, "noUnusedLocals": true, "noUnusedParameters": true, "strict": true, - "strictBindCallApply": true, - "strictFunctionTypes": true, - "strictNullChecks": true, - "strictPropertyInitialization": true, - "useUnknownInCatchVariables": true, - "outDir": "dist", - "target": "ESNext", "module": "NodeNext", "moduleResolution": "NodeNext", - "types": [ - "node" - ], - "esModuleInterop": true, - "removeComments": true, "resolveJsonModule": true, + "rootDir": "src", + "outDir": "dist", + "removeComments": true, "lib": [ "ESNext", ], + "target": "ESNext", }, "include": [ - "src/**/*.ts", - "config/*.json", - "eslint.config.js" + "src/**/*", ], "exclude": [ "node_modules", - "dist", + "dist" ] } \ No newline at end of file