From bbd6a710bb49110e2bb5dde65eb1de630be7df7c Mon Sep 17 00:00:00 2001 From: Naomi Date: Sun, 12 May 2024 01:15:42 -0700 Subject: [PATCH] feat: migrate from github --- .eslintrc.json | 12 + .gitattributes | 8 + .gitignore | 5 + .prettierrc.json | 1 + CODE_OF_CONDUCT.md | 3 + CONTRIBUTING.md | 3 + LICENSE.md | 5 + PRIVACY.md | 3 + README.md | 29 + SECURITY.md | 3 + TERMS.md | 3 + logs/.gitkeep | 1 + package.json | 58 + pnpm-lock.yaml | 2948 ++++++++++++++++++++ prisma/schema.prisma | 22 + sample.env | 18 + src/config/Tickets.ts | 1 + src/config/Trello.ts | 46 + src/config/Webhooks.ts | 25 + src/events/onInteractionCreate.ts | 45 + src/events/onMemberAdd.ts | 20 + src/events/onMessageCreate.ts | 130 + src/events/onReactionAdd.ts | 109 + src/events/onReady.ts | 42 + src/index.ts | 76 + src/interface/AirtableResponse.ts | 71 + src/interface/ButtonHandler.ts | 8 + src/interface/ExtendedClient.ts | 13 + src/modules/buttons/ticketClaim.ts | 78 + src/modules/buttons/ticketClose.ts | 60 + src/modules/buttons/ticketOpen.ts | 41 + src/modules/checkAirtableRecords.ts | 182 ++ src/modules/createLogsFile.ts | 32 + src/modules/generateLogs.ts | 46 + src/modules/logTicketMessage.ts | 39 + src/modules/messages/fetchMessages.ts | 40 + src/modules/messages/startTicketPost.ts | 48 + src/modules/modals/handleTicketModal.ts | 82 + src/modules/reminders/sendUnclaimedArt.ts | 40 + src/modules/reminders/sendUnfinishedArt.ts | 43 + src/server/serve.ts | 125 + src/utils/getMuteDurationUnit.ts | 24 + src/utils/getNewsFeed.ts | 119 + src/utils/isValidWebhook.ts | 10 + src/utils/logHandler.ts | 24 + test/index.spec.ts | 7 + tsconfig.json | 8 + 47 files changed, 4756 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .prettierrc.json create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.md create mode 100644 PRIVACY.md create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 TERMS.md create mode 100644 logs/.gitkeep create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 prisma/schema.prisma create mode 100644 sample.env create mode 100644 src/config/Tickets.ts create mode 100644 src/config/Trello.ts create mode 100644 src/config/Webhooks.ts create mode 100644 src/events/onInteractionCreate.ts create mode 100644 src/events/onMemberAdd.ts create mode 100644 src/events/onMessageCreate.ts create mode 100644 src/events/onReactionAdd.ts create mode 100644 src/events/onReady.ts create mode 100644 src/index.ts create mode 100644 src/interface/AirtableResponse.ts create mode 100644 src/interface/ButtonHandler.ts create mode 100644 src/interface/ExtendedClient.ts create mode 100644 src/modules/buttons/ticketClaim.ts create mode 100644 src/modules/buttons/ticketClose.ts create mode 100644 src/modules/buttons/ticketOpen.ts create mode 100644 src/modules/checkAirtableRecords.ts create mode 100644 src/modules/createLogsFile.ts create mode 100644 src/modules/generateLogs.ts create mode 100644 src/modules/logTicketMessage.ts create mode 100644 src/modules/messages/fetchMessages.ts create mode 100644 src/modules/messages/startTicketPost.ts create mode 100644 src/modules/modals/handleTicketModal.ts create mode 100644 src/modules/reminders/sendUnclaimedArt.ts create mode 100644 src/modules/reminders/sendUnfinishedArt.ts create mode 100644 src/server/serve.ts create mode 100644 src/utils/getMuteDurationUnit.ts create mode 100644 src/utils/getNewsFeed.ts create mode 100644 src/utils/isValidWebhook.ts create mode 100644 src/utils/logHandler.ts create mode 100644 test/index.spec.ts create mode 100644 tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..508836d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "extends": "@nhcarrigan", + "overrides": [ + { + "files": ["src/modules/buttons/*.ts"], + "rules": { + "jsdoc/require-param": "off", + "jsdoc/require-returns": "off" + } + } + ] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2bffe04 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Auto detect text files and perform LF normalization +* text eol=LF +*.ts text +*.spec.ts text + +# Ignore binary files >:( +*.png binary +*.jpg binary \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d463ad8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/node_modules/ +/prod/ +.env +/logs/*.txt +latestAirtableRecord.txt diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..74cfb8c --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +"@nhcarrigan/prettier-config" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a3bbfe2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +Our Code of Conduct can be found here: https://docs.nhcarrigan.com/#/coc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f67101e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing + +Our contributing guidelines can be found here: https://docs.nhcarrigan.com/#/contributing diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..5424732 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,5 @@ +# License + +This software is licensed under our [global software license](https://docs.nhcarrigan.com/#/license). + +Copyright held by Naomi Carrigan. diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..96791d1 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,3 @@ +# Privacy Policy + +Our privacy policy can be found here: https://docs.nhcarrigan.com/#/privacy diff --git a/README.md b/README.md new file mode 100644 index 0000000..bcd0d5b --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Art for Palestine Bot + +This is a bot for the [Art for Palestine](https://art4palestine.org) charity event. It serves as a bridge between AirTable, Trello, and Discord. + +## Live Version + +This page is currently deployed. [View the live website.](https://discord.gg/kHNyb6Vyf8) + +## Feedback and Bugs + +If you have feedback or a bug report, please feel free to open a GitHub issue! + +## Contributing + +If you would like to contribute to the project, you may create a Pull Request containing your proposed changes and we will review it as soon as we are able! Please review our [contributing guidelines](CONTRIBUTING.md) first. + +## Code of Conduct + +Before interacting with our community, please read our [Code of Conduct](CODE_OF_CONDUCT.md). + +## License + +This software is licensed under our [global software license](https://docs.nhcarrigan.com/#/license). + +Copyright held by Naomi Carrigan. + +## Contact + +We may be contacted through our [Chat Server](http://chat.nhcarrigan.com) or via email at `contact@nhcarrigan.com`. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..bb3fc2d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +Our security policy can be found here: https://docs.nhcarrigan.com/#/security diff --git a/TERMS.md b/TERMS.md new file mode 100644 index 0000000..f556156 --- /dev/null +++ b/TERMS.md @@ -0,0 +1,3 @@ +# Terms of Service + +Our Terms of Service can be found here: https://docs.nhcarrigan.com/#/terms diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 0000000..68c33b7 --- /dev/null +++ b/logs/.gitkeep @@ -0,0 +1 @@ +We need this for the ticket system. diff --git a/package.json b/package.json new file mode 100644 index 0000000..8edf38b --- /dev/null +++ b/package.json @@ -0,0 +1,58 @@ +{ + "name": "nodejs-typescript-template", + "version": "2.0.0", + "description": "A template for my nodejs projects", + "main": "prod/index.js", + "scripts": { + "build": "tsc", + "lint": "eslint src test --max-warnings 0 && prettier src test --check", + "start": "node -r dotenv/config prod/index.js", + "test": "ts-mocha -u tdd test/**/*.spec.ts --recursive --exit --timeout 10000" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/naomi-lgbt/nodejs-typescript-template.git" + }, + "engines": { + "node": "20", + "pnpm": "8" + }, + "keywords": [ + "template", + "typescript", + "eslint", + "nodejs", + "prettier" + ], + "author": "Naomi Carrigan", + "license": "SEE LICENSE IN https://docs.nhcarrigan.com/#/license", + "bugs": { + "url": "https://github.com/naomi-lgbt/nodejs-typescript-template/issues" + }, + "homepage": "https://github.com/naomi-lgbt/nodejs-typescript-template#readme", + "dependencies": { + "@prisma/client": "5.13.0", + "discord.js": "14.15.2", + "dotenv": "16.4.5", + "express": "4.19.2", + "node-schedule": "2.1.1", + "winston": "3.13.0" + }, + "devDependencies": { + "@nhcarrigan/eslint-config": "1.1.3", + "@nhcarrigan/prettier-config": "1.0.1", + "@nhcarrigan/typescript-config": "1.0.1", + "@types/chai": "4.3.16", + "@types/express": "4.17.21", + "@types/mocha": "10.0.6", + "@types/node": "18.19.33", + "@types/node-schedule": "2.1.7", + "chai": "4.4.1", + "eslint": "8.57.0", + "mocha": "10.4.0", + "prettier": "2.8.8", + "prisma": "5.13.0", + "ts-mocha": "10.0.0", + "typescript": "5.4.5" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..406fded --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2948 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@prisma/client': + specifier: 5.13.0 + version: 5.13.0(prisma@5.13.0) + discord.js: + specifier: 14.15.2 + version: 14.15.2 + dotenv: + specifier: 16.4.5 + version: 16.4.5 + express: + specifier: 4.19.2 + version: 4.19.2 + node-schedule: + specifier: 2.1.1 + version: 2.1.1 + winston: + specifier: 3.13.0 + version: 3.13.0 + +devDependencies: + '@nhcarrigan/eslint-config': + specifier: 1.1.3 + version: 1.1.3(eslint@8.57.0)(prettier@2.8.8)(typescript@5.4.5) + '@nhcarrigan/prettier-config': + specifier: 1.0.1 + version: 1.0.1(prettier@2.8.8) + '@nhcarrigan/typescript-config': + specifier: 1.0.1 + version: 1.0.1(typescript@5.4.5) + '@types/chai': + specifier: 4.3.16 + version: 4.3.16 + '@types/express': + specifier: 4.17.21 + version: 4.17.21 + '@types/mocha': + specifier: 10.0.6 + version: 10.0.6 + '@types/node': + specifier: 18.19.33 + version: 18.19.33 + '@types/node-schedule': + specifier: 2.1.7 + version: 2.1.7 + chai: + specifier: 4.4.1 + version: 4.4.1 + eslint: + specifier: 8.57.0 + version: 8.57.0 + mocha: + specifier: 10.4.0 + version: 10.4.0 + prettier: + specifier: 2.8.8 + version: 2.8.8 + prisma: + specifier: 5.13.0 + version: 5.13.0 + ts-mocha: + specifier: 10.0.0 + version: 10.0.0(mocha@10.4.0) + typescript: + specifier: 5.4.5 + version: 5.4.5 + +packages: + + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: true + + /@colors/colors@1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + dev: false + + /@colors/colors@1.6.0: + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + dev: false + + /@dabh/diagnostics@2.0.3: + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + dev: false + + /@discordjs/builders@1.8.1: + resolution: {integrity: sha512-GkF+HM01FHy+NSoTaUPR8z44otfQgJ1AIsRxclYGUZDyUbdZEFyD/5QVv2Y1Flx6M+B0bQLzg2M9CJv5lGTqpA==} + engines: {node: '>=16.11.0'} + dependencies: + '@discordjs/formatters': 0.4.0 + '@discordjs/util': 1.1.0 + '@sapphire/shapeshift': 3.9.7 + discord-api-types: 0.37.83 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.4 + tslib: 2.6.2 + dev: false + + /@discordjs/collection@1.5.3: + resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} + engines: {node: '>=16.11.0'} + dev: false + + /@discordjs/collection@2.1.0: + resolution: {integrity: sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==} + engines: {node: '>=18'} + dev: false + + /@discordjs/formatters@0.4.0: + resolution: {integrity: sha512-fJ06TLC1NiruF35470q3Nr1bi95BdvKFAF+T5bNfZJ4bNdqZ3VZ+Ttg6SThqTxm6qumSG3choxLBHMC69WXNXQ==} + engines: {node: '>=16.11.0'} + dependencies: + discord-api-types: 0.37.83 + dev: false + + /@discordjs/rest@2.3.0: + resolution: {integrity: sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==} + engines: {node: '>=16.11.0'} + dependencies: + '@discordjs/collection': 2.1.0 + '@discordjs/util': 1.1.0 + '@sapphire/async-queue': 1.5.2 + '@sapphire/snowflake': 3.5.3 + '@vladfrangu/async_event_emitter': 2.2.4 + discord-api-types: 0.37.83 + magic-bytes.js: 1.10.0 + tslib: 2.6.2 + undici: 6.13.0 + dev: false + + /@discordjs/util@1.1.0: + resolution: {integrity: sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg==} + engines: {node: '>=16.11.0'} + dev: false + + /@discordjs/ws@1.1.0: + resolution: {integrity: sha512-O97DIeSvfNTn5wz5vaER6ciyUsr7nOqSEtsLoMhhIgeFkhnxLRqSr00/Fpq2/ppLgjDGLbQCDzIK7ilGoB/M7A==} + engines: {node: '>=16.11.0'} + dependencies: + '@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 + discord-api-types: 0.37.83 + tslib: 2.6.2 + ws: 8.17.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /@es-joy/jsdoccomment@0.37.1: + resolution: {integrity: sha512-5vxWJ1gEkEF0yRd0O+uK6dHJf7adrxwQSX8PuRiPfFSAbNLnY0ZJfXaZucoz14Jj2N11xn2DnlEPwWRpYpvRjg==} + engines: {node: ^14 || ^16 || ^17 || ^18 || ^19 || ^20} + dependencies: + comment-parser: 1.3.1 + esquery: 1.5.0 + jsdoc-type-pratt-parser: 4.0.0 + dev: true + + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/regexpp@4.6.2: + resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@2.1.4: + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4(supports-color@8.1.1) + espree: 9.6.1 + globals: 13.20.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js@8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@humanwhocodes/config-array@0.11.14: + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 2.0.2 + debug: 4.3.4(supports-color@8.1.1) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@2.0.2: + resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + dev: true + + /@nhcarrigan/eslint-config@1.1.3(eslint@8.57.0)(prettier@2.8.8)(typescript@5.4.5): + resolution: {integrity: sha512-w0aQdDY/xdLtRLUS6KVx9elyN+ZkdjJ/CPNSQt/jflX48lbZ4PiXLJc6MEcC5zqpieoBrTCB8KVg8MWQ1fjRdA==} + peerDependencies: + eslint: '>=8' + dependencies: + '@typescript-eslint/eslint-plugin': 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 5.59.5(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + eslint-config-prettier: 8.8.0(eslint@8.57.0) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.5)(eslint@8.57.0) + eslint-plugin-jsdoc: 41.1.2(eslint@8.57.0) + eslint-plugin-no-only-tests: 3.1.0 + eslint-plugin-prettier: 4.2.1(eslint-config-prettier@8.8.0)(eslint@8.57.0)(prettier@2.8.8) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - prettier + - supports-color + - typescript + dev: true + + /@nhcarrigan/prettier-config@1.0.1(prettier@2.8.8): + resolution: {integrity: sha512-xClbv78v+spYk0y42cKiT7VJz8bWrXw5hHVztghRI+dpMNwEk/YgNmsPDsFxLS394NcoeqAvsWYxMUEQWKceuA==} + peerDependencies: + prettier: '>=2' + dependencies: + prettier: 2.8.8 + dev: true + + /@nhcarrigan/typescript-config@1.0.1(typescript@5.4.5): + resolution: {integrity: sha512-RGx6CecTDbn8KkKuRamb+mt4HfTRdWhGhkOVpgXf8oOmyHIJjJetS407OsJ8pIKefjamYXmZ1WEPNLPR0CJpXg==} + peerDependencies: + typescript: ^5.0.0 + dependencies: + typescript: 5.4.5 + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@prisma/client@5.13.0(prisma@5.13.0): + resolution: {integrity: sha512-uYdfpPncbZ/syJyiYBwGZS8Gt1PTNoErNYMuqHDa2r30rNSFtgTA/LXsSk55R7pdRTMi5pHkeP9B14K6nHmwkg==} + engines: {node: '>=16.13'} + requiresBuild: true + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + dependencies: + prisma: 5.13.0 + dev: false + + /@prisma/debug@5.13.0: + resolution: {integrity: sha512-699iqlEvzyCj9ETrXhs8o8wQc/eVW+FigSsHpiskSFydhjVuwTJEfj/nIYqTaWFYuxiWQRfm3r01meuW97SZaQ==} + + /@prisma/engines-version@5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b: + resolution: {integrity: sha512-AyUuhahTINGn8auyqYdmxsN+qn0mw3eg+uhkp8zwknXYIqoT3bChG4RqNY/nfDkPvzWAPBa9mrDyBeOnWSgO6A==} + + /@prisma/engines@5.13.0: + resolution: {integrity: sha512-hIFLm4H1boj6CBZx55P4xKby9jgDTeDG0Jj3iXtwaaHmlD5JmiDkZhh8+DYWkTGchu+rRF36AVROLnk0oaqhHw==} + requiresBuild: true + dependencies: + '@prisma/debug': 5.13.0 + '@prisma/engines-version': 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b + '@prisma/fetch-engine': 5.13.0 + '@prisma/get-platform': 5.13.0 + + /@prisma/fetch-engine@5.13.0: + resolution: {integrity: sha512-Yh4W+t6YKyqgcSEB3odBXt7QyVSm0OQlBSldQF2SNXtmOgMX8D7PF/fvH6E6qBCpjB/yeJLy/FfwfFijoHI6sA==} + dependencies: + '@prisma/debug': 5.13.0 + '@prisma/engines-version': 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b + '@prisma/get-platform': 5.13.0 + + /@prisma/get-platform@5.13.0: + resolution: {integrity: sha512-B/WrQwYTzwr7qCLifQzYOmQhZcFmIFhR81xC45gweInSUn2hTEbfKUPd2keAog+y5WI5xLAFNJ3wkXplvSVkSw==} + dependencies: + '@prisma/debug': 5.13.0 + + /@sapphire/async-queue@1.5.2: + resolution: {integrity: sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /@sapphire/shapeshift@3.9.7: + resolution: {integrity: sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g==} + engines: {node: '>=v16'} + dependencies: + fast-deep-equal: 3.1.3 + lodash: 4.17.21 + dev: false + + /@sapphire/snowflake@3.5.3: + resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /@types/body-parser@1.19.5: + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + dependencies: + '@types/connect': 3.4.38 + '@types/node': 18.19.33 + dev: true + + /@types/chai@4.3.16: + resolution: {integrity: sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==} + dev: true + + /@types/connect@3.4.38: + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + dependencies: + '@types/node': 18.19.33 + dev: true + + /@types/express-serve-static-core@4.17.41: + resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} + dependencies: + '@types/node': 18.19.33 + '@types/qs': 6.9.10 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + dev: true + + /@types/express@4.17.21: + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.17.41 + '@types/qs': 6.9.10 + '@types/serve-static': 1.15.5 + dev: true + + /@types/http-errors@2.0.4: + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + dev: true + + /@types/json-schema@7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + dev: true + + /@types/json5@0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + requiresBuild: true + dev: true + + /@types/mime@1.3.5: + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + dev: true + + /@types/mime@3.0.4: + resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} + dev: true + + /@types/mocha@10.0.6: + resolution: {integrity: sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==} + dev: true + + /@types/node-schedule@2.1.7: + resolution: {integrity: sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA==} + dependencies: + '@types/node': 18.19.33 + dev: true + + /@types/node@18.19.33: + resolution: {integrity: sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==} + dependencies: + undici-types: 5.26.5 + + /@types/qs@6.9.10: + resolution: {integrity: sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==} + dev: true + + /@types/range-parser@1.2.7: + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + dev: true + + /@types/semver@7.5.0: + resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} + dev: true + + /@types/send@0.17.4: + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + dependencies: + '@types/mime': 1.3.5 + '@types/node': 18.19.33 + dev: true + + /@types/serve-static@1.15.5: + resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} + dependencies: + '@types/http-errors': 2.0.4 + '@types/mime': 3.0.4 + '@types/node': 18.19.33 + dev: true + + /@types/triple-beam@1.3.2: + resolution: {integrity: sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==} + dev: false + + /@types/ws@8.5.10: + resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + dependencies: + '@types/node': 18.19.33 + dev: false + + /@typescript-eslint/eslint-plugin@5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.6.2 + '@typescript-eslint/parser': 5.59.5(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 5.59.5 + '@typescript-eslint/type-utils': 5.59.5(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 5.59.5(eslint@8.57.0)(typescript@5.4.5) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.57.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + natural-compare-lite: 1.4.0 + semver: 7.5.2 + tsutils: 3.21.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@5.59.5(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.59.5 + '@typescript-eslint/types': 5.59.5 + '@typescript-eslint/typescript-estree': 5.59.5(typescript@5.4.5) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.57.0 + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@5.59.5: + resolution: {integrity: sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.59.5 + '@typescript-eslint/visitor-keys': 5.59.5 + dev: true + + /@typescript-eslint/type-utils@5.59.5(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.59.5(typescript@5.4.5) + '@typescript-eslint/utils': 5.59.5(eslint@8.57.0)(typescript@5.4.5) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.57.0 + tsutils: 3.21.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@5.59.5: + resolution: {integrity: sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/typescript-estree@5.59.5(typescript@5.4.5): + resolution: {integrity: sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.59.5 + '@typescript-eslint/visitor-keys': 5.59.5 + debug: 4.3.4(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.2 + tsutils: 3.21.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@5.59.5(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 5.59.5 + '@typescript-eslint/types': 5.59.5 + '@typescript-eslint/typescript-estree': 5.59.5(typescript@5.4.5) + eslint: 8.57.0 + eslint-scope: 5.1.1 + semver: 7.5.2 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@5.59.5: + resolution: {integrity: sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.59.5 + eslint-visitor-keys: 3.4.3 + dev: true + + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true + + /@vladfrangu/async_event_emitter@2.2.4: + resolution: {integrity: sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + + /acorn-jsx@5.3.2(acorn@8.9.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.9.0 + dev: true + + /acorn@8.9.0: + resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-colors@4.1.1: + resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + engines: {node: '>=6'} + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.2 + is-array-buffer: 3.0.2 + dev: true + + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: false + + /array-includes@3.1.6: + resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + get-intrinsic: 1.2.1 + is-string: 1.0.7 + dev: true + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /array.prototype.flat@1.3.1: + resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + es-shim-unscopables: 1.0.0 + dev: true + + /array.prototype.flatmap@1.3.1: + resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + es-shim-unscopables: 1.0.0 + dev: true + + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + + /async@3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: false + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + dev: true + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + + /chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.6 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: false + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: false + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + + /color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + dev: false + + /colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + dev: false + + /comment-parser@1.3.1: + resolution: {integrity: sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==} + engines: {node: '>= 12.0.0'} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + dev: false + + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: false + + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + dev: false + + /cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + dependencies: + luxon: 3.4.4 + dev: false + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + + /debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: true + + /debug@4.3.4(supports-color@8.1.1): + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 8.1.1 + dev: true + + /decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + dev: true + + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /define-properties@1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + + /diff@3.5.0: + resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==} + engines: {node: '>=0.3.1'} + dev: true + + /diff@5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /discord-api-types@0.37.83: + resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==} + dev: false + + /discord.js@14.15.2: + resolution: {integrity: sha512-wGD37YCaTUNprtpqMIRuNiswwsvSWXrHykBSm2SAosoTYut0VUDj9yo9t4iLtMKvuhI49zYkvKc2TNdzdvpJhg==} + engines: {node: '>=16.11.0'} + dependencies: + '@discordjs/builders': 1.8.1 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.4.0 + '@discordjs/rest': 2.3.0 + '@discordjs/util': 1.1.0 + '@discordjs/ws': 1.1.0 + '@sapphire/snowflake': 3.5.3 + discord-api-types: 0.37.83 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + tslib: 2.6.2 + undici: 6.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: false + + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: false + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + dev: false + + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: false + + /es-abstract@1.21.2: + resolution: {integrity: sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.2.1 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.10 + is-weakref: 1.0.2 + object-inspect: 1.12.3 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.5.0 + safe-regex-test: 1.0.0 + string.prototype.trim: 1.2.7 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.6 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.9 + dev: true + + /es-set-tostringtag@2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + has: 1.0.3 + has-tostringtag: 1.0.0 + dev: true + + /es-shim-unscopables@1.0.0: + resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + dependencies: + has: 1.0.3 + dev: true + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /eslint-config-prettier@8.8.0(eslint@8.57.0): + resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.57.0 + dev: true + + /eslint-import-resolver-node@0.3.7: + resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} + dependencies: + debug: 3.2.7 + is-core-module: 2.12.1 + resolve: 1.22.2 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.59.5)(eslint-import-resolver-node@0.3.7)(eslint@8.57.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.5(eslint@8.57.0)(typescript@5.4.5) + debug: 3.2.7 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.7 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.5)(eslint@8.57.0): + resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.5(eslint@8.57.0)(typescript@5.4.5) + array-includes: 3.1.6 + array.prototype.flat: 1.3.1 + array.prototype.flatmap: 1.3.1 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.7 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.5)(eslint-import-resolver-node@0.3.7)(eslint@8.57.0) + has: 1.0.3 + is-core-module: 2.12.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.6 + resolve: 1.22.2 + semver: 6.3.0 + tsconfig-paths: 3.14.2 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-plugin-jsdoc@41.1.2(eslint@8.57.0): + resolution: {integrity: sha512-MePJXdGiPW7AG06CU5GbKzYtKpoHwTq1lKijjq+RwL/cQkZtBZ59Zbv5Ep0RVxSMnq6242249/n+w4XrTZ1Afg==} + engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@es-joy/jsdoccomment': 0.37.1 + are-docs-informative: 0.0.2 + comment-parser: 1.3.1 + debug: 4.3.4(supports-color@8.1.1) + escape-string-regexp: 4.0.0 + eslint: 8.57.0 + esquery: 1.5.0 + semver: 7.5.2 + spdx-expression-parse: 3.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-no-only-tests@3.1.0: + resolution: {integrity: sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==} + engines: {node: '>=5.0.0'} + dev: true + + /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0)(eslint@8.57.0)(prettier@2.8.8): + resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.57.0 + eslint-config-prettier: 8.8.0(eslint@8.57.0) + prettier: 2.8.8 + prettier-linter-helpers: 1.0.0 + dev: true + + /eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.6.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4(supports-color@8.1.1) + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.20.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.9.0 + acorn-jsx: 5.3.2(acorn@8.9.0) + eslint-visitor-keys: 3.4.3 + dev: true + + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: false + + /express@4.19.2: + resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.2 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.6.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + /fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + dev: true + + /fast-glob@3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + dev: false + + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat-cache@3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: true + + /flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: true + + /flatted@3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true + + /fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + dev: false + + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /function.prototype.name@1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + functions-have-names: 1.2.3 + dev: true + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + + /get-intrinsic@1.2.1: + resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-proto: 1.0.1 + has-symbols: 1.0.3 + + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.0.1 + once: 1.4.0 + dev: true + + /globals@13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.0 + dev: true + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.1 + dev: true + + /grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true + + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors@1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.2.1 + dev: true + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /internal-slot@1.0.5: + resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + + /is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-typed-array: 1.1.10 + dev: true + + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-core-module@2.12.1: + resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} + dependencies: + has: 1.0.3 + dev: true + + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: true + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: false + + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-typed-array@1.1.10: + resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsdoc-type-pratt-parser@4.0.0: + resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} + engines: {node: '>=12.0.0'} + dev: true + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + requiresBuild: true + dependencies: + minimist: 1.2.8 + dev: true + + /kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + dev: false + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + dev: false + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /logform@2.5.1: + resolution: {integrity: sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==} + dependencies: + '@colors/colors': 1.5.0 + '@types/triple-beam': 1.3.2 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.4.3 + triple-beam: 1.3.0 + dev: false + + /long-timeout@0.1.1: + resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} + dev: false + + /loupe@2.3.6: + resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + deprecated: Please upgrade to 2.3.7 which fixes GHSA-4q6p-r6v2-jvc5 + dependencies: + get-func-name: 2.0.2 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /luxon@3.4.4: + resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} + engines: {node: '>=12'} + dev: false + + /magic-bytes.js@1.10.0: + resolution: {integrity: sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==} + dev: false + + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@5.0.1: + resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: true + + /mocha@10.4.0: + resolution: {integrity: sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==} + engines: {node: '>= 14.0.0'} + hasBin: true + dependencies: + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.5.3 + debug: 4.3.4(supports-color@8.1.1) + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 8.1.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.0.1 + ms: 2.1.3 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.2.1 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + dev: true + + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: false + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + /natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + + /node-schedule@2.1.1: + resolution: {integrity: sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==} + engines: {node: '>=6'} + dependencies: + cron-parser: 4.9.0 + long-timeout: 0.1.1 + sorted-array-functions: 1.3.0 + dev: false + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /object-inspect@1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /object.values@1.1.6: + resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + dev: true + + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + dependencies: + fn.name: 1.1.0 + dev: false + + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: false + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: 1.3.0 + dev: true + + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + /prisma@5.13.0: + resolution: {integrity: sha512-kGtcJaElNRAdAGsCNykFSZ7dBKpL14Cbs+VaQ8cECxQlRPDjBlMHNFYeYt0SKovAVy2Y65JXQwB3A5+zIQwnTg==} + engines: {node: '>=16.13'} + hasBin: true + requiresBuild: true + dependencies: + '@prisma/engines': 5.13.0 + + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + + /punycode@2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} + dev: true + + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: false + + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /regexp.prototype.flags@1.5.0: + resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + functions-have-names: 1.2.3 + dev: true + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve@1.22.2: + resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + hasBin: true + dependencies: + is-core-module: 2.12.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + /safe-regex-test@1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-regex: 1.1.4 + dev: true + + /safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + dev: false + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + + /semver@6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + dev: true + + /semver@7.5.2: + resolution: {integrity: sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /serialize-javascript@6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + dependencies: + randombytes: 2.1.0 + dev: true + + /serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + dev: false + + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + object-inspect: 1.12.3 + + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /sorted-array-functions@1.3.0: + resolution: {integrity: sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==} + dev: false + + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.13 + dev: true + + /spdx-license-ids@3.0.13: + resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} + dev: true + + /stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + dev: false + + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string.prototype.trim@1.2.7: + resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + dev: true + + /string.prototype.trimend@1.0.6: + resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + dev: true + + /string.prototype.trimstart@1.0.6: + resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + requiresBuild: true + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + dev: false + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /triple-beam@1.3.0: + resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==} + dev: false + + /ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + dev: false + + /ts-mocha@10.0.0(mocha@10.4.0): + resolution: {integrity: sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==} + engines: {node: '>= 6.X.X'} + hasBin: true + peerDependencies: + mocha: ^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X + dependencies: + mocha: 10.4.0 + ts-node: 7.0.1 + optionalDependencies: + tsconfig-paths: 3.14.2 + dev: true + + /ts-node@7.0.1: + resolution: {integrity: sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==} + engines: {node: '>=4.2.0'} + hasBin: true + dependencies: + arrify: 1.0.1 + buffer-from: 1.1.2 + diff: 3.5.0 + make-error: 1.3.6 + minimist: 1.2.8 + mkdirp: 0.5.6 + source-map-support: 0.5.21 + yn: 2.0.0 + dev: true + + /tsconfig-paths@3.14.2: + resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + dev: true + + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true + + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: false + + /tsutils@3.21.0(typescript@5.4.5): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 5.4.5 + dev: true + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + + /typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + is-typed-array: 1.1.10 + dev: true + + /typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + /undici@6.13.0: + resolution: {integrity: sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw==} + engines: {node: '>=18.0'} + dev: false + + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.0 + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + dev: false + + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-typed-array@1.1.9: + resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + is-typed-array: 1.1.10 + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /winston-transport@4.7.0: + resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==} + engines: {node: '>= 12.0.0'} + dependencies: + logform: 2.5.1 + readable-stream: 3.6.2 + triple-beam: 1.3.0 + dev: false + + /winston@3.13.0: + resolution: {integrity: sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.4 + is-stream: 2.0.1 + logform: 2.5.1 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.4.3 + stack-trace: 0.0.10 + triple-beam: 1.3.0 + winston-transport: 4.7.0 + dev: false + + /workerpool@6.2.1: + resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /ws@8.17.0: + resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true + + /yargs-parser@20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + dev: true + + /yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + dev: true + + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.4 + dev: true + + /yn@2.0.0: + resolution: {integrity: sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==} + engines: {node: '>=4'} + dev: true + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..2a30be3 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,22 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mongodb" + url = env("MONGO_URI") +} + +model rewards { + id String @id @default(auto()) @map("_id") @db.ObjectId + trelloId String + messageId String + createdAt Int @default(0) + claimedBy String @default("") // user who claimed + completed Boolean @default(false) + + @@unique([trelloId], map: "trello") + @@unique([messageId], map: "message") + @@index([claimedBy, completed]) + @@index([claimedBy]) +} diff --git a/sample.env b/sample.env new file mode 100644 index 0000000..1caab92 --- /dev/null +++ b/sample.env @@ -0,0 +1,18 @@ +# Bot +TOKEN="" +MONGO_URI="" + +# Hooks +DEBUG="" +COMM="" +DIST="" +NEWS="" + +#Env +NODE_ENV="development" + +# Trello +TRELLO_KEY="" +TRELLO_TOKEN="" +TRELLO_SECRET="" +TRELLO_HOOK_CALLBACK="" diff --git a/src/config/Tickets.ts b/src/config/Tickets.ts new file mode 100644 index 0000000..01439c2 --- /dev/null +++ b/src/config/Tickets.ts @@ -0,0 +1 @@ +export const TicketSupportRole = "1173582640843063366"; diff --git a/src/config/Trello.ts b/src/config/Trello.ts new file mode 100644 index 0000000..6b6b4b1 --- /dev/null +++ b/src/config/Trello.ts @@ -0,0 +1,46 @@ +import { Webhooks } from "./Webhooks"; + +export const Trello = { + boardId: "", + newCardListId: "6552f24264ac58e98f8b78cd", + readyToSendListId: "6552f248af3c5fb5a81e4788", + platformLabels: { + discord: "6552f2b18af54bcca998abfe", + twitter: "6552f2cb9f21432b29106558", + email: "6552f2cee1258dfe159d48c8", + }, + actionLabels: { + donate: "6552f2d257c42c85759d0199", + call: "6552f2d6d0e69a4c17f5f8ab", + protest: "6552f2dbfa9f5ac4267b136a", + }, + checklistId: "6552f66d91daf000137ed4d2", +}; + +export const PlatformsToLabel: Record = { + Email: Trello.platformLabels.email, + Discord: Trello.platformLabels.discord, + Twitter: Trello.platformLabels.twitter, +}; + +export const ActionsToLabel: Record = { + "Sent a donation": Trello.actionLabels.donate, + "Called my representatives": Trello.actionLabels.call, + "Attended a protest": Trello.actionLabels.protest, +}; + +/** + * Grabs the message from Discord, formats it into a Trello comment. + * + * @param {string} userName The name of the user that triggered the comment. + * @returns {string} The formatted text to send. + */ +export const TrelloComments: { + [key in Webhooks]: (userName: string) => string; +} = { + [Webhooks.NewCommissions]: (userName) => `Artwork claimed by ${userName}.`, + [Webhooks.CompleteCommissions]: (userName) => + `Distribution claimed by ${userName}.`, + [Webhooks.NewTest]: (userName) => `Artwork claimed by ${userName}.`, + [Webhooks.CompleteTest]: (userName) => `Distribution claimed by ${userName}.`, +}; diff --git a/src/config/Webhooks.ts b/src/config/Webhooks.ts new file mode 100644 index 0000000..816cd79 --- /dev/null +++ b/src/config/Webhooks.ts @@ -0,0 +1,25 @@ +import { Message } from "discord.js"; + +export enum Webhooks { + NewCommissions = "1172850885571911760", + CompleteCommissions = "1173062416041525259", + NewTest = "1173009530511171634", + CompleteTest = "946487942526935060", +} + +/** + * Grabs the message from Discord, formats it based on the webhook ID. + * + * @param {Message} message The message payload from Discord. + * @returns {string} The formatted text to send. + */ +export const DMTexts: { [key in Webhooks]: (message: Message) => string } = { + [Webhooks.NewCommissions]: (message) => + `[Here is the donation commission you claimed~!](${message.url})\n> ${message.content}`, + [Webhooks.CompleteCommissions]: (message) => + `[Thank you for agreeing to deliver the following commission~!](${message.url})\n> ${message.content}`, + [Webhooks.NewTest]: (message) => + `[Here is the donation commission you claimed~!](${message.url})\n> ${message.content}`, + [Webhooks.CompleteTest]: (message) => + `[Thank you for agreeing to deliver the following commission~!](${message.url})\n> ${message.content}`, +}; diff --git a/src/events/onInteractionCreate.ts b/src/events/onInteractionCreate.ts new file mode 100644 index 0000000..1277243 --- /dev/null +++ b/src/events/onInteractionCreate.ts @@ -0,0 +1,45 @@ +import { Interaction } from "discord.js"; + +import { ExtendedClient } from "../interface/ExtendedClient"; +import { ticketClaimHandler } from "../modules/buttons/ticketClaim"; +import { ticketCloseHandler } from "../modules/buttons/ticketClose"; +import { ticketOpenHandler } from "../modules/buttons/ticketOpen"; +import { handleTicketModal } from "../modules/modals/handleTicketModal"; + +/** + * Handles the logic for the interaction create event. + * + * @param {ExtendedClient} bot The bot's Discord instance. + * @param {Interaction} interaction The interaction payload from Discord. + */ +export const onInteractionCreate = async ( + bot: ExtendedClient, + interaction: Interaction +) => { + try { + if (interaction.isModalSubmit()) { + if (interaction.customId === "ticket-modal") { + await handleTicketModal(bot, interaction); + } + } + + if (interaction.isButton()) { + if (!interaction.inCachedGuild()) { + return; + } + if (interaction.customId === "ticket") { + await ticketOpenHandler(bot, interaction); + } + if (interaction.customId === "claim") { + await ticketClaimHandler(bot, interaction); + } + if (interaction.customId === "close") { + await ticketCloseHandler(bot, interaction); + } + } + } catch (err) { + await bot.debug.send( + `Error in interaction create event: ${(err as Error).message}` + ); + } +}; diff --git a/src/events/onMemberAdd.ts b/src/events/onMemberAdd.ts new file mode 100644 index 0000000..5a3bcf2 --- /dev/null +++ b/src/events/onMemberAdd.ts @@ -0,0 +1,20 @@ +import { GuildMember } from "discord.js"; + +import { ExtendedClient } from "../interface/ExtendedClient"; + +/** + * Handles the member add event from discord. Gives the new member + * the `Members` role. + * + * @param {ExtendedClient} bot The bot's Discord instance. + * @param {GuildMember} member The member payload from Discord. + */ +export const onMemberAdd = async (bot: ExtendedClient, member: GuildMember) => { + try { + await member.roles.add("1173580547952484352"); + } catch (err) { + await bot.debug.send( + `Error on member add event: ${(err as Error).message}` + ); + } +}; diff --git a/src/events/onMessageCreate.ts b/src/events/onMessageCreate.ts new file mode 100644 index 0000000..7fc5165 --- /dev/null +++ b/src/events/onMessageCreate.ts @@ -0,0 +1,130 @@ +import { Message, PermissionFlagsBits } from "discord.js"; + +import { ExtendedClient } from "../interface/ExtendedClient"; +import { logTicketMessage } from "../modules/logTicketMessage"; +import { startTicketPost } from "../modules/messages/startTicketPost"; +import { getMuteDurationUnit } from "../utils/getMuteDurationUnit"; +import { isValidWebhook } from "../utils/isValidWebhook"; +import { logHandler } from "../utils/logHandler"; + +/** + * Handles the message create event. Adds reactions to messages from approved + * webhooks. + * + * @param {ExtendedClient} bot The bot's Discord instance. + * @param {Message} message The message payload from Discord. + */ +export const onMessageCreate = async ( + bot: ExtendedClient, + message: Message +) => { + try { + if (!message.author.bot) { + if ( + message.member?.permissions.has(PermissionFlagsBits.ManageGuild) && + message.content === "~tickets" + ) { + await startTicketPost(bot, message); + return; + } + if ( + !message.channel.isDMBased() && + message.channel.name.startsWith("ticket-") + ) { + const id = message.channel.id; + const cached = bot.ticketLogs[id]; + if (!cached) { + return; + } + await logTicketMessage(bot, message, cached); + } + if ( + message.member?.permissions.has(PermissionFlagsBits.ModerateMembers) + ) { + if (message.content.startsWith("!mute")) { + const [, id, durationString, ...reason] = + message.content.split(/\s+/); + const target = await message.guild?.members.fetch(id); + if (!target) { + await message.reply("Target not found."); + return; + } + const durationNumber = parseInt( + durationString.replace(/\w$/, ""), + 10 + ); + const durationUnit = durationString.replace(/^\d+/, ""); + const conversion = getMuteDurationUnit(durationUnit); + if (!conversion) { + await message.reply(`Invalid unit ${durationUnit}.`); + return; + } + const time = durationNumber * conversion; + if (time > 1000 * 60 * 60 * 24 * 28) { + await message.reply("Cannot time out user for more than 28 days."); + return; + } + await target.timeout(time, reason.join(" ") || "No reason provided."); + await message.reply({ content: "Done" }); + const logChannel = message.guild?.channels.cache.find( + (c) => c.name === "bot-logs" + ); + if (logChannel && "send" in logChannel) { + await logChannel?.send({ + content: `<@!${message.author.id}> muted <@!${target.id}> (${ + target.id + }) - ${durationString} for: ${ + reason.join(" ") ?? "No reason provided." + }`, + allowedMentions: { + users: [], + }, + }); + } + } + if (message.content.startsWith("!unmute")) { + const [, id, ...reason] = message.content.split(/\s+/); + const target = await message.guild?.members.fetch(id); + if (!target) { + await message.reply("Target not found."); + return; + } + await target.timeout(null, reason.join(" ") || "No reason provided."); + const logChannel = message.guild?.channels.cache.find( + (c) => c.name === "bot-logs" + ); + await message.reply({ content: "Done" }); + if (logChannel && "send" in logChannel) { + await logChannel?.send({ + content: `<@!${message.author.id}> unmuted <@!${target.id}> (${ + target.id + }) for: ${reason.join(" ") ?? "No reason provided."}`, + allowedMentions: { + users: [], + }, + }); + } + } + } + return; + } + if (message.author.bot && !isValidWebhook(message.author.id)) { + logHandler.log( + "info", + `Got bot message from ${message.author.id} but is not a valid webhook.` + ); + return; + } + logHandler.log("info", "Processing webhook message."); + await message.react("✅").catch(async (err) => { + await bot.debug.send({ + content: `[Failed to add reaction:](<${message.url}>) ${err.message}`, + }); + }); + logHandler.log("info", "Added reaction~!"); + } catch (err) { + await bot.debug.send( + `Error in message create event: ${(err as Error).message}` + ); + } +}; diff --git a/src/events/onReactionAdd.ts b/src/events/onReactionAdd.ts new file mode 100644 index 0000000..ccea41d --- /dev/null +++ b/src/events/onReactionAdd.ts @@ -0,0 +1,109 @@ +import { + MessageReaction, + PartialMessageReaction, + PartialUser, + User, +} from "discord.js"; + +import { TrelloComments } from "../config/Trello"; +import { DMTexts } from "../config/Webhooks"; +import { ExtendedClient } from "../interface/ExtendedClient"; +import { isValidWebhook } from "../utils/isValidWebhook"; + +/** + * Handles the message reaction add. If added to a valid webhook, processes logic to send + * DM to the first user who reacted, remove further reactions. + * + * @param {ExtendedClient} bot The bot's Discord instance. + * @param {MessageReaction | PartialMessageReaction} r The reaction payload from Discord. + * @param {User | PartialUser} user The user who reacted. + */ +export const onReactionAdd = async ( + bot: ExtendedClient, + r: MessageReaction | PartialMessageReaction, + user: User | PartialUser +) => { + try { + if (user.bot) { + return; + } + const reaction = await r.fetch().catch(async (err) => { + await bot.debug.send({ + content: `[Failed to fetch reactions:](<${r.message.url}>) ${err.message}`, + }); + return null; + }); + if (!reaction) { + return; + } + const message = await r.message.fetch().catch(async (err) => { + await bot.debug.send({ + content: `[Failed to fetch message:](<${r.message.url}>) ${err.message}`, + }); + return null; + }); + if (!message || !isValidWebhook(message.author.id)) { + return; + } + if (reaction.count > 2) { + await r.users.remove(user.id); + return; + } + const claimedByUser = await bot.db.rewards.findMany({ + where: { + claimedBy: user.id, + completed: false, + }, + }); + if (claimedByUser.length >= 2) { + await r.users.remove(user.id); + await user.send( + "You currently have 2 open art rewards. Please do not claim another until you have completed one of your outstanding art works." + ); + return; + } + const files = [...message.attachments.values()]; + await user + .send({ + content: DMTexts[message.author.id](message), + files, + }) + .catch(async (err) => { + await bot.debug.send({ + content: `[Failed to DM ${user.username}:](<${r.message.url}>) ${err.message}`, + }); + }); + const trelloId = + message.content.split("You can ignore this it's just for the bot. ")[1] || + message.content.split( + "You can ignore this ID it's just for the bot: " + )[1]; + if (trelloId) { + await fetch( + `https://api.trello.com/1/cards/${trelloId}/actions/comments?text=${TrelloComments[ + message.author.id + ](user.displayName || user.username || user.id)}&key=${ + process.env.TRELLO_KEY + }&token=${process.env.TRELLO_TOKEN}`, + { + method: "POST", + headers: { + accept: "application/json", + }, + } + ); + await bot.db.rewards.update({ + where: { + trelloId, + }, + data: { + claimedBy: user.id, + }, + }); + } + } catch (err) { + await bot.debug.send( + `Error in processing new reaction: ${(err as Error).message}` + ); + } +}; diff --git a/src/events/onReady.ts b/src/events/onReady.ts new file mode 100644 index 0000000..d1a1a6e --- /dev/null +++ b/src/events/onReady.ts @@ -0,0 +1,42 @@ +import { scheduleJob } from "node-schedule"; + +import { ExtendedClient } from "../interface/ExtendedClient"; +import { checkAirtableRecords } from "../modules/checkAirtableRecords"; +import { fetchMessages } from "../modules/messages/fetchMessages"; +import { sendUnclaimedArt } from "../modules/reminders/sendUnclaimedArt"; +import { sendUnfinishedArt } from "../modules/reminders/sendUnfinishedArt"; +import { serve } from "../server/serve"; +import { getNewsFeed } from "../utils/getNewsFeed"; + +/** + * Handles the logic when the bot is ready to receive Discord events. + * + * @param {ExtendedClient} bot The bot's Discord instance. + */ +export const onReady = async (bot: ExtendedClient) => { + try { + await bot.debug.send("Bot is online and ready to help!"); + // donor channel + await fetchMessages(bot, "1172568787330019340"); + // delivery channel + await fetchMessages(bot, "1173061747737903315"); + await bot.debug.send("Fetching initial news feed..."); + await getNewsFeed(bot); + await bot.debug.send("Fetching news posts every 10 minutes."); + setInterval(async () => await getNewsFeed(bot), 1000 * 60 * 10); + await bot.debug.send("Fetching new airtable submissions every 60 minutes."); + setInterval(async () => await checkAirtableRecords(bot), 1000 * 60 * 60); + + scheduleJob("0 9 * * 1,3,5", async () => { + await sendUnclaimedArt(bot); + }); + + scheduleJob("0 9 * * 6", async () => { + await sendUnfinishedArt(bot); + }); + + await serve(bot); + } catch (err) { + await bot.debug.send(`Error on ready event: ${(err as Error).message}`); + } +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2adf24a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,76 @@ +import { execSync } from "child_process"; + +import { PrismaClient } from "@prisma/client"; +import { Client, Events, GatewayIntentBits, WebhookClient } from "discord.js"; + +import { onInteractionCreate } from "./events/onInteractionCreate"; +import { onMemberAdd } from "./events/onMemberAdd"; +import { onMessageCreate } from "./events/onMessageCreate"; +import { onReactionAdd } from "./events/onReactionAdd"; +import { onReady } from "./events/onReady"; +import { ExtendedClient } from "./interface/ExtendedClient"; +import { logHandler } from "./utils/logHandler"; + +(async () => { + if ( + !process.env.TOKEN || + !process.env.DEBUG || + !process.env.COMM || + !process.env.DIST || + !process.env.NEWS || + !process.env.TICKET + ) { + logHandler.log("error", "Missing environment variables."); + return; + } + const bot = new Client({ + intents: [ + GatewayIntentBits.GuildMessages, + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMembers, + ], + }) as ExtendedClient; + bot.db = new PrismaClient(); + bot.debug = new WebhookClient({ url: process.env.DEBUG }); + bot.comm = new WebhookClient({ url: process.env.COMM }); + bot.dist = new WebhookClient({ url: process.env.DIST }); + bot.news = new WebhookClient({ url: process.env.NEWS }); + bot.ticket = new WebhookClient({ url: process.env.TICKET }); + bot.ticketLogs = {}; + await bot.db.$connect(); + + const commit = execSync("git rev-parse HEAD").toString().trim(); + + await bot.debug.send({ + content: `Bot is starting up.\nVersion: ${ + process.env.npm_package_version + }\nCommit: [${commit.slice( + 0, + 7 + )}]()`, + }); + + bot.on(Events.ClientReady, async () => { + await onReady(bot); + }); + + bot.on(Events.MessageCreate, async (message) => { + await onMessageCreate(bot, message); + }); + + bot.on(Events.MessageReactionAdd, async (r, user) => { + await onReactionAdd(bot, r, user); + }); + + bot.on(Events.GuildMemberAdd, async (member) => { + await onMemberAdd(bot, member); + }); + + bot.on(Events.InteractionCreate, async (interaction) => { + await onInteractionCreate(bot, interaction); + }); + + await bot.login(process.env.TOKEN); +})(); diff --git a/src/interface/AirtableResponse.ts b/src/interface/AirtableResponse.ts new file mode 100644 index 0000000..8545392 --- /dev/null +++ b/src/interface/AirtableResponse.ts @@ -0,0 +1,71 @@ +export interface AirtableResponse { + records: { + id: string; + createdTime: string; + fields: { + "Anything Else?"?: string; + Reference: { + id: string; + width?: number; + height?: number; + url: string; + filename: string; + size: number; + type: string; + thumbnails?: { + small: { + url: string; + width: number; + height: number; + }; + large: { + url: string; + width: number; + height: number; + }; + full: { + url: string; + width: number; + height: number; + }; + }; + }[]; + "Acknowledgement "?: boolean; + "Email Address"?: string; + "What would you like us to draw?"?: string; + Action: string; + Name: string; + "Contact Method": string; + Created: string; + "Proof of Donation"?: { + id: string; + width: number; + height: number; + url: string; + filename: string; + size: number; + type: string; + thumbnails: { + small: { + url: string; + width: number; + height: number; + }; + large: { + url: string; + width: number; + height: number; + }; + full: { + url: string; + width: number; + height: number; + }; + }; + }[]; + "Donation Amount"?: number; + Fund?: string; + Handle?: string; + }; + }[]; +} diff --git a/src/interface/ButtonHandler.ts b/src/interface/ButtonHandler.ts new file mode 100644 index 0000000..4b74107 --- /dev/null +++ b/src/interface/ButtonHandler.ts @@ -0,0 +1,8 @@ +import { ButtonInteraction } from "discord.js"; + +import { ExtendedClient } from "./ExtendedClient"; + +export type ButtonHandler = ( + Bot: ExtendedClient, + interaction: ButtonInteraction<"cached"> +) => Promise; diff --git a/src/interface/ExtendedClient.ts b/src/interface/ExtendedClient.ts new file mode 100644 index 0000000..9ebeab4 --- /dev/null +++ b/src/interface/ExtendedClient.ts @@ -0,0 +1,13 @@ +import { PrismaClient } from "@prisma/client"; +import { Client, WebhookClient } from "discord.js"; + +export interface ExtendedClient extends Client { + db: PrismaClient; + debug: WebhookClient; + comm: WebhookClient; + dist: WebhookClient; + news: WebhookClient; + ticket: WebhookClient; + lastArticle: number; + ticketLogs: { [key: string]: string }; +} diff --git a/src/modules/buttons/ticketClaim.ts b/src/modules/buttons/ticketClaim.ts new file mode 100644 index 0000000..1f8b561 --- /dev/null +++ b/src/modules/buttons/ticketClaim.ts @@ -0,0 +1,78 @@ +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + Embed, + GuildMember, + Message, +} from "discord.js"; + +import { TicketSupportRole } from "../../config/Tickets"; +import { ButtonHandler } from "../../interface/ButtonHandler"; + +/** + * Handles the process of claiming a ticket. + */ +export const ticketClaimHandler: ButtonHandler = async (bot, interaction) => { + try { + await interaction.deferReply({ ephemeral: true }); + const { guild, message, member } = interaction; + const { embeds } = message; + const supportRole = await guild.roles.fetch(TicketSupportRole); + + if (!supportRole) { + await interaction.editReply("Cannot find support role!"); + return; + } + + const isSupport = (member as GuildMember).roles.cache.has(supportRole.id); + + if (!isSupport) { + await interaction.editReply({ + content: "Only support members can claim a ticket.", + }); + return; + } + + const ticketEmbed = embeds[0] as Embed; + const updatedEmbed = { + title: ticketEmbed.title || "Lost the Title", + description: ticketEmbed.description || "Lost the Description", + fields: [{ name: "Claimed by:", value: `<@${member.user.id}>` }], + }; + + const claimButton = new ButtonBuilder() + .setCustomId("claim") + .setStyle(ButtonStyle.Success) + .setLabel("Claim this ticket!") + .setEmoji("✋") + .setDisabled(true); + const closeButton = new ButtonBuilder() + .setCustomId("close") + .setStyle(ButtonStyle.Danger) + .setLabel("Close this ticket!") + .setEmoji("🗑️"); + + const row = new ActionRowBuilder().addComponents([ + claimButton, + closeButton, + ]); + + await (message as Message).edit({ + embeds: [updatedEmbed], + components: [row], + }); + + await interaction.editReply( + "I have marked you as responsible for this query." + ); + } catch (err) { + await bot.debug.send( + `Error in ticket claim module: ${(err as Error).message}` + ); + await interaction.editReply({ + content: + "Forgive me, but I failed to complete your request. Please try again later.", + }); + } +}; diff --git a/src/modules/buttons/ticketClose.ts b/src/modules/buttons/ticketClose.ts new file mode 100644 index 0000000..3517cf6 --- /dev/null +++ b/src/modules/buttons/ticketClose.ts @@ -0,0 +1,60 @@ +import { GuildMember, EmbedBuilder, TextChannel } from "discord.js"; + +import { TicketSupportRole } from "../../config/Tickets"; +import { ButtonHandler } from "../../interface/ButtonHandler"; +import { generateLogs } from "../generateLogs"; + +/** + * Handles closing a ticket. + */ +export const ticketCloseHandler: ButtonHandler = async (bot, interaction) => { + try { + await interaction.deferReply({ ephemeral: true }); + const { guild, member, channel } = interaction; + + if (!guild || !member || !channel) { + await interaction.editReply({ + content: "Error finding the guild!", + }); + return; + } + + const supportRole = await guild.roles.fetch(TicketSupportRole); + + if (!supportRole) { + await interaction.editReply("Cannot find support role!"); + return; + } + + const isSupport = (member as GuildMember).roles.cache.has(supportRole.id); + + if (!isSupport) { + await interaction.editReply({ + content: "Only support members can close a ticket.", + }); + return; + } + + const logEmbed = new EmbedBuilder(); + logEmbed.setTitle("Ticket Closed"); + logEmbed.setDescription(`Ticket closed by <@!${member.user.id}>`); + logEmbed.addFields({ + name: "User", + value: + (channel as TextChannel)?.name.split("-").slice(1).join("-") || + "unknown", + }); + + const logFile = await generateLogs(bot, channel.id); + await bot.ticket.send({ embeds: [logEmbed], files: [logFile] }); + await channel.delete(); + } catch (err) { + await bot.debug.send( + `Error in close ticket module: ${(err as Error).message}` + ); + await interaction.editReply({ + content: + "Forgive me, but I failed to complete your request. Please try again later.", + }); + } +}; diff --git a/src/modules/buttons/ticketOpen.ts b/src/modules/buttons/ticketOpen.ts new file mode 100644 index 0000000..4e33d20 --- /dev/null +++ b/src/modules/buttons/ticketOpen.ts @@ -0,0 +1,41 @@ +import { + ActionRowBuilder, + ModalBuilder, + ModalActionRowComponentBuilder, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; + +import { ButtonHandler } from "../../interface/ButtonHandler"; + +/** + * Generates the modal for opening a new ticket. + */ +export const ticketOpenHandler: ButtonHandler = async (bot, interaction) => { + try { + const ticketModal = new ModalBuilder() + .setCustomId("ticket-modal") + .setTitle("Ticket System"); + const reasonInput = new TextInputBuilder() + .setCustomId("reason") + .setLabel("Why are you opening this ticket?") + .setStyle(TextInputStyle.Paragraph) + .setRequired(true) + .setMaxLength(1000); + + const actionRow = + new ActionRowBuilder().addComponents( + reasonInput + ); + ticketModal.addComponents(actionRow); + await interaction.showModal(ticketModal); + } catch (err) { + await bot.debug.send( + `Error in open ticket module: ${(err as Error).message}` + ); + await interaction.editReply({ + content: + "Forgive me, but I failed to complete your request. Please try again later.", + }); + } +}; diff --git a/src/modules/checkAirtableRecords.ts b/src/modules/checkAirtableRecords.ts new file mode 100644 index 0000000..8f5e891 --- /dev/null +++ b/src/modules/checkAirtableRecords.ts @@ -0,0 +1,182 @@ +import { readFile, writeFile } from "fs/promises"; +import { join } from "path"; + +import { AttachmentBuilder } from "discord.js"; + +import { ActionsToLabel, PlatformsToLabel, Trello } from "../config/Trello"; +import { AirtableResponse } from "../interface/AirtableResponse"; +import { ExtendedClient } from "../interface/ExtendedClient"; + +/** + * Fetches the Airtable records and pipes new records to trello. + * + * @param {ExtendedClient} bot The bot's Discord instance. + */ +export const checkAirtableRecords = async (bot: ExtendedClient) => { + try { + const latestId = await readFile( + join(__dirname, "..", "..", "latestAirtableRecord.txt"), + "utf-8" + ); + const records = await fetch( + `https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/${process.env.AIRTABLE_TABLE_ID}?maxRecords=100&sort%5B0%5D%5Bfield%5D=Created&sort%5B0%5D%5Bdirection%5D=desc`, + { + headers: { + Authorization: `Bearer ${process.env.AIRTABLE_KEY}`, + }, + } + ); + const res = (await records.json()) as AirtableResponse; + const alreadySeenIndex = res.records.findIndex((r) => r.id === latestId); + if (alreadySeenIndex === 0) { + return; + } + const newRecords = res.records.slice(0, alreadySeenIndex); + const newLatestId = newRecords[0]?.id as string; + + await writeFile( + join(__dirname, "..", "..", "latestAirtableRecord.txt"), + newLatestId, + "utf-8" + ); + + for (const record of newRecords) { + const { + Name: name, + Reference: images, + Action: action, + "Contact Method": platform, + Handle: handle, + "Anything Else?": note, + "Email Address": email, + "What would you like us to draw?": request, + } = record.fields; + const files: AttachmentBuilder[] = []; + for (const imageUrl of images) { + const image = await fetch(imageUrl.url); + const imageBuffer = await image.arrayBuffer(); + const imageFinal = Buffer.from(imageBuffer); + const file = new AttachmentBuilder(imageFinal, { + name: "reference.png", + }); + files.push(file); + } + + if (!process.env.TRELLO_TOKEN || !process.env.TRELLO_KEY) { + await bot.debug.send({ + content: + "Cannot create card on Trello. Missing environment variables.", + }); + await bot.comm.send({ + content: `${name} | Trello failed to generate. | References attached below:`, + files, + }); + return; + } + const cardRes = await fetch( + `https://api.trello.com/1/cards?idList=${Trello.newCardListId}&key=${ + process.env.TRELLO_KEY + }&token=${process.env.TRELLO_TOKEN}&name=${encodeURIComponent( + name + )}&desc=${encodeURIComponent( + `${name} helped us with: ${action}\n\nPlease contact them at ${ + platform === "Email" ? email : `${handle} on ${platform}` + } to update them on art.\n\nUSER NOTE: ${note}\nART REQUEST: ${request}` + )}`, + { + method: "POST", + headers: { + Accept: "application/json", + }, + } + ); + const card = (await cardRes.json()) as { id: string; url: string }; + await fetch( + `https://api.trello.com/1/cards/${card.id}/attachments?key=${ + process.env.TRELLO_KEY + }&token=${ + process.env.TRELLO_TOKEN + }&name=reference.png&url=${encodeURIComponent( + images[0]?.url ?? "https://cdn.naomi.lgbt/banner.png" + )}&setCover=true`, + { + method: "POST", + headers: { + accept: "application/json", + }, + } + ); + for (const imageUrl of images.slice(1)) { + await fetch( + `https://api.trello.com/1/cards/${card.id}/attachments?key=${ + process.env.TRELLO_KEY + }&token=${ + process.env.TRELLO_TOKEN + }&name=reference.png&url=${encodeURIComponent(imageUrl.url)}`, + { + method: "POST", + headers: { + accept: "application/json", + }, + } + ); + } + // Use this if we want to add checklist to every card :3 + // await fetch( + // `https://api.trello.com/1/cards/${ + // card.id + // }/checklists?idChecklistSource=${ + // Trello.checklistId + // }&name=${encodeURIComponent("To Do")}&key=${ + // process.env.TRELLO_KEY + // }&token=${process.env.TRELLO_TOKEN}`, + // { + // method: "POST", + // headers: { + // accept: "application/json", + // }, + // } + // ); + if (PlatformsToLabel[platform]) { + await fetch( + `https://api.trello.com/1/cards/${card.id}/idLabels?value=${PlatformsToLabel[platform]}&key=${process.env.TRELLO_KEY}&token=${process.env.TRELLO_TOKEN}`, + { + method: "POST", + headers: { + accept: "application/json", + }, + } + ); + } + if (ActionsToLabel[action]) { + await fetch( + `https://api.trello.com/1/cards/${card.id}/idLabels?value=${ActionsToLabel[action]}&key=${process.env.TRELLO_KEY}&token=${process.env.TRELLO_TOKEN}`, + { + method: "POST", + headers: { + accept: "application/json", + }, + } + ); + } + const msg = await bot.comm.send({ + content: `${name} | [Trello](<${card.url}>) | References attached below.\nYou can ignore this ID it's just for the bot: ${card.id}`, + files, + }); + await bot.db.rewards.create({ + data: { + trelloId: card.id, + createdAt: Date.now(), + claimedBy: "", + completed: false, + messageId: msg.id, + }, + }); + return; + } + } catch (err) { + await bot.debug.send( + `Error in check airtable records: ${(err as Error).message}` + ); + } +}; diff --git a/src/modules/createLogsFile.ts b/src/modules/createLogsFile.ts new file mode 100644 index 0000000..9e56081 --- /dev/null +++ b/src/modules/createLogsFile.ts @@ -0,0 +1,32 @@ +import { writeFile } from "fs/promises"; +import { join } from "path"; + +import { ExtendedClient } from "../interface/ExtendedClient"; + +/** + * Creates the initial ticket log file. + * + * @param {ExtendedClient} bot The bot's Discord instance. + * @param {string} channelId The ticket channel ID, used as a unique identifier. + * @param {string} user The user tag of the person who opened the ticket. + * @param {string} content The initial content of the log file. + */ +export const createLogFile = async ( + bot: ExtendedClient, + channelId: string, + user: string, + content: string +): Promise => { + try { + bot.ticketLogs[channelId] = channelId; + + await writeFile( + join(process.cwd(), "logs", `${channelId}.txt`), + `[${new Date().toLocaleString()}] - **TICKET CREATED**\n[${new Date().toLocaleString()}] - ${user}: ${content}\n` + ); + } catch (err) { + await bot.debug.send( + `Error in create log file module: ${(err as Error).message}` + ); + } +}; diff --git a/src/modules/generateLogs.ts b/src/modules/generateLogs.ts new file mode 100644 index 0000000..7b7f3ae --- /dev/null +++ b/src/modules/generateLogs.ts @@ -0,0 +1,46 @@ +import { readFile, unlink } from "fs/promises"; +import { join } from "path"; + +import { AttachmentBuilder } from "discord.js"; + +import { ExtendedClient } from "../interface/ExtendedClient"; + +/** + * To run when a ticket is closed. Finds the ticket log file, + * creates a message attachement with the logs, and deletes the file. + * + * @param {ExtendedClient} bot The bot's Discord instance. + * @param {string} channelId The channel ID of the ticket. + * @returns {Promise} The log file as a Discord attachment. + */ +export const generateLogs = async ( + bot: ExtendedClient, + channelId: string +): Promise => { + try { + delete bot.ticketLogs[channelId]; + + const logs = await readFile( + join(process.cwd(), "logs", `${channelId}.txt`), + "utf8" + ).catch(() => "no logs found..."); + + const attachment = new AttachmentBuilder(Buffer.from(logs, "utf-8"), { + name: "log.txt", + }); + + await unlink(join(process.cwd(), "logs", `${channelId}.txt`)).catch( + () => null + ); + + return attachment; + } catch (err) { + await bot.debug.send( + `Error in generate logs module: ${(err as Error).message}` + ); + return new AttachmentBuilder( + Buffer.from("An error occurred fetching these logs.", "utf-8"), + { name: "log.txt" } + ); + } +}; diff --git a/src/modules/logTicketMessage.ts b/src/modules/logTicketMessage.ts new file mode 100644 index 0000000..28567f2 --- /dev/null +++ b/src/modules/logTicketMessage.ts @@ -0,0 +1,39 @@ +import { readFile, writeFile } from "fs/promises"; +import { join } from "path"; + +import { Message } from "discord.js"; + +import { ExtendedClient } from "../interface/ExtendedClient"; + +/** + * Logs messages to a file to track ticket activity. + * + * @param {ExtendedClient} bot The bot's Discord instance. + * @param {Message} message The Discord message payload. + * @param {string} logId The logId to identify the file. + */ +export const logTicketMessage = async ( + bot: ExtendedClient, + message: Message, + logId: string +): Promise => { + try { + const logFile = await readFile( + join(process.cwd(), "logs", `${logId}.txt`), + "utf8" + ); + + const parsedString = `[${new Date( + message.createdTimestamp + ).toLocaleString()}] - ${message.author.tag}: ${message.content}\n`; + + await writeFile( + join(process.cwd(), "logs", `${logId}.txt`), + logFile + parsedString + ); + } catch (err) { + await bot.debug.send( + `Error in log ticket message module: ${(err as Error).message}` + ); + } +}; diff --git a/src/modules/messages/fetchMessages.ts b/src/modules/messages/fetchMessages.ts new file mode 100644 index 0000000..deba78d --- /dev/null +++ b/src/modules/messages/fetchMessages.ts @@ -0,0 +1,40 @@ +import { ExtendedClient } from "../../interface/ExtendedClient"; + +/** + * Caches the messages from a specific channel. This should be done to ensure + * all donation + delivery messages are cached and reaction payloads will be received. + * + * @param {ExtendedClient} bot The bot's Discord instance. + * @param {string} channelId The ID of the channel to cache. + */ +export const fetchMessages = async (bot: ExtendedClient, channelId: string) => { + try { + await bot.debug.send(`Caching messages for ${channelId}`); + const guild = await bot.guilds.fetch("1172566005311090798"); + const channel = await guild.channels.fetch(channelId); + + if (!channel || !("send" in channel)) { + await bot.debug.send(`Failed to load <#${channelId}>~!`); + return; + } + + let before: string | undefined = undefined; + + await bot.debug.send("Fetching first wave of messages."); + let messages = await channel.messages.fetch({ limit: 100 }); + + while (messages.size === 100) { + before = messages.last()?.id; + if (!before) { + await bot.debug.send("Failed to get oldest messages from batch."); + break; + } + await bot.debug.send( + `Found more than 100 messages. Fetching next batch from ${before}` + ); + messages = await channel.messages.fetch({ limit: 100, before }); + } + } catch (err) { + await bot.debug.send({ content: `` }); + } +}; diff --git a/src/modules/messages/startTicketPost.ts b/src/modules/messages/startTicketPost.ts new file mode 100644 index 0000000..7f60655 --- /dev/null +++ b/src/modules/messages/startTicketPost.ts @@ -0,0 +1,48 @@ +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + Message, +} from "discord.js"; + +import { ExtendedClient } from "../../interface/ExtendedClient"; + +/** + * Handles the logic to start a ticket post. + * + * @param {ExtendedClient} bot The bot's Discord instance. + * @param {Message} message The message payload from Discord. + */ +export const startTicketPost = async ( + bot: ExtendedClient, + message: Message +) => { + try { + const embed = new EmbedBuilder(); + embed.setTitle("Need Help?"); + embed.setDescription( + `If you have concerns with behavior in this Discord server, click the button below to open a private ticket with the Moderation team. + +If you want to contact the team for any other reason, you can message an Admin or send an email to \`contact@art4palestine.org\`.` + ); + embed.setColor("#0099ff"); + + const button = new ButtonBuilder() + .setLabel("Open a Ticket!") + .setEmoji("❔") + .setStyle(ButtonStyle.Primary) + .setCustomId("ticket"); + + const row = new ActionRowBuilder().addComponents(button); + await message.channel.send({ embeds: [embed], components: [row] }); + } catch (err) { + await bot.debug.send( + `Error in start ticket post module: ${(err as Error).message}` + ); + await message.reply({ + content: + "Forgive me, but I failed to complete your request. Please try again later.", + }); + } +}; diff --git a/src/modules/modals/handleTicketModal.ts b/src/modules/modals/handleTicketModal.ts new file mode 100644 index 0000000..c515ced --- /dev/null +++ b/src/modules/modals/handleTicketModal.ts @@ -0,0 +1,82 @@ +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ChannelType, + EmbedBuilder, + ModalSubmitInteraction, + TextChannel, +} from "discord.js"; + +import { TicketSupportRole } from "../../config/Tickets"; +import { ExtendedClient } from "../../interface/ExtendedClient"; +import { createLogFile } from "../createLogsFile"; + +/** + * Handles responding to the ticket modal. + * + * @param {ExtendedClient} bot The bot's discord instance. + * @param {ModalSubmitInteraction} interaction The interaction payload from Discord. + */ +export const handleTicketModal = async ( + bot: ExtendedClient, + interaction: ModalSubmitInteraction +) => { + try { + await interaction.deferReply({ ephemeral: true }); + const { guild, user, channel } = interaction; + const reason = interaction.fields.getTextInputValue("reason"); + + if (!guild || !channel || !("threads" in channel)) { + await interaction.editReply( + "Forgive me, but this can only be done within a server" + ); + return; + } + + const claimButton = new ButtonBuilder() + .setCustomId("claim") + .setStyle(ButtonStyle.Success) + .setLabel("Claim this ticket!") + .setEmoji("✋"); + const closeButton = new ButtonBuilder() + .setCustomId("close") + .setStyle(ButtonStyle.Danger) + .setLabel("Close this ticket!") + .setEmoji("🗑️"); + + const row = new ActionRowBuilder().addComponents([ + claimButton, + closeButton, + ]); + + const ticketEmbed = new EmbedBuilder(); + ticketEmbed.setTitle("Ticket Created"); + ticketEmbed.setDescription( + `<@!${user.id}> opened a ticket for:\n${reason}` + ); + + const ticketThread = await (channel as TextChannel).threads.create({ + name: `ticket-${user.username}`, + type: ChannelType.PrivateThread, + }); + await ticketThread.members.add(user.id); + await ticketThread.send({ + content: `<@&${TicketSupportRole}>, a ticket has been opened!`, + embeds: [ticketEmbed], + components: [row], + }); + await createLogFile(bot, ticketThread.id, user.tag, reason); + await interaction.editReply( + "Your ticket channel has been created! Please head there and describe the issue you are having." + ); + } catch (err) { + await bot.debug.send( + `Error in ticket modal module: ${(err as Error).message}` + ); + await interaction.editReply({ + content: + "Forgive me, but I failed to complete your request. Please try again later.", + }); + } +}; diff --git a/src/modules/reminders/sendUnclaimedArt.ts b/src/modules/reminders/sendUnclaimedArt.ts new file mode 100644 index 0000000..61fcf0c --- /dev/null +++ b/src/modules/reminders/sendUnclaimedArt.ts @@ -0,0 +1,40 @@ +import { ExtendedClient } from "../../interface/ExtendedClient"; + +/** + * Sends a reminder to claim the unclaimed art rewards. + * + * @param {ExtendedClient} bot The bot's Discord instance. + */ +export const sendUnclaimedArt = async (bot: ExtendedClient) => { + try { + const unclaimed = await bot.db.rewards.findMany({ + where: { + claimedBy: "", + }, + }); + if (!unclaimed.length) { + return; + } + + const guild = await bot.guilds.fetch("1172566005311090798"); + const channel = await guild.channels.fetch("1172568865218252801"); + + if (!channel || !("send" in channel)) { + await bot.debug.send(`Failed to find <#1172568865218252801>~!`); + return; + } + + await channel.send(`## Unclaimed Art! + +The following art rewards have not been claimed yet. If you have the capacity to do so, please consider taking one on. + +${unclaimed + .map( + (r) => + `- https://discord.com/channels/1172566005311090798/1172568787330019340/${r.messageId}` + ) + .join("\n")}`); + } catch (err) { + await bot.debug.send(`Cannot send unclaimed art reminder: ${err}`); + } +}; diff --git a/src/modules/reminders/sendUnfinishedArt.ts b/src/modules/reminders/sendUnfinishedArt.ts new file mode 100644 index 0000000..2c28c5c --- /dev/null +++ b/src/modules/reminders/sendUnfinishedArt.ts @@ -0,0 +1,43 @@ +import { ExtendedClient } from "../../interface/ExtendedClient"; + +/** + * Sends a reminder to finish claimed art rewards. + * + * @param {ExtendedClient} bot The bot's Discord instance. + */ +export const sendUnfinishedArt = async (bot: ExtendedClient) => { + try { + const unfinished = await bot.db.rewards.findMany({ + where: { + claimedBy: { + not: "", + }, + completed: false, + }, + }); + if (!unfinished.length) { + return; + } + + const guild = await bot.guilds.fetch("1172566005311090798"); + const channel = await guild.channels.fetch("1172568865218252801"); + + if (!channel || !("send" in channel)) { + await bot.debug.send(`Failed to find <#1172568865218252801>~!`); + return; + } + + await channel.send(`## Unfinished Art! + +The following art rewards have not been completed yet. We would like to keep a turnaround time of a couple of weeks, but your health is also important! So if you have claimed one of these and don't have the capacity to finish it, let Angel or Naomi know. + +${unfinished + .map( + (r) => + `- https://discord.com/channels/1172566005311090798/1172568787330019340/${r.messageId}` + ) + .join("\n")}`); + } catch (err) { + await bot.debug.send(`Cannot send unfinished art reminder: ${err}`); + } +}; diff --git a/src/server/serve.ts b/src/server/serve.ts new file mode 100644 index 0000000..e641b53 --- /dev/null +++ b/src/server/serve.ts @@ -0,0 +1,125 @@ +import { createHmac } from "crypto"; +import { readFile } from "fs/promises"; +import http from "http"; +import https from "https"; + +import express from "express"; + +import { Trello } from "../config/Trello"; +import { ExtendedClient } from "../interface/ExtendedClient"; + +/** + * Instantiates the web server for listening to webhook payloads. + * + * @param {ExtendedClient} bot The bot's Discord instance. + */ +export const serve = async (bot: ExtendedClient) => { + const app = express(); + app.use(express.json()); + + app.get("/", (_req, res) => { + res.status(302).redirect("https://art4palestine.org/"); + }); + + /** + * Trello expects a 200 head status. + */ + app.head("/trello", (_req, res) => { + res.status(200).send("OK~"); + }); + + app.post("/trello", async (req, res) => { + const secret = process.env.TRELLO_SECRET; + if (!secret) { + res.status(500).send("Missing trello key~!"); + await bot.debug.send({ + content: + "Received a request to the trello endpoint, but key is not configured.", + }); + return; + } + const headerHash = req.headers["x-trello-webhook"]; + const getDigest = (s: string) => + createHmac("sha1", secret).update(s).digest("base64"); + const content = JSON.stringify(req.body) + process.env.TRELLO_HOOK_CALLBACK; + const contentHash = getDigest(content); + if (headerHash !== contentHash) { + res.status(403).send("Invalid hash!"); + await bot.debug.send({ + content: + "Received a request to the trello endpoint, but hash was incorrect.", + }); + return; + } + res.status(200).send("OK~!"); + const oldList = req.body.action?.data?.old?.idList; + const newList = req.body.action?.data?.card?.idList; + const cardId = req.body.action?.data?.card?.id; + if ( + !oldList || + !newList || + !cardId || + oldList === newList || + newList !== Trello.readyToSendListId + ) { + return; + } + + const cardRes = await fetch( + `https://api.trello.com/1/cards/${cardId}?key=${process.env.TRELLO_KEY}&token=${process.env.TRELLO_TOKEN}` + ); + const card = (await cardRes.json()) as { + id: string; + url: string; + name: string; + desc: string; + }; + const contact = card.desc + .split(/\n+/g) + .find((s) => s.startsWith("Please contact them")); + await bot.dist.send({ + content: `${card.name} | [Trello Card](<${card.url}>) | ${contact}\nYou can ignore this ID it's just for the bot: ${card.id}`, + }); + await bot.db.rewards.update({ + where: { + trelloId: card.id, + }, + data: { + completed: true, + }, + }); + }); + + const httpServer = http.createServer(app); + + httpServer.listen(10080, async () => { + await bot.debug.send("http server listening on port 10080"); + }); + + if (process.env.NODE_ENV === "production") { + const privateKey = await readFile( + "/etc/letsencrypt/live/afp.nhcarrigan.com/privkey.pem", + "utf8" + ); + const certificate = await readFile( + "/etc/letsencrypt/live/afp.nhcarrigan.com/cert.pem", + "utf8" + ); + const ca = await readFile( + "/etc/letsencrypt/live/afp.nhcarrigan.com/chain.pem", + "utf8" + ); + + const credentials = { + key: privateKey, + cert: certificate, + ca: ca, + }; + + const httpsServer = https.createServer(credentials, app); + + httpsServer.listen(10443, async () => { + await bot.debug.send("https server listening on port 10443"); + }); + } +}; diff --git a/src/utils/getMuteDurationUnit.ts b/src/utils/getMuteDurationUnit.ts new file mode 100644 index 0000000..8cedf1a --- /dev/null +++ b/src/utils/getMuteDurationUnit.ts @@ -0,0 +1,24 @@ +/** + * Parses a unit string into units. + * + * @param {string} unit The unit string to parse. + * @returns {number} The unit conversion rate, to convert a number to ms. + */ +export const getMuteDurationUnit = (unit: string): number | null => { + switch (unit.toLowerCase()) { + case "m": + case "minute": + case "min": + case "minutes": + return 1000 * 60; + case "h": + case "hours": + case "hour": + return 1000 * 60 * 60; + case "d": + case "day": + case "days": + return 1000 * 60 * 60 * 24; + } + return null; +}; diff --git a/src/utils/getNewsFeed.ts b/src/utils/getNewsFeed.ts new file mode 100644 index 0000000..9c4a1d5 --- /dev/null +++ b/src/utils/getNewsFeed.ts @@ -0,0 +1,119 @@ +import { ExtendedClient } from "../interface/ExtendedClient"; + +const getHeadlineArticle = async (bot: ExtendedClient) => { + try { + const req = await fetch( + "https://www.aljazeera.com/tag/israel-palestine-conflict/", + { + method: "GET", + headers: { + "Wp-site": "Naomi's Art for Palestine Bot", + }, + } + ); + const res = await req.text(); + const link = res.match(/Live\supdates<\/a>/); + return link?.[1]; + } catch (err) { + await bot.debug.send( + `Error in fetching latest news post: ${(err as Error).message}` + ); + return null; + } +}; + +const getArticleChildren = async (bot: ExtendedClient, slug: string) => { + try { + const req = await fetch( + `https://www.aljazeera.com/graphql?wp-site=aje&operationName=SingleLiveBlogChildrensQuery&variables=%7B%22postName%22%3A%22${slug}%22%7D&extensions=%7B%7D`, + { + method: "GET", + headers: { + "Wp-site": "Naomi's Art for Palestine Bot", + }, + } + ); + const res = (await req.json()) as { + data: { + article: { + children: number[]; + }; + }; + }; + return res.data.article.children; + } catch (err) { + await bot.debug.send( + `Error in fetching article children: ${(err as Error).message}` + ); + return null; + } +}; + +const getArticleInfo = async (bot: ExtendedClient, postId: number) => { + try { + const req = await fetch( + `https://www.aljazeera.com/graphql?wp-site=aje&operationName=LiveBlogUpdateQuery&variables=%7B%22postID%22%3A${postId}%2C%22postType%22%3A%22liveblog-update%22%2C%22preview%22%3A%22%22%2C%22isAmp%22%3Afalse%7D&extensions=%7B%7D` + ); + const res = (await req.json()) as { + data: { + posts: { + link: string; + title: string; + id: string; + }; + }; + }; + return res.data.posts; + } catch (err) { + await bot.debug.send( + `Error in fetching article info: ${(err as Error).message}` + ); + return null; + } +}; + +/** + * Fetches the GraphQL feed from Aljazeera's Palestine live feed. + * + * @param {ExtendedClient} bot The bot's Discord instance. + */ +export const getNewsFeed = async (bot: ExtendedClient) => { + try { + const headline = await getHeadlineArticle(bot); + if (!headline) { + return; + } + const children = await getArticleChildren( + bot, + headline.split("/").slice(-1).join() + ); + if (!children) { + return; + } + if (!bot.lastArticle) { + bot.lastArticle = children[0]; + await bot.debug.send( + `No cache found. Caching ${children[0]} and skipping run.` + ); + return; + } + const laterChildren = children.slice(0, children.indexOf(bot.lastArticle)); + bot.lastArticle = children[0]; + for (const postId of laterChildren) { + const article = await getArticleInfo(bot, postId); + if (!article) { + continue; + } + await bot.news.send( + `[${article.title}]()\n--------` + ); + } + } catch (err) { + await bot.debug.send( + `Error in fetching news feed: ${(err as Error).message}` + ); + } +}; diff --git a/src/utils/isValidWebhook.ts b/src/utils/isValidWebhook.ts new file mode 100644 index 0000000..f0c6efd --- /dev/null +++ b/src/utils/isValidWebhook.ts @@ -0,0 +1,10 @@ +import { Webhooks } from "../config/Webhooks"; + +/** + * Checks if the webhook ID is present in the enum. + * + * @param {string} id The ID to check. + * @returns {boolean} If the webhook ID is valid. + */ +export const isValidWebhook = (id: string): id is Webhooks => + (Object.values(Webhooks) as string[]).includes(id); diff --git a/src/utils/logHandler.ts b/src/utils/logHandler.ts new file mode 100644 index 0000000..fdbe023 --- /dev/null +++ b/src/utils/logHandler.ts @@ -0,0 +1,24 @@ +import { createLogger, format, transports, config } from "winston"; + +const { combine, timestamp, colorize, printf } = format; + +/** + * Standard log handler, using winston to wrap and format + * messages. Call with `logHandler.log(level, message)`. + * + * @param {string} level - The log level to use. + * @param {string} message - The message to log. + */ +export const logHandler = createLogger({ + levels: config.npm.levels, + level: "silly", + transports: [new transports.Console()], + format: combine( + timestamp({ + format: "YYYY-MM-DD HH:mm:ss", + }), + colorize(), + printf((info) => `${info.level}: ${[info.timestamp]}: ${info.message}`) + ), + exitOnError: false, +}); diff --git a/test/index.spec.ts b/test/index.spec.ts new file mode 100644 index 0000000..44e64d7 --- /dev/null +++ b/test/index.spec.ts @@ -0,0 +1,7 @@ +import { assert } from "chai"; + +suite("This is an example test", () => { + test("It uses the assert API", () => { + assert.isTrue(true); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..679dcfb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@nhcarrigan/typescript-config", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./prod" + }, + "exclude": ["./test"] +}