From 1c07905f21c0a31d60b3b885b2ecfd0600450d7b Mon Sep 17 00:00:00 2001 From: Josh S Date: Mon, 28 Aug 2023 20:17:50 -0400 Subject: [PATCH] Initial commit --- .dockerignore | 11 + .eslintignore | 8 + .eslintrc.json | 94 + .gitbook.yaml | 1 + .gitignore | 129 + .prettierignore | 8 + .prettierrc.json | 12 + .vscode/extensions.json | 7 + .vscode/launch.json | 138 + .vscode/settings.json | 44 + .vscode/tasks.json | 19 + Dockerfile | 22 + LEGAL.md | 76 + LICENSE | 21 + README.md | 133 + config/bot-sites.example.json | 44 + config/config.example.json | 80 + config/debug.example.json | 12 + lang/lang.common.json | 37 + lang/lang.en-GB.json | 308 + lang/lang.en-US.json | 308 + lang/logs.json | 60 + ...rd Bot Cluster API.postman_collection.json | 135 + package-lock.json | 5989 +++++++++++++++++ package.json | 71 + process.json | 10 + src/buttons/button.ts | 17 + src/buttons/index.ts | 1 + src/commands/args.ts | 60 + src/commands/chat/dev-command.ts | 98 + src/commands/chat/help-command.ts | 51 + src/commands/chat/index.ts | 4 + src/commands/chat/info-command.ts | 47 + src/commands/chat/test-command.ts | 19 + src/commands/command.ts | 28 + src/commands/index.ts | 3 + src/commands/message/index.ts | 1 + src/commands/message/view-date-sent.ts | 30 + src/commands/metadata.ts | 96 + src/commands/user/index.ts | 1 + src/commands/user/view-date-joined.ts | 32 + src/constants/discord-limits.ts | 15 + src/constants/index.ts | 1 + src/controllers/controller.ts | 8 + src/controllers/guilds-controller.ts | 37 + src/controllers/index.ts | 4 + src/controllers/root-controller.ts | 17 + src/controllers/shards-controller.ts | 80 + src/enums/dev-command-name.ts | 3 + src/enums/help-option.ts | 4 + src/enums/index.ts | 3 + src/enums/info-option.ts | 4 + src/events/button-handler.ts | 83 + src/events/command-handler.ts | 182 + src/events/event-handler.ts | 3 + src/events/guild-join-handler.ts | 67 + src/events/guild-leave-handler.ts | 18 + src/events/index.ts | 8 + src/events/message-handler.ts | 17 + src/events/reaction-handler.ts | 65 + src/events/trigger-handler.ts | 56 + src/extensions/custom-client.ts | 23 + src/extensions/index.ts | 1 + src/jobs/index.ts | 2 + src/jobs/job.ts | 8 + src/jobs/update-server-count-job.ts | 68 + src/middleware/check-auth.ts | 11 + src/middleware/handle-error.ts | 19 + src/middleware/index.ts | 3 + src/middleware/map-class.ts | 40 + src/models/api.ts | 38 + src/models/bot.ts | 203 + src/models/cluster-api/guilds.ts | 3 + src/models/cluster-api/index.ts | 2 + src/models/cluster-api/shards.ts | 34 + src/models/config-models.ts | 7 + src/models/enum-helpers/index.ts | 2 + src/models/enum-helpers/language.ts | 110 + src/models/enum-helpers/permission.ts | 244 + src/models/internal-models.ts | 12 + src/models/manager.ts | 50 + src/models/master-api/clusters.ts | 42 + src/models/master-api/index.ts | 5 + src/reactions/index.ts | 1 + src/reactions/reaction.ts | 16 + src/services/command-registration-service.ts | 157 + src/services/event-data-service.ts | 39 + src/services/http-service.ts | 54 + src/services/index.ts | 7 + src/services/job-service.ts | 53 + src/services/lang.ts | 83 + src/services/logger.ts | 92 + src/services/master-api-service.ts | 65 + src/start-bot.ts | 143 + src/start-manager.ts | 90 + src/triggers/index.ts | 1 + src/triggers/trigger.ts | 9 + src/utils/client-utils.ts | 247 + src/utils/command-utils.ts | 73 + src/utils/format-utils.ts | 57 + src/utils/index.ts | 13 + src/utils/interaction-utils.ts | 176 + src/utils/math-utils.ts | 17 + src/utils/message-utils.ts | 169 + src/utils/partial-utils.ts | 88 + src/utils/permission-utils.ts | 127 + src/utils/random-utils.ts | 13 + src/utils/regex-utils.ts | 31 + src/utils/shard-utils.ts | 43 + src/utils/string-utils.ts | 37 + src/utils/thread-utils.ts | 52 + tsconfig.json | 19 + 112 files changed, 11839 insertions(+) create mode 100644 .dockerignore create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .gitbook.yaml create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 Dockerfile create mode 100644 LEGAL.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config/bot-sites.example.json create mode 100644 config/config.example.json create mode 100644 config/debug.example.json create mode 100644 lang/lang.common.json create mode 100644 lang/lang.en-GB.json create mode 100644 lang/lang.en-US.json create mode 100644 lang/logs.json create mode 100644 misc/Discord Bot Cluster API.postman_collection.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 process.json create mode 100644 src/buttons/button.ts create mode 100644 src/buttons/index.ts create mode 100644 src/commands/args.ts create mode 100644 src/commands/chat/dev-command.ts create mode 100644 src/commands/chat/help-command.ts create mode 100644 src/commands/chat/index.ts create mode 100644 src/commands/chat/info-command.ts create mode 100644 src/commands/chat/test-command.ts create mode 100644 src/commands/command.ts create mode 100644 src/commands/index.ts create mode 100644 src/commands/message/index.ts create mode 100644 src/commands/message/view-date-sent.ts create mode 100644 src/commands/metadata.ts create mode 100644 src/commands/user/index.ts create mode 100644 src/commands/user/view-date-joined.ts create mode 100644 src/constants/discord-limits.ts create mode 100644 src/constants/index.ts create mode 100644 src/controllers/controller.ts create mode 100644 src/controllers/guilds-controller.ts create mode 100644 src/controllers/index.ts create mode 100644 src/controllers/root-controller.ts create mode 100644 src/controllers/shards-controller.ts create mode 100644 src/enums/dev-command-name.ts create mode 100644 src/enums/help-option.ts create mode 100644 src/enums/index.ts create mode 100644 src/enums/info-option.ts create mode 100644 src/events/button-handler.ts create mode 100644 src/events/command-handler.ts create mode 100644 src/events/event-handler.ts create mode 100644 src/events/guild-join-handler.ts create mode 100644 src/events/guild-leave-handler.ts create mode 100644 src/events/index.ts create mode 100644 src/events/message-handler.ts create mode 100644 src/events/reaction-handler.ts create mode 100644 src/events/trigger-handler.ts create mode 100644 src/extensions/custom-client.ts create mode 100644 src/extensions/index.ts create mode 100644 src/jobs/index.ts create mode 100644 src/jobs/job.ts create mode 100644 src/jobs/update-server-count-job.ts create mode 100644 src/middleware/check-auth.ts create mode 100644 src/middleware/handle-error.ts create mode 100644 src/middleware/index.ts create mode 100644 src/middleware/map-class.ts create mode 100644 src/models/api.ts create mode 100644 src/models/bot.ts create mode 100644 src/models/cluster-api/guilds.ts create mode 100644 src/models/cluster-api/index.ts create mode 100644 src/models/cluster-api/shards.ts create mode 100644 src/models/config-models.ts create mode 100644 src/models/enum-helpers/index.ts create mode 100644 src/models/enum-helpers/language.ts create mode 100644 src/models/enum-helpers/permission.ts create mode 100644 src/models/internal-models.ts create mode 100644 src/models/manager.ts create mode 100644 src/models/master-api/clusters.ts create mode 100644 src/models/master-api/index.ts create mode 100644 src/reactions/index.ts create mode 100644 src/reactions/reaction.ts create mode 100644 src/services/command-registration-service.ts create mode 100644 src/services/event-data-service.ts create mode 100644 src/services/http-service.ts create mode 100644 src/services/index.ts create mode 100644 src/services/job-service.ts create mode 100644 src/services/lang.ts create mode 100644 src/services/logger.ts create mode 100644 src/services/master-api-service.ts create mode 100644 src/start-bot.ts create mode 100644 src/start-manager.ts create mode 100644 src/triggers/index.ts create mode 100644 src/triggers/trigger.ts create mode 100644 src/utils/client-utils.ts create mode 100644 src/utils/command-utils.ts create mode 100644 src/utils/format-utils.ts create mode 100644 src/utils/index.ts create mode 100644 src/utils/interaction-utils.ts create mode 100644 src/utils/math-utils.ts create mode 100644 src/utils/message-utils.ts create mode 100644 src/utils/partial-utils.ts create mode 100644 src/utils/permission-utils.ts create mode 100644 src/utils/random-utils.ts create mode 100644 src/utils/regex-utils.ts create mode 100644 src/utils/shard-utils.ts create mode 100644 src/utils/string-utils.ts create mode 100644 src/utils/thread-utils.ts create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a5be230 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +# Folders +/.cache +/.git +/dist +/docs +/misc +/node_modules +/temp + +# Files +/npm-debug.log diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..9739255 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,8 @@ +# Folders +/.cache +/.git +/dist +/docs +/misc +/node_modules +/temp diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..f49619b --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,94 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json" + }, + "plugins": ["@typescript-eslint", "import", "unicorn"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:import/recommended", + "plugin:import/typescript" + ], + "rules": { + "@typescript-eslint/explicit-function-return-type": [ + "error", + { + "allowExpressions": true + } + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-inferrable-types": [ + "error", + { + "ignoreParameters": true + } + ], + "@typescript-eslint/no-misused-promises": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/restrict-template-expressions": "off", + "@typescript-eslint/return-await": ["error", "always"], + "@typescript-eslint/typedef": [ + "error", + { + "parameter": true, + "propertyDeclaration": true + } + ], + "import/extensions": ["error", "ignorePackages"], + "import/no-extraneous-dependencies": "error", + "import/no-unresolved": "off", + "import/no-useless-path-segments": "error", + "import/order": [ + "error", + { + "alphabetize": { + "caseInsensitive": true, + "order": "asc" + }, + "groups": [ + ["builtin", "external", "object", "type"], + ["internal", "parent", "sibling", "index"] + ], + "newlines-between": "always" + } + ], + "no-return-await": "off", + "no-unused-vars": "off", + "prefer-const": "off", + "quotes": [ + "error", + "single", + { + "allowTemplateLiterals": true + } + ], + "sort-imports": [ + "error", + { + "allowSeparatedGroups": true, + "ignoreCase": true, + "ignoreDeclarationSort": true, + "ignoreMemberSort": false, + "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] + } + ], + "unicorn/prefer-node-protocol": "error" + } +} diff --git a/.gitbook.yaml b/.gitbook.yaml new file mode 100644 index 0000000..e454be0 --- /dev/null +++ b/.gitbook.yaml @@ -0,0 +1 @@ +root: ./docs/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a199ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# ===================================================== +# Custom +# ===================================================== +/dist +/temp +/**/config/**/*.json +!/**/config/**/*.example.json + +# ===================================================== +# Node.js +# ===================================================== +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env.production + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9739255 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +# Folders +/.cache +/.git +/dist +/docs +/misc +/node_modules +/temp diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..ad2cf87 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,12 @@ +{ + "printWidth": 100, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "auto" +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b987fdf --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "streetsidesoftware.code-spell-checker" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..881d8f2 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,138 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "start:bot", + "type": "node", + "request": "launch", + "protocol": "inspector", + "preLaunchTask": "build", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "node", + "args": ["--enable-source-maps", "${workspaceFolder}/dist/start-bot.js"], + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], + "outputCapture": "std", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": ["/**"], + "restart": false + }, + { + "name": "start:manager", + "type": "node", + "request": "launch", + "protocol": "inspector", + "preLaunchTask": "build", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "node", + "args": ["--enable-source-maps", "${workspaceFolder}/dist/start-manager.js"], + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], + "outputCapture": "std", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": ["/**"], + "restart": false + }, + { + "name": "commands:view", + "type": "node", + "request": "launch", + "protocol": "inspector", + "preLaunchTask": "build", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "node", + "args": [ + "--enable-source-maps", + "${workspaceFolder}/dist/start-bot.js", + "commands", + "view" + ], + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], + "outputCapture": "std", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": ["/**"], + "restart": false + }, + { + "name": "commands:register", + "type": "node", + "request": "launch", + "protocol": "inspector", + "preLaunchTask": "build", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "node", + "args": [ + "--enable-source-maps", + "${workspaceFolder}/dist/start-bot.js", + "commands", + "register" + ], + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], + "outputCapture": "std", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": ["/**"], + "restart": false + }, + { + "name": "commands:rename", + "type": "node", + "request": "launch", + "protocol": "inspector", + "preLaunchTask": "build", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "node", + "args": [ + "--enable-source-maps", + "${workspaceFolder}/dist/start-bot.js", + "commands", + "rename", + "old_name", + "new_name" + ], + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], + "outputCapture": "std", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": ["/**"], + "restart": false + }, + { + "name": "commands:delete", + "type": "node", + "request": "launch", + "protocol": "inspector", + "preLaunchTask": "build", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "node", + "args": [ + "--enable-source-maps", + "${workspaceFolder}/dist/start-bot.js", + "commands", + "delete", + "command_name" + ], + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], + "outputCapture": "std", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": ["/**"], + "restart": false + }, + { + "name": "commands:clear", + "type": "node", + "request": "launch", + "protocol": "inspector", + "preLaunchTask": "build", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "node", + "args": [ + "--enable-source-maps", + "${workspaceFolder}/dist/start-bot.js", + "commands", + "clear" + ], + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], + "outputCapture": "std", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": ["/**"], + "restart": false + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c97f399 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,44 @@ +{ + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "cSpell.enabled": true, + "cSpell.words": [ + "autocompletes", + "autocompleting", + "bot's", + "cmds", + "cooldown", + "cooldowns", + "datas", + "descs", + "discordbotlist", + "discordjs", + "discordlabs", + "discordlist", + "disforge", + "filesize", + "luxon", + "millis", + "Novak", + "ondiscord", + "parens", + "pino", + "regexes", + "respawn", + "respawned", + "restjson", + "unescapes", + "varchar" + ], + "typescript.preferences.importModuleSpecifierEnding": "js" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..9d05f7d --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,19 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "${workspaceFolder}\\node_modules\\.bin\\tsc", + "args": ["--project", "${workspaceFolder}\\tsconfig.json"] + } + ], + "windows": { + "options": { + "shell": { + "executable": "cmd.exe", + "args": ["/d", "/c"] + } + } + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1a4e991 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM node:16 + +# Create app directory +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install packages +RUN npm install + +# Copy the app code +COPY . . + +# Build the project +RUN npm run build + +# Expose ports +EXPOSE 3001 + +# Run the application +CMD [ "node", "dist/start-manager.js" ] diff --git a/LEGAL.md b/LEGAL.md new file mode 100644 index 0000000..952e32c --- /dev/null +++ b/LEGAL.md @@ -0,0 +1,76 @@ +# Terms of Service + +## Usage Agreement + +By inviting the bot or using its features, you are agreeing to the below mentioned Terms of Service and Privacy Policy. + +You acknowledge that you have the privilege to use the bot freely on any Discord server you share with it, that you can invite it to any server that you have "Manage Server" rights for and that this privilege might get revoked for you, if you're subject of breaking the terms and/or policy of this bot, or the Terms of Service, Privacy Policy and/or Community Guidelines of Discord Inc. + +Through inviting or interacting with the bot it may collect specific data as described in its [Privacy Policy](#privacy-policy). The intended usage of this data is for core functionalities of the bot such as command handling, server settings, and user settings. + +## Intended Age + +The bot may not be used by individuals under the minimal age described in Discord's Terms of Service. + +Do not provide any age-restricted content (as defined in Discord's safety policies) to the bot. Age-restricted content includes but is not limited to content and discussion related to: + +- Sexually explicit material such as pornography or sexually explicit text +- Violent content +- Illegal, dangerous, and regulated goods such as firearms, tactical gear, alcohol, or drug use +- Gambling-adjacent or addictive behavior + +Content submitted to the bot through the use of commands arguments, text inputs, image inputs, or otherwise must adhere to the above conditions. Violating these conditions may result in your account being reported to Discord Inc for further action. + +## Affiliation + +The bot is not affiliated with, supported by, or made by Discord Inc. + +Any direct connection to Discord or any of its trademark objects is purely coincidental. We do not claim to have the copyright ownership of any of Discord's assets, trademarks or other intellectual property. + +## Liability + +The owner(s) of the bot may not be made liable for individuals breaking these Terms at any given time. We have faith in the end users being truthful about their information and not misusing this bot or the services of Discord Inc in a malicious way. + +We reserve the right to update these terms at our own discretion, giving you a 1-week (7 days) period to opt out of these terms if you're not agreeing with the new changes. + +You may opt out by removing the bot from any server you have the rights for. + +## Contact + +People may get in contact through the official support server of the bot. + +Other ways of support may be provided but aren't guaranteed. + +# Privacy Policy + +## Usage of Data + +The bot may use stored data, as defined below, for different features including but not limited to: + +- Command handling +- Providing server and user preferences + +The bot may share non-sensitive data with 3rd party sites or services, including but not limited to: + +- Aggregate/statistical data (ex: total number of server or users) +- Discord generated IDs needed to tie 3rd party data to Discord or user-provided data + +Personally identifiable (other than IDs) or sensitive information will not be shared with 3rd party sites or services. + +## Updating Data + +The bot's data may be updated when using specific commands. + +Updating data can require the input of an end user, and data that can be seen as sensitive, such as content of a message, may need to be stored when using certain commands. + +## Temporarily Stored Data + +The bot may keep stored data in an internal caching mechanic for a certain amount of time. After this time period, the cached information will be dropped and only be re-added when required. + +Data may be dropped from cache pre-maturely through actions such as removing the bot from the server. + +## Removal of Data + +Manual removal of the data can be requested through the official support server. Discord IDs such as user, guild, role, etc. may be stored even after the removal of other data in order to properly identify bot specific statistics since those IDs are public and non-sensitive. + +For security reasons we will ask you to provide us with proof of ownership to the data you wish to be removed. Only a server owner may request manual removal of server data. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ad57793 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Kevin Novak + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa3888c --- /dev/null +++ b/README.md @@ -0,0 +1,133 @@ +# Discord Bot TypeScript Template + +[![discord.js](https://img.shields.io/github/package-json/dependency-version/KevinNovak/Discord-Bot-TypeScript-Template/discord.js)](https://discord.js.org/) +[![License](https://img.shields.io/badge/license-MIT-blue)](https://opensource.org/licenses/MIT) +[![Stars](https://img.shields.io/github/stars/KevinNovak/Discord-Bot-TypeScript-Template.svg)](https://github.com/KevinNovak/Discord-Bot-TypeScript-Template/stargazers) +[![Pull Requests](https://img.shields.io/badge/Pull%20Requests-Welcome!-brightgreen)](https://github.com/KevinNovak/Discord-Bot-TypeScript-Template/pulls) + +**Discord bot** - A discord.js bot template written with TypeScript. + +## Introduction + +This template was created to give developers a starting point for new Discord bots, so that much of the initial setup can be avoided and developers can instead focus on meaningful bot features. Developers can simply copy this repo, follow the [setup instructions](#setup) below, and have a working bot with many [boilerplate features](#features) already included! + +For help using this template, feel free to [join our support server](https://discord.gg/c9kQktCbsE)! + +[![Discord Shield](https://discord.com/api/guilds/660711235766976553/widget.png?style=shield)](https://discord.gg/c9kQktCbsE) + +## Features + +### Built-In Bot Features: + +- Basic command structure. +- Rate limits and command cooldowns. +- Welcome message when joining a server. +- Shows server count in bot status. +- Posts server count to popular bot list websites. +- Support for multiple languages. + +### Developer Friendly: + +- Written with TypeScript. +- Uses the [discord.js](https://discord.js.org/) framework. +- Built-in debugging setup for VSCode. +- Written with [ESM](https://nodejs.org/api/esm.html#introduction) for future compatibility with packages. +- Support for running with the [PM2](https://pm2.keymetrics.io/) process manger. +- Support for running with [Docker](https://www.docker.com/). + +### Scales as Your Bot Grows: + +- Supports [sharding](https://discordjs.guide/sharding/) which is required when your bot is in 2500+ servers. +- Supports [clustering](https://github.com/KevinNovak/Discord-Bot-TypeScript-Template-Master-Api) which allows you to run your bot on multiple machines. + +## Commands + +This bot has a few example commands which can be modified as needed. + +### Help Command + +A `/help` command to get help on different areas of the bot or to contact support: + +![](https://i.imgur.com/UUA4WzL.png) + +![](https://i.imgur.com/YtDdmTe.png) + +![](https://i.imgur.com/JXMisap.png) + +### Info Command + +A `/info` command to get information about the bot or links to different resources. + +![](https://i.imgur.com/0kKOaWM.png) + +### Test Command + +A generic command, `/test`, which can be copied to create additional commands. + +![](https://i.imgur.com/lqjkNKM.png) + +### Dev Command + +A `/dev` command which can only be run by the bot developer. Shows developer information, but can be extended to perform developer-only actions. + +![](https://i.imgur.com/2o1vEno.png) + +### Welcome Message + +A welcome message is sent to the server and owner when the bot is added. + +![](https://i.imgur.com/QBw8H8v.png) + +## Setup + +1. Copy example config files. + - Navigate to the `config` folder of this project. + - Copy all files ending in `.example.json` and remove the `.example` from the copied file names. + - Ex: `config.example.json` should be copied and renamed as `config.json`. +2. Obtain a bot token. + - You'll need to create a new bot in your [Discord Developer Portal](https://discord.com/developers/applications/). + - See [here](https://www.writebots.com/discord-bot-token/) for detailed instructions. + - At the end you should have a **bot token**. +3. Modify the config file. + - Open the `config/config.json` file. + - You'll need to edit the following values: + - `client.id` - Your discord bot's [user ID](https://techswift.org/2020/04/22/how-to-find-your-user-id-on-discord/). + - `client.token` - Your discord bot's token. +4. Install packages. + - Navigate into the downloaded source files and type `npm install`. +5. Register commands. + - In order to use slash commands, they first [have to be registered](https://discordjs.guide/creating-your-bot/command-deployment.html). + - Type `npm run commands:register` to register the bot's commands. + - Run this script any time you change a command name, structure, or add/remove commands. + - This is so Discord knows what your commands look like. + - It may take up to an hour for command changes to appear. + +## Start Scripts + +You can run the bot in multiple modes: + +1. Normal Mode + - Type `npm start`. + - Starts a single instance of the bot. +2. Manager Mode + - Type `npm run start:manager`. + - Starts a shard manager which will spawn multiple bot shards. +3. PM2 Mode + - Type `npm run start:pm2`. + - Similar to Manager Mode but uses [PM2](https://pm2.keymetrics.io/) to manage processes. + +## Bots Using This Template + +A list of Discord bots using this template. + +| Bot | Servers | +| ---------------------------------------------------------------------- | ------------------------------------------------------------- | +| [Birthday Bot](https://top.gg/bot/656621136808902656) | ![](https://top.gg/api/widget/servers/656621136808902656.svg) | +| [QOTD Bot](https://top.gg/bot/713586207119900693) | ![](https://top.gg/api/widget/servers/713586207119900693.svg) | +| [Friend Time](https://top.gg/bot/471091072546766849) | ![](https://top.gg/api/widget/servers/471091072546766849.svg) | +| [Bento](https://top.gg/bot/787041583580184609) | ![](https://top.gg/api/widget/servers/787041583580184609.svg) | +| [NFT-Info](https://top.gg/bot/902249456072818708) | ![](https://top.gg/api/widget/servers/902249456072818708.svg) | +| [Skylink-IF](https://top.gg/bot/929527099922993162) | ![](https://top.gg/api/widget/servers/929527099922993162.svg) | +| [Topcoder TC-101](https://github.com/topcoder-platform/tc-discord-bot) | | + +Don't see your bot listed? [Contact us](https://discord.gg/c9kQktCbsE) to have your bot added! diff --git a/config/bot-sites.example.json b/config/bot-sites.example.json new file mode 100644 index 0000000..9831d08 --- /dev/null +++ b/config/bot-sites.example.json @@ -0,0 +1,44 @@ +[ + { + "name": "top.gg", + "enabled": false, + "url": "https://top.gg/api/bots//stats", + "authorization": "", + "body": "{\"server_count\":{{SERVER_COUNT}}}" + }, + { + "name": "bots.ondiscord.xyz", + "enabled": false, + "url": "https://bots.ondiscord.xyz/bot-api/bots//guilds", + "authorization": "", + "body": "{\"guildCount\":{{SERVER_COUNT}}}" + }, + { + "name": "discord.bots.gg", + "enabled": false, + "url": "https://discord.bots.gg/api/v1/bots//stats", + "authorization": "", + "body": "{\"guildCount\":{{SERVER_COUNT}}}" + }, + { + "name": "discordbotlist.com", + "enabled": false, + "url": "https://discordbotlist.com/api/bots//stats", + "authorization": "Bot ", + "body": "{\"guilds\":{{SERVER_COUNT}}}" + }, + { + "name": "discords.com", + "enabled": false, + "url": "https://discords.com/bots/api/bot/", + "authorization": "", + "body": "{\"server_count\":{{SERVER_COUNT}}}" + }, + { + "name": "disforge.com", + "enabled": false, + "url": "https://disforge.com/api/botstats/", + "authorization": "", + "body": "{\"servers\":{{SERVER_COUNT}}}" + } +] diff --git a/config/config.example.json b/config/config.example.json new file mode 100644 index 0000000..27b7f3f --- /dev/null +++ b/config/config.example.json @@ -0,0 +1,80 @@ +{ + "developers": [""], + "client": { + "id": "", + "token": "", + "intents": [ + "Guilds", + "GuildMessages", + "GuildMessageReactions", + "DirectMessages", + "DirectMessageReactions" + ], + "partials": ["Message", "Channel", "Reaction"], + "caches": { + "AutoModerationRuleManager": 0, + "BaseGuildEmojiManager": 0, + "GuildEmojiManager": 0, + "GuildBanManager": 0, + "GuildInviteManager": 0, + "GuildScheduledEventManager": 0, + "GuildStickerManager": 0, + "MessageManager": 0, + "PresenceManager": 0, + "StageInstanceManager": 0, + "ThreadManager": 0, + "ThreadMemberManager": 0, + "VoiceStateManager": 0 + } + }, + "api": { + "port": 3001, + "secret": "00000000-0000-0000-0000-000000000000" + }, + "sharding": { + "spawnDelay": 5, + "spawnTimeout": 300, + "serversPerShard": 1000 + }, + "clustering": { + "enabled": false, + "shardCount": 16, + "callbackUrl": "http://localhost:3001/", + "masterApi": { + "url": "http://localhost:5000/", + "token": "00000000-0000-0000-0000-000000000000" + } + }, + "jobs": { + "updateServerCount": { + "schedule": "0 */10 * * * *", + "log": false, + "runOnce": false, + "initialDelaySecs": 0 + } + }, + "rateLimiting": { + "commands": { + "amount": 10, + "interval": 30 + }, + "buttons": { + "amount": 10, + "interval": 30 + }, + "triggers": { + "amount": 10, + "interval": 30 + }, + "reactions": { + "amount": 10, + "interval": 30 + } + }, + "logging": { + "pretty": true, + "rateLimit": { + "minTimeout": 30 + } + } +} diff --git a/config/debug.example.json b/config/debug.example.json new file mode 100644 index 0000000..da7037a --- /dev/null +++ b/config/debug.example.json @@ -0,0 +1,12 @@ +{ + "override": { + "shardMode": { + "enabled": false, + "value": "worker" + } + }, + "dummyMode": { + "enabled": false, + "whitelist": ["212772875793334272", "478288246858711040"] + } +} diff --git a/lang/lang.common.json b/lang/lang.common.json new file mode 100644 index 0000000..2d290e3 --- /dev/null +++ b/lang/lang.common.json @@ -0,0 +1,37 @@ +{ + "bot": { + "name": "My Bot", + "author": "My Name" + }, + "emojis": { + "yes": "✅", + "no": "❌", + "enabled": "đŸŸĸ", + "disabled": "🔴", + "info": "â„šī¸", + "warning": "âš ī¸", + "previous": "â—€ī¸", + "next": "â–ļī¸", + "first": "âĒ", + "last": "⏊", + "refresh": "🔄" + }, + "colors": { + "default": "#0099ff", + "success": "#00ff83", + "warning": "#ffcc66", + "error": "#ff4a4a" + }, + "links": { + "author": "https://github.com/", + "docs": "https://top.gg/", + "donate": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=EW389DYYSS4FC", + "invite": "https://discord.com/", + "source": "https://github.com/", + "stream": "https://www.twitch.tv/novakevin", + "support": "https://support.discord.com/", + "template": "https://github.com/KevinNovak/Discord-Bot-TypeScript-Template", + "terms": "https://github.com/KevinNovak/Discord-Bot-TypeScript-Template/blob/master/LEGAL.md#terms-of-service", + "vote": "https://top.gg/" + } +} diff --git a/lang/lang.en-GB.json b/lang/lang.en-GB.json new file mode 100644 index 0000000..f1867ec --- /dev/null +++ b/lang/lang.en-GB.json @@ -0,0 +1,308 @@ +{ + "data": { + "displayEmbeds": { + "welcome": { + "title": "Thank you for using {{COM:bot.name}}!", + "description": ["{{REF:bot.description}}"], + "fields": [ + { + "name": "Important {{REF:fields.commands}}", + "value": ["{{CMD_LINK_HELP}} - {{REF:commandDescs.help}}"] + }, + { + "name": "{{REF:fields.links}}", + "value": ["{{REF:links.docsEmbed}}", "{{REF:links.supportEmbed}}"] + } + ] + }, + "helpContactSupport": { + "title": "Help - {{REF:helpOptions.contactSupport}}", + "description": [ + "Have a question or feedback? Join our support server at the link below!" + ], + "fields": [ + { + "name": "{{REF:fields.links}}", + "value": ["{{REF:links.supportEmbed}}"] + } + ] + }, + "helpCommands": { + "title": "Help - {{REF:helpOptions.commands}}", + "fields": [ + { + "name": "Commands", + "value": [ + "To see the available commands, just type `/` and select the bot from the left side. You can then scroll through all available commands. Some commands may be hidden if you don't have permission to view them.", + "", + "{{CMD_LINK_TEST}} - {{REF:commandDescs.test}}", + "{{CMD_LINK_INFO}} - {{REF:commandDescs.info}}" + ] + }, + { + "name": "Command Permissions", + "value": [ + "Want to restrict commands to certain roles, users, or channels? Set up permissions in the bot's integration page by going to **Server Settings** > **Integrations**, and then **Manage** for this bot." + ] + }, + { + "name": "{{REF:fields.links}}", + "value": ["{{REF:links.docsEmbed}}", "{{REF:links.supportEmbed}}"] + } + ] + }, + "test": { + "description": "Test command works!" + }, + "viewDateJoined": { + "description": "{{TARGET}} joined on {{DATE}}!" + }, + "viewDateSent": { + "description": "This message was sent on {{DATE}}!" + }, + "about": { + "title": "{{COM:bot.name}} - About", + "description": "{{REF:bot.description}}", + "fields": [ + { "name": "Author", "value": "{{REF:links.authorEmbed}}" }, + { + "name": "{{REF:fields.links}}", + "value": [ + "{{REF:links.sourceEmbed}}", + "{{REF:links.docsEmbed}}", + "{{REF:links.termsEmbed}}", + "{{REF:links.voteEmbed}}", + "{{REF:links.donateEmbed}}", + "{{REF:links.supportEmbed}}", + "{{REF:links.inviteEmbed}}" + ] + }, + { + "name": "Created With", + "value": ["{{REF:links.templateEmbed}}"] + } + ] + }, + "translate": { + "title": "{{COM:bot.name}} - Translations", + "description": "Thank you to our translators who have made it possible for {{COM:bot.name}} to be used in the following languages. If you are interested in providing a translation, please contact the staff in our [support server]({{COM:links.support}})." + }, + "devInfo": { + "title": "{{COM:bot.name}} - Developer Info", + "fields": [ + { + "name": "Versions", + "value": [ + "**Node.js**: {{NODE_VERSION}}", + "**TypeScript**: {{TS_VERSION}}", + "**ECMAScript**: {{ES_VERSION}}", + "**discord.js**: {{DJS_VERSION}}" + ] + }, + { + "name": "Stats", + "value": [ + "**Shards**: {{SHARD_COUNT}}", + "**Servers**: {{SERVER_COUNT}} ({{SERVER_COUNT_PER_SHARD}}/Shard)" + ] + }, + { + "name": "Memory", + "value": [ + "**RSS**: {{RSS_SIZE}} ({{RSS_SIZE_PER_SERVER}}/Server)", + "**Heap**: {{HEAP_TOTAL_SIZE}} ({{HEAP_TOTAL_SIZE_PER_SERVER}}/Server)", + "**Used**: {{HEAP_USED_SIZE}} ({{HEAP_USED_SIZE_PER_SERVER}}/Server)" + ] + }, + { + "name": "IDs", + "value": [ + "**Hostname**: {{HOSTNAME}}", + "**Shard ID**: {{SHARD_ID}}", + "**Server ID**: {{SERVER_ID}}", + "**Bot ID**: {{BOT_ID}}", + "**User ID**: {{USER_ID}}" + ] + } + ] + } + }, + "validationEmbeds": { + "cooldownHit": { + "description": "You can only run this command {{AMOUNT}} time(s) every {{INTERVAL}}. Please wait before attempting this command again.", + "color": "{{COM:colors.warning}}" + }, + "devOnly": { + "description": "This action can only be done by developers.", + "color": "{{COM:colors.warning}}" + }, + "missingClientPerms": { + "description": [ + "I don't have all permissions required to run that command here! Please check the server and channel permissions to make sure I have the following permissions.", + "", + "Required permissions: {{PERMISSIONS}}" + ], + "color": "{{COM:colors.warning}}" + } + }, + "errorEmbeds": { + "command": { + "description": "Something went wrong!", + "fields": [ + { + "name": "Error code", + "value": "{{ERROR_CODE}}" + }, + { + "name": "Server ID", + "value": "{{GUILD_ID}}" + }, + { + "name": "Shard ID", + "value": "{{SHARD_ID}}" + }, + { + "name": "Contact support", + "value": "{{COM:links.support}}" + } + ], + "color": "{{COM:colors.error}}" + }, + "startupInProcess": { + "description": "{{COM:bot.name}} is still starting up. Try again later.", + "color": "{{COM:colors.warning}}" + }, + "notImplemented": { + "description": "This feature has not been implemented yet!", + "color": "{{COM:colors.warning}}" + } + }, + "channelRegexes": { + "bot": "/bot|command|cmd/i" + } + }, + "refs": { + "meta": { + "translators": "[TranslatorName#1234](https://github.com/)" + }, + "bot": { + "description": "{{REF:links.templateEmbed}} helps give developers a starting point for new Discord bots, so that much of the initial setup can be avoided and developers can instead focus on meaningful bot features." + }, + "chatCommands": { + "dev": "dev", + "help": "help", + "info": "info", + "test": "test" + }, + "userCommands": { + "viewDateJoined": "View Date Joined" + }, + "messageCommands": { + "viewDateSent": "View Date Sent" + }, + "arguments": { + "command": "command", + "option": "option" + }, + "commandDescs": { + "dev": "Developer use only.", + "help": "Find help or contact support.", + "info": "View bot info.", + "test": "Run the test command." + }, + "argDescs": { + "devCommand": "Command.", + "helpOption": "Option.", + "infoOption": "Option." + }, + "fields": { + "commands": "Commands", + "links": "Links" + }, + "permissions": { + "AddReactions": "Add Reactions", + "Administrator": "Administrator", + "AttachFiles": "Attach Files", + "BanMembers": "Ban Members", + "ChangeNickname": "Change Nickname", + "Connect": "Connect", + "CreateInstantInvite": "Create Invite", + "CreatePrivateThreads": "Create Private Threads", + "CreatePublicThreads": "Create Public Threads", + "DeafenMembers": "Deafen Members", + "EmbedLinks": "Embed Links", + "KickMembers": "Kick Members", + "ManageChannels": "Manage Channel(s)", + "ManageEmojisAndStickers": "Manage Emoji and Stickers", + "ManageEvents": "Manage Events", + "ManageGuild": "Manage Server", + "ManageGuildExpressions": "Manage Expressions", + "ManageMessages": "Manage Messages", + "ManageNicknames": "Manage Nicknames", + "ManageRoles": "Manage Roles / Permissions", + "ManageThreads": "Manage Threads / Posts", + "ManageWebhooks": "Manage Webhooks", + "MentionEveryone": "Mention Everyone, Here, and All Roles", + "ModerateMembers": "Timeout Members", + "MoveMembers": "Move Members", + "MuteMembers": "Mute Members", + "PrioritySpeaker": "Priority Speaker", + "ReadMessageHistory": "Read Message History", + "RequestToSpeak": "Request to Speak", + "SendMessages": "Send Messages / Create Posts", + "SendMessagesInThreads": "Send Messages in Threads / Posts", + "SendTTSMessages": "Send Text-to-Speech Messages", + "SendVoiceMessages": "Send Voice Messages", + "Speak": "Speak", + "Stream": "Video", + "UseApplicationCommands": "Use Application Commands", + "UseEmbeddedActivities": "Use Activities", + "UseExternalEmojis": "Use External Emoji", + "UseExternalSounds": "Use External Sounds", + "UseExternalStickers": "Use External Stickers", + "UseSoundboard": "Use Soundboard", + "UseVAD": "Use Voice Activity", + "ViewAuditLog": "View Audit Log", + "ViewChannel": "View Channel(s)", + "ViewCreatorMonetizationAnalytics": "View Server Subscription Insights", + "ViewGuildInsights": "View Server Insights" + }, + "devCommandNames": { + "info": "info" + }, + "helpOptions": { + "contactSupport": "Contact Support", + "commands": "Commands" + }, + "helpOptionDescs": { + "contactSupport": "❓ {{REF:helpOptions.contactSupport}} ❓", + "commands": "{{REF:helpOptions.commands}} -- What commands are there? How do I restrict who is allowed to use commands?" + }, + "infoOptions": { + "about": "About", + "translate": "Translate" + }, + "yesNo": { + "yes": "Yes", + "no": "No" + }, + "boolean": { + "true": "True", + "false": "False" + }, + "other": { + "na": "N/A" + }, + "links": { + "authorEmbed": "[{{COM:bot.author}}]({{COM:links.author}})", + "docsEmbed": "[View Documentation]({{COM:links.docs}})", + "donateEmbed": "[Donate via PayPal]({{COM:links.donate}})", + "inviteEmbed": "[Invite {{COM:bot.name}} to a Server!]({{COM:links.invite}})", + "sourceEmbed": "[View Source Code]({{COM:links.source}})", + "supportEmbed": "[Join Support Server]({{COM:links.support}})", + "templateEmbed": "[Discord Bot TypeScript Template]({{COM:links.template}})", + "termsEmbed": "[View Terms of Service]({{COM:links.terms}})", + "voteEmbed": "[Vote for {{COM:bot.name}}!]({{COM:links.vote}})" + } + } +} diff --git a/lang/lang.en-US.json b/lang/lang.en-US.json new file mode 100644 index 0000000..f1867ec --- /dev/null +++ b/lang/lang.en-US.json @@ -0,0 +1,308 @@ +{ + "data": { + "displayEmbeds": { + "welcome": { + "title": "Thank you for using {{COM:bot.name}}!", + "description": ["{{REF:bot.description}}"], + "fields": [ + { + "name": "Important {{REF:fields.commands}}", + "value": ["{{CMD_LINK_HELP}} - {{REF:commandDescs.help}}"] + }, + { + "name": "{{REF:fields.links}}", + "value": ["{{REF:links.docsEmbed}}", "{{REF:links.supportEmbed}}"] + } + ] + }, + "helpContactSupport": { + "title": "Help - {{REF:helpOptions.contactSupport}}", + "description": [ + "Have a question or feedback? Join our support server at the link below!" + ], + "fields": [ + { + "name": "{{REF:fields.links}}", + "value": ["{{REF:links.supportEmbed}}"] + } + ] + }, + "helpCommands": { + "title": "Help - {{REF:helpOptions.commands}}", + "fields": [ + { + "name": "Commands", + "value": [ + "To see the available commands, just type `/` and select the bot from the left side. You can then scroll through all available commands. Some commands may be hidden if you don't have permission to view them.", + "", + "{{CMD_LINK_TEST}} - {{REF:commandDescs.test}}", + "{{CMD_LINK_INFO}} - {{REF:commandDescs.info}}" + ] + }, + { + "name": "Command Permissions", + "value": [ + "Want to restrict commands to certain roles, users, or channels? Set up permissions in the bot's integration page by going to **Server Settings** > **Integrations**, and then **Manage** for this bot." + ] + }, + { + "name": "{{REF:fields.links}}", + "value": ["{{REF:links.docsEmbed}}", "{{REF:links.supportEmbed}}"] + } + ] + }, + "test": { + "description": "Test command works!" + }, + "viewDateJoined": { + "description": "{{TARGET}} joined on {{DATE}}!" + }, + "viewDateSent": { + "description": "This message was sent on {{DATE}}!" + }, + "about": { + "title": "{{COM:bot.name}} - About", + "description": "{{REF:bot.description}}", + "fields": [ + { "name": "Author", "value": "{{REF:links.authorEmbed}}" }, + { + "name": "{{REF:fields.links}}", + "value": [ + "{{REF:links.sourceEmbed}}", + "{{REF:links.docsEmbed}}", + "{{REF:links.termsEmbed}}", + "{{REF:links.voteEmbed}}", + "{{REF:links.donateEmbed}}", + "{{REF:links.supportEmbed}}", + "{{REF:links.inviteEmbed}}" + ] + }, + { + "name": "Created With", + "value": ["{{REF:links.templateEmbed}}"] + } + ] + }, + "translate": { + "title": "{{COM:bot.name}} - Translations", + "description": "Thank you to our translators who have made it possible for {{COM:bot.name}} to be used in the following languages. If you are interested in providing a translation, please contact the staff in our [support server]({{COM:links.support}})." + }, + "devInfo": { + "title": "{{COM:bot.name}} - Developer Info", + "fields": [ + { + "name": "Versions", + "value": [ + "**Node.js**: {{NODE_VERSION}}", + "**TypeScript**: {{TS_VERSION}}", + "**ECMAScript**: {{ES_VERSION}}", + "**discord.js**: {{DJS_VERSION}}" + ] + }, + { + "name": "Stats", + "value": [ + "**Shards**: {{SHARD_COUNT}}", + "**Servers**: {{SERVER_COUNT}} ({{SERVER_COUNT_PER_SHARD}}/Shard)" + ] + }, + { + "name": "Memory", + "value": [ + "**RSS**: {{RSS_SIZE}} ({{RSS_SIZE_PER_SERVER}}/Server)", + "**Heap**: {{HEAP_TOTAL_SIZE}} ({{HEAP_TOTAL_SIZE_PER_SERVER}}/Server)", + "**Used**: {{HEAP_USED_SIZE}} ({{HEAP_USED_SIZE_PER_SERVER}}/Server)" + ] + }, + { + "name": "IDs", + "value": [ + "**Hostname**: {{HOSTNAME}}", + "**Shard ID**: {{SHARD_ID}}", + "**Server ID**: {{SERVER_ID}}", + "**Bot ID**: {{BOT_ID}}", + "**User ID**: {{USER_ID}}" + ] + } + ] + } + }, + "validationEmbeds": { + "cooldownHit": { + "description": "You can only run this command {{AMOUNT}} time(s) every {{INTERVAL}}. Please wait before attempting this command again.", + "color": "{{COM:colors.warning}}" + }, + "devOnly": { + "description": "This action can only be done by developers.", + "color": "{{COM:colors.warning}}" + }, + "missingClientPerms": { + "description": [ + "I don't have all permissions required to run that command here! Please check the server and channel permissions to make sure I have the following permissions.", + "", + "Required permissions: {{PERMISSIONS}}" + ], + "color": "{{COM:colors.warning}}" + } + }, + "errorEmbeds": { + "command": { + "description": "Something went wrong!", + "fields": [ + { + "name": "Error code", + "value": "{{ERROR_CODE}}" + }, + { + "name": "Server ID", + "value": "{{GUILD_ID}}" + }, + { + "name": "Shard ID", + "value": "{{SHARD_ID}}" + }, + { + "name": "Contact support", + "value": "{{COM:links.support}}" + } + ], + "color": "{{COM:colors.error}}" + }, + "startupInProcess": { + "description": "{{COM:bot.name}} is still starting up. Try again later.", + "color": "{{COM:colors.warning}}" + }, + "notImplemented": { + "description": "This feature has not been implemented yet!", + "color": "{{COM:colors.warning}}" + } + }, + "channelRegexes": { + "bot": "/bot|command|cmd/i" + } + }, + "refs": { + "meta": { + "translators": "[TranslatorName#1234](https://github.com/)" + }, + "bot": { + "description": "{{REF:links.templateEmbed}} helps give developers a starting point for new Discord bots, so that much of the initial setup can be avoided and developers can instead focus on meaningful bot features." + }, + "chatCommands": { + "dev": "dev", + "help": "help", + "info": "info", + "test": "test" + }, + "userCommands": { + "viewDateJoined": "View Date Joined" + }, + "messageCommands": { + "viewDateSent": "View Date Sent" + }, + "arguments": { + "command": "command", + "option": "option" + }, + "commandDescs": { + "dev": "Developer use only.", + "help": "Find help or contact support.", + "info": "View bot info.", + "test": "Run the test command." + }, + "argDescs": { + "devCommand": "Command.", + "helpOption": "Option.", + "infoOption": "Option." + }, + "fields": { + "commands": "Commands", + "links": "Links" + }, + "permissions": { + "AddReactions": "Add Reactions", + "Administrator": "Administrator", + "AttachFiles": "Attach Files", + "BanMembers": "Ban Members", + "ChangeNickname": "Change Nickname", + "Connect": "Connect", + "CreateInstantInvite": "Create Invite", + "CreatePrivateThreads": "Create Private Threads", + "CreatePublicThreads": "Create Public Threads", + "DeafenMembers": "Deafen Members", + "EmbedLinks": "Embed Links", + "KickMembers": "Kick Members", + "ManageChannels": "Manage Channel(s)", + "ManageEmojisAndStickers": "Manage Emoji and Stickers", + "ManageEvents": "Manage Events", + "ManageGuild": "Manage Server", + "ManageGuildExpressions": "Manage Expressions", + "ManageMessages": "Manage Messages", + "ManageNicknames": "Manage Nicknames", + "ManageRoles": "Manage Roles / Permissions", + "ManageThreads": "Manage Threads / Posts", + "ManageWebhooks": "Manage Webhooks", + "MentionEveryone": "Mention Everyone, Here, and All Roles", + "ModerateMembers": "Timeout Members", + "MoveMembers": "Move Members", + "MuteMembers": "Mute Members", + "PrioritySpeaker": "Priority Speaker", + "ReadMessageHistory": "Read Message History", + "RequestToSpeak": "Request to Speak", + "SendMessages": "Send Messages / Create Posts", + "SendMessagesInThreads": "Send Messages in Threads / Posts", + "SendTTSMessages": "Send Text-to-Speech Messages", + "SendVoiceMessages": "Send Voice Messages", + "Speak": "Speak", + "Stream": "Video", + "UseApplicationCommands": "Use Application Commands", + "UseEmbeddedActivities": "Use Activities", + "UseExternalEmojis": "Use External Emoji", + "UseExternalSounds": "Use External Sounds", + "UseExternalStickers": "Use External Stickers", + "UseSoundboard": "Use Soundboard", + "UseVAD": "Use Voice Activity", + "ViewAuditLog": "View Audit Log", + "ViewChannel": "View Channel(s)", + "ViewCreatorMonetizationAnalytics": "View Server Subscription Insights", + "ViewGuildInsights": "View Server Insights" + }, + "devCommandNames": { + "info": "info" + }, + "helpOptions": { + "contactSupport": "Contact Support", + "commands": "Commands" + }, + "helpOptionDescs": { + "contactSupport": "❓ {{REF:helpOptions.contactSupport}} ❓", + "commands": "{{REF:helpOptions.commands}} -- What commands are there? How do I restrict who is allowed to use commands?" + }, + "infoOptions": { + "about": "About", + "translate": "Translate" + }, + "yesNo": { + "yes": "Yes", + "no": "No" + }, + "boolean": { + "true": "True", + "false": "False" + }, + "other": { + "na": "N/A" + }, + "links": { + "authorEmbed": "[{{COM:bot.author}}]({{COM:links.author}})", + "docsEmbed": "[View Documentation]({{COM:links.docs}})", + "donateEmbed": "[Donate via PayPal]({{COM:links.donate}})", + "inviteEmbed": "[Invite {{COM:bot.name}} to a Server!]({{COM:links.invite}})", + "sourceEmbed": "[View Source Code]({{COM:links.source}})", + "supportEmbed": "[Join Support Server]({{COM:links.support}})", + "templateEmbed": "[Discord Bot TypeScript Template]({{COM:links.template}})", + "termsEmbed": "[View Terms of Service]({{COM:links.terms}})", + "voteEmbed": "[Vote for {{COM:bot.name}}!]({{COM:links.vote}})" + } + } +} diff --git a/lang/logs.json b/lang/logs.json new file mode 100644 index 0000000..ad95953 --- /dev/null +++ b/lang/logs.json @@ -0,0 +1,60 @@ +{ + "info": { + "appStarted": "Application started.", + "apiStarted": "API started on port {PORT}.", + "commandActionView": "\nLocal and remote:\n {LOCAL_AND_REMOTE_LIST}\nLocal only:\n {LOCAL_ONLY_LIST}\nRemote only:\n {REMOTE_ONLY_LIST}", + "commandActionCreating": "Creating commands: {COMMAND_LIST}", + "commandActionCreated": "Commands created.", + "commandActionUpdating": "Updating commands: {COMMAND_LIST}", + "commandActionUpdated": "Commands updated.", + "commandActionRenaming": "Renaming command: '{OLD_COMMAND_NAME}' --> '{NEW_COMMAND_NAME}'", + "commandActionRenamed": "Command renamed.", + "commandActionDeleting": "Deleting command: '{COMMAND_NAME}'", + "commandActionDeleted": "Command deleted.", + "commandActionClearing": "Deleting all commands: {COMMAND_LIST}", + "commandActionCleared": "Commands deleted.", + "managerSpawningShards": "Spawning {SHARD_COUNT} shards: [{SHARD_LIST}].", + "managerLaunchedShard": "Launched Shard {SHARD_ID}.", + "managerAllShardsSpawned": "All shards have been spawned.", + "clientLogin": "Client logged in as '{USER_TAG}'.", + "clientReady": "Client is ready!", + "jobScheduled": "Scheduled job '{JOB}' for '{SCHEDULE}'.", + "jobRun": "Running job '{JOB}'.", + "jobCompleted": "Job '{JOB}' completed.", + "updatedServerCount": "Updated server count. Connected to {SERVER_COUNT} total servers.", + "updatedServerCountSite": "Updated server count on '{BOT_SITE}'.", + "guildJoined": "Guild '{GUILD_NAME}' ({GUILD_ID}) joined.", + "guildLeft": "Guild '{GUILD_NAME}' ({GUILD_ID}) left." + }, + "warn": { + "managerNoShards": "No shards to spawn." + }, + "error": { + "unspecified": "An unspecified error occurred.", + "unhandledRejection": "An unhandled promise rejection occurred.", + "retrieveShards": "An error occurred while retrieving which shards to spawn.", + "managerSpawningShards": "An error occurred while spawning shards.", + "managerShardInfo": "An error occurred while retrieving shard info.", + "commandAction": "An error occurred while running a command action.", + "commandActionNotFound": "Could not find a command with the name '{COMMAND_NAME}'.", + "commandActionRenameMissingArg": "Please supply the current command name and new command name.", + "commandActionDeleteMissingArg": "Please supply a command name to delete.", + "clientLogin": "An error occurred while the client attempted to login.", + "job": "An error occurred while running the '{JOB}' job.", + "updatedServerCountSite": "An error occurred while updating the server count on '{BOT_SITE}'.", + "guildJoin": "An error occurred while processing a guild join.", + "guildLeave": "An error occurred while processing a guild leave.", + "message": "An error occurred while processing a message.", + "reaction": "An error occurred while processing a reaction.", + "command": "An error occurred while processing a command interaction.", + "button": "An error occurred while processing a button interaction.", + "commandNotFound": "[{INTERACTION_ID}] A command with the name '{COMMAND_NAME}' could not be found.", + "autocompleteNotFound": "[{INTERACTION_ID}] An autocomplete method for the '{COMMAND_NAME}' command could not be found.", + "commandGuild": "[{INTERACTION_ID}] An error occurred while executing the '{COMMAND_NAME}' command for user '{USER_TAG}' ({USER_ID}) in channel '{CHANNEL_NAME}' ({CHANNEL_ID}) in guild '{GUILD_NAME}' ({GUILD_ID}).", + "autocompleteGuild": "[{INTERACTION_ID}] An error occurred while autocompleting the '{OPTION_NAME}' option for the '{COMMAND_NAME}' command for user '{USER_TAG}' ({USER_ID}) in channel '{CHANNEL_NAME}' ({CHANNEL_ID}) in guild '{GUILD_NAME}' ({GUILD_ID}).", + "commandOther": "[{INTERACTION_ID}] An error occurred while executing the '{COMMAND_NAME}' command for user '{USER_TAG}' ({USER_ID}).", + "autocompleteOther": "[{INTERACTION_ID}] An error occurred while autocompleting the '{OPTION_NAME}' option for the '{COMMAND_NAME}' command for user '{USER_TAG}' ({USER_ID}).", + "apiRequest": "An error occurred while processing a '{HTTP_METHOD}' request to '{URL}'.", + "apiRateLimit": "A rate limit was hit while making a request." + } +} diff --git a/misc/Discord Bot Cluster API.postman_collection.json b/misc/Discord Bot Cluster API.postman_collection.json new file mode 100644 index 0000000..026461d --- /dev/null +++ b/misc/Discord Bot Cluster API.postman_collection.json @@ -0,0 +1,135 @@ +{ + "info": { + "_postman_id": "d37e9bae-0a24-4940-af63-2716ab3bb660", + "name": "Discord Bot Cluster API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Shards", + "item": [ + { + "name": "Get Shards", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/shards", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "shards" + ] + } + }, + "response": [] + }, + { + "name": "Set Shard Presences", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"type\": \"STREAMING\",\r\n \"name\": \"to 1,000,000 servers\",\r\n \"url\": \"https://www.twitch.tv/novakevin\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/shards/presence", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "shards", + "presence" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Guilds", + "item": [ + { + "name": "Get Guilds", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/guilds", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "guilds" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Get Root", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}", + "host": [ + "{{BASE_URL}}" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "key", + "value": "Authorization", + "type": "string" + }, + { + "key": "value", + "value": "00000000-0000-0000-0000-000000000000", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "BASE_URL", + "value": "localhost:3001" + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a43a489 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5989 @@ +{ + "name": "my-bot", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "my-bot", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@discordjs/rest": "2.0.1", + "class-transformer": "0.5.1", + "class-validator": "0.14.0", + "cron-parser": "^4.9.0", + "discord.js": "14.13.0", + "discord.js-rate-limiter": "1.3.2", + "express": "4.18.2", + "express-promise-router": "4.1.1", + "filesize": "10.0.12", + "linguini": "1.3.1", + "luxon": "3.4.0", + "node-fetch": "3.3.2", + "node-schedule": "2.1.1", + "pino": "8.15.0", + "pino-pretty": "10.2.0", + "pm2": "^5.3.0", + "reflect-metadata": "^0.1.13", + "remove-markdown": "0.5.0" + }, + "devDependencies": { + "@types/express": "4.17.17", + "@types/luxon": "3.3.1", + "@types/node": "^20.5.0", + "@types/node-schedule": "2.1.0", + "@types/remove-markdown": "0.3.1", + "@typescript-eslint/eslint-plugin": "^6.4.0", + "@typescript-eslint/parser": "^6.4.0", + "eslint": "^8.47.0", + "eslint-plugin-import": "^2.28.0", + "eslint-plugin-unicorn": "^48.0.1", + "prettier": "^3.0.2", + "typescript": "^5.1.6" + }, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", + "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.10", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", + "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.5.tgz", + "integrity": "sha512-SdweyCs/+mHj+PNhGLLle7RrRFX9ZAhzynHahMCLqp5Zeq7np7XC6/mgzHc79QoVlQ1zZtOkTTiJpOZu5V8Ufg==", + "dependencies": { + "@discordjs/formatters": "^0.3.2", + "@discordjs/util": "^1.0.1", + "@sapphire/shapeshift": "^3.9.2", + "discord-api-types": "0.37.50", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.3", + "tslib": "^2.6.1" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.2.tgz", + "integrity": "sha512-lE++JZK8LSSDRM5nLjhuvWhGuKiXqu+JZ/DsOR89DVVia3z9fdCJVcHF2W/1Zxgq0re7kCzmAJlCMMX3tetKpA==", + "dependencies": { + "discord-api-types": "0.37.50" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.0.1.tgz", + "integrity": "sha512-/eWAdDRvwX/rIE2tuQUmKaxmWeHmGealttIzGzlYfI4+a7y9b6ZoMp8BG/jaohs8D8iEnCNYaZiOFLVFLQb8Zg==", + "dependencies": { + "@discordjs/collection": "^1.5.3", + "@discordjs/util": "^1.0.1", + "@sapphire/async-queue": "^1.5.0", + "@sapphire/snowflake": "^3.5.1", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.50", + "magic-bytes.js": "^1.0.15", + "tslib": "^2.6.1", + "undici": "5.22.1" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.1.tgz", + "integrity": "sha512-d0N2yCxB8r4bn00/hvFZwM7goDcUhtViC5un4hPj73Ba4yrChLSJD8fy7Ps5jpTLg1fE9n4K0xBLc1y9WGwSsA==", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.1.tgz", + "integrity": "sha512-avvAolBqN3yrSvdBPcJ/0j2g42ABzrv3PEL76e3YTp2WYMGH7cuspkjfSyNWaqYl1J+669dlLp+YFMxSVQyS5g==", + "dependencies": { + "@discordjs/collection": "^1.5.3", + "@discordjs/rest": "^2.0.1", + "@discordjs/util": "^1.0.1", + "@sapphire/async-queue": "^1.5.0", + "@types/ws": "^8.5.5", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.50", + "tslib": "^2.6.1", + "ws": "^8.13.0" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz", + "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opencensus/core": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.9.tgz", + "integrity": "sha512-31Q4VWtbzXpVUd2m9JS6HEaPjlKvNMOiF7lWKNmXF84yUcgfAFL5re7/hjDmdyQbOp32oGc+RFV78jXIldVz6Q==", + "dependencies": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^5.5.0", + "shimmer": "^1.2.0", + "uuid": "^3.2.1" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@opencensus/core/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@opencensus/propagation-b3": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@opencensus/propagation-b3/-/propagation-b3-0.0.8.tgz", + "integrity": "sha512-PffXX2AL8Sh0VHQ52jJC4u3T0H6wDK6N/4bg7xh4ngMYOIi13aR1kzVvX1sVDBgfGwDOkMbl4c54Xm3tlPx/+A==", + "dependencies": { + "@opencensus/core": "^0.0.8", + "uuid": "^3.2.1" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@opencensus/propagation-b3/node_modules/@opencensus/core": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.8.tgz", + "integrity": "sha512-yUFT59SFhGMYQgX0PhoTR0LBff2BEhPrD9io1jWfF/VDbakRfs6Pq60rjv0Z7iaTav5gQlttJCX2+VPxFWCuoQ==", + "dependencies": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^5.5.0", + "shimmer": "^1.2.0", + "uuid": "^3.2.1" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@opencensus/propagation-b3/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@pm2/agent": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.3.tgz", + "integrity": "sha512-xkqqCoTf5VsciMqN0vb9jthW7olVAi4KRFNddCc7ZkeJZ3i8QwZANr4NSH2H5DvseRFHq7MiPspRY/EWAFWWTg==", + "dependencies": { + "async": "~3.2.0", + "chalk": "~3.0.0", + "dayjs": "~1.8.24", + "debug": "~4.3.1", + "eventemitter2": "~5.0.1", + "fast-json-patch": "^3.0.0-1", + "fclone": "~1.0.11", + "nssocket": "0.6.0", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.0", + "proxy-agent": "~6.3.0", + "semver": "~7.5.0", + "ws": "~7.4.0" + } + }, + "node_modules/@pm2/agent/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@pm2/agent/node_modules/dayjs": { + "version": "1.8.36", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", + "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==" + }, + "node_modules/@pm2/agent/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@pm2/io": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@pm2/io/-/io-5.0.0.tgz", + "integrity": "sha512-3rToDVJaRoob5Lq8+7Q2TZFruoEkdORxwzFpZaqF4bmH6Bkd7kAbdPrI/z8X6k1Meq5rTtScM7MmDgppH6aLlw==", + "dependencies": { + "@opencensus/core": "0.0.9", + "@opencensus/propagation-b3": "0.0.8", + "async": "~2.6.1", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "require-in-the-middle": "^5.0.0", + "semver": "6.3.0", + "shimmer": "^1.2.0", + "signal-exit": "^3.0.3", + "tslib": "1.9.3" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@pm2/io/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/io/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" + }, + "node_modules/@pm2/io/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@pm2/io/node_modules/tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "node_modules/@pm2/js-api": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.6.7.tgz", + "integrity": "sha512-jiJUhbdsK+5C4zhPZNnyA3wRI01dEc6a2GhcQ9qI38DyIk+S+C8iC3fGjcjUbt/viLYKPjlAaE+hcT2/JMQPXw==", + "dependencies": { + "async": "^2.6.3", + "axios": "^0.21.0", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "ws": "^7.0.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@pm2/js-api/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/js-api/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" + }, + "node_modules/@pm2/js-api/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@pm2/pm2-version-check": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", + "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "dependencies": { + "debug": "^4.3.1" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz", + "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.2.tgz", + "integrity": "sha512-YRbCXWy969oGIdqR/wha62eX8GNHsvyYi0Rfd4rNW6tSVVa8p0ELiMEuOH/k8rgtvRoM+EMV7Csqz77YdwiDpA==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", + "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "devOptional": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "devOptional": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "devOptional": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "devOptional": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/luxon": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.1.tgz", + "integrity": "sha512-XOS5nBcgEeP2PpcqJHjCWhUCAzGfXIU8ILOSLpx2FhxqMW9KdxgCGXNOEKGVBfveKtIpztHzKK5vSRVLyW/NqA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "devOptional": true + }, + "node_modules/@types/node": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.0.tgz", + "integrity": "sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q==" + }, + "node_modules/@types/node-schedule": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.0.tgz", + "integrity": "sha512-NiTwl8YN3v/1YCKrDFSmCTkVxFDylueEqsOFdgF+vPsm+AlyJKGAo5yzX1FiOxPsZiN6/r8gJitYx2EaSuBmmg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "devOptional": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "devOptional": true + }, + "node_modules/@types/remove-markdown": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@types/remove-markdown/-/remove-markdown-0.3.1.tgz", + "integrity": "sha512-JpJNEJEsmmltyL2LdE8KRjJ0L2ad5vgLibqNj85clohT9AyTrfN6jvHxStPshDkmtcL/ShFu0p2tbY7DBS1mqQ==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "devOptional": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "devOptional": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/validator": { + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.1.tgz", + "integrity": "sha512-d/MUkJYdOeKycmm75Arql4M5+UuXmf4cHdHKsyw1GcvnNgL6s77UkgSgJ8TE/rI5PYsnwYq5jkcWBLuN/MpQ1A==" + }, + "node_modules/@types/ws": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.0.tgz", + "integrity": "sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.4.0", + "@typescript-eslint/type-utils": "6.4.0", + "@typescript-eslint/utils": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", + "integrity": "sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.4.0", + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/typescript-estree": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.0.tgz", + "integrity": "sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.4.0.tgz", + "integrity": "sha512-TvqrUFFyGY0cX3WgDHcdl2/mMCWCDv/0thTtx/ODMY1QhEiyFtv/OlLaNIiYLwRpAxAtOLOY9SUf1H3Q3dlwAg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.4.0", + "@typescript-eslint/utils": "6.4.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.4.0.tgz", + "integrity": "sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.0.tgz", + "integrity": "sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.4.0.tgz", + "integrity": "sha512-BvvwryBQpECPGo8PwF/y/q+yacg8Hn/2XS+DqL/oRsOPK+RPt29h5Ui5dqOKHDlbXrAeHUTnyG3wZA0KTDxRZw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.4.0", + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/typescript-estree": "6.4.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.0.tgz", + "integrity": "sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.4.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.2.tgz", + "integrity": "sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/amp": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", + "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==" + }, + "node_modules/amp-message": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", + "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", + "dependencies": { + "amp": "0.3.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", + "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/async-listener": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", + "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", + "dependencies": { + "semver": "^5.3.0", + "shimmer": "^1.1.0" + }, + "engines": { + "node": "<=0.11.8 || >0.11.10" + } + }, + "node_modules/async-listener/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", + "bin": { + "blessed": "bin/tput.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/bodec": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", + "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==" + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", + "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "dependencies": { + "@types/validator": "^13.7.10", + "libphonenumber-js": "^1.10.14", + "validator": "^13.7.0" + } + }, + "node_modules/clean-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clean-regexp/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cli-tableau": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", + "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", + "dependencies": { + "chalk": "3.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/cli-tableau/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/continuation-local-storage": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", + "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", + "dependencies": { + "async-listener": "^0.6.0", + "emitter-listener": "^1.1.1" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/croner": { + "version": "4.1.97", + "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", + "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/culvert": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", + "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "node_modules/dayjs": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/discord-api-types": { + "version": "0.37.50", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.50.tgz", + "integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==" + }, + "node_modules/discord.js": { + "version": "14.13.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.13.0.tgz", + "integrity": "sha512-Kufdvg7fpyTEwANGy9x7i4od4yu5c6gVddGi5CKm4Y5a6sF0VBODObI3o0Bh7TGCj0LfNT8Qp8z04wnLFzgnbA==", + "dependencies": { + "@discordjs/builders": "^1.6.5", + "@discordjs/collection": "^1.5.3", + "@discordjs/formatters": "^0.3.2", + "@discordjs/rest": "^2.0.1", + "@discordjs/util": "^1.0.1", + "@discordjs/ws": "^1.0.1", + "@sapphire/snowflake": "^3.5.1", + "@types/ws": "^8.5.5", + "discord-api-types": "0.37.50", + "fast-deep-equal": "^3.1.3", + "lodash.snakecase": "^4.1.1", + "tslib": "^2.6.1", + "undici": "5.22.1", + "ws": "^8.13.0" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/discord.js-rate-limiter": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/discord.js-rate-limiter/-/discord.js-rate-limiter-1.3.2.tgz", + "integrity": "sha512-gS0suMdhWKh4IJRf2NbzHGNJfYI9ApKhO5XsBLByT0qy5vbk6SiS/9FofQKoWgbx5XXOmQ8dwqQtzzUVlSh5ZA==", + "dependencies": { + "limiter": "2.1.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "discord.js": ">=12.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emitter-listener": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", + "dependencies": { + "shimmer": "^1.2.0" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "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-array-concat": "^1.0.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-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz", + "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "^8.47.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "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.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "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" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.0.tgz", + "integrity": "sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.8.0", + "has": "^1.0.3", + "is-core-module": "^2.12.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", + "object.values": "^1.1.6", + "resolve": "^1.22.3", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-unicorn": { + "version": "48.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-48.0.1.tgz", + "integrity": "sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "@eslint-community/eslint-utils": "^4.4.0", + "ci-info": "^3.8.0", + "clean-regexp": "^1.0.0", + "esquery": "^1.5.0", + "indent-string": "^4.0.0", + "is-builtin-module": "^3.2.1", + "jsesc": "^3.0.2", + "lodash": "^4.17.21", + "pluralize": "^8.0.0", + "read-pkg-up": "^7.0.1", + "regexp-tree": "^0.1.27", + "regjsparser": "^0.10.0", + "semver": "^7.5.4", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=8.44.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", + "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.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" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-promise-router": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/express-promise-router/-/express-promise-router-4.1.1.tgz", + "integrity": "sha512-Lkvcy/ZGrBhzkl3y7uYBHLMtLI4D6XQ2kiFg9dq7fbktBch5gjqJ0+KovX0cvCAvTJw92raWunRLM/OM+5l4fA==", + "dependencies": { + "is-promise": "^4.0.0", + "lodash.flattendeep": "^4.0.0", + "methods": "^1.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/express": "^4.0.0", + "express": "^4.0.0" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/fast-copy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz", + "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-redact": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", + "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fclone": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", + "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filesize": { + "version": "10.0.12", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.0.12.tgz", + "integrity": "sha512-6RS9gDchbn+qWmtV2uSjo5vmKizgfCQeb5jKmqx8HyzA3MoLqqyQxN+QcjkGBJt7FjJ9qFce67Auyya5rRRbpw==", + "engines": { + "node": ">= 10.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-uri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.1.tgz", + "integrity": "sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^5.0.1", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz", + "integrity": "sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/git-node-fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", + "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==" + }, + "node_modules/git-sha1": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", + "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==" + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/help-me": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz", + "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==", + "dependencies": { + "glob": "^8.0.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/help-me/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz", + "integrity": "sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-git": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", + "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", + "dependencies": { + "bodec": "^0.1.0", + "culvert": "^0.1.2", + "git-sha1": "^0.1.2", + "pako": "^0.2.5" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "optional": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/just-performance": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/just-performance/-/just-performance-4.3.0.tgz", + "integrity": "sha512-L7RjvtJsL0QO8xFs5wEoDDzzJwoiowRw6Rn/GnvldlchS2JQr9wFYPiwZcDfrbbujEKqKN0tvENdbjXdYhDp5Q==" + }, + "node_modules/lazy": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", + "integrity": "sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.10.41", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.41.tgz", + "integrity": "sha512-4rmmF4u4vD3eGNuuCGjCPwRwO+fIuu1WWcS7VwbPTiMFkJd8F02v8o5pY5tlYuMR+xOvJ88mtOHpkm0Tnu2LcQ==" + }, + "node_modules/limiter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-2.1.0.tgz", + "integrity": "sha512-361TYz6iay6n+9KvUUImqdLuFigK+K79qrUtBsXhJTLdH4rIt/r1y8r1iozwh8KbZNpujbFTSh74mJ7bwbAMOw==", + "dependencies": { + "just-performance": "4.3.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/linguini": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/linguini/-/linguini-1.3.1.tgz", + "integrity": "sha512-nGg/+fMQ4zQyEqDLaniETT31y1NQs8JhlCkbRhwuxTk8fKiUDFzflie58H0cHFObd2OMHKNncYSXf7HqWOgxxw==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "engines": { + "node": ">=0.8.6" + } + }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/luxon": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.0.tgz", + "integrity": "sha512-7eDo4Pt7aGhoCheGFIuq4Xa2fJm4ZpmldpGhjTYBNUYNCN6TIEP6v7chwwwt3KRp7YR+rghbfvjyo3V5y9hgBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-bytes.js": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.0.15.tgz", + "integrity": "sha512-bpRmwbRHqongRhA+mXzbLWjVy7ylqmfMBYaQkSs6pac0z6hBTvsgrH0r4FBYd/UYVJBmS6Rp/O+oCCQVLzKV1g==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nssocket": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/nssocket/-/nssocket-0.6.0.tgz", + "integrity": "sha512-a9GSOIql5IqgWJR3F/JXG4KpJTA3Z53Cj0MeMvGpglytB1nxE4PdFNC0jINe27CS7cGivoynwc054EzCcT3M3w==", + "dependencies": { + "eventemitter2": "~0.4.14", + "lazy": "~1.0.11" + }, + "engines": { + "node": ">= 0.10.x" + } + }, + "node_modules/nssocket/node_modules/eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==" + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", + "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.0.tgz", + "integrity": "sha512-t4tRAMx0uphnZrio0S0Jw9zg3oDbz1zVhQ/Vy18FjLfP1XOLNUEjaVxYCYRI6NS+BsMBXKIzV6cTLOkO9AtywA==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", + "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "dependencies": { + "degenerator": "^5.0.0", + "ip": "^1.1.8", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidusage": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", + "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pino": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.15.0.tgz", + "integrity": "sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.0.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.1.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", + "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.2.0.tgz", + "integrity": "sha512-tRvpyEmGtc2D+Lr3FulIZ+R1baggQ4S3xD2Ar93KixFEDx6SEAUP3W5aYuEw1C73d6ROrNcB2IXLteW8itlwhA==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^4.0.1", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pm2": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-5.3.0.tgz", + "integrity": "sha512-xscmQiAAf6ArVmKhjKTeeN8+Td7ZKnuZFFPw1DGkdFPR/0Iyx+m+1+OpCdf9+HQopX3VPc9/wqPQHqVOfHum9w==", + "dependencies": { + "@pm2/agent": "~2.0.0", + "@pm2/io": "~5.0.0", + "@pm2/js-api": "~0.6.7", + "@pm2/pm2-version-check": "latest", + "async": "~3.2.0", + "blessed": "0.1.81", + "chalk": "3.0.0", + "chokidar": "^3.5.3", + "cli-tableau": "^2.0.0", + "commander": "2.15.1", + "croner": "~4.1.92", + "dayjs": "~1.11.5", + "debug": "^4.3.1", + "enquirer": "2.3.6", + "eventemitter2": "5.0.1", + "fclone": "1.0.11", + "mkdirp": "1.0.4", + "needle": "2.4.0", + "pidusage": "~3.0", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.1", + "pm2-deploy": "~1.0.2", + "pm2-multimeter": "^0.1.2", + "promptly": "^2", + "semver": "^7.2", + "source-map-support": "0.5.21", + "sprintf-js": "1.1.2", + "vizion": "~2.2.1", + "yamljs": "0.3.0" + }, + "bin": { + "pm2": "bin/pm2", + "pm2-dev": "bin/pm2-dev", + "pm2-docker": "bin/pm2-docker", + "pm2-runtime": "bin/pm2-runtime" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "pm2-sysmonit": "^1.2.8" + } + }, + "node_modules/pm2-axon": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", + "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", + "dependencies": { + "amp": "~0.3.1", + "amp-message": "~0.1.1", + "debug": "^4.3.1", + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=5" + } + }, + "node_modules/pm2-axon-rpc": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", + "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", + "dependencies": { + "debug": "^4.3.1" + }, + "engines": { + "node": ">=5" + } + }, + "node_modules/pm2-deploy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", + "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", + "dependencies": { + "run-series": "^1.1.8", + "tv4": "^1.3.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pm2-multimeter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", + "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", + "dependencies": { + "charm": "~0.1.1" + } + }, + "node_modules/pm2-sysmonit": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", + "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", + "optional": true, + "dependencies": { + "async": "^3.2.0", + "debug": "^4.3.1", + "pidusage": "^2.0.21", + "systeminformation": "^5.7", + "tx2": "~1.0.4" + } + }, + "node_modules/pm2-sysmonit/node_modules/pidusage": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", + "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", + "optional": true, + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pm2/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz", + "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz", + "integrity": "sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==" + }, + "node_modules/promptly": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", + "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", + "dependencies": { + "read": "^1.0.4" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regjsparser": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", + "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/remove-markdown": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.5.0.tgz", + "integrity": "sha512-x917M80K97K5IN1L8lUvFehsfhR8cYjGQ/yAMRI9E7JIKivtl5Emo5iD13DhMr+VojzMCiYk8V2byNPwT/oapg==" + }, + "node_modules/require-in-the-middle": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", + "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", + "dependencies": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-series": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz", + "integrity": "sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==", + "dependencies": { + "agent-base": "^7.0.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "node_modules/sonic-boom": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz", + "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/systeminformation": { + "version": "5.18.15", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.18.15.tgz", + "integrity": "sha512-IS7UFVYDC7kILt/C1I5qYwxddC849uJidzR+56bv/RdpU6deOwXvXa5EgFaRP18TCPBULQj/zrri5++fXC9EGg==", + "optional": true, + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thread-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.0.tgz", + "integrity": "sha512-xZYtOtmnA63zj04Q+F9bdEay5r47bvpo1CaNqsKi7TpoJHcotUez8Fkfo2RJWpW91lnnaApdpRbVwCWsy+ifcw==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", + "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", + "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + }, + "node_modules/tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tx2": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", + "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", + "optional": true, + "dependencies": { + "json-stringify-safe": "^5.0.1" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vizion": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", + "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", + "dependencies": { + "async": "^2.6.3", + "git-node-fs": "^1.0.0", + "ini": "^1.3.5", + "js-git": "^0.7.8" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/vizion/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "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 + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "dependencies": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + }, + "bin": { + "json2yaml": "bin/json2yaml", + "yaml2json": "bin/yaml2json" + } + }, + "node_modules/yamljs/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/yamljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/yamljs/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c270e79 --- /dev/null +++ b/package.json @@ -0,0 +1,71 @@ +{ + "name": "my-bot", + "version": "1.0.0", + "author": "Kevin Novak", + "description": "A discord.js bot template written with TypeScript", + "license": "MIT", + "private": true, + "engines": { + "node": ">=16.9.0" + }, + "type": "module", + "exports": [ + "./dist/start-bot.js", + "./dist/start-manager.js" + ], + "scripts": { + "lint": "eslint . --cache --ext .js,.jsx,.ts,.tsx", + "lint:fix": "eslint . --fix --cache --ext .js,.jsx,.ts,.tsx", + "format": "prettier --check .", + "format:fix": "prettier --write .", + "clean": "git clean -xdf --exclude=\"/config/**/*\"", + "clean:dry": "git clean -xdf --exclude=\"/config/**/*\" --dry-run", + "build": "tsc --project tsconfig.json", + "commands:view": "npm run build && node --enable-source-maps dist/start-bot.js commands view", + "commands:register": "npm run build && node --enable-source-maps dist/start-bot.js commands register", + "commands:rename": "npm run build && node --enable-source-maps dist/start-bot.js commands rename", + "commands:delete": "npm run build && node --enable-source-maps dist/start-bot.js commands delete", + "commands:clear": "npm run build && node --enable-source-maps dist/start-bot.js commands clear", + "start": "npm run start:bot", + "start:bot": "npm run build && node --enable-source-maps dist/start-bot.js", + "start:manager": "npm run build && node --enable-source-maps dist/start-manager.js", + "start:pm2": "npm run build && npm run pm2:start", + "pm2:start": "pm2 start process.json", + "pm2:stop": "pm2 stop process.json", + "pm2:delete": "pm2 delete process.json" + }, + "dependencies": { + "@discordjs/rest": "2.0.1", + "class-transformer": "0.5.1", + "class-validator": "0.14.0", + "cron-parser": "^4.9.0", + "discord.js": "14.13.0", + "discord.js-rate-limiter": "1.3.2", + "express": "4.18.2", + "express-promise-router": "4.1.1", + "filesize": "10.0.12", + "linguini": "1.3.1", + "luxon": "3.4.0", + "node-fetch": "3.3.2", + "node-schedule": "2.1.1", + "pino": "8.15.0", + "pino-pretty": "10.2.0", + "pm2": "^5.3.0", + "reflect-metadata": "^0.1.13", + "remove-markdown": "0.5.0" + }, + "devDependencies": { + "@types/express": "4.17.17", + "@types/luxon": "3.3.1", + "@types/node": "^20.5.0", + "@types/node-schedule": "2.1.0", + "@types/remove-markdown": "0.3.1", + "@typescript-eslint/eslint-plugin": "^6.4.0", + "@typescript-eslint/parser": "^6.4.0", + "eslint": "^8.47.0", + "eslint-plugin-import": "^2.28.0", + "eslint-plugin-unicorn": "^48.0.1", + "prettier": "^3.0.2", + "typescript": "^5.1.6" + } +} diff --git a/process.json b/process.json new file mode 100644 index 0000000..1e1af06 --- /dev/null +++ b/process.json @@ -0,0 +1,10 @@ +{ + "apps": [ + { + "name": "my-bot", + "script": "dist/start-manager.js", + "node_args": ["--enable-source-maps"], + "restart_delay": 10000 + } + ] +} diff --git a/src/buttons/button.ts b/src/buttons/button.ts new file mode 100644 index 0000000..e638cc7 --- /dev/null +++ b/src/buttons/button.ts @@ -0,0 +1,17 @@ +import { ButtonInteraction } from 'discord.js'; + +import { EventData } from '../models/internal-models.js'; + +export interface Button { + ids: string[]; + deferType: ButtonDeferType; + requireGuild: boolean; + requireEmbedAuthorTag: boolean; + execute(intr: ButtonInteraction, data: EventData): Promise; +} + +export enum ButtonDeferType { + REPLY = 'REPLY', + UPDATE = 'UPDATE', + NONE = 'NONE', +} diff --git a/src/buttons/index.ts b/src/buttons/index.ts new file mode 100644 index 0000000..3067c6d --- /dev/null +++ b/src/buttons/index.ts @@ -0,0 +1 @@ +export { Button, ButtonDeferType } from './button.js'; diff --git a/src/commands/args.ts b/src/commands/args.ts new file mode 100644 index 0000000..14b0d75 --- /dev/null +++ b/src/commands/args.ts @@ -0,0 +1,60 @@ +import { APIApplicationCommandBasicOption, ApplicationCommandOptionType } from 'discord.js'; + +import { DevCommandName, HelpOption, InfoOption } from '../enums/index.js'; +import { Language } from '../models/enum-helpers/index.js'; +import { Lang } from '../services/index.js'; + +export class Args { + public static readonly DEV_COMMAND: APIApplicationCommandBasicOption = { + name: Lang.getRef('arguments.command', Language.Default), + name_localizations: Lang.getRefLocalizationMap('arguments.command'), + description: Lang.getRef('argDescs.devCommand', Language.Default), + description_localizations: Lang.getRefLocalizationMap('argDescs.devCommand'), + type: ApplicationCommandOptionType.String, + choices: [ + { + name: Lang.getRef('devCommandNames.info', Language.Default), + name_localizations: Lang.getRefLocalizationMap('devCommandNames.info'), + value: DevCommandName.INFO, + }, + ], + }; + public static readonly HELP_OPTION: APIApplicationCommandBasicOption = { + name: Lang.getRef('arguments.option', Language.Default), + name_localizations: Lang.getRefLocalizationMap('arguments.option'), + description: Lang.getRef('argDescs.helpOption', Language.Default), + description_localizations: Lang.getRefLocalizationMap('argDescs.helpOption'), + type: ApplicationCommandOptionType.String, + choices: [ + { + name: Lang.getRef('helpOptionDescs.contactSupport', Language.Default), + name_localizations: Lang.getRefLocalizationMap('helpOptionDescs.contactSupport'), + value: HelpOption.CONTACT_SUPPORT, + }, + { + name: Lang.getRef('helpOptionDescs.commands', Language.Default), + name_localizations: Lang.getRefLocalizationMap('helpOptionDescs.commands'), + value: HelpOption.COMMANDS, + }, + ], + }; + public static readonly INFO_OPTION: APIApplicationCommandBasicOption = { + name: Lang.getRef('arguments.option', Language.Default), + name_localizations: Lang.getRefLocalizationMap('arguments.option'), + description: Lang.getRef('argDescs.helpOption', Language.Default), + description_localizations: Lang.getRefLocalizationMap('argDescs.helpOption'), + type: ApplicationCommandOptionType.String, + choices: [ + { + name: Lang.getRef('infoOptions.about', Language.Default), + name_localizations: Lang.getRefLocalizationMap('infoOptions.about'), + value: InfoOption.ABOUT, + }, + { + name: Lang.getRef('infoOptions.translate', Language.Default), + name_localizations: Lang.getRefLocalizationMap('infoOptions.translate'), + value: InfoOption.TRANSLATE, + }, + ], + }; +} diff --git a/src/commands/chat/dev-command.ts b/src/commands/chat/dev-command.ts new file mode 100644 index 0000000..b5477bb --- /dev/null +++ b/src/commands/chat/dev-command.ts @@ -0,0 +1,98 @@ +import djs, { ChatInputCommandInteraction, PermissionsString } from 'discord.js'; +import { createRequire } from 'node:module'; +import os from 'node:os'; +import typescript from 'typescript'; + +import { DevCommandName } from '../../enums/index.js'; +import { Language } from '../../models/enum-helpers/index.js'; +import { EventData } from '../../models/internal-models.js'; +import { Lang } from '../../services/index.js'; +import { FormatUtils, InteractionUtils, ShardUtils } from '../../utils/index.js'; +import { Command, CommandDeferType } from '../index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../../config/config.json'); +let TsConfig = require('../../../tsconfig.json'); + +export class DevCommand implements Command { + public names = [Lang.getRef('chatCommands.dev', Language.Default)]; + public deferType = CommandDeferType.HIDDEN; + public requireClientPerms: PermissionsString[] = []; + public async execute(intr: ChatInputCommandInteraction, data: EventData): Promise { + if (!Config.developers.includes(intr.user.id)) { + await InteractionUtils.send(intr, Lang.getEmbed('validationEmbeds.devOnly', data.lang)); + return; + } + + let args = { + command: intr.options.getString( + Lang.getRef('arguments.command', Language.Default) + ) as DevCommandName, + }; + + switch (args.command) { + case DevCommandName.INFO: { + let shardCount = intr.client.shard?.count ?? 1; + let serverCount: number; + if (intr.client.shard) { + try { + serverCount = await ShardUtils.serverCount(intr.client.shard); + } catch (error) { + if (error.name.includes('ShardingInProcess')) { + await InteractionUtils.send( + intr, + Lang.getEmbed('errorEmbeds.startupInProcess', data.lang) + ); + return; + } else { + throw error; + } + } + } else { + serverCount = intr.client.guilds.cache.size; + } + + let memory = process.memoryUsage(); + + await InteractionUtils.send( + intr, + Lang.getEmbed('displayEmbeds.devInfo', data.lang, { + NODE_VERSION: process.version, + TS_VERSION: `v${typescript.version}`, + ES_VERSION: TsConfig.compilerOptions.target, + DJS_VERSION: `v${djs.version}`, + SHARD_COUNT: shardCount.toLocaleString(data.lang), + SERVER_COUNT: serverCount.toLocaleString(data.lang), + SERVER_COUNT_PER_SHARD: Math.round(serverCount / shardCount).toLocaleString( + data.lang + ), + RSS_SIZE: FormatUtils.fileSize(memory.rss), + RSS_SIZE_PER_SERVER: + serverCount > 0 + ? FormatUtils.fileSize(memory.rss / serverCount) + : Lang.getRef('other.na', data.lang), + HEAP_TOTAL_SIZE: FormatUtils.fileSize(memory.heapTotal), + HEAP_TOTAL_SIZE_PER_SERVER: + serverCount > 0 + ? FormatUtils.fileSize(memory.heapTotal / serverCount) + : Lang.getRef('other.na', data.lang), + HEAP_USED_SIZE: FormatUtils.fileSize(memory.heapUsed), + HEAP_USED_SIZE_PER_SERVER: + serverCount > 0 + ? FormatUtils.fileSize(memory.heapUsed / serverCount) + : Lang.getRef('other.na', data.lang), + HOSTNAME: os.hostname(), + SHARD_ID: (intr.guild?.shardId ?? 0).toString(), + SERVER_ID: intr.guild?.id ?? Lang.getRef('other.na', data.lang), + BOT_ID: intr.client.user?.id, + USER_ID: intr.user.id, + }) + ); + break; + } + default: { + return; + } + } + } +} diff --git a/src/commands/chat/help-command.ts b/src/commands/chat/help-command.ts new file mode 100644 index 0000000..9fb05b8 --- /dev/null +++ b/src/commands/chat/help-command.ts @@ -0,0 +1,51 @@ +import { ChatInputCommandInteraction, EmbedBuilder, PermissionsString } from 'discord.js'; + +import { HelpOption } from '../../enums/index.js'; +import { Language } from '../../models/enum-helpers/index.js'; +import { EventData } from '../../models/internal-models.js'; +import { Lang } from '../../services/index.js'; +import { ClientUtils, FormatUtils, InteractionUtils } from '../../utils/index.js'; +import { Command, CommandDeferType } from '../index.js'; + +export class HelpCommand implements Command { + public names = [Lang.getRef('chatCommands.help', Language.Default)]; + public deferType = CommandDeferType.HIDDEN; + public requireClientPerms: PermissionsString[] = []; + public async execute(intr: ChatInputCommandInteraction, data: EventData): Promise { + let args = { + option: intr.options.getString( + Lang.getRef('arguments.option', Language.Default) + ) as HelpOption, + }; + + let embed: EmbedBuilder; + switch (args.option) { + case HelpOption.CONTACT_SUPPORT: { + embed = Lang.getEmbed('displayEmbeds.helpContactSupport', data.lang); + break; + } + case HelpOption.COMMANDS: { + embed = Lang.getEmbed('displayEmbeds.helpCommands', data.lang, { + CMD_LINK_TEST: FormatUtils.commandMention( + await ClientUtils.findAppCommand( + intr.client, + Lang.getRef('chatCommands.test', Language.Default) + ) + ), + CMD_LINK_INFO: FormatUtils.commandMention( + await ClientUtils.findAppCommand( + intr.client, + Lang.getRef('chatCommands.info', Language.Default) + ) + ), + }); + break; + } + default: { + return; + } + } + + await InteractionUtils.send(intr, embed); + } +} diff --git a/src/commands/chat/index.ts b/src/commands/chat/index.ts new file mode 100644 index 0000000..4061706 --- /dev/null +++ b/src/commands/chat/index.ts @@ -0,0 +1,4 @@ +export { DevCommand } from './dev-command.js'; +export { HelpCommand } from './help-command.js'; +export { InfoCommand } from './info-command.js'; +export { TestCommand } from './test-command.js'; diff --git a/src/commands/chat/info-command.ts b/src/commands/chat/info-command.ts new file mode 100644 index 0000000..b00d669 --- /dev/null +++ b/src/commands/chat/info-command.ts @@ -0,0 +1,47 @@ +import { ChatInputCommandInteraction, EmbedBuilder, PermissionsString } from 'discord.js'; + +import { InfoOption } from '../../enums/index.js'; +import { Language } from '../../models/enum-helpers/index.js'; +import { EventData } from '../../models/internal-models.js'; +import { Lang } from '../../services/index.js'; +import { InteractionUtils } from '../../utils/index.js'; +import { Command, CommandDeferType } from '../index.js'; + +export class InfoCommand implements Command { + public names = [Lang.getRef('chatCommands.info', Language.Default)]; + public deferType = CommandDeferType.HIDDEN; + public requireClientPerms: PermissionsString[] = []; + + public async execute(intr: ChatInputCommandInteraction, data: EventData): Promise { + let args = { + option: intr.options.getString( + Lang.getRef('arguments.option', Language.Default) + ) as InfoOption, + }; + + let embed: EmbedBuilder; + switch (args.option) { + case InfoOption.ABOUT: { + embed = Lang.getEmbed('displayEmbeds.about', data.lang); + break; + } + case InfoOption.TRANSLATE: { + embed = Lang.getEmbed('displayEmbeds.translate', data.lang); + for (let langCode of Language.Enabled) { + embed.addFields([ + { + name: Language.Data[langCode].nativeName, + value: Lang.getRef('meta.translators', langCode), + }, + ]); + } + break; + } + default: { + return; + } + } + + await InteractionUtils.send(intr, embed); + } +} diff --git a/src/commands/chat/test-command.ts b/src/commands/chat/test-command.ts new file mode 100644 index 0000000..33941e1 --- /dev/null +++ b/src/commands/chat/test-command.ts @@ -0,0 +1,19 @@ +import { ChatInputCommandInteraction, PermissionsString } from 'discord.js'; +import { RateLimiter } from 'discord.js-rate-limiter'; + +import { Language } from '../../models/enum-helpers/index.js'; +import { EventData } from '../../models/internal-models.js'; +import { Lang } from '../../services/index.js'; +import { InteractionUtils } from '../../utils/index.js'; +import { Command, CommandDeferType } from '../index.js'; + +export class TestCommand implements Command { + public names = [Lang.getRef('chatCommands.test', Language.Default)]; + public cooldown = new RateLimiter(1, 5000); + public deferType = CommandDeferType.HIDDEN; + public requireClientPerms: PermissionsString[] = []; + + public async execute(intr: ChatInputCommandInteraction, data: EventData): Promise { + await InteractionUtils.send(intr, Lang.getEmbed('displayEmbeds.test', data.lang)); + } +} diff --git a/src/commands/command.ts b/src/commands/command.ts new file mode 100644 index 0000000..9e4e900 --- /dev/null +++ b/src/commands/command.ts @@ -0,0 +1,28 @@ +import { + ApplicationCommandOptionChoiceData, + AutocompleteFocusedOption, + AutocompleteInteraction, + CommandInteraction, + PermissionsString, +} from 'discord.js'; +import { RateLimiter } from 'discord.js-rate-limiter'; + +import { EventData } from '../models/internal-models.js'; + +export interface Command { + names: string[]; + cooldown?: RateLimiter; + deferType: CommandDeferType; + requireClientPerms: PermissionsString[]; + autocomplete?( + intr: AutocompleteInteraction, + option: AutocompleteFocusedOption + ): Promise; + execute(intr: CommandInteraction, data: EventData): Promise; +} + +export enum CommandDeferType { + PUBLIC = 'PUBLIC', + HIDDEN = 'HIDDEN', + NONE = 'NONE', +} diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..00bcd51 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,3 @@ +export { Args } from './args.js'; +export { Command, CommandDeferType } from './command.js'; +export { ChatCommandMetadata, MessageCommandMetadata, UserCommandMetadata } from './metadata.js'; diff --git a/src/commands/message/index.ts b/src/commands/message/index.ts new file mode 100644 index 0000000..eac1a13 --- /dev/null +++ b/src/commands/message/index.ts @@ -0,0 +1 @@ +export { ViewDateSent } from './view-date-sent.js'; diff --git a/src/commands/message/view-date-sent.ts b/src/commands/message/view-date-sent.ts new file mode 100644 index 0000000..f621e57 --- /dev/null +++ b/src/commands/message/view-date-sent.ts @@ -0,0 +1,30 @@ +import { MessageContextMenuCommandInteraction, PermissionsString } from 'discord.js'; +import { RateLimiter } from 'discord.js-rate-limiter'; +import { DateTime } from 'luxon'; + +import { Language } from '../../models/enum-helpers/index.js'; +import { EventData } from '../../models/internal-models.js'; +import { Lang } from '../../services/index.js'; +import { InteractionUtils } from '../../utils/index.js'; +import { Command, CommandDeferType } from '../index.js'; + +export class ViewDateSent implements Command { + public names = [Lang.getRef('messageCommands.viewDateSent', Language.Default)]; + public cooldown = new RateLimiter(1, 5000); + public deferType = CommandDeferType.HIDDEN; + public requireClientPerms: PermissionsString[] = []; + + public async execute( + intr: MessageContextMenuCommandInteraction, + data: EventData + ): Promise { + await InteractionUtils.send( + intr, + Lang.getEmbed('displayEmbeds.viewDateSent', data.lang, { + DATE: DateTime.fromJSDate(intr.targetMessage.createdAt).toLocaleString( + DateTime.DATE_HUGE + ), + }) + ); + } +} diff --git a/src/commands/metadata.ts b/src/commands/metadata.ts new file mode 100644 index 0000000..b1ab2f5 --- /dev/null +++ b/src/commands/metadata.ts @@ -0,0 +1,96 @@ +import { + ApplicationCommandType, + PermissionFlagsBits, + PermissionsBitField, + RESTPostAPIChatInputApplicationCommandsJSONBody, + RESTPostAPIContextMenuApplicationCommandsJSONBody, +} from 'discord.js'; + +import { Args } from './index.js'; +import { Language } from '../models/enum-helpers/index.js'; +import { Lang } from '../services/index.js'; + +export const ChatCommandMetadata: { + [command: string]: RESTPostAPIChatInputApplicationCommandsJSONBody; +} = { + DEV: { + type: ApplicationCommandType.ChatInput, + name: Lang.getRef('chatCommands.dev', Language.Default), + name_localizations: Lang.getRefLocalizationMap('chatCommands.dev'), + description: Lang.getRef('commandDescs.dev', Language.Default), + description_localizations: Lang.getRefLocalizationMap('commandDescs.dev'), + dm_permission: true, + default_member_permissions: PermissionsBitField.resolve([ + PermissionFlagsBits.Administrator, + ]).toString(), + options: [ + { + ...Args.DEV_COMMAND, + required: true, + }, + ], + }, + HELP: { + type: ApplicationCommandType.ChatInput, + name: Lang.getRef('chatCommands.help', Language.Default), + name_localizations: Lang.getRefLocalizationMap('chatCommands.help'), + description: Lang.getRef('commandDescs.help', Language.Default), + description_localizations: Lang.getRefLocalizationMap('commandDescs.help'), + dm_permission: true, + default_member_permissions: undefined, + options: [ + { + ...Args.HELP_OPTION, + required: true, + }, + ], + }, + INFO: { + type: ApplicationCommandType.ChatInput, + name: Lang.getRef('chatCommands.info', Language.Default), + name_localizations: Lang.getRefLocalizationMap('chatCommands.info'), + description: Lang.getRef('commandDescs.info', Language.Default), + description_localizations: Lang.getRefLocalizationMap('commandDescs.info'), + dm_permission: true, + default_member_permissions: undefined, + options: [ + { + ...Args.INFO_OPTION, + required: true, + }, + ], + }, + TEST: { + type: ApplicationCommandType.ChatInput, + name: Lang.getRef('chatCommands.test', Language.Default), + name_localizations: Lang.getRefLocalizationMap('chatCommands.test'), + description: Lang.getRef('commandDescs.test', Language.Default), + description_localizations: Lang.getRefLocalizationMap('commandDescs.test'), + dm_permission: true, + default_member_permissions: undefined, + }, +}; + +export const MessageCommandMetadata: { + [command: string]: RESTPostAPIContextMenuApplicationCommandsJSONBody; +} = { + VIEW_DATE_SENT: { + type: ApplicationCommandType.Message, + name: Lang.getRef('messageCommands.viewDateSent', Language.Default), + name_localizations: Lang.getRefLocalizationMap('messageCommands.viewDateSent'), + default_member_permissions: undefined, + dm_permission: true, + }, +}; + +export const UserCommandMetadata: { + [command: string]: RESTPostAPIContextMenuApplicationCommandsJSONBody; +} = { + VIEW_DATE_JOINED: { + type: ApplicationCommandType.User, + name: Lang.getRef('userCommands.viewDateJoined', Language.Default), + name_localizations: Lang.getRefLocalizationMap('userCommands.viewDateJoined'), + default_member_permissions: undefined, + dm_permission: true, + }, +}; diff --git a/src/commands/user/index.ts b/src/commands/user/index.ts new file mode 100644 index 0000000..5ae6945 --- /dev/null +++ b/src/commands/user/index.ts @@ -0,0 +1 @@ +export { ViewDateJoined } from './view-date-joined.js'; diff --git a/src/commands/user/view-date-joined.ts b/src/commands/user/view-date-joined.ts new file mode 100644 index 0000000..5f45aa5 --- /dev/null +++ b/src/commands/user/view-date-joined.ts @@ -0,0 +1,32 @@ +import { DMChannel, PermissionsString, UserContextMenuCommandInteraction } from 'discord.js'; +import { RateLimiter } from 'discord.js-rate-limiter'; +import { DateTime } from 'luxon'; + +import { Language } from '../../models/enum-helpers/index.js'; +import { EventData } from '../../models/internal-models.js'; +import { Lang } from '../../services/index.js'; +import { InteractionUtils } from '../../utils/index.js'; +import { Command, CommandDeferType } from '../index.js'; + +export class ViewDateJoined implements Command { + public names = [Lang.getRef('userCommands.viewDateJoined', Language.Default)]; + public cooldown = new RateLimiter(1, 5000); + public deferType = CommandDeferType.HIDDEN; + public requireClientPerms: PermissionsString[] = []; + + public async execute(intr: UserContextMenuCommandInteraction, data: EventData): Promise { + let joinDate: Date; + if (!(intr.channel instanceof DMChannel)) { + let member = await intr.guild.members.fetch(intr.targetUser.id); + joinDate = member.joinedAt; + } else joinDate = intr.targetUser.createdAt; + + await InteractionUtils.send( + intr, + Lang.getEmbed('displayEmbeds.viewDateJoined', data.lang, { + TARGET: intr.targetUser.toString(), + DATE: DateTime.fromJSDate(joinDate).toLocaleString(DateTime.DATE_HUGE), + }) + ); + } +} diff --git a/src/constants/discord-limits.ts b/src/constants/discord-limits.ts new file mode 100644 index 0000000..0a4e541 --- /dev/null +++ b/src/constants/discord-limits.ts @@ -0,0 +1,15 @@ +export class DiscordLimits { + public static readonly GUILDS_PER_SHARD = 2500; + public static readonly CHANNELS_PER_GUILD = 500; + public static readonly ROLES_PER_GUILD = 250; + public static readonly PINS_PER_CHANNEL = 50; + public static readonly ACTIVE_THREADS_PER_GUILD = 1000; + public static readonly EMBEDS_PER_MESSAGE = 10; + public static readonly FIELDS_PER_EMBED = 25; + public static readonly CHOICES_PER_AUTOCOMPLETE = 25; + public static readonly EMBED_COMBINED_LENGTH = 6000; + public static readonly EMBED_TITLE_LENGTH = 256; + public static readonly EMBED_DESCRIPTION_LENGTH = 4096; + public static readonly EMBED_FIELD_NAME_LENGTH = 256; + public static readonly EMBED_FOOTER_LENGTH = 2048; +} diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..6f52f4e --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1 @@ +export { DiscordLimits } from './discord-limits.js'; diff --git a/src/controllers/controller.ts b/src/controllers/controller.ts new file mode 100644 index 0000000..cfd37f6 --- /dev/null +++ b/src/controllers/controller.ts @@ -0,0 +1,8 @@ +import { Router } from 'express'; + +export interface Controller { + path: string; + router: Router; + authToken?: string; + register(): void; +} diff --git a/src/controllers/guilds-controller.ts b/src/controllers/guilds-controller.ts new file mode 100644 index 0000000..2a14ac1 --- /dev/null +++ b/src/controllers/guilds-controller.ts @@ -0,0 +1,37 @@ +import { ShardingManager } from 'discord.js'; +import { Request, Response, Router } from 'express'; +import router from 'express-promise-router'; +import { createRequire } from 'node:module'; + +import { Controller } from './index.js'; +import { GetGuildsResponse } from '../models/cluster-api/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); + +export class GuildsController implements Controller { + public path = '/guilds'; + public router: Router = router(); + public authToken: string = Config.api.secret; + + constructor(private shardManager: ShardingManager) {} + + public register(): void { + this.router.get('/', (req, res) => this.getGuilds(req, res)); + } + + private async getGuilds(req: Request, res: Response): Promise { + let guilds: string[] = [ + ...new Set( + ( + await this.shardManager.broadcastEval(client => [...client.guilds.cache.keys()]) + ).flat() + ), + ]; + + let resBody: GetGuildsResponse = { + guilds, + }; + res.status(200).json(resBody); + } +} diff --git a/src/controllers/index.ts b/src/controllers/index.ts new file mode 100644 index 0000000..145ca06 --- /dev/null +++ b/src/controllers/index.ts @@ -0,0 +1,4 @@ +export { Controller } from './controller.js'; +export { GuildsController } from './guilds-controller.js'; +export { ShardsController } from './shards-controller.js'; +export { RootController } from './root-controller.js'; diff --git a/src/controllers/root-controller.ts b/src/controllers/root-controller.ts new file mode 100644 index 0000000..90b1389 --- /dev/null +++ b/src/controllers/root-controller.ts @@ -0,0 +1,17 @@ +import { Request, Response, Router } from 'express'; +import router from 'express-promise-router'; + +import { Controller } from './index.js'; + +export class RootController implements Controller { + public path = '/'; + public router: Router = router(); + + public register(): void { + this.router.get('/', (req, res) => this.get(req, res)); + } + + private async get(req: Request, res: Response): Promise { + res.status(200).json({ name: 'Discord Bot Cluster API', author: 'Kevin Novak' }); + } +} diff --git a/src/controllers/shards-controller.ts b/src/controllers/shards-controller.ts new file mode 100644 index 0000000..7812093 --- /dev/null +++ b/src/controllers/shards-controller.ts @@ -0,0 +1,80 @@ +import { ActivityType, ShardingManager } from 'discord.js'; +import { Request, Response, Router } from 'express'; +import router from 'express-promise-router'; +import { createRequire } from 'node:module'; + +import { Controller } from './index.js'; +import { CustomClient } from '../extensions/index.js'; +import { mapClass } from '../middleware/index.js'; +import { + GetShardsResponse, + SetShardPresencesRequest, + ShardInfo, + ShardStats, +} from '../models/cluster-api/index.js'; +import { Logger } from '../services/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); +let Logs = require('../../lang/logs.json'); + +export class ShardsController implements Controller { + public path = '/shards'; + public router: Router = router(); + public authToken: string = Config.api.secret; + + constructor(private shardManager: ShardingManager) {} + + public register(): void { + this.router.get('/', (req, res) => this.getShards(req, res)); + this.router.put('/presence', mapClass(SetShardPresencesRequest), (req, res) => + this.setShardPresences(req, res) + ); + } + + private async getShards(req: Request, res: Response): Promise { + let shardDatas = await Promise.all( + this.shardManager.shards.map(async shard => { + let shardInfo: ShardInfo = { + id: shard.id, + ready: shard.ready, + error: false, + }; + + try { + let uptime = (await shard.fetchClientValue('uptime')) as number; + shardInfo.uptimeSecs = Math.floor(uptime / 1000); + } catch (error) { + Logger.error(Logs.error.managerShardInfo, error); + shardInfo.error = true; + } + + return shardInfo; + }) + ); + + let stats: ShardStats = { + shardCount: this.shardManager.shards.size, + uptimeSecs: Math.floor(process.uptime()), + }; + + let resBody: GetShardsResponse = { + shards: shardDatas, + stats, + }; + res.status(200).json(resBody); + } + + private async setShardPresences(req: Request, res: Response): Promise { + let reqBody: SetShardPresencesRequest = res.locals.input; + + await this.shardManager.broadcastEval( + (client: CustomClient, context) => { + return client.setPresence(context.type, context.name, context.url); + }, + { context: { type: ActivityType[reqBody.type], name: reqBody.name, url: reqBody.url } } + ); + + res.sendStatus(200); + } +} diff --git a/src/enums/dev-command-name.ts b/src/enums/dev-command-name.ts new file mode 100644 index 0000000..cc1d5e5 --- /dev/null +++ b/src/enums/dev-command-name.ts @@ -0,0 +1,3 @@ +export enum DevCommandName { + INFO = 'INFO', +} diff --git a/src/enums/help-option.ts b/src/enums/help-option.ts new file mode 100644 index 0000000..5bcc4ed --- /dev/null +++ b/src/enums/help-option.ts @@ -0,0 +1,4 @@ +export enum HelpOption { + CONTACT_SUPPORT = 'CONTACT_SUPPORT', + COMMANDS = 'COMMANDS', +} diff --git a/src/enums/index.ts b/src/enums/index.ts new file mode 100644 index 0000000..f85f36f --- /dev/null +++ b/src/enums/index.ts @@ -0,0 +1,3 @@ +export { DevCommandName } from './dev-command-name.js'; +export { HelpOption } from './help-option.js'; +export { InfoOption } from './info-option.js'; diff --git a/src/enums/info-option.ts b/src/enums/info-option.ts new file mode 100644 index 0000000..451b05a --- /dev/null +++ b/src/enums/info-option.ts @@ -0,0 +1,4 @@ +export enum InfoOption { + ABOUT = 'ABOUT', + TRANSLATE = 'TRANSLATE', +} diff --git a/src/events/button-handler.ts b/src/events/button-handler.ts new file mode 100644 index 0000000..9fcedf0 --- /dev/null +++ b/src/events/button-handler.ts @@ -0,0 +1,83 @@ +import { ButtonInteraction } from 'discord.js'; +import { RateLimiter } from 'discord.js-rate-limiter'; +import { createRequire } from 'node:module'; + +import { EventHandler } from './index.js'; +import { Button, ButtonDeferType } from '../buttons/index.js'; +import { EventDataService } from '../services/index.js'; +import { InteractionUtils } from '../utils/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); + +export class ButtonHandler implements EventHandler { + private rateLimiter = new RateLimiter( + Config.rateLimiting.buttons.amount, + Config.rateLimiting.buttons.interval * 1000 + ); + + constructor(private buttons: Button[], private eventDataService: EventDataService) {} + + public async process(intr: ButtonInteraction): Promise { + // Don't respond to self, or other bots + if (intr.user.id === intr.client.user?.id || intr.user.bot) { + return; + } + + // Check if user is rate limited + let limited = this.rateLimiter.take(intr.user.id); + if (limited) { + return; + } + + // Try to find the button the user wants + let button = this.findButton(intr.customId); + if (!button) { + return; + } + + if (button.requireGuild && !intr.guild) { + return; + } + + // Check if the embeds author equals the users tag + if ( + button.requireEmbedAuthorTag && + intr.message.embeds[0]?.author?.name !== intr.user.tag + ) { + return; + } + + // Defer interaction + // NOTE: Anything after this point we should be responding to the interaction + switch (button.deferType) { + case ButtonDeferType.REPLY: { + await InteractionUtils.deferReply(intr); + break; + } + case ButtonDeferType.UPDATE: { + await InteractionUtils.deferUpdate(intr); + break; + } + } + + // Return if defer was unsuccessful + if (button.deferType !== ButtonDeferType.NONE && !intr.deferred) { + return; + } + + // Get data from database + let data = await this.eventDataService.create({ + user: intr.user, + channel: intr.channel, + guild: intr.guild, + }); + + // Execute the button + await button.execute(intr, data); + } + + private findButton(id: string): Button { + return this.buttons.find(button => button.ids.includes(id)); + } +} diff --git a/src/events/command-handler.ts b/src/events/command-handler.ts new file mode 100644 index 0000000..fbcdf16 --- /dev/null +++ b/src/events/command-handler.ts @@ -0,0 +1,182 @@ +import { + AutocompleteInteraction, + ChatInputCommandInteraction, + CommandInteraction, + NewsChannel, + TextChannel, + ThreadChannel, +} from 'discord.js'; +import { RateLimiter } from 'discord.js-rate-limiter'; +import { createRequire } from 'node:module'; + +import { EventHandler } from './index.js'; +import { Command, CommandDeferType } from '../commands/index.js'; +import { DiscordLimits } from '../constants/index.js'; +import { EventData } from '../models/internal-models.js'; +import { EventDataService, Lang, Logger } from '../services/index.js'; +import { CommandUtils, InteractionUtils } from '../utils/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); +let Logs = require('../../lang/logs.json'); + +export class CommandHandler implements EventHandler { + private rateLimiter = new RateLimiter( + Config.rateLimiting.commands.amount, + Config.rateLimiting.commands.interval * 1000 + ); + + constructor(public commands: Command[], private eventDataService: EventDataService) {} + + public async process(intr: CommandInteraction | AutocompleteInteraction): Promise { + // Don't respond to self, or other bots + if (intr.user.id === intr.client.user?.id || intr.user.bot) { + return; + } + + let commandParts = + intr instanceof ChatInputCommandInteraction || intr instanceof AutocompleteInteraction + ? [ + intr.commandName, + intr.options.getSubcommandGroup(false), + intr.options.getSubcommand(false), + ].filter(Boolean) + : [intr.commandName]; + let commandName = commandParts.join(' '); + + // Try to find the command the user wants + let command = CommandUtils.findCommand(this.commands, commandParts); + if (!command) { + Logger.error( + Logs.error.commandNotFound + .replaceAll('{INTERACTION_ID}', intr.id) + .replaceAll('{COMMAND_NAME}', commandName) + ); + return; + } + + if (intr instanceof AutocompleteInteraction) { + if (!command.autocomplete) { + Logger.error( + Logs.error.autocompleteNotFound + .replaceAll('{INTERACTION_ID}', intr.id) + .replaceAll('{COMMAND_NAME}', commandName) + ); + return; + } + + try { + let option = intr.options.getFocused(true); + let choices = await command.autocomplete(intr, option); + await InteractionUtils.respond( + intr, + choices?.slice(0, DiscordLimits.CHOICES_PER_AUTOCOMPLETE) + ); + } catch (error) { + Logger.error( + intr.channel instanceof TextChannel || + intr.channel instanceof NewsChannel || + intr.channel instanceof ThreadChannel + ? Logs.error.autocompleteGuild + .replaceAll('{INTERACTION_ID}', intr.id) + .replaceAll('{OPTION_NAME}', commandName) + .replaceAll('{COMMAND_NAME}', commandName) + .replaceAll('{USER_TAG}', intr.user.tag) + .replaceAll('{USER_ID}', intr.user.id) + .replaceAll('{CHANNEL_NAME}', intr.channel.name) + .replaceAll('{CHANNEL_ID}', intr.channel.id) + .replaceAll('{GUILD_NAME}', intr.guild?.name) + .replaceAll('{GUILD_ID}', intr.guild?.id) + : Logs.error.autocompleteOther + .replaceAll('{INTERACTION_ID}', intr.id) + .replaceAll('{OPTION_NAME}', commandName) + .replaceAll('{COMMAND_NAME}', commandName) + .replaceAll('{USER_TAG}', intr.user.tag) + .replaceAll('{USER_ID}', intr.user.id), + error + ); + } + return; + } + + // Check if user is rate limited + let limited = this.rateLimiter.take(intr.user.id); + if (limited) { + return; + } + + // Defer interaction + // NOTE: Anything after this point we should be responding to the interaction + switch (command.deferType) { + case CommandDeferType.PUBLIC: { + await InteractionUtils.deferReply(intr, false); + break; + } + case CommandDeferType.HIDDEN: { + await InteractionUtils.deferReply(intr, true); + break; + } + } + + // Return if defer was unsuccessful + if (command.deferType !== CommandDeferType.NONE && !intr.deferred) { + return; + } + + // Get data from database + let data = await this.eventDataService.create({ + user: intr.user, + channel: intr.channel, + guild: intr.guild, + args: intr instanceof ChatInputCommandInteraction ? intr.options : undefined, + }); + + try { + // Check if interaction passes command checks + let passesChecks = await CommandUtils.runChecks(command, intr, data); + if (passesChecks) { + // Execute the command + await command.execute(intr, data); + } + } catch (error) { + await this.sendError(intr, data); + + // Log command error + Logger.error( + intr.channel instanceof TextChannel || + intr.channel instanceof NewsChannel || + intr.channel instanceof ThreadChannel + ? Logs.error.commandGuild + .replaceAll('{INTERACTION_ID}', intr.id) + .replaceAll('{COMMAND_NAME}', commandName) + .replaceAll('{USER_TAG}', intr.user.tag) + .replaceAll('{USER_ID}', intr.user.id) + .replaceAll('{CHANNEL_NAME}', intr.channel.name) + .replaceAll('{CHANNEL_ID}', intr.channel.id) + .replaceAll('{GUILD_NAME}', intr.guild?.name) + .replaceAll('{GUILD_ID}', intr.guild?.id) + : Logs.error.commandOther + .replaceAll('{INTERACTION_ID}', intr.id) + .replaceAll('{COMMAND_NAME}', commandName) + .replaceAll('{USER_TAG}', intr.user.tag) + .replaceAll('{USER_ID}', intr.user.id), + error + ); + } + } + + private async sendError(intr: CommandInteraction, data: EventData): Promise { + try { + await InteractionUtils.send( + intr, + Lang.getEmbed('errorEmbeds.command', data.lang, { + ERROR_CODE: intr.id, + GUILD_ID: intr.guild?.id ?? Lang.getRef('other.na', data.lang), + SHARD_ID: (intr.guild?.shardId ?? 0).toString(), + }) + ); + } catch { + // Ignore + } + } +} diff --git a/src/events/event-handler.ts b/src/events/event-handler.ts new file mode 100644 index 0000000..b8ef74e --- /dev/null +++ b/src/events/event-handler.ts @@ -0,0 +1,3 @@ +export interface EventHandler { + process(...args: any[]): Promise; +} diff --git a/src/events/guild-join-handler.ts b/src/events/guild-join-handler.ts new file mode 100644 index 0000000..ca878f4 --- /dev/null +++ b/src/events/guild-join-handler.ts @@ -0,0 +1,67 @@ +import { Guild } from 'discord.js'; +import { createRequire } from 'node:module'; + +import { EventHandler } from './index.js'; +import { Language } from '../models/enum-helpers/index.js'; +import { EventDataService, Lang, Logger } from '../services/index.js'; +import { ClientUtils, FormatUtils, MessageUtils } from '../utils/index.js'; + +const require = createRequire(import.meta.url); +let Logs = require('../../lang/logs.json'); + +export class GuildJoinHandler implements EventHandler { + constructor(private eventDataService: EventDataService) {} + + public async process(guild: Guild): Promise { + Logger.info( + Logs.info.guildJoined + .replaceAll('{GUILD_NAME}', guild.name) + .replaceAll('{GUILD_ID}', guild.id) + ); + + let owner = await guild.fetchOwner(); + + // Get data from database + let data = await this.eventDataService.create({ + user: owner?.user, + guild, + }); + + // Send welcome message to the server's notify channel + let notifyChannel = await ClientUtils.findNotifyChannel(guild, data.langGuild); + if (notifyChannel) { + await MessageUtils.send( + notifyChannel, + Lang.getEmbed('displayEmbeds.welcome', data.langGuild, { + CMD_LINK_HELP: FormatUtils.commandMention( + await ClientUtils.findAppCommand( + guild.client, + Lang.getRef('chatCommands.help', Language.Default) + ) + ), + }).setAuthor({ + name: guild.name, + iconURL: guild.iconURL(), + }) + ); + } + + // Send welcome message to owner + if (owner) { + await MessageUtils.send( + owner.user, + Lang.getEmbed('displayEmbeds.welcome', data.lang, { + CMD_LINK_HELP: FormatUtils.commandMention( + await ClientUtils.findAppCommand( + guild.client, + Lang.getRef('chatCommands.help', Language.Default) + ) + ), + }).setAuthor({ + name: guild.name, + iconURL: guild.iconURL(), + }) + ); + } + } +} diff --git a/src/events/guild-leave-handler.ts b/src/events/guild-leave-handler.ts new file mode 100644 index 0000000..d9e7603 --- /dev/null +++ b/src/events/guild-leave-handler.ts @@ -0,0 +1,18 @@ +import { Guild } from 'discord.js'; +import { createRequire } from 'node:module'; + +import { EventHandler } from './index.js'; +import { Logger } from '../services/index.js'; + +const require = createRequire(import.meta.url); +let Logs = require('../../lang/logs.json'); + +export class GuildLeaveHandler implements EventHandler { + public async process(guild: Guild): Promise { + Logger.info( + Logs.info.guildLeft + .replaceAll('{GUILD_NAME}', guild.name) + .replaceAll('{GUILD_ID}', guild.id) + ); + } +} diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..59af22e --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,8 @@ +export { ButtonHandler } from './button-handler.js'; +export { CommandHandler } from './command-handler.js'; +export { EventHandler } from './event-handler.js'; +export { GuildJoinHandler } from './guild-join-handler.js'; +export { GuildLeaveHandler } from './guild-leave-handler.js'; +export { ReactionHandler } from './reaction-handler.js'; +export { MessageHandler } from './message-handler.js'; +export { TriggerHandler } from './trigger-handler.js'; diff --git a/src/events/message-handler.ts b/src/events/message-handler.ts new file mode 100644 index 0000000..09ac213 --- /dev/null +++ b/src/events/message-handler.ts @@ -0,0 +1,17 @@ +import { Message } from 'discord.js'; + +import { EventHandler, TriggerHandler } from './index.js'; + +export class MessageHandler implements EventHandler { + constructor(private triggerHandler: TriggerHandler) {} + + public async process(msg: Message): Promise { + // Don't respond to system messages or self + if (msg.system || msg.author.id === msg.client.user?.id) { + return; + } + + // Process trigger + await this.triggerHandler.process(msg); + } +} diff --git a/src/events/reaction-handler.ts b/src/events/reaction-handler.ts new file mode 100644 index 0000000..943972c --- /dev/null +++ b/src/events/reaction-handler.ts @@ -0,0 +1,65 @@ +import { Message, MessageReaction, User } from 'discord.js'; +import { RateLimiter } from 'discord.js-rate-limiter'; +import { createRequire } from 'node:module'; + +import { EventHandler } from './index.js'; +import { Reaction } from '../reactions/index.js'; +import { EventDataService } from '../services/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); + +export class ReactionHandler implements EventHandler { + private rateLimiter = new RateLimiter( + Config.rateLimiting.reactions.amount, + Config.rateLimiting.reactions.interval * 1000 + ); + + constructor(private reactions: Reaction[], private eventDataService: EventDataService) {} + + public async process(msgReaction: MessageReaction, msg: Message, reactor: User): Promise { + // Don't respond to self, or other bots + if (reactor.id === msgReaction.client.user?.id || reactor.bot) { + return; + } + + // Check if user is rate limited + let limited = this.rateLimiter.take(msg.author.id); + if (limited) { + return; + } + + // Try to find the reaction the user wants + let reaction = this.findReaction(msgReaction.emoji.name); + if (!reaction) { + return; + } + + if (reaction.requireGuild && !msg.guild) { + return; + } + + if (reaction.requireSentByClient && msg.author.id !== msg.client.user?.id) { + return; + } + + // Check if the embeds author equals the reactors tag + if (reaction.requireEmbedAuthorTag && msg.embeds[0]?.author?.name !== reactor.tag) { + return; + } + + // Get data from database + let data = await this.eventDataService.create({ + user: reactor, + channel: msg.channel, + guild: msg.guild, + }); + + // Execute the reaction + await reaction.execute(msgReaction, msg, reactor, data); + } + + private findReaction(emoji: string): Reaction { + return this.reactions.find(reaction => reaction.emoji === emoji); + } +} diff --git a/src/events/trigger-handler.ts b/src/events/trigger-handler.ts new file mode 100644 index 0000000..12b97e0 --- /dev/null +++ b/src/events/trigger-handler.ts @@ -0,0 +1,56 @@ +import { Message } from 'discord.js'; +import { RateLimiter } from 'discord.js-rate-limiter'; +import { createRequire } from 'node:module'; + +import { EventDataService } from '../services/index.js'; +import { Trigger } from '../triggers/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); + +export class TriggerHandler { + private rateLimiter = new RateLimiter( + Config.rateLimiting.triggers.amount, + Config.rateLimiting.triggers.interval * 1000 + ); + + constructor(private triggers: Trigger[], private eventDataService: EventDataService) {} + + public async process(msg: Message): Promise { + // Check if user is rate limited + let limited = this.rateLimiter.take(msg.author.id); + if (limited) { + return; + } + + // Find triggers caused by this message + let triggers = this.triggers.filter(trigger => { + if (trigger.requireGuild && !msg.guild) { + return false; + } + + if (!trigger.triggered(msg)) { + return false; + } + + return true; + }); + + // If this message causes no triggers then return + if (triggers.length === 0) { + return; + } + + // Get data from database + let data = await this.eventDataService.create({ + user: msg.author, + channel: msg.channel, + guild: msg.guild, + }); + + // Execute triggers + for (let trigger of triggers) { + await trigger.execute(msg, data); + } + } +} diff --git a/src/extensions/custom-client.ts b/src/extensions/custom-client.ts new file mode 100644 index 0000000..393020a --- /dev/null +++ b/src/extensions/custom-client.ts @@ -0,0 +1,23 @@ +import { ActivityType, Client, ClientOptions, Presence } from 'discord.js'; + +export class CustomClient extends Client { + constructor(clientOptions: ClientOptions) { + super(clientOptions); + } + + public setPresence( + type: Exclude, + name: string, + url: string + ): Presence { + return this.user?.setPresence({ + activities: [ + { + type, + name, + url, + }, + ], + }); + } +} diff --git a/src/extensions/index.ts b/src/extensions/index.ts new file mode 100644 index 0000000..5e7bcd4 --- /dev/null +++ b/src/extensions/index.ts @@ -0,0 +1 @@ +export { CustomClient } from './custom-client.js'; diff --git a/src/jobs/index.ts b/src/jobs/index.ts new file mode 100644 index 0000000..419acf8 --- /dev/null +++ b/src/jobs/index.ts @@ -0,0 +1,2 @@ +export { Job } from './job.js'; +export { UpdateServerCountJob } from './update-server-count-job.js'; diff --git a/src/jobs/job.ts b/src/jobs/job.ts new file mode 100644 index 0000000..e897048 --- /dev/null +++ b/src/jobs/job.ts @@ -0,0 +1,8 @@ +export abstract class Job { + abstract name: string; + abstract log: boolean; + abstract schedule: string; + runOnce = false; + initialDelaySecs = 0; + abstract run(): Promise; +} diff --git a/src/jobs/update-server-count-job.ts b/src/jobs/update-server-count-job.ts new file mode 100644 index 0000000..37c179d --- /dev/null +++ b/src/jobs/update-server-count-job.ts @@ -0,0 +1,68 @@ +import { ActivityType, ShardingManager } from 'discord.js'; +import { createRequire } from 'node:module'; + +import { Job } from './index.js'; +import { CustomClient } from '../extensions/index.js'; +import { BotSite } from '../models/config-models.js'; +import { HttpService, Lang, Logger } from '../services/index.js'; +import { ShardUtils } from '../utils/index.js'; + +const require = createRequire(import.meta.url); +let BotSites: BotSite[] = require('../../config/bot-sites.json'); +let Config = require('../../config/config.json'); +let Logs = require('../../lang/logs.json'); + +export class UpdateServerCountJob extends Job { + public name = 'Update Server Count'; + public schedule: string = Config.jobs.updateServerCount.schedule; + public log: boolean = Config.jobs.updateServerCount.log; + public runOnce: boolean = Config.jobs.updateServerCount.runOnce; + public initialDelaySecs: number = Config.jobs.updateServerCount.initialDelaySecs; + + private botSites: BotSite[]; + + constructor(private shardManager: ShardingManager, private httpService: HttpService) { + super(); + this.botSites = BotSites.filter(botSite => botSite.enabled); + } + + public async run(): Promise { + let serverCount = await ShardUtils.serverCount(this.shardManager); + + let type = ActivityType.Streaming; + let name = `to ${serverCount.toLocaleString()} servers`; + let url = Lang.getCom('links.stream'); + + await this.shardManager.broadcastEval( + (client: CustomClient, context) => { + return client.setPresence(context.type, context.name, context.url); + }, + { context: { type, name, url } } + ); + + Logger.info( + Logs.info.updatedServerCount.replaceAll('{SERVER_COUNT}', serverCount.toLocaleString()) + ); + + for (let botSite of this.botSites) { + try { + let body = JSON.parse( + botSite.body.replaceAll('{{SERVER_COUNT}}', serverCount.toString()) + ); + let res = await this.httpService.post(botSite.url, botSite.authorization, body); + + if (!res.ok) { + throw res; + } + } catch (error) { + Logger.error( + Logs.error.updatedServerCountSite.replaceAll('{BOT_SITE}', botSite.name), + error + ); + continue; + } + + Logger.info(Logs.info.updatedServerCountSite.replaceAll('{BOT_SITE}', botSite.name)); + } + } +} diff --git a/src/middleware/check-auth.ts b/src/middleware/check-auth.ts new file mode 100644 index 0000000..e423167 --- /dev/null +++ b/src/middleware/check-auth.ts @@ -0,0 +1,11 @@ +import { RequestHandler } from 'express'; + +export function checkAuth(token: string): RequestHandler { + return (req, res, next) => { + if (req.headers.authorization !== token) { + res.sendStatus(401); + return; + } + next(); + }; +} diff --git a/src/middleware/handle-error.ts b/src/middleware/handle-error.ts new file mode 100644 index 0000000..f8eb104 --- /dev/null +++ b/src/middleware/handle-error.ts @@ -0,0 +1,19 @@ +import { ErrorRequestHandler } from 'express'; +import { createRequire } from 'node:module'; + +import { Logger } from '../services/index.js'; + +const require = createRequire(import.meta.url); +let Logs = require('../../lang/logs.json'); + +export function handleError(): ErrorRequestHandler { + return (error, req, res, _next) => { + Logger.error( + Logs.error.apiRequest + .replaceAll('{HTTP_METHOD}', req.method) + .replaceAll('{URL}', req.url), + error + ); + res.status(500).json({ error: true, message: error.message }); + }; +} diff --git a/src/middleware/index.ts b/src/middleware/index.ts new file mode 100644 index 0000000..ebc330b --- /dev/null +++ b/src/middleware/index.ts @@ -0,0 +1,3 @@ +export { checkAuth } from './check-auth.js'; +export { handleError } from './handle-error.js'; +export { mapClass } from './map-class.js'; diff --git a/src/middleware/map-class.ts b/src/middleware/map-class.ts new file mode 100644 index 0000000..c72faf6 --- /dev/null +++ b/src/middleware/map-class.ts @@ -0,0 +1,40 @@ +import { ClassConstructor, plainToInstance } from 'class-transformer'; +import { validate, ValidationError } from 'class-validator'; +import { NextFunction, Request, RequestHandler, Response } from 'express'; + +export function mapClass(cls: ClassConstructor): RequestHandler { + return async (req: Request, res: Response, next: NextFunction) => { + // Map to class + let obj: object = plainToInstance(cls, req.body); + + // Validate class + let errors = await validate(obj, { + skipMissingProperties: true, + whitelist: true, + forbidNonWhitelisted: false, + forbidUnknownValues: true, + }); + if (errors.length > 0) { + res.status(400).send({ error: true, errors: formatValidationErrors(errors) }); + return; + } + + // Set validated class to locals + res.locals.input = obj; + next(); + }; +} + +interface ValidationErrorLog { + property: string; + constraints?: { [type: string]: string }; + children?: ValidationErrorLog[]; +} + +function formatValidationErrors(errors: ValidationError[]): ValidationErrorLog[] { + return errors.map(error => ({ + property: error.property, + constraints: error.constraints, + children: error.children?.length > 0 ? formatValidationErrors(error.children) : undefined, + })); +} diff --git a/src/models/api.ts b/src/models/api.ts new file mode 100644 index 0000000..abd8fab --- /dev/null +++ b/src/models/api.ts @@ -0,0 +1,38 @@ +import express, { Express } from 'express'; +import { createRequire } from 'node:module'; +import util from 'node:util'; + +import { Controller } from '../controllers/index.js'; +import { checkAuth, handleError } from '../middleware/index.js'; +import { Logger } from '../services/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); +let Logs = require('../../lang/logs.json'); + +export class Api { + private app: Express; + + constructor(public controllers: Controller[]) { + this.app = express(); + this.app.use(express.json()); + this.setupControllers(); + this.app.use(handleError()); + } + + public async start(): Promise { + let listen = util.promisify(this.app.listen.bind(this.app)); + await listen(Config.api.port); + Logger.info(Logs.info.apiStarted.replaceAll('{PORT}', Config.api.port)); + } + + private setupControllers(): void { + for (let controller of this.controllers) { + if (controller.authToken) { + controller.router.use(checkAuth(controller.authToken)); + } + controller.register(); + this.app.use(controller.path, controller.router); + } + } +} diff --git a/src/models/bot.ts b/src/models/bot.ts new file mode 100644 index 0000000..55b43c0 --- /dev/null +++ b/src/models/bot.ts @@ -0,0 +1,203 @@ +import { + AutocompleteInteraction, + ButtonInteraction, + Client, + CommandInteraction, + Events, + Guild, + Interaction, + Message, + MessageReaction, + PartialMessageReaction, + PartialUser, + RateLimitData, + RESTEvents, + User, +} from 'discord.js'; +import { createRequire } from 'node:module'; + +import { + ButtonHandler, + CommandHandler, + GuildJoinHandler, + GuildLeaveHandler, + MessageHandler, + ReactionHandler, +} from '../events/index.js'; +import { JobService, Logger } from '../services/index.js'; +import { PartialUtils } from '../utils/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); +let Debug = require('../../config/debug.json'); +let Logs = require('../../lang/logs.json'); + +export class Bot { + private ready = false; + + constructor( + private token: string, + private client: Client, + private guildJoinHandler: GuildJoinHandler, + private guildLeaveHandler: GuildLeaveHandler, + private messageHandler: MessageHandler, + private commandHandler: CommandHandler, + private buttonHandler: ButtonHandler, + private reactionHandler: ReactionHandler, + private jobService: JobService + ) {} + + public async start(): Promise { + this.registerListeners(); + await this.login(this.token); + } + + private registerListeners(): void { + this.client.on(Events.ClientReady, () => this.onReady()); + this.client.on(Events.ShardReady, (shardId: number, unavailableGuilds: Set) => + this.onShardReady(shardId, unavailableGuilds) + ); + this.client.on(Events.GuildCreate, (guild: Guild) => this.onGuildJoin(guild)); + this.client.on(Events.GuildDelete, (guild: Guild) => this.onGuildLeave(guild)); + this.client.on(Events.MessageCreate, (msg: Message) => this.onMessage(msg)); + this.client.on(Events.InteractionCreate, (intr: Interaction) => this.onInteraction(intr)); + this.client.on( + Events.MessageReactionAdd, + (messageReaction: MessageReaction | PartialMessageReaction, user: User | PartialUser) => + this.onReaction(messageReaction, user) + ); + this.client.rest.on(RESTEvents.RateLimited, (rateLimitData: RateLimitData) => + this.onRateLimit(rateLimitData) + ); + } + + private async login(token: string): Promise { + try { + await this.client.login(token); + } catch (error) { + Logger.error(Logs.error.clientLogin, error); + return; + } + } + + private async onReady(): Promise { + let userTag = this.client.user?.tag; + Logger.info(Logs.info.clientLogin.replaceAll('{USER_TAG}', userTag)); + + if (!Debug.dummyMode.enabled) { + this.jobService.start(); + } + + this.ready = true; + Logger.info(Logs.info.clientReady); + } + + private onShardReady(shardId: number, _unavailableGuilds: Set): void { + Logger.setShardId(shardId); + } + + private async onGuildJoin(guild: Guild): Promise { + if (!this.ready || Debug.dummyMode.enabled) { + return; + } + + try { + await this.guildJoinHandler.process(guild); + } catch (error) { + Logger.error(Logs.error.guildJoin, error); + } + } + + private async onGuildLeave(guild: Guild): Promise { + if (!this.ready || Debug.dummyMode.enabled) { + return; + } + + try { + await this.guildLeaveHandler.process(guild); + } catch (error) { + Logger.error(Logs.error.guildLeave, error); + } + } + + private async onMessage(msg: Message): Promise { + if ( + !this.ready || + (Debug.dummyMode.enabled && !Debug.dummyMode.whitelist.includes(msg.author.id)) + ) { + return; + } + + try { + msg = await PartialUtils.fillMessage(msg); + if (!msg) { + return; + } + + await this.messageHandler.process(msg); + } catch (error) { + Logger.error(Logs.error.message, error); + } + } + + private async onInteraction(intr: Interaction): Promise { + if ( + !this.ready || + (Debug.dummyMode.enabled && !Debug.dummyMode.whitelist.includes(intr.user.id)) + ) { + return; + } + + if (intr instanceof CommandInteraction || intr instanceof AutocompleteInteraction) { + try { + await this.commandHandler.process(intr); + } catch (error) { + Logger.error(Logs.error.command, error); + } + } else if (intr instanceof ButtonInteraction) { + try { + await this.buttonHandler.process(intr); + } catch (error) { + Logger.error(Logs.error.button, error); + } + } + } + + private async onReaction( + msgReaction: MessageReaction | PartialMessageReaction, + reactor: User | PartialUser + ): Promise { + if ( + !this.ready || + (Debug.dummyMode.enabled && !Debug.dummyMode.whitelist.includes(reactor.id)) + ) { + return; + } + + try { + msgReaction = await PartialUtils.fillReaction(msgReaction); + if (!msgReaction) { + return; + } + + reactor = await PartialUtils.fillUser(reactor); + if (!reactor) { + return; + } + + await this.reactionHandler.process( + msgReaction, + msgReaction.message as Message, + reactor + ); + } catch (error) { + Logger.error(Logs.error.reaction, error); + } + } + + private async onRateLimit(rateLimitData: RateLimitData): Promise { + if (rateLimitData.timeToReset >= Config.logging.rateLimit.minTimeout * 1000) { + Logger.error(Logs.error.apiRateLimit, rateLimitData); + } + } +} diff --git a/src/models/cluster-api/guilds.ts b/src/models/cluster-api/guilds.ts new file mode 100644 index 0000000..f389ffd --- /dev/null +++ b/src/models/cluster-api/guilds.ts @@ -0,0 +1,3 @@ +export interface GetGuildsResponse { + guilds: string[]; +} diff --git a/src/models/cluster-api/index.ts b/src/models/cluster-api/index.ts new file mode 100644 index 0000000..5237e6e --- /dev/null +++ b/src/models/cluster-api/index.ts @@ -0,0 +1,2 @@ +export { GetGuildsResponse } from './guilds.js'; +export { GetShardsResponse, ShardInfo, ShardStats, SetShardPresencesRequest } from './shards.js'; diff --git a/src/models/cluster-api/shards.ts b/src/models/cluster-api/shards.ts new file mode 100644 index 0000000..a77f34a --- /dev/null +++ b/src/models/cluster-api/shards.ts @@ -0,0 +1,34 @@ +import { IsDefined, IsEnum, IsString, IsUrl, Length } from 'class-validator'; +import { ActivityType } from 'discord.js'; + +export interface GetShardsResponse { + shards: ShardInfo[]; + stats: ShardStats; +} + +export interface ShardStats { + shardCount: number; + uptimeSecs: number; +} + +export interface ShardInfo { + id: number; + ready: boolean; + error: boolean; + uptimeSecs?: number; +} + +export class SetShardPresencesRequest { + @IsDefined() + @IsEnum(ActivityType) + type: string; + + @IsDefined() + @IsString() + @Length(1, 128) + name: string; + + @IsDefined() + @IsUrl() + url: string; +} diff --git a/src/models/config-models.ts b/src/models/config-models.ts new file mode 100644 index 0000000..9d63ed3 --- /dev/null +++ b/src/models/config-models.ts @@ -0,0 +1,7 @@ +export interface BotSite { + name: string; + enabled: boolean; + url: string; + authorization: string; + body: string; +} diff --git a/src/models/enum-helpers/index.ts b/src/models/enum-helpers/index.ts new file mode 100644 index 0000000..c4d2ef4 --- /dev/null +++ b/src/models/enum-helpers/index.ts @@ -0,0 +1,2 @@ +export { Language } from './language.js'; +export { Permission } from './permission.js'; diff --git a/src/models/enum-helpers/language.ts b/src/models/enum-helpers/language.ts new file mode 100644 index 0000000..ef42dc2 --- /dev/null +++ b/src/models/enum-helpers/language.ts @@ -0,0 +1,110 @@ +import { Locale } from 'discord.js'; + +interface LanguageData { + englishName: string; + nativeName: string; +} + +export class Language { + public static Default = Locale.EnglishUS; + public static Enabled: Locale[] = [Locale.EnglishUS, Locale.EnglishGB]; + + // See https://discord.com/developers/docs/reference#locales + public static Data: { + [key in Locale]: LanguageData; + } = { + bg: { englishName: 'Bulgarian', nativeName: 'ĐąŅŠĐģĐŗĐ°Ņ€ŅĐēи' }, + cs: { englishName: 'Czech', nativeName: 'ČeÅĄtina' }, + da: { englishName: 'Danish', nativeName: 'Dansk' }, + de: { englishName: 'German', nativeName: 'Deutsch' }, + el: { englishName: 'Greek', nativeName: 'ΕÎģÎģΡÎŊΚÎēÎŦ' }, + 'en-GB': { englishName: 'English, UK', nativeName: 'English, UK' }, + 'en-US': { englishName: 'English, US', nativeName: 'English, US' }, + 'es-ES': { englishName: 'Spanish', nativeName: 'EspaÃąol' }, + fi: { englishName: 'Finnish', nativeName: 'Suomi' }, + fr: { englishName: 'French', nativeName: 'Français' }, + hi: { englishName: 'Hindi', nativeName: 'ā¤šā¤ŋ⤍āĨā¤ĻāĨ€' }, + hr: { englishName: 'Croatian', nativeName: 'Hrvatski' }, + hu: { englishName: 'Hungarian', nativeName: 'Magyar' }, + id: { englishName: 'Indonesian', nativeName: 'Bahasa Indonesia' }, + it: { englishName: 'Italian', nativeName: 'Italiano' }, + ja: { englishName: 'Japanese', nativeName: 'æ—ĨæœŦčĒž' }, + ko: { englishName: 'Korean', nativeName: '한ęĩ­ė–´' }, + lt: { englishName: 'Lithuanian', nativeName: 'LietuviÅĄkai' }, + nl: { englishName: 'Dutch', nativeName: 'Nederlands' }, + no: { englishName: 'Norwegian', nativeName: 'Norsk' }, + pl: { englishName: 'Polish', nativeName: 'Polski' }, + 'pt-BR': { englishName: 'Portuguese, Brazilian', nativeName: 'PortuguÃĒs do Brasil' }, + ro: { englishName: 'Romanian, Romania', nativeName: 'RomÃĸnă' }, + ru: { englishName: 'Russian', nativeName: 'P҃ҁҁĐēиК' }, + 'sv-SE': { englishName: 'Swedish', nativeName: 'Svenska' }, + th: { englishName: 'Thai', nativeName: 'āš„ā¸—ā¸ĸ' }, + tr: { englishName: 'Turkish', nativeName: 'TÃŧrkçe' }, + uk: { englishName: 'Ukrainian', nativeName: 'ĐŖĐēŅ€Đ°Ņ—ĐŊҁҌĐēа' }, + vi: { englishName: 'Vietnamese', nativeName: 'Tiáēŋng Viáģ‡t' }, + 'zh-CN': { englishName: 'Chinese, China', nativeName: '中文' }, + 'zh-TW': { englishName: 'Chinese, Taiwan', nativeName: 'įšéĢ”ä¸­æ–‡' }, + }; + + public static find(input: string, enabled: boolean): Locale { + return this.findMultiple(input, enabled, 1)[0]; + } + + public static findMultiple( + input: string, + enabled: boolean, + limit: number = Number.MAX_VALUE + ): Locale[] { + let langCodes = enabled ? this.Enabled : Object.values(Locale).sort(); + let search = input.toLowerCase(); + let found = new Set(); + // Exact match + if (found.size < limit) + langCodes + .filter(langCode => langCode.toLowerCase() === search) + .forEach(langCode => found.add(langCode)); + if (found.size < limit) + langCodes + .filter(langCode => this.Data[langCode].nativeName.toLowerCase() === search) + .forEach(langCode => found.add(langCode)); + if (found.size < limit) + langCodes + .filter(langCode => this.Data[langCode].nativeName.toLowerCase() === search) + .forEach(langCode => found.add(langCode)); + if (found.size < limit) + langCodes + .filter(langCode => this.Data[langCode].englishName.toLowerCase() === search) + .forEach(langCode => found.add(langCode)); + // Starts with search term + if (found.size < limit) + langCodes + .filter(langCode => langCode.toLowerCase().startsWith(search)) + .forEach(langCode => found.add(langCode)); + if (found.size < limit) + langCodes + .filter(langCode => this.Data[langCode].nativeName.toLowerCase().startsWith(search)) + .forEach(langCode => found.add(langCode)); + if (found.size < limit) + langCodes + .filter(langCode => + this.Data[langCode].englishName.toLowerCase().startsWith(search) + ) + .forEach(langCode => found.add(langCode)); + // Includes search term + if (found.size < limit) + langCodes + .filter(langCode => langCode.toLowerCase().startsWith(search)) + .forEach(langCode => found.add(langCode)); + if (found.size < limit) + langCodes + .filter(langCode => this.Data[langCode].nativeName.toLowerCase().startsWith(search)) + .forEach(langCode => found.add(langCode)); + if (found.size < limit) + langCodes + .filter(langCode => + this.Data[langCode].englishName.toLowerCase().startsWith(search) + ) + .forEach(langCode => found.add(langCode)); + return [...found]; + } +} diff --git a/src/models/enum-helpers/permission.ts b/src/models/enum-helpers/permission.ts new file mode 100644 index 0000000..2f306c2 --- /dev/null +++ b/src/models/enum-helpers/permission.ts @@ -0,0 +1,244 @@ +import { Locale, PermissionsString } from 'discord.js'; + +import { Lang } from '../../services/index.js'; + +interface PermissionData { + displayName(langCode: Locale): string; +} + +export class Permission { + public static Data: { + [key in PermissionsString]: PermissionData; + } = { + AddReactions: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.AddReactions', langCode); + }, + }, + Administrator: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.Administrator', langCode); + }, + }, + AttachFiles: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.AttachFiles', langCode); + }, + }, + BanMembers: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.BanMembers', langCode); + }, + }, + ChangeNickname: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ChangeNickname', langCode); + }, + }, + Connect: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.Connect', langCode); + }, + }, + CreateInstantInvite: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.CreateInstantInvite', langCode); + }, + }, + CreatePrivateThreads: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.CreatePrivateThreads', langCode); + }, + }, + CreatePublicThreads: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.CreatePublicThreads', langCode); + }, + }, + DeafenMembers: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.DeafenMembers', langCode); + }, + }, + EmbedLinks: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.EmbedLinks', langCode); + }, + }, + KickMembers: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.KickMembers', langCode); + }, + }, + ManageChannels: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageChannels', langCode); + }, + }, + ManageEmojisAndStickers: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageEmojisAndStickers', langCode); + }, + }, + ManageEvents: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageEvents', langCode); + }, + }, + ManageGuild: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageGuild', langCode); + }, + }, + ManageGuildExpressions: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageGuildExpressions', langCode); + }, + }, + ManageMessages: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageMessages', langCode); + }, + }, + ManageNicknames: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageNicknames', langCode); + }, + }, + ManageRoles: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageRoles', langCode); + }, + }, + ManageThreads: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageThreads', langCode); + }, + }, + ManageWebhooks: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ManageWebhooks', langCode); + }, + }, + MentionEveryone: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.MentionEveryone', langCode); + }, + }, + ModerateMembers: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ModerateMembers', langCode); + }, + }, + MoveMembers: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.MoveMembers', langCode); + }, + }, + MuteMembers: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.MuteMembers', langCode); + }, + }, + PrioritySpeaker: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.PrioritySpeaker', langCode); + }, + }, + ReadMessageHistory: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ReadMessageHistory', langCode); + }, + }, + RequestToSpeak: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.RequestToSpeak', langCode); + }, + }, + SendMessages: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.SendMessages', langCode); + }, + }, + SendMessagesInThreads: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.SendMessagesInThreads', langCode); + }, + }, + SendTTSMessages: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.SendTTSMessages', langCode); + }, + }, + SendVoiceMessages: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.SendVoiceMessages', langCode); + }, + }, + Speak: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.Speak', langCode); + }, + }, + Stream: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.Stream', langCode); + }, + }, + UseApplicationCommands: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.UseApplicationCommands', langCode); + }, + }, + UseEmbeddedActivities: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.UseEmbeddedActivities', langCode); + }, + }, + UseExternalEmojis: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.UseExternalEmojis', langCode); + }, + }, + UseExternalSounds: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.UseExternalSounds', langCode); + }, + }, + UseExternalStickers: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.UseExternalStickers', langCode); + }, + }, + UseSoundboard: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.UseSoundboard', langCode); + }, + }, + UseVAD: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.UseVAD', langCode); + }, + }, + ViewAuditLog: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ViewAuditLog', langCode); + }, + }, + ViewChannel: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ViewChannel', langCode); + }, + }, + ViewCreatorMonetizationAnalytics: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ViewCreatorMonetizationAnalytics', langCode); + }, + }, + ViewGuildInsights: { + displayName(langCode: Locale): string { + return Lang.getRef('permissions.ViewGuildInsights', langCode); + }, + }, + }; +} diff --git a/src/models/internal-models.ts b/src/models/internal-models.ts new file mode 100644 index 0000000..7524594 --- /dev/null +++ b/src/models/internal-models.ts @@ -0,0 +1,12 @@ +import { Locale } from 'discord.js'; + +// This class is used to store and pass data along in events +export class EventData { + // TODO: Add any data you want to store + constructor( + // Event language + public lang: Locale, + // Guild language + public langGuild: Locale + ) {} +} diff --git a/src/models/manager.ts b/src/models/manager.ts new file mode 100644 index 0000000..1347a9a --- /dev/null +++ b/src/models/manager.ts @@ -0,0 +1,50 @@ +import { Shard, ShardingManager } from 'discord.js'; +import { createRequire } from 'node:module'; + +import { JobService, Logger } from '../services/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); +let Debug = require('../../config/debug.json'); +let Logs = require('../../lang/logs.json'); + +export class Manager { + constructor(private shardManager: ShardingManager, private jobService: JobService) {} + + public async start(): Promise { + this.registerListeners(); + + let shardList = this.shardManager.shardList as number[]; + + try { + Logger.info( + Logs.info.managerSpawningShards + .replaceAll('{SHARD_COUNT}', shardList.length.toLocaleString()) + .replaceAll('{SHARD_LIST}', shardList.join(', ')) + ); + await this.shardManager.spawn({ + amount: this.shardManager.totalShards, + delay: Config.sharding.spawnDelay * 1000, + timeout: Config.sharding.spawnTimeout * 1000, + }); + Logger.info(Logs.info.managerAllShardsSpawned); + } catch (error) { + Logger.error(Logs.error.managerSpawningShards, error); + return; + } + + if (Debug.dummyMode.enabled) { + return; + } + + this.jobService.start(); + } + + private registerListeners(): void { + this.shardManager.on('shardCreate', shard => this.onShardCreate(shard)); + } + + private onShardCreate(shard: Shard): void { + Logger.info(Logs.info.managerLaunchedShard.replaceAll('{SHARD_ID}', shard.id.toString())); + } +} diff --git a/src/models/master-api/clusters.ts b/src/models/master-api/clusters.ts new file mode 100644 index 0000000..85cfa61 --- /dev/null +++ b/src/models/master-api/clusters.ts @@ -0,0 +1,42 @@ +import { Type } from 'class-transformer'; +import { + IsDefined, + IsInt, + IsPositive, + IsString, + IsUrl, + Length, + ValidateNested, +} from 'class-validator'; + +export class Callback { + @IsDefined() + @IsUrl({ require_tld: false }) + url: string; + + @IsDefined() + @IsString() + @Length(5, 2000) + token: string; +} + +export class RegisterClusterRequest { + @IsDefined() + @IsInt() + @IsPositive() + shardCount: number; + + @IsDefined() + @ValidateNested() + @Type(() => Callback) + callback: Callback; +} + +export interface RegisterClusterResponse { + id: string; +} + +export interface LoginClusterResponse { + shardList: number[]; + totalShards: number; +} diff --git a/src/models/master-api/index.ts b/src/models/master-api/index.ts new file mode 100644 index 0000000..7c61b05 --- /dev/null +++ b/src/models/master-api/index.ts @@ -0,0 +1,5 @@ +export { + RegisterClusterRequest, + RegisterClusterResponse, + LoginClusterResponse, +} from './clusters.js'; diff --git a/src/reactions/index.ts b/src/reactions/index.ts new file mode 100644 index 0000000..1de7c49 --- /dev/null +++ b/src/reactions/index.ts @@ -0,0 +1 @@ +export { Reaction } from './reaction.js'; diff --git a/src/reactions/reaction.ts b/src/reactions/reaction.ts new file mode 100644 index 0000000..1e09403 --- /dev/null +++ b/src/reactions/reaction.ts @@ -0,0 +1,16 @@ +import { Message, MessageReaction, User } from 'discord.js'; + +import { EventData } from '../models/internal-models.js'; + +export interface Reaction { + emoji: string; + requireGuild: boolean; + requireSentByClient: boolean; + requireEmbedAuthorTag: boolean; + execute( + msgReaction: MessageReaction, + msg: Message, + reactor: User, + data: EventData + ): Promise; +} diff --git a/src/services/command-registration-service.ts b/src/services/command-registration-service.ts new file mode 100644 index 0000000..5861a91 --- /dev/null +++ b/src/services/command-registration-service.ts @@ -0,0 +1,157 @@ +import { REST } from '@discordjs/rest'; +import { + APIApplicationCommand, + RESTGetAPIApplicationCommandsResult, + RESTPatchAPIApplicationCommandJSONBody, + RESTPostAPIApplicationCommandsJSONBody, + Routes, +} from 'discord.js'; +import { createRequire } from 'node:module'; + +import { Logger } from './logger.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); +let Logs = require('../../lang/logs.json'); + +export class CommandRegistrationService { + constructor(private rest: REST) {} + + public async process( + localCmds: RESTPostAPIApplicationCommandsJSONBody[], + args: string[] + ): Promise { + let remoteCmds = (await this.rest.get( + Routes.applicationCommands(Config.client.id) + )) as RESTGetAPIApplicationCommandsResult; + + let localCmdsOnRemote = localCmds.filter(localCmd => + remoteCmds.some(remoteCmd => remoteCmd.name === localCmd.name) + ); + let localCmdsOnly = localCmds.filter( + localCmd => !remoteCmds.some(remoteCmd => remoteCmd.name === localCmd.name) + ); + let remoteCmdsOnly = remoteCmds.filter( + remoteCmd => !localCmds.some(localCmd => localCmd.name === remoteCmd.name) + ); + + switch (args[3]) { + case 'view': { + Logger.info( + Logs.info.commandActionView + .replaceAll( + '{LOCAL_AND_REMOTE_LIST}', + this.formatCommandList(localCmdsOnRemote) + ) + .replaceAll('{LOCAL_ONLY_LIST}', this.formatCommandList(localCmdsOnly)) + .replaceAll('{REMOTE_ONLY_LIST}', this.formatCommandList(remoteCmdsOnly)) + ); + return; + } + case 'register': { + if (localCmdsOnly.length > 0) { + Logger.info( + Logs.info.commandActionCreating.replaceAll( + '{COMMAND_LIST}', + this.formatCommandList(localCmdsOnly) + ) + ); + for (let localCmd of localCmdsOnly) { + await this.rest.post(Routes.applicationCommands(Config.client.id), { + body: localCmd, + }); + } + Logger.info(Logs.info.commandActionCreated); + } + + if (localCmdsOnRemote.length > 0) { + Logger.info( + Logs.info.commandActionUpdating.replaceAll( + '{COMMAND_LIST}', + this.formatCommandList(localCmdsOnRemote) + ) + ); + for (let localCmd of localCmdsOnRemote) { + await this.rest.post(Routes.applicationCommands(Config.client.id), { + body: localCmd, + }); + } + Logger.info(Logs.info.commandActionUpdated); + } + + return; + } + case 'rename': { + let oldName = args[4]; + let newName = args[5]; + if (!(oldName && newName)) { + Logger.error(Logs.error.commandActionRenameMissingArg); + return; + } + + let remoteCmd = remoteCmds.find(remoteCmd => remoteCmd.name == oldName); + if (!remoteCmd) { + Logger.error( + Logs.error.commandActionNotFound.replaceAll('{COMMAND_NAME}', oldName) + ); + return; + } + + Logger.info( + Logs.info.commandActionRenaming + .replaceAll('{OLD_COMMAND_NAME}', remoteCmd.name) + .replaceAll('{NEW_COMMAND_NAME}', newName) + ); + let body: RESTPatchAPIApplicationCommandJSONBody = { + name: newName, + }; + await this.rest.patch(Routes.applicationCommand(Config.client.id, remoteCmd.id), { + body, + }); + Logger.info(Logs.info.commandActionRenamed); + return; + } + case 'delete': { + let name = args[4]; + if (!name) { + Logger.error(Logs.error.commandActionDeleteMissingArg); + return; + } + + let remoteCmd = remoteCmds.find(remoteCmd => remoteCmd.name == name); + if (!remoteCmd) { + Logger.error( + Logs.error.commandActionNotFound.replaceAll('{COMMAND_NAME}', name) + ); + return; + } + + Logger.info( + Logs.info.commandActionDeleting.replaceAll('{COMMAND_NAME}', remoteCmd.name) + ); + await this.rest.delete(Routes.applicationCommand(Config.client.id, remoteCmd.id)); + Logger.info(Logs.info.commandActionDeleted); + return; + } + case 'clear': { + Logger.info( + Logs.info.commandActionClearing.replaceAll( + '{COMMAND_LIST}', + this.formatCommandList(remoteCmds) + ) + ); + await this.rest.put(Routes.applicationCommands(Config.client.id), { body: [] }); + Logger.info(Logs.info.commandActionCleared); + return; + } + } + } + + private formatCommandList( + cmds: RESTPostAPIApplicationCommandsJSONBody[] | APIApplicationCommand[] + ): string { + return cmds.length > 0 + ? cmds.map((cmd: { name: string }) => `'${cmd.name}'`).join(', ') + : 'N/A'; + } +} diff --git a/src/services/event-data-service.ts b/src/services/event-data-service.ts new file mode 100644 index 0000000..39dbf52 --- /dev/null +++ b/src/services/event-data-service.ts @@ -0,0 +1,39 @@ +import { + Channel, + CommandInteractionOptionResolver, + Guild, + PartialDMChannel, + User, +} from 'discord.js'; + +import { Language } from '../models/enum-helpers/language.js'; +import { EventData } from '../models/internal-models.js'; + +export class EventDataService { + public async create( + options: { + user?: User; + channel?: Channel | PartialDMChannel; + guild?: Guild; + args?: Omit; + } = {} + ): Promise { + // TODO: Retrieve any data you want to pass along in events + + // Event language + let lang = + options.guild?.preferredLocale && + Language.Enabled.includes(options.guild.preferredLocale) + ? options.guild.preferredLocale + : Language.Default; + + // Guild language + let langGuild = + options.guild?.preferredLocale && + Language.Enabled.includes(options.guild.preferredLocale) + ? options.guild.preferredLocale + : Language.Default; + + return new EventData(lang, langGuild); + } +} diff --git a/src/services/http-service.ts b/src/services/http-service.ts new file mode 100644 index 0000000..adaf3e4 --- /dev/null +++ b/src/services/http-service.ts @@ -0,0 +1,54 @@ +import fetch, { Response } from 'node-fetch'; +import { URL } from 'node:url'; + +export class HttpService { + public async get(url: string | URL, authorization: string): Promise { + return await fetch(url.toString(), { + method: 'get', + headers: { + Authorization: authorization, + Accept: 'application/json', + }, + }); + } + + public async post(url: string | URL, authorization: string, body?: object): Promise { + return await fetch(url.toString(), { + method: 'post', + headers: { + Authorization: authorization, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }); + } + + public async put(url: string | URL, authorization: string, body?: object): Promise { + return await fetch(url.toString(), { + method: 'put', + headers: { + Authorization: authorization, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }); + } + + public async delete( + url: string | URL, + authorization: string, + body?: object + ): Promise { + return await fetch(url.toString(), { + method: 'delete', + headers: { + Authorization: authorization, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }); + } +} diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..fc90dad --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,7 @@ +export { CommandRegistrationService } from './command-registration-service.js'; +export { EventDataService } from './event-data-service.js'; +export { HttpService } from './http-service.js'; +export { JobService } from './job-service.js'; +export { Lang } from './lang.js'; +export { Logger } from './logger.js'; +export { MasterApiService } from './master-api-service.js'; diff --git a/src/services/job-service.ts b/src/services/job-service.ts new file mode 100644 index 0000000..68f76b6 --- /dev/null +++ b/src/services/job-service.ts @@ -0,0 +1,53 @@ +import parser from 'cron-parser'; +import { DateTime } from 'luxon'; +import schedule from 'node-schedule'; +import { createRequire } from 'node:module'; + +import { Logger } from './index.js'; +import { Job } from '../jobs/index.js'; + +const require = createRequire(import.meta.url); +let Logs = require('../../lang/logs.json'); + +export class JobService { + constructor(private jobs: Job[]) {} + + public start(): void { + for (let job of this.jobs) { + let jobSchedule = job.runOnce + ? parser + .parseExpression(job.schedule, { + currentDate: DateTime.now() + .plus({ seconds: job.initialDelaySecs }) + .toJSDate(), + }) + .next() + .toDate() + : { + start: DateTime.now().plus({ seconds: job.initialDelaySecs }).toJSDate(), + rule: job.schedule, + }; + + schedule.scheduleJob(jobSchedule, async () => { + try { + if (job.log) { + Logger.info(Logs.info.jobRun.replaceAll('{JOB}', job.name)); + } + + await job.run(); + + if (job.log) { + Logger.info(Logs.info.jobCompleted.replaceAll('{JOB}', job.name)); + } + } catch (error) { + Logger.error(Logs.error.job.replaceAll('{JOB}', job.name), error); + } + }); + Logger.info( + Logs.info.jobScheduled + .replaceAll('{JOB}', job.name) + .replaceAll('{SCHEDULE}', job.schedule) + ); + } + } +} diff --git a/src/services/lang.ts b/src/services/lang.ts new file mode 100644 index 0000000..a71599b --- /dev/null +++ b/src/services/lang.ts @@ -0,0 +1,83 @@ +import { EmbedBuilder, Locale, LocalizationMap, resolveColor } from 'discord.js'; +import { Linguini, TypeMapper, TypeMappers, Utils } from 'linguini'; +import path, { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { Language } from '../models/enum-helpers/index.js'; + +export class Lang { + private static linguini = new Linguini( + path.resolve(dirname(fileURLToPath(import.meta.url)), '../../lang'), + 'lang' + ); + + public static getEmbed( + location: string, + langCode: Locale, + variables?: { [name: string]: string } + ): EmbedBuilder { + return ( + this.linguini.get(location, langCode, this.embedTm, variables) ?? + this.linguini.get(location, Language.Default, this.embedTm, variables) + ); + } + + public static getRegex(location: string, langCode: Locale): RegExp { + return ( + this.linguini.get(location, langCode, TypeMappers.RegExp) ?? + this.linguini.get(location, Language.Default, TypeMappers.RegExp) + ); + } + + public static getRef( + location: string, + langCode: Locale, + variables?: { [name: string]: string } + ): string { + return ( + this.linguini.getRef(location, langCode, variables) ?? + this.linguini.getRef(location, Language.Default, variables) + ); + } + + public static getRefLocalizationMap( + location: string, + variables?: { [name: string]: string } + ): LocalizationMap { + let obj = {}; + for (let langCode of Language.Enabled) { + obj[langCode] = this.getRef(location, langCode, variables); + } + return obj; + } + + public static getCom(location: string, variables?: { [name: string]: string }): string { + return this.linguini.getCom(location, variables); + } + + private static embedTm: TypeMapper = (jsonValue: any) => { + return new EmbedBuilder({ + author: jsonValue.author, + title: Utils.join(jsonValue.title, '\n'), + url: jsonValue.url, + thumbnail: { + url: jsonValue.thumbnail, + }, + description: Utils.join(jsonValue.description, '\n'), + fields: jsonValue.fields?.map(field => ({ + name: Utils.join(field.name, '\n'), + value: Utils.join(field.value, '\n'), + inline: field.inline ? field.inline : false, + })), + image: { + url: jsonValue.image, + }, + footer: { + text: Utils.join(jsonValue.footer?.text, '\n'), + iconURL: jsonValue.footer?.icon, + }, + timestamp: jsonValue.timestamp ? Date.now() : undefined, + color: resolveColor(jsonValue.color ?? Lang.getCom('colors.default')), + }); + }; +} diff --git a/src/services/logger.ts b/src/services/logger.ts new file mode 100644 index 0000000..8a48067 --- /dev/null +++ b/src/services/logger.ts @@ -0,0 +1,92 @@ +import { DiscordAPIError } from 'discord.js'; +import { Response } from 'node-fetch'; +import { createRequire } from 'node:module'; +import pino from 'pino'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); + +let logger = pino( + { + formatters: { + level: label => { + return { level: label }; + }, + }, + }, + Config.logging.pretty + ? pino.transport({ + target: 'pino-pretty', + options: { + colorize: true, + ignore: 'pid,hostname', + translateTime: 'yyyy-mm-dd HH:MM:ss.l', + }, + }) + : undefined +); + +export class Logger { + private static shardId: number; + + public static info(message: string, obj?: any): void { + obj ? logger.info(obj, message) : logger.info(message); + } + + public static warn(message: string, obj?: any): void { + obj ? logger.warn(obj, message) : logger.warn(message); + } + + public static async error(message: string, obj?: any): Promise { + // Log just a message if no error object + if (!obj) { + logger.error(message); + return; + } + + // Otherwise log details about the error + if (typeof obj === 'string') { + logger + .child({ + message: obj, + }) + .error(message); + } else if (obj instanceof Response) { + let resText: string; + try { + resText = await obj.text(); + } catch { + // Ignore + } + logger + .child({ + path: obj.url, + statusCode: obj.status, + statusName: obj.statusText, + headers: obj.headers.raw(), + body: resText, + }) + .error(message); + } else if (obj instanceof DiscordAPIError) { + logger + .child({ + message: obj.message, + code: obj.code, + statusCode: obj.status, + method: obj.method, + url: obj.url, + stack: obj.stack, + }) + .error(message); + } else { + logger.error(obj, message); + } + } + + public static setShardId(shardId: number): void { + if (this.shardId !== shardId) { + this.shardId = shardId; + logger = logger.child({ shardId }); + } + } +} diff --git a/src/services/master-api-service.ts b/src/services/master-api-service.ts new file mode 100644 index 0000000..66a88f1 --- /dev/null +++ b/src/services/master-api-service.ts @@ -0,0 +1,65 @@ +import { createRequire } from 'node:module'; +import { URL } from 'node:url'; + +import { HttpService } from './index.js'; +import { + LoginClusterResponse, + RegisterClusterRequest, + RegisterClusterResponse, +} from '../models/master-api/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../../config/config.json'); + +export class MasterApiService { + private clusterId: string; + + constructor(private httpService: HttpService) {} + + public async register(): Promise { + let reqBody: RegisterClusterRequest = { + shardCount: Config.clustering.shardCount, + callback: { + url: Config.clustering.callbackUrl, + token: Config.api.secret, + }, + }; + + let res = await this.httpService.post( + new URL('/clusters', Config.clustering.masterApi.url), + Config.clustering.masterApi.token, + reqBody + ); + + if (!res.ok) { + throw res; + } + + let resBody = (await res.json()) as RegisterClusterResponse; + this.clusterId = resBody.id; + } + + public async login(): Promise { + let res = await this.httpService.put( + new URL(`/clusters/${this.clusterId}/login`, Config.clustering.masterApi.url), + Config.clustering.masterApi.token + ); + + if (!res.ok) { + throw res; + } + + return (await res.json()) as LoginClusterResponse; + } + + public async ready(): Promise { + let res = await this.httpService.put( + new URL(`/clusters/${this.clusterId}/ready`, Config.clustering.masterApi.url), + Config.clustering.masterApi.token + ); + + if (!res.ok) { + throw res; + } + } +} diff --git a/src/start-bot.ts b/src/start-bot.ts new file mode 100644 index 0000000..45cd032 --- /dev/null +++ b/src/start-bot.ts @@ -0,0 +1,143 @@ +import { REST } from '@discordjs/rest'; +import { Options, Partials } from 'discord.js'; +import { createRequire } from 'node:module'; + +import { Button } from './buttons/index.js'; +import { DevCommand, HelpCommand, InfoCommand, TestCommand } from './commands/chat/index.js'; +import { + ChatCommandMetadata, + Command, + MessageCommandMetadata, + UserCommandMetadata, +} from './commands/index.js'; +import { ViewDateSent } from './commands/message/index.js'; +import { ViewDateJoined } from './commands/user/index.js'; +import { + ButtonHandler, + CommandHandler, + GuildJoinHandler, + GuildLeaveHandler, + MessageHandler, + ReactionHandler, + TriggerHandler, +} from './events/index.js'; +import { CustomClient } from './extensions/index.js'; +import { Job } from './jobs/index.js'; +import { Bot } from './models/bot.js'; +import { Reaction } from './reactions/index.js'; +import { + CommandRegistrationService, + EventDataService, + JobService, + Logger, +} from './services/index.js'; +import { Trigger } from './triggers/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../config/config.json'); +let Logs = require('../lang/logs.json'); + +async function start(): Promise { + // Services + let eventDataService = new EventDataService(); + + // Client + let client = new CustomClient({ + intents: Config.client.intents, + partials: (Config.client.partials as string[]).map(partial => Partials[partial]), + makeCache: Options.cacheWithLimits({ + // Keep default caching behavior + ...Options.DefaultMakeCacheSettings, + // Override specific options from config + ...Config.client.caches, + }), + }); + + // Commands + let commands: Command[] = [ + // Chat Commands + new DevCommand(), + new HelpCommand(), + new InfoCommand(), + new TestCommand(), + + // Message Context Commands + new ViewDateSent(), + + // User Context Commands + new ViewDateJoined(), + + // TODO: Add new commands here + ]; + + // Buttons + let buttons: Button[] = [ + // TODO: Add new buttons here + ]; + + // Reactions + let reactions: Reaction[] = [ + // TODO: Add new reactions here + ]; + + // Triggers + let triggers: Trigger[] = [ + // TODO: Add new triggers here + ]; + + // Event handlers + let guildJoinHandler = new GuildJoinHandler(eventDataService); + let guildLeaveHandler = new GuildLeaveHandler(); + let commandHandler = new CommandHandler(commands, eventDataService); + let buttonHandler = new ButtonHandler(buttons, eventDataService); + let triggerHandler = new TriggerHandler(triggers, eventDataService); + let messageHandler = new MessageHandler(triggerHandler); + let reactionHandler = new ReactionHandler(reactions, eventDataService); + + // Jobs + let jobs: Job[] = [ + // TODO: Add new jobs here + ]; + + // Bot + let bot = new Bot( + Config.client.token, + client, + guildJoinHandler, + guildLeaveHandler, + messageHandler, + commandHandler, + buttonHandler, + reactionHandler, + new JobService(jobs) + ); + + // Register + if (process.argv[2] == 'commands') { + try { + let rest = new REST({ version: '10' }).setToken(Config.client.token); + let commandRegistrationService = new CommandRegistrationService(rest); + let localCmds = [ + ...Object.values(ChatCommandMetadata).sort((a, b) => (a.name > b.name ? 1 : -1)), + ...Object.values(MessageCommandMetadata).sort((a, b) => (a.name > b.name ? 1 : -1)), + ...Object.values(UserCommandMetadata).sort((a, b) => (a.name > b.name ? 1 : -1)), + ]; + await commandRegistrationService.process(localCmds, process.argv); + } catch (error) { + Logger.error(Logs.error.commandAction, error); + } + // Wait for any final logs to be written. + await new Promise(resolve => setTimeout(resolve, 1000)); + process.exit(); + } + + await bot.start(); +} + +process.on('unhandledRejection', (reason, _promise) => { + Logger.error(Logs.error.unhandledRejection, reason); +}); + +start().catch(error => { + Logger.error(Logs.error.unspecified, error); +}); diff --git a/src/start-manager.ts b/src/start-manager.ts new file mode 100644 index 0000000..dfdfe66 --- /dev/null +++ b/src/start-manager.ts @@ -0,0 +1,90 @@ +import { ShardingManager } from 'discord.js'; +import { createRequire } from 'node:module'; +import 'reflect-metadata'; + +import { GuildsController, RootController, ShardsController } from './controllers/index.js'; +import { Job, UpdateServerCountJob } from './jobs/index.js'; +import { Api } from './models/api.js'; +import { Manager } from './models/manager.js'; +import { HttpService, JobService, Logger, MasterApiService } from './services/index.js'; +import { MathUtils, ShardUtils } from './utils/index.js'; + +const require = createRequire(import.meta.url); +let Config = require('../config/config.json'); +let Debug = require('../config/debug.json'); +let Logs = require('../lang/logs.json'); + +async function start(): Promise { + Logger.info(Logs.info.appStarted); + + // Dependencies + let httpService = new HttpService(); + let masterApiService = new MasterApiService(httpService); + if (Config.clustering.enabled) { + await masterApiService.register(); + } + + // Sharding + let shardList: number[]; + let totalShards: number; + try { + if (Config.clustering.enabled) { + let resBody = await masterApiService.login(); + shardList = resBody.shardList; + let requiredShards = await ShardUtils.requiredShardCount(Config.client.token); + totalShards = Math.max(requiredShards, resBody.totalShards); + } else { + let recommendedShards = await ShardUtils.recommendedShardCount( + Config.client.token, + Config.sharding.serversPerShard + ); + shardList = MathUtils.range(0, recommendedShards); + totalShards = recommendedShards; + } + } catch (error) { + Logger.error(Logs.error.retrieveShards, error); + return; + } + + if (shardList.length === 0) { + Logger.warn(Logs.warn.managerNoShards); + return; + } + + let shardManager = new ShardingManager('dist/start-bot.js', { + token: Config.client.token, + mode: Debug.override.shardMode.enabled ? Debug.override.shardMode.value : 'process', + respawn: true, + totalShards, + shardList, + }); + + // Jobs + let jobs: Job[] = [ + Config.clustering.enabled ? undefined : new UpdateServerCountJob(shardManager, httpService), + // TODO: Add new jobs here + ].filter(Boolean); + + let manager = new Manager(shardManager, new JobService(jobs)); + + // API + let guildsController = new GuildsController(shardManager); + let shardsController = new ShardsController(shardManager); + let rootController = new RootController(); + let api = new Api([guildsController, shardsController, rootController]); + + // Start + await manager.start(); + await api.start(); + if (Config.clustering.enabled) { + await masterApiService.ready(); + } +} + +process.on('unhandledRejection', (reason, _promise) => { + Logger.error(Logs.error.unhandledRejection, reason); +}); + +start().catch(error => { + Logger.error(Logs.error.unspecified, error); +}); diff --git a/src/triggers/index.ts b/src/triggers/index.ts new file mode 100644 index 0000000..3f50751 --- /dev/null +++ b/src/triggers/index.ts @@ -0,0 +1 @@ +export { Trigger } from './trigger.js'; diff --git a/src/triggers/trigger.ts b/src/triggers/trigger.ts new file mode 100644 index 0000000..d5ebebd --- /dev/null +++ b/src/triggers/trigger.ts @@ -0,0 +1,9 @@ +import { Message } from 'discord.js'; + +import { EventData } from '../models/internal-models.js'; + +export interface Trigger { + requireGuild: boolean; + triggered(msg: Message): boolean; + execute(msg: Message, data: EventData): Promise; +} diff --git a/src/utils/client-utils.ts b/src/utils/client-utils.ts new file mode 100644 index 0000000..2092dc4 --- /dev/null +++ b/src/utils/client-utils.ts @@ -0,0 +1,247 @@ +import { + ApplicationCommand, + Channel, + Client, + DiscordAPIError, + RESTJSONErrorCodes as DiscordApiErrors, + Guild, + GuildMember, + Locale, + NewsChannel, + Role, + StageChannel, + TextChannel, + User, + VoiceChannel, +} from 'discord.js'; + +import { PermissionUtils, RegexUtils } from './index.js'; +import { Lang } from '../services/index.js'; + +const FETCH_MEMBER_LIMIT = 20; +const IGNORED_ERRORS = [ + DiscordApiErrors.UnknownMessage, + DiscordApiErrors.UnknownChannel, + DiscordApiErrors.UnknownGuild, + DiscordApiErrors.UnknownMember, + DiscordApiErrors.UnknownUser, + DiscordApiErrors.UnknownInteraction, + DiscordApiErrors.MissingAccess, +]; + +export class ClientUtils { + public static async getGuild(client: Client, discordId: string): Promise { + discordId = RegexUtils.discordId(discordId); + if (!discordId) { + return; + } + + try { + return await client.guilds.fetch(discordId); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async getChannel(client: Client, discordId: string): Promise { + discordId = RegexUtils.discordId(discordId); + if (!discordId) { + return; + } + + try { + return await client.channels.fetch(discordId); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async getUser(client: Client, discordId: string): Promise { + discordId = RegexUtils.discordId(discordId); + if (!discordId) { + return; + } + + try { + return await client.users.fetch(discordId); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async findAppCommand(client: Client, name: string): Promise { + let commands = await client.application.commands.fetch(); + return commands.find(command => command.name === name); + } + + public static async findMember(guild: Guild, input: string): Promise { + try { + let discordId = RegexUtils.discordId(input); + if (discordId) { + return await guild.members.fetch(discordId); + } + + let tag = RegexUtils.tag(input); + if (tag) { + return ( + await guild.members.fetch({ query: tag.username, limit: FETCH_MEMBER_LIMIT }) + ).find(member => member.user.discriminator === tag.discriminator); + } + + return (await guild.members.fetch({ query: input, limit: 1 })).first(); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async findRole(guild: Guild, input: string): Promise { + try { + let discordId = RegexUtils.discordId(input); + if (discordId) { + return await guild.roles.fetch(discordId); + } + + let search = input.trim().toLowerCase().replace(/^@/, ''); + let roles = await guild.roles.fetch(); + return ( + roles.find(role => role.name.toLowerCase() === search) ?? + roles.find(role => role.name.toLowerCase().includes(search)) + ); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async findTextChannel( + guild: Guild, + input: string + ): Promise { + try { + let discordId = RegexUtils.discordId(input); + if (discordId) { + let channel = await guild.channels.fetch(discordId); + if (channel instanceof NewsChannel || channel instanceof TextChannel) { + return channel; + } else { + return; + } + } + + let search = input.trim().toLowerCase().replace(/^#/, '').replaceAll(' ', '-'); + let channels = [...(await guild.channels.fetch()).values()] + .filter(channel => channel instanceof NewsChannel || channel instanceof TextChannel) + .map(channel => channel as NewsChannel | TextChannel); + return ( + channels.find(channel => channel.name.toLowerCase() === search) ?? + channels.find(channel => channel.name.toLowerCase().includes(search)) + ); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async findVoiceChannel( + guild: Guild, + input: string + ): Promise { + try { + let discordId = RegexUtils.discordId(input); + if (discordId) { + let channel = await guild.channels.fetch(discordId); + if (channel instanceof VoiceChannel || channel instanceof StageChannel) { + return channel; + } else { + return; + } + } + + let search = input.trim().toLowerCase().replace(/^#/, ''); + let channels = [...(await guild.channels.fetch()).values()] + .filter( + channel => channel instanceof VoiceChannel || channel instanceof StageChannel + ) + .map(channel => channel as VoiceChannel | StageChannel); + return ( + channels.find(channel => channel.name.toLowerCase() === search) ?? + channels.find(channel => channel.name.toLowerCase().includes(search)) + ); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async findNotifyChannel( + guild: Guild, + langCode: Locale + ): Promise { + // Prefer the system channel + let systemChannel = guild.systemChannel; + if (systemChannel && PermissionUtils.canSend(systemChannel, true)) { + return systemChannel; + } + + // Otherwise look for a bot channel + return (await guild.channels.fetch()).find( + channel => + (channel instanceof TextChannel || channel instanceof NewsChannel) && + PermissionUtils.canSend(channel, true) && + Lang.getRegex('channelRegexes.bot', langCode).test(channel.name) + ) as TextChannel | NewsChannel; + } +} diff --git a/src/utils/command-utils.ts b/src/utils/command-utils.ts new file mode 100644 index 0000000..65ecc51 --- /dev/null +++ b/src/utils/command-utils.ts @@ -0,0 +1,73 @@ +import { + CommandInteraction, + GuildChannel, + MessageComponentInteraction, + ModalSubmitInteraction, + ThreadChannel, +} from 'discord.js'; + +import { FormatUtils, InteractionUtils } from './index.js'; +import { Command } from '../commands/index.js'; +import { Permission } from '../models/enum-helpers/index.js'; +import { EventData } from '../models/internal-models.js'; +import { Lang } from '../services/index.js'; + +export class CommandUtils { + public static findCommand(commands: Command[], commandParts: string[]): Command { + let found = [...commands]; + let closestMatch: Command; + for (let [index, commandPart] of commandParts.entries()) { + found = found.filter(command => command.names[index] === commandPart); + if (found.length === 0) { + return closestMatch; + } + + if (found.length === 1) { + return found[0]; + } + + let exactMatch = found.find(command => command.names.length === index + 1); + if (exactMatch) { + closestMatch = exactMatch; + } + } + return closestMatch; + } + + public static async runChecks( + command: Command, + intr: CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction, + data: EventData + ): Promise { + if (command.cooldown) { + let limited = command.cooldown.take(intr.user.id); + if (limited) { + await InteractionUtils.send( + intr, + Lang.getEmbed('validationEmbeds.cooldownHit', data.lang, { + AMOUNT: command.cooldown.amount.toLocaleString(data.lang), + INTERVAL: FormatUtils.duration(command.cooldown.interval, data.lang), + }) + ); + return false; + } + } + + if ( + (intr.channel instanceof GuildChannel || intr.channel instanceof ThreadChannel) && + !intr.channel.permissionsFor(intr.client.user).has(command.requireClientPerms) + ) { + await InteractionUtils.send( + intr, + Lang.getEmbed('validationEmbeds.missingClientPerms', data.lang, { + PERMISSIONS: command.requireClientPerms + .map(perm => `**${Permission.Data[perm].displayName(data.lang)}**`) + .join(', '), + }) + ); + return false; + } + + return true; + } +} diff --git a/src/utils/format-utils.ts b/src/utils/format-utils.ts new file mode 100644 index 0000000..2d08bf9 --- /dev/null +++ b/src/utils/format-utils.ts @@ -0,0 +1,57 @@ +import { ApplicationCommand, Guild, Locale } from 'discord.js'; +import { filesize } from 'filesize'; +import { Duration } from 'luxon'; + +export class FormatUtils { + public static roleMention(guild: Guild, discordId: string): string { + if (discordId === '@here') { + return discordId; + } + + if (discordId === guild.id) { + return '@everyone'; + } + + return `<@&${discordId}>`; + } + + public static channelMention(discordId: string): string { + return `<#${discordId}>`; + } + + public static userMention(discordId: string): string { + return `<@!${discordId}>`; + } + + // TODO: Replace with ApplicationCommand#toString() once discord.js #8818 is merged + // https://github.com/discordjs/discord.js/pull/8818 + public static commandMention(command: ApplicationCommand, subParts: string[] = []): string { + let name = [command.name, ...subParts].join(' '); + return ``; + } + + public static duration(milliseconds: number, langCode: Locale): string { + return Duration.fromObject( + Object.fromEntries( + Object.entries( + Duration.fromMillis(milliseconds, { locale: langCode }) + .shiftTo( + 'year', + 'quarter', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second' + ) + .toObject() + ).filter(([_, value]) => !!value) // Remove units that are 0 + ) + ).toHuman({ maximumFractionDigits: 0 }); + } + + public static fileSize(bytes: number): string { + return filesize(bytes, { output: 'string', pad: true, round: 2 }) as string; + } +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..c7d69fd --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,13 @@ +export { ClientUtils } from './client-utils.js'; +export { CommandUtils } from './command-utils.js'; +export { FormatUtils } from './format-utils.js'; +export { InteractionUtils } from './interaction-utils.js'; +export { MathUtils } from './math-utils.js'; +export { MessageUtils } from './message-utils.js'; +export { PartialUtils } from './partial-utils.js'; +export { PermissionUtils } from './permission-utils.js'; +export { RandomUtils } from './random-utils.js'; +export { RegexUtils } from './regex-utils.js'; +export { ShardUtils } from './shard-utils.js'; +export { StringUtils } from './string-utils.js'; +export { ThreadUtils } from './thread-utils.js'; diff --git a/src/utils/interaction-utils.ts b/src/utils/interaction-utils.ts new file mode 100644 index 0000000..d0e4cd2 --- /dev/null +++ b/src/utils/interaction-utils.ts @@ -0,0 +1,176 @@ +import { + ApplicationCommandOptionChoiceData, + AutocompleteInteraction, + CommandInteraction, + DiscordAPIError, + RESTJSONErrorCodes as DiscordApiErrors, + EmbedBuilder, + InteractionReplyOptions, + InteractionResponse, + InteractionUpdateOptions, + Message, + MessageComponentInteraction, + ModalSubmitInteraction, + WebhookMessageEditOptions, +} from 'discord.js'; + +const IGNORED_ERRORS = [ + DiscordApiErrors.UnknownMessage, + DiscordApiErrors.UnknownChannel, + DiscordApiErrors.UnknownGuild, + DiscordApiErrors.UnknownUser, + DiscordApiErrors.UnknownInteraction, + DiscordApiErrors.CannotSendMessagesToThisUser, // User blocked bot or DM disabled + DiscordApiErrors.ReactionWasBlocked, // User blocked bot or DM disabled + DiscordApiErrors.MaximumActiveThreads, +]; + +export class InteractionUtils { + public static async deferReply( + intr: CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction, + hidden: boolean = false + ): Promise { + try { + return await intr.deferReply({ + ephemeral: hidden, + }); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async deferUpdate( + intr: MessageComponentInteraction | ModalSubmitInteraction + ): Promise { + try { + return await intr.deferUpdate(); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async send( + intr: CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction, + content: string | EmbedBuilder | InteractionReplyOptions, + hidden: boolean = false + ): Promise { + try { + let options: InteractionReplyOptions = + typeof content === 'string' + ? { content } + : content instanceof EmbedBuilder + ? { embeds: [content] } + : content; + if (intr.deferred || intr.replied) { + return await intr.followUp({ + ...options, + ephemeral: hidden, + }); + } else { + return await intr.reply({ + ...options, + ephemeral: hidden, + fetchReply: true, + }); + } + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async respond( + intr: AutocompleteInteraction, + choices: ApplicationCommandOptionChoiceData[] = [] + ): Promise { + try { + return await intr.respond(choices); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async editReply( + intr: CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction, + content: string | EmbedBuilder | WebhookMessageEditOptions + ): Promise { + try { + let options: WebhookMessageEditOptions = + typeof content === 'string' + ? { content } + : content instanceof EmbedBuilder + ? { embeds: [content] } + : content; + return await intr.editReply(options); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async update( + intr: MessageComponentInteraction, + content: string | EmbedBuilder | InteractionUpdateOptions + ): Promise { + try { + let options: InteractionUpdateOptions = + typeof content === 'string' + ? { content } + : content instanceof EmbedBuilder + ? { embeds: [content] } + : content; + return await intr.update({ + ...options, + fetchReply: true, + }); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } +} diff --git a/src/utils/math-utils.ts b/src/utils/math-utils.ts new file mode 100644 index 0000000..bd1fc18 --- /dev/null +++ b/src/utils/math-utils.ts @@ -0,0 +1,17 @@ +export class MathUtils { + public static sum(numbers: number[]): number { + return numbers.reduce((a, b) => a + b, 0); + } + + public static clamp(input: number, min: number, max: number): number { + return Math.min(Math.max(input, min), max); + } + + public static range(start: number, size: number): number[] { + return [...Array(size).keys()].map(i => i + start); + } + + public static ceilToMultiple(input: number, multiple: number): number { + return Math.ceil(input / multiple) * multiple; + } +} diff --git a/src/utils/message-utils.ts b/src/utils/message-utils.ts new file mode 100644 index 0000000..0aee13c --- /dev/null +++ b/src/utils/message-utils.ts @@ -0,0 +1,169 @@ +import { + BaseMessageOptions, + DiscordAPIError, + RESTJSONErrorCodes as DiscordApiErrors, + EmbedBuilder, + EmojiResolvable, + Message, + MessageEditOptions, + MessageReaction, + StartThreadOptions, + TextBasedChannel, + ThreadChannel, + User, +} from 'discord.js'; + +const IGNORED_ERRORS = [ + DiscordApiErrors.UnknownMessage, + DiscordApiErrors.UnknownChannel, + DiscordApiErrors.UnknownGuild, + DiscordApiErrors.UnknownUser, + DiscordApiErrors.UnknownInteraction, + DiscordApiErrors.CannotSendMessagesToThisUser, // User blocked bot or DM disabled + DiscordApiErrors.ReactionWasBlocked, // User blocked bot or DM disabled + DiscordApiErrors.MaximumActiveThreads, +]; + +export class MessageUtils { + public static async send( + target: User | TextBasedChannel, + content: string | EmbedBuilder | BaseMessageOptions + ): Promise { + try { + let options: BaseMessageOptions = + typeof content === 'string' + ? { content } + : content instanceof EmbedBuilder + ? { embeds: [content] } + : content; + return await target.send(options); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async reply( + msg: Message, + content: string | EmbedBuilder | BaseMessageOptions + ): Promise { + try { + let options: BaseMessageOptions = + typeof content === 'string' + ? { content } + : content instanceof EmbedBuilder + ? { embeds: [content] } + : content; + return await msg.reply(options); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async edit( + msg: Message, + content: string | EmbedBuilder | MessageEditOptions + ): Promise { + try { + let options: MessageEditOptions = + typeof content === 'string' + ? { content } + : content instanceof EmbedBuilder + ? { embeds: [content] } + : content; + return await msg.edit(options); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async react(msg: Message, emoji: EmojiResolvable): Promise { + try { + return await msg.react(emoji); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async pin(msg: Message, pinned: boolean = true): Promise { + try { + return pinned ? await msg.pin() : await msg.unpin(); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async startThread( + msg: Message, + options: StartThreadOptions + ): Promise { + try { + return await msg.startThread(options); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async delete(msg: Message): Promise { + try { + return await msg.delete(); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } +} diff --git a/src/utils/partial-utils.ts b/src/utils/partial-utils.ts new file mode 100644 index 0000000..102477d --- /dev/null +++ b/src/utils/partial-utils.ts @@ -0,0 +1,88 @@ +import { + DiscordAPIError, + RESTJSONErrorCodes as DiscordApiErrors, + Message, + MessageReaction, + PartialMessage, + PartialMessageReaction, + PartialUser, + User, +} from 'discord.js'; + +const IGNORED_ERRORS = [ + DiscordApiErrors.UnknownMessage, + DiscordApiErrors.UnknownChannel, + DiscordApiErrors.UnknownGuild, + DiscordApiErrors.UnknownUser, + DiscordApiErrors.UnknownInteraction, + DiscordApiErrors.MissingAccess, +]; + +export class PartialUtils { + public static async fillUser(user: User | PartialUser): Promise { + if (user.partial) { + try { + return await user.fetch(); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + return user as User; + } + + public static async fillMessage(msg: Message | PartialMessage): Promise { + if (msg.partial) { + try { + return await msg.fetch(); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + return msg as Message; + } + + public static async fillReaction( + msgReaction: MessageReaction | PartialMessageReaction + ): Promise { + if (msgReaction.partial) { + try { + msgReaction = await msgReaction.fetch(); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + msgReaction.message = await this.fillMessage(msgReaction.message); + if (!msgReaction.message) { + return; + } + + return msgReaction as MessageReaction; + } +} diff --git a/src/utils/permission-utils.ts b/src/utils/permission-utils.ts new file mode 100644 index 0000000..d6b3143 --- /dev/null +++ b/src/utils/permission-utils.ts @@ -0,0 +1,127 @@ +import { Channel, DMChannel, GuildChannel, PermissionFlagsBits, ThreadChannel } from 'discord.js'; + +export class PermissionUtils { + public static canSend(channel: Channel, embedLinks: boolean = false): boolean { + if (channel instanceof DMChannel) { + return true; + } else if (channel instanceof GuildChannel || channel instanceof ThreadChannel) { + let channelPerms = channel.permissionsFor(channel.client.user); + if (!channelPerms) { + // This can happen if the guild disconnected while a collector is running + return false; + } + + // VIEW_CHANNEL - Needed to view the channel + // SEND_MESSAGES - Needed to send messages + // EMBED_LINKS - Needed to send embedded links + return channelPerms.has([ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + ...(embedLinks ? [PermissionFlagsBits.EmbedLinks] : []), + ]); + } else { + return false; + } + } + + public static canMention(channel: Channel): boolean { + if (channel instanceof DMChannel) { + return true; + } else if (channel instanceof GuildChannel || channel instanceof ThreadChannel) { + let channelPerms = channel.permissionsFor(channel.client.user); + if (!channelPerms) { + // This can happen if the guild disconnected while a collector is running + return false; + } + + // VIEW_CHANNEL - Needed to view the channel + // MENTION_EVERYONE - Needed to mention @everyone, @here, and all roles + return channelPerms.has([ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.MentionEveryone, + ]); + } else { + return false; + } + } + + public static canReact(channel: Channel, removeOthers: boolean = false): boolean { + if (channel instanceof DMChannel) { + return true; + } else if (channel instanceof GuildChannel || channel instanceof ThreadChannel) { + let channelPerms = channel.permissionsFor(channel.client.user); + if (!channelPerms) { + // This can happen if the guild disconnected while a collector is running + return false; + } + + // VIEW_CHANNEL - Needed to view the channel + // ADD_REACTIONS - Needed to add new reactions to messages + // READ_MESSAGE_HISTORY - Needed to add new reactions to messages + // https://discordjs.guide/popular-topics/permissions-extended.html#implicit-permissions + // MANAGE_MESSAGES - Needed to remove others reactions + return channelPerms.has([ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.AddReactions, + PermissionFlagsBits.ReadMessageHistory, + ...(removeOthers ? [PermissionFlagsBits.ManageMessages] : []), + ]); + } else { + return false; + } + } + + public static canPin(channel: Channel, findOld: boolean = false): boolean { + if (channel instanceof DMChannel) { + return true; + } else if (channel instanceof GuildChannel || channel instanceof ThreadChannel) { + let channelPerms = channel.permissionsFor(channel.client.user); + if (!channelPerms) { + // This can happen if the guild disconnected while a collector is running + return false; + } + + // VIEW_CHANNEL - Needed to view the channel + // MANAGE_MESSAGES - Needed to pin messages + // READ_MESSAGE_HISTORY - Needed to find old pins + return channelPerms.has([ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.ManageMessages, + ...(findOld ? [PermissionFlagsBits.ReadMessageHistory] : []), + ]); + } else { + return false; + } + } + + public static canCreateThreads( + channel: Channel, + manageThreads: boolean = false, + findOld: boolean = false + ): boolean { + if (channel instanceof DMChannel) { + return false; + } else if (channel instanceof GuildChannel || channel instanceof ThreadChannel) { + let channelPerms = channel.permissionsFor(channel.client.user); + if (!channelPerms) { + // This can happen if the guild disconnected while a collector is running + return false; + } + + // VIEW_CHANNEL - Needed to view the channel + // SEND_MESSAGES_IN_THREADS - Needed to send messages in threads + // CREATE_PUBLIC_THREADS - Needed to create public threads + // MANAGE_THREADS - Needed to rename, delete, archive, unarchive, slow mode threads + // READ_MESSAGE_HISTORY - Needed to find old threads + return channelPerms.has([ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessagesInThreads, + PermissionFlagsBits.CreatePublicThreads, + ...(manageThreads ? [PermissionFlagsBits.ManageThreads] : []), + ...(findOld ? [PermissionFlagsBits.ReadMessageHistory] : []), + ]); + } else { + return false; + } + } +} diff --git a/src/utils/random-utils.ts b/src/utils/random-utils.ts new file mode 100644 index 0000000..fb5bb2f --- /dev/null +++ b/src/utils/random-utils.ts @@ -0,0 +1,13 @@ +export class RandomUtils { + public static intFromInterval(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1) + min); + } + + public static shuffle(input: any[]): any[] { + for (let i = input.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [input[i], input[j]] = [input[j], input[i]]; + } + return input; + } +} diff --git a/src/utils/regex-utils.ts b/src/utils/regex-utils.ts new file mode 100644 index 0000000..e8d2216 --- /dev/null +++ b/src/utils/regex-utils.ts @@ -0,0 +1,31 @@ +export class RegexUtils { + public static regex(input: string): RegExp { + let match = input.match(/^\/(.*)\/([^/]*)$/); + if (!match) { + return; + } + + return new RegExp(match[1], match[2]); + } + + public static escapeRegex(input: string): string { + return input?.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + } + + public static discordId(input: string): string { + return input?.match(/\b\d{17,20}\b/)?.[0]; + } + + public static tag(input: string): { username: string; tag: string; discriminator: string } { + let match = input.match(/\b(.+)#([\d]{4})\b/); + if (!match) { + return; + } + + return { + tag: match[0], + username: match[1], + discriminator: match[2], + }; + } +} diff --git a/src/utils/shard-utils.ts b/src/utils/shard-utils.ts new file mode 100644 index 0000000..0ae3502 --- /dev/null +++ b/src/utils/shard-utils.ts @@ -0,0 +1,43 @@ +import { fetchRecommendedShardCount, ShardClientUtil, ShardingManager } from 'discord.js'; + +import { MathUtils } from './index.js'; +import { DiscordLimits } from '../constants/index.js'; + +export class ShardUtils { + public static async requiredShardCount(token: string): Promise { + return await this.recommendedShardCount(token, DiscordLimits.GUILDS_PER_SHARD); + } + + public static async recommendedShardCount( + token: string, + serversPerShard: number + ): Promise { + return Math.ceil( + await fetchRecommendedShardCount(token, { guildsPerShard: serversPerShard }) + ); + } + + public static shardIds(shardInterface: ShardingManager | ShardClientUtil): number[] { + if (shardInterface instanceof ShardingManager) { + return shardInterface.shards.map(shard => shard.id); + } else if (shardInterface instanceof ShardClientUtil) { + return shardInterface.ids; + } + } + + public static shardId(guildId: number | string, shardCount: number): number { + // See sharding formula: + // https://discord.com/developers/docs/topics/gateway#sharding-sharding-formula + // tslint:disable-next-line:no-bitwise + return Number((BigInt(guildId) >> 22n) % BigInt(shardCount)); + } + + public static async serverCount( + shardInterface: ShardingManager | ShardClientUtil + ): Promise { + let shardGuildCounts = (await shardInterface.fetchClientValues( + 'guilds.cache.size' + )) as number[]; + return MathUtils.sum(shardGuildCounts); + } +} diff --git a/src/utils/string-utils.ts b/src/utils/string-utils.ts new file mode 100644 index 0000000..0b8caa7 --- /dev/null +++ b/src/utils/string-utils.ts @@ -0,0 +1,37 @@ +import { escapeMarkdown } from 'discord.js'; +import removeMarkdown from 'remove-markdown'; + +export class StringUtils { + public static truncate(input: string, length: number, addEllipsis: boolean = false): string { + if (input.length <= length) { + return input; + } + + let output = input.substring(0, addEllipsis ? length - 3 : length); + if (addEllipsis) { + output += '...'; + } + + return output; + } + + public static escapeMarkdown(input: string): string { + return ( + escapeMarkdown(input) + // Unescapes custom emojis + // TODO: Update once discord.js update their escapeMarkdown() + // See https://github.com/discordjs/discord.js/issues/8943 + .replaceAll( + /<(a?):(\S+):(\d{17,20})>/g, + (_match, animatedPrefix, emojiName, emojiId) => { + let emojiNameUnescaped = emojiName.replaceAll(/\\/g, ''); + return `<${animatedPrefix}:${emojiNameUnescaped}:${emojiId}>`; + } + ) + ); + } + + public static stripMarkdown(input: string): string { + return removeMarkdown(input); + } +} diff --git a/src/utils/thread-utils.ts b/src/utils/thread-utils.ts new file mode 100644 index 0000000..b9e32f3 --- /dev/null +++ b/src/utils/thread-utils.ts @@ -0,0 +1,52 @@ +import { DiscordAPIError, RESTJSONErrorCodes as DiscordApiErrors, ThreadChannel } from 'discord.js'; + +const IGNORED_ERRORS = [ + DiscordApiErrors.UnknownMessage, + DiscordApiErrors.UnknownChannel, + DiscordApiErrors.UnknownGuild, + DiscordApiErrors.UnknownUser, + DiscordApiErrors.UnknownInteraction, + DiscordApiErrors.CannotSendMessagesToThisUser, // User blocked bot or DM disabled + DiscordApiErrors.ReactionWasBlocked, // User blocked bot or DM disabled + DiscordApiErrors.MaximumActiveThreads, +]; + +export class ThreadUtils { + public static async archive( + thread: ThreadChannel, + archived: boolean = true + ): Promise { + try { + return await thread.setArchived(archived); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } + + public static async lock( + thread: ThreadChannel, + locked: boolean = true + ): Promise { + try { + return await thread.setLocked(locked); + } catch (error) { + if ( + error instanceof DiscordAPIError && + typeof error.code == 'number' && + IGNORED_ERRORS.includes(error.code) + ) { + return; + } else { + throw error; + } + } + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f6abb8a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "es2022", + "lib": ["es2021"], + "declaration": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": false, + "moduleResolution": "node", + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "exclude": ["dist", "node_modules", "tests"] +}