From e4dbbfd68cd53c60514d5ace2d1c24a77128cb15 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 24 Nov 2023 22:54:41 +0700 Subject: [PATCH 001/312] chore: initialize a new project --- .commitlintrc.js | 3 + .editorconfig | 15 + .eslintignore | 3 + .eslintrc | 33 + .eslintrc.js | 18 - .gitattributes | 3 + .github/workflows/build.yml | 64 - .gitignore | 67 +- .prettierignore | 3 + .prettierrc | 9 + .prettierrc.json | 4 - .turbo/cookies/0.cookie | 0 .turbo/cookies/1.cookie | 0 .turbo/cookies/10.cookie | 0 .turbo/cookies/11.cookie | 0 .turbo/cookies/12.cookie | 0 .turbo/cookies/13.cookie | 0 .turbo/cookies/14.cookie | 0 .turbo/cookies/15.cookie | 0 .turbo/cookies/16.cookie | 0 .turbo/cookies/17.cookie | 0 .turbo/cookies/2.cookie | 0 .turbo/cookies/3.cookie | 0 .turbo/cookies/4.cookie | 0 .turbo/cookies/5.cookie | 0 .turbo/cookies/6.cookie | 0 .turbo/cookies/7.cookie | 0 .turbo/cookies/8.cookie | 0 .turbo/cookies/9.cookie | 0 .vscode/settings.json | 19 + CODE_OF_CONDUCT.md | 134 + Dockerfile | 25 - LICENSE | 674 ----- apps/bot-discord/src/commands/ban.js | 45 - apps/bot-discord/src/commands/exile.js | 52 - .../src/commands/exileMemberCtx.js | 43 - .../src/commands/feedbackDislike.js | 19 - apps/bot-discord/src/commands/feedbackLike.js | 29 - apps/bot-discord/src/commands/mute.js | 56 - apps/bot-discord/src/commands/trainMessage.js | 20 - apps/bot-discord/src/commands/unban.js | 31 - apps/bot-discord/src/commands/unexile.js | 45 - apps/bot-discord/src/commands/unmute.js | 45 - apps/bot-discord/src/config.example.json | 358 --- apps/bot-discord/src/events/guildMemberAdd.js | 22 - .../src/events/guildMemberUpdate.js | 10 - .../src/events/interactionCreate.js | 22 - apps/bot-discord/src/events/messageCreate.js | 43 - apps/bot-discord/src/events/ready.js | 18 - apps/bot-discord/src/events/threadCreate.js | 9 - .../src/helperEvents/aiResponse.js | 97 - .../src/helperEvents/ocrResponse.js | 31 - apps/bot-discord/src/index.js | 108 - apps/bot-discord/src/msgCommands/exile.js | 40 - apps/bot-discord/src/package-lock.json | 1335 ---------- apps/bot-discord/src/package.json | 17 - apps/bot-discord/src/registerCommands.js | 36 - apps/bot-discord/src/utils/checkModPerms.js | 8 - .../src/utils/checkSupporterPerms.js | 8 - apps/bot-discord/src/utils/cureUsername.js | 17 - .../src/utils/exileMemberToChannel.js | 47 - apps/bot-discord/src/utils/muteMember.js | 87 - apps/bot-discord/src/utils/reportToLogs.js | 45 - apps/bot-discord/src/utils/setMuteTimeout.js | 24 - .../src/utils/trainAISelectMenu.js | 77 - apps/bot-discord/src/utils/unmuteMember.js | 24 - apps/bot-telegram/src/commands/train.js | 61 - apps/bot-telegram/src/config.example.json | 109 - apps/bot-telegram/src/events/callbackQuery.js | 26 - apps/bot-telegram/src/events/message.js | 15 - .../src/helperEvents/aiResponse.js | 28 - .../src/helperEvents/ocrResponse.js | 24 - apps/bot-telegram/src/index.js | 66 - apps/bot-telegram/src/package-lock.json | 2302 ----------------- apps/bot-telegram/src/package.json | 17 - apps/server/src/PROTOCOL.md | 64 - apps/server/src/config.example.json | 9 - apps/server/src/events/index.js | 5 - apps/server/src/events/runAI.js | 43 - apps/server/src/events/runOCR.js | 61 - apps/server/src/events/trainAI.js | 17 - apps/server/src/index.js | 31 - apps/server/src/package-lock.json | 228 -- apps/server/src/package.json | 14 - bun.lockb | Bin 0 -> 330476 bytes docker-compose.yml | 15 - lefthook.yml | 24 + package-lock.json | 1939 -------------- package.json | 64 +- packages/client/index.js | 102 - packages/client/package-lock.json | 117 - packages/client/package.json | 15 - tsconfig.apis.json | 3 + tsconfig.base.json | 17 + tsconfig.packages.json | 15 + turbo.json | 26 + 96 files changed, 418 insertions(+), 8981 deletions(-) create mode 100755 .commitlintrc.js create mode 100755 .editorconfig create mode 100755 .eslintignore create mode 100755 .eslintrc delete mode 100644 .eslintrc.js create mode 100755 .gitattributes delete mode 100644 .github/workflows/build.yml mode change 100644 => 100755 .gitignore create mode 100755 .prettierignore create mode 100755 .prettierrc delete mode 100644 .prettierrc.json create mode 100644 .turbo/cookies/0.cookie create mode 100644 .turbo/cookies/1.cookie create mode 100644 .turbo/cookies/10.cookie create mode 100644 .turbo/cookies/11.cookie create mode 100644 .turbo/cookies/12.cookie create mode 100644 .turbo/cookies/13.cookie create mode 100644 .turbo/cookies/14.cookie create mode 100644 .turbo/cookies/15.cookie create mode 100644 .turbo/cookies/16.cookie create mode 100644 .turbo/cookies/17.cookie create mode 100644 .turbo/cookies/2.cookie create mode 100644 .turbo/cookies/3.cookie create mode 100644 .turbo/cookies/4.cookie create mode 100644 .turbo/cookies/5.cookie create mode 100644 .turbo/cookies/6.cookie create mode 100644 .turbo/cookies/7.cookie create mode 100644 .turbo/cookies/8.cookie create mode 100644 .turbo/cookies/9.cookie create mode 100755 .vscode/settings.json create mode 100755 CODE_OF_CONDUCT.md delete mode 100644 Dockerfile delete mode 100644 LICENSE delete mode 100644 apps/bot-discord/src/commands/ban.js delete mode 100644 apps/bot-discord/src/commands/exile.js delete mode 100644 apps/bot-discord/src/commands/exileMemberCtx.js delete mode 100644 apps/bot-discord/src/commands/feedbackDislike.js delete mode 100644 apps/bot-discord/src/commands/feedbackLike.js delete mode 100644 apps/bot-discord/src/commands/mute.js delete mode 100644 apps/bot-discord/src/commands/trainMessage.js delete mode 100644 apps/bot-discord/src/commands/unban.js delete mode 100644 apps/bot-discord/src/commands/unexile.js delete mode 100644 apps/bot-discord/src/commands/unmute.js delete mode 100644 apps/bot-discord/src/config.example.json delete mode 100644 apps/bot-discord/src/events/guildMemberAdd.js delete mode 100644 apps/bot-discord/src/events/guildMemberUpdate.js delete mode 100644 apps/bot-discord/src/events/interactionCreate.js delete mode 100644 apps/bot-discord/src/events/messageCreate.js delete mode 100644 apps/bot-discord/src/events/ready.js delete mode 100644 apps/bot-discord/src/events/threadCreate.js delete mode 100644 apps/bot-discord/src/helperEvents/aiResponse.js delete mode 100644 apps/bot-discord/src/helperEvents/ocrResponse.js delete mode 100644 apps/bot-discord/src/index.js delete mode 100644 apps/bot-discord/src/msgCommands/exile.js delete mode 100644 apps/bot-discord/src/package-lock.json delete mode 100644 apps/bot-discord/src/package.json delete mode 100644 apps/bot-discord/src/registerCommands.js delete mode 100644 apps/bot-discord/src/utils/checkModPerms.js delete mode 100644 apps/bot-discord/src/utils/checkSupporterPerms.js delete mode 100644 apps/bot-discord/src/utils/cureUsername.js delete mode 100644 apps/bot-discord/src/utils/exileMemberToChannel.js delete mode 100644 apps/bot-discord/src/utils/muteMember.js delete mode 100644 apps/bot-discord/src/utils/reportToLogs.js delete mode 100644 apps/bot-discord/src/utils/setMuteTimeout.js delete mode 100644 apps/bot-discord/src/utils/trainAISelectMenu.js delete mode 100644 apps/bot-discord/src/utils/unmuteMember.js delete mode 100644 apps/bot-telegram/src/commands/train.js delete mode 100644 apps/bot-telegram/src/config.example.json delete mode 100644 apps/bot-telegram/src/events/callbackQuery.js delete mode 100644 apps/bot-telegram/src/events/message.js delete mode 100644 apps/bot-telegram/src/helperEvents/aiResponse.js delete mode 100644 apps/bot-telegram/src/helperEvents/ocrResponse.js delete mode 100644 apps/bot-telegram/src/index.js delete mode 100644 apps/bot-telegram/src/package-lock.json delete mode 100644 apps/bot-telegram/src/package.json delete mode 100644 apps/server/src/PROTOCOL.md delete mode 100644 apps/server/src/config.example.json delete mode 100644 apps/server/src/events/index.js delete mode 100644 apps/server/src/events/runAI.js delete mode 100644 apps/server/src/events/runOCR.js delete mode 100644 apps/server/src/events/trainAI.js delete mode 100644 apps/server/src/index.js delete mode 100644 apps/server/src/package-lock.json delete mode 100644 apps/server/src/package.json create mode 100755 bun.lockb delete mode 100644 docker-compose.yml create mode 100755 lefthook.yml delete mode 100644 package-lock.json mode change 100644 => 100755 package.json delete mode 100644 packages/client/index.js delete mode 100644 packages/client/package-lock.json delete mode 100644 packages/client/package.json create mode 100755 tsconfig.apis.json create mode 100755 tsconfig.base.json create mode 100755 tsconfig.packages.json create mode 100755 turbo.json diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100755 index 0000000..da13e76 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +} diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..43b07b3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +indent_style = space +indent_size = 4 + +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintignore b/.eslintignore new file mode 100755 index 0000000..559a55b --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100755 index 0000000..9146069 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,33 @@ +{ + "root": true, + "extends": ["prettier"], + "parserOptions": { + "sourceType": "module", + "ecmaVersion": "latest" + }, + "rules": { + "import/no-unresolved": "error", + "prettier/prettier": [ + "error", + { + "quoteProps": "consistent", + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "es5", + "useTabs": false + } + ] + }, + "plugins": ["import", "prettier"], + "settings": { + "import/parsers": { + "@typescript-eslint/parser": [".ts", ".tsx"] + }, + "import/resolver": { + "typescript": { + "alwaysTryTypes": true, + "project": ["./tsconfig.base.json"] + } + } + } +} diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 3e22563..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - env: { - es2021: true, - node: true - }, - extends: 'eslint:recommended', - overrides: [], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module' - }, - rules: { - indent: ['error', 'tab'], - 'linebreak-style': ['error', 'windows'], - quotes: ['error', 'single'], - semi: ['error', 'always'] - } -}; diff --git a/.gitattributes b/.gitattributes new file mode 100755 index 0000000..cfdc41a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.pbxproj -text +# specific for windows script files +*.bat text eol=lf \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index d8a24b7..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Build and Publish Docker Image - -on: - push: - branches: [main] - workflow_dispatch: - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - IMAGE_TAG: ${{ github.sha }} - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout Dockerfile - id: checkout - uses: actions/checkout@v4 - - - name: Setup QEMU - id: qemu - uses: docker/setup-qemu-action@v3 - with: - image: tonistiigi/binfmt:latest - platforms: all - - - name: Setup Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - id: ghcr - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - flavor: | - latest=${{ startsWith(github.ref, 'refs/heads/main') }} - suffix=-${{ github.sha }} - - - name: Build and push main Docker image - id: build - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64/v8 - cache-to: type=gha,mode=max,ignore-error=true - cache-from: type=gha - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 4fa1d7b..a1a690b --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,60 @@ -node_modules -model -test.js -eng.traineddata -bots/* -server/**/** -test/* \ No newline at end of file +# 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 + +# Dependency directories +node_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Turborepo +.turbo/ +dist/ +build/ + +# Tesseract +*.traineddata + +# ESLint +.eslintcache + +# VSCode +.vscode/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100755 index 0000000..559a55b --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100755 index 0000000..ce11b5c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "arrowParens": "avoid", + "quoteProps": "as-needed", + "tabWidth": 4, + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "endOfLine": "crlf" +} diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 32ebab4..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "none" -} diff --git a/.turbo/cookies/0.cookie b/.turbo/cookies/0.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/1.cookie b/.turbo/cookies/1.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/10.cookie b/.turbo/cookies/10.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/11.cookie b/.turbo/cookies/11.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/12.cookie b/.turbo/cookies/12.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/13.cookie b/.turbo/cookies/13.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/14.cookie b/.turbo/cookies/14.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/15.cookie b/.turbo/cookies/15.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/16.cookie b/.turbo/cookies/16.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/17.cookie b/.turbo/cookies/17.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/2.cookie b/.turbo/cookies/2.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/3.cookie b/.turbo/cookies/3.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/4.cookie b/.turbo/cookies/4.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/5.cookie b/.turbo/cookies/5.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/6.cookie b/.turbo/cookies/6.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/7.cookie b/.turbo/cookies/7.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/8.cookie b/.turbo/cookies/8.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.turbo/cookies/9.cookie b/.turbo/cookies/9.cookie new file mode 100644 index 0000000..e69de29 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100755 index 0000000..893de2b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true + }, + "typescript.tsserver.experimental.enableProjectDiagnostics": true, + "typescript.inlayHints.variableTypes.enabled": true, + "typescript.inlayHints.parameterNames.enabled": "all", + "typescript.inlayHints.enumMemberValues.enabled": true, + "typescript.inlayHints.functionLikeReturnTypes.enabled": true, + "typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true, + "typescript.inlayHints.propertyDeclarationTypes.enabled": true, + "typescript.inlayHints.parameterTypes.enabled": true, + "typescript.inlayHints.variableTypes.suppressWhenTypeMatchesName": true +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100755 index 0000000..57e133b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[this email](mailto:palmpasuthorn@gmail.com). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 19881bd..0000000 --- a/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM node:latest - -ENV WIT_AI_TOKEN $WIT_AI_TOKEN -ENV MONGODB_URI $MONGODB_URI -ENV DISCORD_TOKEN $DISCORD_TOKEN - -# Create app directory and install dependencies -WORKDIR /usr/src/revanced-helper -COPY . . -RUN npm i - -# Install the server -WORKDIR /usr/src/revanced-helper/apps/server/src -RUN npm i - -# Install the client for the server -WORKDIR /usr/src/revanced-helper/packages/client -RUN npm i - -# Install the bot -WORKDIR /usr/src/revanced-helper/apps/bot-discord/src -RUN npm i - -WORKDIR /usr/src/revanced-helper -CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/apps/bot-discord/src/commands/ban.js b/apps/bot-discord/src/commands/ban.js deleted file mode 100644 index 5ca51c3..0000000 --- a/apps/bot-discord/src/commands/ban.js +++ /dev/null @@ -1,45 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js'; -import { checkForPerms } from '../utils/checkModPerms.js'; -import reportToLogs from '../utils/reportToLogs.js'; - -export default { - data: new SlashCommandBuilder() - .setName('ban') - .setDescription('Ban a member.') - .setDMPermission(false) - .addUserOption(option => - option - .setName('user') - .setDescription('The member to ban') - .setRequired(true) - ) - .addIntegerOption(option => - option - .setName('dmd') - .setDescription('Amount of days to delete messages') - ) - .addStringOption(option => - option - .setName('reason') - .setDescription('Reason for the ban') - ), - async execute(_, config, interaction) { - if (!checkForPerms(config, interaction.member)) return interaction.reply({ - epheremal: true, - content: 'You don\'t have the required permissions.' - }); - - interaction.guild.members.ban(interaction.options.getUser('user'), { - reason: interaction.options.getString('reason'), - deleteMessageSeconds: interaction.options.getString('dmd') ? - interaction.options.getString('dmd') * 86_400 : 0 - }); - - reportToLogs(config, interaction.client, 'banned', null, { - reason: interaction.options.getString('reason'), - actionTo: interaction.options.getUser('user'), - actionBy: interaction.member, - channel: interaction.channel - }, interaction); - } -}; diff --git a/apps/bot-discord/src/commands/exile.js b/apps/bot-discord/src/commands/exile.js deleted file mode 100644 index 9ed8f41..0000000 --- a/apps/bot-discord/src/commands/exile.js +++ /dev/null @@ -1,52 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js'; -import { checkForPerms } from '../utils/checkSupporterPerms.js' -import reportToLogs from '../utils/reportToLogs.js'; -import muteMember from '../utils/muteMember.js'; -import exileMemberToChannel from '../utils/exileMemberToChannel.js'; - -export default { - data: new SlashCommandBuilder() - .setName('exile') - .setDescription('Exile a member to support.') - .setDMPermission(false) - .addUserOption(option => - option - .setName('user') - .setDescription('The member to exile') - .setRequired(true) - ) - .addStringOption(option => - option - .setName('reason') - .setDescription('The reason of the exile') - .setRequired(true) - ), - async execute(_, config, interaction) { - if (!checkForPerms(config, interaction.member)) return interaction.reply({ - epheremal: true, - content: 'You don\'t have the required permissions.' - }); - - await interaction.deferReply(); - - const user = interaction.options.getUser('user'); - - const member = await interaction.guild.members.fetch(user); - const reason = interaction.options.getString('reason'); - const parsedDuration = await muteMember(config, member, { - reason, - supportMute: true, - guild: interaction.guild - }); - - exileMemberToChannel(member, interaction.channel, null, reason, config); - - reportToLogs(config, interaction.client, 'exiled', null, { - reason, - actionTo: user, - actionBy: interaction.member, - channel: interaction.channel, - expire: parsedDuration - }, interaction); - } -}; diff --git a/apps/bot-discord/src/commands/exileMemberCtx.js b/apps/bot-discord/src/commands/exileMemberCtx.js deleted file mode 100644 index 77a8b62..0000000 --- a/apps/bot-discord/src/commands/exileMemberCtx.js +++ /dev/null @@ -1,43 +0,0 @@ -import { ContextMenuCommandBuilder, ApplicationCommandType } from 'discord.js'; -import { checkForPerms } from '../utils/checkSupporterPerms.js' -import muteMember from '../utils/muteMember.js'; -import exileMemberToChannel from '../utils/exileMemberToChannel.js'; -import reportToLogs from '../utils/reportToLogs.js'; - -export default { - data: new ContextMenuCommandBuilder() - .setName('Exile Member') - .setType(ApplicationCommandType.Message), - async execute(helper, config, interaction) { - if ( - !checkForPerms(config, interaction.member) - ) - return interaction.reply({ - content: 'You don\'t have the permission to do this.', - ephemeral: true - }); - - await interaction.deferReply(); - const targetMsg = interaction.targetMessage; - - const member = await interaction.guild.members.fetch(targetMsg.author.id); - const parsedDuration = await muteMember(config, member, { - channel: interaction.channel, - reason: null, - supportMute: true, - guild: interaction.guild - }); - - exileMemberToChannel(targetMsg.author, interaction.channel, targetMsg.content, null, config); - - reportToLogs(config, interaction.client, 'exiled', null, { - reason: null, - actionTo: targetMsg.author, - actionBy: interaction.member, - channel: interaction.channel, - expire: parsedDuration - }, interaction); - - await targetMsg.delete(); - } -}; diff --git a/apps/bot-discord/src/commands/feedbackDislike.js b/apps/bot-discord/src/commands/feedbackDislike.js deleted file mode 100644 index 8b9e5ba..0000000 --- a/apps/bot-discord/src/commands/feedbackDislike.js +++ /dev/null @@ -1,19 +0,0 @@ -import { checkForPerms } from '../utils/checkSupporterPerms.js'; -import trainAISelectMenu from '../utils/trainAISelectMenu.js'; - -export default { - data: { - name: 'fb-dislike' - }, - async execute(helper, config, interaction) { - if ( - !checkForPerms(config, interaction.member) - ) - return interaction.reply({ - content: 'You don\'t have the permission to do this.', - ephemeral: true - }); - - trainAISelectMenu(interaction, config, helper); - } -}; diff --git a/apps/bot-discord/src/commands/feedbackLike.js b/apps/bot-discord/src/commands/feedbackLike.js deleted file mode 100644 index 82e5a16..0000000 --- a/apps/bot-discord/src/commands/feedbackLike.js +++ /dev/null @@ -1,29 +0,0 @@ -import { checkForPerms } from '../utils/checkSupporterPerms.js'; - -export default { - data: { - name: 'fb-like' - }, - async execute(helper, config, interaction) { - if ( - !checkForPerms(config, interaction.member) - ) - return interaction.reply({ - content: 'You don\'t have the permission to do this.', - ephemeral: true - }); - // FIXME: somehow get the intent? - // maybe storing in a collection and fetching the msg id with its label? - /* - helper.sendTrainData(interactedMessage, i.values[0]); - - i.reply({ content: 'Sent training data to server.', ephemeral: true }); - - interaction.message.edit({ components: [] }); - */ - interaction.reply({ - content: 'Feature currently not available. Please use the dislike button.', - ephemeral: true - }); - } -}; diff --git a/apps/bot-discord/src/commands/mute.js b/apps/bot-discord/src/commands/mute.js deleted file mode 100644 index 6a4b9a0..0000000 --- a/apps/bot-discord/src/commands/mute.js +++ /dev/null @@ -1,56 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js'; -import { checkForPerms } from '../utils/checkModPerms.js'; -import reportToLogs from '../utils/reportToLogs.js'; -import muteMember from '../utils/muteMember.js'; - -export default { - data: new SlashCommandBuilder() - .setName('mute') - .setDescription('Mute a member.') - .setDMPermission(false) - .addUserOption(option => - option - .setName('user') - .setDescription('The member to mute') - .setRequired(true) - ) - .addStringOption(option => - option - .setName('duration') - .setDescription('The duration of mute') - .setRequired(true) - ) - .addStringOption(option => - option - .setName('reason') - .setDescription('The reason of the mute') - .setRequired(true) - ), - async execute(_, config, interaction) { - if (!checkForPerms(config, interaction.member)) return interaction.reply({ - epheremal: true, - content: 'You don\'t have the required permissions.' - }); - - await interaction.deferReply(); - - const user = interaction.options.getUser('user'); - - const member = await interaction.guild.members.fetch(user); - const reason = interaction.options.getString('reason'); - const parsedDuration = await muteMember(config, member, { - duration: interaction.options.getString('duration'), - reason, - supportMute: false, - guild: interaction.guild - }); - - reportToLogs(config, interaction.client, 'muted', null, { - reason, - actionTo: user, - actionBy: interaction.member, - channel: interaction.channel, - expire: parsedDuration - }, interaction); - } -}; diff --git a/apps/bot-discord/src/commands/trainMessage.js b/apps/bot-discord/src/commands/trainMessage.js deleted file mode 100644 index 988668d..0000000 --- a/apps/bot-discord/src/commands/trainMessage.js +++ /dev/null @@ -1,20 +0,0 @@ -import { ContextMenuCommandBuilder, ApplicationCommandType } from 'discord.js'; -import trainAISelectMenu from '../utils/trainAISelectMenu.js'; -import { checkForPerms } from '../utils/checkSupporterPerms.js' - -export default { - data: new ContextMenuCommandBuilder() - .setName('Train Message') - .setType(ApplicationCommandType.Message), - async execute(helper, config, interaction) { - if ( - !checkForPerms(config, interaction.member) - ) - return interaction.reply({ - content: 'You don\'t have the permission to do this.', - ephemeral: true - }); - - trainAISelectMenu(interaction, config, helper); - } -}; diff --git a/apps/bot-discord/src/commands/unban.js b/apps/bot-discord/src/commands/unban.js deleted file mode 100644 index 65e987c..0000000 --- a/apps/bot-discord/src/commands/unban.js +++ /dev/null @@ -1,31 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js'; -import { checkForPerms } from '../utils/checkModPerms.js'; -import reportToLogs from '../utils/reportToLogs.js'; - -export default { - data: new SlashCommandBuilder() - .setName('unban') - .setDescription('Unban a member.') - .setDMPermission(false) - .addUserOption(option => - option - .setName('user') - .setDescription('The member to ban') - .setRequired(true) - ), - async execute(_, config, interaction) { - if (!checkForPerms(config, interaction.member)) return interaction.reply({ - epheremal: true, - content: 'You don\'t have the required permissions.' - }); - - interaction.guild.members.unban(interaction.options.getUser('user')); - - reportToLogs(config, interaction.client, 'unbanned', null, { - reason: null, - actionTo: await client.users.fetch(interaction.options.getString('user')), - actionBy: interaction.member, - channel: interaction.channel - }, interaction); - } -}; diff --git a/apps/bot-discord/src/commands/unexile.js b/apps/bot-discord/src/commands/unexile.js deleted file mode 100644 index 023e060..0000000 --- a/apps/bot-discord/src/commands/unexile.js +++ /dev/null @@ -1,45 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js'; -import { checkForPerms } from '../utils/checkSupporterPerms.js'; -import reportToLogs from '../utils/reportToLogs.js'; -import unmuteMember from '../utils/unmuteMember.js'; - -export default { - data: new SlashCommandBuilder() - .setName('unexile') - .setDescription('Get the member back from an exilation.') - .setDMPermission(false) - .addUserOption(option => - option - .setName('user') - .setDescription('The member to unexile') - .setRequired(true) - ), - async execute(_, config, interaction) { - if (!checkForPerms(config, interaction.member)) return interaction.reply({ - epheremal: true, - content: 'You don\'t have the required permissions.' - }); - - await interaction.deferReply(); - - const user = interaction.options.getUser('user'); - - const member = await interaction.guild.members.fetch(user); - const isExiled = await unmuteMember(config, member, true); - - if (!isExiled) { - await interaction.editReply({ - content: 'Member was not exiled.' - }); - - return; - } - - reportToLogs(config, interaction.client, 'unexiled', null, { - reason: null, - actionTo: user, - actionBy: interaction.member, - channel: interaction.channel, - }, interaction); - } -}; diff --git a/apps/bot-discord/src/commands/unmute.js b/apps/bot-discord/src/commands/unmute.js deleted file mode 100644 index 7375b14..0000000 --- a/apps/bot-discord/src/commands/unmute.js +++ /dev/null @@ -1,45 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js'; -import { checkForPerms } from '../utils/checkModPerms.js'; -import reportToLogs from '../utils/reportToLogs.js'; -import unmuteMember from '../utils/unmuteMember.js'; - -export default { - data: new SlashCommandBuilder() - .setName('unmute') - .setDescription('Unmute a member.') - .setDMPermission(false) - .addUserOption(option => - option - .setName('user') - .setDescription('The member to unmute') - .setRequired(true) - ), - async execute(_, config, interaction) { - if (!checkForPerms(config, interaction.member)) return interaction.reply({ - epheremal: true, - content: 'You don\'t have the required permissions.' - }); - - await interaction.deferReply(); - - const user = interaction.options.getUser('user'); - - const member = await interaction.guild.members.fetch(user); - const isMuted = await unmuteMember(config, member, false); - - if (!isMuted) { - await interaction.editReply({ - content: 'Member was not muted.' - }); - - return; - } - - reportToLogs(config, interaction.client, 'unmuted', null, { - reason: null, - actionTo: user, - actionBy: interaction.member, - channel: interaction.channel, - }, interaction); - } -}; diff --git a/apps/bot-discord/src/config.example.json b/apps/bot-discord/src/config.example.json deleted file mode 100644 index abfc6a5..0000000 --- a/apps/bot-discord/src/config.example.json +++ /dev/null @@ -1,358 +0,0 @@ -{ - "discord": { - "trainRoles": [ - "1019903194941362198", - "955220417969262612", - "973886585294704640" - ], - "modRoles": [ - "955220417969262612", - "973886585294704640" - ], - "botId": "1038762591805247518", - "ignoreRole": "1027874293192863765", - "ignoreChannels": [ - "953965039105232906", - "953964264400515092", - "952987428786941952" - ], - "supportChannel": "1135563848586379264", - "mute": { - "takeRoles": [ - "996121272897519687", - "965267139902705744", - "995126555867086938" - ], - "giveRoles": [ - "953984696491061289" - ], - "supportTakeRoles": [], - "supportGiveRoles": [ - "1140310515730632814" - ], - "supportMuteDuration": 600000 - } - }, - "logs": { - "channelId": "952987428786941952", - "threadId": "1027892160797872179" - }, - "sticky": { - "channelId": "1135563848586379264", - "stickyMessage": { - "title": "Sticky notice", - "color": 5150960, - "fields": [ - { - "name": "🔸 Before you ask", - "value": "Check the pinned messages and <#953993848374325269> for existing answers." - }, - { - "name": "🔸 How to ask", - "value": "Include the following information:\n* Version of ReVanced Manager\n* Name of the app you are patching\n* Version of the app you are patching" - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - }, - "timeout": 180000 - }, - "server": { - "port": 3000, - "host": "127.0.0.1" - }, - "responses": [ - { - "label": "suggested_version", - "threshold": 0.85, - "reply": { - "title": "Which version is suggested ❓", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "The suggested version can be seen in ReVanced Manager in the app selector screen. Refer to the ReVanced Manager documentation in <#953993848374325269> `3`." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "revanced_crash", - "threshold": 0.85, - "reply": { - "title": "Why am I experiencing crashes ❓", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "You may have patched an unsuggested version of the app, changed the selection of patches or used a faulty APK. Refer to the documentation in <#953993848374325269> `3` in order to correctly patch your app correctly using ReVanced CLI or ReVanced Manager." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "rvmanager_abort", - "threshold": 0.85, - "reply": { - "title": "Why is ReVanced Manager aborting ❓", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "Your device may be unsupported by ReVanced Manager. Refer to the documentation in <#953993848374325269> `3` in order to use ReVanced CLI or check if your device is supported by ReVanced Manager." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "revanced_download", - "threshold": 0.85, - "reply": { - "title": "Where or how to get ReVanced ❓", - "description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "You can use ReVanced CLI or ReVanced Manager to get ReVanced. Refer to the documentation in <#953993848374325269> `3`." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "androidtv_support", - "threshold": 0.85, - "reply": { - "title": "Does ReVanced support YouTube for Android TVs ❓", - "description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "Please refer to <#953993848374325269> `5`. Alternative, there is [SmartTubeNext](https://github.com/yuliskov/SmartTubeNext#smarttube)." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "revanced_nodownloader", - "threshold": 0.85, - "reply": { - "title": "How do I download videos on YouTube ❓", - "description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "In order to be able to download videos on YouTube without YouTube Premium, you can patch YouTube with the `External downloads` patch. You can configure the downloader in the settings of the patched app. NewPipe is the default downloader. Please refer to <#953993848374325269> `24`." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "revanced_casting", - "threshold": 0.85, - "reply": { - "title": "Why can I not cast videos on YouTube ❓", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "You may have patched YouTube with the `GmsCore support` patch which makes YouTube use Vanced MicroG instead of Google Services, but Vanced MicroG does not reliably support casting. In order to be able to cast videos on the patched app, you should not patch the app with the `GmsCore support` patch, but then you are forced to mount the patched app with root permissions, because you will not be able to install the app in normal circumstances and Google Services will reject the patched app." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "microg_download", - "threshold": 0.85, - "reply": { - "title": "Where can I get Vanced MicroG ❓", - "description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "If you patched YouTube using the `GmsCore support` patch, the patched app will redirect you to the download link of Vanced MicroG if you open it. In case it does not, please refer to <#953993848374325269> `17`." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "microg_nointernet", - "threshold": 0.85, - "reply": { - "title": "Why does YouTube say, I am offline ❓", - "description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "Please refer to <#953993848374325269> `15`." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "rvdownload_unofficial", - "threshold": 0.85, - "reply": { - "title": "What are the official links of ReVanced ❓", - "description": "A list of official links can be found in <#954066838856273960>.", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "ReVanced is always available at [revanced.app](https://revanced.app)." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "yt_buffering", - "threshold": 0.85, - "reply": { - "title": "Why do videos fail to play❓", - "description": "You might have asked a question that has been answered in the <#953993848374325269> channel already. Make sure to read it as it will answer a lot of your questions, guaranteed.", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "Please refer to <#953993848374325269> `32`." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "label": "false", - "threshold": 0, - "reply": null - } - ], - "ocrResponses": [ - { - "regex": "is not installed", - "reply": { - "title": "How do I download videos on YouTube ❓", - "description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "In order to be able to download videos on YouTube without YouTube Premium, you can patch YouTube with the `External downloads` patch. You can configure the downloader in the settings of the patched app. NewPipe is the default downloader. Please refer to <#953993848374325269> `24`." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - }, - { - "regex": "You're offline|Please check your", - "reply": { - "title": "Why does YouTube say, I am offline ❓", - "description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.", - "color": 5150960, - "fields": [ - { - "name": "🔸 Regarding your question", - "value": "Please refer to <#953993848374325269> `15`." - } - ], - "footer": { - "text": "ReVanced", - "icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png" - } - } - } - ] -} diff --git a/apps/bot-discord/src/events/guildMemberAdd.js b/apps/bot-discord/src/events/guildMemberAdd.js deleted file mode 100644 index d3294cb..0000000 --- a/apps/bot-discord/src/events/guildMemberAdd.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Events } from 'discord.js'; -import cureUsername from '../utils/cureUsername.js'; - -export default { - name: Events.GuildMemberAdd, - once: false, - async execute(_, config, member) { - cureUsername(member); - const mute = await client.db.collection('muted').findOne({ - guild_id: member.guild.id, - user_id: member.id - }); - - if (mute) { - // Add the roles given. - member.roles.add(mute.support_mute ? - config.mute.supportGiveRoles : - config.mute.giveRoles - ); - } - } -}; diff --git a/apps/bot-discord/src/events/guildMemberUpdate.js b/apps/bot-discord/src/events/guildMemberUpdate.js deleted file mode 100644 index b34429d..0000000 --- a/apps/bot-discord/src/events/guildMemberUpdate.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Events } from 'discord.js'; -import cureUsername from '../utils/cureUsername.js'; - -export default { - name: Events.GuildMemberUpdate, - once: false, - async execute(_, config, oldMember, newMember) { - cureUsername(newMember); - } -}; diff --git a/apps/bot-discord/src/events/interactionCreate.js b/apps/bot-discord/src/events/interactionCreate.js deleted file mode 100644 index 6cf9477..0000000 --- a/apps/bot-discord/src/events/interactionCreate.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Events } from 'discord.js'; - -export default { - name: Events.InteractionCreate, - once: false, - async execute(helper, config, interaction) { - const command = interaction.client.commands.get(interaction.commandName || interaction.customId); - - // It's the select menu interaction (hopefully), ignore. - if (!command) return; - - try { - await command.execute(helper, config, interaction); - } catch (error) { - console.error(error); - await interaction.reply({ - content: 'There was an error while executing this command!', - ephemeral: true - }); - } - } -}; diff --git a/apps/bot-discord/src/events/messageCreate.js b/apps/bot-discord/src/events/messageCreate.js deleted file mode 100644 index da59826..0000000 --- a/apps/bot-discord/src/events/messageCreate.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Events } from 'discord.js'; - -export default { - name: Events.MessageCreate, - once: false, - async execute(helper, config, msg) { - if (!msg.guild || msg.system || msg.webhookId || msg.author.bot) return; - if (msg.content.startsWith('?')) { - const [cmd, args] = msg.content.replace('?', '').split(/\s/g); - const command = msg.client.msgCommands.get(cmd); - if (command) { - await command.execute(msg, args, config); - } - } - const hasImmunity = msg.member.roles.cache.some(role => role.id === config.discord.ignoreRole); - if (config.discord.ignoreChannels.includes(msg.channelId)) return; - if (msg.attachments.first()?.contentType?.startsWith('image') && !hasImmunity) { - helper.scanImage(msg.attachments.first().url, `${msg.channelId}/${msg.id}`); - } - - if (msg.content && !hasImmunity) { - helper.scanText( - msg.content.toLowerCase().replace(/<.*?>/g, ''), - `${msg.channelId}/${msg.id}` - ); - } - - // Sticky message - if (msg.channel.id !== config.sticky.channelId) return; - if (msg.client.stickiedMessageTimeout) clearInterval(msg.client.stickiedMessageTimeout); - - msg.client.stickiedMessageTimeout = setTimeout(async () => { - const channel = await msg.client.channels.fetch(config.sticky.channelId); - - const message = await channel.send({ embeds: [config.sticky.stickyMessage] }); - - if (msg.client.stickiedMessage && channel.messages.cache.get(msg.client.stickiedMessage)?.deletable) - channel.messages.delete(msg.client.stickiedMessage).catch(() => {}); - - msg.client.stickiedMessage = message.id; - }, config.sticky.timeout); - } -}; diff --git a/apps/bot-discord/src/events/ready.js b/apps/bot-discord/src/events/ready.js deleted file mode 100644 index dd7337e..0000000 --- a/apps/bot-discord/src/events/ready.js +++ /dev/null @@ -1,18 +0,0 @@ -import { Events } from 'discord.js'; -import setMuteTimeout from '../utils/setMuteTimeout.js'; - -export default { - name: Events.ClientReady, - once: false, - async execute(_, config, client) { - console.log('Client is ready. Reloading mutes.'); - - const mutes = await client.db.collection('muted').find().toArray(); - - for (const mute of mutes) { - await setMuteTimeout(mute, client, config); - } - - console.log(`Loaded ${mutes.length} mutes.`); - } -}; diff --git a/apps/bot-discord/src/events/threadCreate.js b/apps/bot-discord/src/events/threadCreate.js deleted file mode 100644 index d242e65..0000000 --- a/apps/bot-discord/src/events/threadCreate.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Events } from 'discord.js'; - -export default { - name: Events.ThreadCreate, - once: false, - async execute(helper, _, thread) { - helper.scanText(thread.name.toLowerCase(), thread.id); - } -}; diff --git a/apps/bot-discord/src/helperEvents/aiResponse.js b/apps/bot-discord/src/helperEvents/aiResponse.js deleted file mode 100644 index b54027a..0000000 --- a/apps/bot-discord/src/helperEvents/aiResponse.js +++ /dev/null @@ -1,97 +0,0 @@ -import { - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle -} from 'discord.js'; - -export default { - name: 'aiResponse', - once: false, - async execute(client, config, helper, aiRes) { - if (!aiRes.response) return; - if (!aiRes.response[0]) return; - - try { - const ids = aiRes.id.split('/'); - - const intent = aiRes.response.reduce((a, b) => - a.confidence > b.confidence ? a : b - ); - - const response = config.responses.find( - (res) => res.label === intent.name - ); - - if (response.threshold > intent.confidence) return; - if (!response.reply) return; - - const embed = response.reply; - const feedbackRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('fb-like') - .setEmoji('👍') - .setStyle(ButtonStyle.Primary), - new ButtonBuilder() - .setCustomId('fb-dislike') - .setEmoji('👎') - .setStyle(ButtonStyle.Primary) - ); - - let channel = client.channels.cache.get(ids[0]); - - if (!channel) { - await client.channels.fetch(ids[0]); - channel = client.channels.cache.get(ids[0]); - } - - if (!ids[1]) { - // This means that it's a thread/forum. - const threadEditOption = { locked: false, archived: false }; - if (response.closeThread) { - threadEditOption.archived = true; - } - - if (response.lockThread) { - threadEditOption.locked = true; - } - - await channel.send({ - embeds: [embed], - components: [feedbackRow] - }); - - channel.edit(threadEditOption); - } else { - let message = channel.messages.cache.get(ids[1]); - - if (!message) { - await channel.messages.fetch(ids[1]); - message = channel.messages.cache.get(ids[1]); - } - - const firstMsg = (await channel.messages.fetch()).first(); - - await message.reply({ - embeds: [embed], - components: [feedbackRow] - }); - - if (firstMsg == message) { - const threadEditOption = { locked: false, archived: false }; - if (response.closeThread) { - threadEditOption.archived = true; - } - - if (response.lockThread) { - threadEditOption.locked = true; - } - - channel.edit(threadEditOption); - } - } - } catch (e) { - console.log(e); - } - } -}; diff --git a/apps/bot-discord/src/helperEvents/ocrResponse.js b/apps/bot-discord/src/helperEvents/ocrResponse.js deleted file mode 100644 index 2d05814..0000000 --- a/apps/bot-discord/src/helperEvents/ocrResponse.js +++ /dev/null @@ -1,31 +0,0 @@ -export default { - name: 'ocrResponse', - once: false, - async execute(client, config, helper, ocrRes) { - try { - const ids = ocrRes.id.split('/'); - let channel = client.channels.cache.get(ids[0]); - - if (!channel) { - await client.channels.fetch(ids[0]); - channel = client.channels.cache.get(ids[0]); - } - - let message = channel.messages.cache.get(ids[1]); - - if (!message) { - await channel.messages.fetch(ids[1]); - message = channel.messages.cache.get(ids[1]); - } - - for (const ocrReply of config.ocrResponses) { - if (ocrRes.ocrText.match(ocrReply.regex)) { - message.reply({ embeds: [ocrReply.reply] }); - break; - } - } - } catch (e) { - console.log(e); - } - } -}; diff --git a/apps/bot-discord/src/index.js b/apps/bot-discord/src/index.js deleted file mode 100644 index 11652cc..0000000 --- a/apps/bot-discord/src/index.js +++ /dev/null @@ -1,108 +0,0 @@ -import { Client, GatewayIntentBits, Collection } from 'discord.js'; -import { readFileSync, readdirSync } from 'node:fs'; -// Fix __dirname not being defined in ES modules. (https://stackoverflow.com/a/64383997) -import { fileURLToPath } from 'node:url'; -import { dirname, join } from 'node:path'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -import HelperClient from '@revanced-helper/helper-client'; -import config from './config.json' assert { type: 'json' }; -import { MongoClient } from 'mongodb'; -const helper = new HelperClient(config); -const mongoDBClient = new MongoClient(process.env.MONGODB_URI); - -await mongoDBClient.connect(); -helper.connect(); - - -const client = new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent - ] -}); - -client.commands = new Collection(); -client.msgCommands = new Collection(); -client.trainingVotes = new Collection(); -client.stickiedMessage = null; -client.stickiedMessageTimeout = null; -client.db = mongoDBClient.db('revanced_discord_bot'); -client.mutes = new Collection(); - -const commandsPath = join(__dirname, 'commands'); -const commandFiles = readdirSync(commandsPath).filter((file) => - file.endsWith('.js') -); - -for (const file of commandFiles) { - const filePath = join(commandsPath, file); - const command = (await import(`file://${filePath}`)).default; - if ('data' in command && 'execute' in command) { - client.commands.set(command.data.name, command); - } else { - console.log( - `[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.` - ); - } -} - -const msgCommandsPath = join(__dirname, 'msgCommands'); -const msgCommandFiles = readdirSync(msgCommandsPath).filter((file) => - file.endsWith('.js') -); - -for (const file of msgCommandFiles) { - const filePath = join(msgCommandsPath, file); - const command = (await import(`file://${filePath}`)).default; - if ('name' in command && 'execute' in command) { - client.msgCommands.set(command.name, command); - } else { - console.log( - `[WARNING] The command at ${filePath} is missing a required "name" or "execute" property.` - ); - } -} - -const discordEventsPath = join(__dirname, 'events'); -const discordEventFiles = readdirSync(discordEventsPath).filter((file) => - file.endsWith('.js') -); - -for (const file of discordEventFiles) { - const filePath = join(discordEventsPath, file); - const event = (await import(`file://${filePath}`)).default; - if (event.once) { - client.once(event.name, (...args) => - event.execute(helper, config, ...args) - ); - } else { - client.on(event.name, (...args) => - event.execute(helper, config, ...args) - ); - } -} - -// The ReVanced Helper events. - -const helperEventsPath = join(__dirname, 'helperEvents'); -const helperEventFiles = readdirSync(helperEventsPath).filter((file) => - file.endsWith('.js') -); - -for (const file of helperEventFiles) { - const filePath = join(helperEventsPath, file); - const event = (await import(`file://${filePath}`)).default; - if (event.once) { - helper.once(event.name, (...args) => - event.execute(client, config, helper, ...args) - ); - } else { - helper.on(event.name, (...args) => - event.execute(client, config, helper, ...args) - ); - } -} - -client.login(process.env.DISCORD_TOKEN); \ No newline at end of file diff --git a/apps/bot-discord/src/msgCommands/exile.js b/apps/bot-discord/src/msgCommands/exile.js deleted file mode 100644 index 3939110..0000000 --- a/apps/bot-discord/src/msgCommands/exile.js +++ /dev/null @@ -1,40 +0,0 @@ -import exileMemberToChannel from '../utils/exileMemberToChannel.js'; -import { checkForPerms } from '../utils/checkSupporterPerms.js' -import muteMember from '../utils/muteMember.js'; -import reportToLogs from '../utils/reportToLogs.js'; - -export default { - name: 'exile', - async execute(msg, args, config) { - if (!checkForPerms(config, msg.member)) return msg.reply('You don\'t have the permission to do this.'); - - if (!msg.reference) return msg.reply('You did not reply to anyone!'); - const referencedMsg = await msg.channel.messages.fetch(msg.reference.messageId); - let message = referencedMsg.content; - if (args && args[0]) { - if (isNaN(args[0])) return msg.reply('The argument you entered is not a number!'); - - const msgsByAuthor = (await msg.channel.messages.fetch({ limit: 50 })).filter( - m => m.author.id === referencedMsg.author.id - ).map(m => m.content); - message = msgsByAuthor.slice(Number(`-${args[0]}`)); - } - - const parsedDuration = await muteMember(config, referencedMsg.member, { - supportMute: true, - guild: msg.guild - }); - - exileMemberToChannel(referencedMsg.author, msg.channel, message, null, config, false); - - reportToLogs(config, msg.client, 'exiled', null, { - reason: null, - actionTo: referencedMsg.author, - actionBy: msg.member, - channel: msg.channel, - expire: parsedDuration - }, null, msg); - - await referencedMsg.delete(); - } -} \ No newline at end of file diff --git a/apps/bot-discord/src/package-lock.json b/apps/bot-discord/src/package-lock.json deleted file mode 100644 index f6bcfef..0000000 --- a/apps/bot-discord/src/package-lock.json +++ /dev/null @@ -1,1335 +0,0 @@ -{ - "name": "bot-discord", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "bot-discord", - "version": "1.0.0", - "license": "GPL-3.0-or-later", - "dependencies": { - "@revanced-helper/helper-client": "file:../../../packages/client", - "decancer": "^1.6.5", - "discord.js": "^14.11.0", - "mongodb": "^5.7.0", - "parse-duration": "^1.1.0" - } - }, - "node_modules/@discordjs/builders": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.3.tgz", - "integrity": "sha512-CTCh8NqED3iecTNuiz49mwSsrc2iQb4d0MjMdmS/8pb69Y4IlzJ/DIy/p5GFlgOrFbNO2WzMHkWKQSiJ3VNXaw==", - "dependencies": { - "@discordjs/formatters": "^0.3.1", - "@discordjs/util": "^0.3.1", - "@sapphire/shapeshift": "^3.8.2", - "discord-api-types": "^0.37.41", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.3", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/@discordjs/collection": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.1.tgz", - "integrity": "sha512-aWEc9DCf3TMDe9iaJoOnO2+JVAjeRNuRxPZQA6GVvBf+Z3gqUuWYBy2NWh4+5CLYq5uoc3MOvUQ5H5m8CJBqOA==", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/@discordjs/formatters": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.1.tgz", - "integrity": "sha512-M7X4IGiSeh4znwcRGcs+49B5tBkNDn4k5bmhxJDAUhRxRHTiFAOTVUNQ6yAKySu5jZTnCbSvTYHW3w0rAzV1MA==", - "dependencies": { - "discord-api-types": "^0.37.41" - }, - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/@discordjs/rest": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.7.1.tgz", - "integrity": "sha512-Ofa9UqT0U45G/eX86cURQnX7gzOJLG2oC28VhIk/G6IliYgQF7jFByBJEykPSHE4MxPhqCleYvmsrtfKh1nYmQ==", - "dependencies": { - "@discordjs/collection": "^1.5.1", - "@discordjs/util": "^0.3.0", - "@sapphire/async-queue": "^1.5.0", - "@sapphire/snowflake": "^3.4.2", - "discord-api-types": "^0.37.41", - "file-type": "^18.3.0", - "tslib": "^2.5.0", - "undici": "^5.22.0" - }, - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/@discordjs/util": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.3.1.tgz", - "integrity": "sha512-HxXKYKg7vohx2/OupUN/4Sd02Ev3PBJ5q0gtjdcvXb0ErCva8jNHWfe/v5sU3UKjIB/uxOhc+TDOnhqffj9pRA==", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/@discordjs/ws": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-0.8.3.tgz", - "integrity": "sha512-hcYtppanjHecbdNyCKQNH2I4RP9UrphDgmRgLYrATEQF1oo4sYSve7ZmGsBEXSzH72MO2tBPdWSThunbxUVk0g==", - "dependencies": { - "@discordjs/collection": "^1.5.1", - "@discordjs/rest": "^1.7.1", - "@discordjs/util": "^0.3.1", - "@sapphire/async-queue": "^1.5.0", - "@types/ws": "^8.5.4", - "@vladfrangu/async_event_emitter": "^2.2.1", - "discord-api-types": "^0.37.41", - "tslib": "^2.5.0", - "ws": "^8.13.0" - }, - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.0.tgz", - "integrity": "sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@revanced-helper/helper-client": { - "version": "1.0.0", - "resolved": "file:../../../packages/client", - "license": "GPL-3.0-or-later", - "dependencies": { - "bson": "^4.7.0" - } - }, - "node_modules/@revanced-helper/helper-client/node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" - }, - "node_modules/@types/node": { - "version": "20.2.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", - "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" - }, - "node_modules/@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", - "dependencies": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@vierofernando/decancer-android-arm-eabi": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-android-arm-eabi/-/decancer-android-arm-eabi-1.6.5.tgz", - "integrity": "sha512-/xPMebqSFb6fePZdwD9Pz8pIc9GkCGlMOQA4ucFtKeepQrMhoil4qN+ri5DUSeW9PFvnj/cEWuPEeNDbFc4WyA==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-android-arm64": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-android-arm64/-/decancer-android-arm64-1.6.5.tgz", - "integrity": "sha512-FraWlT6lfjFuV5L383+XzzFD5I2pzZ5TRDfix4BVXvDxjnO8ZKt4AB7DbWteDVu18zZbuVpBh3Tp1Eu1adidUg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-darwin-arm64": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-darwin-arm64/-/decancer-darwin-arm64-1.6.5.tgz", - "integrity": "sha512-pOarySDUPdxf/YQ673HxH+tJubapiiPt7SOuK8lRw3NoiQZ3cOf/LofU8rEUgK/yKfDyWbmQLnvMi1Hl1JTmhg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-darwin-x64": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-darwin-x64/-/decancer-darwin-x64-1.6.5.tgz", - "integrity": "sha512-B7JzUC5EHQtA7yJOrdRrKjHWBbzvBaPNAKHBKJ+brAwqa3yStpBodJZTgsfGHidCY8Gje0pGxSZVbPnGs+5Xbg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-linux-arm-gnueabihf": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-arm-gnueabihf/-/decancer-linux-arm-gnueabihf-1.6.5.tgz", - "integrity": "sha512-1ALPTMzUo750lIuHn/paZ2wq+GkDXv15dP9w4bmP3ytrYVj8b0uJmb57FexNEIMPf4M0gkGmyNLjq+Y1AJoQvw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-linux-arm64-gnu": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-arm64-gnu/-/decancer-linux-arm64-gnu-1.6.5.tgz", - "integrity": "sha512-RDNTJ9812zlQkWRxZS513xZoSUeRbONzcFX6VDnl/Lx20NVRZOryF8o4EtKDwzEd3kQrIr2tQlZyLLLsTGL48g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-linux-arm64-musl": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-arm64-musl/-/decancer-linux-arm64-musl-1.6.5.tgz", - "integrity": "sha512-Qnm6yQxvUF8uojyIIUiZepTZIyJN2jdqvm2QptxfB1LGABkWCpTlv+xml8KTPFTUcPT1Epwye1FaVEwd90W7VQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-linux-x64-gnu": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-x64-gnu/-/decancer-linux-x64-gnu-1.6.5.tgz", - "integrity": "sha512-eKdF0jsdVpm+lcDHALqtH79Z8vPX5qsoJHM2YHrQ5pJ3khP2+OcEYdAyis2rl438OK6NJyQZfECSUSj9dtfAeQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-linux-x64-musl": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-x64-musl/-/decancer-linux-x64-musl-1.6.5.tgz", - "integrity": "sha512-RaJoJSIkLSu0l4tODVFWoeE+4BSVjHUn+Gb3yQKn5DHmsl1/EOuj6rlwX6uddleV9aQIDEAljSL3SExNZbmm2A==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-win32-arm64-msvc": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-win32-arm64-msvc/-/decancer-win32-arm64-msvc-1.6.5.tgz", - "integrity": "sha512-SXI6r8m+J4vYCvjKQeafukSpBORU9wzM38SkBhjdp8wfcHVYfjVtoHVr4JxkpNfMJoIhv9j2MJYIaYVExvw8pg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-win32-ia32-msvc": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-win32-ia32-msvc/-/decancer-win32-ia32-msvc-1.6.5.tgz", - "integrity": "sha512-Ml6Gx6iwOzLopcH8BEd3Kn2MQ1bg5S4mKHQrxNn2MK3SqCLNzZKZhoWe18aCzaKLnwTTkcZvXNcZk1OLPLxmUw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@vierofernando/decancer-win32-x64-msvc": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-win32-x64-msvc/-/decancer-win32-x64-msvc-1.6.5.tgz", - "integrity": "sha512-fnMKkLkYJ/K/e1jo6WfwpoI4eNEkcWsA/rJMMF6XGLD6YhHnZZdlugqsbSK8j4GiBBTfgj6yCeCMEfLe2QP15w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "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/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/bson": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.4.0.tgz", - "integrity": "sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==", - "engines": { - "node": ">=14.20.1" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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.1.13" - } - }, - "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/decancer": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/decancer/-/decancer-1.6.5.tgz", - "integrity": "sha512-7p9ShXWXwKwTIwa5VoFtvUpIWMI4MEgCYLJqOwczBDyEHR65GhTxW/l0V/fazxV3w9KvlmoHI0HtTTI3nSNy1A==", - "engines": { - "node": ">= 16" - }, - "optionalDependencies": { - "@vierofernando/decancer-android-arm-eabi": "1.6.5", - "@vierofernando/decancer-android-arm64": "1.6.5", - "@vierofernando/decancer-darwin-arm64": "1.6.5", - "@vierofernando/decancer-darwin-x64": "1.6.5", - "@vierofernando/decancer-linux-arm-gnueabihf": "1.6.5", - "@vierofernando/decancer-linux-arm64-gnu": "1.6.5", - "@vierofernando/decancer-linux-arm64-musl": "1.6.5", - "@vierofernando/decancer-linux-x64-gnu": "1.6.5", - "@vierofernando/decancer-linux-x64-musl": "1.6.5", - "@vierofernando/decancer-win32-arm64-msvc": "1.6.5", - "@vierofernando/decancer-win32-ia32-msvc": "1.6.5", - "@vierofernando/decancer-win32-x64-msvc": "1.6.5" - } - }, - "node_modules/discord-api-types": { - "version": "0.37.43", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.43.tgz", - "integrity": "sha512-bBhDWU3TF9KADxR/mHp1K4Bvu/LRtFQdGyBjADu4e66F3ZnD4kp12W/SJCttIaCcMXzPV3sfty6eDGRNRph51Q==" - }, - "node_modules/discord.js": { - "version": "14.11.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.11.0.tgz", - "integrity": "sha512-CkueWYFQ28U38YPR8HgsBR/QT35oPpMbEsTNM30Fs8loBIhnA4s70AwQEoy6JvLcpWWJO7GY0y2BUzZmuBMepQ==", - "dependencies": { - "@discordjs/builders": "^1.6.3", - "@discordjs/collection": "^1.5.1", - "@discordjs/formatters": "^0.3.1", - "@discordjs/rest": "^1.7.1", - "@discordjs/util": "^0.3.1", - "@discordjs/ws": "^0.8.3", - "@sapphire/snowflake": "^3.4.2", - "@types/ws": "^8.5.4", - "discord-api-types": "^0.37.41", - "fast-deep-equal": "^3.1.3", - "lodash.snakecase": "^4.1.1", - "tslib": "^2.5.0", - "undici": "^5.22.0", - "ws": "^8.13.0" - }, - "engines": { - "node": ">=16.9.0" - } - }, - "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/file-type": { - "version": "18.5.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.5.0.tgz", - "integrity": "sha512-yvpl5U868+V6PqXHMmsESpg6unQ5GfnPssl4dxdJudBrr9qy7Fddt7EVX1VLlddFfe8Gj9N7goCZH22FXuSQXQ==", - "dependencies": { - "readable-web-to-node-stream": "^3.0.2", - "strtok3": "^7.0.0", - "token-types": "^5.0.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "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/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "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/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "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/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "node_modules/mongodb": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.8.1.tgz", - "integrity": "sha512-wKyh4kZvm6NrCPH8AxyzXm3JBoEf4Xulo0aUWh3hCgwgYJxyQ1KLST86ZZaSWdj6/kxYUA3+YZuyADCE61CMSg==", - "dependencies": { - "bson": "^5.4.0", - "mongodb-connection-string-url": "^2.6.0", - "socks": "^2.7.1" - }, - "engines": { - "node": ">=14.20.1" - }, - "optionalDependencies": { - "@mongodb-js/saslprep": "^1.1.0" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.0.0", - "kerberos": "^1.0.0 || ^2.0.0", - "mongodb-client-encryption": ">=2.3.0 <3", - "snappy": "^7.2.2" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/parse-duration": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.0.tgz", - "integrity": "sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ==" - }, - "node_modules/peek-readable": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", - "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "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/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", - "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "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/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/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "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/strtok3": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz", - "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/token-types": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", - "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "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/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" - }, - "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/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/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "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 - } - } - } - }, - "dependencies": { - "@discordjs/builders": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.3.tgz", - "integrity": "sha512-CTCh8NqED3iecTNuiz49mwSsrc2iQb4d0MjMdmS/8pb69Y4IlzJ/DIy/p5GFlgOrFbNO2WzMHkWKQSiJ3VNXaw==", - "requires": { - "@discordjs/formatters": "^0.3.1", - "@discordjs/util": "^0.3.1", - "@sapphire/shapeshift": "^3.8.2", - "discord-api-types": "^0.37.41", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.3", - "tslib": "^2.5.0" - } - }, - "@discordjs/collection": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.1.tgz", - "integrity": "sha512-aWEc9DCf3TMDe9iaJoOnO2+JVAjeRNuRxPZQA6GVvBf+Z3gqUuWYBy2NWh4+5CLYq5uoc3MOvUQ5H5m8CJBqOA==" - }, - "@discordjs/formatters": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.1.tgz", - "integrity": "sha512-M7X4IGiSeh4znwcRGcs+49B5tBkNDn4k5bmhxJDAUhRxRHTiFAOTVUNQ6yAKySu5jZTnCbSvTYHW3w0rAzV1MA==", - "requires": { - "discord-api-types": "^0.37.41" - } - }, - "@discordjs/rest": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.7.1.tgz", - "integrity": "sha512-Ofa9UqT0U45G/eX86cURQnX7gzOJLG2oC28VhIk/G6IliYgQF7jFByBJEykPSHE4MxPhqCleYvmsrtfKh1nYmQ==", - "requires": { - "@discordjs/collection": "^1.5.1", - "@discordjs/util": "^0.3.0", - "@sapphire/async-queue": "^1.5.0", - "@sapphire/snowflake": "^3.4.2", - "discord-api-types": "^0.37.41", - "file-type": "^18.3.0", - "tslib": "^2.5.0", - "undici": "^5.22.0" - } - }, - "@discordjs/util": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.3.1.tgz", - "integrity": "sha512-HxXKYKg7vohx2/OupUN/4Sd02Ev3PBJ5q0gtjdcvXb0ErCva8jNHWfe/v5sU3UKjIB/uxOhc+TDOnhqffj9pRA==" - }, - "@discordjs/ws": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-0.8.3.tgz", - "integrity": "sha512-hcYtppanjHecbdNyCKQNH2I4RP9UrphDgmRgLYrATEQF1oo4sYSve7ZmGsBEXSzH72MO2tBPdWSThunbxUVk0g==", - "requires": { - "@discordjs/collection": "^1.5.1", - "@discordjs/rest": "^1.7.1", - "@discordjs/util": "^0.3.1", - "@sapphire/async-queue": "^1.5.0", - "@types/ws": "^8.5.4", - "@vladfrangu/async_event_emitter": "^2.2.1", - "discord-api-types": "^0.37.41", - "tslib": "^2.5.0", - "ws": "^8.13.0" - } - }, - "@mongodb-js/saslprep": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.0.tgz", - "integrity": "sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, - "@revanced-helper/helper-client": { - "version": "1.0.0", - "requires": { - "bson": "^4.7.0" - }, - "dependencies": { - "bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "requires": { - "buffer": "^5.6.0" - } - } - } - }, - "@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==" - }, - "@sapphire/shapeshift": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.2.tgz", - "integrity": "sha512-YRbCXWy969oGIdqR/wha62eX8GNHsvyYi0Rfd4rNW6tSVVa8p0ELiMEuOH/k8rgtvRoM+EMV7Csqz77YdwiDpA==", - "requires": { - "fast-deep-equal": "^3.1.3", - "lodash": "^4.17.21" - } - }, - "@sapphire/snowflake": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", - "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==" - }, - "@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" - }, - "@types/node": { - "version": "20.2.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", - "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" - }, - "@types/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" - }, - "@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", - "requires": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, - "@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", - "requires": { - "@types/node": "*" - } - }, - "@vierofernando/decancer-android-arm-eabi": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-android-arm-eabi/-/decancer-android-arm-eabi-1.6.5.tgz", - "integrity": "sha512-/xPMebqSFb6fePZdwD9Pz8pIc9GkCGlMOQA4ucFtKeepQrMhoil4qN+ri5DUSeW9PFvnj/cEWuPEeNDbFc4WyA==", - "optional": true - }, - "@vierofernando/decancer-android-arm64": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-android-arm64/-/decancer-android-arm64-1.6.5.tgz", - "integrity": "sha512-FraWlT6lfjFuV5L383+XzzFD5I2pzZ5TRDfix4BVXvDxjnO8ZKt4AB7DbWteDVu18zZbuVpBh3Tp1Eu1adidUg==", - "optional": true - }, - "@vierofernando/decancer-darwin-arm64": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-darwin-arm64/-/decancer-darwin-arm64-1.6.5.tgz", - "integrity": "sha512-pOarySDUPdxf/YQ673HxH+tJubapiiPt7SOuK8lRw3NoiQZ3cOf/LofU8rEUgK/yKfDyWbmQLnvMi1Hl1JTmhg==", - "optional": true - }, - "@vierofernando/decancer-darwin-x64": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-darwin-x64/-/decancer-darwin-x64-1.6.5.tgz", - "integrity": "sha512-B7JzUC5EHQtA7yJOrdRrKjHWBbzvBaPNAKHBKJ+brAwqa3yStpBodJZTgsfGHidCY8Gje0pGxSZVbPnGs+5Xbg==", - "optional": true - }, - "@vierofernando/decancer-linux-arm-gnueabihf": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-arm-gnueabihf/-/decancer-linux-arm-gnueabihf-1.6.5.tgz", - "integrity": "sha512-1ALPTMzUo750lIuHn/paZ2wq+GkDXv15dP9w4bmP3ytrYVj8b0uJmb57FexNEIMPf4M0gkGmyNLjq+Y1AJoQvw==", - "optional": true - }, - "@vierofernando/decancer-linux-arm64-gnu": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-arm64-gnu/-/decancer-linux-arm64-gnu-1.6.5.tgz", - "integrity": "sha512-RDNTJ9812zlQkWRxZS513xZoSUeRbONzcFX6VDnl/Lx20NVRZOryF8o4EtKDwzEd3kQrIr2tQlZyLLLsTGL48g==", - "optional": true - }, - "@vierofernando/decancer-linux-arm64-musl": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-arm64-musl/-/decancer-linux-arm64-musl-1.6.5.tgz", - "integrity": "sha512-Qnm6yQxvUF8uojyIIUiZepTZIyJN2jdqvm2QptxfB1LGABkWCpTlv+xml8KTPFTUcPT1Epwye1FaVEwd90W7VQ==", - "optional": true - }, - "@vierofernando/decancer-linux-x64-gnu": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-x64-gnu/-/decancer-linux-x64-gnu-1.6.5.tgz", - "integrity": "sha512-eKdF0jsdVpm+lcDHALqtH79Z8vPX5qsoJHM2YHrQ5pJ3khP2+OcEYdAyis2rl438OK6NJyQZfECSUSj9dtfAeQ==", - "optional": true - }, - "@vierofernando/decancer-linux-x64-musl": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-linux-x64-musl/-/decancer-linux-x64-musl-1.6.5.tgz", - "integrity": "sha512-RaJoJSIkLSu0l4tODVFWoeE+4BSVjHUn+Gb3yQKn5DHmsl1/EOuj6rlwX6uddleV9aQIDEAljSL3SExNZbmm2A==", - "optional": true - }, - "@vierofernando/decancer-win32-arm64-msvc": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-win32-arm64-msvc/-/decancer-win32-arm64-msvc-1.6.5.tgz", - "integrity": "sha512-SXI6r8m+J4vYCvjKQeafukSpBORU9wzM38SkBhjdp8wfcHVYfjVtoHVr4JxkpNfMJoIhv9j2MJYIaYVExvw8pg==", - "optional": true - }, - "@vierofernando/decancer-win32-ia32-msvc": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-win32-ia32-msvc/-/decancer-win32-ia32-msvc-1.6.5.tgz", - "integrity": "sha512-Ml6Gx6iwOzLopcH8BEd3Kn2MQ1bg5S4mKHQrxNn2MK3SqCLNzZKZhoWe18aCzaKLnwTTkcZvXNcZk1OLPLxmUw==", - "optional": true - }, - "@vierofernando/decancer-win32-x64-msvc": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@vierofernando/decancer-win32-x64-msvc/-/decancer-win32-x64-msvc-1.6.5.tgz", - "integrity": "sha512-fnMKkLkYJ/K/e1jo6WfwpoI4eNEkcWsA/rJMMF6XGLD6YhHnZZdlugqsbSK8j4GiBBTfgj6yCeCMEfLe2QP15w==", - "optional": true - }, - "@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==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bson": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.4.0.tgz", - "integrity": "sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==" - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "requires": { - "streamsearch": "^1.1.0" - } - }, - "decancer": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/decancer/-/decancer-1.6.5.tgz", - "integrity": "sha512-7p9ShXWXwKwTIwa5VoFtvUpIWMI4MEgCYLJqOwczBDyEHR65GhTxW/l0V/fazxV3w9KvlmoHI0HtTTI3nSNy1A==", - "requires": { - "@vierofernando/decancer-android-arm-eabi": "1.6.5", - "@vierofernando/decancer-android-arm64": "1.6.5", - "@vierofernando/decancer-darwin-arm64": "1.6.5", - "@vierofernando/decancer-darwin-x64": "1.6.5", - "@vierofernando/decancer-linux-arm-gnueabihf": "1.6.5", - "@vierofernando/decancer-linux-arm64-gnu": "1.6.5", - "@vierofernando/decancer-linux-arm64-musl": "1.6.5", - "@vierofernando/decancer-linux-x64-gnu": "1.6.5", - "@vierofernando/decancer-linux-x64-musl": "1.6.5", - "@vierofernando/decancer-win32-arm64-msvc": "1.6.5", - "@vierofernando/decancer-win32-ia32-msvc": "1.6.5", - "@vierofernando/decancer-win32-x64-msvc": "1.6.5" - } - }, - "discord-api-types": { - "version": "0.37.43", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.43.tgz", - "integrity": "sha512-bBhDWU3TF9KADxR/mHp1K4Bvu/LRtFQdGyBjADu4e66F3ZnD4kp12W/SJCttIaCcMXzPV3sfty6eDGRNRph51Q==" - }, - "discord.js": { - "version": "14.11.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.11.0.tgz", - "integrity": "sha512-CkueWYFQ28U38YPR8HgsBR/QT35oPpMbEsTNM30Fs8loBIhnA4s70AwQEoy6JvLcpWWJO7GY0y2BUzZmuBMepQ==", - "requires": { - "@discordjs/builders": "^1.6.3", - "@discordjs/collection": "^1.5.1", - "@discordjs/formatters": "^0.3.1", - "@discordjs/rest": "^1.7.1", - "@discordjs/util": "^0.3.1", - "@discordjs/ws": "^0.8.3", - "@sapphire/snowflake": "^3.4.2", - "@types/ws": "^8.5.4", - "discord-api-types": "^0.37.41", - "fast-deep-equal": "^3.1.3", - "lodash.snakecase": "^4.1.1", - "tslib": "^2.5.0", - "undici": "^5.22.0", - "ws": "^8.13.0" - } - }, - "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==" - }, - "file-type": { - "version": "18.5.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.5.0.tgz", - "integrity": "sha512-yvpl5U868+V6PqXHMmsESpg6unQ5GfnPssl4dxdJudBrr9qy7Fddt7EVX1VLlddFfe8Gj9N7goCZH22FXuSQXQ==", - "requires": { - "readable-web-to-node-stream": "^3.0.2", - "strtok3": "^7.0.0", - "token-types": "^5.0.1" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" - }, - "memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "mongodb": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.8.1.tgz", - "integrity": "sha512-wKyh4kZvm6NrCPH8AxyzXm3JBoEf4Xulo0aUWh3hCgwgYJxyQ1KLST86ZZaSWdj6/kxYUA3+YZuyADCE61CMSg==", - "requires": { - "@mongodb-js/saslprep": "^1.1.0", - "bson": "^5.4.0", - "mongodb-connection-string-url": "^2.6.0", - "socks": "^2.7.1" - } - }, - "mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", - "requires": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "parse-duration": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.0.tgz", - "integrity": "sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ==" - }, - "peek-readable": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", - "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==" - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", - "requires": { - "readable-stream": "^3.6.0" - } - }, - "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==" - }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" - }, - "socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "requires": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - } - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "requires": { - "memory-pager": "^1.0.2" - } - }, - "streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" - }, - "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==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strtok3": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz", - "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==", - "requires": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.0.0" - } - }, - "token-types": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", - "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", - "requires": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-mixer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", - "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" - }, - "tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" - }, - "undici": { - "version": "5.22.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", - "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", - "requires": { - "busboy": "^1.6.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "requires": {} - } - } -} diff --git a/apps/bot-discord/src/package.json b/apps/bot-discord/src/package.json deleted file mode 100644 index d7b0df5..0000000 --- a/apps/bot-discord/src/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "bot-discord", - "version": "1.0.0", - "description": "", - "main": "index.js", - "type": "module", - "keywords": [], - "author": "Reis Can", - "license": "GPL-3.0-or-later", - "dependencies": { - "@revanced-helper/helper-client": "file:../../../packages/client", - "decancer": "^1.6.5", - "discord.js": "^14.11.0", - "mongodb": "^5.7.0", - "parse-duration": "^1.1.0" - } -} diff --git a/apps/bot-discord/src/registerCommands.js b/apps/bot-discord/src/registerCommands.js deleted file mode 100644 index 1cea873..0000000 --- a/apps/bot-discord/src/registerCommands.js +++ /dev/null @@ -1,36 +0,0 @@ -import { REST, Routes } from 'discord.js'; -import { readdirSync, readFileSync } from 'node:fs'; -const configJSON = readFileSync('./config.json', 'utf-8'); -const config = JSON.parse(configJSON); - -const commands = []; -// Grab all the command files from the commands directory you created earlier -const commandFiles = readdirSync('./commands').filter((file) => - file.endsWith('.js') -); - -// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment -for (const file of commandFiles) { - const command = await import(`./commands/${file}`); - if (!command.default.data.toJSON) continue; - commands.push(command.default.data.toJSON()); -} - -// Construct and prepare an instance of the REST module -const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN); - -try { - console.log( - `Started refreshing ${commands.length} application (/) commands.` - ); - - // The put method is used to fully refresh all commands in the guild with the current set - const data = await rest.put(Routes.applicationCommands(config.discord.botId), { - body: commands - }); - - console.log(`Successfully reloaded ${data.length} application (/) commands.`); -} catch (error) { - // And of course, make sure you catch and log any errors! - console.error(error); -} \ No newline at end of file diff --git a/apps/bot-discord/src/utils/checkModPerms.js b/apps/bot-discord/src/utils/checkModPerms.js deleted file mode 100644 index 6907adb..0000000 --- a/apps/bot-discord/src/utils/checkModPerms.js +++ /dev/null @@ -1,8 +0,0 @@ -export function checkForPerms(config, member) { - for (const role of config.discord.modRoles) { - if (member.roles.cache.has(role)) { - return true; - } - } - return false; -} \ No newline at end of file diff --git a/apps/bot-discord/src/utils/checkSupporterPerms.js b/apps/bot-discord/src/utils/checkSupporterPerms.js deleted file mode 100644 index 6deadd3..0000000 --- a/apps/bot-discord/src/utils/checkSupporterPerms.js +++ /dev/null @@ -1,8 +0,0 @@ -export function checkForPerms(config, member) { - for (const role of config.discord.trainRoles) { - if (member.roles.cache.has(role)) { - return true; - } - } - return false; -} \ No newline at end of file diff --git a/apps/bot-discord/src/utils/cureUsername.js b/apps/bot-discord/src/utils/cureUsername.js deleted file mode 100644 index f0b32f2..0000000 --- a/apps/bot-discord/src/utils/cureUsername.js +++ /dev/null @@ -1,17 +0,0 @@ -import decancer from 'decancer'; - -export default async function cureUsername(member) { - let toCure = member.user.displayName; - if (member.nickname) toCure = member.nickname; - - if (!/^[\x20-\x7D]+$/.test(toCure)) { - // Cure username/nickname if there's non-ascii characters. - - let curedName = decancer(toCure).toString(); - - // If the output is empty, rename them to "ReVanced member". - if (/^\s*$/.test(curedName)) curedName = 'ReVanced member'; - - member.setNickname(curedName); - } -} \ No newline at end of file diff --git a/apps/bot-discord/src/utils/exileMemberToChannel.js b/apps/bot-discord/src/utils/exileMemberToChannel.js deleted file mode 100644 index 8cbfaac..0000000 --- a/apps/bot-discord/src/utils/exileMemberToChannel.js +++ /dev/null @@ -1,47 +0,0 @@ -export default async function exileMemberToChannel(member, channel, message, reason, config) { - const redirectChannel = await channel.client.channels.fetch(config.discord.supportChannel); - - let messageContent = ''; - if (Array.isArray(message)) { - for (const msg of message) { - messageContent += `${msg}\n`; - } - } else if (!message) { - message = 'No message provided' - } else messageContent = message; - - const embedFields = [ - { - name: 'Orginal message', - value: messageContent - } - ]; - - if (reason) { - embedFields.push( - { - name: 'Reason', - value: reason - } - ); - } - - - await redirectChannel.send({ - content: `<@${member.id}>`, - embeds: [ - { - title: `Restricted to <#${redirectChannel.id}>`, - fields: embedFields, - thumbnail: { - url: member.avatarURL() - }, - footer: { - text: 'ReVanced', - icon_url: channel.client.user.avatarURL() - }, - color: 5150960 - } - ] - }); -} \ No newline at end of file diff --git a/apps/bot-discord/src/utils/muteMember.js b/apps/bot-discord/src/utils/muteMember.js deleted file mode 100644 index 5765c65..0000000 --- a/apps/bot-discord/src/utils/muteMember.js +++ /dev/null @@ -1,87 +0,0 @@ -import parse from 'parse-duration' -import setMuteTimeout from './setMuteTimeout.js'; - -parse['mo'] = parse['month'] - -export default async function muteMember(config, member, { duration, reason, supportMute, guild }) { - let expires; - - if (supportMute) { - expires = Math.floor((Date.now() + config.discord.mute.supportMuteDuration) / 1000); - } else { - const parsedDuration = parse(duration); - expires = Math.floor((Date.now() + parsedDuration) / 1000); - } - - const takenRoles = []; - for (const takeRole of supportMute ? - config.discord.mute.supportTakeRoles : - config.discord.mute.takeRoles - ) { - if (member.roles.cache.get(takeRole)) { - takenRoles.push(takeRole); - } - } - - const existingMute = await guild.client.db.collection('muted').findOne({ - guild_id: guild.id, - user_id: member.id - }); - - if (existingMute) { - // Update existing mute - - await guild.client.db.collection('muted').updateOne({ - guild_id: guild.id, - user_id: member.id - }, { - $set: { - reason, - expires, - support_mute: supportMute - } - }); - - if (guild.client.mutes.has(member.id)) { - clearTimeout(guild.client.mutes.get(member.id)) - guild.client.mutes.delete(member.id); - } - } else { - await guild.client.db.collection('muted').insertOne({ - guild_id: guild.id, - user_id: member.id, - taken_roles: takenRoles, - expires, - reason, - support_mute: supportMute - }); - } - - // Remove the roles, give defined roles. - if (!existingMute) { - const currentRoles = member.roles.cache.map((role) => role.id); - let setRoles = []; - for (const role of currentRoles) { - if (takenRoles.includes(role)) continue; - setRoles.push(role); - } - - setRoles = setRoles.concat(supportMute ? - config.discord.mute.supportGiveRoles : - config.discord.mute.giveRoles) - await member.roles.set(setRoles); - } - - - // Start a timeout. - setMuteTimeout({ - guild_id: guild.id, - user_id: member.id, - taken_roles: takenRoles, - expires, - support_mute: supportMute - }, guild.client, config); - - // Return parsed time for the mute command to resolve. - return expires; -} \ No newline at end of file diff --git a/apps/bot-discord/src/utils/reportToLogs.js b/apps/bot-discord/src/utils/reportToLogs.js deleted file mode 100644 index 6e7db8b..0000000 --- a/apps/bot-discord/src/utils/reportToLogs.js +++ /dev/null @@ -1,45 +0,0 @@ -import { EmbedBuilder, messageLink } from 'discord.js'; - -export default async function reportToLogs(config, client, action, message, { reason, expire, actionTo, actionBy }, interaction, commandMsg) { - const actionUpper = action.charAt(0).toUpperCase() + action.slice(1); - const actionTitle = `${actionUpper} ${actionTo.tag}`; - const actionEmbed = new EmbedBuilder() - .setThumbnail(actionTo.avatarURL()) - .setTitle(actionTitle) - .setColor(5150960) - .setFooter({ text: 'ReVanced', iconURL: client.user.avatarURL() }); - - const fields = [ - { name: 'Action', value: `${actionTo.toString()} was ${action} by ${actionBy.toString()}` } - ]; - - if (action === 'banned' || action === 'muted' || action === 'exiled') fields.push({ - name: 'Reason', - value: reason ? reason : 'No reason provided', - inline: true - }); - - if (expire) fields.push({ name: 'Expires', value: ``, inline: true }); - - if (message) fields.push({ name: 'Reference', value: `[Jump to message](${messageLink( - message.channelId, - message.id, - message.guild.id)})`, - inline: true - }); - - actionEmbed.setFields(fields); - - if (interaction) { - await interaction.editReply({ embeds: [actionEmbed] }); - const msg = await interaction.fetchReply(); - reportToLogs(config, client, action, msg, { reason, expire, actionTo, actionBy }); - } else if (commandMsg) { - const msg = await commandMsg.reply({ embeds: [actionEmbed] }); - reportToLogs(config, client, action, msg, { reason, expire, actionTo, actionBy }); - } else { - const channel = await client.channels.fetch(config.logs.channelId); - const thread = await channel.threads.fetch(config.logs.threadId); - thread.send({ embeds: [actionEmbed] }); - } -} \ No newline at end of file diff --git a/apps/bot-discord/src/utils/setMuteTimeout.js b/apps/bot-discord/src/utils/setMuteTimeout.js deleted file mode 100644 index 415638d..0000000 --- a/apps/bot-discord/src/utils/setMuteTimeout.js +++ /dev/null @@ -1,24 +0,0 @@ -export default async function setMuteTimeout(mute, client, config) { - const duration = (mute.expires - Math.floor(new Date() / 1000)) * 1000; - client.mutes.set(mute.user_id, setTimeout(async() => { - const guild = await client.guilds.fetch(mute.guild_id); - let member; - try { - member = await guild.members.fetch(mute.user_id); - } catch (_) { - return; - } - - member.roles.add(mute.taken_roles); - member.roles.remove( - mute.support_mute ? - config.discord.mute.supportGiveRoles : - config.discord.mute.giveRoles - ); - - client.db.collection('muted').deleteOne({ - user_id: mute.user_id, - guild_id: mute.guild_id - }); - }, duration)); -} \ No newline at end of file diff --git a/apps/bot-discord/src/utils/trainAISelectMenu.js b/apps/bot-discord/src/utils/trainAISelectMenu.js deleted file mode 100644 index 9e36c28..0000000 --- a/apps/bot-discord/src/utils/trainAISelectMenu.js +++ /dev/null @@ -1,77 +0,0 @@ -import { - ActionRowBuilder, - StringSelectMenuBuilder, - ComponentType -} from 'discord.js'; - -export default async function trainAISelectMenu( - interaction, - config, - helper -) { - const options = []; - - for (const { label } of config.responses) { - options.push({ - label: label, - description: `The ${label} label.`, - value: label.toLowerCase() - }); - } - - const row = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('select') - .setPlaceholder('Nothing selected') - .addOptions(options) - ); - - let interactedMessage; - - if (!interaction.isMessageContextMenuCommand()) { - try { - const channel = await interaction.client.channels.fetch(interaction.message.reference.channelId); - const message = await channel.messages.fetch(interaction.message.reference.messageId); - interactedMessage = message.content.toLowerCase(); - } catch (e) { - interaction.reply({ - content: 'The message that you wanted to train the bot with was deleted.', - ephemeral: true - }) - } - - } else { - interactedMessage = interaction.targetMessage.content.toLowerCase() - } - - const reply = await interaction.reply({ - content: 'Please select the corresponding label to train the bot.', - components: [row], - ephemeral: true - }); - - const collector = reply.createMessageComponentCollector({ - componentType: ComponentType.StringSelect, - time: 30000 - }); - - const voteId = interaction.targetMessage ? interaction.targetMessage.id : - interaction.message.reference.messageId; - collector.on('collect', (i) => { - interaction.editReply({ content: 'Sent training data to server.', components: [] }); - - const existingVote = interaction.client.trainingVotes.get(voteId); - if (existingVote) clearTimeout(existingVote); - interaction.client.trainingVotes.set(voteId, - setTimeout(() => { - helper.sendTrainData(interactedMessage, i.values[0]); - - if (!interaction.isMessageContextMenuCommand()) { - interaction.message.edit({ components: [] }); - } - - interaction.client.trainingVotes.delete(voteId); - }, 10_000) - ); - }); -} diff --git a/apps/bot-discord/src/utils/unmuteMember.js b/apps/bot-discord/src/utils/unmuteMember.js deleted file mode 100644 index 23499f4..0000000 --- a/apps/bot-discord/src/utils/unmuteMember.js +++ /dev/null @@ -1,24 +0,0 @@ -export default async function unmuteMember(config, member, supportMute) { - const mute = await member.client.db.collection('muted').findOne({ - guild_id: member.guild.id, - user_id: member.id - }); - - if (!mute) return false; - if (supportMute) { - if (!mute.support_mute) return false; - } - - member.roles.remove(mute.support_mute ? - config.discord.mute.supportGiveRoles : - config.discord.mute.giveRoles - ); - - member.roles.add(mute.taken_roles); - - await member.client.db.collection('muted').deleteOne({ - user_id: member.id - }); - - return true; -} \ No newline at end of file diff --git a/apps/bot-telegram/src/commands/train.js b/apps/bot-telegram/src/commands/train.js deleted file mode 100644 index ddde333..0000000 --- a/apps/bot-telegram/src/commands/train.js +++ /dev/null @@ -1,61 +0,0 @@ -export default { - command: /\/train/, - async execute(bot, config, msg) { - const admins = await bot.getChatAdministrators(msg.chat.id); - const isAdmin = admins.find((admin) => admin.user.id === msg.from.id); - - if (!isAdmin) - return bot.sendMessage(msg.chat.id, 'You\'re not an admin.', { - message_thread_id: msg.message_thread_id, - reply_to_message_id: msg.message_id - }); - - if (msg.reply_to_message.message_id === msg.message_thread_id) - return bot.sendMessage(msg.chat.id, 'Please reply to a message!', { - message_thread_id: msg.message_thread_id, - reply_to_message_id: msg.message_id - }); - - const options = []; - let arrI = 0; - let i = 0; - for (const { label } of config.responses) { - if (arrI === 0 && i === 0) { - options.push([ - { - text: label, - callback_data: `label_${label.toLowerCase()}` - } - ]); - i++; - } else if (i === 2) { - options.push([ - { - text: label, - callback_data: `label_${label.toLowerCase()}` - } - ]); - i = 0; - arrI++; - } else { - options[arrI].push({ - text: label, - callback_data: `label_${label.toLowerCase()}` - }); - i++; - } - } - - bot.sendMessage( - msg.chat.id, - 'Please select the corresponding label to train the bot.', - { - message_thread_id: msg.message_thread_id, - reply_to_message_id: msg.reply_to_message.message_id, - reply_markup: { - inline_keyboard: options - } - } - ); - } -}; diff --git a/apps/bot-telegram/src/config.example.json b/apps/bot-telegram/src/config.example.json deleted file mode 100644 index bd1706f..0000000 --- a/apps/bot-telegram/src/config.example.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "server": { - "port": 3000, - "host": "127.0.0.1" - }, - "responses": [ - { - "label": "revanced_crash", - "threshold": 0.85, - "reply": { - "title": "Why is my patched app crashing?", - "description": "It seems like your patched application is crashing, these might be the reason why:\n * You patched a non-suggested version.\n * Patches failed to apply.\n * Non-default patches were used.\n * Split APK was used." - } - }, - { - "label": "rvmanager_abort", - "threshold": 0.85, - "reply": { - "title": "Why is ReVanced Manager aborting?", - "description": "It seems like your Manager is aborting. If you see the text \"exit code = 135\" or \"exit code = 139\", then your device's architecture is not supported. Please patch on another device or on your PC." - } - }, - { - "label": "revanced_download", - "threshold": 0.85, - "reply": { - "title": "How to download ReVanced?", - "description": "ReVanced is a patcher. You can use the [ReVanced Manager](https://github.com/revanced/revanced-manager/releases/latest) or the [ReVanced CLI](https://github.com/revanced/revanced-cli) to patch the app you want to modify.\n* [ReVanced Manager docs](https://github.com/revanced/revanced-manager/tree/main/docs#-revanced-manager)\n* [ReVanced CLI docs](https://github.com/revanced/revanced-cli/tree/main/docs#-documentation-and-guides-of-revanced-cli)" - } - }, - { - "label": "androidtv_support", - "threshold": 0.85, - "reply": { - "title": "Will YT ReVanced support Android TVs?", - "description": "YouTube ReVanced for Android/Google TVs won't be made. There are alternatives, such as [SmartTubeNext](https://github.com/yuliskov/SmartTubeNext#smarttube)." - } - }, - { - "label": "revanced_nodownloader", - "threshold": 0.85, - "reply": { - "title": "Why can't I download videos?", - "description": "You need to install a downloader to download videos. NewPipe is the default downloader, and it can be downloaded from [here](https://github.com/TeamNewPipe/NewPipe/releases/latest)." - } - }, - { - "label": "revanced_casting", - "threshold": 0.85, - "reply": { - "title": "Why can't I cast?", - "description": "Because of devices like Chromecast, Android/Google TVs using the Cast v2 API, casting to those devices won't work." - } - }, - { - "label": "microg_download", - "threshold": 0.85, - "reply": { - "title": "From where can I download Vanced MicroG?", - "description": "You can download Vanced MicroG from [here](https://github.com/TeamVanced/VancedMicroG/releases/latest)." - } - }, - { - "label": "microg_nointernet", - "threshold": 0.85, - "reply": { - "title": "Why does YT ReVanced say I'm offline?", - "description": "If your YT ReVanced appears to not have internet and you recently changed your Google password, remove the Vanced MicroG account from within your device settings and then log back in." - } - }, - { - "label": "rvdownload_unofficial", - "threshold": 0.85, - "reply": { - "title": "What are the official ReVanced sites?", - "description": "The official sites for ReVanced are [revanced.app](https://revanced.app) and [github.com/revanced](https://github.com/revanced)." - } - }, - { - "label": "yt_buffering", - "threshold": 0.85, - "reply": { - "title": "Why do videos fail to play?", - "description": "Check [this announcement](https://t.me/app_revanced/19)." - } - }, - { - "label": "false", - "threshold": 0, - "reply": null - } - ], - "ocrResponses": [ - { - "regex": "is not installed", - "reply": { - "title": "Why can't I download videos?", - "description": "You need to install a downloader to download videos. NewPipe is the default downloader, and it can be downloaded from [here](https://github.com/TeamNewPipe/NewPipe/releases/latest)." - } - }, - { - "regex": "You're offline|Please check your", - "reply": { - "title": "Why does YT ReVanced say I'm offline?", - "description": "If your YT ReVanced appears to not have internet and you recently changed your Google password, remove the Vanced MicroG account from within your device settings and then log back in." - } - } - ] -} \ No newline at end of file diff --git a/apps/bot-telegram/src/events/callbackQuery.js b/apps/bot-telegram/src/events/callbackQuery.js deleted file mode 100644 index 8368286..0000000 --- a/apps/bot-telegram/src/events/callbackQuery.js +++ /dev/null @@ -1,26 +0,0 @@ -export default { - name: 'callback_query', - once: false, - async execute(bot, helper, cb) { - const admins = await bot.getChatAdministrators(cb.message.chat.id); - const isAdmin = admins.find((admin) => admin.user.id === cb.from.id); - - if (!isAdmin) - return bot.sendMessage(cb.message.chat.id, 'You\'re not an admin.', { - message_thread_id: cb.message.message_thread_id, - reply_to_message_id: cb.message.message_id - }); - - helper.sendTrainData( - cb.message.reply_to_message.text.toLowerCase(), - cb.data.replace('label_', '').toUpperCase() - ); - - bot.sendMessage(cb.message.chat.id, 'Sent training data to server.', { - message_thread_id: cb.message.message_thread_id, - reply_to_message_id: cb.message.message_id - }); - - bot.deleteMessage(cb.message.chat.id, cb.message.message_id); - } -}; diff --git a/apps/bot-telegram/src/events/message.js b/apps/bot-telegram/src/events/message.js deleted file mode 100644 index 53ceb03..0000000 --- a/apps/bot-telegram/src/events/message.js +++ /dev/null @@ -1,15 +0,0 @@ -export default { - name: 'message', - once: false, - async execute(bot, helper, msg) { - if (msg.photo) { - const fileLink = await bot.getFileLink(msg.photo.at(-1).file_id); - helper.scanImage(fileLink, `${msg.chat.id}/${msg.message_thread_id}/${msg.message_id}`) - } - if (!msg.text) return; - helper.scanText( - msg.text.toLowerCase(), - `${msg.chat.id}/${msg.message_thread_id}/${msg.message_id}` - ); - } -}; diff --git a/apps/bot-telegram/src/helperEvents/aiResponse.js b/apps/bot-telegram/src/helperEvents/aiResponse.js deleted file mode 100644 index e30322e..0000000 --- a/apps/bot-telegram/src/helperEvents/aiResponse.js +++ /dev/null @@ -1,28 +0,0 @@ -export default { - name: 'aiResponse', - once: false, - async execute(bot, config, aiRes) { - if (!aiRes.response) return; - if (!aiRes.response[0]) return; - const ids = aiRes.id.split('/'); - const intent = aiRes.response.reduce((a, b) => - a.confidence > b.confidence ? a : b - ); - const response = config.responses.find((res) => res.label === intent.name); - if (!response) return; - if (response.threshold > intent.confidence) return; - if (!response.reply) return; - - bot.sendMessage( - ids[0], - `**${response.reply.title}**\n\n${response.reply.description}\n`, - { - message_thread_id: ids[1], - reply_to_message_id: ids[2], - parse_mode: 'markdown' - } - ); - - return; - } -}; diff --git a/apps/bot-telegram/src/helperEvents/ocrResponse.js b/apps/bot-telegram/src/helperEvents/ocrResponse.js deleted file mode 100644 index e9c71de..0000000 --- a/apps/bot-telegram/src/helperEvents/ocrResponse.js +++ /dev/null @@ -1,24 +0,0 @@ -export default { - name: 'ocrResponse', - once: false, - async execute(bot, config, ocrRes) { - const ids = ocrRes.id.split('/'); - - for (const ocrReply of config.ocrResponses) { - if (ocrRes.ocrText.match(ocrReply.regex)) { - - bot.sendMessage( - ids[0], - `## ${ocrReply.reply.title}\n\n${ocrReply.reply.description}`, - { - message_thread_id: ids[1], - reply_to_message_id: ids[2], - parse_mode: 'markdown' - } - ); - break; - } - } - return; - } -}; diff --git a/apps/bot-telegram/src/index.js b/apps/bot-telegram/src/index.js deleted file mode 100644 index cfc9a99..0000000 --- a/apps/bot-telegram/src/index.js +++ /dev/null @@ -1,66 +0,0 @@ -import TelegramBot from 'node-telegram-bot-api'; -import { readFileSync, readdirSync } from 'node:fs'; -// Fix __dirname not being defined in ES modules. (https://stackoverflow.com/a/64383997) -import { fileURLToPath } from 'node:url'; -import { dirname, join } from 'node:path'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -import HelperClient from '@revanced-helper/helper-client'; -import config from './config.json' assert { type: 'json' }; - -const helper = new HelperClient(config); -helper.connect(); - -const bot = new TelegramBot(process.env.TELEGRAM_TOKEN, { polling: true }); - -const commandsPath = join(__dirname, 'commands'); -const commandFiles = readdirSync(commandsPath).filter((file) => - file.endsWith('.js') -); - -for (const file of commandFiles) { - const filePath = join(commandsPath, file); - const command = (await import(`file://${filePath}`)).default; - if ('command' in command && 'execute' in command) { - bot.onText(command.command, (...args) => - command.execute(bot, config, ...args) - ); - } else { - console.log( - `[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.` - ); - } -} - -const tgEventsPath = join(__dirname, 'events'); -const tgEventFiles = readdirSync(tgEventsPath).filter((file) => - file.endsWith('.js') -); - -for (const file of tgEventFiles) { - const filePath = join(tgEventsPath, file); - const event = (await import(`file://${filePath}`)).default; - if (event.once) { - bot.once(event.name, (...args) => event.execute(bot, helper, ...args)); - } else { - bot.on(event.name, (...args) => event.execute(bot, helper, ...args)); - } -} - -// The ReVanced Helper events. - -const helperEventsPath = join(__dirname, 'helperEvents'); -const helperEventFiles = readdirSync(helperEventsPath).filter((file) => - file.endsWith('.js') -); - -for (const file of helperEventFiles) { - const filePath = join(helperEventsPath, file); - const event = (await import(`file://${filePath}`)).default; - if (event.once) { - helper.once(event.name, (...args) => event.execute(bot, config, ...args)); - } else { - helper.on(event.name, (...args) => event.execute(bot, config, ...args)); - } -} diff --git a/apps/bot-telegram/src/package-lock.json b/apps/bot-telegram/src/package-lock.json deleted file mode 100644 index 822c834..0000000 --- a/apps/bot-telegram/src/package-lock.json +++ /dev/null @@ -1,2302 +0,0 @@ -{ - "name": "bot-telegram", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "bot-telegram", - "version": "1.0.0", - "license": "GPL-3.0-or-later", - "dependencies": { - "@revanced-helper/helper-client": "file:../../../packages/client", - "node-telegram-bot-api": "^0.61.0" - } - }, - "../../../packages/client": { - "name": "@revanced-helper/helper-client", - "version": "1.0.0", - "license": "GPL-3.0-or-later", - "dependencies": { - "bson": "^4.7.0" - } - }, - "node_modules/@revanced-helper/helper-client": { - "resolved": "../../../packages/client", - "link": true - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "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/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==", - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findindex": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/array.prototype.findindex/-/array.prototype.findindex-2.2.1.tgz", - "integrity": "sha512-tMj4uTmGpaGUh4XFMUh3H7KYAIqlygrlXchOEVTiICbTwRwMhDqtzsOwvtI+WAf1GdjJBeIP3Bu92Qg0SnXdtA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "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==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/bl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", - "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "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/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "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/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==", - "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/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "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/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "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.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - }, - "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==", - "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==", - "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==", - "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/eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "engines": [ - "node >=0.6.0" - ] - }, - "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-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==" - }, - "node_modules/file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "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==", - "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==", - "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==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "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==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "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==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "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/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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==", - "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-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "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-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "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==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "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==", - "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==", - "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==", - "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.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "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/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "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==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "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==" - }, - "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==" - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "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/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/node-telegram-bot-api": { - "version": "0.61.0", - "resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.61.0.tgz", - "integrity": "sha512-BZXd8Bh2C5+uBEQuuI3FD7TFJF3alV+6oFQt8CNLx3ldX/hsd+NYyllTX+Y+5X0tG+xtcRQQjbfLgz/4sRvmBQ==", - "dependencies": { - "array.prototype.findindex": "^2.0.2", - "bl": "^1.2.3", - "debug": "^3.2.7", - "eventemitter3": "^3.0.0", - "file-type": "^3.9.0", - "mime": "^1.6.0", - "pump": "^2.0.0", - "request": "^2.83.0", - "request-promise": "^4.2.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "engines": { - "node": "*" - } - }, - "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==", - "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==", - "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/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/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "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==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "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==", - "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/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request-promise": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz", - "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==", - "deprecated": "request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", - "dependencies": { - "bluebird": "^3.5.0", - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dependencies": { - "lodash": "^4.17.19" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "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==", - "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/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "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/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "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==", - "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==", - "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==", - "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/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" - }, - "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==", - "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/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "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/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/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, - "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==", - "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.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "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==" - } - }, - "dependencies": { - "@revanced-helper/helper-client": { - "version": "file:../../../packages/client", - "requires": { - "bson": "^4.7.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - } - }, - "array.prototype.findindex": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/array.prototype.findindex/-/array.prototype.findindex-2.2.1.tgz", - "integrity": "sha512-tMj4uTmGpaGUh4XFMUh3H7KYAIqlygrlXchOEVTiICbTwRwMhDqtzsOwvtI+WAf1GdjJBeIP3Bu92Qg0SnXdtA==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "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==" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" - }, - "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", - "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "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==", - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "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==", - "requires": { - "once": "^1.4.0" - } - }, - "es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "requires": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "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.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - } - }, - "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==", - "requires": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - } - }, - "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==", - "requires": { - "has": "^1.0.3" - } - }, - "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==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" - }, - "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==" - }, - "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==" - }, - "file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==" - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "requires": { - "is-callable": "^1.1.3" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "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==" - }, - "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==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "requires": { - "define-properties": "^1.1.3" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "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==" - }, - "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==", - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "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==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - } - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "requires": { - "has-bigints": "^1.0.1" - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "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==" - }, - "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==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "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==" - }, - "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==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "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==", - "requires": { - "call-bind": "^1.0.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "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==" - }, - "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==" - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "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==", - "requires": { - "mime-db": "1.52.0" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node-telegram-bot-api": { - "version": "0.61.0", - "resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.61.0.tgz", - "integrity": "sha512-BZXd8Bh2C5+uBEQuuI3FD7TFJF3alV+6oFQt8CNLx3ldX/hsd+NYyllTX+Y+5X0tG+xtcRQQjbfLgz/4sRvmBQ==", - "requires": { - "array.prototype.findindex": "^2.0.2", - "bl": "^1.2.3", - "debug": "^3.2.7", - "eventemitter3": "^3.0.0", - "file-type": "^3.9.0", - "mime": "^1.6.0", - "pump": "^2.0.0", - "request": "^2.83.0", - "request-promise": "^4.2.2" - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "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==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" - }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-promise": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz", - "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==", - "requires": { - "bluebird": "^3.5.0", - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "requires": { - "lodash": "^4.17.19" - } - }, - "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==" - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" - }, - "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==", - "requires": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - } - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "dependencies": { - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - } - } - }, - "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==", - "requires": { - "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" - } - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - } - } -} diff --git a/apps/bot-telegram/src/package.json b/apps/bot-telegram/src/package.json deleted file mode 100644 index 6cc77c6..0000000 --- a/apps/bot-telegram/src/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "bot-telegram", - "version": "1.0.0", - "description": "", - "main": "index.js", - "type": "module", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "Reis Can", - "license": "GPL-3.0-or-later", - "dependencies": { - "@revanced-helper/helper-client": "file:../../../packages/client", - "node-telegram-bot-api": "^0.61.0" - } -} diff --git a/apps/server/src/PROTOCOL.md b/apps/server/src/PROTOCOL.md deleted file mode 100644 index 4cfbdd4..0000000 --- a/apps/server/src/PROTOCOL.md +++ /dev/null @@ -1,64 +0,0 @@ -# Server Protocol - -The server uses TCP for connection and BSON for messages, so you need to serialize and deserialize the messages. - -## AI - -Sending the server this JSON (BSON) will send you back the AI predictions. - -```json -{ - "op": 1, - "id": "String", - "text": "How do i download ReVanced?" -} -``` - -And the server would return something like this: - -```json -{ - "op": 2, - "id": "String", - "response": [ - { - "confidence": 0.99, - "name": "revanced_download" - } - ] -} -``` - -### Training the AI - -To train the AI, send the server a JSON (BSON) like this: - -```json -{ - "op": 3, - "label": "revanced_download", - "text": "how to download revanced" -} -``` - -## OCR - -Sending the server this JSON (BSON) will send you back the read text. - -```json -{ - "op": 5, - "id": "String", - "url": "https://cdn.discordapp.com/attachments/1033338556493606963/1033338557231796224/Screenshot_20221022-121318.jpg" -} -``` - -And the server would return something like this: - -```json -{ - "op": 6, - "id": "String", - "ocrText": "..." -} -``` diff --git a/apps/server/src/config.example.json b/apps/server/src/config.example.json deleted file mode 100644 index cdd14d0..0000000 --- a/apps/server/src/config.example.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "regexSupport": false, - "regexResponses": [ - { - "label": "videobuffer", - "regex": "insert regex here" - } - ] -} \ No newline at end of file diff --git a/apps/server/src/events/index.js b/apps/server/src/events/index.js deleted file mode 100644 index 4cc4c44..0000000 --- a/apps/server/src/events/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import runAI from './runAI.js'; -import runOCR from './runOCR.js'; -import trainAI from './trainAI.js'; - -export { runAI, runOCR, trainAI }; \ No newline at end of file diff --git a/apps/server/src/events/runAI.js b/apps/server/src/events/runAI.js deleted file mode 100644 index 85b3772..0000000 --- a/apps/server/src/events/runAI.js +++ /dev/null @@ -1,43 +0,0 @@ -import { serialize } from 'bson'; - -export default async function runAI(client, data, config) { - if (config.regexSupport) { - for (const reply of config.regexResponses) { - if (new RegExp(reply.regex).test(data.text)) { - client.write( - serialize({ - op: 2, - id: data.id, - response: [ - { - confidence: 1, - name: reply.label - } - ] - }) - ); - return; - } - } - } - const witAIReq = await fetch( - `https://api.wit.ai/message?v=20230215&q=${encodeURI(data.text)}`, - { - headers: { - authorization: `Bearer ${process.env.WIT_AI_TOKEN}` - } - } - ); - - const response = await witAIReq.json(); - - client.write( - serialize({ - op: 2, - id: data.id, - response: response.intents - }) - ); - - return; -} \ No newline at end of file diff --git a/apps/server/src/events/runOCR.js b/apps/server/src/events/runOCR.js deleted file mode 100644 index 0f22ad8..0000000 --- a/apps/server/src/events/runOCR.js +++ /dev/null @@ -1,61 +0,0 @@ -import { createWorker } from 'tesseract.js'; -import { serialize } from 'bson'; -import EventEmitter from 'node:events'; - -const worker = await createWorker(); -await worker.loadLanguage('eng'); -await worker.initialize('eng'); - -async function recognize({ client, data }) { - const { data: { text } } = await worker.recognize(data.url); - - client.write( - serialize({ - op: 6, - id: data.id, - ocrText: text - }) - ); -} - -class Queue extends EventEmitter { - constructor() { - super(); - this.isRunning = false; - this.items = [] - } - - push(item) { - this.items.push(item); - this.emit('item', item) - } - - shift() { - return this.items.shift(); - } - -} - -const queue = new Queue(); - -queue.on('item', async ({ client, data }) => { - if (!queue.isRunning) { - queue.isRunning = true; - await recognize(queue.items.shift()); - queue.isRunning = false; - queue.emit('finished'); - } -}); - -queue.on('finished', async () => { - if (queue.items.length !== 0) { - queue.isRunning = true; - await recognize(queue.items.shift()); - queue.isRunning = false; - queue.emit('finished'); - } -}); - -export default async function runOCR(client, data) { - queue.push({ client, data }); -} diff --git a/apps/server/src/events/trainAI.js b/apps/server/src/events/trainAI.js deleted file mode 100644 index a3904ad..0000000 --- a/apps/server/src/events/trainAI.js +++ /dev/null @@ -1,17 +0,0 @@ -export default async function trainAI(data) { - fetch('https://api.wit.ai/utterances', { - headers: { - authorization: `Bearer ${process.env.WIT_AI_TOKEN}` - }, - body: JSON.stringify([ - { - text: data.text, - intent: data.label, - entities: [], - traits: [] - } - ]), - method: 'POST' - }); - return; -} diff --git a/apps/server/src/index.js b/apps/server/src/index.js deleted file mode 100644 index 1e40d87..0000000 --- a/apps/server/src/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import { createServer } from 'node:net'; -import { deserialize } from 'bson'; -import { runAI, runOCR, trainAI } from './events/index.js'; -import Config from './config.json' assert { type: 'json' }; - -const server = createServer(async (client) => { - client.on('data', async (data) => { - const eventData = deserialize(data, { - allowObjectSmallerThanBufferSize: true - }); - - switch (eventData.op) { - case 1: { - runAI(client, eventData, Config); - break; - } - - case 3: { - trainAI(eventData); - break; - } - - case 5: { - await runOCR(client, eventData); - break; - } - } - }); -}); - -server.listen(process.env.PORT || 3000); diff --git a/apps/server/src/package-lock.json b/apps/server/src/package-lock.json deleted file mode 100644 index 4d27871..0000000 --- a/apps/server/src/package-lock.json +++ /dev/null @@ -1,228 +0,0 @@ -{ - "name": "server", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "server", - "version": "1.0.0", - "license": "GPL-3.0-or-later", - "dependencies": { - "bson": "^5.3.0", - "tesseract.js": "^4.1.0" - } - }, - "node_modules/bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" - }, - "node_modules/bson": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz", - "integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==", - "engines": { - "node": ">=14.20.1" - } - }, - "node_modules/idb-keyval": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", - "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" - }, - "node_modules/is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" - }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" - }, - "node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/opencollective-postinstall": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", - "bin": { - "opencollective-postinstall": "index.js" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/tesseract.js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-4.1.0.tgz", - "integrity": "sha512-sCfWJtei6Ykc7uLiWs5H4IKDAhwsMauEVDwUiSkEpzMo4kH+7n8QDBPPNRtGJoZ4NJBf1WSlcbU+Puf64GjOfw==", - "hasInstallScript": true, - "dependencies": { - "bmp-js": "^0.1.0", - "idb-keyval": "^6.2.0", - "is-electron": "^2.2.2", - "is-url": "^1.2.4", - "node-fetch": "^2.6.9", - "opencollective-postinstall": "^2.0.3", - "regenerator-runtime": "^0.13.3", - "tesseract.js-core": "^4.0.4", - "wasm-feature-detect": "^1.2.11", - "zlibjs": "^0.3.1" - } - }, - "node_modules/tesseract.js-core": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-4.0.4.tgz", - "integrity": "sha512-MJ+vtktjAaT0681uPl6TDUPhbRbpD/S9emko5rtorgHRZpQo7R3BG7h+3pVHgn1KjfNf1bvnx4B7KxEK8YKqpg==" - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/wasm-feature-detect": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.5.1.tgz", - "integrity": "sha512-GHr23qmuehNXHY4902/hJ6EV5sUANIJC3R/yMfQ7hWDg3nfhlcJfnIL96R2ohpIwa62araN6aN4bLzzzq5GXkg==" - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/zlibjs": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", - "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", - "engines": { - "node": "*" - } - } - }, - "dependencies": { - "bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" - }, - "bson": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz", - "integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==" - }, - "idb-keyval": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", - "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" - }, - "is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" - }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" - }, - "node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "opencollective-postinstall": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==" - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "tesseract.js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-4.1.0.tgz", - "integrity": "sha512-sCfWJtei6Ykc7uLiWs5H4IKDAhwsMauEVDwUiSkEpzMo4kH+7n8QDBPPNRtGJoZ4NJBf1WSlcbU+Puf64GjOfw==", - "requires": { - "bmp-js": "^0.1.0", - "idb-keyval": "^6.2.0", - "is-electron": "^2.2.2", - "is-url": "^1.2.4", - "node-fetch": "^2.6.9", - "opencollective-postinstall": "^2.0.3", - "regenerator-runtime": "^0.13.3", - "tesseract.js-core": "^4.0.4", - "wasm-feature-detect": "^1.2.11", - "zlibjs": "^0.3.1" - } - }, - "tesseract.js-core": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-4.0.4.tgz", - "integrity": "sha512-MJ+vtktjAaT0681uPl6TDUPhbRbpD/S9emko5rtorgHRZpQo7R3BG7h+3pVHgn1KjfNf1bvnx4B7KxEK8YKqpg==" - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "wasm-feature-detect": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.5.1.tgz", - "integrity": "sha512-GHr23qmuehNXHY4902/hJ6EV5sUANIJC3R/yMfQ7hWDg3nfhlcJfnIL96R2ohpIwa62araN6aN4bLzzzq5GXkg==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "zlibjs": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", - "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==" - } - } -} diff --git a/apps/server/src/package.json b/apps/server/src/package.json deleted file mode 100644 index 8468519..0000000 --- a/apps/server/src/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "server", - "version": "1.0.0", - "description": "", - "main": "index.js", - "type": "module", - "keywords": [], - "author": "Reis Can", - "license": "GPL-3.0-or-later", - "dependencies": { - "bson": "^5.3.0", - "tesseract.js": "^4.1.0" - } -} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..a26d7ad3ff228130a64797f2a2ac59d21b5125ec GIT binary patch literal 330476 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

e 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�)f=U~@^(sXLH!AoFEt65#!_ggghjM-Oxw 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 Z01{*nU|?u~nGBKvEfI&vfTafs0RVPP{+R#( literal 0 HcmV?d00001 diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 338dbf3..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: '3.8' - -services: - revanced-helper: - container_name: revanced-helper - image: ghcr.io/revanced/revanced-helper:latest - # Required because otherwise failing with ECONNREFUSED - network_mode: "host" - environment: - - WIT_AI_TOKEN=YOUR_WIT_AI_TOKEN - - MONGODB_URI=YOUR_MONGODB_URI - - DISCORD_TOKEN=YOUR_DISCORD_TOKEN - volumes: - - /data/revanced-helper/discord-bot-config.json:/usr/src/revanced-helper/apps/bot-discord/src/config.json - - /data/revanced-helper/server-config.json:/usr/src/revanced-helper/apps/server/src/config.json diff --git a/lefthook.yml b/lefthook.yml new file mode 100755 index 0000000..28cc971 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,24 @@ +pre-commit: + parallel: true + commands: + types: + files: git diff --name-only @{push} + glob: '*.{js,ts}' + run: npx tsc --noEmit + types_example: + files: git diff --name-only @{push} + glob: '*.{js,ts}' + run: npx tsc --project example/tsconfig.json --noEmit + format: + files: git diff --name-only @{push} + glob: '*.{js,ts}' + run: npx prettier --check {files} + lint: + files: git diff --name-only @{push} + glob: '*.{js,ts}' + run: npx eslint --cache {files} +commit-msg: + parallel: false + commands: + commitlint: + run: npx commitlint --edit diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e2ee3db..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1939 +0,0 @@ -{ - "name": "revanced-helper", - "version": "0.0.1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "revanced-helper", - "version": "0.0.1", - "license": "GPL-3.0-or-later", - "devDependencies": { - "eslint": "^8.27.0", - "prettier": "2.7.1" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.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/@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", - "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/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "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/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/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "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/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "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==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "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/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==", - "dev": true, - "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==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "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/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "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/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/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==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.27.0.tgz", - "integrity": "sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "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.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "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.15.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "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.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "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-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "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==", - "dev": true, - "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==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true - }, - "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/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "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/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/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "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/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/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "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/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "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==", - "dev": true - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-sdsl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==", - "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/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/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/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.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/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "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==", - "dev": true - }, - "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/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "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/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/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==", - "dev": true, - "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/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": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "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/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/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/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-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==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/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/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/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/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true - }, - "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" - } - } - }, - "dependencies": { - "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.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" - } - }, - "@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@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 - }, - "@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 - }, - "@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, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@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 - }, - "@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, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true - }, - "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, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "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 - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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 - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "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, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "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 - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "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==", - "dev": true - }, - "eslint": { - "version": "8.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.27.0.tgz", - "integrity": "sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "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.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "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.15.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "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.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - } - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "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, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "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==", - "dev": true - }, - "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 - }, - "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 - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "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, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "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, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "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 - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "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" - } - }, - "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, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "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, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "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 - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "js-sdsl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==", - "dev": true - }, - "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, - "requires": { - "argparse": "^2.0.1" - } - }, - "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 - }, - "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 - }, - "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, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "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, - "requires": { - "p-locate": "^5.0.0" - } - }, - "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 - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "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, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "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, - "requires": { - "p-limit": "^3.0.2" - } - }, - "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, - "requires": { - "callsites": "^3.0.0" - } - }, - "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 - }, - "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==", - "dev": true - }, - "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 - }, - "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 - }, - "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "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 - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "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 - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "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, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "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, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "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 - }, - "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, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "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==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "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 - }, - "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, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "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 - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "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 - } - } -} diff --git a/package.json b/package.json old mode 100644 new mode 100755 index 4b40891..574b8fe --- a/package.json +++ b/package.json @@ -1,23 +1,61 @@ { "name": "revanced-helper", - "version": "0.0.1", - "description": "The community bots (and the server) to help the community and make moderators job easy.", + "version": "0.0.0", + "description": "🤖 Bots assisting ReVanced on multiple platforms", + "private": true, + "workspaces": [ + "apis/*", + "bots/*", + "packages/*" + ], + "scripts": { + "build": "bun task build", + "commitlint": "commitlint --edit", + "format": "turbo run format", + "format:check": "turbo run format:check", + "lint": "turbo run lint", + "lint:apply": "turbo run lint:apply", + "watch": "bun task watch", + "task": "turbo run", + "t": "bun task", + "prepare": "lefthook install" + }, "repository": { "type": "git", - "url": "git+https://github.com/reisxd/revanced-helper.git" + "url": "git+https://github.com/revanced/revanced-helper.git" }, - "author": "Reis Can", + "keywords": [ + "revanced", + "bot" + ], + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], "license": "GPL-3.0-or-later", "bugs": { - "url": "https://github.com/reisxd/revanced-helper/issues" - }, - "homepage": "https://github.com/reisxd/revanced-helper#readme", - "scripts": { - "lint": "prettier --write . && eslint --fix .", - "start": "cd apps/server/src && (node . &) && cd ../../bot-discord/src && (node .)" + "url": "https://github.com/revanced/revanced-helper/issues" }, + "homepage": "https://github.com/revanced/revanced-helper#readme", "devDependencies": { - "eslint": "^8.27.0", - "prettier": "2.7.1" + "@commitlint/cli": "^18.4.3", + "@commitlint/config-conventional": "^18.4.3", + "@tsconfig/strictest": "^2.0.2", + "conventional-changelog-conventionalcommits": "^7.0.2", + "eslint": "^8.54.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "lefthook": "^1.5.3", + "prettier": "^3.1.0", + "semantic-release": "^22.0.8", + "turbo": "^1.10.16", + "typescript": "^5.3.2" + }, + "overrides": { + "uuid": ">=9.0.0", + "isomorphic-fetch": ">=3.0.0" } -} \ No newline at end of file +} diff --git a/packages/client/index.js b/packages/client/index.js deleted file mode 100644 index 4488d7b..0000000 --- a/packages/client/index.js +++ /dev/null @@ -1,102 +0,0 @@ -import { Socket } from 'node:net'; -import { serialize, deserialize } from 'bson'; -import EventEmitter from 'node:events'; - -class HelperClient extends EventEmitter { - constructor({ server }) { - super(); - if (!server?.port) throw new Error('You did not specify the server port.'); - this.server = server; - this.client = new Socket(); - } - - connect() { - this.client.connect( - this.server.port, - this.server.host ? this.server.host : 'localhost' - ); - - this.client.on('data', (data) => { - const eventData = deserialize(data, { - allowObjectSmallerThanBufferSize: true - }); - - switch (eventData.op) { - case 2: { - // The 'aiResponse' event. - - this.emit('aiResponse', eventData); - break; - } - - case 6: { - // The 'ocrResponse' event. - - this.emit('ocrResponse', eventData); - break; - } - } - }); - - this.client.on('connect', () => { - if (this.reconnectionInterval) { - clearInterval(this.reconnectionInterval); - this.reconnectionInterval = null; - } - - this.emit('connect'); - }); - - this.client.on('close', () => { - this.reconnectionInterval = setInterval(() => { - this.client.connect( - this.server.port, - this.server.host ? this.server.host : 'localhost' - ); - }, 5000); - }) - } - - sendData(data) { - this.client.write(serialize(data)); - return; - } - - scanText(text, id) { - this.sendData({ - op: 1, - id, - text - }); - - return; - } - - scanImage(url, id) { - this.sendData({ - op: 5, - id, - url - }); - - return; - } - - sendTrainData(text, label) { - this.sendData({ - op: 3, - label, - text - }); - - return; - } - - trainAI() { - this.sendData({ op: 4 }); - - return; - } -} - -export default HelperClient; diff --git a/packages/client/package-lock.json b/packages/client/package-lock.json deleted file mode 100644 index 012ca1b..0000000 --- a/packages/client/package-lock.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "name": "helper-client", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "helper-client", - "version": "1.0.0", - "license": "GPL-3.0-or-later", - "dependencies": { - "bson": "^4.7.0" - } - }, - "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/bson": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.0.tgz", - "integrity": "sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA==", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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.1.13" - } - }, - "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" - } - ] - } - }, - "dependencies": { - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bson": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.0.tgz", - "integrity": "sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA==", - "requires": { - "buffer": "^5.6.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - } - } -} diff --git a/packages/client/package.json b/packages/client/package.json deleted file mode 100644 index 2824f83..0000000 --- a/packages/client/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@revanced-helper/helper-client", - "version": "1.0.0", - "description": "The ReVanced Helper client.", - "main": "index.js", - "type": "module", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Reis Can", - "license": "GPL-3.0-or-later", - "dependencies": { - "bson": "^4.7.0" - } -} \ No newline at end of file diff --git a/tsconfig.apis.json b/tsconfig.apis.json new file mode 100755 index 0000000..80c29da --- /dev/null +++ b/tsconfig.apis.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.base.json", +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100755 index 0000000..f11e436 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,17 @@ +{ + // `bun-types` will not be added until https://github.com/oven-sh/bun/issues/7247 is fixed + "extends": ["@tsconfig/strictest", /* "bun-types" */], + "compilerOptions": { + "lib": ["ESNext"], + "module": "NodeNext", + "moduleResolution": "Bundler", + "target": "ESNext", + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "declaration": false, + "allowSyntheticDefaultImports": true, + "isolatedModules": true, + }, +} diff --git a/tsconfig.packages.json b/tsconfig.packages.json new file mode 100755 index 0000000..831dee6 --- /dev/null +++ b/tsconfig.packages.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + }, + "references": [ + { + "path": "./packages/shared" + }, + { + "path": "./packages/api" + }, + ] +} diff --git a/turbo.json b/turbo.json new file mode 100755 index 0000000..00f8c5a --- /dev/null +++ b/turbo.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"], + "outputMode": "errors-only" + }, + "watch": { + "dependsOn": ["^watch"], + "outputMode": "errors-only" + }, + "format": { + "dependsOn": ["^format"], + "outputMode": "errors-only" + }, + "lint": { + "dependsOn": ["^lint"], + "outputMode": "errors-only" + }, + "lint:apply": { + "dependsOn": ["^lint:apply"], + "outputMode": "errors-only" + } + } +} \ No newline at end of file From abf532704fc8721e29c102fc7c227994dcb55647 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 24 Nov 2023 23:03:24 +0700 Subject: [PATCH 002/312] feat(packages): add `shared` package --- packages/shared/LICENSE | 674 ++++++++++++++++++ packages/shared/package.json | 42 ++ .../shared/src/constants/DisconnectReason.ts | 27 + .../constants/HumanizedDisconnectReason.ts | 11 + packages/shared/src/constants/Operation.ts | 57 ++ packages/shared/src/constants/index.ts | 3 + packages/shared/src/index.ts | 3 + packages/shared/src/schemas/Packet.ts | 127 ++++ packages/shared/src/schemas/index.ts | 1 + packages/shared/src/utils/guard.ts | 30 + packages/shared/src/utils/index.ts | 3 + packages/shared/src/utils/serialization.ts | 23 + packages/shared/src/utils/string.ts | 3 + packages/shared/tsconfig.json | 11 + 14 files changed, 1015 insertions(+) create mode 100755 packages/shared/LICENSE create mode 100755 packages/shared/package.json create mode 100755 packages/shared/src/constants/DisconnectReason.ts create mode 100755 packages/shared/src/constants/HumanizedDisconnectReason.ts create mode 100755 packages/shared/src/constants/Operation.ts create mode 100755 packages/shared/src/constants/index.ts create mode 100755 packages/shared/src/index.ts create mode 100755 packages/shared/src/schemas/Packet.ts create mode 100755 packages/shared/src/schemas/index.ts create mode 100755 packages/shared/src/utils/guard.ts create mode 100755 packages/shared/src/utils/index.ts create mode 100755 packages/shared/src/utils/serialization.ts create mode 100755 packages/shared/src/utils/string.ts create mode 100755 packages/shared/tsconfig.json diff --git a/packages/shared/LICENSE b/packages/shared/LICENSE new file mode 100755 index 0000000..f288702 --- /dev/null +++ b/packages/shared/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100755 index 0000000..71b6fbe --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,42 @@ +{ + "name": "@revanced/bot-shared", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Shared components for bots assisting ReVanced", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/shared" + }, + "keywords": [ + "revanced", + "bot", + "server" + ], + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "bson": "^6.2.0", + "valibot": "^0.21.0", + "zod": "^3.22.4" + } +} diff --git a/packages/shared/src/constants/DisconnectReason.ts b/packages/shared/src/constants/DisconnectReason.ts new file mode 100755 index 0000000..cd017f8 --- /dev/null +++ b/packages/shared/src/constants/DisconnectReason.ts @@ -0,0 +1,27 @@ +/** + * Disconnect reasons for clients + */ +enum DisconnectReason { + /** + * Unknown reason + */ + Generic = 1, + /** + * The client did not respond in time + */ + TimedOut, + /** + * The client sent an invalid packet (unserializable or invalid JSON) + */ + InvalidPacket, + /** + * The server has encountered an internal error + */ + ServerError, + /** + * The client had never connected to the server (**CLIENT-ONLY**) + */ + NeverConnected +} + +export default DisconnectReason \ No newline at end of file diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts new file mode 100755 index 0000000..95f510a --- /dev/null +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -0,0 +1,11 @@ +import DisconnectReason from './DisconnectReason.js' + +const HumanizedDisconnectReason = { + [DisconnectReason.InvalidPacket]: 'has sent invalid packet', + [DisconnectReason.Generic]: 'has been disconnected for unknown reasons', + [DisconnectReason.TimedOut]: 'has timed out', + [DisconnectReason.ServerError]: 'has been disconnected due to an internal server error', + [DisconnectReason.NeverConnected]: 'had never connected to the server' +} as const satisfies Record + +export default HumanizedDisconnectReason \ No newline at end of file diff --git a/packages/shared/src/constants/Operation.ts b/packages/shared/src/constants/Operation.ts new file mode 100755 index 0000000..5a0a7cf --- /dev/null +++ b/packages/shared/src/constants/Operation.ts @@ -0,0 +1,57 @@ +/** + * Client operation codes for the gateway + */ +export enum ClientOperation { + /** + * Client's heartbeat (to check if the connection is dead or not) + */ + Heartbeat = 100, + + /** + * Client's request to parse text + */ + ParseText = 110, + /** + * Client's request to parse image + */ + ParseImage, +} + +/** + * Server operation codes for the gateway + */ +export enum ServerOperation { + /** + * Server's acknowledgement of a client's heartbeat + */ + HeartbeatAck = 1, + /** + * Server's initial response to a client's connection + */ + Hello, + + /** + * Server's response to client's request to parse text + */ + ParsedText = 10, + /** + * Server's response to client's request to parse image + */ + ParsedImage, + /** + * Server's failure response to client's request to parse text + */ + ParseTextFailed, + /** + * Server's failure response to client's request to parse image + */ + ParseImageFailed, + + /** + * Server's disconnect message + */ + Disconnect = 20 +} + +export const Operation = { ...ClientOperation, ...ServerOperation } as const +export type Operation = (ClientOperation | ServerOperation) \ No newline at end of file diff --git a/packages/shared/src/constants/index.ts b/packages/shared/src/constants/index.ts new file mode 100755 index 0000000..aa16eda --- /dev/null +++ b/packages/shared/src/constants/index.ts @@ -0,0 +1,3 @@ +export { default as DisconnectReason } from './DisconnectReason.js' +export { default as HumanizedDisconnectReason } from './HumanizedDisconnectReason.js' +export * from './Operation.js' \ No newline at end of file diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100755 index 0000000..4cc2cfc --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,3 @@ +export * from './constants/index.js' +export * from './schemas/index.js' +export * from './utils/index.js' \ No newline at end of file diff --git a/packages/shared/src/schemas/Packet.ts b/packages/shared/src/schemas/Packet.ts new file mode 100755 index 0000000..07267a3 --- /dev/null +++ b/packages/shared/src/schemas/Packet.ts @@ -0,0 +1,127 @@ +import DisconnectReason from '../constants/DisconnectReason.js' +import { + ClientOperation, + Operation, + ServerOperation, +} from '../constants/Operation.js' +import { + object, + enum_, + special, + ObjectSchema, + number, + string, + Output, + AnySchema, + null_, + NullSchema, + array, + url, + parse, + // merge +} from 'valibot' + +/** + * Schema to validate packets + */ +export const PacketSchema = special(input => { + if ( + typeof input === 'object' && + input && + 'op' in input && + typeof input.op === 'number' && + input.op in Operation && + 'd' in input && + typeof input.d === 'object' + ) { + try { + parse(PacketDataSchemas[input.op as Operation], input.d) + return true + } catch { + return false + } + } + return false +}, 'Invalid packet data') + +// merge([ +// object({ +// op: nativeEnum(Operation, 'Not a valid operation number'), +// }), +// object({ +// d: special(input => { +// if ( +// typeof input === 'object' && +// input && +// 'op' in input && +// typeof input.op === 'number' && +// input.op in Operation && +// 'd' in input && +// typeof input.d === 'object' +// ) { +// try { +// PacketDataSchemas[input.op as Operation].parse(input) +// return true +// } catch { +// return false +// } +// } +// return false +// }, 'Invalid packet data'), +// }), +// ]) + +/** + * Schema to validate packet data for each possible operations + */ +export const PacketDataSchemas = { + [ServerOperation.Hello]: object({ + heartbeatInterval: number(), + }), + [ServerOperation.HeartbeatAck]: object({ + nextHeartbeat: number(), + }), + [ServerOperation.ParsedText]: object({ + id: string(), + labels: array( + object({ + name: string(), + confidence: special( + input => + typeof input === 'number' && input >= 0 && input <= 1 + ), + }) + ), + }), + [ServerOperation.ParsedImage]: object({ + id: string(), + text: string(), + }), + [ServerOperation.ParseTextFailed]: object({ + id: string(), + }), + [ServerOperation.ParseImageFailed]: object({ + id: string(), + }), + [ServerOperation.Disconnect]: object({ + reason: enum_(DisconnectReason), + }), + + [ClientOperation.Heartbeat]: null_(), + [ClientOperation.ParseText]: object({ + id: string(), + text: string(), + }), + [ClientOperation.ParseImage]: object({ + id: string(), + image_url: string([url()]), + }), +} as const satisfies Record< + Operation, + ObjectSchema | AnySchema | NullSchema +> + +export type Packet = { + op: TOp + d: Output<(typeof PacketDataSchemas)[TOp]> +} diff --git a/packages/shared/src/schemas/index.ts b/packages/shared/src/schemas/index.ts new file mode 100755 index 0000000..bbcb9e0 --- /dev/null +++ b/packages/shared/src/schemas/index.ts @@ -0,0 +1 @@ +export * from './Packet.js' \ No newline at end of file diff --git a/packages/shared/src/utils/guard.ts b/packages/shared/src/utils/guard.ts new file mode 100755 index 0000000..b8fd1ad --- /dev/null +++ b/packages/shared/src/utils/guard.ts @@ -0,0 +1,30 @@ +import { Packet } from '../schemas/Packet.js' +import { ClientOperation, Operation, ServerOperation } from '../constants/Operation.js' + +/** + * Checks whether a packet is trying to do the given operation + * @param op Operation code to check + * @param packet A packet + * @returns Whether this packet is trying to do the operation given + */ +export function packetMatchesOperation(op: TOp, packet: Packet): packet is Packet { + return packet.op === op +} + +/** + * Checks whether this packet is a client packet **(this does NOT validate the data)** + * @param packet A packet + * @returns Whether this packet is a client packet + */ +export function isClientPacket(packet: Packet): packet is Packet { + return packet.op in ClientOperation +} + +/** + * Checks whether this packet is a server packet **(this does NOT validate the data)** + * @param packet A packet + * @returns Whether this packet is a server packet + */ +export function isServerPacket(packet: Packet): packet is Packet { + return packet.op in ServerOperation +} \ No newline at end of file diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts new file mode 100755 index 0000000..61fcb8d --- /dev/null +++ b/packages/shared/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './guard.js' +export * from './serialization.js' +export * from './string.js' diff --git a/packages/shared/src/utils/serialization.ts b/packages/shared/src/utils/serialization.ts new file mode 100755 index 0000000..807ddc5 --- /dev/null +++ b/packages/shared/src/utils/serialization.ts @@ -0,0 +1,23 @@ +import * as BSON from 'bson' +import { Packet, PacketSchema } from '../schemas/index.js' +import { Operation } from '../constants/index.js' +import { parse } from 'valibot' + +/** + * Compresses a packet into a buffer + * @param packet The packet to compress + * @returns A buffer of the compressed packet + */ +export function serializePacket(packet: Packet) { + return BSON.serialize(packet) +} + +/** + * Decompresses a buffer into a packet + * @param buffer The buffer to decompress + * @returns A packet + */ +export function deserializePacket(buffer: Buffer) { + const data = BSON.deserialize(buffer) + return parse(PacketSchema, data) as Packet +} \ No newline at end of file diff --git a/packages/shared/src/utils/string.ts b/packages/shared/src/utils/string.ts new file mode 100755 index 0000000..1c63acf --- /dev/null +++ b/packages/shared/src/utils/string.ts @@ -0,0 +1,3 @@ +export function uncapitalize(str: T): Uncapitalize { + return str.charAt(0).toLowerCase() + str.slice(1) as Uncapitalize +} \ No newline at end of file diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100755 index 0000000..9342b09 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "./src", + "outDir": "dist", + "module": "ESNext", + "composite": true, + }, + "exclude": ["node_modules", "dist"] +} \ No newline at end of file From ab44312e7b81aeae3cb6ef9a77abfb838f46b9d8 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 24 Nov 2023 23:03:56 +0700 Subject: [PATCH 003/312] chore(packages/client)!: rewrite and rename package --- packages/api/LICENSE | 674 ++++++++++++++++++++++ packages/api/package.json | 45 ++ packages/api/src/classes/Client.ts | 134 +++++ packages/api/src/classes/ClientGateway.ts | 198 +++++++ packages/api/src/classes/index.ts | 4 + packages/api/src/index.ts | 1 + packages/api/tsconfig.json | 11 + packages/api/utility-types.d.ts | 1 + 8 files changed, 1068 insertions(+) create mode 100755 packages/api/LICENSE create mode 100755 packages/api/package.json create mode 100755 packages/api/src/classes/Client.ts create mode 100755 packages/api/src/classes/ClientGateway.ts create mode 100755 packages/api/src/classes/index.ts create mode 100755 packages/api/src/index.ts create mode 100755 packages/api/tsconfig.json create mode 100755 packages/api/utility-types.d.ts diff --git a/packages/api/LICENSE b/packages/api/LICENSE new file mode 100755 index 0000000..f288702 --- /dev/null +++ b/packages/api/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/packages/api/package.json b/packages/api/package.json new file mode 100755 index 0000000..c6abc5b --- /dev/null +++ b/packages/api/package.json @@ -0,0 +1,45 @@ +{ + "name": "@revanced/bot-api", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its server", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/api" + }, + "keywords": [ + "revanced", + "bot", + "server" + ], + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@revanced/bot-shared": "workspace:*", + "ws": "^8.14.2" + }, + "devDependencies": { + "@types/ws": "^8.5.10", + "typed-emitter": "^2.1.0" + } +} diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts new file mode 100755 index 0000000..9daab24 --- /dev/null +++ b/packages/api/src/classes/Client.ts @@ -0,0 +1,134 @@ +import { ClientOperation, Packet, ServerOperation } from '@revanced/bot-shared' +import ClientGateway, { ClientGatewayEventHandlers } from './ClientGateway.js' + +/** + * The client that connects to the API. + */ +export default class Client { + ready: boolean = false + gateway: ClientGateway + #parseId: number = 0 + + constructor(options: ClientOptions) { + this.gateway = new ClientGateway({ + url: options.api.gatewayUrl, + }) + + this.gateway.on('ready', () => { + this.ready = true + }) + } + + connect() { + return this.gateway.connect() + } + + isReady(): this is ReadiedClient { + return this.ready + } + + async parseText(text: string) { + this.#throwIfNotReady() + + const currentId = (this.#parseId++).toString() + + this.gateway.send({ + op: ClientOperation.ParseText, + d: { + text, + id: currentId, + }, + }) + + type CorrectPacket = Packet + + const promise = new Promise((rs, rj) => { + const parsedTextListener = (packet: CorrectPacket) => { + if (packet.d.id !== currentId) return + this.gateway.off('parsedText', parsedTextListener) + rs(packet) + } + + const parseTextFailedListener = (packet: Packet) => { + if (packet.d.id !== currentId) return + this.gateway.off('parseTextFailed', parseTextFailedListener) + rj(packet) + } + + this.gateway.on('parsedText', parsedTextListener) + this.gateway.on('parseTextFailed', parseTextFailedListener) + }) + + return await promise + } + + async parseImage(url: string) { + this.#throwIfNotReady() + + const currentId = (this.#parseId++).toString() + + this.gateway.send({ + op: ClientOperation.ParseImage, + d: { + image_url: url, + id: currentId, + }, + }) + + type CorrectPacket = Packet + + const promise = new Promise((rs, rj) => { + const parsedImageListener = (packet: CorrectPacket) => { + if (packet.d.id !== currentId) return + this.gateway.off('parsedImage', parsedImageListener) + rs(packet) + } + + const parseImageFailedListener = (packet: Packet) => { + if (packet.d.id !== currentId) return + this.gateway.off('parseImageFailed', parseImageFailedListener) + rj(packet) + } + + this.gateway.on('parsedImage', parsedImageListener) + this.gateway.on('parseImageFailed', parseImageFailedListener) + }) + + return await promise + } + + on( + name: TOpName, + handler: ClientGatewayEventHandlers[typeof name] + ) { + this.gateway.on(name, handler) + } + + off( + name: TOpName, + handler: ClientGatewayEventHandlers[typeof name] + ) { + this.gateway.off(name, handler) + } + + once( + name: TOpName, + handler: ClientGatewayEventHandlers[typeof name] + ) { + this.gateway.once(name, handler) + } + + #throwIfNotReady() { + if (!this.isReady()) throw new Error('Client is not ready') + } +} + +export type ReadiedClient = Client & { ready: true } + +export interface ClientOptions { + api: ClientApiOptions +} + +export interface ClientApiOptions { + gatewayUrl: string +} diff --git a/packages/api/src/classes/ClientGateway.ts b/packages/api/src/classes/ClientGateway.ts new file mode 100755 index 0000000..8ff72e1 --- /dev/null +++ b/packages/api/src/classes/ClientGateway.ts @@ -0,0 +1,198 @@ +import { type RawData, WebSocket } from 'ws' +import type TypedEmitter from 'typed-emitter' +import { + ClientOperation, + DisconnectReason, + Packet, + ServerOperation, + deserializePacket, + isServerPacket, + serializePacket, + uncapitalize, +} from '@revanced/bot-shared' +import { EventEmitter } from 'events' + +/** + * The class that handles the WebSocket connection to the server. + * This is the only relevant class for the time being. But in the future, there may be more classes to handle different protocols of the API. + */ +export default class ClientGateway { + readonly url: string + ready: boolean = false + disconnected: boolean | DisconnectReason = DisconnectReason.NeverConnected + config: Readonly['d']> | null = null! + + #hbTimeout: NodeJS.Timeout = null! + #socket: WebSocket = null! + #emitter = + new EventEmitter() as TypedEmitter + + constructor(options: ClientGatewayOptions) { + this.url = options.url + } + + connect() { + return new Promise((rs, rj) => { + try { + this.#socket = new WebSocket(this.url) + + this.#socket.on('open', () => { + this.disconnected = false + rs() + }) + + this.#socket.on('close', () => + this.#handleDisconnect(DisconnectReason.Generic) + ) + + this.#listen() + this.ready = true + this.#emitter.emit('ready') + } catch (e) { + rj(e) + } + }) + } + + on( + name: TOpName, + handler: ClientGatewayEventHandlers[typeof name] + ) { + this.#emitter.on(name, handler) + } + + off( + name: TOpName, + handler: ClientGatewayEventHandlers[typeof name] + ) { + this.#emitter.off(name, handler) + } + + once( + name: TOpName, + handler: ClientGatewayEventHandlers[typeof name] + ) { + this.#emitter.once(name, handler) + } + + send(packet: Packet) { + this.#throwIfDisconnected( + 'Cannot send a packet when already disconnected from the server' + ) + + return new Promise((resolve, reject) => + this.#socket.send(serializePacket(packet), err => + err ? reject(err) : resolve() + ) + ) + } + + disconnect() { + this.#throwIfDisconnected( + 'Cannot disconnect when already disconnected from the server' + ) + + this.#handleDisconnect(DisconnectReason.Generic) + } + + isReady(): this is ReadiedClientGateway { + return this.ready + } + + #listen() { + this.#socket.on('message', data => { + const packet = deserializePacket(this._toBuffer(data)) + // TODO: maybe log this? + // Just ignore the invalid packet, we don't have to disconnect + if (!isServerPacket(packet)) return + + this.#emitter.emit('packet', packet) + + switch (packet.op) { + case ServerOperation.Hello: + const data = Object.freeze( + (packet as Packet).d + ) + this.config = data + this.#emitter.emit('hello', data) + this.#startHeartbeating() + break + case ServerOperation.Disconnect: + return this.#handleDisconnect( + (packet as Packet).d.reason + ) + default: + return this.#emitter.emit( + uncapitalize( + ServerOperation[packet.op] as ClientGatewayServerEventName + ), + // @ts-expect-error + packet + ) + } + }) + } + + #throwIfDisconnected(errorMessage: string) { + if (this.disconnected !== false) throw new Error(errorMessage) + if (this.#socket.readyState !== this.#socket.OPEN) + throw new Error(errorMessage) + } + + #handleDisconnect(reason: DisconnectReason) { + clearTimeout(this.#hbTimeout) + this.disconnected = reason + this.#socket.close() + + this.#emitter.emit('disconnect', reason) + } + + #startHeartbeating() { + this.on('heartbeatAck', packet => { + this.#hbTimeout = setTimeout(() => { + this.send({ + op: ClientOperation.Heartbeat, + d: null, + }) + }, packet.d.nextHeartbeat - Date.now()) + }) + + // Immediately send a heartbeat so we can get when to send the next one + this.send({ + op: ClientOperation.Heartbeat, + d: null, + }) + } + + protected _toBuffer(data: RawData) { + if (data instanceof Buffer) return data + else if (data instanceof ArrayBuffer) return Buffer.from(data) + else return Buffer.concat(data) + } +} + +export interface ClientGatewayOptions { + /** + * The gateway URL to connect to + */ + url: string +} + +export type ClientGatewayServerEventName = keyof typeof ServerOperation + +export type ClientGatewayEventHandlers = { + [K in Uncapitalize]: ( + packet: Packet<(typeof ServerOperation)[Capitalize]> + ) => Promise | void +} & { + hello: ( + config: NonNullable + ) => Promise | void + ready: () => Promise | void + packet: (packet: Packet) => Promise | void + disconnect: (reason: DisconnectReason) => Promise | void +} + +export type ReadiedClientGateway = RequiredProperty< + InstanceType +> diff --git a/packages/api/src/classes/index.ts b/packages/api/src/classes/index.ts new file mode 100755 index 0000000..2ad36c8 --- /dev/null +++ b/packages/api/src/classes/index.ts @@ -0,0 +1,4 @@ +export { default as Client } from './Client.js' +export * from './Client.js' +export { default as ClientGateway } from './ClientGateway.js' +export * from './ClientGateway.js' \ No newline at end of file diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts new file mode 100755 index 0000000..d5e08ef --- /dev/null +++ b/packages/api/src/index.ts @@ -0,0 +1 @@ +export * from './classes/index.js' diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json new file mode 100755 index 0000000..9342b09 --- /dev/null +++ b/packages/api/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "./src", + "outDir": "dist", + "module": "ESNext", + "composite": true, + }, + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/packages/api/utility-types.d.ts b/packages/api/utility-types.d.ts new file mode 100755 index 0000000..7fc44c5 --- /dev/null +++ b/packages/api/utility-types.d.ts @@ -0,0 +1 @@ +type RequiredProperty = { [P in keyof T]: Required>; }; \ No newline at end of file From c97ba54802a225fba52a4878b594bad278c9e7f0 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 24 Nov 2023 23:06:57 +0700 Subject: [PATCH 004/312] chore(apps/server)!: rewrite rename and move to appropriate dir --- apis/websocket/.env.example | 5 + apis/websocket/LICENSE | 674 ++++++++++++++++++ apis/websocket/README.md | 57 ++ apis/websocket/config.json | 9 + apis/websocket/config.schema.json | 31 + .../docs/0_development_environment.md | 39 + apis/websocket/docs/1_configuration.md | 39 + apis/websocket/docs/2_running.md | 42 ++ apis/websocket/docs/3_packets.md | 33 + apis/websocket/docs/README.md | 16 + apis/websocket/package.json | 49 ++ apis/websocket/src/classes/Client.ts | 218 ++++++ apis/websocket/src/events/index.ts | 17 + apis/websocket/src/events/parseImage.ts | 63 ++ apis/websocket/src/events/parseText.ts | 42 ++ apis/websocket/src/index.ts | 150 ++++ apis/websocket/src/types.d.ts | 9 + apis/websocket/src/utils/checkEnv.ts | 28 + apis/websocket/src/utils/getConfig.ts | 37 + apis/websocket/src/utils/index.ts | 3 + apis/websocket/src/utils/logger.ts | 16 + apis/websocket/tsconfig.json | 11 + 22 files changed, 1588 insertions(+) create mode 100755 apis/websocket/.env.example create mode 100755 apis/websocket/LICENSE create mode 100755 apis/websocket/README.md create mode 100755 apis/websocket/config.json create mode 100755 apis/websocket/config.schema.json create mode 100644 apis/websocket/docs/0_development_environment.md create mode 100644 apis/websocket/docs/1_configuration.md create mode 100644 apis/websocket/docs/2_running.md create mode 100644 apis/websocket/docs/3_packets.md create mode 100755 apis/websocket/docs/README.md create mode 100755 apis/websocket/package.json create mode 100755 apis/websocket/src/classes/Client.ts create mode 100755 apis/websocket/src/events/index.ts create mode 100755 apis/websocket/src/events/parseImage.ts create mode 100755 apis/websocket/src/events/parseText.ts create mode 100755 apis/websocket/src/index.ts create mode 100755 apis/websocket/src/types.d.ts create mode 100755 apis/websocket/src/utils/checkEnv.ts create mode 100755 apis/websocket/src/utils/getConfig.ts create mode 100755 apis/websocket/src/utils/index.ts create mode 100755 apis/websocket/src/utils/logger.ts create mode 100755 apis/websocket/tsconfig.json diff --git a/apis/websocket/.env.example b/apis/websocket/.env.example new file mode 100755 index 0000000..9e40ca4 --- /dev/null +++ b/apis/websocket/.env.example @@ -0,0 +1,5 @@ +# Safety measures, do not remove +IS_USING_DOT_ENV=1 + +# Your Wit.ai token +WIT_AI_TOKEN="YOUR_TOKEN_HERE" diff --git a/apis/websocket/LICENSE b/apis/websocket/LICENSE new file mode 100755 index 0000000..f288702 --- /dev/null +++ b/apis/websocket/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/apis/websocket/README.md b/apis/websocket/README.md new file mode 100755 index 0000000..b13d97a --- /dev/null +++ b/apis/websocket/README.md @@ -0,0 +1,57 @@ +

+ + + + +
+ + +     + + + + + +     + + +     + + +     + + +     + + + + + +     + + + +
+
+ Continuing the legacy of Vanced +

+ +# 🚙 ReVanced Bot WebSocket API + +![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) + +The WebSocket API for ReVanced bots utilizing BSON for packet transmission with a heartbeating system to monitor the statuses of clients. + +## 📚 Documentation + +Documentation are provided [here](./docs/README.md). + +## 📄 License + +**ReVanced Bot WebSocket API** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. \ No newline at end of file diff --git a/apis/websocket/config.json b/apis/websocket/config.json new file mode 100755 index 0000000..06dcc28 --- /dev/null +++ b/apis/websocket/config.json @@ -0,0 +1,9 @@ +{ + "$schema": "./config.schema.json", + + "address": "127.0.0.1", + "port": 3000, + "ocrConcurrentQueues": 1, + "clientHeartbeatInterval": 5000, + "debugLogsInProduction": false +} diff --git a/apis/websocket/config.schema.json b/apis/websocket/config.schema.json new file mode 100755 index 0000000..adc77ed --- /dev/null +++ b/apis/websocket/config.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "address": { + "description": "Address to listen on", + "type": "string", + "default": "127.0.0.1" + }, + "port": { + "description": "Port to listen on", + "type": "integer", + "default": 80 + }, + "ocrConcurrentQueues": { + "description": "Number of concurrent queues for OCR", + "type": "integer", + "default": 1 + }, + "clientHeartbeatInterval": { + "description": "Time in milliseconds to wait for a client to send a heartbeat packet, if no packet is received, the server will wait for `clientHeartbeatExtraTime` milliseconds before disconnecting the client", + "type": "integer", + "default": 60000 + }, + "debugLogsInProduction": { + "description": "Whether to print debug logs in production", + "type": "boolean", + "default": false + } + } +} diff --git a/apis/websocket/docs/0_development_environment.md b/apis/websocket/docs/0_development_environment.md new file mode 100644 index 0000000..9e523f1 --- /dev/null +++ b/apis/websocket/docs/0_development_environment.md @@ -0,0 +1,39 @@ +# 🏗️ Setting up the development environment + +> [!IMPORTANT] +> **This project uses [Bun](https://bun.sh) to run and bundle the code.** +> Compatibility with other runtimes (Node.js, Deno, ...) are not guaranteed and most package scripts won't work. + +To start developing, you'll need to set up the development environment first. + +1. Install [Bun](https://bun.sh) + +2. Clone the mono-repository + + ```sh + git clone https://github.com/ReVanced/revanced-helper.git && + cd revanced-helper + ``` + +3. Install dependencies + + ```sh + bun install + ``` + +4. Build packages/libraries + + ```sh + bun build:deps + ``` + +5. Change your directory to this project's root + ```sh + cd apis/websocket + ``` + +## ⏭️ What's next + +The next page will tell you about server configurations. + +Continue: [⚙️ Configuration](./1_configuration.md) diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md new file mode 100644 index 0000000..dd134be --- /dev/null +++ b/apis/websocket/docs/1_configuration.md @@ -0,0 +1,39 @@ +# ⚙️ Configuration + +This is the default configuration: + +```json +{ + "address": "127.0.0.1", + "port": 3000, + "ocrConcurrentQueues": 1, + "clientHeartbeatInterval": 60000, + "debugLogsInProduction": false +} +``` + +--- + +### `config.address` & `config.port` + +The address and port for the server to listen on. + +### `config.ocrConcurrentQueues` + +Amount of concurrent queues that can be run at a time. + +> Setting this too high may cause performance issues. + +### `config.clientHeartbeatInterval` + +Heartbeat interval for clients. See [**💓 Heartbeating**](./packets.md#💓-heartbeating). + +### `config.debugLogsInProduction` + +Whether to print debug logs at all in production mode (when `NODE_ENV` is `production`). + +## ⏭️ What's next + +The next page will tell you how to run and bundle the server. + +Continue: [🏃🏻‍♂️ Running the server](./2_running.md) diff --git a/apis/websocket/docs/2_running.md b/apis/websocket/docs/2_running.md new file mode 100644 index 0000000..6734a27 --- /dev/null +++ b/apis/websocket/docs/2_running.md @@ -0,0 +1,42 @@ +# 🏃🏻‍♂️ Running the server + +There are many methods to run the server. Choose one that suits best for the situation. + +> [!IMPORTANT] +> Make sure you've followed the [**🏗️ Setting up the environment**](./0_development_environment.md) steps. + +## 👷🏻 Development mode (recommended) + +There will be no compilation step, and Bun will automatically watch changes and restart the server for you. + +You can quickly start the server by running: + +```sh +bun dev +``` + +## 🌐 Production mode + +Production mode runs no different from the development server, it simply has less debugging information printed to console by default. However, more production-specific features may come. + +To start the server in production mode, you'll have to: + +1. Set the `NODE_ENV` environment variable to `production` + + > It is very possible to set the value in the `.env` file and let Bun load it, **but it is recommended to set the variable before Bun even starts**. + +2. Start the server + ```sh + bun dev + ``` + +## 📦 Building + +If you're looking to build and host the server somewhere else, you can run: + +```sh +bun bundle +``` + +The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** + diff --git a/apis/websocket/docs/3_packets.md b/apis/websocket/docs/3_packets.md new file mode 100644 index 0000000..fe2ea75 --- /dev/null +++ b/apis/websocket/docs/3_packets.md @@ -0,0 +1,33 @@ +# 📨 Packets + +Packets are BSON messages sent to the server. They're structured like the following when decoded: + +```json +{ + "op": 12345, + "d": { + "some_field": "some data" + } +} +``` + +### `packet.op` + +Operation codes are numbers that communicate an action. + +### `packet.d` + +Data fields include additional information for the server to process. They are **either an object with specific fields or just `null`**. + +#### 📦 Schemas and constants + +Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well. + +[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../packages/shared/src/schemas/Packet.ts) +[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../packages/shared/src/constants/Operation.ts) + +## 💓 Heartbeating + +Heartbeating is a process where the client regularly send each other signals to confirm that they are still connected and functioning. If the server doesn't receive a heartbeat from the client within a specified timeframe, it assume the client has disconnected and closes the socket. + +You can configure the interval in the configuration file. See [**📝 Configuration > `config.clientHeartbeatInterval`**](./1_configuration.md#configclientheartbeatinterval). diff --git a/apis/websocket/docs/README.md b/apis/websocket/docs/README.md new file mode 100755 index 0000000..1ce65ca --- /dev/null +++ b/apis/websocket/docs/README.md @@ -0,0 +1,16 @@ +# 🚙 ReVanced Bot WebSocket API + +This documentation explains how the server works, how to start developing, and how to configure the server. + +# 📖 Table of contents + +0. [🏗️ Setting up the development environment](./0_development_environment.md) +1. [⚙️ Configuration](./1_configuration.md) +2. [🏃🏻‍♂️ Running the server](./2_running.md) +3. [📨 Packets](./3_packets.md) + +## ⏭️ Start here + +The next page will tell you how to set up the development environment. + +Continue: [🏗️ Setting up the development environment](./0_development_environment.md) diff --git a/apis/websocket/package.json b/apis/websocket/package.json new file mode 100755 index 0000000..c4eeb04 --- /dev/null +++ b/apis/websocket/package.json @@ -0,0 +1,49 @@ +{ + "name": "@revanced/bot-websocket-api", + "type": "module", + "private": true, + "version": "0.1.0", + "description": "⚙️ Gateway for bots assisting ReVanced", + "main": "dist/index.js", + "scripts": { + "bundle": "bun build src/index.ts --outdir=dist --target=node --minify --sourcemap=external", + "dev": "bun run src/index.ts --watch", + "build": "bun bundle", + "watch": "bun dev" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "apis/websocket" + }, + "keywords": [ + "revanced", + "bot", + "server", + "gateway" + ], + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@fastify/websocket": "^8.2.0", + "@revanced/bot-shared": "workspace:*", + "@sapphire/async-queue": "^1.5.0", + "fastify": "^4.24.3", + "node-wit": "^6.6.0", + "tesseract.js": "^5.0.3" + }, + "devDependencies": { + "@types/node-wit": "^6.0.3", + "@types/ws": "^8.5.10", + "chalk": "^5.3.0", + "typed-emitter": "^2.1.0" + } +} diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts new file mode 100755 index 0000000..5c96086 --- /dev/null +++ b/apis/websocket/src/classes/Client.ts @@ -0,0 +1,218 @@ +import { + ClientOperation, + DisconnectReason, + Packet, + ServerOperation, + deserializePacket, + isClientPacket, + serializePacket, + uncapitalize, +} from '@revanced/bot-shared' +import { EventEmitter } from 'node:events' + +import type TypedEmitter from 'typed-emitter' +import type { RawData, WebSocket } from 'ws' + +export default class Client { + id: string + disconnected: DisconnectReason | false = false + ready: boolean = false + + lastHeartbeat: number = null! + heartbeatInterval: number + + #hbTimeout: NodeJS.Timeout = null! + #emitter = new EventEmitter() as TypedEmitter + #socket: WebSocket + + constructor(options: ClientOptions) { + this.#socket = options.socket + this.heartbeatInterval = options.heartbeatInterval ?? 60000 + this.id = options.id + + this.#socket.on('error', () => this.forceDisconnect()) + this.#socket.on('close', () => this.forceDisconnect()) + this.#socket.on('unexpected-response', () => this.forceDisconnect()) + + this.send({ + op: ServerOperation.Hello, + d: { + heartbeatInterval: this.heartbeatInterval, + }, + }) + .then(() => { + this.#listen() + this.#listenHeartbeat() + this.ready = true + this.#emitter.emit('ready') + }) + .catch(() => { + if (this.disconnected === false) + this.disconnect(DisconnectReason.ServerError) + else this.forceDisconnect(DisconnectReason.ServerError) + }) + } + + on( + name: TOpName, + handler: ClientEventHandlers[typeof name] + ) { + this.#emitter.on(name, handler) + } + + once( + name: TOpName, + handler: ClientEventHandlers[typeof name] + ) { + this.#emitter.once(name, handler) + } + + off( + name: TOpName, + handler: ClientEventHandlers[typeof name] + ) { + this.#emitter.off(name, handler) + } + + send(packet: Packet) { + return new Promise((resolve, reject) => { + try { + this.#throwIfDisconnected( + 'Cannot send packet to client that has already disconnected' + ) + + this.#socket.send(serializePacket(packet), err => + err ? reject(err) : resolve() + ) + } catch (e) { + reject(e) + } + }) + } + + async disconnect(reason: DisconnectReason = DisconnectReason.Generic) { + this.#throwIfDisconnected( + 'Cannot disconnect client that has already disconnected' + ) + + try { + await this.send({ op: ServerOperation.Disconnect, d: { reason } }) + } catch (err) { + throw new Error( + `Cannot send disconnect reason to client ${this.id}: ${err}` + ) + } finally { + this.forceDisconnect(reason) + } + } + + forceDisconnect(reason: DisconnectReason = DisconnectReason.Generic) { + if (this.disconnected !== false) return + + if (this.#hbTimeout) clearTimeout(this.#hbTimeout) + this.#socket.terminate() + + this.ready = false + this.disconnected = reason + + this.#emitter.emit('disconnect', reason) + } + + #throwIfDisconnected(errorMessage: string) { + if (this.disconnected !== false) throw new Error(errorMessage) + + if (this.#socket.readyState !== this.#socket.OPEN) { + this.forceDisconnect(DisconnectReason.Generic) + throw new Error(errorMessage) + } + } + + #listen() { + this.#socket.on('message', data => { + try { + const rawPacket = deserializePacket(this._toBuffer(data)) + if (!isClientPacket(rawPacket)) throw null + + const packet: ClientPacketObject = { + ...rawPacket, + client: this, + } + + this.#emitter.emit('packet', packet) + this.#emitter.emit( + uncapitalize(ClientOperation[packet.op] as ClientEventName), + // @ts-expect-error + packet + ) + } catch (e) { + // TODO: add error fields to sent packet so we can log what went wrong + this.disconnect(DisconnectReason.InvalidPacket) + } + }) + } + + #listenHeartbeat() { + this.lastHeartbeat = Date.now() + this.#startHeartbeatTimeout() + + this.on('heartbeat', () => { + this.lastHeartbeat = Date.now() + this.#hbTimeout.refresh() + + this.send({ + op: ServerOperation.HeartbeatAck, + d: { + nextHeartbeat: this.lastHeartbeat + this.heartbeatInterval, + }, + }).catch(() => {}) + }) + } + + #startHeartbeatTimeout() { + this.#hbTimeout = setTimeout(() => { + if (Date.now() - this.lastHeartbeat > 0) { + // TODO: put into config + // 5000 is extra time to account for latency + const interval = setTimeout( + () => this.disconnect(DisconnectReason.TimedOut), + 5000 + ) + + this.once('heartbeat', () => clearTimeout(interval)) + // This should never happen but it did in my testing so I'm adding this just in case + this.once('disconnect', () => clearTimeout(interval)) + // Technically we don't have to do this, but JUST IN CASE! + } else this.#hbTimeout.refresh() + }, this.heartbeatInterval) + } + + protected _toBuffer(data: RawData) { + if (data instanceof Buffer) return data + else if (data instanceof ArrayBuffer) return Buffer.from(data) + else return Buffer.concat(data) + } +} + +export interface ClientOptions { + id: string + socket: WebSocket + heartbeatInterval?: number +} + +export type ClientPacketObject = Packet & { + client: Client +} + +export type ClientEventName = keyof typeof ClientOperation + +export type ClientEventHandlers = { + [K in Uncapitalize]: ( + packet: ClientPacketObject<(typeof ClientOperation)[Capitalize]> + ) => Promise | void +} & { + ready: () => Promise | void + packet: ( + packet: ClientPacketObject + ) => Promise | void + disconnect: (reason: DisconnectReason) => Promise | void +} diff --git a/apis/websocket/src/events/index.ts b/apis/websocket/src/events/index.ts new file mode 100755 index 0000000..9724ec5 --- /dev/null +++ b/apis/websocket/src/events/index.ts @@ -0,0 +1,17 @@ +import type { ClientOperation } from '@revanced/bot-shared' +import type { Wit } from 'node-wit' +import { ClientPacketObject } from '../classes/Client.js' +import type { Config } from '../utils/getConfig.js' +import type { Logger } from '../utils/logger.js' +import type { Worker as TesseractWorker } from 'tesseract.js' + +export { default as parseTextEventHandler } from './parseText.js' +export { default as parseImageEventHandler } from './parseImage.js' + +export type EventHandler = (packet: ClientPacketObject, context: EventContext) => void | Promise +export type EventContext = { + witClient: Wit + tesseractWorker: TesseractWorker + logger: Logger + config: Config +} \ No newline at end of file diff --git a/apis/websocket/src/events/parseImage.ts b/apis/websocket/src/events/parseImage.ts new file mode 100755 index 0000000..53f8451 --- /dev/null +++ b/apis/websocket/src/events/parseImage.ts @@ -0,0 +1,63 @@ +import { ClientOperation, ServerOperation } from '@revanced/bot-shared' +import { AsyncQueue } from '@sapphire/async-queue' + +import type { EventHandler } from './index.js' + +const queue = new AsyncQueue() + +const parseImageEventHandler: EventHandler = async ( + packet, + { tesseractWorker, logger, config } +) => { + const { + client, + d: { image_url: imageUrl, id }, + } = packet + + logger.debug( + `Client ${client.id} requested to parse image from URL:`, + imageUrl + ) + logger.debug( + `Queue currently has ${queue.remaining}/${config.ocrConcurrentQueues} items in it` + ) + + if (queue.remaining < config.ocrConcurrentQueues) queue.shift() + await queue.wait() + + try { + logger.debug(`Recognizing image from URL for client ${client.id}`) + + const { data, jobId } = await tesseractWorker.recognize(imageUrl) + + logger.debug( + `Recognized image from URL for client ${client.id} (job ${jobId}):`, + data.text + ) + await client.send({ + op: ServerOperation.ParsedImage, + d: { + id, + text: data.text, + }, + }) + } catch { + logger.error( + `Failed to parse image from URL for client ${client.id}:`, + imageUrl + ) + await client.send({ + op: ServerOperation.ParseImageFailed, + d: { + id, + }, + }) + } finally { + queue.shift() + logger.debug( + `Finished processing image from URL for client ${client.id}, queue has ${queue.remaining}/${config.ocrConcurrentQueues} remaining items in it` + ) + } +} + +export default parseImageEventHandler diff --git a/apis/websocket/src/events/parseText.ts b/apis/websocket/src/events/parseText.ts new file mode 100755 index 0000000..bfd7bb9 --- /dev/null +++ b/apis/websocket/src/events/parseText.ts @@ -0,0 +1,42 @@ +import { ClientOperation, ServerOperation } from '@revanced/bot-shared' + +import { inspect as inspectObject } from 'node:util' + +import type { EventHandler } from './index.js' + +const parseTextEventHandler: EventHandler = async ( + packet, + { witClient, logger } +) => { + const { + client, + d: { text, id }, + } = packet + + logger.debug(`Client ${client.id} requested to parse text:`, text) + + try { + const { intents } = await witClient.message(text, {}) + const intentsWithoutIds = intents.map(({ id, ...rest }) => rest) + + await client.send({ + op: ServerOperation.ParsedText, + d: { + id, + labels: intentsWithoutIds, + }, + }) + } catch (e) { + await client.send({ + op: ServerOperation.ParseTextFailed, + d: { + id, + }, + }) + + if (e instanceof Error) logger.error(e.stack ?? e.message) + else logger.error(inspectObject(e)) + } +} + +export default parseTextEventHandler diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts new file mode 100755 index 0000000..fab0d4e --- /dev/null +++ b/apis/websocket/src/index.ts @@ -0,0 +1,150 @@ +import { fastify } from 'fastify' +import fastifyWebsocket from '@fastify/websocket' + +import { createWorker as createTesseractWorker } from 'tesseract.js' +import witPkg from 'node-wit' +const { Wit } = witPkg + +import { inspect as inspectObject } from 'node:util' + +import Client from './classes/Client.js' + +import { + EventContext, + parseImageEventHandler, + parseTextEventHandler, +} from './events/index.js' + +import { getConfig, checkEnv, logger } from './utils/index.js' +import { WebSocket } from 'ws' +import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared' + +// Load environment variables and config + +(async () => { + +const environment = checkEnv(logger) +const config = getConfig() + +if (!config.debugLogsInProduction && environment === 'production') logger.debug = () => {} + +// Workers and API clients + +const tesseractWorker = await createTesseractWorker('eng') +const witClient = new Wit({ + accessToken: process.env['WIT_AI_TOKEN']!, +}) + +process.on('beforeExit', () => tesseractWorker.terminate()) + +// Server logic + +const clients = new Set() +const clientSocketMap = new WeakMap() +const eventContext: EventContext = { + tesseractWorker, + logger, + witClient, + config, +} + +const server = fastify() + .register(fastifyWebsocket, { + options: { + // 16 KiB max payload + // A Discord message can not be longer than 4000 characters + // OCR should not be longer than 16000 characters + maxPayload: 16 * 1024, + }, + }) + .register(async instance => { + instance.get('/', { websocket: true }, async (connection, request) => { + try { + const client = new Client({ + socket: connection.socket, + id: request.hostname, + heartbeatInterval: config.clientHeartbeatInterval, + }) + + clientSocketMap.set(connection.socket, client) + clients.add(client) + + logger.debug(`Client ${client.id}'s instance has been added`) + logger.info( + `New client connected (now ${clients.size} clients) with ID:`, + client.id + ) + + client.on('disconnect', reason => { + clients.delete(client) + logger.info( + `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}` + ) + }) + + client.on('parseText', async packet => + parseTextEventHandler(packet, eventContext) + ) + + client.on('parseImage', async packet => + parseImageEventHandler(packet, eventContext) + ) + + if (environment === 'development' && !config.debugLogsInProduction) { + logger.debug('Running development mode or debug logs in production is enabled, attaching debug events...') + client.on('packet', ({ client: _, ...rawPacket }) => + logger.debug( + `Packet received from client ${client.id}:`, + inspectObject(rawPacket) + ) + ) + + client.on('heartbeat', () => + logger.debug('Heartbeat received from client', client.id) + ) + } + } catch (e) { + if (e instanceof Error) logger.error(e.stack ?? e.message) + else logger.error(inspectObject(e)) + + const client = clientSocketMap.get(connection.socket) + + if (!client) { + logger.error( + 'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!' + ) + return connection.socket.terminate() + } + + if (client.disconnected === false) + client.disconnect(DisconnectReason.ServerError) + else client.forceDisconnect() + + clients.delete(client) + + logger.debug( + `Client ${client.id} disconnected because of an internal error` + ) + } + }) + }) + +// Start the server + +logger.debug('Starting with these configurations:', inspectObject(config)) + +await server.listen({ + host: config.address ?? '0.0.0.0', + port: config.port ?? 80, +}) + +const addressInfo = server.server.address() +if (!addressInfo || typeof addressInfo !== 'object') + logger.debug('Server started, but cannot determine address information') +else + logger.info( + 'Server started at:', + `${addressInfo.address}:${addressInfo.port}` + ) + +})() diff --git a/apis/websocket/src/types.d.ts b/apis/websocket/src/types.d.ts new file mode 100755 index 0000000..27a69ed --- /dev/null +++ b/apis/websocket/src/types.d.ts @@ -0,0 +1,9 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + WIT_AI_TOKEN?: string + } + } +} + +declare type NodeEnvironment = 'development' | 'production' diff --git a/apis/websocket/src/utils/checkEnv.ts b/apis/websocket/src/utils/checkEnv.ts new file mode 100755 index 0000000..6bbe68b --- /dev/null +++ b/apis/websocket/src/utils/checkEnv.ts @@ -0,0 +1,28 @@ +import type { Logger } from './logger.js' + +export default function checkEnv(logger: Logger) { + if (!process.env['NODE_ENV']) logger.warn('NODE_ENV not set, defaulting to `development`') + const environment = (process.env['NODE_ENV'] ?? + 'development') as NodeEnvironment + + if (!['development', 'production'].includes(environment)) { + logger.error( + 'NODE_ENV is neither `development` nor `production`, unable to determine environment' + ) + logger.info('Set NODE_ENV to blank to use `development` mode') + process.exit(1) + } + + logger.info(`Running in ${environment} mode...`) + + if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { + logger.warn('You seem to be using .env files, this is generally not a good idea in production...') + } + + if (!process.env['WIT_AI_TOKEN']) { + logger.error('WIT_AI_TOKEN is not defined in the environment variables') + process.exit(1) + } + + return environment +} diff --git a/apis/websocket/src/utils/getConfig.ts b/apis/websocket/src/utils/getConfig.ts new file mode 100755 index 0000000..2b39b31 --- /dev/null +++ b/apis/websocket/src/utils/getConfig.ts @@ -0,0 +1,37 @@ +import { existsSync } from 'node:fs' +import { resolve as resolvePath } from 'node:path' +import { pathToFileURL } from 'node:url' + +const configPath = resolvePath( + process.cwd(), + 'config.json' +) + +const userConfig: Partial = existsSync(configPath) + ? (await import(pathToFileURL(configPath).href, { + assert: { + type: 'json', + }, + })).default + : {} + +type BaseTypeOf = T extends (infer U)[] + ? U[] + : T extends (...args: any[]) => infer U + ? (...args: any[]) => U + : T extends object + ? { [K in keyof T]: T[K] } + : T + +export type Config = Omit, '$schema'> & {} +export const defaultConfig: Config = { + address: '127.0.0.1', + port: 80, + ocrConcurrentQueues: 1, + clientHeartbeatInterval: 60000, + debugLogsInProduction: false, +} + +export default function getConfig() { + return Object.assign(defaultConfig, userConfig) satisfies Config +} diff --git a/apis/websocket/src/utils/index.ts b/apis/websocket/src/utils/index.ts new file mode 100755 index 0000000..9fe7e87 --- /dev/null +++ b/apis/websocket/src/utils/index.ts @@ -0,0 +1,3 @@ +export { default as getConfig } from './getConfig.js' +export { default as checkEnv } from './checkEnv.js' +export { default as logger } from './logger.js' \ No newline at end of file diff --git a/apis/websocket/src/utils/logger.ts b/apis/websocket/src/utils/logger.ts new file mode 100755 index 0000000..2a71a75 --- /dev/null +++ b/apis/websocket/src/utils/logger.ts @@ -0,0 +1,16 @@ +import { Chalk } from 'chalk' + +const chalk = new Chalk() +const logger = { + debug: (...args) => console.debug(chalk.gray('DEBUG:', ...args)), + info: (...args) => console.info(chalk.bgBlue.whiteBright(' INFO '), ...args), + warn: (...args) => console.warn(chalk.bgYellow.blackBright.bold(' WARN '), chalk.yellowBright(...args)), + error: (...args) => console.error(chalk.bgRed.whiteBright.bold(' ERROR '), chalk.redBright(...args)), + log: console.log, +} satisfies Logger + +export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'log' +export type LogFunction = (...x: any[]) => void +export type Logger = Record + +export default logger \ No newline at end of file diff --git a/apis/websocket/tsconfig.json b/apis/websocket/tsconfig.json new file mode 100755 index 0000000..f69c640 --- /dev/null +++ b/apis/websocket/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.apis.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "module": "ESNext", + "composite": false, + }, + "exclude": ["node_modules", "dist"], + "include": ["./*.json", "src/**/*.ts"] +} \ No newline at end of file From 7223efbce1d879d1a63014da242507a1405fbeb0 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 24 Nov 2023 23:09:33 +0700 Subject: [PATCH 005/312] chore: remove turbo junk --- .turbo/cookies/0.cookie | 0 .turbo/cookies/1.cookie | 0 .turbo/cookies/10.cookie | 0 .turbo/cookies/11.cookie | 0 .turbo/cookies/12.cookie | 0 .turbo/cookies/13.cookie | 0 .turbo/cookies/14.cookie | 0 .turbo/cookies/15.cookie | 0 .turbo/cookies/16.cookie | 0 .turbo/cookies/17.cookie | 0 .turbo/cookies/2.cookie | 0 .turbo/cookies/3.cookie | 0 .turbo/cookies/4.cookie | 0 .turbo/cookies/5.cookie | 0 .turbo/cookies/6.cookie | 0 .turbo/cookies/7.cookie | 0 .turbo/cookies/8.cookie | 0 .turbo/cookies/9.cookie | 0 18 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .turbo/cookies/0.cookie delete mode 100644 .turbo/cookies/1.cookie delete mode 100644 .turbo/cookies/10.cookie delete mode 100644 .turbo/cookies/11.cookie delete mode 100644 .turbo/cookies/12.cookie delete mode 100644 .turbo/cookies/13.cookie delete mode 100644 .turbo/cookies/14.cookie delete mode 100644 .turbo/cookies/15.cookie delete mode 100644 .turbo/cookies/16.cookie delete mode 100644 .turbo/cookies/17.cookie delete mode 100644 .turbo/cookies/2.cookie delete mode 100644 .turbo/cookies/3.cookie delete mode 100644 .turbo/cookies/4.cookie delete mode 100644 .turbo/cookies/5.cookie delete mode 100644 .turbo/cookies/6.cookie delete mode 100644 .turbo/cookies/7.cookie delete mode 100644 .turbo/cookies/8.cookie delete mode 100644 .turbo/cookies/9.cookie diff --git a/.turbo/cookies/0.cookie b/.turbo/cookies/0.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/1.cookie b/.turbo/cookies/1.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/10.cookie b/.turbo/cookies/10.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/11.cookie b/.turbo/cookies/11.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/12.cookie b/.turbo/cookies/12.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/13.cookie b/.turbo/cookies/13.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/14.cookie b/.turbo/cookies/14.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/15.cookie b/.turbo/cookies/15.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/16.cookie b/.turbo/cookies/16.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/17.cookie b/.turbo/cookies/17.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/2.cookie b/.turbo/cookies/2.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/3.cookie b/.turbo/cookies/3.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/4.cookie b/.turbo/cookies/4.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/5.cookie b/.turbo/cookies/5.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/6.cookie b/.turbo/cookies/6.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/7.cookie b/.turbo/cookies/7.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/8.cookie b/.turbo/cookies/8.cookie deleted file mode 100644 index e69de29..0000000 diff --git a/.turbo/cookies/9.cookie b/.turbo/cookies/9.cookie deleted file mode 100644 index e69de29..0000000 From de24f183d64479fa39d2ab6fd0b209e8bb1d5ad2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 24 Nov 2023 23:10:24 +0700 Subject: [PATCH 006/312] chore: remove personal vscode config --- .vscode/settings.json | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100755 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100755 index 893de2b..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "**/Thumbs.db": true - }, - "typescript.tsserver.experimental.enableProjectDiagnostics": true, - "typescript.inlayHints.variableTypes.enabled": true, - "typescript.inlayHints.parameterNames.enabled": "all", - "typescript.inlayHints.enumMemberValues.enabled": true, - "typescript.inlayHints.functionLikeReturnTypes.enabled": true, - "typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true, - "typescript.inlayHints.propertyDeclarationTypes.enabled": true, - "typescript.inlayHints.parameterTypes.enabled": true, - "typescript.inlayHints.variableTypes.suppressWhenTypeMatchesName": true -} \ No newline at end of file From d6e6f73ca04b6665558d7bf4d78c90140bcb0eb3 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 24 Nov 2023 23:12:18 +0700 Subject: [PATCH 007/312] chore: remove CoC --- CODE_OF_CONDUCT.md | 134 --------------------------------------------- 1 file changed, 134 deletions(-) delete mode 100755 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100755 index 57e133b..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,134 +0,0 @@ - -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[this email](mailto:palmpasuthorn@gmail.com). -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations - From 8d7bd40fb7caf563d86ba19bba3f8956caf6335a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 25 Nov 2023 15:25:53 +0700 Subject: [PATCH 008/312] chore: update packages metadata --- apis/websocket/package.json | 10 ++-------- bun.lockb | Bin 330476 -> 330156 bytes lefthook.yml | 12 ++++-------- package.json | 4 ---- packages/api/package.json | 7 +------ packages/shared/package.json | 5 ----- 6 files changed, 7 insertions(+), 31 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index c4eeb04..99fd315 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -3,7 +3,7 @@ "type": "module", "private": true, "version": "0.1.0", - "description": "⚙️ Gateway for bots assisting ReVanced", + "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { "bundle": "bun build src/index.ts --outdir=dist --target=node --minify --sourcemap=external", @@ -16,12 +16,6 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "apis/websocket" }, - "keywords": [ - "revanced", - "bot", - "server", - "gateway" - ], "author": "Palm (https://github.com/PalmDevs)", "contributors": [ "Palm (https://github.com/PalmDevs)", @@ -36,6 +30,7 @@ "@fastify/websocket": "^8.2.0", "@revanced/bot-shared": "workspace:*", "@sapphire/async-queue": "^1.5.0", + "chalk": "^5.3.0", "fastify": "^4.24.3", "node-wit": "^6.6.0", "tesseract.js": "^5.0.3" @@ -43,7 +38,6 @@ "devDependencies": { "@types/node-wit": "^6.0.3", "@types/ws": "^8.5.10", - "chalk": "^5.3.0", "typed-emitter": "^2.1.0" } } diff --git a/bun.lockb b/bun.lockb index a26d7ad3ff228130a64797f2a2ac59d21b5125ec..36573204465ab4fbc11e79d09d77c78be2c49c57 100755 GIT binary patch delta 67343 zcmaDeRbH^4o*8#!^W6#+o^T-}Ab*HXPRacPuH2rw`(@G~$pOl4$X;9_8CC@C#U%4c9;n8w7wAk4tf(8dH-(@@I9 zz#z!L(2&l=z`)1A&=AVRz#zoH(BQ@d(cj7lQCFE*lwQohz>vnqz`(=6&`@b;kq5FW z3d(n7XJFu}XJBaP1~C{I8uZv17{nPE8kE@?7`Pc28a&w`7Fn@D3{EXhPRvP6Nlnhl zEX`zKV2I~rVBln6XjsGvQU98gfq|ESp}~p^;(_AK^t{BJREC1=blv3qytH>*PzQ5E z^c7^LRoa1EsWhE|fgu-^&OtGspP#K;T$0ko3UPl)QDRwYQE_TXMp1rgdWKO& zNormS0|Ub&Wk`4}Qh}(O0-@_0I#nP^qErQvq%w;Wi;5B}88Y)SQj0Q6iZfLp4ogyo zxPOlt#9_q+IhiF!3=F=?kQlUAh8Wxc4d`NMz`JWe^rvN}mlmZKGn~|b*pph3nw-c` z&%m%)6XLTOnh=NN=cSfp=B6^_X69xl>!#)PXhIAqNG!?F%}%Xsfbz>VAr8sXgv2n+ z;&`YzCUsSFGZaXOF?Fw}*p2b)))om!ciSEies$-wYd7os2on)rM4 zAQr&VI=4O~jlR zGB7Z>TSG!%nsq%SF~Cwij0P#xP01``vW7V9s|_S^<=H|k%FF|m#taO*Z6OYew1rpz zD>fZ%A;oG@GALXa7@X}P=2+N6LN>1;7c9~0=md$=2Tl+P^hA^6#K0iQz|fFXnwe9Q znOB_b1o2Uz6Ql-mcbd$^p;!Oi0ir!QBfmVa2vHt`D;%WyrntBW>~K&379VlKUp^?KV8)c;_m+*5b;i|SJkJ3vCT zq%=)8tC)eI%Ms!)HZO>HQEEvMDCAR%igc6nQ&Km1LjrCIRNWG9NB~cR(y4jL`6-!s z>HB;k>O}k?0blM5QP=AYaY&^%C<5yl8j=%}6O%Ji8A|;jEgu-7$&dglC57A3rQ;rV<90l#{uFF&sd1PH#{L# z@QHYczO$YX+C2dxpI4BZlb_DO@GAjgetKeQI;h-SkO=Yc)I^B-#|e-&Q%52sz!D3J z@{`qweZ`It_oO>Q+!+NGPtMfM%uCB>2!P6aIY9K6XM)w$H>74l0?;oL;sNVSNPwwl zLIPSY6XFotY=}k4xhcBE8JW3)nGpWJY>2+(;^NfeWCn%~2Z+9(8IXv+mjNl3iyR;k ztm^=2lDLDDay>(XE!1VkP+Aj8%R}ju%(OI69%#&mgiK~W#9(2lPcw^klQUC_7%mk+ zLLd{=$VpGFV1lNQf};G~s>GrcSn2r00ph{i4h#&OsSFGa<;mrlDJ2;UDXD3Rr8y2f%14Rs5)n0VE9-H(RQmGV(jU1NJ`&c4hhI52ea#)3&nVy$ll*+)cyb|K9+|;7z?aTvEuuz)+N0TAZ0y$?&KeV&UQ%h{N93KpfNsRo_zyiP4nIBCvtiOBooX z7#JF6ltMhf39Y_<)Il5^R|g8WdWMG2^^hd+s18Es=Ou%}nE_Opf@;784PcixT&jaO ztfvuTz`aI@0}esO*VjQDm{SMw$e~h5o?coCNm{2_7#I{87#a$SQcFrQQ;Tv^(@HYx z^YgR+wm^dPc?-k=m!bTn*QSd`xau^^ujqA#^LCo`{vfuXe%;(#BW z5C`X^mVtX#@1WwRq4d>G28Mc2g_)9?TA-U*%uv<|@ez7?Zr=kjFsl<1^sb!@45|za z4d$JoAY^FJ>tq1;v_SEgm{ZIEYZ*Y}xB=EN+6?U&!8%9%nd;-McqRiZ)#54v5kOiPjcV+^_XPJ3vIhpAhB?l)!^kt`3mVx9?O@#2% zauQ2QQd1cCp&m;uE-lH-DP~A5E-Xzgs$}>*3EEgnEiNcZO=V#C(*X(E(#a4XB_`(= zfx_-X2gK)dra;6m@lD<(pjZEBDkSDkOogPin;nq6wYvk-)>_a3Nt4VoAU&vQ9T5Mt zbwJz?YwF~7K&lsisJ`OLyyWc65{8oEoJ>$nD?bb3pDD8-`tu7?L9VK2$Sl?^FV10L zD3}d#m*#AU`(b>^*$|7kWhGEd63Mr zZXUz|C+9$Dv-u$N>KPhf2EjT8xH<&r9e|hZkdW$L2(c)d5fYLY7D18}sI)R=U|{fF z3<-ExC;0Fph=bynFfed~ni5}^L2_f@a*&G{8Y-4TT$r9;0P0FHXf1_k$X^PvC=Jwg zPfX634^`*71S0OR5)yLZOCja>!j%vQ-B|+3T+fz4%x78+$#j!fGceSHh8HeC4Q^fy z3F@NN5C>(hh8X;P6~sYTmO+B{=2D0c_b!8kz%nR31u9>)43d`omO#w;4|QnLGKj-` zmq8q0w~V14JRp-E85)9`TOd*43srBv6k2c!FNOGo1*&1;R)`NjErIy>;1WnKSh)n^ zgRRgwUc3k*KVcCh=waRE(nS#UMXAO4Ic2E~>vu3P$TKiBEZqTdXi9!^Nl|8AYMkRD zh{gYQLJaU)1o5HnB1nlV54FgO5mNG=-wpAB^d5*0_wSxOOT?^RWIx2cY5O5D#k?O9 zV4wCu=tuh?%DWCg0tDJcGlI39(7Sf95iM9T$a4r1;6~90<1{E? z)iX34f?D|NJS67wFF+DR%ms)BuM3bA*K-jfUvm*+K@OCTfzm!u`X$ty%jY3kW5Yg( z{v`|$4_Y%o%qa(TK0pJdlLMp_>RGNproO^87+ZbJCyZbFKhBR3%te)%?pmb?S;W=WBW8K^qVEY>Y8%3)yGc@q+k z>!IqCatpv6*u4CdRNeB-lG4)56b6RL^TdVf>+eCF0PA_ddSa;+1x2aF#SCSM;4UtM z*8_-=C6xuKpe6|GLx>YV%~0KxB!*v5`QnnqlG5VTVw(q$Ffn)lF~1lxV#c5XZWBD6_s#PZU-g3N+c28ONoA;~B?KR-J&m4RU;RDRlhNH`z73$eQ>v0OJP zKcy1ZQBSHY0S#-`Gj!gEq>RMmm%;h(q_hh3GSa@|VAbq@32bpmbf&&`|gml2M}GLP}K6w-5{L z-a=e{`ZdId?><1p54?tmPyYy^i&9HKfy~hU5uz^a6Qn`2_BA8~Og@8hC_@9tBf6zU znVO#=`oupoFbD-RFf>ei4Y4?}tPIqXVrYX}nD`pf)C_(NiFdo#5SItuge1|}ZxC^( zn~)F?TJei>cj*?s%?8pI0vvAh@2*-TzWc}K3~d2cfr`f z!43f?{mAyb1MC9qES|MH`fN^5E-+D$5U=G_Y0T3xoT?+t+GSufck$^NmGiSMWlmlp zWia`Hlm^GHgdg*66*6wyY+-1@EFtr!+9GIw$^R#jPLeqWrpott4A%4LF439(P=(oH zvV#Q&(^Ia4_p7`zx58kj)FGOg#Be9G32^D74fg9QUa12agFQozV128+qQ%{Cld z3=A#|3=J$Gog8u85Od%T@e^Ex^8`13CAB^h+%Lw zoF;q_BN!)Nv@_?(;)8?$D3%Q-o#AAM$|BT1<6~e50K4s?tvQn!|70!~JI*qGh=*Ax zUotW3)AqsI4*bz)YqLaNm?KpReGBAWNFf@SO$z(1z`IM&}=MFIjh9Cw|3Iin= zP4UTIUUr;C;tULK;CQ=eXU@4p9O4J&$rDY@nSO{*=JK}Vu$5q7Fafy*7Mq-P5)hX& zP7d@i=R6<*F@hcBaway($y`2mOpcP1y?pFA=14-K100K!9iDNrOG1JGp5Qnnq#%aD zML46RAVI-085FVer6zaz+Hu~Lg1CZtvY@Fsr?@mk3D{1~L}`dEtRNR~u9Jp@2spQJ zzL18vj%{*aiv^?1WG;U@jzuyI41ORV!IQ-Y8Hfq&AdhpJ$U+Bvv+ z3bbP_lV@PCnS9pFnsbvpB)PMK581?N;Kj|J@6(+Uv789;fG zlU)%Ke{5iHxGOR+Sc4;6(A%7;NpbS2U^}LLij%oQ>^Q}hAVC23B2%i;sER)T~QBglBBBIU_lp?0jRltE?9SzBw)kIE1~uuKlDwO~}4 z>=kCmxj_Y{C2*Dn3y3qB*V3BPLKTv`SSJU1Sa7I9GBOj$M;ymgA-;ts84C{1KdKOi zFipPbY0hb)2Js`~WI-Qu))F;P(G4>4s2U_<7$yhWn{)hEgCrNYDICV?kmQdLX;p`~ z4I*+Nl#@dp5}0h0FSb~4Xh6~>D>!=wYd~xTr%Bey8Vn3(lY4EfIqqse+zw9;oVuEj zP=I9QZJLm9Mg$^@7R2i;U~`?cAif3XRL%-5h)=<>!nr{UVkg7}AK-Em_gJuNL!{u= za%5;jybrPHzywYXZJ7H(b5EnB|4!mZ;4&@+Beysz^ zK?o5$U5L>TyBsVySQ~UfiJ&*%hC`Qu!IXiafeTdbasJVTXl0x%7-qq!H@Pd`j&rIW z#Gw#Bp4Wr8ixr%tWb`L{CD^g1=`%1SP44xz=Dem42_8sb@fbjY2b@qiT?`;$zzohZ z6QMkau@?;>E@T8HDOMRn1_rCiy(?@u3?c4e1-ovGAw&^`_stLzCSaFvnixUi6(ZMS z#K4frz|g=0jI;tY6TaDFz1g(s*?RW*V55L#53Kne?pxmQgfsgwoe zdrnSM1_nd0ErOQj99E`KbK%8qrzyns;9`a8yy@gqsdlV#W}u|#8)nTJVg?CV#>o?X z%{lwbAQnNwW8!Q~~>ar4Pu>2^%o7L&Wu?KsjbAYly26rk4CJd4R( z8Fs94IfguIfD&g2;iSArhD~L(pB8|h=3gT5nDk`*sxEF2{ z=V~iRdy5+sYn-CikYHp61v*EFH6*eS!Mw^E5=CGivi`CLJI~&l$;4)|SGFBTz752B zgofodFmDT*nzJ(7GBDUp_BFR=s>=hulIIe8NGf5N92jWM`Olt# zK_8qi0u#(Rr#V4dfZzz=eC-5RX>ZPH>J0HV#9Qeg-elf(8xCg%hA4RR;FU@+O&%$nn}J0x7-34}@0V{%uC9Y=u&L<1sfS9m~z zo*A5S_&gyQ1>6Ya4Dp174jZ`TFvAm4Kna2^v+@F!TfS!2tSi8*UQ=t<2VM*eCLjlJ zDta?8cr!3G@PdnrJ>HNM2##LXf8GoX#*=$XtT_#RAg*DWEI7%6(FatqGcEN26=a<6 zeIP->2=XVZu`dII%jC13)|{=r3=EbG3=IsE19QxoPWXcKa4Psgau6i&6Z~LWLDk$6 zKTzuCooU122TSyzmVur>1A{pOLxT{=fgCOV5N{)b<*Gj<1i@*EQ#b%AWpTO&KvaXX z7iV_>#B(f@0~;+^0zi=q3JdN)aP?|x&6FHC*{jBm>2l!Yt{OW|ibbIwyC z5Vvzq4m3CCcc9}pJByoa0$?6gY z3dLSiYtCI^kPOH%S@5_8WBBA#4R)MP;SlS=g)v7{IHWBEP1Y71oa@4oQZnbOaEJ-) z;8bN20dWGPN~no|G`t{s57=?CM?ex1L;zG_ibsMwpN`h7sgY1evCfYK1-q}AHRs(( zNc$bq*!7BnWPM2DUJ?bf7*wyni(+8#WngF!m^{(lf+L!N!5!S`0QE1XMMIJb<7B~f zuoS4z$vHW&!-69QQaZ4KYtEV&h`%A7)8jF)pcV8n=VXnA1Utl+8L^OL%QZRhwgpEl zr0WQ7GIRWhg#rmxaeeBl{ooSryb|- zM2HnklLc2>a3n$c?~wAnKMB;;@^!W5xR?Yn8R19PWQffylP9jT;7Eq#0+z{wF6NwT zk|7}n?#*(3NQRit3i3XaUdm*y9y`vo6o`3{2Fxrdj}7dn>nRW)GJ-U4@}@#Uk8$!u zb8`;&REVvJ@K}%v2~XC^7lSO=p^D+fCWlHIL>l4nyflb=I6(ym=bAK#f#9N@^Gg~e z<%9b>oC@iXf&m;{oJr}BJOF8~&P#^`E;!Y5-h-;-1VtLBLI%VGkfJ9n1Ew$Vyah)F zOxr|9bJpfe1_p=8XPa$UGNJV>Cw~?!0w&s+v!-T&vWl<0HRt9mSc(RvZ{}=Bps;`g z%qtt>PKbRy*^n|BQsy6lssv{sCefV9T$AlM%5or~j|jz8IY`04@hJyl86wb}av{zF zdzW=aF37o{()>v-B!5EEi(MW#KigPyR^%}-*n;bPP)^;N2MGd5Kz@Ysz;zy{Zazc~ z68DqxAvp`u?mv_di9Dvs7Yi)d3!shy*YpjUoE!y^u!OYh=M+E!9g;8Z6hKUc1dv1_ zBuF5U98d@`6&#Tqv!HVDB*S^B5Yk9s0S`wp6frRPgPRk9Ugn(sMUa4J1h;HDiy(GF za^LXq|*5hSlcc$&qKL?>y_1|cIm#jN0uEoM z=jD^R=Gk#-Rv;O`lwUEqYn~nF{t8Gr$pUV3@m50Og$11N!z&?;6+Un_-B1a04QRmV zQ6(fqz;5T1se+_taATV@pb8?#3>p<++FCWaYk?i-%PNS2A=RsTH6+>~i6gxl67=99 z2G+&Z3=H<5CN0y~>dB`T+Hq>sKompbF|LMz0krOyY4XKRbI$cO5TAkv1vvlJK)lEZ z3SU;2T983!ZLK+)YoQ4jK1g!8782UvOv%Yz$G`v@spbF;vT&BwK_ZtGloD8F>%jxM zHrAZE^^nkE0-4Uay&mFd9&k0r*#N23gh6^ZLmMD9E+n_DZh*uNnZ6m}>kbpLBf~bVlh(%41pa3T>rtM9W zyO!IreraN0@C1zxaN0IAFhnviG;o5(Bsix*dEDT1`M(+F|3EWyCi9lbr&idp3bum0 zdUl01Yho({L&#(hYb#ugwGAc~*#<6rR#>wxZG-9f+6F2oK{}k<85n{&;ZeUKy# zDdyAqAWnfq)EuZBIBGa=L3!Z($SKhe344gjfPRQRa3hg(Vn4)8@USfF`F>CXxYx#- zQ*i>s?clLd&e#c%{Kh<4aJdD?1W2HPu8m<=h# z94t6EXHA4?Wtn`@)tvJxR34lsn1m-yKDF77({mCe{V{{HM*k#;W0^p;9_vvM4?KX# zIvL_TNCDtD8R9(7$$}Z?Om&kddu_F2Ju?{;ioTZCOqx?BpW15288ZbE0qmeAHtW(U zAnCJq)|~gJKr$;h=s5JJLc9Pj+9rb>ojw&(5P^#M zb*w;HD`^_UFmQ@u-87AXAqL#J;+qbuQzn|4bH+|*U5+%E~Jn{7-}>RVlmiI&YXD=)!=Rt)6RL5x%S&}{Fnz#b>Kz=C<0yQLtFw$|K0N; z_OgQNdXByGVSWQm-ZcE;!fK-P&7eL$ru7X)VF90{1 z&8#_07eX3M@Sx(HvJj#Y+%jf5w{SAoAv;cv#gKxOd9t8|IcxG_1_q_Q3{krj}TVg#jq&af3QH-TD~6IMW?hGDW`h&j`V6_dM;+Htb2gekdbYt9+G z5)w*`lLcp6aIA#Ql7ZT*ysJPZGH6ITb`_-bg>?Tnt^$>fpn6AqHK=^%HMQoPvl?!w zr#a^}1aGf7r@$IW0%Dweaj!XN#2QG|2bptOv<68Z>zy^AdJJTq)LKaT09VvZX=^8Y zowVavy%sGBI2hNVi-fO(XhekF;&luRG2k2tvi|=%nDwAGyXShag_hQw?dxG)zqr?& z_5OO0$9glYISedF29AEt&s!lO%ryC8zXi)S28OuFyr$M1bGJbP5NhNhPL6FbU7)V$cDSa! z=1i^IC!e}#$9Z`>B)>9Go_N%piFLPMFIBEzMa4cYzuYpn!_s1rKRUbI$d#DoVRyFf{kVJL~C;n?LCm-hI%WFlVcA9gC}JCa-liro;{E<1zenR{@DWw zR!DC&U@rqh4R~A`G}CuxFT_enyb0`MV2B0}cm-}X=Pca^8D|li9Jtk-Luo%mCDcj_ z4$i{;knGO@D%hCT@1K0?x*g}w{g4J6SQDqk0Z1PW%ww8)U~<O%i1W8=rI-TSC zA&7Q(0nQE7B1C>Q{4@3L_9?oF8eRy)$Z97iUBM=8d<|=%TKnerM zNOSuUNMJI7X23bW9)Z}$3W^mb&7+e~-Ld1$KMDzb$gIlJqYxV)mBHhq5TjWpU$nR2 zI0jM31ZrWk1|9>I%An!1=3@{$z^yaR>&GAg1TjwbI3(sFyu#xU6Cgd?<;Nj@1^1LW zpC5;~4O}3zN}m7?8-j*n<4=GKN=s|b^(P>@!74bPo`468sX3?ONl0|Ffg_^)BqR*L zl@aU4li)JN&YJT%RGbxDb10sgeCnYcYr-k8bg(t&v{R6*1nJVBIK{x=KAHEr4aaGu zRKa=fG$epnz#+9n+>WlTTULG5tF;x$ChVlm6MsrykpJW}b!g zX~1cV^UPUTxPV%_BIh9O9f(}!Imqk)$K;76=A5g~LFyK8lIQq&4w4@c$=dTgWXunu zciMSmw{l-VPTkEHASEM2<%tUn49@V(!+jBwSRrzAFG5ONa5IDR`9-)}?aev$FTr@A zk-Tzb9_xM(Z!)OS$bK2wX1B|b><%`assHliQ!mPyuBJ*)d(cKAFqhj!E&x zWG{0&rs^A$yUgua_ul|D^v{}EGqK;C%w=gdx#6V;XZ}q{dWDRq$lrpbR|Zg8;tak8 zi6ur*6Pt74ElBu*Qw8Je$tPdgb6Veq)S%#|I_LD;kkrdGS#X{?=Upfdte#W*4y2d| zml2%hcOZUd05|cM-vM=3K@(B8?|^DuUvq0t$-5AhaG2kNL@T7=aj@WE>b^JG>zy6vnR_t5fC?b7`!Kr%P0g8t z?oZ}=Z^s$>0AehpHPZS3l0g_JUu?Eud@%Xcdpjonhm*NJ*fIG$ob2_%jib6!bC|%L z4Bf||$_&)fN_`A>PoO!+)W;B2kaTdso0H=)+z=ac4*4e##Sm$b$Nis7?)q%U*$9Eu&i>^LSqg$$a)&1BvC6zsSVYtFAv z85ndSt8r@0IhCG4!jlQqtYa#8Hks?29p|cNkm3v6Zs&aR3=$#?;A&RmIm|xLYMZ#{ zkjQ})i7TGNJ!Wdo@!~loAs~thnHP{ahO}%_UO?m-zy;Zi7cjk`2JMX(kmSq&>WDI_ zy_|gNhaG3?OL#8}!+niT(BDQ@`vuS>HfH0y6&X_y*!JaQyI7h<+qUf6r2P&Mc+X@0SUO!cQ6wL=b3X%gvvpKV+SY4J4nBf3DjQTRDKVs z#8|*XmF4dt#Vn*cIsP8rI5IQml>Gp68Yl+RKS0t2#Mw(fKngn6$ro$QIsYS62AXqv zeuSuG2W5GVMNl3zi9F-v_z2044B#4!?-MMlK$hfxf{iJI^5Dr&kjP=3Ecnfw)8{j! z$p}dTOFqMb7Btm+=QG4g@IW4?)E7vJ5AJGl#(qKe)aEacXygEoG(Pvr-^%fl8ASspwG(o|+{u?CjL6yG# z>LnRWR5+E8Rkq8wB?ScaF!ONW)7#NUgkip=+m<-^a1q0~#63{^#AfrGu z$mif?(F_a>$TZ03pdE%F{kD+x%M1+2G)Q9w)W=m&x&~w_1GxPUW`X!j)AzD7s)M}1 z&gVYrQ4wLAR3gEdZBy}4btBaRW|`DJ^_@3K<=Fam6!^Z zz(<1&nhsTmOoPN{Ld}^4l}DyQ4xSAaN6^-gv^Ezi0ir<)=Rx_%G$?a{_E3PNKs3|z zuN;i>^&sWzKw=CG48+hNcW!{Xb0d`A1l5m?2ARJZDi5MT{4G%a7SIZLP6h^sy&!?< zYdIO^13~ipL0kp~24ZMXLOKL>$6=^G5DhZt2#7!ZDkr17B`AHK25}h}7(g^A+Rj7y z$TUdX1*kZPW||(##VB77^7U1yFo*^zy9VWhXl@1uhKJD9`WPw>qCxteLir#XWbI2R zADIT3_X;WwqCxuJK>2S#$qd9s5?}zu<|mLK0|Nty2Knzhlurx|vhWAg!M~uv_ZzAo zndW0)VBln|hZJSJjF2*v4@&ccOaUEHgoOqP2{1y6OkqYyG8F|0g0}G>(IBQ6h+tq~ zK&C+=gz6_ zAk!eB`%w9Zj0_Cm{Qnp#@DwD=z`%e^gW~W7)S{PAb+4i7Kr|>dzh`6sw@|)8qu>`* z-5)3))M@}}VPt~jGA1a^%*0R+4mu7dPZ#fdLy0GDrj}528U#F{nOq zCI$u$1_lOskRX%>IZzqO2hkvTZK!%3CQ#dlfq_8>ssI}elG0;>#IZ3{qX|fmfq?-` zPnQ*76bD5lgBes9M1$OE4&`H`K~k0=f`NennFf`2c1)0B#Q|!aBh+7>Q1u`hTo(Hvf0aV{YD7^@(9z=s20@~~Z zQojl+4yQp086>s_s&OrpUI!8cwE&ao!v^BzOw>lr`{kV~FG zEq)4-U;xn|=5wg{3lN8afdNE=n6IGXuR$CJ1_o?2$lNzfkUH-@6EwF&)qexi^$ZLk z21wy|umA%Chz14aFQ^5-q2kCiC^7wq8q5Sz463=AAr`Sh`N%X#Jts3Ht#LC$a=S1{ z9VjkA436n{g&D=CzY%8Sz)94D!cBphfkBOdfx(TL0o>;B0hz+UzyP8_4)tS(6juIF zaeOq$*8xyh1wqy0p}}PZ14sc#V=z<$hz9XPpaz9O#X&SkJRItv2q+y1l?Txv`6y_x z#X{xdpdL$sssqs=kEJmOgR(XQLmE_JI#dBZ8e~BRR2_&0F|$Dg0|NtyW}H4(gi(F^ z9T7%>dQcpdLCr3Qsv(93DXjt#3=9m&G^icj3>8PFL7r)a239*%zMYw&9vq#$PyrAP za?nJmgC;@6K{Uuylc9WM8l-+YR2)Qu9Iyb&N2WmzSOgUZ(IE2{GecsRVKLOgB~W@P zGo%Du2h{+gK^oRW`5+p^-vH$!(;)GUP;n3qYCs)kh7`faq2`}}%Acr*F`((;G&2K( zI;b(t45`DuLpA(>ssqs=3w}Zk{0kKa(ID}EP(FwTF&SAP7PGQIG6)+BB)f8gf{g)m zjs;W!FAKy%A*e!OC@lh|MOh&ESRBfifEoazK^93v#bu!8C_>Fug<7ZqrM0279z=gV z1Bd}Kzz`~70;SEN7Ft3LvV~}10MQ`z4p4Q@P;n3qV!DF}(BXAZ2YEngPpCME2I=#t{E@CP#(7=oY*gP|6OLM@Jj%Ev(EW1;*67D&;W4ppBCRi6u$FMz5qgwjP&@lq&V z0df#1{ec+_3=AL|6o-{igKD7~>!AE5XwbGp=^hqHBAp6VHw|PU=vY>$gJwYaAR1)u zOsKk9P;n3q?$XR=fwbtBLKQ57(#xR=K{UvK6;O-ULg`IVb(^6++6LusXMwaM4ng%} zqe0p62n$0!c+BZ2R3V54#mxyQA4G$gXFvo40|ST#F)x4!1_lOX8f4%lr~@uT=_^qB zDpWm)1~G3!#c#1VL$bqNkSGHK13nsL!F{MYWSV(;tQ4dC^c*Qh0T96f4${Xg;O+Me z@1Yt%G>G{bYV#Kmhk=0snFa;%R~ASG_Zun?qCpn_g{A>UP;Ch6C}W{PT3A`3t#mG^ z0%RK0LJ@$96GMZdLYNhj??s`sI4h)_kYR-sEDBI@C00mCsH~NLoRb00r1es0MsA$bwanV~`m@hwy^@xeFRFyP^7#X;8rJg{nUZO}l47X%%$1 z^Bu7#KjaA0P|sK@5D|~A4G%rqthU)kcnk{(;uJ! z1IdFLU&DDCBomZ!=rRp5{jVis!E{##M(^o6tQdKx&vjt*W@MiJ(t$CYk!8B0BV#xt z>-3o*if#Hw5XC;-(}^*hkz@Ko5XCwDrxRoN^a@)>-s!Q%!>G$U8mKg)w~k4UoWIkO2R5PFKe8=^gfrywh`C8NC?=r|$#_SU529PM39K^kx*E zp6SLI&L}edAczv3F6qt~&L}p$5JZVjKMA5FrYm|dhBHb|uk>IHpZ>#%k$3uC4@PfB z>FJuDjN#K)I5YB2@AYK#W|W2E;-iqkE< z8N;XV013?XX7pxMp8gUfkm1J2JKfiZ(VJ0q`b;0j@aYonjJ(tTf&|p3d-^hlPd@<@$}3<#_;JcKmun$0;ba?gBZi7&+uX7on9Nn=*?(8 z{Uk`h!2hB%3x41gxiPhJeDu4-_6DjNXj4({F+VBK$$&5z6Sz zXg|F(6cio-pzr_*I8L_=1BC}jU~U+rH>30Pmmq^+ z?IcLRBLWm2ag5%Kk<%;VK;Z!rxC;`9o~{`W3Xe!oc*HY$GsaH82@;5i0)yx z^v(oOctnH310;|*-7*mr9w33aiHzQi$yjD07&E8;1PN5cfx;t&(VH=Q`pOhgc*KLkBbCvcF?V`oDkwZa0((IM z`O`VmK;e-93Xe2KZ^pvuJ3#^#iJ8DdSwYHJU{|>K?1X%0gG}_c+@g_Gj5)qSqlmekic1xz}D%Kb)fL50EI^# zqc`LB=_f$~9+jZ*sAu$M+&R6n9uyuRfx94q-P1K2K;cmZ3XcXxZ^pgTZ-N9OszKq= z$mq?ue|l#lC_HLF;Q-RU<$0ugPX z@aSRmX1qVWvj-F&?V#`g2|S!`*$WB}kigttMsLQ)(_exFGCDxv(Z}e`_;mWrK2Ugc zg2DqN@O-*wKPWsv0&Dvjy%}Fl{|OSP=mLeu1V(Sh*V9){0EI_4C_E-IdNaPA9yt*d z9w33e6B)gk-ZM<+n#343y`u*dAd?uqnLaX1-vtt}=mmwyWJYhM&kWPECNqXje*qFX z3ljOtFkNa2W7zZ=eV{Oz!syNPoniVZkcdY=C{U&{dNciGm|iuNF>Lw|kjPz-$Zv+} zTGJTArmvU)3YKY%-b{ZPrr!dIL`(#Q%XCI>rvD7nyQVXSP3M>d3Yh7P-i(aXEoXoN z1|%?d2Bg^m63Cbg3YeLU-k_GpOi;i~0R;?5fPK2>EKtCJ1lG=C^k(Fo{u3lnF%=Xr zvmxz|*`R=#1`3!tkoLzMP{4o$_JRcXr*qB)C6wu)aG48fe}DumW`Kfa9;E#-50p?q z0%t)2qSGbkgA&S2P@v3b^kx*Fei9_$F$)wX3mCmY4Uq+)gaQ({3lfl?uDKADP-cTd zWFe#>0uqRr0}7BukcP-2P(qmt3J;Kg;&jW!pzr_*%v}s=h=2q#=7GXv38W#i1QZ_g zLE!-sP@nF(6cipHfwfB^4H1w)#R5=xEQ2&emVv@!At*eSLmDE>LE!-s*b5TSpU$}g z6dsE};jseJ5CI8TECz+gN=QRwB`7>V0%t)2rqd-?fx=@6C_Gj{8X_P8kENjSSPf~2 ztOkV#NZ>9=zrAOVjJpzzoRX^3nCg$GFBE=VAHy5@FJcx(iP$970V1SAl#2^1bXAPtcn zpzzoX3J;J#;&jWMpzr_*%-soTh=2q#wt&K87o#_*A+ie;9$P`-0TM``?ztNj9w33W zyBWPf4H1w)#WqlQ?13~y_JG1;J19K%LK-4_LE!-s*b5TKpU$}t6dpT3;js_W5CI8T z>;#3!en>-PKPWsv0%t)2rPC!3fWl)JC_D~88X_P8kKLf~I0$Ko90Y|2NZ>9=pnAII zAy9bi0fomQNJ9i95V02&9)}?fk;930NEfg~usIa6{x2C_F#{XF&o}r%Rp&g~w4)c$|hbL_h)_ z$3Wq62GS5Y0}2n2z+I5Q?CF|kLE&*66dq?G4H1w)#0gM%oP#t(&Vjpzyc~X^30}g$GFB zEJ$GMbjfR=@VEd9k86;I2uQ%=A}BnrLmDF2LE!-sxC;{4Jzet#C_FBK!s7;{Ap#PJ zxC{!9n~;XcO~!D>1JfUZsDsljZ!v~59-2N8L>->~5=0%D?s%Iq9Mt{*QOBl#1X0JQ zd)@(s!8JzS>1*#m8Xq8mitC^-xC?1~+y#Zf4Nw@|gET(wfx-YJuoon7emduUP#D|< zg~5GDCqdMW>57jS!$A#> zM<9RSVdR^B?-8RHEIzK9e@O$PxpKd3ImY9+UJl42S}je5hx5^KpGq` zKw;9#6y^%m6N0EyfMiTq}q zuJsPo;CKNFmUo~A2jlcxAd!fdpm2E)YH%=4?|KhvaJ&Kq%zH?K;{zyQKmv0=KpGq% zfsEInfcXe%aC`&>%o|X^fCSj5dwv213`n5X2HfCS2ok7x%g8&O^)tA^5%?JtFz*<7 zr*8!b@J?s^0ty(AK5^MPyYcDXk}*dX0)Du5hSqUA1E+bm_WlGjVw&z(`c7VRe6~}gC2#vpwIw$Tz=0jBWj9NeJb5Mc6VOq~7@B(MV{;3~)j8uXYb2nr7#PV1^JV zJS3PvgC2zvpzsi8;+=jKBv3hBQ4$m$Ac0m%CU3^-=@&r)D?~uyA;kn5^k|dVLnK>|BK0H0Y722nr7wPN15-pP|9T zJH1qw2{h_)5G3HC2?`-SCeWxyp&lqeKmu1m0=uUx>NAB;U!ldsJH1t(2{h_)5hM_y z4GJLxCeWxyqX8&DbU+~l5;!>B(2yy7`VNr5R6{1vsK-N)K!z?8&-6EjOg>CUnWi_{ zF@{Z-(1VE_XPO=q#uPUFgdQjXgfV$Don)HM6wVYjy+R-4od_mxrqfK*lOmYHrfV30 zY_enYW;)9>J;{nOZ2Aq5$WbdsZ>IB1(}k=-HLoGai`I2E>de-m^tR`~QE zAc3z&Ox}#Qr#l*hLdFyntHw;A@sE!nfe14u-szzxOrY_Pg(jeoF$cw}DU&zj!|8#h zppXFxYy}BCp3Z0nN-7qhI5cAdjel$e2}oFiJZ;Y8&G>wJqB&Fe^b;U~qacBo(*-R+ zNyQ4}K?^3(_{TwzfQB{5f0j(3@sC1FP)Y#_Tm=cdpRQ;HN+~v=IJ9E&X8bt)B1pi( z78HlpOx}#2r#D)IQVK}mDM;Y!bVD0ZO0feaKN}`*#_!V~f&@J5LA8P{lQ-kf=@V^1 zDFr0(6(sO`x}zN^JRCsbVaMdn_;>n8kU)eZC_L<$ycz#bUuX{s4<}G~I52rLF)~jN za$pLZz5^t(6(qvUJe|prDQtR%GbltHnY@`;nWt|8iAcDBg2ai*n~{Bbq7x`cKmtcW z0-VzYok2n33JMZuCeX~wL6Crk8z@Lzm_V}zg)X2V0SR0M3Gh!>bOi;8J19t8nLsly z7eN9R9-ts`V*<^*G`fL;1SIejBp^E7&>a*co}eIcX9A6UJOl}Nc!7e%g9$Y9G0_7Q zBp`vWAOY#=j-H?(@dgEnClhE2<|9ZT!Uq&2UQD1Vn1x=TAn^qSi8m9dArj~fN+KYE ztsnvA>5M+0B;p4O5FaMc6wF4DfP_CNJbalz4Ut4&P0g4Gd>E~#JBEP5 zBMcNCA>g*iN02~7I4C?qnLtx83qwKS5drdTIJhygF&q>g5ljJ-kC=!|Kaj-4GW|>x zlME{-=!}Z#2jiHkCp%cMOwWmC;$Y2YVPFv7elQM{#}hJ{Qou47dYL$w-Uw{p7svF7 zi78QL`o1`(Z(x}@NlYB9PvjXGctGZeZ$FU4RKm=as{`IH*if!B-7byk9n%T@?Rx1< zN{p<|#taP7(>XJlbcQ;oA7s{apByF*R%urT1}=~j zKZC@0Eu0|t9SeFeFi3!egF(Vv1&PV|C8-PyeV*I*(qklG83lP{R4faNxHF>$aa z?_gly2Kj>>YzVIkBk0O(hK5gjpyt?v)G}r7n=aMERL0tMfPsM@Y%wT(oB+vz?O=U< z1R5UhV82b9z{J7&`3%(T%V4Wnz&0?Rn;tod={(b!yP%M00*h_mFokIf>-Gz?n3CJ4 z*Kn{JOt0o(HJq+s#b^ZPLL3ev7c-&>TwnwVlS-aurN;4~kXLHc61!E$GQAAfxy}0t^fccc5Y{3=9k&&@GIhlTtv77(iUm zK6H@M`%v>in?dHXKN(!jvLzz02aMi~^$P%$y6UKNmqP%&}n!6d2-3=C|H5c4FV>eN8#j~%K| z3aSuvM8|bd5P;GIGbFAxK*0|clZR^7WME)80TlzCVFNNoi-CawbR-}s?I=PWtZT)Ma2`038eoGE)mGrU%_N3Q9cMP%%(q104wp zN;^7G2O2Pdk4R+ziRnYtffAq>$S?*51_P)VC;@_Y4ujH|A?Sz}kgGunkO`C_85kIh zpbAYG7#O~?GBAJ=o-tI+l!1Za0W=|*Ktsz6bciA=0|O}GnL*W=GcYiuvqBD5HHV5> zFfcIWK*cPeVwRxuNeiI{T0#Y_7#J98p<_BUmSr`~V3E3H{4s=rMMvxdN{#~Gg4h#$oF(BI*7#Lik zVva~VD?sNHfkMj(8lRv9>JC*0+OiCaPmq`gRLq5efk7Ii0TeaR&~gQ(DX5q?GxXFq z&^~8bsGu)YGw29HIj9)y03=YH%R|LLhaiENo}fYs#9&}x03Cb;VuIpa5ySu;4GT37 zbY!e5R1CJz-v_iyLKi9sTkY@5z`$S#6$7pO2QmFXhu?x23=9mRPy_uL7#Pf;Vz7ge z0zfA-K*eB(B!SMPwS$Vm4onIHor%lHz@P+*f7q!>pxxO{P=%lqlt4_-#%vc5gMon| z8XAP4P1kf(+mjkQqXuVo6Z3Xa)v`2&h;xR4fLh7HS~uSSQe7xbaYRu#=rY2jV6`)qzfT z0x{!3`Tsow%}g1Ne}ChCHa=R8T^Ph}ARXLj^%6?Sc{ssL&{Yilu{2;er}i2o=j< zU|;|pRRAh8ilAbd3=9mQ;}JmRLork=i-CawboPTK0|P?|R19=#FX+4qP!=x*i`6qQ z+nB)jBQVs1j?Dro ztOQjsjNt1m7}}r;L1z;;F@bLuU;rJf1yWbdz`y`H3lJ159nd%iomE`T1UVkM3#zvk zdT1l4eCURX)iE$Iurq;A6ldsxh}DB~Aj2Av$)MnX2r@JMU|_Iif*f=`4Jy{jz`y`HvJ@0DptHh2 zvC{>rcA3DZtuxGos_SN8U=W0g&4R^y4+8@ODE>f&%WSAZ(3#Ajo(QOLnFAH;V_;zT z!N|Y>DqQA5#Xx5^|7QfBjLt9*Dh4{g`4h-T3=9nOp<)vm7#Kc7#XyIbftdA^poe3E zT)hyga54h}gCfZJpezIxo5H}r06GU1R9JxyGy_S4&VWv51Yh;Q06NqRWZ*RD(X=4* zmO=G`&VUvKNrDbrg^JB!U|`^71jjxD!wRS%=x}IImI7&B2^9mKi~!0~pn_=?G~0m= zhh7a*1WG4Rb#oZN*S>=;W@mt%s0KPK8dQXX$`;V6Y9L30&Wi>e)db4o8)5Cg`3wvU z*I5`CK!Tf~1}*?K9$6R|K>2<%RBR!r@dy>$0!{sk7#P6m0+gS(K~Gj&3_3rGi2;1- z_im_O(1FyT<~}Iv?tzLe1(pA@ObiU5Y_%6ExQu~;K@KXm4=T2tfq?;ZE-uK^`=Meh zpiu(K83&+_SqVK}7NqVF^w_^u3=9l6puv9vsuvV`3qhy8f)t*F3a(*bU|0-QcnT`E zmVtpmmx+M^lukhB$AP3lXJ~^?a0Tf-162nq_d!PygW~@zR19>6HYij;(RvOVb)fUK zL8rEZqV*!I{RcW!8&s2l1TR4~gAURLD^Rhm3=9k>Sr`~V zQF9d<3fmYM7(h7&6gAhO>b8T<8f9i+0EyjzitPZUzbA|g44~k>2^9p@bf6P?K|y&7 zDh8_QjxsVZfW&S?#dd?z0#xh{RBR8ZNe&ge3l#$${CyHCb`R>neV~2>69dCKQ2alD zDg>Q<0SX#Wuswu|9e`GqAhAbKv4hZ*4hqW0P%+TC7@z~nKq2%5Dh4_t98~Xv%zX+K z1N99+2bh7>KVxQK03CD9Fu@a2!x9z-2GE&tppFZuqoTyjz@W^`z@Wm+V8o!x%)p=qI;@Zx(qjR2 zMnIhpP-jDznSnu%nSlY+$pCdK44D}ijHXNaFe?jLGcz#QFf%ZKTIiq_xjplAJ8x#G z`d$_W22c+k)MKB(!oV<*g@FNdcp@mLgK{<~Cxd$KJDC|6K$#Gf=|Gtbl&KCfGcZUq zGcd?7GcbTo6%}D-U=U?yU=U+wU;y>(B$yc(K)pFo=MB`)1@#a>-B?g-AJk2i1+{TO z{ZLRB6V!bKbr(V1Lr`}Rbm}Omw+HIofqHYG#uTWxrUS}!;B#^y4JS~i3Dn*Jb;3X$ zEKuhO)Gq>cz(M^U&~ay=qtHM{Fo8Pm=U5pS&a*NwTwrBjxX8-Rz;KC`f#LG>Mt^4U z`kSl_47XSr81Aq_I_jW~c{D2nLkueeLo6!;LmVpuLp&=3gEuP!gAXeMgD)!sgC8pc zgFh<+LjWrSLm(>yLl7$iLoh1?LkKJ6Bs3dV1_oPJ1_pap1_o1B1_m=$1_pCh1_scP z^q@oKm{=JYm{}PZ9)r&0d&0uN@RWst;S~!5!)q1>hPNyX44}jRROlQ8&{-!xK}}m` z25{2|)P4h2z7(h*8P!kx` zK>+ncKs^so*9A1-13FJ1bUrMoy9DYAf%;*feix`uWe+_jaXJeF184;5Ark`w=om%N z(TW|QGf_KP7#O-(7#O-)7#Lbv7#K=eK#7Eb0d$b(5oQL4W6Y4JP; zXlMhp)qWon1H*nM28IJn3=Bt@Aj2D=;fxbZ3=F537#L17F)*9~)pMYexc9#As})T{y3M4&bq=oSr7ClGWXA*f%^%*Ma~I!^2hD?=2+ zFIEPI53CFfA6Xd~KxdV`V`X3fosD*nm4V?tE2M+}5R_P085o|hGB7-4Wng&5%E0iP zm4N|t>f1tA1_sb!Yx`Ik7!I&9FdSrMU^v9ez;Jl_T_0xgde8xPX{-zk8LSKp*{lo< zd8`Z!`K$~K1*{AVpss!qD+5CbD+5C*D+5D0D+5CXD+5C{D+5CfE2P_B$I8F}I=QZa zm4Ts&m4Ts|m4P9Nm4P9Vm4N|tsvqdcKvq@;1~$+}Dpm%DA1n;ugBW)*F)-``oxTk^ zz!EeD0BVGQ4!Z<(*Fj?ips@kaH3Xn57eGUNpi`JZ{S8p>8`OjY71y9*8PxX#^)*35 z0icd8s8<~bDxg^y7(fS{>aZ{{=&~>{fNEnJ(G43>-x3|5Q`4AzVc3^t4m z47Q96450A>dqxHZM@9w)(2xLV2*4H8IbvjBaA#y-@L*(M0Cnv_U3qU%UxSf>!IzPN z!4K4+4Pazo0A2AB#K^!9%*enH!pOi725Pc1GB8AeTFZZbcJFfjaQVPN>o%E0i6m4RUvD+9w^Rt5&piH4xl8bL=Zf{uY~ zWo2O4!^*$_I+HSjm4Sg9bfO{XbVpVO20m5>27Xos2GHrNJ6ISPcCs)q>|$YH*v-Pg z0O|nmXJKHt1nPx@TDYLGBhbJDXb2{Xg@GZOg@GZ0g@M7Ig@M6_g@Hi{bR;b33{w^c z2GFU@pe`P$O9$%0fo=wQ&A`9_x;X?iRty?z28}I)&eHG)^|+WA7=oA>7=oD?7($sC z7{Zwt7$TV%7-Ed|0~Q8`hb)kDXm7DF zFx+NgV7SA=z;Kv_fdMpV1sbFRjVpmh$#_{982DHi82DKj7(kt4P-hs_(FOIML03S5 zhW0^sE`Y8L09`Rqz{J2H&BVaa2Rfh@bbkiu^nK7h7l#=c7(fFK3z--gKsSAKF+w^c zpw0)VuK~K;V=gGWGBGfyFflNwF)=V`frkE>7#Kizs(?mLK!YNn?mB4L4s@Xc=&l7* zQ2hsLOENPsfI73FzH2ck{y|q*fR6mX&B(w2I*J=~mj>t_jJwbtGH9@SD>DNFsH+AV zEd`A?fhIA;KvfJ21H%hY^Mi$f0d&GLXj%w#vNC9D05lZ<8os{`9bpIcJ3##mP`?7y zWdL;%c7y71P#*v^!UY;N19e(Kqo$xy5YVVMXh0hzzMmB`Kn)s@28}C&#*sIGTBR%u z3>O&~7@mVp~Yk%0j;at0bH137>bbhip41H(H|G0nih@PUDW0d(olSx}!6G=K$~%mR(BK<5cS zogI)xppFZuLj&rmfy@E*S3u*Epxzs3XuuoPq4~!G8KMKxpkcY!tPBjG^ItbZY0$77 zXc%rXD+9w6P}R!7zyMMU8gbjp!oYBVg@NH93uF`xG-?JKWd#j@!7K+2a)Ac5HZd_U zfCjQa*PnpKsX${;^`M(VK!Z%6u_Dko5NO zl7buxvJi$re9&+KXm|i5267Y#gKnY$U4OF`6#o|)85lqcK?-0rhz-J^8)B|OeF4%4 z5<|wn85tNrN8^FI{OX|KWn^I3&d9(3x*Q19y$4-y1nM(_y7kC$4;m%_jVFhK;vY0% zb&~}WRG{uUsC#}I)Zb%fV3@@W>6(KEuRsG+pvy5}4gfg_)Efugvjege)C&jox{on3 zFdS!OU^v0ZzyRt=gRZv$UE%|};0F}Kpj%`>W`G=24@-^E_yCWTfqKTE3%Z?f^&+sO!MMz`$iwYHVbu zV4_f*S(TcfHoe-5nYSL)G6%KFL6g~snHU%3w!L5YnC;tS9yDyZ00WrFx@ zKO+OfK1T351W;N8pIr``dj?JMg676RQ&OOzW>8ZQRB3fHGJvm40(I#@^#Q0o0Lqqm zphO626o6Xipkfo$4gj^vK}9KOBn>pm4ytfKqv4JMx1nA&?}91M8x~_^>`Q zsEZ5g?t=QspzbiJuMFxMgQjOe-DS{pE~t2hb(=xmWl&cc)MbWsmqA@+&|D;l2F+c9 z<|;w+kcOZ{2y#7W4iVH<1Z77{2Jmf>450ZXP?H?gRRnb%LET2sP(7%5ZUa>V>cN2= z2pVw*(V&?*P^f{5a*(;8*)~uk2b5kw&390m0kJ_esHm?8b$&q2E>L|9s^7fu?jo2@({Fpw1PjChC1~stG0(iI{bfgpm0|RK-A5=Yq<_SRK z&mcBvm>#4Sgh6^i4GU0j57e#%jUj^?nxGB~Xe=5uZVl@FfqH?UL2*zouoo2npq43U zd>GUa1+`8T26VwR@bvr@bO;EQJG;#_W zp#!yiK*cSn!w#x;LDes4EE42l_`x zhaA-22Q{fd{Y6l<3>tDc#{g*`fU0T`8{{xh2Oe~aDX2fu%E-VV18RikGBPlL8keA+ z4XD@im4Sf))Z=M^4g-J^UkoDyLo*}zPHzTKvklZx1$DPTSKxuNA}GbzGBPk!gPL%l z-Vp->LpdV@1L%S`P(v1U5ge$71l0|o4ox()gA>W<4VkC}ojwcd)qvXbe;61T!jZaP zpezXL6@j{EdW;MVpcV|M(E{qSfqG1!)(i(D1HaJ6E960d$EW zhz4O08`SjxIT_Re1CLS7dm6Z`P!46Uk@&l-w3GxTX51@Hf&`1=h z8w%=@egSokK+6{x7#K7e85m&wRZzr$*q|0Zs2>YT%b;)r^>;z$vw+8M>KPb7mi%L2 zU;wp4L4gUn!4PBtC`3TR3ZNk`kn2Ghq?U=1fdSNY{0|zXhYkjTocMj^Sz+xEGEPxpZ>&u8RLR#^lJ}#*2 z0!1CDRsc04Ks5uXSqhQ^iAyjtFo-iUFo4*gq6QRwAjg5Cz8=)o2Q85Ub@xG*fQAA< zVxZsx4GDmT13<$9AU;eSMyoM0Fn}Bh3wBWOf`S|rx1f;%P;Y;rf}#ZE08r~36wjbw1VsnP zP*4bgECv-PAPYeK9FQYGw`+n1UqI0Tax^H#gGNL_eI-!G92Cl+_AY3oGy_ziW->y$ z2_PSX;tXaf$af$EL56@F1ab(-A`luR%=_P_Gd*Dg^QbXoLvVvDylX|BZ|c z3?RvMjF6=X8=!p99igBhCD51$sNVup2O4SusR#9AKx|N#62u3wK{QAm%pwpUWFe?s z18Q!-+BBf{3@CI0LGcd?7EmV$)ENSGia?zs(BKMakOg$XD5%c@y44cY_XVw{1$C4_ z-3d0(g_WRt^+1a|K@$T^3=E(??jlA8hJTC<3_lqmD+)kGE+~q=GcquMuJ8Q9$iVQC zk%8d@Xp{gH|8GGF2vi(0GBAJ!R6qkPpq?0LY8e##Hy9WgKtX&R6zmKP44`i28Ab+% ztBec`po>aD-BM5|3zV=x)i$W}1-jVuJR<`GXpjb^_B0~{14tai2hpIOJ!nj<9@KBW z#R#zgbc-oyGzT;&@_-RCKmr=|0bN20x|kHyg#?|{4LYU!IjH@@0C4~)nw~K-Fo0-K zbiHC^U;tfM3R)8Z8odMg7R3Gv6$iF1v@AKgF=oQbonXhf=}qMga8u* z10NFu0}p7K3NI4_18BTLiiv>%bjvAdoC9=AD#!p(?@p8n(v(DQm4im;K&^5KCI$x3 zji{2)Q92pWcnE0a5Cde0613O@qz^P~2^v}e=>rWjfQB+b4S5yNSVTS(1L%5D@W5&| z69Ypk6J%*o8WRIU3KL}YC#Zu2@(IYd$xI9kNzl!RP%jTe zgGTf~gRG$ISQD8T7!sHm7%Z3=z_+T#gGTj0Lq4D(UeK6kG!p|u6cYmjXp9py90(fY z1dVxudR`IGAz@=C28J*u1_mGKFtH621A`N2(G7Iy*aJF@3>s1f-JR+UT9yRz1ay?h z6|`oGiGjhHiGjfpG^+!;m=$y3xBnHU&A7r@$r;veJ#YbFK;kR-@b5C&-g(I8DA zHpo#RU!pq{R^_D#6TFNALJkq4T@6G=zcv+0yOLo z(g@>&Xb>OdAdumpAdh2WU;u>_$Uz`OKs3l8P{Kl150V2-AAqJ2K;j@ah=!R5l85r^ z!9E71cu)@;M1xW~XubhNgQgom(+;4*0;&P5J_nSb7$B(_W++GuT@8{&V0|DyD7ryq z3200^1r-0FLZT45$PSdnKv@YibOaiK2UWWz&|xJ|zqE{rf#DEn^p1&vVFhRkf{B5l zmWhD@lN}D2wk28Y8--w4M6-Q(8&|f#lCZ(lPA-d7#O;k7#P|?(-2Gy46RHI;G2R$eF)IS zz@STkCo(ZG^e{0nbVC>D^)WFpfJO#-nHU%*z-D#&L3xpZfdRBQ4^;L~W@2ENf;3YD ziUwG#1~lq3n~8y8783&lXykW3Xb_eOGRp(HJs30<0jj_@GBGf0Wny4h4-FmA1;LG`Ipa6f{N+ zsyDVUF)(aqVqgH(8wa8D_uHXrc7dj&m>3v9HxGl%0Ywvt2F>$>=KJ@8;(rel0|V#^ zVz2~s8W6+=`5HunTn?I01r3y+1x=2Cie=EXhR}rppy5c+UI&2eiBgw9E&z+(!tsh7Pn$4K(S+0$RfcTEWH4z#srx zP{qu^0NSR)4_ZjY%)kJeujGKP)!+hM^2*G>0GcQRO%a3UFhO&fpgRCSbD5y|aL~Ls zXnq_t9}b%vhuHx#2c!qI8-)Qhs}H*S7$ynY-vZkA0z;fDB(0cIvhKgd#$enDmi29Tpc3!6Y8!3LVu2ZaF05gBBRzZP$1JVT20Fwvt89^gBObiU5!3vNZ*f7ulHfWtBH1v?ol4XXh6aeW3 zsfU?W4-x=vr~obN0xjIw;&_L=zJ>vDC_8%xQfE0t4 zoq_T*XiExs=@~Nv186G>X!i&xuYU@A44H5&X z2dQ;rhOFgT0G(E_0nNUGrx&7)%wo5<6*0eKlmX3O@iQha$;Q9{3gaht1CM9Em5l?-7=qjp#WuZf zH?zd_Bh}1OOlfS>A5=3-m?^V^ud;1;lM)qr*XDo=6JwkO#N&GG;0;X;Yw``Z51@*9<^e1S+N#yE3`>5Dk0ZvdH&)eJ+384!DAl^!Y|;8fbl z#29A=(fgWn`o9`x2_`G9=@PZf4QB7SAX}3?yF~kVn)Wk*-2qB73=Z57e_ME+dS^6S zssb!ysAt5$;LSb#TrIN^Qz-ZJf7h8Mrc2c^OUbbCfOm2=#1y!_@mgnp4HP|k2B1(~ z!!tdgj#+{!i+6ff9kUVBX5Q)Z>X?0)F7ZuQsAratS;Psk$WQ;!BFR&SMZm#m1kwpr z!IZ&2-Kw71i0L)w^t^hA<2yinUBT&lK(wmR^ba7~SZI1r1G5CvI-%)yAo{e>^ncTs zC1kw0A;FPh8ox|m=j~KP6hd{#JQRX#ZjL#s^Fp##-H(Ye&Oi?wlsUrF4>T}K$kYl$ zQcaA8wZk&)+BHxWh71gS!qWw&GD|QS3r**1WR_r>EizrBk=cmpt;qBk5X~<-eF8`v znh%(G#ik!=WHvIB76b1lZJ7N*c=h2sGkL-OHqbL+V3;ljNgP!U+K-HNL%5+b1`G^~ z#ir{tF-ypBi9=F1yM0dAE`@z5&@^noz#u6;J)wzNf(fdU3Cf?oriodKsZe72y1UF0 zOuB;8A2l%>$xM@irm=)K|9?$L{024#oF#dsryDdgOPB>qL(&IJgi**@wpB+FDR`+g zM6Z^<$Dbe3BA*cHQ%`1kT{E)}laG&kU5HcgRkk)52`TbVzpkg%)NBGgdk9R=tL6 zn{Rb96!C-745Tc;ld&@7r^~f6OE76DOt)xd_5sCLTPw2>6N}RHT_D6pzUDW7=}&Ve#yXHI4H+14=rv$q*rPVR4`liyWk?L4e#;u+ zsJ7n>9B2lh?Dt4{`k{7aBN=xMNGkafE6M)-U+XS#1~t$#Wneg|Fv zx|}_CHf{!`Sx^yT%)kIzH_64o(4g`0-uq(l58I(R!jOR>OLKZ$2eSlIx#sjU6F{Z8 z=JW-ZnI)KdG^elW0F~yN(;tB37i&)c)xn&`6sI#isgv1<$xwIthE8T9nYX$Scb_e> zj1;r(*#|8Lj2IXi^rrsV31{C zXt3^DbjL{X=Rt5l7=sc8xBm2VAQdeJ3=F&s3=OLlBNqK?{jUPG(g3uAb-GM9vxJPW zAtbH7=24L3Zc;TuWT-iY(*wGhC76~QPM6uwEFp7=kAXpifuW&q;ik~PwdX^b7@@Ts z!&{?iN0}vLxQ!vssav%{u{+pwF(TP(7*CJsW0qiYHlF^bo7o6d4=VI9OPFD;_YCxm z85rV>A@S3_<;ki|B87nnv!FF1(^J;z9X-rGOv{a@U+ZBuGQ*K73>X+-=TH<(&uJ-5wyAfR3e6^bXr=K~EiOG6;$^_;#XzDX! zG8URPky*mb-5L_Zm%^_;y8mgy6h!<@vxdai_vmY09{Wl}!Hx$TX)H9|52W|24I~qO z36&2!u)mHAoPNNikE1Q5$}$tVQEnNyTn_9UuuP=w^f?omC1mn!Az7@jO?~EZh6j70 zDhv%7cH2(BFp=3v2D6p~RpW*R3=9_b)8!^H`!G4%PtTdeEFq)o07*E~d#}syYp7v{ znq|Pi;N>uV0Z2uZ1H`4;U6XEf%2)G4Go=v&Lx#ija~qi@n2H>xznjD?VbGL)*OE4XFm>xEnd6vut2T0<( z@%QJ6-9~+}h>(Mo+qWGc(Y5LG8p9{tXC8uG3@(nJI81k#!Ysk`-C=sd9A?q!`BRu> znH;#Mt!5TwH8f5DmLK^rW_3=Eo%)8nQxOPCo$Rj@Q_Iqm6WX@z>(kb%J#DkJT=-aK_>{z;HC z4Zy7(cgN{#rZP*&1VB~1-S9#;c!7~V!t^M|=?_4rr$b#PX6LV4x01;m5p2a!y=h5D zj^3@8z7ExEz`#)JI9(6ay6AF*v<$XsT6+rrvN;8^AJPt-?Ku6xB4*L)<%V1vD+b#O zF7~cFPT#PBS%T@A-Y}h6LWao+qT^=Mmpzlk_~(P%VFqr*h&WB(GMzb% zN!4k(%nW8DCJU$O0W+9=Ky8G1GnkEJT%pZWq~@YISR+FQ|MWK?9qvxk6=pI^m<2-Z zINj8Hw|S+Y4#*BYu&a`tAc^Itm1g+5(=l3zESlpqJ#QwnkxZ)-B%F9v_uIH!)Q2Tn zLk5NiPSf|yWR_r>p*dY?3)L~2~LJp$rAo8ir^pvmj(YlAeEApMV6dB^JYeHFoRp$XFaFio6Rg? zcEc02(VU^d+VS!82@B@^2A2}x<}2PDr89@w2;|ZP5DkeKF%|=cU!K#~%|VJHCa3AA zHZU8>uz5ksu?x15*Rx|vrXg$*_L^=mmsx^I#cR63c4mp`NpqR~WQx2Xsbeo=)7fe_ zdr%j^2of(%UeoQCGfT+K@`4l@5j&(sb8eZrf(-;0VTT>2&105ef-0PDG>=(|NyK}) z-#lg`8Qdv0$9wvmdCW#kmEO}YfVB5|Pk#mCPxGEGH=kKTW{Ee%zL^?wmv_r=(F4an zxXl4g-b|akr{~OPmSAG@n%*{_c?(my?{u35%swXjd?7jfG+8!=4{oW5oevxLmjKuA(NAf?K-J3iq7I9|ZDF{Dh;GFul2K9r*2 zL?9$tw+h7MwZ07dCy*GG+;;j$L>g%+5TxPSc<5{>cXbKS<0NoWEMU>WEry&$m};unYE^W_{+=z;_Lwl zZjYG$0c0U0A&ANwGcXX&=eTn$G=noi>sThksOfwwm?fs~Th1&67lc+fFiEB^$LSg? zn2ngA*_z2ZdU_d%4=ur_A6UUG10A>VVS<$3;C7?iN@gP&tThw3n1$NS1T*HyN@g>r zJu%b&tORw7A(bJk0Rw|)>~w=w%o0p<9Hx7%Vm5*odTleArDPVyLX!QgfQz4^&P{`~ z*T9jxDt7uAkaBQCLjvRpsnyKqWKP6Ga!^l}uK46x^Y0__G0Z(s+6;G5jytv?wX6}7 zd&2YupxTzrYx=G=%sw)g6Ck-G{gVE+C#`~|i00Gdgy~9anT?o!B}@-n%dBNrkqA)< zJ3#FQZ_|Rw-N~0AIUUpwc$@&qSN+TDOG9-XKSL+3oZK?&8#D?JJ5(Adrg$X3r z&Pkr$w~jeYW(u@NyZh3!q!q;F!b=>qFP!*(gtwbnBmG4-cTj|1_cRUgw? z&*^JGd{{HIPk8!+^~`BFS}maD#N;$RX#=wntgQr1RT4}P6C0SI&1NQ;iPs&cH*91! z!qd=()Fcv2H$11y?PQjaDRO`u=O(!7FYh0Y$Dn};P`Pfzz;G{Py38hK37MZ6kS@ZF zoH@$MVs_rp5*Cq$@ifH+Ga)72!J5~JEKA+DLDQun0|U&(a+%Y2Y+^QIQqP?J24ov( zpBHG%GD`iRS>uEdPznL{*AZh(GS-=p*aFV!p`s7{?x zwH7L4#J~V)vxrWQ+`}w2efJh-SsC1o!Sc-M7j`g9Oy}9kECnl$WNfn`AyvHFqWkqO zj(do_)ao=nW-GG<)4%NLWm}nzWH3im;y`KFh=Bpxkehx7WH4-~4OS>Z%fIR0Rxs;N zFW$y14=NcStY?;-zIGe4G1JeC>5sNC8!STcms(8!(s zZ#%OTEX__g+QFOwE#@HM#A?pK01XRRLYB$Qha@)5HC>l_UQO5vO)v%w4A2D2)R;fr zVkfFYp{aiQj-AX>*oIO~AkA}VyyNl5rGn{7yO@ojP8I{z0j$#tcQG3>LEEO&_v~Vp zlEFP{39TolbM0o9f)!BH?RGQ!p_f{qZtS7m%nir^BFh8|4RGl$I(_*bW?81LlIaij zfTj{4v>AGDn*}s#Zm4I*z%Zi}l7yJor-_uSP65>#(1Dw!rPKBHGE0~pDuon(n%c%Y zrkGy^Rol=p@$03KrkJ41>M)txme$Y;-GBi$aRM61n6{TW4c167n*nVCEJ=y4UF4G= zjOZ?YEQK^?3w`$arhXKEhv|<^+!_uNOft0~>3?a1{N2fFU{l$xH5qV)b z!6B;2<kmU~4|w zz`rH_HYnIk^vpqnYdw{a5!N!rv$Z`><=;ZHy8#2k^2+IN_A?tX_2^AkIKXU#XY9PI zYI@!QP%otlQUmVSnSIMTRKg2vmJz7tTU9lE(E(;7&}7dwkXfKfX+;i*XeI`cev<}1Kn&~|UnI&Z2*Faju*76>4Rg=6x zWwxQ75vZpD9baUMtD8RI5VME^s6@=EgV^}Zp)X;})2pBY&q&YERL_`UM(K3EL(CFp z>+2w)xp0%?+56fjWx=5dE)oybLHs39QMhrLt30Up1uCx%7#J?qO^-RmEWz}sZhGAj zW(lZ`5;8yPAZ^Di(fSER+vGs4eIv-&CujZib%&WHm_FA}zX#F@aiav&f`;irhnant zdK#xEfaK9kWP<5~s+YOf2=To_Wj`gO)`f zI;QuX0FCQ*OkZ(=IgN?GbGpDuW(g*_&gohwnT?oMRZWir(R!WJCxK{ae|h@7lgv^~ z=AF}jfdpMUr|X?!Hj>Hegp`bVKP`XwW(7%uy$v1!nC&>d<`lDpOj#!+>52+$++Ee5 z2cCS^GXWJwt)0_1oMHxd071=O8Fwd0rVYLMP+Lgey9(@ma1fsEoc<4F+tp4621y2n zh8+&`&v`TK`HCoV-gQp*In8Xu^rLfn!)Z`a2-YDX^R5$Oro}YfDT;@MPBJmp89;1@ zm?nd*5mV{p={+F+pN{Fv&M|ALuuXwfZF4}!C^IlLBpy;c>OMmf z6dcfo!S6}a`OZU3;5!d$VR1s-f;1Zo?E_@+PmckqgrovdRzn7c6H}*800}*sI(^l7 zW*;Wz8Pk89hqO<0E-=qxYS5d0;sS&Zj$vneNaplkd$IiObxBij3kO`h|LK@+aFJPy z>)cF8!j_*kJ>?>^kqnLwyfL^M+H{fGhiS^JX_uIdWVmKS5>G#e+>3?9OF`qb&{Qlr zd%E8xW(g+E+0!#FF&i-z%$`2y60?L1_?T!0hK7n4i>Ju{`wXgUK&^iR28NS!re6T* zxH4z@t4qv2q|Kv&8WoUvv>ed98J>BxcbAzZ%ob}xf}|kye8ltcqIz(UfNK|-d5|(y z*E_kbBh$SQVpi1##}+@Eg`r@Vf?Fi=i>BYX3Ys2S1eqS; zvaxMk&p2TvSOvI^;I(MFz%@`Ex@fxAHD(`~(nXLYlG-C|J?ZpjP@mU8&sfirfnma; z>65N88-ZrnC%j}9n||jSvkWN9fYaXMMUei>=7}4bcqg0#t#kmj9LyOQwl12kcO5h{ zvS_-;b!H=`3yY@LTxZtey0?gdL5qQ*!FTcW4cD1{pz9rsm|T}k-|&Q4g2`{mbc5H- z5=`++rVBh~mS9@BWO~C5W(l){OCS#X&~Q3HOYJ9UG6Pfu8!<54SpunG@@KZZSR!`- zGNS?>K6PC(eZn1PQA<+>hEGc%4&`&0_DZ@sI072(h71fWOCf3eZOW6E-#V&&pkZXl zz#zPIy39>x2^p=Wkl1{)-gcqL)=N(UO7;nt%sC?Uq4;sd}-$SK_)e8Bkjc7#Mt)P4BtQEFqJ$4ARpoaHv^* zpx&Afs=}Osp=#Om10d6>#STNfS7XKy;49}o0`@kcfm2v5pFI2A)1H;#4(-%Bo7Lzk3m_~e< z0+&y}a33CJqVh%z3|Qmc5Y)nkE%`u?XRNV^qC;!x^tyY@5;6`eAtls@?Hv3&%3KX8TV5ruW3??=b``4Ekb$9S^>n}c%n~xqt0Cgfv4nMGNR85kf|in1CpFu;NuM#Hi`l#k2Zh71f)mDArm zV3uOK3C;nctd_8dmXN_)6o85sh)!6vK}?W<1sGmQCP*ktK&^#q!YHs7uAJ`hh*<(& z7C zmV5>b3^148+c>=jRP@41FQ|)T{FXp;dK|G8^DFWT2d4q>xELfd;ROraEl`~>KCU!t zz`y_tdab3?XFX*$V$$3){oGSfV-!+k%NyXxe&FQ$ap`oKXUr0~9B2$rDA*kU&So&D zLW2oj-@pQ6{?h4hKz1zL3Ym_PaceAlaf3-790=eN5t6tiU?m53yTDy!Xp~KFd(JEc ztI%L!gc0ws_=MIB@NysKV2C)lr3e#3_XErb9P-m0UNZYZ>vLGWF2S^P$MkzInI)Lk z@0kAMB~p4rH~inu>3?1^OF%q>!v-d(BAlLt$rErXJkx=T6eF4ayCG#x#``VrgG@>^ z!BftLpgzikMbrJ>fYt?7Ldq>e`z5M1&4N+u97I9XH;^=bt`aJ$rRKP z;uV`d;U%-k^bK#AgJh)lK&+56c1wIwHB|{3-v$f}&@nXQx6I~D8^DWmjF>j;o4(*J zvxM29eGpyWE+r`Ubuy@fbXn+`=ovFyg31IOx0$l+(=2ek36XiUZ~8mX0-sO&rW^cV zmYA;aj#-L{dH-~qcgzwppko4g7#JGf{7AYRFU(KTddCx4x)OBFG!+Yi}poMqe-ZLASVV!gU=L{Uf!G;VBkCdm! zeE|9PAfzo&U^)LvwaOgOA~{fl$bf<2;z3Am$zeHs?f$wwdEmYoI1k)EIDO3rW(k>h zP!$o|D_?&Seg|4M0-b*Ub8z|tkc!hnkoMf~;5f;f_qsrp60{5Ply$nuM`j~4opd~L*6~;o4=szfA{Waca(rHMi&qB|DA&qT%%}3CJX+wzK?YhiM`OhB) zt+0f4X}Awh-|!Jsa37w2>Las{8CJ6_^b8po)(JtH)t_qCI_wW*Y6BOk;6C+fq3J%K zurH2<4j35=O>g)FntWoNKI;>+i{0%bkd%XC2pH7q!R+rs&4ToI(`2wrae({;@~PPL zd7qh$a1AgUGBD^0PJi>6*@!9i_;iIY%tlO0PfQO1(Wxh=cYx@;lhgNrXrI&5KYU@9 zFv~a%Y30p5sGutU3^XA!%|1On;w!TeQwHnwp0CUj zGRsaw!ZG3O|6B#-C^v9+26u*_L#k%D$2Sh2hBR(!t_f`XyT}Z*&dfv)oSh*n*NtRm zLsd-FkLp$1YHKOL2s(9#fdOxjyO^37DpAlc0hwSRWe z9h{($fecK~I5&OWcV;7*%jY3!US&h{{-C|jE`U@(N{N@}r~moREFtsjJft$7*Y~sX zl)(XTa|@yZ>RvOi3y{|J&MnV#_>cSorAkAHikJ(K#^!Zfn`p7Kc83tA=U6gB0~m-`%szZ+1WEAC8o>qvG`42z{g^Ok<6Ij$?W9S>FY{Cqd=FZ-z#C3khyo2fkB>up<$(Q+4n1+ za)MBI8Zj{Zy*k~moLOSJRw=Uy6W_Jz38l;uW>~!i9>7w*1__E;NsEt6-{Ub66rXzF zmJ!rSvlZ7MF@7TI*^{VqasR;8ped;ABIpRF-Pfjzl`;Fs;I;+o!T{In5O?gln^h;b zr4Y0QzzCAADX<^AzroRo+eOfbmT|fXanU^FJ>dAlUJv|`^D_Tlzel#&2MHSru8?c7yM?HVA^?ede?7eBgP}s_x@&fqh!hyH+;euqu}_J_H_?8r?>lAG{t>4IId%%JHdBRw-vjMd+R zG+f@@>U*|f`@bFFxHJLD)P+r7^OreIrt?0ePHXY!$g^pl8N~$J90gLqaPaPQy?@LS zGSluu(!*V6$2)fef30Lkwud6-1K-B z7Rl*i|Cyy2Z%#M-&uq;2bb2i-izMTR>Gl7ajhPr9OkcpxA~F5Ue`YBeo(GU>pSvu* z{_6dI+92b>W4H`b52iA)7|EzSfcSy!h2j}sE}o6xb%x;WD4i-(16Yh?44?{T*XXc5 zJ^cM4*bCs|+~&d5c`QCmUJs_eVKHL-HC>*OMUsj2;dCoT79%v*E?{J7LK6yOVljcb zeEJe57897enN%K3|H8!5zyvlP6kjWuS&U)b5Sh4Vkak4%mur^ws|zAP9s(yphV*CC zg;>yShM5Wujp=JxSWK9}{*svfi-kpsY32RtVyrA_Pk$S ztSmuL)lB<*r)RLS7@1+N=)3h25~!>XAxTG{aq{%I(}`c080$byDq|CdP2STlu(23{ z9Kga(yaQ&T+imw6(#Cu**@PA zSR|O*UQfTl!D7TT?e%mXP8JE7wXY#b(LM9O&pvKTJ8-2BYN0V4cs<>LlSP8@^z=wh z7GuWS(|b8tB$=chOkcstV#N4r`fZTt@9F$pERtqMZy|oaX%jj%`$jdWQU;Aa7%(u{ zy@fOk&i60>a^=E4&^k6_$PN9&m02#jg zEhMOZz4@2-ZS}#E;6fhU0on5w5>2|{;k?mPt@eY13Op@*Y`QKtizE}&2pQadd!#(Q zhMUEQ5$c+EA0TalwF|SuPlwupCcTXGz$F7@Izd9F{Uf9be|keR`@YRIVQ5WZz`!v5 z<8&h)79*LkPmnns0q2f2XAUodZTc}}U=aU2y@7|th)MJF^f$aL63_;X#Pnx8EGA4Q zpQpz%c)t0!IS z(-BkqLU5(GzA=U3jSQ$@s;4BU|(4(=rlaE2mT)T;D47%m4G1ok}rWtPCLF zGBI4fev1_|h|R#jAi%)Dz|X+Yu$Pg6ftP`yp^XW`U&jcc3z--gL>U+wO7l`OlQS6@ zRxm@vONw(clNcBndmh`P$eqI8g*t=tgvD-A93Ku)OSW?-lXS(w5D zv1k>P_T^z<5NBX$aN>bDEQ1?jQ6x9S;MC&e#GJ&G)a0DZ(o6;hhB`h522KWshJ$<% z^<4ar5RBx9m|L8go|l-D%21G84hk7K9iaF94y_GV@Y&OA8n}g&_P(LX$T!s?~oKW?&FuU}#9rNX*G*U|?t$ zf!LFgSgf0tlbDXnebU?zV~SIA%TkLN7<|Ma{;5pN$;m7(Vc>@b91B!kkT^uXC^a!f zw;($mDn!~>5#nK3h+pM`77_-f#SAZWAr_@pq$VdaFfbg_g9OudJ%~f{^HNJP zb5j{|GjlVOb<^@z>wyhuC`c^H(9KS*oDbzs(}OsqQx6hdFpKM;=9Fh-CTB1(Fk}{| zR-`i2GceQ|LP8+G2%-UIU}|2OE~w1nF^0&ub3q)k+8AO1Ea7UIKoTr00cREKRwm}= zFeDbG7bF%Hr_MKqI24v67MnusF*1XeWfjnfIc5ft*lh-}2$nVdxfmEU85kOBEg<5@ zEFcD^73Jp|F)%Q|e3)bbNiVP{$W6^J2bB;nEFtP=T0uN=&k~YQN-~P_>r2x!7__V* zK5~Z|pk)Ec0p&K3D9E*eXfU;fs4LGeN+~Z&EYP-vh$}(Gb2IZYb2E!ecq|wgIbzY zF*&~^m4Tr+GrhPZzbN&QDJJewN-Ze@1#fCmk#2H+O6tV` zNbnqnsH<-{8~_Q&tx!5OFF8LYGcWya5JaJIFeH4Z1wquU34l0cMgSz9k`t2?lQU8o zri4Ho`Z5IKp#t69!~zBe23#q;B?MG>)H5`s=j4N$0}Ri@AR2eMK`f{VhZuM<9Afd2 zFo<|o1V|%8Lu>@Zhp^fdR6T=gXIL$KI})M~R*P*3gXn`rOuH)s0~e@0(H#IW=tMLm z=!)}`vqANLXbi;XnILa4Ffh!GhJ?s=sJi9R5Ce^&`me=8Qrtp+NJ`u34{^Xme~3C~ ze@Lwr5D!T}M*a*8^`MrIA~fZEbA$LeDghF-tk9JB#0`?NB>f={x}69KsSBOIOOiC(BEn{FfnFL8g2a+Hmx7!usz>Fk_gTDJQ)Pw8O=P3{a-}pi3 zv{Z4~N3sSFGZ`_mvHu_XdIeKZH2D#iT>aZjsQWS?)~?HfL_|&oMBEq}4r0(8dBqK)E*BI_3=9l&+#uFY za)UUl%?;w*N~m~prfz0lT0TQQR6f%cqJLT*SY3TXOCBU3bMqknh{}TmoLe3wB5d;@ z4v8s%Sd^TbqFbDinQM>-;ma06^d%P;rxqtOFf4b4=#$8Uq=dh@kkWagD=_r91Yy|abX0dK^W=avm$5KcLWP%zl z>8TZpkX+tSP?Vorl~|MlDE$vj!5TuWBHf;aUwOEDzT}oV=+95(J=PBdL;sp*Sb87*waj zs^-k}y!@h628LsG5C`X`7Nw^eF)*C0hj=ivSQn&~p{4;6R6FZIiJ_jMAuX}Eq>zDu zp(wSq7}UOIY=l^Ns1f2Yz9xu+R)RG!G_0h<&d*B*w?!C0)hehe+}{du>BnY}%NQC~w?Pc}+XivKL#X(< zW{3m3n;|}VSOv-6N2(yH>lG&hgCYY%LqSn$Nl9jEQBG=FNk)EtwoDhqVQgIx2Yl*e zV5kT61MYM}1Wt8AvctAch{DC45R3XcAr|B_Le!-e=Va!UFfc6XgE&CEAL8Jg)G}~~ ziMJmj{t7Dpr4Lf@rKF}7=w=o(OznesXqOv9J-A;3EArzeLNs>vL4rP|kAXpzfuSL+ z4-$mFeGFjrpomP&DQ1AR5nyo)>+f8G_IF_Yo&w#R{N%)v)B@d{%-qZphNV*=A#K|S zjZ?F!5Q~d4bBhww7#Khnfb!kzsSuxK=B4Fire~Bqm*7m_BG=0Z9~TYDk)E$xN4Kgk7R zUr#S2Ci0;AiYxPyvolK=?B+xK(KsKXFTWra9@a8HxDb-W#1=!$<6I2MG_w~&9I$O6gjQYxF%M!+ zJ)&!Xt4o011>k^&P{lHcMbV6q5Zt>Ql9WK@mnj1SgVhR1QiJtrH!O!Z$Y&+QA&*uw zFbINL3wCRuE?NbVNY5`w0VM(PRS*p^s~{GofqLSJ$r(LRbw(>8;=1b~A?LCRQc(7- zgE;8qN=T-Vp zy$V!4!>W3SPd=`MXz1Go@!|cI5Ff8w2}%9aRziHR2pY!|mP6!gmqUU+B{iuuoq>TN zX*ooFQEG91PFX6$oV}0{s7ZSv4o%5VE-A{)OZ8q3vG=v*K8OM4%OO6LS`H~`d7u_q zF+xh*JqI8@;5Z2J;hF=Jr9{l?8IM5R8+-&3Qy&gP0_;ANK7SaZy!Lo3=jv;zX1u+ zs+`OuQ2)>4Cd6X%n-B-7K>1uZ8O*`W6Na0Rpyj^}p+j#&EGQ{5F$0xjnZ>%LML7%% zUvEGX>FXPlH;L=j@4gGM2iEz4b%s(a3W`#Tiy6ui!QD)Tw)+rmC6xuKpgP|10mK?m zJ5M(yiNWjvM83Erv81#(wYcIwBn9N$hnQcSng=dU6QF!shCsj0eYnMK7VAcH{e zRD?)0@K3Ikkmuxr@)##Cl+dpayAQE+@l!}(&4SV%P|I>tOEU6PiWzk7Ljp+@YFTMs zL1sZJ1H;FAkVs0-&(F?GWng%E58}8>_aK4$_YTApMTzCQN%<+2u)cCqWeI3Bu%6-Q zJxESWOioTMC@E$@*8pm3W~Nm#n7@WNRP8myAw_8^;Nc1J*O2^BkeLS>$!1`B3o-Bd z8;C=Gyn*P;gYqB0fuxRuZ$PQ0o}ppw8%R2w_6AaPwZ4H^Q1u4la`x8{A8LJoi2r#7 z5x@KaLKmfidy))?KS0z?`Uq(-ynF=-fr3wv^bhigZfQ|w>L-Z4;7<$;LdFaX4VPX) zEKV#d%LIAh&?|^Rb6-K4a{aF$@m}=`;_}HG#1t%gZ$Q$b{x^tZ!wrbHiz+}#n}MP5 z1|(Rl86d6}TJei>cj*?sNrna-Zu9T1S|q;v$7BOT4Gne{&)OY*Hm4V#o>4hJ>r&=K z{it5Gt+tk&`8LO11*v!4hW3Zk_cZm*@eq?*z z0d|4O8%#V{-!L*Tm`|?VXT!q8z+gRjuZ=b51rUpYp@C^~qnkO04l@ITI|D<*^o=@9 z29rOSa4;1!PyY47j&(aT1B314T5D_8FU$-KMw9<0TXU+hFfh0!ls*HWmhksL6ZNtvLl)A=1o~4|bSyhOja)c)%2M&S7O>&}Cp~;F#>V&z$KY>*QU} z?Kl%JZ5QZ@z#Yp}jXb91IQY?FVP*fANhPqs3(V?Dvn zzz{Im*3_C+f`fs<3(QL5U|{e8v(|AiFt~wPUpN>T{J<f}G_X!)G&g5r;+g!*+>R-aXR?)r9p@Gv1_m3j zoTG&~=La4JhA;+(2Ik3(tri@-3=DQ)KOKw%bG#tIYHrSSj(2jEr5&dTAH;*qlN-&= zIeqvT7>vN`K}NOnF)&y$Ff_1FK4@*ubcSzom6aW*I6nh}6WA0-3v;Gq{>i_r?3m{B zPqwnQDaI%UpFa$tUTbeV4icH>RZ^txM zWb!Y2JI z6fL|GkQiW?%-CSTApwbe4zScI2?mBFaO8u`(vf6f07W>%uAFmaA?{_I%;;jyd0!Ty zmmTC_CRMq~yWH)V667ZTa<}8$EeFY-;F#h3F9-28B&^NlA*MihmGTg8vrKNZG-ui; zKiSIDj`N2+#A@cr2W`wb%@iO?z!q^fC_s#61zF2^Rsj+h;FQhDt_X1w+vG-TbEaO! z$-BJlI1ef^F!+Hz4hl+sC5U!*P?5wMti-@z0SY?KZY78%Op_TM%{g8vL5xMDC3|HC zhB$Cq0+}*b8R8?R$&S|MtXGx6so%z$Nl<0-FCRN5ZC|4Re&AmHgyID9dLAmBJhzq#0ijy zkkWtz3?s;7rimJptpe>>Pila|r`FP%Q$Q2qdX~wI(H4xFlYa%;v7XmtU~mB!^uk(j z&F1Eu;aZT~#0pL)Q?($ukZH2x9SaUEh({p_Ls}c+0;b80uI8M<+7Q1nPCn>v&N*2d zR;Ggj=!rHYLKr49TAOpq=|Iv4#FQW%NXmxrmgvBo0*a^?I*`y~o9wvGfoI1 zgR@8%VjVc;v2M}@Wn)_lYmPs<5ceYD-bWAOcSzQ}ss{;JNU$mEL;T7DHZxHl;yrNA zW1X%K3O!KKa9$r`7uaT2egmjDhr0np9AQ_x0mP#a(+?WJTnzFIvmwMY5O>%cLTqLS z2iGz~h#Q$eS%%{Vlm|DLgUbk#HQ*wgu|^QHAvVo7fd2i6TB9ylj{Fon2_5vq%M{a-$6?r zOGps_G4qQhBn`5Fe9oz1#lT<)vBBJ&Bhm_DCL*v`SV3G4E-;whSxv4=v}3ik2Bo>b zf!3VG)(~GZPImM(XI*Q}zz_!x3vL@oH2`rymklK4u}*Gmuwb;A{42?h$;)=KRk9st zt1TpG!FhmbukGYr$#zUXZ72Uqwqr83n{1V0$C+)%z>orK1aRE8Lv~`pI!+FIhza20 zi6h1y;yZ*7CfLKA2nwfD_K-FWH#m|^93Y{_3=ZF72S~ht6Bg@92T(g|ueCL+q$4QR z*jigN1v^gum1f7;=LoS9tdR4VBg}UPZOoaJoF?x|w_}>=H2GJ$9p@7#1_nKF>B|^o z&MM>#DsTTrTeJE(gBoO@lsU_J@~;d#&YRA#vY^r0oK@R}fk9`oZHhH#sSCt#rpb*x z791{+1i&zv(bt?))|G)lADq@1W6U|XxwFGle{5iA}6T2;=JY! zv78+gTTDVe;I^lq4>x+5ZueneFaR0N`N;c%u z7or#vjmLcuSxpB#42*l7XRtVKQTeIn(nXkRDF^U`Wn@ z1bKZhOe?5TIvfn~1f*>G8w@FASirTmZwLc}IRit35ZIZELm(L*94M?`LO_uNN;yWM z&@{xE5(-lRN~)_u!OgiO8;(#&5JN&iD-6`9044CIu*tv5?U+7=O}47A<8%s#ID`{i z_;-gxi~@%V=lyWFlLIX{A|MKxKuLzFDq?a~r5)!Us2~S8E)pXlN;p92fpc~w#QTuS z`(-4=HJp$fGSClfQR_8JqeO*xxj_*^CU=54BVpS z5Ko2#HNt=1$zcCw+i)bqyys|Z&UrZ*;t+5^amuDZ>;@-S&gK+IRlzj*ppOMd3M85# zQUagQX%YxVk z35ESxkidf!^qkp{fCZO|9M0JgIfzp~m~e3RW*wdfrKX{!u~>4a!zhMW5JROYDj<@nx(mLE$-%=dvjr0Kry~3 z55i*x1sUt3JZRO)sh1B6b4Lqv)|Pxwa`|gz&3P#wo^CD7Ih6_^o@ANqSZu*k0E!rp zg{uo7#V({+e+*Rwwt~r|aPqD`JI<+vu&@TT98MM@d7neD2x12!uo8!kdzTy3~ozUSaVJM94btk~T?$DYko5n%6k-7jI5(J=LBawOv-xEZ zGa<3J6DkKzBb*=0AXPr3=8!LEVDJaG4H(_bIrGXP0nG?%ov^Mb2SsgdsSQUt#BfNv zK&%3imB2h!j|xyx1&WR~xHu>@c2q!|$1&M4$bzE+lD0U&snDzvW(!CmX#O2k5pqvv zJY>O93Gpx_M}Dn@*bC`X>sEosf(ol3`HgcjqlG!AOf^Ia#NQ!M9ys(^d#XY89jF1Y zuNtBUl4DtFAbA2@#B(~;Kw<-|hqI^#;s9{SF|pN7-Zj&X)1wwiJ5yinWUEv~861vh;;^PzIgpbj_F zmHNq6bL==d8X(Sv)QWBmkVF89_O=E{D1$ritcMyH80Ye9#EXoeP-RVS0vQDAdoOB&q(?~F`P2jnZEzmr z)M{p62nG-IF*=%aPHl$N46LBKkkzII)bzHsu;%P(frJbb$YjoIEf5FufXhdXR!GZK zc=ACT3yxMu^$5;CtfyK*4L@6JYt~<_pw`%48*5g*Hc(mvb?$Q7AR}BHlMi~CbM9$_ z^awaXR&(;SL%aeB;GlMhO7_VI*IKZ&GcdSPIIgcW9Q_mC3dU^T_DfZF0p29=we_9nG9lGfs3hh!^A4O zLFF7s_DDBOhe!{oj01@!^)N65f%I~2>4DVptdkG6S}^ub{T? zkdljOvg0gs&isBzl4Y3eILDlGem_j5<3bA#CRW5@r&gIJs)29cRWwm~oBu790~HiG*b`V~RQFlZgxrMc~5XV4DTU zBuGsMZf3F0nglKst*x1^Oq%>_l^qk$ePslN}GhIgr6Ol^Ku#g=F!B84%}jPCl4y&NO$%XEq+$3of>Eb}1cBDgWm z6f|%0udQ~Rv*$tj#w?&-AQQ*@$yM9zSUu;1T(sBTnzMU8B#0nke|$b9>^VVk%&EBm z;tNRqn6?0tWSPL_?1BXlyCC}BLFFJpqOlMX*I*uN!9q}?2L;^ag~$QNy$BL;?2{Wm zSuie|ylbZ&XZ0dTZN&l_5#ijs2vSBt%#mLViVBc9k&7X!7(jzCOv@He{@ijE2zuHv1$p#>4?_Hk0p>K1TN>9^p=7FVzSkKJEriJldJaIv3_37z~DXkZ@M+7?;40Fz-Dt!UIXzU zBe>OZaSbGFIBPe`07p-Gph=3(O&R^>wNfsjKwH{JlLi*J6)`Ozox?r3P;<;{w)J~9jh5n65`dCkH1XVX6^Vl~*(g3&~WeVOj+3Khr z=ZsBAfzJ63h3B#vrUBH%nXs9GAqMOhkd<#X!>k0gjLo*d0u(gXT(Sk`>w`PZSYfiTvkPrip?{fC-fH(zG zzFye@F$YqHD(!@%R&Y3RChdgfYQ`Q5j-9YL1BJ+^o$#>pGUxQz1u+s_X>rck1*urT zvCH{j7bGOXmb2>Y1`ki#SaWvmhKH23Ip?F@Fg>8jEww#xJv+^riuO$2b=Hpaz#d5U zW1if2(46Vhp2=3{?3kkWPOds<$GLki149}lf~?Is75Bm1eQ>8aQ|vx)Mx442=3+)u zbJlr>+{`lh;7JRPW02Tm2DcHsk3k9*W^f@; ze+*JUaDZAEoNUJ-?KF|ejqA;s+>cNGb=8iu={O_>f$LVzBgf&|t<5=EPCx<)Qk?po zfW-?alDkeoG7h-M&vfF%U$n6?QhN5d>-O8aG#iS>v_oN5;!$*{yC47HaIt2fCLu{IDFq-faC;l;4wvCoc!y) z9aGQ6$yN{SIFDU~bQ~b2XkLN^38;-(cnQ+%fXE%Y1Q{UbnCw_!&dG5ZQbRxzc-UoF z&I6Sw%PvC(!65oRUq*IW#uelg{oo3uu!E>ny~@Dg49g~*8CM}G6e9QQDx?5~Gz46( z!QAO+ZO+*P=Yh)YE66-nnd_itX|0ttXUcVCn-^b)uV7==*B3j^y-*3p$&J^|Ip02p*avQ6v1&g7HReDyP3jX^R&R7O zXPW(Fveh>`jfJOQ;0_WE0M8a{=rMPi%WSZU4~_Y@MQOrT~D)0(G~ ztG?TDK6?r&6Tod)PK9TXKw$t^S1He6)`2GMXFY>N5IDR!pFD&43slaiJcpzNP?ciN z8SxwvzmOKe0;n7VxKOzA9InsOoKxflBpow=`dCa!FD6_4vg2I%0usJVpw(4OH(yNN z^~;V^=_RBPVg^?uAuk~{2IJ&|(=0e%!krmp&Uxk~B)>6Ec3f@F$^8mu7UNfQCg)d^ zt^U|?I=+Sk1Z4QN_BF&`ki2pHH6(_>qqnTzUxON!ww~5Zwr?g^{k7xde+#h~k`e;n zLOcTryoqmN#)2~0S*RQ&NO<2tdOu9y8a4hMql8;eg`SVAcZvZdsr(2l&K=$ z!yE^Sg2nG4=>p>LhwmX}2`e~utv|q3`kHgLeqaEv*kT7|c#eBe9wdo?28CrlLNX)+ zxMJ}72#YX~EvrAm#yUYW2A?1?#5%e0yE$jaCs>mUlt~|af(0&UR#D=(j9(}30+YX<*>ik_SjhqE$Z>pwq+}LQB4Ta)2J0ss{sxI(c5p`f z{0)*=K((*_g00B7$@Tyh@br6Sv0ZcP8FfeF9`Q*?{ z3=9lf3=9k)bF`tf4rp5z$V?pu1_m|;a6o~$p!IZMz6F>E4tOw&7``k6xJG4QX8>0U zV0|%Q62b?aE)frPKmx<`Uu=w^J(>&*2~aioXpmme@>viK+Aat33}~G{hz4yD0~rCL zLGqyU4nUp;?fGS4U|<04`~t}%(;)d`@IqS#1_sbxL&oX3?2O`pAoEHY7#KkE;9b@X z3=AL|Bwhh^J7^Cb$UG1Y60ZVr7#J8pG{}C?`fZSS9Y}ooT6RYDdJwk>s=XN`%)r2a zOoOzwLB&Bd=v0nwC?7D4$3En207;f)HxTS^d+c%Fg?ANi%}k&T&_TcK{Uu2 zccFY_8f5)Fs5poQ@$W#nqCw_-g7QH$Nd7aF{~2_814zMFr~rrtDfkBEgJ^D$s~I8rlo=!m z$^=k8E0hnSK@Q@C@{wtf5EqDGU|;~zAp3Zrd>(N4GcYi~1sEZPq!3gAhz9vw9Lgt# z1}T+*I#7xcQW{Hx1VNiUkZ3*z28Mb~Mg|6u%XOg&^`NvqR3SDRBxS$|DGH3C7MX$s z85kIlX^@Z^h+tq~K&C8K4X>?asi!&Qv0DkIsh_|fq~%&lz$XT9|I|3U|;~zAm(up!N9-(qCxh989WRO440uAu0ZLl zQ2H9wAvd5Jk!g_7O-2R=P$IntRrdg@?;(_a1X9GnzyP8_QSb!Hf5OO64{idyf+nW7 zQ2GN@0X7@r~`gN_5Xo7e>536z{Q3+69WSW0|SFSR4s@GImQ{v2hkvTZ>X6*P;q=TNQocR zO~FuoAs|5p1_mS=oNT}XAh|FQ!N9Mg>N^Of4?)#~Xplor zLd9KAK_!rBkkA>Zg0oQi97vFXfdNE=n4m30pt=o2b4=$IVid0jrSIDyIR*v>5DjA9 z1rZDk3?N#Nfq~&2lnBDP<0?06q7%od^|KHDS;G#O6z}64cKUq zfy^M+f+}EUNQPvA(yUN%5DhYd9V*TN6$jBEaW1Gh=y*6#IB+uugD3_DgaE`~9%e}W z#?K7R*-(STkZ4dyh(q}x8pMEARA{ZDLkZF*5 zO=d_zq|FS;+s06JAR3gHt)YA(Xi!ItfdQLBP(V5`Gcc$zFfgPsGcd4Cm(^!f2eted z@t2hkw@1t=ex28myUh}$uM7@&sI zV`vaRhg$FgqJROJ1_`}phKyneu`n>OPOmj&6bDz6k}QxIk_Gvefq?-;gY1!m@dfSy`i)pR6mFY=?{d8hd}9YsD+VGb7GDlFA{ZDLKr|>sGN2C2go=Y`kcBx={rOP32&%3aYH=yl;!3D|4OG6?2P)9O0x3k> zp&B}&8hWAf6QCLRhO@c~D~(K?8C*lwQLENr79T>b8Nj zGcYiKXi!St0p){ekiU0A)$M|cgJ_WWZWc(R>?l;D>4i(I7j7p?nYx3SbddNVixDDh{GS4w7MoQ1@`<5AJ~m>7WO5TIZ3d++pyq&RE(Qh$Css%j@_~xO=z16f8x8WAAJo7A zs76v~P)ji!st=h4nHvcapFZD=krmX6Wr${lL`4iM_}CJLYKQ_98lW&e;XP%Q=t;%&>(}SLG>fkAo1y-keu!-%P2lQ zMwXESLDYl7X#rFhJ{n}oLTE}|3r%qwp~0~UsvnsK1=kj+`U6n)M_9op7cg9bs=vYt z8hc`3Ko$U1!(cvW$mu@Rf(K9yAR6S5hfqE~8svb-P<6;ODCItbia!H2m6@l{m17ha z1eyC9Dh{IAr}J7est1DH^Mw^shkS=hgJ_WQA5cDs1{wPss_q|D92?Cv-B%u3DKmi@ z`=Ano4U&ODR?%seq8>cr4r(2P{DCx00dfOqKQDMl0(2}YNB~5G+)B)>1V}xyI_$F) zpvZ+zTQE<5%f=`UZfWD2kpP(sk_UzJ=*$EtG#Ex_CcweKz<@kc0m}BsGZG*rAU;TZ zbY=n+8rWtkK)GskW@2<^0yIMbYPdirBt~Z@MrS4%7)EC%MrS6VGZdhP?dZ(J=*$GD zHO0WdfILG1N*bdx6QBkb_6Y}&4AMjdsPBbcAt)e6XC}aFtUxmopl}1xppNS3%mip6 z0u&;nGZWBZThNRIC_q3osO363Gch_d0ZIp;wLQ?G|IwKVPzGUOAby5obY=on2{SM- zjLuAerYb;zLC%Z>DA|GZfvRrg83~X$hz}AUotXfI1_Nk@0yGFW;4>58c@UJDiRp7K z84ITC8ZdcJkFa9oo!)A|hUr~KOkvX{Y#Di{zcpg=X5wa;Ze`3AHvI%hWUeujHxnN9yW@iR;>Qeg_4u3--nQDO3C5@eYE3Z&}>NQBpl(VJ0tdZZO&IHSn) zjUY;NI;S;bIHTC~L=YuDeJ6;Lm@a6;7|tj;J=2CUeEJM0M&9XXZ5X{7rKd~UGKNq0 zaAxG4UTe$f%_uwlBuL-~NI=()(VJ0zdZis>`1BPnjJ(tDf&>(&YuYo0PmgeAeDS98N;V%xHIxjpX8W>3GliydNUeNk91`WpWflk$UA*6NWgSDryFDVbPFFw-s!n+ zjNXjq(|3XdUVsE--5I?ZEvIL?gTliX6doV}>*2(JlOTZ~AOT%Z zMsG&@>6MJOV)B;l=39=sf)rOnAW(StFnTk3PJam!kO&5ahcBZyqxbZgzM${`3H${K_)hoq1BFKjC_MZa zy&3(d{{#tWgo47upV6B!aQaGrP{y80zz>jsZV00{ zWBl~W5KwqTfx-hMkT_j46cirOpzsJ~^kz(+eiJ0X5d#X3Fh+02)ajjJpzr_*yafrQ zPqz#Qg-0wS-}HImj9!eH(_exFB;r8f5y9xqm_2=F1SmW}0)IgQxzjx(LE#Y(3Xe!e zZ^r!TKS2T-383(ZV)SM#oW3#&6doV}-e^W|#^ULb(V*~11ce7kpmaKC3@AL3K;aR? z=*?I@eJ4oZ1xP?PmeHHBa(ZSgC_Iuu;Q&3Xc>}c*HS!GuBQ&2@?1L63~rj z^k%G|UKtMxk5o{2fCL(+YbJogBMlTD35?#1&C_p!1US+`;gQJb&Dc7=`TS75}BazNM`hA?4CX|85ABMfxjSu-szqxpzz27g+~gb zH)H?wpCAE^Y*2WlGI}#koW3#@6doV}-ZVyU#>vwo(?H>o0}2n2z|`rS>7ek)1%*dC zqc`L9={rFJFF*pa8I0bHGpA=}fWjjW6doXf+0!L6LE(`P3Xe=iZ^pUPPl5z~fCO~2 z7`++iPp`}Zg+~D>JU{{qr)y?|!lMur9@&iEjEkq=1PO2yfx;t)(VKDU^v)bmcz^`n zf&`XNx6B2FM=>Zoav8lDS5AKk5|AhXg-0HvH{<0TTEN5?DLkGanQlrJ(T0 zXY^)VKm8|2K%)#49tDivj2ow~EC7WENPxGH(VKDe^vFU`c$9;}10=9@I%g3mJSsrp zQN-xYxPAIgkiZL&fNU|NH{;IfnZ=;+s04)vNMQGL$r4a_RDr^ygwdOE@AQ)(fgd0N z-BLzx#{JVPOF`jL4GIsCz`^O7WuWk=0fk2yqc`K>={G?F9JQeEC};F$JUYFz926cP zfwv%mOtXA$>_~^diu;tPX1qK7BuL-~ zNIz0f{b9c(gEjGrpcavjr3$Ac4Oifw$8=TS4K` z4GND|MsLRV(|>{lG2^c3A_La$aXS%Gya^O*$E1deo%OT1b$DK>;i?y1W5iM$1gurp4#>SGL>o-qX!CVhcpV6C%n{oQA ze#WrrCqN>9K_a}2)4e7zhE1=S1`3u5jNVNAjMIOCL^P&@!et_(H>2S6l@meX0utbz z1ZjRu0)@*AP`H2uM5l9328GK^P`FHH^kx*Fz7r(y0wf?ih0z<-4w(W9msy~20SQP? zmz)X;m)W3jnF?u#fCPSk1azlC+9A_G;W7slE+7HL>6+6)>0~Y_Sf)eTAs_*cd7x04 z0cnTK0HqUC>KzF+5Y*0E` z2nvwdkah@2Kw}XoJmx^!A#*_C0TSSy3u%YU1%=0AP|gg~wt@J7h5^JU{|(K?3g6Eti18V-+YomO$DeAOVThpzv4< zX@@KYg$GFBFG#?5y5}-bc&q`1$1+Ge1SFub78D-KA?=Xmpzr_*@UDQgLso#oV;v|w zKmwuDIah+hV?8K5Rzlh#Ab}Sk0ohfMcE~DFcx(WL2S^}#y5wrc@aY~KLE*6)(hdO$ z`~V5)u7R{e)_}re6DT}D0*TW#*Mh=hGblXPLfRo90gf%8@K^_FhpYpI2T0&8NFaT> z<$6$fYz2kKdPZ+hI|L*ku?-X+8zAkF4WRG<3H${Kk6B=7VE_o0X9>+o9aS+lD0SWv73Fsbzv_lSo!s7%e zJU{{qr)wStg~v%ycpQebLqGx?r$FIx1kw&U0tye1z*~^O^68dGLE&*46dp$*?GTWF z#2HX{9D}q&j)B4hB=8p`uy(rVaZq@i1%=0PNIL{1pm7cq9w#8}kQ1Qr015D(gtS9W zg2LlGC_F#{Tc>lL0)@u~Pjs?pa7XAT36RBjVQ>u;1{WZ050HSwbx;^wgtR>_g2DhK@E0U-cDm;!P#D|*g~26A+XEz^ zaT62mbVGIW~JV4Zy=^H`R)#;p98N(T`O-}?-*Qf6UQ8%UwUSkXg zwKuMT{C$U!Z~D1wj9!elr%PT3`TH)&-`64S4UoVOkbv$DNPFW3C>`7br2~+_!|9qg zLFwQ=C>`8{v^PKk91lS0;1;C4aSN0VKmu<;0?(&g-UfxiLr@sphO{?80uqlvVQ>f1 z-nat_1CYR9kigsNo_9fE@E8;ZcOmT!kbuS$P#D~Uv^Va7(g8?-_dcY(aUYZpo`TW= zNZ{*q&Ih1$@C=j=9zfa~Ab}Sk0ojL;_Qpd{csvKC1CYS)>5`8?;qd|#9*-dH4UoVO zkbv%ENPFWkC_G+*!s9Why}>kH>j|j6@d^|mPeAPrrs=mpA{?(lA@UT|-e8*E^%T_J z0ExT>iLf(Gw|WL@Z@dA8$um%UgK7FJkch-vP@p_#^k(8_nm+3}sJ#Ia`3n-^Wt#5w z0@U7k2MU%Kp!NpS^j{zmjrX8%c?oH6yaa^{NPzbhq`mP96fPe?;Q|s6ozD3h6fPe@ z;qn^N-T(=_013#x0k<~}z5#{HCq~}ss&B#L9))i~;qn2+K zg`Yv`HVbk;B6_D0|r#_;JHzZiL^Zv_b$PG|fI3J;J#>Q``kV4iT);qea? z9v}hR>54x=;Qmh7GrCWo_y-gmOiaAfzk&ojr#t=y1qU+|@ARdA!Oe}2Ab}Gg zfzW@9-i*G}7ybhU2MZJLbk_gi=0@OuP;js^@lM|g5(u2i$P_;P1}hWq)Kn&K#^9+N znZl=curcvY7iDDfW(=L42$Hv8XX2fH6eJKnU66?>eEJKJKq(UwXw2gvNMHsB6Yq3Y zW+rdO=;?*bOySc#IGK2-Uj+%oPFG}M3ZMQ1B+$yj1RC?W2ohMq#l$<^l$FVwF>!h$ zD^vLN2yRedfCQ4K8?rHlPv_tP1qK_FH)HDbhaiC+AOTl)CU3^{=@Z$R!l!5Of&v31 zkU8CvgDHHv1Rp3cIG8|V9v?vhCqM$BoJ`(~xziVNGKEjC;0FZ;7ZYgABan+Je7c4J zC@??*h0__iL7@Q>Nabb%jd^SY33Ld8!h?s&o3V6yA`esebPFL+cz^`Trwj6e!UH5w z%F6^A^Ee0+m>~=b4?ZSu#_H*Xe4y|U0fh%hpmw?3@9fCQSS8w!HLLmU(yf=r+>kB155XI@Bj(4N-=@PJT8I+Rw#hNLz>B( zapCkvX;645g2DqNuz0$m3@AL5K;a?71RC>r2ol%<5^$Ae@@8BYLC_F#{+ouaEgTezOP^!!X8uK^^ z5}2V03J(<~Z^qrz6;(mup~b{Iy;YS7H0E&;B=7?yV5-Ih8uMsWV+x}Rhwh2k7&y$Z*~{+k4%rb5<^lZN^ohc zdUxl2@O}#&Ch=RHpRHGD|6*c`yKlsGR|a%|5+lPue$XKUmq7P&Ed*)szV|a8h$Vs3XQ~2~5CLp(h1a41fGzW#MDaftnOrVjI zjUa&^AOTSeCeX-9q6H{a%|KBF5_mXW&=M4?=1jcPOD&l|BPRzz0vr~gsIp=Ljhqx( zfkG7|a1|u*e7d4FC{!&$ac9j08acTL5|FS0C0!dP(8x)n4JcGW0#88#Z>JmDf!=A~T@$2-DAOQ{4kpdfK& z@@8b6Ug!!65|F@EkO2F1MK@58xPXGhjmevlbNWS)fP^b3NZgsc8M&u7x`ToQB=8g@ zz&qX00~91~pdj&J@@C|p{tzUf;SLHCPbP0h!RZq{K|ul%_zDsbp6=)c3K9=cka#h9 zGm1|C2okXH1OI7phz}?{Kmv->1^q$c;R^~6eLPH-!P9 z@Bj&11qrB6R}2J&hd(Gh0+~RQHy1$y5&@v_2x0A>5ieG@CXKlM<^4h5%UowU=acek1!_C zlQ)6kpzsJ~;+Yo{-7JGW|dV zlMIsv`}BJeOkJ#9oD2-&+a1%H9y4xFNM{ORoczIrW%`3=CJt6^F$M-+h}86ibfynr znVxti4%P-m1_s{gj_FLF!D15S;e}QiMcrp&rKK@_&`=jgY9KavS474p8hwDNgiaQ4M;81J0IIN3=I4X3=Iap)BUoUl33UJF)(m}9Q2rR@&OYOu)YnsOdPC5 zVGIlc5D8E?>44ok{Xzi~hgd}t#0-!_^70G77X;r)n*OeUDUT~Q1#+2uc?!rKyFlh~ zEKdXPBW&1SFrDcN*c`6JqNMzy%;J)bxzqVdm~OFl7s8zZvdsakYJ0&HrY}s>6VjO~ zK<3C56s4AwWTqDJb%HnIHGu5TNlh!s$j{H-mjT%XN;_FD?q^s zigl2Q80lvFgego{m?kG!i15NLlMmR-z`zZ%jvef&=@&YgI9Tr=gc@THGKMwcFo=7% zktrXqtny5uCL^Q;_q zpJ=v@GBj^hgoVsTW}WbVVy>;dE zS5S4JlerwgA`A?#p<*DW0ermySSjet70{{@(0MJOt(zcqU!a;n z$0!7WRWUGpg^CF?fKMz2i++PTMg(+x6e!3T7#KiDvVhD3ZOc3b75fR=G7J(FV_;yo z1l`*D3u>S^0|NtWoAPg{m;?g@Lm*W1AE+gg3=9mgjoP49)FAVu7#J8po54Y@`481A z&A`BL8XD4|C9$9=u9sn8V3-f`8ffz<^gsyEK1*Gw7-$(bNFnHWOHlNH3>XN>}t#kzP6sh4t6Sy4d^62R&ZU#U=KCWmVtrc zJT$d}mav1Eb_@&*7g!h=Kq=V;s?MH)f#D)l%oQpI%Ih^C+ZY%a+@NBPNXIU@L&cmx z`5zRYpcLu>6?A4`U;xD@NX!!|=EA_hU=8vT0|SE>G_+hn1r1cp2P)>qz`$S&74w7Y z1s%d?2Nm;&ig_?FFxZ2{K=B^{74&3aU~m8lg7P?2%!`46!4WDJ1QqjUU|?{CiUmW( zd>9xQe4%0?P%&SSIZ&}ssF)w3T3=9k@P_Y}4yrH=l#rle z@ldgFP>z6#B|ya@Ksf>`mIxJ#WME*(hdL$+Di+1Sz)%VmONNR?gN{rBi`6qQq(B8h z=iOF84NQf)Fcx&ABUm8=LpoGl94H?{)n!1%KxgCfGeVBb$%Kl5c9??>)&vy;Sx~V= z1_lODdIOa$*&s1c{3k&Z97r$+B*?(P0NR#*9;B0jfgu;_m=x$iwfZ1mLe-@*fG>9d z-4M%A02KorvI{y$7gS~xLd8G_?&d(v106yIazCitssGH%zyK;VU?)vwg6eQq@YQ4t zpi`$n3bPm(7-q6EfbJq?D1{mbvH)}>1t^chPNM=@a1)eIK_~A*)qzg(y~6@NX`i72 z>XAH98v`O%&j353DxZOY;T}|TEmU&>0|Nu-tQSze2c296at!DwV9WMhS^Nu^R5}Xp_YISB?g^a2r3^yyH-G& zK?fA8Gl38EX6S{g1Er-iAV~%W2G}uM4WQF*p<=M3wi-btDM)2wX44`wlKOFn~_&1UY5~G-SFM z7#Q@Kz{jG45BCD81D&sI2o;+RF_58$fq`KfsFY)1V3-3H>jkxtn7}8DGt7mG^?}+G zpo0%Vh08psAm|tbc_#1?=HONl$dU;R3=Bfh^sxY{4s>v{FjQnHU&A*=iqD4Aca(gNp5kiY*6u6yz!f28IK$_TLHy1_n@^ zfU?FxsAE<_Pgn&74(LoaP%mc{s2va0d=hHlY6b>|gHW+kP_Z=(3=D^%VyB^EYe7{s z69WS%ot%M+tpnv*&>^NEOU^jO}{ zUV`e~1g#H1Vwa&}n?W@o0|Nsn3a>!LKnHY#j=QP{#m!ZyAn5Fimn;knpt!jP4TWtC z3=E)P2Sv>ds5;OY7@%MWiQR;X?OBR5#ONWU)O;uE~vWw(2xOz z+9Rmg0cgm8#6TzDfnw(%==v~FN(Tky6R5&N3=9mQlfOVg^b{&~n1O)-qz+{8GpN`R z1_lPu$zUM$u%q-kK#fDtq5o0S^E{ce>OuWPP}3gNLBnHd;BjZ{#h6x0R{WM*IhwJ||$N>CdT)Mf;=5kXBtP!kZ;O;}Kq71W;sHHAR^ zB~bqe)ExqKe?T2PT?U3Y22c+U)HwsSbwOQKP}dZ6<|nA(20G*jbVd{CT&kn23=GFu z85oYUGBBKAWnehT%D@2X@t4IR+Z(0*!2eMyNm| z9-sjz(BKkiFbOnx1R5(+U}RuW1oaCTAp;hm0SZ-6`=6140W{$77gRPdFfe=p6=I-5 zi-CdRDdOI_ z28P?L3=DTz85r)eGBDg@Wncj9n3}=LzyLa3Z7VAS!**5%h8?U73_DpF7}O?Q03B=>%gVqI&&t4%#LB>s%F4iy#>&8u&I%a;$Yf<;$Yy0= z$YEt*$YW(-$Y*6>C<2|%R?N!402&S`Wo2L}V`X3{XJufhWMyEeVr5_e9itb)3fUwF zI$Thdm4QJGG!((g06xs{CKCe#Xao~F%)s!0nStRWGXukCW(I~Y%nS@)nHdJS9UuFh|f6NRF|3S3`=y*=h-eS-(mZ0N{*+A!ZvM?}kfDY6I9fk=yzmkQ4fro{G zftQ7WfsciOfuDteL4bvUL6C)k0dy4VG!_Pi>7X>t%)kI@wSlfiU}I!p0F9XmFf%X+ zGBYp;F*7g-Gcz!VFf%ZSf=+D(IgE*c0W?DRmx+M^G?d8)8oLCIOMYhpoj*ODFOXTP z9yHtl8eVW>WMFV+gbXXVGcquEFfuTBfw~Bw);}X;XaRJ=N)RIhLog!)LkJ@SLntEy zLl`3iLpUP?Lj)rOLnI>uLlh%q)F6hDfgzTWfdMpH5YNcKkjTiu02&bhjR2%FGBAKT z{pq05zYInOhD=5VhAc(~hHOR#h8#u)hFnGlhCD_Fh5|+ghC)ULh9X7=hGIqrh7v{w zhB8J5hH^#*22e-6hLM4xmXU#>juCV%Bjid1P`3kg#exW{=y@f^_UQGcqu20mc6|Mh1pmj0_B*Q*ktz7#L)k zAe}8x=L&TGz5^2jgCi3IgA)@2!#qX?hAE5;3{x2)mqmcuxS;L}=%$J;M#xPTpxz(o zDh*H{$%qlsFA`>CU=U$sU=U?wU=U-3^ou~F1IIw|e-hgNIRom$GB7ZJT797I1!y<| zba1~vsJ{y8o-#2o)Uq-#fRAbg9VH2By|FMbfR23q&BDO&hlPRRF$)6&XqW=jscU9t z0FQTo&JfUJVPMc_VPF9Dhqf>?Fl=LHU;y=(6hI0=f?ZpgaGw_KnM3TGchon zWny6X&B(w2I-?qNnZzGP28O?k3=E)g575{ssOJFcDS!r4K%;}8DGXCq1_pLk1_lmL z3y6h*0dyEKXu1J(2r+2lMwSsWc>fzT`Ue_dzX}@V0gXpP2gg8zueTW(818`jL7*E< zKm%@|Q9;lsAgBYz%E0i3m4N}&S=+`68J-3WM}vlyr9n+9P!p4df#D+q0|PrF0|N&m z0|R*ego%LxbW;VwzIw=L9I}N0j0_B*@yujK$WS6^=&*v3fuWKSGFWQK$iM&^vH%TH zfE)n2??s!Dfq@THBr`HF2rx1*fNs5c3+hqg@fhgNpSz&mAE-kFG6*z81nRqjMu0#= zNT2~WSyl!H&;T8X2Axm(h?Ri>vrPUNkG@AfCgqj12Ld3E9hQ1~k0U!N>r< zfToEN9DVf+pz#QpYfdmQFr0#Rv_NBCpy3A4=m5w^L7+ki>g#_j3=E)Q2hcDBXjtJH z3j@P*76yh#EDQ`+pcpiY06MuHbfE%hqyXelP$BOL06=FgZKjMBaj#}mS$pL09}#e3mWwUg)Sol!*xc;?Kq&$ zy(uFDgC1z~9n`Bwj(gDERG@SL8kPhNL;hp|2Nn2mYEb9=HK?n{%)qdV8PYKab;Lnq z8=xyAU=9Fz1k@P^T{Hu-6x0a^b-F=)?B|RO3@;cV{pfno1uvj$Y(Q7sfPxs*kp>xp z>>yCT7Ze?!@lQ~{7<6j(J5Xf>8pUORgwQ)sT@N~Ym=SWs8))PRG&BtgHJE;or$GH> z5Dhv>J0gl1G^NJCP*4QwEUVQ!Gcz!Nrj9N%F)&<&Rz9Fg2vixhf!cqJ3=FMIpqu3x z7)+rR&jm&X22cV8&3A%2+Mo_MXu=jWJ_73DgJy_96L_E*WzcE-AR9q5#Grv@kQ&f1 z7Dx>@s4#*l0!>AN6oQ7IKng&LL8CvA$@KsKK~u+|nPP6x4OGx;|6qoJECo%-f+lJ~ z6S$y>UC@j#XawLKBLl-6Mh1qtj0_B*LI{-gLG2At#XnFz^4WRM?G)NB`y$8)4fTk2cBYL1-E~vK)>ivSIus}Tn&^#Hag9@521$9C3ntA|rr9oY2P`4e_jfQpILEUyxcO687y6&KXd{F&S59+^z zTo3EJgL-e_;4nm}w29R-SiQ2!RxkpQJ$&;$%91%o;epk^2-M}V>;C|iOW z51{c)P$vu&6`*z@sJI5vpsE!#MFE<+02N-Kb_6ICL4_HpSO?`IQ0)mS>Omn6${nCV z`bnVp2i4J_5)m{u4H{?%_4Gh3TF?MHXbc?4_gGP%%jaN{w5!8DG^)5kuVbDk*sO1D|L4igTK?AxVJ^@Bk^2Kfjy*aqq(gF4HgPBW;p ze2jsC0n~H{)fu2>97qln&meh_C7`A~=w?4qi3)1ood(SmgH8yCcET@%W(XJ=7(iJN zeAD7gM#yB^3`Pcq>5L2v&lwmPKn)a7A_O%?K!#sqfJ{?^f)dn70ks{jGcYjRfHq!0 zcl?3w<9i62&H|N=ptUogkz~+X2`C%nFwkfKsQ3p>SAuRb(`BrO^kk}`-7e4!FQ}gf z>WYC{yr7mVDDi=MN1#?PC=r6HRnT>7pq`E#BP1(=QatERHc;i$3F^8rFff3sUC>Md zs9pe9z0e*LsFwxmae;bXSs>Sdx?-T_5@>wm4+Es@1nQ!>F)}cK`h}n@n9j(+0O~J- z=C470M^Il5G`|h%*Ma(opwS@~XqO9gyTgCbMFWvE<0$z zr~VyiodN>`!z-jg6Hp2T_1QqpDNyi(23J6r2!fhepj8~8o)T!B4>ZsMqCptM{>8w+ z0P?^;(4aeLEey2(=MM@Iq!0#$Dk${+fbu`cN1$FX$X6gAfm{#jK7nTRL9_p$t~bc_ zpoS&rR9g#11_n@j5EM-yHmI=)I`tNmvO%E;y891g0jT2v>VSY81ezrVwG}~~QqU!Z zAoD?Ha)aU@)Mo>^9)v-PK|L3cFF}n;&|xZ|+Y3Q*pxGXft3h!NDzHFmLCtEI!JsGr z847YJ=!QekpxsMQ!ewM&kYQwC0Nq{)G6&Rf1+BLMM>ojT@}Pb@H0WUCTcB|*(D;@* zBLn!d+n)@Oiw!^va6t70sH+W<1BruL!y1eX3?MeB-~mM+$Z?>k19d_`tsWDo9iVPH zNDO2zsLNIl>aT(NZ6E=d1dKLkWMBZf5*F;B-~|OasO+}^ISdr9&;?i^K1d#9F(|k} zgB+kx1Vt|>N;p|Spqv0o3!p>_8X@t7 zS`HdC23ZQqB{0K5!4Fag%EBPpkCA}^WIhOcLqh?S)wSsau`T0s3!yp9nio9s4oCA+a47EAWK1s2GsNd83gh*Xk??C5wcvU7u5ZRE-?Zb z2x`%QQZUF8P&xozsR_D96LfVZC_RA0Kv4j47^qGFg(}EV$PNM}+}Vr_46{J-HVayb zfCdskV?3a-o;}c3E2x?TrIFo?3=E*b2UuQS0AhZq?c zKysk&9jMa^nzRM+LHBwd2E{+97X-S*6Q&TP2&CWyBLf474eBX@_#ifj2C0Kt1mc4% z1hsNNjSpB02h_R&HEcjlbx^2)`j?SKcL1_cfCTmr@aMMee&VQCs%2WU$TX!r)y%?AzVykUfN*FbZsYeDf3sw6-QFF+kcP+JewSp<#ifjW+$ z0VvQw6lg2}l(0bc_I*YM2GFgfpne|cwo;JVJB$nrAaM{MM1wk?N{kE)pbqG3M#%Wz zdqxHZP@fTWAt|Vr4Z47|9@M1<4KV!z4WTeHFo3$(pbqK}Mh5Vz4v+&tBVFG?1C5N3 zds#t)Q=oO?px!QMz={cUK`0Xg0~@H{2E`y>gK`w8i4O8T9}@!uFX$pkCI$u`=y+p2 zsQd?6018S_5Hd1Bf(Dc*VJ%;fyeKqCK~WXVqnN(f?Tx<8mI&f z@z#TeVn7baU}9hZ4HSd!TLld?#V|22q%tuu_%JarfCed2m>3w6L5p;l7#Nb67#QN2 zAS;NXnHU(Nm>}1mf<`w%1Dl`$PSCxpflLexo=gl3Hb{;FjVXi1m_e&X?3ftp!7c;! z0YR>TIR@kikQm6-uF$a@5F4ZspgGGTmbPPBG8U-+MkbYz~NG~Xa zT|n^RRN++OYicq=|6#o@W3=E*c0$BrSHUwlbx)?eiNW;7(g}HekRDI6X+sfkgxYZ zYeLYF+Afg6j0_B*$_unS5i~lsn~4Fmtwe5C%;ugC>_j6V9NCXFg^I23}?c2GFg(pt&8;mKD&} z6VPNcXwn%pc?p_w0quDKO~-?FeSr4KFhQs0L5n;I6#t;5B%qQCw3Y?5rUhg;%utX4AWK08a5FP7fLsllatDP3NFB&PkRw1o z22IC<)PsBonw|%F2BZh19wrasgSzt|kAcPkgh6Xx2pI<26NAez&_)c<1`W{04Krp2 z22*AR@NN%%W(Ee(wiFF!1_oJX1_l|>?XS!X44^q-(A*;^0fOd*6+x4c>Y!VUnIYFG zt3VYiGcz!Nu3ZN0fl**)V333ILHlGtolVei8z?P+3UP%Z(@9e~6afEKueHfQi}uPS8z#TYOjG&je<(6B4v$GlsGjN3qsM9_ju z$RsOh1Lkw?KL*P8@0~-)>;)|xU|?w2zVT_;a@7so2pMIj>2rQCOE6h5O+WLUS%S%t zY5D~aKY?l54`v^xLZ<0{KbR%VKn+3^qZ#9j^bGY37#Kh$J4j|$(&8i2_jpWXVvIA? zGte^vH3%U_b2Cq0@Dtr=2_`w_=`25)ePnPO0J9vlG#F&XuDe-va$5?gGJz~N09g)N zi^j#k(2!BQ?*A|T)tMrUaVB~OdWH-PmszL(`Nk|EBgzK8W4&RbepIj8R$EIZM$pJV zD7+%rrU!gtmSDo+ymgGz-~D8kz~j8VjMH^MR;02sfJ)(pZnatS&o6v2kA=}t&(J{6 z(2SvkeforAW(l)4cF6YHoBTHC4?SQ#i%>Czoq<7+fuTXlGfX^{>GC9m%u@F0_dt5L zvO~6vc2}{?xZ-9ki%{{36MVmY!*XY>Yqh+)DnTI+3VH(uZm#JH-OLg)N?hQrP7Qmf zH-G1wG%JCLG0svC9B@`#(-TUVCCvP}7#K7e7#h?+R#zO@_GJdh3?s0T3NDDRSg(6C zmMk)DU}B6j1xqlrb4_1Y!rZ_#iEFw+DYFFAe6Hzr7nmiOR&q^GDP=Zd+Rin7-V$aB znPX5BzP)CC>{4td2{K#H7!)~Ixu)xNFiT8-QpzmFbdPIV8M6e_TdwIb2bm?9zH?1C zDPuO0Vc~{ETxj^2z|=Q&9w6Ni-|=%#KlhPYV*0EyW+^6V?&;ggm>bL@xgpM7pSk+# z8R30m&@^Joz>vWW3F0Sr1CM9Em5l?-7=oNp$vxd~JF~>}mU3n(rdICh3(A=#%$#^2 z_P$Ap3cYJ{z=er1&I00ZUmoygvW7MJhFfypCC_7Gj5CJFr0`6C2hzKW2eO?yigC_u zx9{@{pkZdnz);Bz-Z0qkH-~XUfs)HDCQw2(0wrOnUYR;R$adk-%WqVw@&zK97~{+# zrXS>+UQod-VTRQVa2`y7+AFK{Q279-(oQDEI5UV|F8=BJDwrjhBKfD^s9shvBH&;&0>uYZ1yj4o^j{#IT>R7Zsv(Z|sAe`|@)4U}1EO8Tr*8n! zLE_VWYM3RM&Wca}2U7P+eEPmg%n~wL0+49PFpXcPuk#jEUO=M|szZiB0|uRbj}$uvTLFg$c|OOhMw)&w*^; zB{}^?4YLswkJNOTT4p0AJ*nvdwagN*Y`~-=J-wxt*~rXFnt?%vfuUjc2jSI+@66-{ z``bXzgn?n3G$e6UIcPsJ)(zo?$`~*(9Fm^?rj}VkMpFioy4me>x^^k-OM#|g0|o{Q zndu63%o0pcl}u3n^qe|oDW(as)AMdJOMn7yQ6009%vO158cTTd|JQ`XZ(w7=+)Bq`12!L|^r_)@;=?6e6+eN1HH82~=99M$uwokXR^Se^~2b9N6^vpoH z;=0oGmhvEC z%s!w9b8BQaVp3M0UIn7#G^Vd>WR_sc(U^V$#BbD?{-=@Ihlx>hx>pmk5mSfe^tvWy zAEqgq)6X<9OUSIygd|+W+xl_paf;8G80$>+zy-->&1ucd5@rWAA%VBZ_?qARr9aJ? z80$ceG-P1Fq1Awa;kNd4zh-6$8AeS=44;0>8sVt6-wYgR;A)gnb9z%Vvyn`iE+mzF ziIrsk{;zcxID;DKnKCfE(4Bq(WPhC=#DM{IIeYGG+zd*ypd!SWfnh!uBuX?s-g{px z{$V?`P%~s;=+v7o*TO8pG)-@MPY_uvwchlc7EocXH+=y}{*d1ET`kOM zOtps7m0Fp7m;#KZ7ql`P$?zCM+`_1_luZhK9a{n?nEAo)2YWgjRA4JQmXr9AK7^(XxQJr*72-#qMC!#fWt8ZZTb^ zi&=sx$zuANc4i|`J@}xVS;7o!y=S0j%)n4<0g0gQEl*Z$5-AKsm<6p7nOL}|dvq}S zFdeg)KCOe<$P7oaFkoP~X9G*r6Y~_a#2vxyIN6PDti@<_rwwHq*CsGN&;?d}hR?WIH_o|1TOzO^Sj!k z2kb_0Z6o3^eNQ)Y8k3^Kbd?_FG-&cO0_D*IJf@H%lq4Hq|_SbQNQxLfPNpONxT4o|Q$}Izz z%YoekmZ@->9@EP#A=B#w$z+9X>NAfsJlG3WVQ9#3%W3+AUS=a1%zDy9&q&YEfPo?0 zW%|8dW*?>`m+3lv%n~v_u8@=?z4yBOzJ?lRs96RK44JOe6F@2|T_G;j?wWL?Q@)xX znk|hO7}{N@_pN1?V4CPUeO({3gxPXeh_5|19m!H+ESdMNkkohM@6Qvvjrw8{AqOkDf4M^9Yt!d7hEKT9 zJOsNKTplyKO#{_=Vs6tFW-yCR*Pp;F%M>p#{lGG2QC34khV5L_GbS(_F)6xDp97+y zb)=b*8zeaOUS7#g6`u4L>On&W20J%MVsv6;N=aYLF$>ycF=k-!aGNeSky*kl2&#gm zQOjvhCrc~T%Z3aLF;E$4&-Lc1EAvl+oM`}V@1(g+&zZ<9A(IbP@pi)t-QWdA`Uul2 z-KH-9ncfCzLBxXM*Xxqf>iW{WLt1{5(y}<16 z4E2=(1H%(H@M#JS`)roif6Mw-47L|s@O^WeUa*E)f{E3Ay3Pt_2_`Z3=?;^bC1e!c zAv$h0ec3ZvjDJ4J9cJKWjIsOllF7_zOfK%z?@VSkVhVSkE-;1J2h>W4o5E})gQdY} z4%W!fE;4=16lNo)H23KbrZ7vG6+rDc-PC)xd8MEZ$PPWQtD4*)iRGu2X85|(Fr`JpcwUP9u-&w#cF@4=s<_sn=x9Jko zm|rlh;+f7go!Lm{xep}0YWL4(SgSPkFtj=~fH%R+MExL6a5Ah)mhf*;WP-PQ<@_Mk zl9ff4oIUeqMkY}21zhR8@tZzxIC8qT zmnzI)He!NAj2MdngQWlTyctMQr071qYYnrJjH*AR9J^o}c|AL(WE#R2Bme0iKsucL zr+?VQEHPbaCbOT+M1M%?*vr^-w%W}e)CqugvlseL|F?))LT0Bwq{xWaAuXD7%ghyQ zAh-y71Dd{>E(T23nawQ0r0PH2Z8q~3rfEUb z|IB9gF}WKA$>Jy1+wDpTu~7gwq0K-&3gci%vG9{uX{)Hif)7lL%E1tiPY;;KEX4#X z{UF7S#Pm~hn596C*k^N?jm%m?AWEMpo8Mc!=RBxF2DP$`7#IjSHBDwp2qam=#(8n; z9}(i#wN=COFi65~6^O~nX35&096;wV3E7 zP_YT>{1`DXWJOFbn2)_532Mh0F);8(LW1_kuj-6L5mS~jF@B4H^m$Wwrth22ERkgt z2}unpk%te><6pqa#3&mHseiEaUo9X-N=PKcMTT4-Uu(IEurM(?L_%saEL}lJ`NdGl zJ>6jevjh{=UYXladn1pT1-vUXL=v9N*s+~0|tie;KIg-X>RoNnnlbK zOv|IE@7oC~grcX%Eo7E3!wmd5Lr}G1$Z#SWQjpz|F0=j9Sg;gPjm?dozGfkF8dGS@ zbdg2OMj*4-EM(T2e&8=N2Z&Px61)~WeFMlsNIDReH)dcUoY8UTSZD@kf>yFj0rAt% zEoPROUbmQ83N8q(Z(x#4E8V8QSj=q11kKh=Q3=z{mLQ6-=?zPmWuRSFAEs__0S<0C zvMpsclEGRvf!kbAyJ02a^p>T}W=yver|((HEWrfzFRK9qLq^i{4SEH zSOpro%bfmd6|)i3x~%DPtC=O3pp_rf8^7r}AU>=ix>jQPg4N7vINB|s^rYxMU1<%o z5v;WYO;#`y8Af1F8Idhbi#q7MHMJyr_<7tc=a3qY~K(2@sfV5(U@P@OuXYAsa8h=Bo;e?_ND?qHUhUcG@?Rt9%-a9ZB< z30s&Yrk~lsECnl%WMT>+AyvHFqWkqOj(do_w8VY7%tmGjCfUO2W*eD}WH3inU=s|` zrrh*78=0k`WtRkc0XTiz5@!AB#+#VsK{5+gGfPg--NbCnB#}3L(I#dib-Lj; zWvVd}HfAXq+#{FJiembyZOl@z0&4odZOne?r530o+q9jz0XaZqnP8y-Dc(h= zr|)2vWm;J|eZdaUbOMAnL+^02fI9q!dS(m^+p8dHhiy*W}f{oM{` z3A2Y)kn&Gc+jz$m^Q)kW8#*xltqRf>6LeV}CUe`;8d{|rFu*2HK!X`!JDJm9%>=XU z&_=+Ll=#|3KKa3j9-}}tq&ZvYv(Gp6qxd^SHv~EmGF@dCbCVgCCM9UH%7B4kw;7~5 zZY&R&1}T9 z+Iaed-ONUK2GCd5PuJT6>ZsI1s=)m^vu|04N_c_IG6Ge7C+nvt?O`?o&G<~)!)zo| z(*UW^xaK~*)x!SR7+m0j$5j~{r$5=l>?5-hYF2m3l#P3CoxIP)2%4h;ooaEYak|f5 zW(gU-CP=&3THYhBYLXYI&^FXF0`)bZgN#hI&C>(+F^edGip1__h>hPI`VzK0y$UMv zjPwjm^^6&|S4}^+ms!H>Tr(sz7jAMqdtdvcEI2g5W#YqTh`$6X3O6oul?QdeKn1n| z1H;GW=`#D6C72jnrrRB0mVnwQAtT-bX+36%)=wzfCI@Qn8$kv@HCm?U?Pr!?5^9}3 z52O>~MhT|i0>65PZf*gM1vADG$TM&%6x>n zx=7G|uK1={P&otY6&NxwK+Qlm6Xpvud|?GiN3iTCz-VU+iW8WbBGVTfU}lj)&!q@u z&>>dna3g#m75l^?cmNfepFl|`<{+~XQ(x!w2?v=a%ocY-qDw{b`oYuN*VnT!8tQ?f zlYwDdC#0(p+xYbA-K5pC5HhDar{4qVz0(P45qS3T^4ZQiy|K`hh_s~28OM@(+v(YOPKBNg=9}#zk*8H z)2-&hC76EnPM>j@*@vl4Z~C*t z%syBKzT=>U5r5xwzayZ*-M;A=N0`%?^!lb>0P$`6roTGEY{Yc3e!ARIW+NuwzUe_A z8rox?KJO^A6jNB=^j#pql)mZjjxrm`boN1t#=M`FKYX)-q`}?>j{)p*n{IQASwd!N zA0+LH3T)h6)t?8Rfz~qtl}1bYrWYK8^Z`K)Uzs#_NUjaN`A}O(-n$CyeQ*%I>YKjr z7_*Vgmp<@OoDDl1=AZLs*z*-p=J57U=Q+-7#3bH7-QhT>EClP2km2ozm}xOhcZ%X+ zp_5FEbp{aIA!bU*VCiui>KTGY%b`7PrnrgI?|{q<5}*FW{lWcC5I zQEE;y8!^3}I(-9(|8m;&eSB2AxLD zz|fF*Nb#uq3`tOMm_V8aQZuHX1L=U!M%Wt>V7Ii3OqV&$ECEdgqO67t49{mx4*&@< z&Yqrin%Rd*Y3}qLry(trH>a6rG0iug-f;%P2gh)d3nX*;uf16Q_PV4gxQzoY-=+Je ze>lUe#r1X`Bw^dlpRRJ2*+>RQAKn;T4;7td_F-zAKm7nm<@foJ)YH!)_hMo3QqW*6 zG#Rrkn9g?&G+4S|y2d$XBc|8|(__wo#z&yXa#y@qJVpNBXHacp3?4gX*tT%`1dxt{ z3#YF-$LwQ<+Z9-*(?HD%$aI{4)>glGBmITx8FWTciu z=190~Y#Y}zPFM+60d6IjFQ0zlBC`aO_wwnlKxQQ^hoq6z9%1WAr#FMTy#{*5dX@|f zwacdmU1Bz3(vhAX@RV6>`kYJ5GN3#IPJ9!VL%J}VCvIfoop27c-~rTjFlS&`w0!!z zOUx2X8eUT~S&2fFORh{^nwS>5=_=B zr+;_`np9so{lY!a%<0PM4p%@+q*g*4_@Uu+fR@@%(2NGC3^rn5IJpv1!{pCwd9g(9 zz(cSn!9%D3DBWnj3!65>!khiR{*tAit;;cm#l@Np$1jlWHK^730pwGUW3 zc)13R2466rZjq z)R6WBtQS1FeQ(wD4cC|@nBJ_KzV89Egv@uSJIs8lwDsn{*8|rV;La|~>goTkF-w?9 zt%kIwDs;79*Jw#W22Q{upxUb;;Z(iY-z#z5nGC2c1`G^VtEc;1XO@uhUk&MP6*$za zK2UGX2UTItz>vOrdc$>Q3A4u4kXX36K=!fOjO>8fdOm88-g0yu%#d95sft(QFMr}nr?TCSwcp49i)i*u)Sl#Opd1( zU`K_kfm_KvcpL6`o{>rV^HyWS*>tBq|2WFJ+6|yQ{%=fD;BJVM>_2Uk~ZQ ziS|_}{=0b=G)@4`Rbd+-h0CkNb*#1}%dSH88Zt1%Zn6*s&e|8 zJIqo{$G}-Yl+_Xz(-Jax3jV)xeC0S69 z7aVmGpfHVk2%7WTIlb>8GdN5^MYg;Fj@$=Mz4um4zw?k;0+$1g;mHKM1Hicq=2U1X zG5J928(3iUteU>&5wnC$-!90MjEq}j*^3)Y^58%K7l@FgEdeVyu-gUhBtv6ty4z!B zDW++V3Jn%U7|{-kPH4>lFZW>%hKP%Tnu;(XbU(n1z#%{FDYGB6I)_#35=@i!PM`Ob zS%PWK-swA@BBeHTvtRFql-lLf`Cfol2h~A}EkpYysx{4mQs7n-cn#j31CYLwD4%Cka>jKl zaNTDL>Ip$wHX_ptUN8sAa2$kKA!qEC_@Zj65;VRI7#Lu~XrEs&n=?Vy=om50Jv=?( zC9{Ot`oj=y-!3I6_jNL;gS1)bndlia?1#z(9JiUW?9(i8y$O*ye|Y*j&^n*{ho>ui zW|o-#;3cyZ(}%;;|GZ?Dkl}|eX?^n}>2AC*hY4co6yuTU2CtZnn1YW?PkF`cBa?I# zQidm+Y^$(3AI^rb8fNg5SIkmOuhJD8%xh03?@U{Ev_T+&(XW%?==GgR{H_Q?;x1lN` zwpYIXB>WDvb_6;H|MJ-M1t1l##3Aju-@$Q`H}7?U>Lh3vf`xnfl{d^rW^DW=7ZL~Kve{ZL!$qnkoDJipGl`7y*>*)1BO=a={9dc%cjjBdbjH`FXcaf7_`vR z1kyqIaeR8gTV@F+mlM;w-ZJ}`VKvJ_&ya!PtT?1u{i$ZH!~Q^~HgJgw?o_`LpU(3R z`x;s3kWrBMbcc7K`6sUFQSX>t>`t77Bpe*0z@SbKW``GQ7No!0lQ1`5+t_Pcg)vw-|_4$ zxDWvkxIk=`kYPLm$pLvfSESRY{sOIs2epa}7#JX?8zJ}g8D~$=`^+rC^!3v8j?c_S zOa@n`@A=FuA>(!hQu8+6d!6ck*-{1(l;KyVe*o!#7$zaJ{0gK+y>rX+9R4G}KuOUM zQo8KCGTrVAvxLlR^6K;hUzjD#c3y?}^wjkp z_8G?2`@lX0mk%eQGK&jDj+eD}gO-1S<}?i%7;avj&i9pBLgwXFi1V8pBOP?!z6Mzd z8W}N!g@FVUY-|#;xKjcY@)N!?8-ZwWQF!MovlM9Is(_jRXlW5R>~(KI@^Na;h}u=@@c=cC>)UTY7IyU-ApI zNExy(XZnX<%qC3pZ%kMF&1@v|`Ua#Qb?X{m)uOOtHPFJ#fPvxbjp-%7nT;43rYHVo z7GnXe4V`}QH?t(8%Jkd6nI&b+Z$dn*#%@>SwEYvP)`4bGkDJp4{xBOc@=tgE!|W>) zdK(g5OSJBFmwIY~hV7vx*WWwS*Zcvk2#%iq;16>H#41Uq-FK(g{AHE^Eq0jpmpP5; z=)LLx{xVCL{k;Q8{yL|wzFB)g*p-RVP|pb5EV*sJ>3tk55;AjNF)*ky zFf>Fy?WlJXeG6KA2+F3QjS;V=9|F1b(kn=>+<3cJfA7_+;L-)MeBtG*=>nWA64HNO zL1Hi`rKLP!!fsGyZ4R03VxR8J$zm)c{u6x(m(3Uo&5@xM$Ai3=j^P452QoljV z0H95XX>TCz-MCmWc)N+@9VW&y&}cGfP2-#CHC!wb*qs9|UJkyQz5!(V^*4~X5Uxr) zs%fMS88`-~ipOstA^Pjhzr1g&4}uoP8bNkk{dhC|A4o6LTS&oUr}aqq&5B>3N(nR^ zX28IJ+f=7^H{Aqd?h@bjt`aBRHI_EI`0uPIcOu;8eg)VScp2^(74KlX} zE=lzIrYrEWG%&qQCq7V=8IIPrKTtHv4k@{V4J>+ zk3~+YT@~V;6J4AB91aLvHeD5x9v+u8WeTKt-d?CW{TCmL922AKbSZuoIVpbV>TZYA z3e}=XI%O50gRmJG7*?@w58`Jz$~fKQF^l$eaX}WP>6;>1q^CV&;ht{rltrDRq_QBj zSiih@d)QN!bBxpXu4FOVuJW9vjB)ys$1JYXKR#iRDISdR&UR~!*Z96IjJ;ndgBKc zMVqA3JY9%^y83XoE~r^xXslO~UWH_vZh2-2NNI|0YHnspNovvdlP_4F$V`9vm}T1Z z11zkZ)3>p*_DoL@WMP@!{Dh@+x+fc}F6Rr-8M>fdm(v?LSXC7*=h)2N^WXIf>+FYj zUv7DSf%VSX*VU7*b$Wn~*#(_HH=U7#Rc88AHrCBt9Gsw&D;XFpIHxzTv8qi!!Om*H z)c_UTz&ZUC8>{qmSq@fhNzm~)JPZsCpfmnJH0TUR5PfZWA_uED*9)kgA5eok$CuQ?UmC diff --git a/lefthook.yml b/lefthook.yml index 28cc971..d691082 100755 --- a/lefthook.yml +++ b/lefthook.yml @@ -4,21 +4,17 @@ pre-commit: types: files: git diff --name-only @{push} glob: '*.{js,ts}' - run: npx tsc --noEmit - types_example: - files: git diff --name-only @{push} - glob: '*.{js,ts}' - run: npx tsc --project example/tsconfig.json --noEmit + run: bunx tsc --noEmit {files} format: files: git diff --name-only @{push} glob: '*.{js,ts}' - run: npx prettier --check {files} + run: bunx prettier --check {files} lint: files: git diff --name-only @{push} glob: '*.{js,ts}' - run: npx eslint --cache {files} + run: bunx eslint --cache {files} commit-msg: parallel: false commands: commitlint: - run: npx commitlint --edit + run: bunx commitlint --edit diff --git a/package.json b/package.json index 574b8fe..6ae41cf 100755 --- a/package.json +++ b/package.json @@ -24,10 +24,6 @@ "type": "git", "url": "git+https://github.com/revanced/revanced-helper.git" }, - "keywords": [ - "revanced", - "bot" - ], "author": "Palm (https://github.com/PalmDevs)", "contributors": [ "Palm (https://github.com/PalmDevs)", diff --git a/packages/api/package.json b/packages/api/package.json index c6abc5b..84bcd7f 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-api", "type": "module", "version": "0.1.0", - "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its server", + "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its API server", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { @@ -19,11 +19,6 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "packages/api" }, - "keywords": [ - "revanced", - "bot", - "server" - ], "author": "Palm (https://github.com/PalmDevs)", "contributors": [ "Palm (https://github.com/PalmDevs)", diff --git a/packages/shared/package.json b/packages/shared/package.json index 71b6fbe..ac2f589 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -19,11 +19,6 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "packages/shared" }, - "keywords": [ - "revanced", - "bot", - "server" - ], "author": "Palm (https://github.com/PalmDevs)", "contributors": [ "Palm (https://github.com/PalmDevs)", From 72adec51b407b6f86de74d9a7ad14be85951960c Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 25 Nov 2023 16:02:55 +0700 Subject: [PATCH 009/312] chore: configure lefthook to work --- lefthook.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index d691082..6dfade6 100755 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,16 +1,12 @@ pre-commit: parallel: true commands: - types: - files: git diff --name-only @{push} - glob: '*.{js,ts}' - run: bunx tsc --noEmit {files} format: - files: git diff --name-only @{push} - glob: '*.{js,ts}' - run: bunx prettier --check {files} + files: git diff --name-only --cached --diff-filter=AM @{push} + glob: '*.{js,ts,json,yml,md}' + run: bunx prettier --ignore-path .gitignore --check {files} lint: - files: git diff --name-only @{push} + files: git diff --name-only --cached --diff-filter=AM @{push} glob: '*.{js,ts}' run: bunx eslint --cache {files} commit-msg: From 306f627cef317a34850f0275b4704251e8a2c6f0 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 25 Nov 2023 22:45:27 +0700 Subject: [PATCH 010/312] chore: format and remove unneccessary code --- .eslintrc | 3 +- apis/websocket/README.md | 114 +++---- .../docs/0_development_environment.md | 78 ++--- apis/websocket/docs/1_configuration.md | 78 ++--- apis/websocket/docs/2_running.md | 83 +++-- apis/websocket/docs/3_packets.md | 66 ++-- apis/websocket/docs/README.md | 32 +- apis/websocket/package.json | 86 ++--- apis/websocket/src/classes/Client.ts | 4 +- apis/websocket/src/events/index.ts | 7 +- apis/websocket/src/index.ts | 307 +++++++++--------- apis/websocket/src/utils/checkEnv.ts | 9 +- apis/websocket/src/utils/getConfig.ts | 33 +- apis/websocket/src/utils/index.ts | 2 +- apis/websocket/src/utils/logger.ts | 19 +- apis/websocket/tsconfig.json | 4 +- bun.lockb | Bin 330156 -> 335700 bytes package.json | 114 +++---- packages/api/package.json | 80 ++--- packages/api/src/classes/Client.ts | 8 +- packages/api/src/classes/ClientGateway.ts | 10 +- packages/api/src/classes/index.ts | 2 +- packages/api/tsconfig.json | 4 +- packages/api/utility-types.d.ts | 2 +- packages/shared/package.json | 74 ++--- .../shared/src/constants/DisconnectReason.ts | 6 +- .../constants/HumanizedDisconnectReason.ts | 7 +- packages/shared/src/constants/Operation.ts | 4 +- packages/shared/src/constants/index.ts | 2 +- packages/shared/src/index.ts | 2 +- packages/shared/src/schemas/index.ts | 2 +- packages/shared/src/utils/guard.ts | 21 +- packages/shared/src/utils/serialization.ts | 2 +- packages/shared/src/utils/string.ts | 4 +- packages/shared/tsconfig.json | 4 +- tsconfig.apis.json | 2 +- tsconfig.base.json | 6 +- tsconfig.packages.json | 4 +- turbo.json | 52 +-- 39 files changed, 690 insertions(+), 647 deletions(-) diff --git a/.eslintrc b/.eslintrc index 9146069..475489e 100755 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,7 @@ { "root": true, - "extends": ["prettier"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], + "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "module", "ecmaVersion": "latest" diff --git a/apis/websocket/README.md b/apis/websocket/README.md index b13d97a..e38c83c 100755 --- a/apis/websocket/README.md +++ b/apis/websocket/README.md @@ -1,57 +1,57 @@ -

- - - - -
- - -     - - - - - -     - - -     - - -     - - -     - - - - - -     - - - -
-
- Continuing the legacy of Vanced -

- -# 🚙 ReVanced Bot WebSocket API - -![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) - -The WebSocket API for ReVanced bots utilizing BSON for packet transmission with a heartbeating system to monitor the statuses of clients. - -## 📚 Documentation - -Documentation are provided [here](./docs/README.md). - -## 📄 License - -**ReVanced Bot WebSocket API** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. \ No newline at end of file +

+ + + + +
+ + +     + + + + + +     + + +     + + +     + + +     + + + + + +     + + + +
+
+ Continuing the legacy of Vanced +

+ +# 🚙 ReVanced Bot WebSocket API + +![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) + +The WebSocket API for ReVanced bots utilizing BSON for packet transmission with a heartbeating system to monitor the statuses of clients. + +## 📚 Documentation + +Documentation are provided [here](./docs/README.md). + +## 📄 License + +**ReVanced Bot WebSocket API** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. diff --git a/apis/websocket/docs/0_development_environment.md b/apis/websocket/docs/0_development_environment.md index 9e523f1..85960a4 100644 --- a/apis/websocket/docs/0_development_environment.md +++ b/apis/websocket/docs/0_development_environment.md @@ -1,39 +1,39 @@ -# 🏗️ Setting up the development environment - -> [!IMPORTANT] -> **This project uses [Bun](https://bun.sh) to run and bundle the code.** -> Compatibility with other runtimes (Node.js, Deno, ...) are not guaranteed and most package scripts won't work. - -To start developing, you'll need to set up the development environment first. - -1. Install [Bun](https://bun.sh) - -2. Clone the mono-repository - - ```sh - git clone https://github.com/ReVanced/revanced-helper.git && - cd revanced-helper - ``` - -3. Install dependencies - - ```sh - bun install - ``` - -4. Build packages/libraries - - ```sh - bun build:deps - ``` - -5. Change your directory to this project's root - ```sh - cd apis/websocket - ``` - -## ⏭️ What's next - -The next page will tell you about server configurations. - -Continue: [⚙️ Configuration](./1_configuration.md) +# 🏗️ Setting up the development environment + +> [!IMPORTANT] +> **This project uses [Bun](https://bun.sh) to run and bundle the code.** +> Compatibility with other runtimes (Node.js, Deno, ...) are not guaranteed and most package scripts won't work. + +To start developing, you'll need to set up the development environment first. + +1. Install [Bun](https://bun.sh) + +2. Clone the mono-repository + + ```sh + git clone https://github.com/ReVanced/revanced-helper.git && + cd revanced-helper + ``` + +3. Install dependencies + + ```sh + bun install + ``` + +4. Build packages/libraries + + ```sh + bun build:deps + ``` + +5. Change your directory to this project's root + ```sh + cd apis/websocket + ``` + +## ⏭️ What's next + +The next page will tell you about server configurations. + +Continue: [⚙️ Configuration](./1_configuration.md) diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md index dd134be..5f2454b 100644 --- a/apis/websocket/docs/1_configuration.md +++ b/apis/websocket/docs/1_configuration.md @@ -1,39 +1,39 @@ -# ⚙️ Configuration - -This is the default configuration: - -```json -{ - "address": "127.0.0.1", - "port": 3000, - "ocrConcurrentQueues": 1, - "clientHeartbeatInterval": 60000, - "debugLogsInProduction": false -} -``` - ---- - -### `config.address` & `config.port` - -The address and port for the server to listen on. - -### `config.ocrConcurrentQueues` - -Amount of concurrent queues that can be run at a time. - -> Setting this too high may cause performance issues. - -### `config.clientHeartbeatInterval` - -Heartbeat interval for clients. See [**💓 Heartbeating**](./packets.md#💓-heartbeating). - -### `config.debugLogsInProduction` - -Whether to print debug logs at all in production mode (when `NODE_ENV` is `production`). - -## ⏭️ What's next - -The next page will tell you how to run and bundle the server. - -Continue: [🏃🏻‍♂️ Running the server](./2_running.md) +# ⚙️ Configuration + +This is the default configuration: + +```json +{ + "address": "127.0.0.1", + "port": 3000, + "ocrConcurrentQueues": 1, + "clientHeartbeatInterval": 60000, + "debugLogsInProduction": false +} +``` + +--- + +### `config.address` & `config.port` + +The address and port for the server to listen on. + +### `config.ocrConcurrentQueues` + +Amount of concurrent queues that can be run at a time. + +> Setting this too high may cause performance issues. + +### `config.clientHeartbeatInterval` + +Heartbeat interval for clients. See [**💓 Heartbeating**](./packets.md#💓-heartbeating). + +### `config.debugLogsInProduction` + +Whether to print debug logs at all in production mode (when `NODE_ENV` is `production`). + +## ⏭️ What's next + +The next page will tell you how to run and bundle the server. + +Continue: [🏃🏻‍♂️ Running the server](./2_running.md) diff --git a/apis/websocket/docs/2_running.md b/apis/websocket/docs/2_running.md index 6734a27..3ffd782 100644 --- a/apis/websocket/docs/2_running.md +++ b/apis/websocket/docs/2_running.md @@ -1,42 +1,41 @@ -# 🏃🏻‍♂️ Running the server - -There are many methods to run the server. Choose one that suits best for the situation. - -> [!IMPORTANT] -> Make sure you've followed the [**🏗️ Setting up the environment**](./0_development_environment.md) steps. - -## 👷🏻 Development mode (recommended) - -There will be no compilation step, and Bun will automatically watch changes and restart the server for you. - -You can quickly start the server by running: - -```sh -bun dev -``` - -## 🌐 Production mode - -Production mode runs no different from the development server, it simply has less debugging information printed to console by default. However, more production-specific features may come. - -To start the server in production mode, you'll have to: - -1. Set the `NODE_ENV` environment variable to `production` - - > It is very possible to set the value in the `.env` file and let Bun load it, **but it is recommended to set the variable before Bun even starts**. - -2. Start the server - ```sh - bun dev - ``` - -## 📦 Building - -If you're looking to build and host the server somewhere else, you can run: - -```sh -bun bundle -``` - -The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** - +# 🏃🏻‍♂️ Running the server + +There are many methods to run the server. Choose one that suits best for the situation. + +> [!IMPORTANT] +> Make sure you've followed the [**🏗️ Setting up the environment**](./0_development_environment.md) steps. + +## 👷🏻 Development mode (recommended) + +There will be no compilation step, and Bun will automatically watch changes and restart the server for you. + +You can quickly start the server by running: + +```sh +bun dev +``` + +## 🌐 Production mode + +Production mode runs no different from the development server, it simply has less debugging information printed to console by default. However, more production-specific features may come. + +To start the server in production mode, you'll have to: + +1. Set the `NODE_ENV` environment variable to `production` + + > It is very possible to set the value in the `.env` file and let Bun load it, **but it is recommended to set the variable before Bun even starts**. + +2. Start the server + ```sh + bun dev + ``` + +## 📦 Building + +If you're looking to build and host the server somewhere else, you can run: + +```sh +bun bundle +``` + +The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** diff --git a/apis/websocket/docs/3_packets.md b/apis/websocket/docs/3_packets.md index fe2ea75..96d5c7f 100644 --- a/apis/websocket/docs/3_packets.md +++ b/apis/websocket/docs/3_packets.md @@ -1,33 +1,33 @@ -# 📨 Packets - -Packets are BSON messages sent to the server. They're structured like the following when decoded: - -```json -{ - "op": 12345, - "d": { - "some_field": "some data" - } -} -``` - -### `packet.op` - -Operation codes are numbers that communicate an action. - -### `packet.d` - -Data fields include additional information for the server to process. They are **either an object with specific fields or just `null`**. - -#### 📦 Schemas and constants - -Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well. - -[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../packages/shared/src/schemas/Packet.ts) -[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../packages/shared/src/constants/Operation.ts) - -## 💓 Heartbeating - -Heartbeating is a process where the client regularly send each other signals to confirm that they are still connected and functioning. If the server doesn't receive a heartbeat from the client within a specified timeframe, it assume the client has disconnected and closes the socket. - -You can configure the interval in the configuration file. See [**📝 Configuration > `config.clientHeartbeatInterval`**](./1_configuration.md#configclientheartbeatinterval). +# 📨 Packets + +Packets are BSON messages sent to the server. They're structured like the following when decoded: + +```json +{ + "op": 12345, + "d": { + "some_field": "some data" + } +} +``` + +### `packet.op` + +Operation codes are numbers that communicate an action. + +### `packet.d` + +Data fields include additional information for the server to process. They are **either an object with specific fields or just `null`**. + +#### 📦 Schemas and constants + +Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well. + +[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../packages/shared/src/schemas/Packet.ts) +[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../packages/shared/src/constants/Operation.ts) + +## 💓 Heartbeating + +Heartbeating is a process where the client regularly send each other signals to confirm that they are still connected and functioning. If the server doesn't receive a heartbeat from the client within a specified timeframe, it assume the client has disconnected and closes the socket. + +You can configure the interval in the configuration file. See [**📝 Configuration > `config.clientHeartbeatInterval`**](./1_configuration.md#configclientheartbeatinterval). diff --git a/apis/websocket/docs/README.md b/apis/websocket/docs/README.md index 1ce65ca..5dc3ad7 100755 --- a/apis/websocket/docs/README.md +++ b/apis/websocket/docs/README.md @@ -1,16 +1,16 @@ -# 🚙 ReVanced Bot WebSocket API - -This documentation explains how the server works, how to start developing, and how to configure the server. - -# 📖 Table of contents - -0. [🏗️ Setting up the development environment](./0_development_environment.md) -1. [⚙️ Configuration](./1_configuration.md) -2. [🏃🏻‍♂️ Running the server](./2_running.md) -3. [📨 Packets](./3_packets.md) - -## ⏭️ Start here - -The next page will tell you how to set up the development environment. - -Continue: [🏗️ Setting up the development environment](./0_development_environment.md) +# 🚙 ReVanced Bot WebSocket API + +This documentation explains how the server works, how to start developing, and how to configure the server. + +# 📖 Table of contents + +0. [🏗️ Setting up the development environment](./0_development_environment.md) +1. [⚙️ Configuration](./1_configuration.md) +2. [🏃🏻‍♂️ Running the server](./2_running.md) +3. [📨 Packets](./3_packets.md) + +## ⏭️ Start here + +The next page will tell you how to set up the development environment. + +Continue: [🏗️ Setting up the development environment](./0_development_environment.md) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 99fd315..e95c042 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -1,43 +1,43 @@ -{ - "name": "@revanced/bot-websocket-api", - "type": "module", - "private": true, - "version": "0.1.0", - "description": "🧦 WebSocket API server for bots assisting ReVanced", - "main": "dist/index.js", - "scripts": { - "bundle": "bun build src/index.ts --outdir=dist --target=node --minify --sourcemap=external", - "dev": "bun run src/index.ts --watch", - "build": "bun bundle", - "watch": "bun dev" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "apis/websocket" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "@fastify/websocket": "^8.2.0", - "@revanced/bot-shared": "workspace:*", - "@sapphire/async-queue": "^1.5.0", - "chalk": "^5.3.0", - "fastify": "^4.24.3", - "node-wit": "^6.6.0", - "tesseract.js": "^5.0.3" - }, - "devDependencies": { - "@types/node-wit": "^6.0.3", - "@types/ws": "^8.5.10", - "typed-emitter": "^2.1.0" - } -} +{ + "name": "@revanced/bot-websocket-api", + "type": "module", + "private": true, + "version": "0.1.0", + "description": "🧦 WebSocket API server for bots assisting ReVanced", + "main": "dist/index.js", + "scripts": { + "bundle": "bun build src/index.ts --outdir=dist --target=node --minify --sourcemap=external", + "dev": "bun run src/index.ts --watch", + "build": "bun bundle", + "watch": "bun dev" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "apis/websocket" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@fastify/websocket": "^8.2.0", + "@revanced/bot-shared": "workspace:*", + "@sapphire/async-queue": "^1.5.0", + "chalk": "^5.3.0", + "fastify": "^4.24.3", + "node-wit": "^6.6.0", + "tesseract.js": "^5.0.3" + }, + "devDependencies": { + "@types/node-wit": "^6.0.3", + "@types/ws": "^8.5.10", + "typed-emitter": "^2.1.0" + } +} diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index 5c96086..09bf3d3 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -141,7 +141,7 @@ export default class Client { this.#emitter.emit('packet', packet) this.#emitter.emit( uncapitalize(ClientOperation[packet.op] as ClientEventName), - // @ts-expect-error + // @ts-expect-error TypeScript doesn't know that the above line will negate the type enough packet ) } catch (e) { @@ -181,7 +181,7 @@ export default class Client { this.once('heartbeat', () => clearTimeout(interval)) // This should never happen but it did in my testing so I'm adding this just in case this.once('disconnect', () => clearTimeout(interval)) - // Technically we don't have to do this, but JUST IN CASE! + // Technically we don't have to do this, but JUST IN CASE! } else this.#hbTimeout.refresh() }, this.heartbeatInterval) } diff --git a/apis/websocket/src/events/index.ts b/apis/websocket/src/events/index.ts index 9724ec5..53d9d03 100755 --- a/apis/websocket/src/events/index.ts +++ b/apis/websocket/src/events/index.ts @@ -8,10 +8,13 @@ import type { Worker as TesseractWorker } from 'tesseract.js' export { default as parseTextEventHandler } from './parseText.js' export { default as parseImageEventHandler } from './parseImage.js' -export type EventHandler = (packet: ClientPacketObject, context: EventContext) => void | Promise +export type EventHandler = ( + packet: ClientPacketObject, + context: EventContext +) => void | Promise export type EventContext = { witClient: Wit tesseractWorker: TesseractWorker logger: Logger config: Config -} \ No newline at end of file +} diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index fab0d4e..6281ab1 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -1,150 +1,157 @@ -import { fastify } from 'fastify' -import fastifyWebsocket from '@fastify/websocket' - -import { createWorker as createTesseractWorker } from 'tesseract.js' -import witPkg from 'node-wit' -const { Wit } = witPkg - -import { inspect as inspectObject } from 'node:util' - -import Client from './classes/Client.js' - -import { - EventContext, - parseImageEventHandler, - parseTextEventHandler, -} from './events/index.js' - -import { getConfig, checkEnv, logger } from './utils/index.js' -import { WebSocket } from 'ws' -import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared' - -// Load environment variables and config - -(async () => { - -const environment = checkEnv(logger) -const config = getConfig() - -if (!config.debugLogsInProduction && environment === 'production') logger.debug = () => {} - -// Workers and API clients - -const tesseractWorker = await createTesseractWorker('eng') -const witClient = new Wit({ - accessToken: process.env['WIT_AI_TOKEN']!, -}) - -process.on('beforeExit', () => tesseractWorker.terminate()) - -// Server logic - -const clients = new Set() -const clientSocketMap = new WeakMap() -const eventContext: EventContext = { - tesseractWorker, - logger, - witClient, - config, -} - -const server = fastify() - .register(fastifyWebsocket, { - options: { - // 16 KiB max payload - // A Discord message can not be longer than 4000 characters - // OCR should not be longer than 16000 characters - maxPayload: 16 * 1024, - }, - }) - .register(async instance => { - instance.get('/', { websocket: true }, async (connection, request) => { - try { - const client = new Client({ - socket: connection.socket, - id: request.hostname, - heartbeatInterval: config.clientHeartbeatInterval, - }) - - clientSocketMap.set(connection.socket, client) - clients.add(client) - - logger.debug(`Client ${client.id}'s instance has been added`) - logger.info( - `New client connected (now ${clients.size} clients) with ID:`, - client.id - ) - - client.on('disconnect', reason => { - clients.delete(client) - logger.info( - `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}` - ) - }) - - client.on('parseText', async packet => - parseTextEventHandler(packet, eventContext) - ) - - client.on('parseImage', async packet => - parseImageEventHandler(packet, eventContext) - ) - - if (environment === 'development' && !config.debugLogsInProduction) { - logger.debug('Running development mode or debug logs in production is enabled, attaching debug events...') - client.on('packet', ({ client: _, ...rawPacket }) => - logger.debug( - `Packet received from client ${client.id}:`, - inspectObject(rawPacket) - ) - ) - - client.on('heartbeat', () => - logger.debug('Heartbeat received from client', client.id) - ) - } - } catch (e) { - if (e instanceof Error) logger.error(e.stack ?? e.message) - else logger.error(inspectObject(e)) - - const client = clientSocketMap.get(connection.socket) - - if (!client) { - logger.error( - 'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!' - ) - return connection.socket.terminate() - } - - if (client.disconnected === false) - client.disconnect(DisconnectReason.ServerError) - else client.forceDisconnect() - - clients.delete(client) - - logger.debug( - `Client ${client.id} disconnected because of an internal error` - ) - } - }) - }) - -// Start the server - -logger.debug('Starting with these configurations:', inspectObject(config)) - -await server.listen({ - host: config.address ?? '0.0.0.0', - port: config.port ?? 80, -}) - -const addressInfo = server.server.address() -if (!addressInfo || typeof addressInfo !== 'object') - logger.debug('Server started, but cannot determine address information') -else - logger.info( - 'Server started at:', - `${addressInfo.address}:${addressInfo.port}` - ) - -})() +import { fastify } from 'fastify' +import fastifyWebsocket from '@fastify/websocket' + +import { createWorker as createTesseractWorker } from 'tesseract.js' +import witPkg from 'node-wit' +const { Wit } = witPkg + +import { inspect as inspectObject } from 'node:util' + +import Client from './classes/Client.js' + +import { + EventContext, + parseImageEventHandler, + parseTextEventHandler, +} from './events/index.js' + +import { getConfig, checkEnv, logger } from './utils/index.js' +import { WebSocket } from 'ws' +import { + DisconnectReason, + HumanizedDisconnectReason, +} from '@revanced/bot-shared' + +// Check environment variables and load config +const environment = checkEnv(logger) +const config = getConfig() + +if (!config.debugLogsInProduction && environment === 'production') + logger.debug = () => {} + +// Workers and API clients + +const tesseractWorker = await createTesseractWorker('eng') +const witClient = new Wit({ + accessToken: process.env['WIT_AI_TOKEN']!, +}) + +process.on('beforeExit', () => tesseractWorker.terminate()) + +// Server logic + +const clients = new Set() +const clientSocketMap = new WeakMap() +const eventContext: EventContext = { + tesseractWorker, + logger, + witClient, + config, +} + +const server = fastify() + .register(fastifyWebsocket, { + options: { + // 16 KiB max payload + // A Discord message can not be longer than 4000 characters + // OCR should not be longer than 16000 characters + maxPayload: 16 * 1024, + }, + }) + .register(async instance => { + instance.get('/', { websocket: true }, async (connection, request) => { + try { + const client = new Client({ + socket: connection.socket, + id: request.hostname, + heartbeatInterval: config.clientHeartbeatInterval, + }) + + clientSocketMap.set(connection.socket, client) + clients.add(client) + + logger.debug(`Client ${client.id}'s instance has been added`) + logger.info( + `New client connected (now ${clients.size} clients) with ID:`, + client.id + ) + + client.on('disconnect', reason => { + clients.delete(client) + logger.info( + `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}` + ) + }) + + client.on('parseText', async packet => + parseTextEventHandler(packet, eventContext) + ) + + client.on('parseImage', async packet => + parseImageEventHandler(packet, eventContext) + ) + + if ( + environment === 'development' && + !config.debugLogsInProduction + ) { + logger.debug( + 'Running development mode or debug logs in production is enabled, attaching debug events...' + ) + client.on('packet', ({ client, ...rawPacket }) => + logger.debug( + `Packet received from client ${client.id}:`, + inspectObject(rawPacket) + ) + ) + + client.on('heartbeat', () => + logger.debug( + 'Heartbeat received from client', + client.id + ) + ) + } + } catch (e) { + if (e instanceof Error) logger.error(e.stack ?? e.message) + else logger.error(inspectObject(e)) + + const client = clientSocketMap.get(connection.socket) + + if (!client) { + logger.error( + 'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!' + ) + return connection.socket.terminate() + } + + if (client.disconnected === false) + client.disconnect(DisconnectReason.ServerError) + else client.forceDisconnect() + + clients.delete(client) + + logger.debug( + `Client ${client.id} disconnected because of an internal error` + ) + } + }) + }) + +// Start the server + +logger.debug('Starting with these configurations:', inspectObject(config)) + +await server.listen({ + host: config.address ?? '0.0.0.0', + port: config.port ?? 80, +}) + +const addressInfo = server.server.address() +if (!addressInfo || typeof addressInfo !== 'object') + logger.debug('Server started, but cannot determine address information') +else + logger.info( + 'Server started at:', + `${addressInfo.address}:${addressInfo.port}` + ) diff --git a/apis/websocket/src/utils/checkEnv.ts b/apis/websocket/src/utils/checkEnv.ts index 6bbe68b..00cee6d 100755 --- a/apis/websocket/src/utils/checkEnv.ts +++ b/apis/websocket/src/utils/checkEnv.ts @@ -1,9 +1,10 @@ import type { Logger } from './logger.js' export default function checkEnv(logger: Logger) { - if (!process.env['NODE_ENV']) logger.warn('NODE_ENV not set, defaulting to `development`') + if (!process.env['NODE_ENV']) + logger.warn('NODE_ENV not set, defaulting to `development`') const environment = (process.env['NODE_ENV'] ?? - 'development') as NodeEnvironment + 'development') as NodeEnvironment if (!['development', 'production'].includes(environment)) { logger.error( @@ -16,7 +17,9 @@ export default function checkEnv(logger: Logger) { logger.info(`Running in ${environment} mode...`) if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { - logger.warn('You seem to be using .env files, this is generally not a good idea in production...') + logger.warn( + 'You seem to be using .env files, this is generally not a good idea in production...' + ) } if (!process.env['WIT_AI_TOKEN']) { diff --git a/apis/websocket/src/utils/getConfig.ts b/apis/websocket/src/utils/getConfig.ts index 2b39b31..384b695 100755 --- a/apis/websocket/src/utils/getConfig.ts +++ b/apis/websocket/src/utils/getConfig.ts @@ -2,28 +2,31 @@ import { existsSync } from 'node:fs' import { resolve as resolvePath } from 'node:path' import { pathToFileURL } from 'node:url' -const configPath = resolvePath( - process.cwd(), - 'config.json' -) +const configPath = resolvePath(process.cwd(), 'config.json') const userConfig: Partial = existsSync(configPath) - ? (await import(pathToFileURL(configPath).href, { - assert: { - type: 'json', - }, - })).default + ? ( + await import(pathToFileURL(configPath).href, { + assert: { + type: 'json', + }, + }) + ).default : {} type BaseTypeOf = T extends (infer U)[] ? U[] - : T extends (...args: any[]) => infer U - ? (...args: any[]) => U - : T extends object - ? { [K in keyof T]: T[K] } - : T + : T extends (...args: unknown[]) => infer U + ? (...args: unknown[]) => U + : T extends object + ? { [K in keyof T]: T[K] } + : T + +export type Config = Omit< + BaseTypeOf, + '$schema' +> -export type Config = Omit, '$schema'> & {} export const defaultConfig: Config = { address: '127.0.0.1', port: 80, diff --git a/apis/websocket/src/utils/index.ts b/apis/websocket/src/utils/index.ts index 9fe7e87..913395a 100755 --- a/apis/websocket/src/utils/index.ts +++ b/apis/websocket/src/utils/index.ts @@ -1,3 +1,3 @@ export { default as getConfig } from './getConfig.js' export { default as checkEnv } from './checkEnv.js' -export { default as logger } from './logger.js' \ No newline at end of file +export { default as logger } from './logger.js' diff --git a/apis/websocket/src/utils/logger.ts b/apis/websocket/src/utils/logger.ts index 2a71a75..96ef157 100755 --- a/apis/websocket/src/utils/logger.ts +++ b/apis/websocket/src/utils/logger.ts @@ -3,14 +3,23 @@ import { Chalk } from 'chalk' const chalk = new Chalk() const logger = { debug: (...args) => console.debug(chalk.gray('DEBUG:', ...args)), - info: (...args) => console.info(chalk.bgBlue.whiteBright(' INFO '), ...args), - warn: (...args) => console.warn(chalk.bgYellow.blackBright.bold(' WARN '), chalk.yellowBright(...args)), - error: (...args) => console.error(chalk.bgRed.whiteBright.bold(' ERROR '), chalk.redBright(...args)), + info: (...args) => + console.info(chalk.bgBlue.whiteBright(' INFO '), ...args), + warn: (...args) => + console.warn( + chalk.bgYellow.blackBright.bold(' WARN '), + chalk.yellowBright(...args) + ), + error: (...args) => + console.error( + chalk.bgRed.whiteBright.bold(' ERROR '), + chalk.redBright(...args) + ), log: console.log, } satisfies Logger export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'log' -export type LogFunction = (...x: any[]) => void +export type LogFunction = (...x: unknown[]) => void export type Logger = Record -export default logger \ No newline at end of file +export default logger diff --git a/apis/websocket/tsconfig.json b/apis/websocket/tsconfig.json index f69c640..fd79f29 100755 --- a/apis/websocket/tsconfig.json +++ b/apis/websocket/tsconfig.json @@ -4,8 +4,8 @@ "baseUrl": ".", "outDir": "dist", "module": "ESNext", - "composite": false, + "composite": false }, "exclude": ["node_modules", "dist"], "include": ["./*.json", "src/**/*.ts"] -} \ No newline at end of file +} diff --git a/bun.lockb b/bun.lockb index 36573204465ab4fbc11e79d09d77c78be2c49c57..78b21eb9de591caa16b365c0cdfca07ea44cf545 100755 GIT binary patch delta 57150 zcmZ28S>(z-kqLU53vE73_vCTT2>vI(EYa}x<*q~C5`8!0wfXzsF~7@y<#R-il>r0- zCx*+{_qsEK*bEE|0t^fc{0s~Y=NK6nxEL53N=l28@);Ny&NDGE2s1D=>}LY2X;{a^ zz#z!L(6E4sfq{>Kp`nk7fkB9Yp`o4$qJJMFL|tWKQF<{01H*ha1_mAmhK5Q*i#(83 zlcD@Nb_NEndIpAuBOnF?Lqj?{1A{mNLqiNZ0|Pe$LqiiA#G+z0h{37F$%#3MDXGah znWdQw3=A_k85lSj7#ePKLe#5pF);8lFfc24RtU# zL|;K>TBR+>MJ?PA4YoWC3=#|s4XH&%`9->^6;F8}23K-J=(Nnd6y4GShQ+)P{+r1g z8P)5#`572Q7#JFoGZJ&M85kHA@Yc?Q;eJ635`k`F zaF6Ae=P@v>k^niNo}u9)D?}m82hW8d4v7$-vMs-54VNz!+j)T2X$k5d#AQ z%%wGxKeCF~GnqnUH=9D-_`?JefF&74`K9R@3@&C6TN8~TNyNn%l4d8GL&B}y9HQRe z0-~-wzbK`=D6zoR0wQh$70=Dg%goIzE>SaPV31>AXn1J}ws&$nn_#`E6+~|cDY zQgaI`85kHEtRX%;Zw-kvSVDu*Aa%ManMKyt5Vz>rLK4ApTd>&;nR%e%kAdNfEyOL8 zY$4{u%Bd<_a5>&mf+CzdauOJsJvCjz-L6Xi8@$!t!9_n&}6!{I4E7|oG4ICl5lQZ(m^NJ7!Ah>`B zmG6^Jvdc#@dO%baIzb|&peR3CHzz+m&I#f+3r~o+z9+`5olKi67 zKMs&UD=AIW%_?SKIP3^?Yn5F*|Z4AEB}4Dlwca)Z^DuuAc32t*&ORyh|4 z(pS&W0E>l%4iFzM^@bSqC=3!##retEpz1h39OCm#kT)0@7`BH&)L(?EI~WEskfFgB zs{ca-BysKb0w=15%U%!%toDMai}hk)5c6SRXvm6!Bo-eph=tbBL?Qr<@RDdq(8@y- z-9JZ2A~N=ZIOuB(B&1$VR^*bee;f-*7Pn#{;d9ji;*6$Ph_!-VkV=d(0iuh|3qm&} zK;-iZa&z+285oQcA*QD%mZpP}^vy(wch4n4)XOA78XX4{A;FbcP?VpP$RNeQlL#>g zR>H$e_Q^N7)a%((q0UN%s5+4f@pM}T*F*z`($;%@JbV8b^q8 z7C1tjITp-cw7ZZ51GZf;1Xh5 zHN^LqCr{)Rua~ZYn02QHVy8we#ID0tkk~(335k@H%p$OMfieaLDF%jyi=_~^In_hl zWLOUg^XYYv5YcIXM51&(gwD@P2IU0?P=yAnk#07CT~*(}T@SUW5n_ON6T|_(>mcGU z>L3nWS_g6V?@~yvyjKcI9W1O242ld44FyH1B_)}uMLDTyB^mko*=DT}hbgo|9KZwR ze`|s8pR|Bdb3H@Dg%*gyy)6)nRA zE{He_l;-P#6iO+nsRg>3#SH5^As#|6jw^Z?7`Q;C*y2t|(ARY`FsOoj(g_K|^iBqF zX9E;#i8;j#u(}!+p|HNe8))BP@#Kw+;*tCN!2#P4-U)GLzyyf%iZXMH64MwMK&F8* z3F}0Nd}dx+PG)*W$*&0zec7p%WgvOxNf3ToPGU((Y6^qLCq6ziW)hQ5hZvM$e0(ybc(;#8ZI1Q4DggPPl;Y$akDRZ*} zlG7#gJ0RBW?|?WK)~Z<60SV)d$r}a4IU{C4oO*WhMFH{pm9rsMCe4O84aN_e z4Kc%cHY7euGD~t&GpkZz%~x2n71j*poeN4J3=K*1Am)Y6gQWQ9^B}e{&4tkU^C9Lz z&8dgCgK@Qg(c8O99gsjcvJhg?6h=rOaW00W15n{%%D}+TwiuF5V7<3Liy#h~u>|4} zz2%@BRL{`RwH)H2jY}aC>G=gIpeRjV3em7)Da4{QP?s|?IpYRYUELChc;!k+$W2%Z zDL-$mgg8iaDI`b8L(R8d4aq5IRzvi0E@NQe1a+_XtcC>j>eV0@F*GcKDlk|LaS-ov zNYDx`gZS{FqPrr;nFHJaTd=#DYE05KP(v ziHf$R5cLI6^*&1(>cImFwonbXwnBWUy%gf3UrQhf{LvDK58f?;$lqB6kw3Wz67(sl zNu}uw3=HcQLDUze7U$=br82zO!N4HTz|e4S2gKr({N$3N%)Hd;iy$FrzN;Q$K=UGq z57QPwipdD5Ma7Je;*w(z#0MdJAU^!D8$y>P79=L+q%y=pOFv^q1_mt#h6X7{NRC^% z58{B_eGm_{?qgt3VPI&uupeSxdVNlQQc@)YLvc=GF{oi5cmNV-zWW#$6c`v9elbAk zsRtnjNFRbk0p}q|2vi(|IQTgO#3LsdAQtX~jukl`frQxPBM@^9jzFSR9!l5qAA$H} z`%y@oLAzf@uvQhUznhzyUk=I@u)!i&@oIS-;-I3`lvGf;Z*c-_sL?o>2pLzil z@%0Q1@1Yi|UxmcroU4!|+j$kDq5LW&MeMu=kzaWYV!=!(-3g^Dp|lv(9EPiq%zpPU zME?>7hzG41Am)^V8pM;=iYeC{-h@VV1*m(+z@Tvx5_Zy1@qpWqFypxip{;L2Oe)Pw z$xO~D686Ejd+&Mej~Ey`hFcy+p{1lK%E#1i5}cf!Ois$>c>6diCy4Ax@a`8WKs}P}&k|b#7`&Mt({$ zgX~jC*s(#aF3l^*EJ$Twc=QC4e3J9?voli}7_L2mSb6vfB+TDEf>>9SSgxCtpHd0y zwXK0xB36eqDKS2unvQH2TvOhsw z{^JA0htl65;%`4d#E*P~&_$^wpfG3H@(rS{`8%Xczx4qU0*OCBIg_CQcnJ#lrD>$h{F9*#@CWWDISkx(z2 z_y5Yt4=w@DG zEjsbiWKHRYdv7LgKYTb-$*_a($@i#rr&ufk_sx(~*xI@|UT-EF6C2ayUnX{p-jgj& z?O7i&F)##7wl%e8HDYF9@B*`Hm>C#+z^oI@3=D2y79R@(gCCd`!@|Je0b()kp8V6y zp3z~lrMW%h>dBSn_MC6n7#Iu~7#dh7Gn$(-$*@oUWp2mVG1=0>p7R_#1A`4%%F)7{ zlZ%6aA&h~cfqC*lYje&74h9B028IR>FmE0Q1A`X>Lj%*~Msst<7n3V3?K$;085k_U z>Ko0?Iny{97>vN`L1r!FWMHsjU}#{U>}YQ>xzfs>(~ygS!3k`FqlGzR?c|?U_KZ6x zTUy(*zUN|Ku$XMS*M^aM@-AyT>9`y%2X?AF?8}y8+*>R zJP=_suxIwBIODFGivgcec1hJBH zGAK6Gg&|?jFqtvlfpCaWbQeIpkn_1TB-q(O0m`W;191%7g0h+Q9-RzSa-OO2YWkJaq6irKIA$BlLW^^>?V3C8E3QaI39GsDI3=DDL z6azA4yBx$bOp_h0%~{{effKroHIugdo`dpOz#e2#gqXqr&NU8-kkrHmaz1B+A_Idp#A|NmjJqdS z`r9*ppS;uGo;5%TRNR2_-n_|{0rs31lo%Lvz!40Jvp-4@+abYkq6`TVMv$3|t0!9q z+Os}Z1_ey5r8TFP3dG$kpr~Msnfx=*p7o^)1B1)ty*AdIx~g!!=H{G*s*vo$3Qig8 zRUw&-3G9#mst`XyQiG`)#05-~8(qygbJZZeVw`-?-JEl+8Uup|IH*Bk^G^*D6%3OZ zt<599`_y4>IcROp#H2CV%EFG*P6LwCSV1X*vquAB5zFL8OLNXM z8W1P3O=kRH!LA8$Eh5U(G$B5RWWM*B$jQi73*u82u(8!zFoOP8RZ|-(&XK4M5l7gyP#fY)NQ~UphB+FP*kpAeDFEV*NF9jH?BK9EpaXFu)8vCb z7VJ;Mq}m7?Xk5R)PH?9_$C0yC(l;QXk|z+lS2(7*-CO01@OpsH(c zhRtNlXnWRkdeDM}lS3ck8rI2-|12i|jJ9W;ug}1cG}+eEnv>rE5)P11aW#OL4vGI- z1BjoQLFu3M6o>~Zs5rR{AueJBr;<=ZSU51YS#TIaoX-k!5a(N{LI_Xa2oemephU|F znp6Xo=McHQMhpy@3=9oC;K*5I3{lBG*)hq2!x-iYP{`_-z(NmHXT+I6e8vuT$t)8{ zhKCr*X9`JoEMV)LOc@vqAr_dMa}=9G%tVCpAybIM!Nmg;huP$+L_5}SGftMJ=B&rf7#QNfL7{36Dc{*AH`ZHlm_yPVxR_&Nu$cTS$&N8)vSqS8=X?uD zz=D%KV(pPd!N0B~8vQDz14 z8^ZmotY8iVh0+r%1_noPodv3&{H!5i#taVJUTa9~fzuP~V{1@jXs@+3tFa9zz1Uh? zGv-eInP$(q!Ukd^SRLmB8<^h?+L$xiOx~Gp&$xN=&vbjvf3^$^df>dz7-Y_>V+Sf& z|3+K0X4rw6RC{f#8MjRSnPJcQ*$$TT8?DV*UF{hdbSB%TSabHe zVkXmM$0`dBD2I8nW3)LZlN&?|oDx~B+!z?_z-^=oH;4g{cr)nYd)A?cS3Tnx^1hnUX}cF09{NE?o6a$}4IhX*8JASo?rvSooi z=SmNVa)^H)dO!l48I);Q-915t#$S7D&R$PQsIY;m4bF?6kfKEpWCc^P*W@Z|JI;q* z5QCUNfx{~4&A?y+@)u{6Hv@w=149EZINN{qhNLj2$&RZmSbP{5j3?U`SaW9iz%(Cp zH)mSpGug`5j_IDy~sqaR2Q zXQUq_cQ8#pxXFUU52h1T5#9BJ_yJNhiTgt;2o`W*neNZPV9vnMAOv>gUVli22ZsqO zUjQhIK-s`20GgCI>jGdZKneCp0I0MXVZjmvtB7rbA&QtlNrrLC!izEI2|S3OK;Y zVQUD)*N~c;ITYdu&dCSU%{d)IA%Or%w5?D%Hc%6o^KdAnZidtt++h%X%wU&!hC$LJ zq-3ZKgV+c580Y6ONOt1@B?m^^$(1$soYmnFH-O7jj@{vq778RagX*g1;gBQ?Nx;ex z5EIxZJHD{sh=4c)-0I`l8Ubkr!L@R*MnaMhL}bHqPL@bed&t(-nsZ(xlDk-MM1rEK z*4mm=EDF+8hBP^wqZk-K8J-zjjzqz%1{H_u(F_c}3=9nd;DGFiW?*oK6^^Xuqd^5= z?H3!47>E{5aPr8EffNdCpt_H9YYfESklq?YEF=usz+q??3kiGn$&RNiIAS5`l?#-& zI2q$0857)c81!i2dNi$~iY4 zQdcm6Vuq6^0pc2noL2&*K4%5HZ9)Ph&>`93MgqhE;M$VIJ`rLj!a4Pcka_}KNHRX2 zT-j>RX_5pn9nvBwN`mxZ89@QbdLjvwCH^{Cb8sa?Oh%YxmkhBP(jx9phU5U2$p`Jt zIiDs&LJU&iYNkMJU&s6LwRhI8>=liQXw8>1S@x&ytBie zvmq5?86*g9rb2=a>9C+q07@C)5#x=pX^v?!I1$`$T^wulm$x$s0{%sS^6_TRikZ(4M-l; zzhnn_fpbqLL@T5?{*wu+k z;DB-<`RQ~%B-4Of;GDnnArS^ClUxfR?t(=0)B;GzL0Z{Y3m^#tlJZ3hAr^qcg)^`a z5*m*4Gvhd2NnfJ_P%lXuOu<4mf6X>a^tG1+pKJ?D=K zNb$x3?pwN5LPDJdoa!f3LYfzR;L`SGB_zC9CpX@-;HZLl9pc*1Do8Q~H+(rep>oWi zt~KMk$(D2MIhCp*E@hp}SZcvh4T%*P^qVso0;LOjO^3QyMBT984YuJ)chU9-cZ# zI5ST^Xl21s$G{K_9&_POv#@O%Sg@f;XoLqLLjX$GWbTxt8Oy`gXd(BJIk6G z7$O-M8aTmy`twj8H>e)qv}l2O8#IJdFu8JxJ*!tM$cME{tXXHZGBAWp2C?43#q8Q( zVw2jyCCL(N)_ZL*9eVAcvJRx9rk#Ny2&9+uTsx$$2m78ec=FF>_M9g=A)&=E`JjV2 zr(+kS1Y?@)ILn-~vkQ`7874c$^%y+OorW)t5(=?R(C_v0(j7Z zbz?WERcvc*&H23>k{X#oiI~Z-XL8j_JImj(A@1Xxd@$LZar@+-8|+zGr+~ul zuZcBd(&WmG_MB6vKmwf|)Bt9^2bQh1vSyW-3Q9(x!Zu?n#O;tWYr#}V5d$ua7%xr! zxyhbWe;ULd7EmH#O`ZlC>;Yw;+0!7o8k~4oUr%FThyizV+^55;gGM)V&S}#b7(&4j z-I!%DdFK{;&WITd41wSwJ4Xw1rj;`$|5|3pd2a^9p^!#|+)PNB2yTrt=1l&%)t+Q%0M~miz4)7f-I-Z_jGKoPoi6^51l8&Y8<0zJP@B{pAoJGJ=!3 z>qa|hybTQ&>)Z7DoC1ToP2Pa1;;8#X$jWG`f3$8R&A^~16D&)8slWf zX%-x-;byyo$Dg*mIyMo@da*3z0aVIJ7v|ZGq=(cXQ5HTVT-#3K_et z@KEzI=bW+?VkEfo;(W0cQon#Bnp0;RBt*fMvsP>ak5bxLb6(p94>4mQzr#a)k$ve;5bMowfWJ2c2jR(yc?Iv5Evu9jBx$>MnC;LtYhBQblS(|g_?u5H~ zr#aKgo#4#)a3{>UpgBXYU9cqUSZKkq3m&wl=A18fLB>wOZ7NQ$-4ItXPj=jI&e^w{ zfguYrO_FTRDY6F=RxFbp&CNMe_CSIdlGWDiVPNnCkMlLoHRt@g2U0vh3O>`lkZ^?b z?K<}|Fw{VXSWjAT?1LD>HrcVjob$#$h(ZWYXFnu}A-tmfkWpm_@6LXRQy@88<^ZG> z3uy<%9DoJeL2Gl?z5}3)0UD<`a{v+|5EGORBJ&O(M6!!l;}B$6gq?wbfps#YuQq5N zl{fhiL=8xkVRGYKuqfxsLy*9PnELY&B>2El&uM#@fgu_^)Wx{boO97(h^a!8K|KE> z5S5VNn|B0~wHQD}3e%kwOgBWx}1OnIJjcr>^lKVaiFw&>IB5`-~kOLj+2wCuG?`sorJgyGR;tR5>l)| z29&p)gv2uwcz#dm6vQG{P_kkQJ2iRN4Li;`rywZ^G9hy56vPHdGP$wTg5?~z zAhfdP6gdwOW(C!6On&Dl@49Qp+It=>?QhL__&mgC;I2RC*Yl9kGjM9)w7GzkHaLG@ zfP@wcICw2DLh?5x>}Fqtgq;YevCee=;^bZT?U=MLP5yP?jw$BSWUB{uoYO8rdQ1?b ze_nzG6Jv@wtK($`hS14>AKGwShRg|YKr-%SNbv$m_G(ukSpt%@%dS91{lLbt9=^iB zU_1HmWgCvG$ccNyRY-{rQS|jH1A{YI(KSdi1q*YYx(2BqAWa>S>oA8oTAOpmz)8q!SrNuWOWfs_Urad<%TRq*!WOsA&ucviP%{M1kJ+tH7bQ3aI z%`lnqfjQ^Pn~*%j1{rt11sdf6C4}f(5OMa&jn~bYx^GS1_1uni|1Aav^T~V9+pyeb zV6Xy!ZK~{H|J%)@3F@ejkl*bUif{P=j9gipPdT+=1^D)c^pi;^C3CyB{Hs(zA zPbOD=u;Xlc3NaJXgxd5Jl2O1}mg&RO$yOijm~5U+uKH-lRQYW3u8(${8=gUwFn~*! zch4XWgfwc^oF+~PL7vwgDlKBd|pBnL!>{La4^-poc!yH9p^fz1S2RF za^8Chu@78BvdX*y_XKRLIm2GTim667bEckGldZnlaqN4=0AAz(HfPjBzw9`L-$P1J@E8KC>w8f4-FwW2<2~GoLFSx$ z-$RNB#>tMW%{l+Phnd6p)tt%X!(^*JcAQ2ZAz=U+7SH<#@e?Fd?Dzx++| zCcmwxHIv?_$yI;tI2k@es#r)WaQY1K2*c!qH5MG7VWxsI*FLBeBsBhghIAO1z)gpM zFOXV|1vDqfx#SC^w1w0yU%o(^HISOi>nqG{pwOT46_O+%?!NRDQu?t@2F0t+H@He) zbI!7F5S8qpjLvZ$%7dhi4YN2ozC-dN1GqY~{tk;OkR@}z!$zV(nef|pNaV0iZv1Y} zS@{Fj00zlj`~eHw#swA}KOr`PhX^^{e=;yQgWI>DDD4H-4USl_ye|U-0}}&y@PvVd0bI+2RRn`c2p=RL%AgGq z356OI2H}DhPJ&q=KFG!4AOokLRbUiX1aYImlHfKk*h4W;K8R+X{+5kV9qcdAmRXQ; z&=Oq`4O+GgqQSe>7#J9kX^^)-dm2C{foRa77LaYt3=AL|+|*28KCMabz0gpt(?SWE!MzK2#h;gVZg6@{ws!R#^rWUj`}+KpIy- z1wb^&N9&<{5DhYD1C&n;4Ki;N$OF^YDl>{3f^=>H5ey6rAR1)cRwy4tgZSH^d=Sk! z{Vpe?cs)q?08|)6gZKxbd}3%&$~ppa1p@=aQK&u;4Kn8#l#fhNlfdQEYnehfH4x&MJyo2(w(V#T&1u74sL4N%S zpZ-LRQ39M|OQ3Qf8pJFG5ey6r*l17+ zt$@lS(?Sdk3|-JL>w`LV0wW|SCNqLJATZ2>iZ6ibU%&{hlaK_!W&A>@rxrmCTn6PY zhti-uy`Xpl(IB%{f(Ql%24tFtfq`KwRQ)z6y&X#LfI4IsBP6jf?1CBqqCw2vj0_B* z<>E}o^D8^nvjeQL@<}FknM1vCDM@9y4YvKnq!2dwi z{e$wEm>{`;8A`K2X;vmkIB_vC)Ps(GVi18!h=M`|v|$Si4YFGdDh{GSObMtyNhSsc z4h9AWMUWtr202g_$_LRPd0nV_J*c=I6SQ@K5CA0*1_lrxq{aa1Qd5w|>3=mD#i#RV zF>-+HW3T`zU|>L@8K=t%Fp5vt5n$v1hpaVJ4KfW%Ar4HCZ0-aymw|!78R`LVsCp0$ z@_;`Rc*7<`0aTnA8lG^VUPvZ2$=}Fff2<5OWiVU|?X_1PXspobQ4PAk(14y%#DD zqCrg1abutm1JQyE3=9`Qf>0V{;YFy0m!KhV1baRAxr3J(lC4Fd>OeFoN@byZyfi5N;Zpz#S|w%%1~mo-1`lQi22fP^K@A4cAkPFa zLkh4!s5m|v6f!|jhlenO!k>YG0mJ|$Dtt6ZV<^-D5DnsoK@Ez4ii2p7cqG(8QBXP> zDi5MT@-fhmj%Q}52iJ)SP@kni6@qAx&oZEV5Dn6p3FYIXLF%%g>OeGznF}Hq7#Kh_ zNInnB&ttBK#9;x{rG-!l5DijT1odGFGo+xYfU2v6sw0L5DFyA*1BEa$&B?&P&{_|Y z1u>9lkk8wp7I#9$K{P1t`=NXg4N8QQp$?h?6$jBEhfjs_k!g_nnNV>M4RV0%BB%f| z4RXK|s5poQ8MqY62hkw@GN{GNnHd;3r`L)x%7dF*o1n^&X^`^GP;n3qYEB)62Ixts zF{hyN$TUdkEHeXxIs*g47iLIh{1dAF7f6zUfdNE=%>NBaN+1vZhf08GkOa7)3krG` zNbY84fy5*`3nbHUK*hO1nn7)2s60Ol#6l6MI#DPs2BpPWASp)@%9mmRWo6Ky2h>1W zsDvEUAZ4h*>QH$tD6I>n4WJGH(I7ts?ZfG0ir=n zPY}VtzyP8_A>svfm^V}$M1u_QgX#~2(jicFp-_v%p%zC&<>R38@lbw!5(}h6&V*{n zhHA)%Dky?#D2CD{Q1NmoT?KUzG7WNYHPjrC2SNM>D8B_7vYilGj-ihQQoT=yS}+4@ zAc$s~Zfgwf#Li)Xl%tEF?py*@2BN{0J5>7`sJcy1d1M-tMYgaoFz_-kFdSroQ~)QT z>X2zr`F##5evSoN+?{6u@1kb73AN}Jl)epB2%b&3xuKSkZF*E#h~J(&>)Z3i?f0^vNI?{4FJ&~ z7pg;Dr~ws6ra>AtSs`ggA1V)`K@K#8hL9;#92*TX-;xzvoYXVeK@}p?pn}~6DozXy z3ITUkNcrOlrM)2*Fa$sy1Ug+2m zG^m2egYrQ%C?N8od{StT|4LaQX}+En+MDWx8bk~Y@<=z-qrFglq|l&TJq4<73ac|H zDKjvD1V9E)gGzvC5Pt?VL}s!wfX=aD*a4MCrkSSe$}!4MkFj9n02831iD5tF6jFvW zPz@j&6l71Yb_Yn z!AavDG>^Q3CZ%`K00Eu&3JN-88Wdn3q3Zua)ibhz&u(A<9c&9ykBR=5lA4G%rYEX04q2eGK6kZxoz6M)8qz2Q6 zDl~vffM}3HLnt4a1{rJu6$jBEb)Z9xK@JDeAbATmNM^Nys)NxWHK5575C*eBZ3vJW zQf5p*8bRvGoH+q$ho&C}hI%J37iDS%ECLb-`Gop2Ct%}23sONn0XdWz6Oh#C%n2wI z7)EDKkY`LlK?34~vdZYp2`DreMrTeyy+qLZLk0$H6C|LL6{`fO*kKr*IbmQJojHL{ z42;g4KxalqXHGyxEAkA==*$VIy*)Z}!oa{VI&(5Qb22(}!Z12>GCFetswEj1Kr<+y zq0Z5nlhK)z(U}wISSa$02`HO@_@E}#=*-FJ%n5XK9^Xs~Xn<>U=7eE%<^)uBFrdt+ z46>P%bUMwPY@cb#$a2$R^ZCan!KZAlq{Voijs3mi8v8QVt7%KQxvovu`^lug<5FHr z%&~P7en%P4*<)`1>!EDD$&4kpcL)FPnYm?7UyIeg>34;gBWs z`|C5N*FAzZSvXXl7ZX=1u{!S&vM)uoby>if^)qHv_J%1le5twil)oKkmNm>ai)giIOz+%8#wfz6IBj-G&KbE^%1uCBIYZC3d@Bcr`~42F&G z6a66DMIg?rMt0tT%W|tO8$Y>Su+;uaN}6R>$iJ|rxn76+?0;&>go{K6{|}XLzs*!3 z`T4=wn>ixWl%t*R{Svfc)_0OGPYvD2rM_>vtq7AueV>qc#+y&qj{Q;AlF8aV&8s%J zFY{p7d4^NsMxU#m9l2DqI(wRig7O{BlgW=(N~#{(`GCLJW6Lww{E*fD*X{@1LkhiG zWal+sF>PHw`*dBOr*=Vp=Ua}%wv5$Mp&A!nA3u1kMP}E{%39t0oxL@Z@-;%+D~_Is z&Ympab>6j9`kcdqGYuuiN;jv!1vyV9XXAn=H-Z&3{9NM=GM$ZP~E@<%Ld}UPl&tmhBQzCy*l-v`P~iw+t-LSoQn( zrW6JTmFrLRSh1Vl_k5n+nA3Ui{k3_wT zBzz(>C&o*pU9A_JwtDf86FCfdvcVrx_x>&9W1N+sbAPY4|BXBRPI6srADZ)&TlOwA zsnbkcyYjJ8(EgiWCWmu7&O>ru>-332OySdah%@m{|0=}f&DcKOQJ5)wdWHlO@ARd@ zOx}#0(?5a)BqW)5r-zC#c{6rTUns&9KK%qpfK`;qo3VF#peR%L^a?2^-sxLG0{zn& z#hAjUYe+NkPEQqM@@AYkeIrQV21r0uoXMMU^7KS;rts+`sKpztt)30$2ns0RuUV^DbLF?lmypMDS|@B$>Ds?X%jcyoH8J}5j) zK;Z!rxIJCb02CgkpzttY@@Bj{{US)<2S~uwkjb0z{`5vePlwLy!Q61t@(OGkG&Uoj%bR6doXfuONZv5T|EYg2Kau$(!-z^p7S?;nO9o zK;dD^3RYMt4xK_<({1B%nCm&;yiAd_ke&!Q{=TJpCa^fWr?ID4tB-jH=TodV-P(NZ>0- zKz+KS7bx5KgM!41$(vDg`bUs}L;xs6yqUZiwWlxi1|<`a0ILs^H>2+KKp#*t2?T`) zNI-u&qc12tfjQl-G36Fs5)u@97Icl+W~^Aj)@oU^r7aqu=zEAj*F_V+2z; zW5D#t2&QI6r|EYin3{QE7t4cIuZnMXOlNw`xIH1ADTHzI2NRa*51N@cSXBiX78y)_L}aS&6Lb|eEY_1CUz#)o0+HCn*S;~GFtf!zcO zHuN~FTL(O%CLb`-0BPG^Q^#b&$QZeOV?EPp z_M44N&lwpfO+VPoq>dcv+b2w6y23O$!9s)+c1hoy?FT!U_!)U&cj#&Fh58^I>~_`} zhZz{Srr&L3$_F`uY5URbv$~kv7+EDwLDfBG1o)fgCTLZ%bdwTjobJ z1v3lCn;?fm#pFS2kf3665WV%FTO}FTpn~#Hixn9d7&xI~icm376x{@c9|Hq}GE@u{ zMWAi!AYoOAfeb1P3=F400t^fcYEUs%1_tmdFi;{^hdKtdZEQDaRV`@Sw;og>Xg|#! zkZ&0n81$iH8Vn2!pu-bDW*R`nG#MZ#VSwV=5V~bcivfI^F9S%-1gZ|SJJkoIk%581 z6e^|zVzYs-R%0*&ZE^y6RF{E)!3>%f%%KYPKnacwe7PHg1yoF*fq_8;s?HJ`3I+@e z44+vcXN*`w)qzejn9T~l$Bn@TDrUsMz_0)+W(yUoH)dd9SPD%@c2GeR1_p+WP%(R` z??5TqfE9F=9Rq_CR2}Hj!pG1A=M3GoWX{0A@Pq|?(HsM)r~!G-0#uej#XvjtKw_2* z3=DCgok^h3@q`*^1>LR*67zzJf$n|W%>cg4lEE7)X2Zb10E#A1!t;TO*@D!7OlDwU z@P&$jPDhc0ih;HSf|&N8)zBaY=5?I8w4>y zYkk#03z+etyFfcH{wgiIK2Rku>Z%SqWZ4U%VyMfl; zfi{DKd>jsSHE7$v8%UOcfdRH}5Of?%AXGDK4jus*8o%0ZNofjL=PvaZo|fnJ%EyBS4Ochl=?zFff3EAC!eadl5lq`$K~tB$fzO z7XV5#AdL(R3`x+08OQ)W@rwbJTT-CvfL93uatKdO71GM=Oq!)CS3}|IPC~<=}I)cO^7#P6Y z5I~7L8){x8DF45Orr;c?U=*lgVF91x1m6`2I*sNdR1CC35>$GEPNMk))eGAc8Oy-H z0NQ#4$_1bek|2xY7#J8p+mk@KpcraLJOcv*XrCg;eg@DEN|3??1_p*nAd?vw7)qfE z6B!s7KpU4qIin0J20HU5o(X(PA454*3^btG!UR5UiUGD06Lc01XnQy)2tm6sL1_VW z9u8=eJ}ALggEqN>EKUOzw@l#U@fbkcFhOGA>x)4Q1_p*&s3o97bJjz}>Y!qopm=A5 ztcr&fW?7©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�tp+ zh?N}B{yKxm{^=WjF&ibR~oNThK!s>iD8^QvZ!70C)rI@-7OmFzj zyalwh#q1BW5oFX@L=l`BaEt;Pf)~Wh`vdadK}avAz;gbTYLz*=K$hr%hX*bmgp{E< zEQhb%U$-X@+&cmH=k6bz{^k#}gv>jriiqu%uRjUD1Fb@WHX;8UoUZT})V~sfv4^RK_msx@-{P1+Hf6P9N;Leo@n*}JwdGo!XP$y01d`wyPD7fY487kDFL(NV z10e%V4@|R9PoKfaV#Js+{WK$sr1Y}W4B#a`31|Q3Dkw*}A-oG2n`4CZtz{0MhO`K3 zt_f`XyT}YQ0%W2GUg`CLb-EuDi;?tfsEUdDQN3zgZ7mTwEo1s@CKgE<^!2I&jCQu5 zGp`sJmYtb?fr&+e2}VQx3CkR24SJCLaBtgc{^X#g3&2?(94C9uLLy*&$9z5a9na2! zdpl;Jek){2r-Y2ien=9{)43v@KJ^!DIg0@U10+O1OaB&LW0qukGR zu&@|0T|PhEhJ{5!=H+=vo!EHqb*lemOBqCv{yIOsfQ1E~hh@AjK$@OAw>;0`Kk^IQ zqJTt8%!TRuKxXA%fRskIT=5T{-C_lg2S8Ln90M%}pd2Qji_;xgStQJ2E<${H>Ut0R z4CCs3U|)hWZw^#uae>J3vi5ExgiOuF>3ys$5;8p(A`x;~=sOM=2^FMsG z7i9cP5)|kHY%E3~8eB5Pu(3$VT(}G=0(K?*n0Kp?aT_QQAPX(;U!I=F#UjDpPo@UKkHH^2WUmCsh*($1Iv}^BJ3nXP!; z|6ly8GetlH;^1D8$Cc?d>@2fn&{Gu?y!<|Sb-D~Eiv-i}%hP>0SR|P4U7a2P;{Uxm zy@7+phl%gn^m8065@z?IPJhn*$3Xf1y>m>UwY=bFsPZ+4r)DKBJ~Dle$3(EFz>Nx+ z*(rTxLT96pz`E zpq5~Ax;cFh$aTLjPrt^+VkEf=II!6zDy23m+4Ffi=AIsFSai;>w8sEP^SmM+*O%M2R8H_|f$ z^}t~VWzJbyRPnNV87Rqvyl2e7@Z#q57#b$D4o(^2ufER8ZaoMXtqP=9awA6^y- z#=7aEd@Siqo%g5L@v%sl9lQ%EymU@oeY5t0uqzXzp`H;qnN7P7X=dDYcD!>p@YhPv z_?s!HmBFy`{`3Y>7K!Ok_*kTvw%(t{&*H;$^#1e>A}kV2=k8C>;Ab&nx_N*491#8V z{`3p{ED}r~?oWRO;xj&&E+@cZB*XInQuc6{rPp7*|4$oaI(VFeLF&Qu903+1CY1-% z7YMLOFm_J&7G;r~eo25uipk)?^mhU*5==G^raus7F`8~A$Rfq$^|xT zfH`J*nJ`NR%yy>BkEe@>uo#(X;%t}4J%eQP>Mz$U>sJ>>UDPsl(h!c#ai;1$BKmx;<2@(ToFt;-9o6ac4A_;TtbQ3X_ zCM3CwVl2iom`i_dy@XU9kkFA}+%(-%oCRjK==5fBmOvy!&@C{S9wfmMgrop$i`{ET zqwBqp+YPgCNuaR`V@O>X{CYZ{B#VSh;%f#5RR)HJ$fq6kZlZ5Niz7gVt^or>+w190 zk}O6t(_TYHA&j?s_4i)A3d%U3-lqWr!`j!=CxFa2@EQ_WIVmmW2@`gM+C}D&sjt(o zryl{Car-r-Y`K1Y$>H!de4zEVMv!)y)Pw0lQY=PHuU=1glVUNF`TZJ_#WPPe{ABZv z@&{)vaI!IaJN=9_iv*M1+v)41SR~9m-$JtMA?7zrM5TU%3VLXJE$S`A#TyqZ25&c! zyu-v;26b`a+v$I#SR`byx(8fMx4xZjAk89SHv26kHiWCvj%pgIgF1Hx5En0h3klO- zZ~oi4{zUI)^9>@B2pv(tJc{AR^3Ij{<_>9|dW`Rp6K6qUC z?!$CH85RlX{DZtP14H{qNTvJqhGzDCn`y$(+QEQ5A zn4l(1&y!=3Vv7AXy-SY8h`R%{s~&XRB0tOY)v~No(;v#QgfkwTZYj?qub80#apj4w zO@9st1TL#kfW-9UlBP_76wljD(<|j!jN+kQ!&MS*d;Tri9H^apoYgtvz(vK-yBo$oP=s3q45(0L)C9k6WEb!AwULHp;X z`^m6MadChS5n*6pn87}Ml`O0D^j;a(&0Hs-f;mu+*TUOqb#nP~|%~AZGZ=u?lm&fC|cRPS28KeFXrR CXrxmB diff --git a/package.json b/package.json index 6ae41cf..102c106 100755 --- a/package.json +++ b/package.json @@ -1,57 +1,57 @@ -{ - "name": "revanced-helper", - "version": "0.0.0", - "description": "🤖 Bots assisting ReVanced on multiple platforms", - "private": true, - "workspaces": [ - "apis/*", - "bots/*", - "packages/*" - ], - "scripts": { - "build": "bun task build", - "commitlint": "commitlint --edit", - "format": "turbo run format", - "format:check": "turbo run format:check", - "lint": "turbo run lint", - "lint:apply": "turbo run lint:apply", - "watch": "bun task watch", - "task": "turbo run", - "t": "bun task", - "prepare": "lefthook install" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "devDependencies": { - "@commitlint/cli": "^18.4.3", - "@commitlint/config-conventional": "^18.4.3", - "@tsconfig/strictest": "^2.0.2", - "conventional-changelog-conventionalcommits": "^7.0.2", - "eslint": "^8.54.0", - "eslint-config-prettier": "^9.0.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.0", - "eslint-plugin-prettier": "^5.0.1", - "lefthook": "^1.5.3", - "prettier": "^3.1.0", - "semantic-release": "^22.0.8", - "turbo": "^1.10.16", - "typescript": "^5.3.2" - }, - "overrides": { - "uuid": ">=9.0.0", - "isomorphic-fetch": ">=3.0.0" - } -} +{ + "name": "revanced-helper", + "version": "0.0.0", + "description": "🤖 Bots assisting ReVanced on multiple platforms", + "private": true, + "workspaces": [ + "apis/*", + "bots/*", + "packages/*" + ], + "scripts": { + "build": "bun task build", + "commitlint": "commitlint --edit", + "format": "turbo run format", + "format:check": "turbo run format:check", + "lint": "turbo run lint", + "lint:apply": "turbo run lint:apply", + "watch": "bun task watch", + "task": "turbo run", + "t": "bun task", + "prepare": "lefthook install" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "devDependencies": { + "@commitlint/cli": "^18.4.3", + "@commitlint/config-conventional": "^18.4.3", + "@tsconfig/strictest": "^2.0.2", + "conventional-changelog-conventionalcommits": "^7.0.2", + "eslint": "^8.54.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "lefthook": "^1.5.3", + "prettier": "^3.1.0", + "semantic-release": "^22.0.8", + "turbo": "^1.10.16", + "typescript": "^5.3.2" + }, + "overrides": { + "uuid": ">=9.0.0", + "isomorphic-fetch": ">=3.0.0" + } +} diff --git a/packages/api/package.json b/packages/api/package.json index 84bcd7f..9604004 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,40 +1,40 @@ -{ - "name": "@revanced/bot-api", - "type": "module", - "version": "0.1.0", - "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its API server", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "bun bundle && bun types", - "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", - "bundle:watch": "bun run bundle --watch", - "types": "tsc --declaration --emitDeclarationOnly", - "types:watch": "bun types --watch --preserveWatchOutput", - "types:clean": "bun types --build --clean" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "packages/api" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "@revanced/bot-shared": "workspace:*", - "ws": "^8.14.2" - }, - "devDependencies": { - "@types/ws": "^8.5.10", - "typed-emitter": "^2.1.0" - } -} +{ + "name": "@revanced/bot-api", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its API server", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/api" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@revanced/bot-shared": "workspace:*", + "ws": "^8.14.2" + }, + "devDependencies": { + "@types/ws": "^8.5.10", + "typed-emitter": "^2.1.0" + } +} diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index 9daab24..725a811 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -49,7 +49,9 @@ export default class Client { rs(packet) } - const parseTextFailedListener = (packet: Packet) => { + const parseTextFailedListener = ( + packet: Packet + ) => { if (packet.d.id !== currentId) return this.gateway.off('parseTextFailed', parseTextFailedListener) rj(packet) @@ -84,7 +86,9 @@ export default class Client { rs(packet) } - const parseImageFailedListener = (packet: Packet) => { + const parseImageFailedListener = ( + packet: Packet + ) => { if (packet.d.id !== currentId) return this.gateway.off('parseImageFailed', parseImageFailedListener) rj(packet) diff --git a/packages/api/src/classes/ClientGateway.ts b/packages/api/src/classes/ClientGateway.ts index 8ff72e1..eb119b4 100755 --- a/packages/api/src/classes/ClientGateway.ts +++ b/packages/api/src/classes/ClientGateway.ts @@ -24,8 +24,7 @@ export default class ClientGateway { #hbTimeout: NodeJS.Timeout = null! #socket: WebSocket = null! - #emitter = - new EventEmitter() as TypedEmitter + #emitter = new EventEmitter() as TypedEmitter constructor(options: ClientGatewayOptions) { this.url = options.url @@ -110,6 +109,7 @@ export default class ClientGateway { switch (packet.op) { case ServerOperation.Hello: + // eslint-disable-next-line no-case-declarations const data = Object.freeze( (packet as Packet).d ) @@ -124,9 +124,11 @@ export default class ClientGateway { default: return this.#emitter.emit( uncapitalize( - ServerOperation[packet.op] as ClientGatewayServerEventName + ServerOperation[ + packet.op + ] as ClientGatewayServerEventName ), - // @ts-expect-error + // @ts-expect-error TypeScript doesn't know that the lines above negate the type enough packet ) } diff --git a/packages/api/src/classes/index.ts b/packages/api/src/classes/index.ts index 2ad36c8..e02f863 100755 --- a/packages/api/src/classes/index.ts +++ b/packages/api/src/classes/index.ts @@ -1,4 +1,4 @@ export { default as Client } from './Client.js' export * from './Client.js' export { default as ClientGateway } from './ClientGateway.js' -export * from './ClientGateway.js' \ No newline at end of file +export * from './ClientGateway.js' diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 9342b09..f4b850d 100755 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -5,7 +5,7 @@ "rootDir": "./src", "outDir": "dist", "module": "ESNext", - "composite": true, + "composite": true }, "exclude": ["node_modules", "dist"] -} \ No newline at end of file +} diff --git a/packages/api/utility-types.d.ts b/packages/api/utility-types.d.ts index 7fc44c5..1710e17 100755 --- a/packages/api/utility-types.d.ts +++ b/packages/api/utility-types.d.ts @@ -1 +1 @@ -type RequiredProperty = { [P in keyof T]: Required>; }; \ No newline at end of file +type RequiredProperty = { [P in keyof T]: Required> } diff --git a/packages/shared/package.json b/packages/shared/package.json index ac2f589..f877e75 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,37 +1,37 @@ -{ - "name": "@revanced/bot-shared", - "type": "module", - "version": "0.1.0", - "description": "🙌🏻 Shared components for bots assisting ReVanced", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "bun bundle && bun types", - "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", - "bundle:watch": "bun run bundle --watch", - "types": "tsc --declaration --emitDeclarationOnly", - "types:watch": "bun types --watch --preserveWatchOutput", - "types:clean": "bun types --build --clean" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "packages/shared" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "bson": "^6.2.0", - "valibot": "^0.21.0", - "zod": "^3.22.4" - } -} +{ + "name": "@revanced/bot-shared", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Shared components for bots assisting ReVanced", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/shared" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "bson": "^6.2.0", + "valibot": "^0.21.0", + "zod": "^3.22.4" + } +} diff --git a/packages/shared/src/constants/DisconnectReason.ts b/packages/shared/src/constants/DisconnectReason.ts index cd017f8..051d5e3 100755 --- a/packages/shared/src/constants/DisconnectReason.ts +++ b/packages/shared/src/constants/DisconnectReason.ts @@ -19,9 +19,9 @@ enum DisconnectReason { */ ServerError, /** - * The client had never connected to the server (**CLIENT-ONLY**) + * The client had never connected to the server (**CLIENT-ONLY**) */ - NeverConnected + NeverConnected, } -export default DisconnectReason \ No newline at end of file +export default DisconnectReason diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts index 95f510a..d9b4022 100755 --- a/packages/shared/src/constants/HumanizedDisconnectReason.ts +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -4,8 +4,9 @@ const HumanizedDisconnectReason = { [DisconnectReason.InvalidPacket]: 'has sent invalid packet', [DisconnectReason.Generic]: 'has been disconnected for unknown reasons', [DisconnectReason.TimedOut]: 'has timed out', - [DisconnectReason.ServerError]: 'has been disconnected due to an internal server error', - [DisconnectReason.NeverConnected]: 'had never connected to the server' + [DisconnectReason.ServerError]: + 'has been disconnected due to an internal server error', + [DisconnectReason.NeverConnected]: 'had never connected to the server', } as const satisfies Record -export default HumanizedDisconnectReason \ No newline at end of file +export default HumanizedDisconnectReason diff --git a/packages/shared/src/constants/Operation.ts b/packages/shared/src/constants/Operation.ts index 5a0a7cf..dcd0b96 100755 --- a/packages/shared/src/constants/Operation.ts +++ b/packages/shared/src/constants/Operation.ts @@ -50,8 +50,8 @@ export enum ServerOperation { /** * Server's disconnect message */ - Disconnect = 20 + Disconnect = 20, } export const Operation = { ...ClientOperation, ...ServerOperation } as const -export type Operation = (ClientOperation | ServerOperation) \ No newline at end of file +export type Operation = ClientOperation | ServerOperation diff --git a/packages/shared/src/constants/index.ts b/packages/shared/src/constants/index.ts index aa16eda..097859d 100755 --- a/packages/shared/src/constants/index.ts +++ b/packages/shared/src/constants/index.ts @@ -1,3 +1,3 @@ export { default as DisconnectReason } from './DisconnectReason.js' export { default as HumanizedDisconnectReason } from './HumanizedDisconnectReason.js' -export * from './Operation.js' \ No newline at end of file +export * from './Operation.js' diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 4cc2cfc..e5ef662 100755 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,3 @@ export * from './constants/index.js' export * from './schemas/index.js' -export * from './utils/index.js' \ No newline at end of file +export * from './utils/index.js' diff --git a/packages/shared/src/schemas/index.ts b/packages/shared/src/schemas/index.ts index bbcb9e0..b593970 100755 --- a/packages/shared/src/schemas/index.ts +++ b/packages/shared/src/schemas/index.ts @@ -1 +1 @@ -export * from './Packet.js' \ No newline at end of file +export * from './Packet.js' diff --git a/packages/shared/src/utils/guard.ts b/packages/shared/src/utils/guard.ts index b8fd1ad..24a98e2 100755 --- a/packages/shared/src/utils/guard.ts +++ b/packages/shared/src/utils/guard.ts @@ -1,5 +1,9 @@ import { Packet } from '../schemas/Packet.js' -import { ClientOperation, Operation, ServerOperation } from '../constants/Operation.js' +import { + ClientOperation, + Operation, + ServerOperation, +} from '../constants/Operation.js' /** * Checks whether a packet is trying to do the given operation @@ -7,7 +11,10 @@ import { ClientOperation, Operation, ServerOperation } from '../constants/Operat * @param packet A packet * @returns Whether this packet is trying to do the operation given */ -export function packetMatchesOperation(op: TOp, packet: Packet): packet is Packet { +export function packetMatchesOperation( + op: TOp, + packet: Packet +): packet is Packet { return packet.op === op } @@ -16,7 +23,9 @@ export function packetMatchesOperation(op: TOp, packet: P * @param packet A packet * @returns Whether this packet is a client packet */ -export function isClientPacket(packet: Packet): packet is Packet { +export function isClientPacket( + packet: Packet +): packet is Packet { return packet.op in ClientOperation } @@ -25,6 +34,8 @@ export function isClientPacket(packet: Packet): packet is Packet { +export function isServerPacket( + packet: Packet +): packet is Packet { return packet.op in ServerOperation -} \ No newline at end of file +} diff --git a/packages/shared/src/utils/serialization.ts b/packages/shared/src/utils/serialization.ts index 807ddc5..764af14 100755 --- a/packages/shared/src/utils/serialization.ts +++ b/packages/shared/src/utils/serialization.ts @@ -20,4 +20,4 @@ export function serializePacket(packet: Packet) { export function deserializePacket(buffer: Buffer) { const data = BSON.deserialize(buffer) return parse(PacketSchema, data) as Packet -} \ No newline at end of file +} diff --git a/packages/shared/src/utils/string.ts b/packages/shared/src/utils/string.ts index 1c63acf..589bfa5 100755 --- a/packages/shared/src/utils/string.ts +++ b/packages/shared/src/utils/string.ts @@ -1,3 +1,3 @@ export function uncapitalize(str: T): Uncapitalize { - return str.charAt(0).toLowerCase() + str.slice(1) as Uncapitalize -} \ No newline at end of file + return (str.charAt(0).toLowerCase() + str.slice(1)) as Uncapitalize +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index 9342b09..f4b850d 100755 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -5,7 +5,7 @@ "rootDir": "./src", "outDir": "dist", "module": "ESNext", - "composite": true, + "composite": true }, "exclude": ["node_modules", "dist"] -} \ No newline at end of file +} diff --git a/tsconfig.apis.json b/tsconfig.apis.json index 80c29da..89b3d2b 100755 --- a/tsconfig.apis.json +++ b/tsconfig.apis.json @@ -1,3 +1,3 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.base.json" } diff --git a/tsconfig.base.json b/tsconfig.base.json index f11e436..a176582 100755 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,6 +1,6 @@ { // `bun-types` will not be added until https://github.com/oven-sh/bun/issues/7247 is fixed - "extends": ["@tsconfig/strictest", /* "bun-types" */], + "extends": ["@tsconfig/strictest" /* "bun-types" */], "compilerOptions": { "lib": ["ESNext"], "module": "NodeNext", @@ -12,6 +12,6 @@ "esModuleInterop": true, "declaration": false, "allowSyntheticDefaultImports": true, - "isolatedModules": true, - }, + "isolatedModules": true + } } diff --git a/tsconfig.packages.json b/tsconfig.packages.json index 831dee6..5b6cb51 100755 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "declaration": true, - "declarationMap": true, + "declarationMap": true }, "references": [ { @@ -10,6 +10,6 @@ }, { "path": "./packages/api" - }, + } ] } diff --git a/turbo.json b/turbo.json index 00f8c5a..263271e 100755 --- a/turbo.json +++ b/turbo.json @@ -1,26 +1,26 @@ -{ - "$schema": "https://turbo.build/schema.json", - "pipeline": { - "build": { - "dependsOn": ["^build"], - "outputs": ["dist/**"], - "outputMode": "errors-only" - }, - "watch": { - "dependsOn": ["^watch"], - "outputMode": "errors-only" - }, - "format": { - "dependsOn": ["^format"], - "outputMode": "errors-only" - }, - "lint": { - "dependsOn": ["^lint"], - "outputMode": "errors-only" - }, - "lint:apply": { - "dependsOn": ["^lint:apply"], - "outputMode": "errors-only" - } - } -} \ No newline at end of file +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"], + "outputMode": "errors-only" + }, + "watch": { + "dependsOn": ["^watch"], + "outputMode": "errors-only" + }, + "format": { + "dependsOn": ["^format"], + "outputMode": "errors-only" + }, + "lint": { + "dependsOn": ["^lint"], + "outputMode": "errors-only" + }, + "lint:apply": { + "dependsOn": ["^lint:apply"], + "outputMode": "errors-only" + } + } +} From a49f45127cb280b9cf297dc976dfb1bc0a8503e9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 25 Nov 2023 23:01:49 +0700 Subject: [PATCH 011/312] chore(packages): add more docs and improve types --- packages/api/src/classes/Client.ts | 57 ++++++++++++++++--- packages/api/src/classes/ClientGateway.ts | 34 +++++++++++ .../constants/HumanizedDisconnectReason.ts | 3 + packages/shared/src/schemas/Packet.ts | 28 +-------- packages/shared/src/utils/string.ts | 5 ++ 5 files changed, 91 insertions(+), 36 deletions(-) diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index 725a811..e7e925a 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -19,14 +19,27 @@ export default class Client { }) } + /** + * Connects to the WebSocket API + * @returns A promise that resolves when the client is ready + */ connect() { return this.gateway.connect() } + /** + * Checks whether the client is ready + * @returns Whether the client is ready + */ isReady(): this is ReadiedClient { return this.ready } + /** + * Requests the API to parse the given text + * @param text The text to parse + * @returns An object containing the ID of the request and the labels + */ async parseText(text: string) { this.#throwIfNotReady() @@ -42,11 +55,11 @@ export default class Client { type CorrectPacket = Packet - const promise = new Promise((rs, rj) => { + const promise = new Promise((rs, rj) => { const parsedTextListener = (packet: CorrectPacket) => { if (packet.d.id !== currentId) return this.gateway.off('parsedText', parsedTextListener) - rs(packet) + rs(packet.d) } const parseTextFailedListener = ( @@ -54,7 +67,7 @@ export default class Client { ) => { if (packet.d.id !== currentId) return this.gateway.off('parseTextFailed', parseTextFailedListener) - rj(packet) + rj() } this.gateway.on('parsedText', parsedTextListener) @@ -64,6 +77,11 @@ export default class Client { return await promise } + /** + * Requests the API to parse the given image and return the text + * @param url The URL of the image + * @returns An object containing the ID of the request and the parsed text + */ async parseImage(url: string) { this.#throwIfNotReady() @@ -79,11 +97,11 @@ export default class Client { type CorrectPacket = Packet - const promise = new Promise((rs, rj) => { + const promise = new Promise((rs, rj) => { const parsedImageListener = (packet: CorrectPacket) => { if (packet.d.id !== currentId) return this.gateway.off('parsedImage', parsedImageListener) - rs(packet) + rs(packet.d) } const parseImageFailedListener = ( @@ -91,7 +109,7 @@ export default class Client { ) => { if (packet.d.id !== currentId) return this.gateway.off('parseImageFailed', parseImageFailedListener) - rj(packet) + rj() } this.gateway.on('parsedImage', parsedImageListener) @@ -101,25 +119,46 @@ export default class Client { return await promise } + /** + * Adds an event listener + * @param name The event name to listen for + * @param handler The event handler + * @returns The event handler function + */ on( name: TOpName, - handler: ClientGatewayEventHandlers[typeof name] + handler: ClientGatewayEventHandlers[TOpName] ) { this.gateway.on(name, handler) + return handler } + /** + * Removes an event listener + * @param name The event name to remove a listener from + * @param handler The event handler to remove + * @returns The removed event handler function + */ off( name: TOpName, - handler: ClientGatewayEventHandlers[typeof name] + handler: ClientGatewayEventHandlers[TOpName] ) { this.gateway.off(name, handler) + return handler } + /** + * Adds an event listener that will only be called once + * @param name The event name to listen for + * @param handler The event handler + * @returns The event handler function + */ once( name: TOpName, - handler: ClientGatewayEventHandlers[typeof name] + handler: ClientGatewayEventHandlers[TOpName] ) { this.gateway.once(name, handler) + return handler } #throwIfNotReady() { diff --git a/packages/api/src/classes/ClientGateway.ts b/packages/api/src/classes/ClientGateway.ts index eb119b4..ef4b2da 100755 --- a/packages/api/src/classes/ClientGateway.ts +++ b/packages/api/src/classes/ClientGateway.ts @@ -30,6 +30,10 @@ export default class ClientGateway { this.url = options.url } + /** + * Connects to the WebSocket API + * @returns A promise that resolves when the client is ready + */ connect() { return new Promise((rs, rj) => { try { @@ -53,6 +57,12 @@ export default class ClientGateway { }) } + /** + * Adds an event listener + * @param name The event name to listen for + * @param handler The event handler + * @returns The event handler function + */ on( name: TOpName, handler: ClientGatewayEventHandlers[typeof name] @@ -60,6 +70,12 @@ export default class ClientGateway { this.#emitter.on(name, handler) } + /** + * Removes an event listener + * @param name The event name to remove a listener from + * @param handler The event handler to remove + * @returns The removed event handler function + */ off( name: TOpName, handler: ClientGatewayEventHandlers[typeof name] @@ -67,6 +83,12 @@ export default class ClientGateway { this.#emitter.off(name, handler) } + /** + * Adds an event listener that will only be called once + * @param name The event name to listen for + * @param handler The event handler + * @returns The event handler function + */ once( name: TOpName, handler: ClientGatewayEventHandlers[typeof name] @@ -74,6 +96,11 @@ export default class ClientGateway { this.#emitter.once(name, handler) } + /** + * Sends a packet to the server + * @param packet The packet to send + * @returns A promise that resolves when the packet has been sent + */ send(packet: Packet) { this.#throwIfDisconnected( 'Cannot send a packet when already disconnected from the server' @@ -86,6 +113,9 @@ export default class ClientGateway { ) } + /** + * Disconnects from the WebSocket API + */ disconnect() { this.#throwIfDisconnected( 'Cannot disconnect when already disconnected from the server' @@ -94,6 +124,10 @@ export default class ClientGateway { this.#handleDisconnect(DisconnectReason.Generic) } + /** + * Checks whether the client is ready + * @returns Whether the client is ready + */ isReady(): this is ReadiedClientGateway { return this.ready } diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts index d9b4022..fa88738 100755 --- a/packages/shared/src/constants/HumanizedDisconnectReason.ts +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -1,5 +1,8 @@ import DisconnectReason from './DisconnectReason.js' +/** + * Humanized disconnect reasons for logs + */ const HumanizedDisconnectReason = { [DisconnectReason.InvalidPacket]: 'has sent invalid packet', [DisconnectReason.Generic]: 'has been disconnected for unknown reasons', diff --git a/packages/shared/src/schemas/Packet.ts b/packages/shared/src/schemas/Packet.ts index 07267a3..34148fa 100755 --- a/packages/shared/src/schemas/Packet.ts +++ b/packages/shared/src/schemas/Packet.ts @@ -44,33 +44,6 @@ export const PacketSchema = special(input => { return false }, 'Invalid packet data') -// merge([ -// object({ -// op: nativeEnum(Operation, 'Not a valid operation number'), -// }), -// object({ -// d: special(input => { -// if ( -// typeof input === 'object' && -// input && -// 'op' in input && -// typeof input.op === 'number' && -// input.op in Operation && -// 'd' in input && -// typeof input.d === 'object' -// ) { -// try { -// PacketDataSchemas[input.op as Operation].parse(input) -// return true -// } catch { -// return false -// } -// } -// return false -// }, 'Invalid packet data'), -// }), -// ]) - /** * Schema to validate packet data for each possible operations */ @@ -118,6 +91,7 @@ export const PacketDataSchemas = { }), } as const satisfies Record< Operation, + // eslint-disable-next-line @typescript-eslint/no-explicit-any ObjectSchema | AnySchema | NullSchema > diff --git a/packages/shared/src/utils/string.ts b/packages/shared/src/utils/string.ts index 589bfa5..c9d44fe 100755 --- a/packages/shared/src/utils/string.ts +++ b/packages/shared/src/utils/string.ts @@ -1,3 +1,8 @@ +/** + * Uncapitalizes the first letter of a string + * @param str The string to uncapitalize + * @returns The uncapitalized string + */ export function uncapitalize(str: T): Uncapitalize { return (str.charAt(0).toLowerCase() + str.slice(1)) as Uncapitalize } From fea894e73080d04fd17f9cc359dbe5059c4f78a3 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 25 Nov 2023 23:16:55 +0700 Subject: [PATCH 012/312] docs: fix docs --- apis/websocket/README.md | 30 +++++++++++++++++++++++------- apis/websocket/docs/README.md | 2 +- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/apis/websocket/README.md b/apis/websocket/README.md index e38c83c..27c3699 100755 --- a/apis/websocket/README.md +++ b/apis/websocket/README.md @@ -6,36 +6,52 @@ srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg" >
- + + + +     - +     - + + + +     - + + + +     - + + + +     - +     - + + + +

diff --git a/apis/websocket/docs/README.md b/apis/websocket/docs/README.md index 5dc3ad7..ae7573b 100755 --- a/apis/websocket/docs/README.md +++ b/apis/websocket/docs/README.md @@ -2,7 +2,7 @@ This documentation explains how the server works, how to start developing, and how to configure the server. -# 📖 Table of contents +## 📖 Table of contents 0. [🏗️ Setting up the development environment](./0_development_environment.md) 1. [⚙️ Configuration](./1_configuration.md) From 475ae30c86a040157b3d910f6a8315cfebb36786 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 25 Nov 2023 23:24:00 +0700 Subject: [PATCH 013/312] chore: add assets and fix README --- apis/websocket/README.md | 8 ++++---- .../revanced-headline/revanced-headline-vertical-dark.svg | 1 + .../revanced-headline-vertical-light.svg | 1 + assets/revanced-logo/revanced-logo-round.svg | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 assets/revanced-headline/revanced-headline-vertical-dark.svg create mode 100644 assets/revanced-headline/revanced-headline-vertical-light.svg create mode 100644 assets/revanced-logo/revanced-logo-round.svg diff --git a/apis/websocket/README.md b/apis/websocket/README.md index 27c3699..b04c21a 100755 --- a/apis/websocket/README.md +++ b/apis/websocket/README.md @@ -3,18 +3,18 @@
- - + +     diff --git a/assets/revanced-headline/revanced-headline-vertical-dark.svg b/assets/revanced-headline/revanced-headline-vertical-dark.svg new file mode 100644 index 0000000..a59bfb5 --- /dev/null +++ b/assets/revanced-headline/revanced-headline-vertical-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/revanced-headline/revanced-headline-vertical-light.svg b/assets/revanced-headline/revanced-headline-vertical-light.svg new file mode 100644 index 0000000..3c5eecc --- /dev/null +++ b/assets/revanced-headline/revanced-headline-vertical-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/revanced-logo/revanced-logo-round.svg b/assets/revanced-logo/revanced-logo-round.svg new file mode 100644 index 0000000..db84091 --- /dev/null +++ b/assets/revanced-logo/revanced-logo-round.svg @@ -0,0 +1 @@ + \ No newline at end of file From a6091871747413ff547c93ce6fbe1f8870998fd6 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 26 Nov 2023 02:17:15 +0700 Subject: [PATCH 014/312] chore: update scripts --- apis/websocket/docs/0_development_environment.md | 2 +- apis/websocket/src/events/parseText.ts | 1 + package.json | 15 +++++++-------- turbo.json | 12 ------------ 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/apis/websocket/docs/0_development_environment.md b/apis/websocket/docs/0_development_environment.md index 85960a4..5443dd8 100644 --- a/apis/websocket/docs/0_development_environment.md +++ b/apis/websocket/docs/0_development_environment.md @@ -24,7 +24,7 @@ To start developing, you'll need to set up the development environment first. 4. Build packages/libraries ```sh - bun build:deps + bun build:libs ``` 5. Change your directory to this project's root diff --git a/apis/websocket/src/events/parseText.ts b/apis/websocket/src/events/parseText.ts index bfd7bb9..8fafbe1 100755 --- a/apis/websocket/src/events/parseText.ts +++ b/apis/websocket/src/events/parseText.ts @@ -17,6 +17,7 @@ const parseTextEventHandler: EventHandler = async ( try { const { intents } = await witClient.message(text, {}) + // eslint-disable-next-line @typescript-eslint/no-unused-vars const intentsWithoutIds = intents.map(({ id, ...rest }) => rest) await client.send({ diff --git a/package.json b/package.json index 102c106..ceab1ff 100755 --- a/package.json +++ b/package.json @@ -9,15 +9,14 @@ "packages/*" ], "scripts": { - "build": "bun task build", + "build": "turbo run build", + "watch": "turbo run watch", + "format": "prettier --write .", + "format:check": "prettier --check .", + "lint": "eslint .", + "lint:apply": "eslint --fix .", "commitlint": "commitlint --edit", - "format": "turbo run format", - "format:check": "turbo run format:check", - "lint": "turbo run lint", - "lint:apply": "turbo run lint:apply", - "watch": "bun task watch", - "task": "turbo run", - "t": "bun task", + "t": "turbo run", "prepare": "lefthook install" }, "repository": { diff --git a/turbo.json b/turbo.json index 263271e..cfafea5 100755 --- a/turbo.json +++ b/turbo.json @@ -9,18 +9,6 @@ "watch": { "dependsOn": ["^watch"], "outputMode": "errors-only" - }, - "format": { - "dependsOn": ["^format"], - "outputMode": "errors-only" - }, - "lint": { - "dependsOn": ["^lint"], - "outputMode": "errors-only" - }, - "lint:apply": { - "dependsOn": ["^lint:apply"], - "outputMode": "errors-only" } } } From d9f69c2c898bc514c3af92e07a1a1a570719131c Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 26 Nov 2023 02:38:08 +0700 Subject: [PATCH 015/312] chore: move configs to package.json --- .eslintignore | 3 - .eslintrc | 34 ----------- .prettierignore | 3 - .prettierrc | 9 --- bun.lockb | Bin 335700 -> 330236 bytes package.json | 158 +++++++++++++++++++++++++++++++----------------- 6 files changed, 102 insertions(+), 105 deletions(-) delete mode 100755 .eslintignore delete mode 100755 .eslintrc delete mode 100755 .prettierignore delete mode 100755 .prettierrc diff --git a/.eslintignore b/.eslintignore deleted file mode 100755 index 559a55b..0000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -dist/ -CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/.eslintrc b/.eslintrc deleted file mode 100755 index 475489e..0000000 --- a/.eslintrc +++ /dev/null @@ -1,34 +0,0 @@ -{ - "root": true, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "sourceType": "module", - "ecmaVersion": "latest" - }, - "rules": { - "import/no-unresolved": "error", - "prettier/prettier": [ - "error", - { - "quoteProps": "consistent", - "singleQuote": true, - "tabWidth": 4, - "trailingComma": "es5", - "useTabs": false - } - ] - }, - "plugins": ["import", "prettier"], - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [".ts", ".tsx"] - }, - "import/resolver": { - "typescript": { - "alwaysTryTypes": true, - "project": ["./tsconfig.base.json"] - } - } - } -} diff --git a/.prettierignore b/.prettierignore deleted file mode 100755 index 559a55b..0000000 --- a/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -dist/ -CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100755 index ce11b5c..0000000 --- a/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "arrowParens": "avoid", - "quoteProps": "as-needed", - "tabWidth": 4, - "semi": false, - "singleQuote": true, - "trailingComma": "es5", - "endOfLine": "crlf" -} diff --git a/bun.lockb b/bun.lockb index 78b21eb9de591caa16b365c0cdfca07ea44cf545..63e3ec00976460de6ee1d96269efc6215c498cbf 100755 GIT binary patch delta 54474 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 z�WAbt#hrv<%i%&(MH@<;rvsb`~EQyDQKfR=n>2FaFh;BA`)laM#D<%Jdp`mRU0B zNr?$wXrH_~U51lIg6a3=={_7R5={55P7eU_|6ZNmz`^3f#CL7_ISv*Hv-?o@KIi^p zpnU({IVR8oUU1t}`5MG0vyv7cnZCzkBG@P3mITb~71yRKfP92%wgl7eYtvgeS$t%0 zn*g<4#`QYHj$L=N>g2W*g31sh$bc&@v!S7g$81RWN-#OyoW6&PMZyeUI75m{@mfxm z#ylNE4!GX~Z%$X?W-(%d`gHm|HWsPr1>7tqOvyK=FXLu0l39Nf5;wQ5@l`DfJ5~cN z&SwEYvP>V{TqJh!G#-~na2+tZKmu=vPG-hpJLC0h5oOFcEc z5IO$f-RU~KETE~WcwUx9863_rWMHVjH~kMUiv(lcbWuK*bf(Vx)9d(HB+L%pg%o5u zr>?$PdqLQhiP2Ec2%P$+-G?+e?m9c(xf}RvC1@pPgxXaS(uipQs4Kf`(=D{HKV0w-KixHE`gXs$dSR@!br+bUCNKU^bz#_$D@L>8o z0Tu}+n+MY$2(uVXw-RKLV)A-0Jxq|rNaoiAh*y##mp5*B)^7na5MHKYOdUdZi+*|#LnxP>vKUJQOcollZQ zLMHJw`1HMo$fq6kZlZ5N3nf4WumJ-@+w190k}O6t(_TYHB#gIv_4i)A3d%U3o~Qu> z!`j!=CxFa2@EQ_WIVmmW2@`gM+DPV*X|U6;ryl{Car-r-$hm%f$>H!de4w?rMvyj| z)Pw0lQY=PHuU=1glVUNF`TZJ_#WPPe{ABZv@&{)vaI!IaJN=9_iv*M1+v)41SR~9m z-$JtMA?7zrM5TU%3VmqXE$S`A#TyqZ25&c!yu-v;26b`a+v$I#SR`byx(8fkx4xZj zAk89SHv26kHiWCvj%pgIgF1Q!5En0h3klO-Z~o@B2- zv(tJc{AR^3Ij{<_>9|dW`Rp6K6qUC?!$CH85RlXT!g$a14H{qNCo`#hGzDC zn`y$(O2dGGVfx4Eb7WYIn8H3yzaYaR!6g29`YRA$^Ye5$S-2U~ePvn9rA5An4l(1&y!=3Vv7AXy-SY8i2DX;r#%A$ z!wi0w>8oW~rKUfWV+m(GINeg7MP4yO0piLNU7P+K4hURUp#X{L$0bdf0x6!io2FOF zv&b_&vY$Rno<&aTgFU1~ayYF}Et;fLb^^46A9P?I+xAQHEDDU%<$_tnr$4yMBD_6R zk>%)~?R<|}L@l{~fX)zMU|(v5Nc^pCQv;!3L-Aw2=mDHNbabADKAP*J8! d@einS4o-*}zH+R>To#-VgL62iXUVa?0svF)v>yNf delta 57186 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©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 (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "devDependencies": { - "@commitlint/cli": "^18.4.3", - "@commitlint/config-conventional": "^18.4.3", - "@tsconfig/strictest": "^2.0.2", - "conventional-changelog-conventionalcommits": "^7.0.2", - "eslint": "^8.54.0", - "eslint-config-prettier": "^9.0.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.0", - "eslint-plugin-prettier": "^5.0.1", - "lefthook": "^1.5.3", - "prettier": "^3.1.0", - "semantic-release": "^22.0.8", - "turbo": "^1.10.16", - "typescript": "^5.3.2" - }, - "overrides": { - "uuid": ">=9.0.0", - "isomorphic-fetch": ">=3.0.0" - } -} +{ + "name": "revanced-helper", + "version": "0.0.0", + "description": "🤖 Bots assisting ReVanced on multiple platforms", + "private": true, + "workspaces": [ + "apis/*", + "bots/*", + "packages/*" + ], + "scripts": { + "build": "turbo run build", + "watch": "turbo run watch", + "format": "prettier --ignore-path .gitignore --write .", + "format:check": "prettier --ignore-path .gitignore --cache --check .", + "lint": "eslint --ignore-path .gitignore --cache .", + "lint:apply": "eslint --ignore-path .gitignore --fix .", + "commitlint": "commitlint --edit", + "t": "turbo run", + "prepare": "lefthook install" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "devDependencies": { + "@commitlint/cli": "^18.4.3", + "@commitlint/config-conventional": "^18.4.3", + "@tsconfig/strictest": "^2.0.2", + "conventional-changelog-conventionalcommits": "^7.0.2", + "eslint": "^8.54.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "lefthook": "^1.5.3", + "prettier": "^3.1.0", + "semantic-release": "^22.0.8", + "turbo": "^1.10.16", + "typescript": "^5.3.2" + }, + "overrides": { + "uuid": ">=9.0.0", + "isomorphic-fetch": ">=3.0.0" + }, + "eslintConfig": { + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "plugins": [ + "import", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module", + "ecmaVersion": "latest" + }, + "rules": { + "import/no-unresolved": "error" + }, + "settings": { + "import/parsers": { + "@typescript-eslint/parser": [ + ".ts", + ".tsx" + ] + }, + "import/resolver": { + "typescript": { + "alwaysTryTypes": true, + "project": [ + "./tsconfig.base.json" + ] + } + } + } + }, + "prettier": { + "arrowParens": "avoid", + "quoteProps": "as-needed", + "tabWidth": 4, + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "endOfLine": "crlf" + } +} + From c80bd068faac3d4dc43a049955842ae513fbd375 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 28 Nov 2023 21:58:59 +0700 Subject: [PATCH 016/312] chore: use biome instead of eslint and prettier --- biome.json | 51 +++++++++++++++++ bun.lockb | Bin 330236 -> 332868 bytes package.json | 154 ++++++++++++++++++--------------------------------- 3 files changed, 105 insertions(+), 100 deletions(-) create mode 100644 biome.json diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..c175a5e --- /dev/null +++ b/biome.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.3.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "useLiteralKeys": { + "level": "off" + } + }, + "style": { + "noNonNullAssertion": { + "level": "off" + } + } + } + }, + "json": { + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + } + }, + "javascript": { + "formatter": { + "enabled": true, + "arrowParentheses": "asNeeded", + "indentStyle": "space", + "indentWidth": 4, + "quoteProperties": "asNeeded", + "quoteStyle": "single", + "semicolons": "asNeeded", + "trailingComma": "all" + } + }, + "files": { + "ignoreUnknown": true, + "include": ["**/*.js", "**/*.json", "**/*.ts"], + "ignore": [ + "**/tsconfig.json", + "**/tsconfig.*.json", + "dist/**/*", + "node_modules/**/*" + ] + } +} diff --git a/bun.lockb b/bun.lockb index 63e3ec00976460de6ee1d96269efc6215c498cbf..ecb2936d05f5cf49a1cbbd14d910bf728e9e3e3d 100755 GIT binary patch delta 53754 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

-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 HvK|2dR3|tE delta 52490 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 diff --git a/package.json b/package.json index 4ebb4b4..210afe1 100755 --- a/package.json +++ b/package.json @@ -1,102 +1,56 @@ { - "name": "revanced-helper", - "version": "0.0.0", - "description": "🤖 Bots assisting ReVanced on multiple platforms", - "private": true, - "workspaces": [ - "apis/*", - "bots/*", - "packages/*" - ], - "scripts": { - "build": "turbo run build", - "watch": "turbo run watch", - "format": "prettier --ignore-path .gitignore --write .", - "format:check": "prettier --ignore-path .gitignore --cache --check .", - "lint": "eslint --ignore-path .gitignore --cache .", - "lint:apply": "eslint --ignore-path .gitignore --fix .", - "commitlint": "commitlint --edit", - "t": "turbo run", - "prepare": "lefthook install" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "devDependencies": { - "@commitlint/cli": "^18.4.3", - "@commitlint/config-conventional": "^18.4.3", - "@tsconfig/strictest": "^2.0.2", - "conventional-changelog-conventionalcommits": "^7.0.2", - "eslint": "^8.54.0", - "eslint-config-prettier": "^9.0.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.0", - "eslint-plugin-prettier": "^5.0.1", - "lefthook": "^1.5.3", - "prettier": "^3.1.0", - "semantic-release": "^22.0.8", - "turbo": "^1.10.16", - "typescript": "^5.3.2" - }, - "overrides": { - "uuid": ">=9.0.0", - "isomorphic-fetch": ">=3.0.0" - }, - "eslintConfig": { - "root": true, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended" - ], - "plugins": [ - "import", - "prettier" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "sourceType": "module", - "ecmaVersion": "latest" - }, - "rules": { - "import/no-unresolved": "error" - }, - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [ - ".ts", - ".tsx" - ] - }, - "import/resolver": { - "typescript": { - "alwaysTryTypes": true, - "project": [ - "./tsconfig.base.json" - ] - } - } - } - }, - "prettier": { - "arrowParens": "avoid", - "quoteProps": "as-needed", - "tabWidth": 4, - "semi": false, - "singleQuote": true, - "trailingComma": "es5", - "endOfLine": "crlf" - } + "name": "revanced-helper", + "version": "0.0.0", + "description": "🤖 Bots assisting ReVanced on multiple platforms", + "private": true, + "workspaces": [ + "apis/*", + "bots/*", + "packages/*" + ], + "scripts": { + "build": "turbo run build", + "watch": "turbo run watch", + "format": "prettier --ignore-path .gitignore --write .", + "format:check": "prettier --ignore-path .gitignore --cache --check .", + "lint": "eslint --ignore-path .gitignore --cache .", + "lint:apply": "eslint --ignore-path .gitignore --fix .", + "commitlint": "commitlint --edit", + "t": "turbo run", + "prepare": "lefthook install" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "devDependencies": { + "@biomejs/biome": "1.3.3", + "@commitlint/cli": "^18.4.3", + "@commitlint/config-conventional": "^18.4.3", + "@tsconfig/strictest": "^2.0.2", + "conventional-changelog-conventionalcommits": "^7.0.2", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "lefthook": "^1.5.3", + "semantic-release": "^22.0.8", + "turbo": "^1.10.16", + "typescript": "^5.3.2" + }, + "overrides": { + "uuid": ">=9.0.0", + "isomorphic-fetch": ">=3.0.0" + }, + "trustedDependencies": ["lefthook", "biome", "turbo"] } - From f2d85c32a495d46df6818c93b5270d87f4f3d366 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 28 Nov 2023 22:03:41 +0700 Subject: [PATCH 017/312] chore: apply code fixes with biome --- .commitlintrc.js | 6 +- apis/websocket/config.json | 18 +- apis/websocket/config.schema.json | 62 +-- apis/websocket/src/classes/Client.ts | 436 ++++++++-------- apis/websocket/src/events/index.ts | 40 +- apis/websocket/src/events/parseImage.ts | 126 ++--- apis/websocket/src/events/parseText.ts | 86 ++-- apis/websocket/src/index.ts | 314 ++++++------ apis/websocket/src/types.d.ts | 18 +- apis/websocket/src/utils/checkEnv.ts | 62 +-- apis/websocket/src/utils/getConfig.ts | 80 +-- apis/websocket/src/utils/index.ts | 6 +- apis/websocket/src/utils/logger.ts | 50 +- packages/api/src/classes/Client.ts | 354 ++++++------- packages/api/src/classes/ClientGateway.ts | 469 +++++++++--------- packages/api/src/classes/index.ts | 8 +- packages/api/src/index.ts | 2 +- packages/api/utility-types.d.ts | 2 +- .../shared/src/constants/DisconnectReason.ts | 54 +- .../constants/HumanizedDisconnectReason.ts | 30 +- packages/shared/src/constants/Operation.ts | 114 ++--- packages/shared/src/constants/index.ts | 6 +- packages/shared/src/index.ts | 6 +- packages/shared/src/schemas/Packet.ts | 202 ++++---- packages/shared/src/schemas/index.ts | 2 +- packages/shared/src/utils/guard.ts | 82 +-- packages/shared/src/utils/index.ts | 6 +- packages/shared/src/utils/serialization.ts | 46 +- packages/shared/src/utils/string.ts | 16 +- tsconfig.apis.json | 6 +- tsconfig.packages.json | 30 +- turbo.json | 28 +- 32 files changed, 1384 insertions(+), 1383 deletions(-) diff --git a/.commitlintrc.js b/.commitlintrc.js index da13e76..3e16e7f 100755 --- a/.commitlintrc.js +++ b/.commitlintrc.js @@ -1,3 +1,3 @@ -module.exports = { - extends: ['@commitlint/config-conventional'], -} +module.exports = { + extends: ['@commitlint/config-conventional'], +} diff --git a/apis/websocket/config.json b/apis/websocket/config.json index 06dcc28..8d54b6c 100755 --- a/apis/websocket/config.json +++ b/apis/websocket/config.json @@ -1,9 +1,9 @@ -{ - "$schema": "./config.schema.json", - - "address": "127.0.0.1", - "port": 3000, - "ocrConcurrentQueues": 1, - "clientHeartbeatInterval": 5000, - "debugLogsInProduction": false -} +{ + "$schema": "./config.schema.json", + + "address": "127.0.0.1", + "port": 3000, + "ocrConcurrentQueues": 1, + "clientHeartbeatInterval": 5000, + "debugLogsInProduction": false +} diff --git a/apis/websocket/config.schema.json b/apis/websocket/config.schema.json index adc77ed..30fc6f7 100755 --- a/apis/websocket/config.schema.json +++ b/apis/websocket/config.schema.json @@ -1,31 +1,31 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "address": { - "description": "Address to listen on", - "type": "string", - "default": "127.0.0.1" - }, - "port": { - "description": "Port to listen on", - "type": "integer", - "default": 80 - }, - "ocrConcurrentQueues": { - "description": "Number of concurrent queues for OCR", - "type": "integer", - "default": 1 - }, - "clientHeartbeatInterval": { - "description": "Time in milliseconds to wait for a client to send a heartbeat packet, if no packet is received, the server will wait for `clientHeartbeatExtraTime` milliseconds before disconnecting the client", - "type": "integer", - "default": 60000 - }, - "debugLogsInProduction": { - "description": "Whether to print debug logs in production", - "type": "boolean", - "default": false - } - } -} +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "address": { + "description": "Address to listen on", + "type": "string", + "default": "127.0.0.1" + }, + "port": { + "description": "Port to listen on", + "type": "integer", + "default": 80 + }, + "ocrConcurrentQueues": { + "description": "Number of concurrent queues for OCR", + "type": "integer", + "default": 1 + }, + "clientHeartbeatInterval": { + "description": "Time in milliseconds to wait for a client to send a heartbeat packet, if no packet is received, the server will wait for `clientHeartbeatExtraTime` milliseconds before disconnecting the client", + "type": "integer", + "default": 60000 + }, + "debugLogsInProduction": { + "description": "Whether to print debug logs in production", + "type": "boolean", + "default": false + } + } +} diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index 09bf3d3..dcfdbc1 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -1,218 +1,218 @@ -import { - ClientOperation, - DisconnectReason, - Packet, - ServerOperation, - deserializePacket, - isClientPacket, - serializePacket, - uncapitalize, -} from '@revanced/bot-shared' -import { EventEmitter } from 'node:events' - -import type TypedEmitter from 'typed-emitter' -import type { RawData, WebSocket } from 'ws' - -export default class Client { - id: string - disconnected: DisconnectReason | false = false - ready: boolean = false - - lastHeartbeat: number = null! - heartbeatInterval: number - - #hbTimeout: NodeJS.Timeout = null! - #emitter = new EventEmitter() as TypedEmitter - #socket: WebSocket - - constructor(options: ClientOptions) { - this.#socket = options.socket - this.heartbeatInterval = options.heartbeatInterval ?? 60000 - this.id = options.id - - this.#socket.on('error', () => this.forceDisconnect()) - this.#socket.on('close', () => this.forceDisconnect()) - this.#socket.on('unexpected-response', () => this.forceDisconnect()) - - this.send({ - op: ServerOperation.Hello, - d: { - heartbeatInterval: this.heartbeatInterval, - }, - }) - .then(() => { - this.#listen() - this.#listenHeartbeat() - this.ready = true - this.#emitter.emit('ready') - }) - .catch(() => { - if (this.disconnected === false) - this.disconnect(DisconnectReason.ServerError) - else this.forceDisconnect(DisconnectReason.ServerError) - }) - } - - on( - name: TOpName, - handler: ClientEventHandlers[typeof name] - ) { - this.#emitter.on(name, handler) - } - - once( - name: TOpName, - handler: ClientEventHandlers[typeof name] - ) { - this.#emitter.once(name, handler) - } - - off( - name: TOpName, - handler: ClientEventHandlers[typeof name] - ) { - this.#emitter.off(name, handler) - } - - send(packet: Packet) { - return new Promise((resolve, reject) => { - try { - this.#throwIfDisconnected( - 'Cannot send packet to client that has already disconnected' - ) - - this.#socket.send(serializePacket(packet), err => - err ? reject(err) : resolve() - ) - } catch (e) { - reject(e) - } - }) - } - - async disconnect(reason: DisconnectReason = DisconnectReason.Generic) { - this.#throwIfDisconnected( - 'Cannot disconnect client that has already disconnected' - ) - - try { - await this.send({ op: ServerOperation.Disconnect, d: { reason } }) - } catch (err) { - throw new Error( - `Cannot send disconnect reason to client ${this.id}: ${err}` - ) - } finally { - this.forceDisconnect(reason) - } - } - - forceDisconnect(reason: DisconnectReason = DisconnectReason.Generic) { - if (this.disconnected !== false) return - - if (this.#hbTimeout) clearTimeout(this.#hbTimeout) - this.#socket.terminate() - - this.ready = false - this.disconnected = reason - - this.#emitter.emit('disconnect', reason) - } - - #throwIfDisconnected(errorMessage: string) { - if (this.disconnected !== false) throw new Error(errorMessage) - - if (this.#socket.readyState !== this.#socket.OPEN) { - this.forceDisconnect(DisconnectReason.Generic) - throw new Error(errorMessage) - } - } - - #listen() { - this.#socket.on('message', data => { - try { - const rawPacket = deserializePacket(this._toBuffer(data)) - if (!isClientPacket(rawPacket)) throw null - - const packet: ClientPacketObject = { - ...rawPacket, - client: this, - } - - this.#emitter.emit('packet', packet) - this.#emitter.emit( - uncapitalize(ClientOperation[packet.op] as ClientEventName), - // @ts-expect-error TypeScript doesn't know that the above line will negate the type enough - packet - ) - } catch (e) { - // TODO: add error fields to sent packet so we can log what went wrong - this.disconnect(DisconnectReason.InvalidPacket) - } - }) - } - - #listenHeartbeat() { - this.lastHeartbeat = Date.now() - this.#startHeartbeatTimeout() - - this.on('heartbeat', () => { - this.lastHeartbeat = Date.now() - this.#hbTimeout.refresh() - - this.send({ - op: ServerOperation.HeartbeatAck, - d: { - nextHeartbeat: this.lastHeartbeat + this.heartbeatInterval, - }, - }).catch(() => {}) - }) - } - - #startHeartbeatTimeout() { - this.#hbTimeout = setTimeout(() => { - if (Date.now() - this.lastHeartbeat > 0) { - // TODO: put into config - // 5000 is extra time to account for latency - const interval = setTimeout( - () => this.disconnect(DisconnectReason.TimedOut), - 5000 - ) - - this.once('heartbeat', () => clearTimeout(interval)) - // This should never happen but it did in my testing so I'm adding this just in case - this.once('disconnect', () => clearTimeout(interval)) - // Technically we don't have to do this, but JUST IN CASE! - } else this.#hbTimeout.refresh() - }, this.heartbeatInterval) - } - - protected _toBuffer(data: RawData) { - if (data instanceof Buffer) return data - else if (data instanceof ArrayBuffer) return Buffer.from(data) - else return Buffer.concat(data) - } -} - -export interface ClientOptions { - id: string - socket: WebSocket - heartbeatInterval?: number -} - -export type ClientPacketObject = Packet & { - client: Client -} - -export type ClientEventName = keyof typeof ClientOperation - -export type ClientEventHandlers = { - [K in Uncapitalize]: ( - packet: ClientPacketObject<(typeof ClientOperation)[Capitalize]> - ) => Promise | void -} & { - ready: () => Promise | void - packet: ( - packet: ClientPacketObject - ) => Promise | void - disconnect: (reason: DisconnectReason) => Promise | void -} +import { EventEmitter } from 'node:events' +import { + ClientOperation, + DisconnectReason, + Packet, + ServerOperation, + deserializePacket, + isClientPacket, + serializePacket, + uncapitalize, +} from '@revanced/bot-shared' + +import type TypedEmitter from 'typed-emitter' +import type { RawData, WebSocket } from 'ws' + +export default class Client { + id: string + disconnected: DisconnectReason | false = false + ready = false + + lastHeartbeat: number = null! + heartbeatInterval: number + + #hbTimeout: NodeJS.Timeout = null! + #emitter = new EventEmitter() as TypedEmitter + #socket: WebSocket + + constructor(options: ClientOptions) { + this.#socket = options.socket + this.heartbeatInterval = options.heartbeatInterval ?? 60000 + this.id = options.id + + this.#socket.on('error', () => this.forceDisconnect()) + this.#socket.on('close', () => this.forceDisconnect()) + this.#socket.on('unexpected-response', () => this.forceDisconnect()) + + this.send({ + op: ServerOperation.Hello, + d: { + heartbeatInterval: this.heartbeatInterval, + }, + }) + .then(() => { + this.#listen() + this.#listenHeartbeat() + this.ready = true + this.#emitter.emit('ready') + }) + .catch(() => { + if (this.disconnected === false) + this.disconnect(DisconnectReason.ServerError) + else this.forceDisconnect(DisconnectReason.ServerError) + }) + } + + on( + name: TOpName, + handler: ClientEventHandlers[typeof name], + ) { + this.#emitter.on(name, handler) + } + + once( + name: TOpName, + handler: ClientEventHandlers[typeof name], + ) { + this.#emitter.once(name, handler) + } + + off( + name: TOpName, + handler: ClientEventHandlers[typeof name], + ) { + this.#emitter.off(name, handler) + } + + send(packet: Packet) { + return new Promise((resolve, reject) => { + try { + this.#throwIfDisconnected( + 'Cannot send packet to client that has already disconnected', + ) + + this.#socket.send(serializePacket(packet), err => + err ? reject(err) : resolve(), + ) + } catch (e) { + reject(e) + } + }) + } + + async disconnect(reason: DisconnectReason = DisconnectReason.Generic) { + this.#throwIfDisconnected( + 'Cannot disconnect client that has already disconnected', + ) + + try { + await this.send({ op: ServerOperation.Disconnect, d: { reason } }) + } catch (err) { + throw new Error( + `Cannot send disconnect reason to client ${this.id}: ${err}`, + ) + } finally { + this.forceDisconnect(reason) + } + } + + forceDisconnect(reason: DisconnectReason = DisconnectReason.Generic) { + if (this.disconnected !== false) return + + if (this.#hbTimeout) clearTimeout(this.#hbTimeout) + this.#socket.terminate() + + this.ready = false + this.disconnected = reason + + this.#emitter.emit('disconnect', reason) + } + + #throwIfDisconnected(errorMessage: string) { + if (this.disconnected !== false) throw new Error(errorMessage) + + if (this.#socket.readyState !== this.#socket.OPEN) { + this.forceDisconnect(DisconnectReason.Generic) + throw new Error(errorMessage) + } + } + + #listen() { + this.#socket.on('message', data => { + try { + const rawPacket = deserializePacket(this._toBuffer(data)) + if (!isClientPacket(rawPacket)) throw null + + const packet: ClientPacketObject = { + ...rawPacket, + client: this, + } + + this.#emitter.emit('packet', packet) + this.#emitter.emit( + uncapitalize(ClientOperation[packet.op] as ClientEventName), + // @ts-expect-error TypeScript doesn't know that the above line will negate the type enough + packet, + ) + } catch (e) { + // TODO: add error fields to sent packet so we can log what went wrong + this.disconnect(DisconnectReason.InvalidPacket) + } + }) + } + + #listenHeartbeat() { + this.lastHeartbeat = Date.now() + this.#startHeartbeatTimeout() + + this.on('heartbeat', () => { + this.lastHeartbeat = Date.now() + this.#hbTimeout.refresh() + + this.send({ + op: ServerOperation.HeartbeatAck, + d: { + nextHeartbeat: this.lastHeartbeat + this.heartbeatInterval, + }, + }).catch(() => {}) + }) + } + + #startHeartbeatTimeout() { + this.#hbTimeout = setTimeout(() => { + if (Date.now() - this.lastHeartbeat > 0) { + // TODO: put into config + // 5000 is extra time to account for latency + const interval = setTimeout( + () => this.disconnect(DisconnectReason.TimedOut), + 5000, + ) + + this.once('heartbeat', () => clearTimeout(interval)) + // This should never happen but it did in my testing so I'm adding this just in case + this.once('disconnect', () => clearTimeout(interval)) + // Technically we don't have to do this, but JUST IN CASE! + } else this.#hbTimeout.refresh() + }, this.heartbeatInterval) + } + + protected _toBuffer(data: RawData) { + if (data instanceof Buffer) return data + else if (data instanceof ArrayBuffer) return Buffer.from(data) + else return Buffer.concat(data) + } +} + +export interface ClientOptions { + id: string + socket: WebSocket + heartbeatInterval?: number +} + +export type ClientPacketObject = Packet & { + client: Client +} + +export type ClientEventName = keyof typeof ClientOperation + +export type ClientEventHandlers = { + [K in Uncapitalize]: ( + packet: ClientPacketObject]>, + ) => Promise | void +} & { + ready: () => Promise | void + packet: ( + packet: ClientPacketObject, + ) => Promise | void + disconnect: (reason: DisconnectReason) => Promise | void +} diff --git a/apis/websocket/src/events/index.ts b/apis/websocket/src/events/index.ts index 53d9d03..eb97665 100755 --- a/apis/websocket/src/events/index.ts +++ b/apis/websocket/src/events/index.ts @@ -1,20 +1,20 @@ -import type { ClientOperation } from '@revanced/bot-shared' -import type { Wit } from 'node-wit' -import { ClientPacketObject } from '../classes/Client.js' -import type { Config } from '../utils/getConfig.js' -import type { Logger } from '../utils/logger.js' -import type { Worker as TesseractWorker } from 'tesseract.js' - -export { default as parseTextEventHandler } from './parseText.js' -export { default as parseImageEventHandler } from './parseImage.js' - -export type EventHandler = ( - packet: ClientPacketObject, - context: EventContext -) => void | Promise -export type EventContext = { - witClient: Wit - tesseractWorker: TesseractWorker - logger: Logger - config: Config -} +import type { ClientOperation } from '@revanced/bot-shared' +import type { Wit } from 'node-wit' +import type { Worker as TesseractWorker } from 'tesseract.js' +import { ClientPacketObject } from '../classes/Client.js' +import type { Config } from '../utils/getConfig.js' +import type { Logger } from '../utils/logger.js' + +export { default as parseTextEventHandler } from './parseText.js' +export { default as parseImageEventHandler } from './parseImage.js' + +export type EventHandler = ( + packet: ClientPacketObject, + context: EventContext, +) => void | Promise +export type EventContext = { + witClient: Wit + tesseractWorker: TesseractWorker + logger: Logger + config: Config +} diff --git a/apis/websocket/src/events/parseImage.ts b/apis/websocket/src/events/parseImage.ts index 53f8451..742f097 100755 --- a/apis/websocket/src/events/parseImage.ts +++ b/apis/websocket/src/events/parseImage.ts @@ -1,63 +1,63 @@ -import { ClientOperation, ServerOperation } from '@revanced/bot-shared' -import { AsyncQueue } from '@sapphire/async-queue' - -import type { EventHandler } from './index.js' - -const queue = new AsyncQueue() - -const parseImageEventHandler: EventHandler = async ( - packet, - { tesseractWorker, logger, config } -) => { - const { - client, - d: { image_url: imageUrl, id }, - } = packet - - logger.debug( - `Client ${client.id} requested to parse image from URL:`, - imageUrl - ) - logger.debug( - `Queue currently has ${queue.remaining}/${config.ocrConcurrentQueues} items in it` - ) - - if (queue.remaining < config.ocrConcurrentQueues) queue.shift() - await queue.wait() - - try { - logger.debug(`Recognizing image from URL for client ${client.id}`) - - const { data, jobId } = await tesseractWorker.recognize(imageUrl) - - logger.debug( - `Recognized image from URL for client ${client.id} (job ${jobId}):`, - data.text - ) - await client.send({ - op: ServerOperation.ParsedImage, - d: { - id, - text: data.text, - }, - }) - } catch { - logger.error( - `Failed to parse image from URL for client ${client.id}:`, - imageUrl - ) - await client.send({ - op: ServerOperation.ParseImageFailed, - d: { - id, - }, - }) - } finally { - queue.shift() - logger.debug( - `Finished processing image from URL for client ${client.id}, queue has ${queue.remaining}/${config.ocrConcurrentQueues} remaining items in it` - ) - } -} - -export default parseImageEventHandler +import { ClientOperation, ServerOperation } from '@revanced/bot-shared' +import { AsyncQueue } from '@sapphire/async-queue' + +import type { EventHandler } from './index.js' + +const queue = new AsyncQueue() + +const parseImageEventHandler: EventHandler = async ( + packet, + { tesseractWorker, logger, config }, +) => { + const { + client, + d: { image_url: imageUrl, id }, + } = packet + + logger.debug( + `Client ${client.id} requested to parse image from URL:`, + imageUrl, + ) + logger.debug( + `Queue currently has ${queue.remaining}/${config.ocrConcurrentQueues} items in it`, + ) + + if (queue.remaining < config.ocrConcurrentQueues) queue.shift() + await queue.wait() + + try { + logger.debug(`Recognizing image from URL for client ${client.id}`) + + const { data, jobId } = await tesseractWorker.recognize(imageUrl) + + logger.debug( + `Recognized image from URL for client ${client.id} (job ${jobId}):`, + data.text, + ) + await client.send({ + op: ServerOperation.ParsedImage, + d: { + id, + text: data.text, + }, + }) + } catch { + logger.error( + `Failed to parse image from URL for client ${client.id}:`, + imageUrl, + ) + await client.send({ + op: ServerOperation.ParseImageFailed, + d: { + id, + }, + }) + } finally { + queue.shift() + logger.debug( + `Finished processing image from URL for client ${client.id}, queue has ${queue.remaining}/${config.ocrConcurrentQueues} remaining items in it`, + ) + } +} + +export default parseImageEventHandler diff --git a/apis/websocket/src/events/parseText.ts b/apis/websocket/src/events/parseText.ts index 8fafbe1..7818036 100755 --- a/apis/websocket/src/events/parseText.ts +++ b/apis/websocket/src/events/parseText.ts @@ -1,43 +1,43 @@ -import { ClientOperation, ServerOperation } from '@revanced/bot-shared' - -import { inspect as inspectObject } from 'node:util' - -import type { EventHandler } from './index.js' - -const parseTextEventHandler: EventHandler = async ( - packet, - { witClient, logger } -) => { - const { - client, - d: { text, id }, - } = packet - - logger.debug(`Client ${client.id} requested to parse text:`, text) - - try { - const { intents } = await witClient.message(text, {}) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const intentsWithoutIds = intents.map(({ id, ...rest }) => rest) - - await client.send({ - op: ServerOperation.ParsedText, - d: { - id, - labels: intentsWithoutIds, - }, - }) - } catch (e) { - await client.send({ - op: ServerOperation.ParseTextFailed, - d: { - id, - }, - }) - - if (e instanceof Error) logger.error(e.stack ?? e.message) - else logger.error(inspectObject(e)) - } -} - -export default parseTextEventHandler +import { ClientOperation, ServerOperation } from '@revanced/bot-shared' + +import { inspect as inspectObject } from 'node:util' + +import type { EventHandler } from './index.js' + +const parseTextEventHandler: EventHandler = async ( + packet, + { witClient, logger }, +) => { + const { + client, + d: { text, id }, + } = packet + + logger.debug(`Client ${client.id} requested to parse text:`, text) + + try { + const { intents } = await witClient.message(text, {}) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const intentsWithoutIds = intents.map(({ id, ...rest }) => rest) + + await client.send({ + op: ServerOperation.ParsedText, + d: { + id, + labels: intentsWithoutIds, + }, + }) + } catch (e) { + await client.send({ + op: ServerOperation.ParseTextFailed, + d: { + id, + }, + }) + + if (e instanceof Error) logger.error(e.stack ?? e.message) + else logger.error(inspectObject(e)) + } +} + +export default parseTextEventHandler diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index 6281ab1..108341a 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -1,157 +1,157 @@ -import { fastify } from 'fastify' -import fastifyWebsocket from '@fastify/websocket' - -import { createWorker as createTesseractWorker } from 'tesseract.js' -import witPkg from 'node-wit' -const { Wit } = witPkg - -import { inspect as inspectObject } from 'node:util' - -import Client from './classes/Client.js' - -import { - EventContext, - parseImageEventHandler, - parseTextEventHandler, -} from './events/index.js' - -import { getConfig, checkEnv, logger } from './utils/index.js' -import { WebSocket } from 'ws' -import { - DisconnectReason, - HumanizedDisconnectReason, -} from '@revanced/bot-shared' - -// Check environment variables and load config -const environment = checkEnv(logger) -const config = getConfig() - -if (!config.debugLogsInProduction && environment === 'production') - logger.debug = () => {} - -// Workers and API clients - -const tesseractWorker = await createTesseractWorker('eng') -const witClient = new Wit({ - accessToken: process.env['WIT_AI_TOKEN']!, -}) - -process.on('beforeExit', () => tesseractWorker.terminate()) - -// Server logic - -const clients = new Set() -const clientSocketMap = new WeakMap() -const eventContext: EventContext = { - tesseractWorker, - logger, - witClient, - config, -} - -const server = fastify() - .register(fastifyWebsocket, { - options: { - // 16 KiB max payload - // A Discord message can not be longer than 4000 characters - // OCR should not be longer than 16000 characters - maxPayload: 16 * 1024, - }, - }) - .register(async instance => { - instance.get('/', { websocket: true }, async (connection, request) => { - try { - const client = new Client({ - socket: connection.socket, - id: request.hostname, - heartbeatInterval: config.clientHeartbeatInterval, - }) - - clientSocketMap.set(connection.socket, client) - clients.add(client) - - logger.debug(`Client ${client.id}'s instance has been added`) - logger.info( - `New client connected (now ${clients.size} clients) with ID:`, - client.id - ) - - client.on('disconnect', reason => { - clients.delete(client) - logger.info( - `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}` - ) - }) - - client.on('parseText', async packet => - parseTextEventHandler(packet, eventContext) - ) - - client.on('parseImage', async packet => - parseImageEventHandler(packet, eventContext) - ) - - if ( - environment === 'development' && - !config.debugLogsInProduction - ) { - logger.debug( - 'Running development mode or debug logs in production is enabled, attaching debug events...' - ) - client.on('packet', ({ client, ...rawPacket }) => - logger.debug( - `Packet received from client ${client.id}:`, - inspectObject(rawPacket) - ) - ) - - client.on('heartbeat', () => - logger.debug( - 'Heartbeat received from client', - client.id - ) - ) - } - } catch (e) { - if (e instanceof Error) logger.error(e.stack ?? e.message) - else logger.error(inspectObject(e)) - - const client = clientSocketMap.get(connection.socket) - - if (!client) { - logger.error( - 'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!' - ) - return connection.socket.terminate() - } - - if (client.disconnected === false) - client.disconnect(DisconnectReason.ServerError) - else client.forceDisconnect() - - clients.delete(client) - - logger.debug( - `Client ${client.id} disconnected because of an internal error` - ) - } - }) - }) - -// Start the server - -logger.debug('Starting with these configurations:', inspectObject(config)) - -await server.listen({ - host: config.address ?? '0.0.0.0', - port: config.port ?? 80, -}) - -const addressInfo = server.server.address() -if (!addressInfo || typeof addressInfo !== 'object') - logger.debug('Server started, but cannot determine address information') -else - logger.info( - 'Server started at:', - `${addressInfo.address}:${addressInfo.port}` - ) +import fastifyWebsocket from '@fastify/websocket' +import { fastify } from 'fastify' + +import witPkg from 'node-wit' +import { createWorker as createTesseractWorker } from 'tesseract.js' +const { Wit } = witPkg + +import { inspect as inspectObject } from 'node:util' + +import Client from './classes/Client.js' + +import { + EventContext, + parseImageEventHandler, + parseTextEventHandler, +} from './events/index.js' + +import { + DisconnectReason, + HumanizedDisconnectReason, +} from '@revanced/bot-shared' +import { WebSocket } from 'ws' +import { checkEnv, getConfig, logger } from './utils/index.js' + +// Check environment variables and load config +const environment = checkEnv(logger) +const config = getConfig() + +if (!config.debugLogsInProduction && environment === 'production') + logger.debug = () => {} + +// Workers and API clients + +const tesseractWorker = await createTesseractWorker('eng') +const witClient = new Wit({ + accessToken: process.env['WIT_AI_TOKEN']!, +}) + +process.on('beforeExit', () => tesseractWorker.terminate()) + +// Server logic + +const clients = new Set() +const clientSocketMap = new WeakMap() +const eventContext: EventContext = { + tesseractWorker, + logger, + witClient, + config, +} + +const server = fastify() + .register(fastifyWebsocket, { + options: { + // 16 KiB max payload + // A Discord message can not be longer than 4000 characters + // OCR should not be longer than 16000 characters + maxPayload: 16 * 1024, + }, + }) + .register(async instance => { + instance.get('/', { websocket: true }, async (connection, request) => { + try { + const client = new Client({ + socket: connection.socket, + id: request.hostname, + heartbeatInterval: config.clientHeartbeatInterval, + }) + + clientSocketMap.set(connection.socket, client) + clients.add(client) + + logger.debug(`Client ${client.id}'s instance has been added`) + logger.info( + `New client connected (now ${clients.size} clients) with ID:`, + client.id, + ) + + client.on('disconnect', reason => { + clients.delete(client) + logger.info( + `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}`, + ) + }) + + client.on('parseText', async packet => + parseTextEventHandler(packet, eventContext), + ) + + client.on('parseImage', async packet => + parseImageEventHandler(packet, eventContext), + ) + + if ( + environment === 'development' && + !config.debugLogsInProduction + ) { + logger.debug( + 'Running development mode or debug logs in production is enabled, attaching debug events...', + ) + client.on('packet', ({ client, ...rawPacket }) => + logger.debug( + `Packet received from client ${client.id}:`, + inspectObject(rawPacket), + ), + ) + + client.on('heartbeat', () => + logger.debug( + 'Heartbeat received from client', + client.id, + ), + ) + } + } catch (e) { + if (e instanceof Error) logger.error(e.stack ?? e.message) + else logger.error(inspectObject(e)) + + const client = clientSocketMap.get(connection.socket) + + if (!client) { + logger.error( + 'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!', + ) + return connection.socket.terminate() + } + + if (client.disconnected === false) + client.disconnect(DisconnectReason.ServerError) + else client.forceDisconnect() + + clients.delete(client) + + logger.debug( + `Client ${client.id} disconnected because of an internal error`, + ) + } + }) + }) + +// Start the server + +logger.debug('Starting with these configurations:', inspectObject(config)) + +await server.listen({ + host: config.address ?? '0.0.0.0', + port: config.port ?? 80, +}) + +const addressInfo = server.server.address() +if (!addressInfo || typeof addressInfo !== 'object') + logger.debug('Server started, but cannot determine address information') +else + logger.info( + 'Server started at:', + `${addressInfo.address}:${addressInfo.port}`, + ) diff --git a/apis/websocket/src/types.d.ts b/apis/websocket/src/types.d.ts index 27a69ed..41da923 100755 --- a/apis/websocket/src/types.d.ts +++ b/apis/websocket/src/types.d.ts @@ -1,9 +1,9 @@ -declare global { - namespace NodeJS { - interface ProcessEnv { - WIT_AI_TOKEN?: string - } - } -} - -declare type NodeEnvironment = 'development' | 'production' +declare global { + namespace NodeJS { + interface ProcessEnv { + WIT_AI_TOKEN?: string + } + } +} + +declare type NodeEnvironment = 'development' | 'production' diff --git a/apis/websocket/src/utils/checkEnv.ts b/apis/websocket/src/utils/checkEnv.ts index 00cee6d..3c503e2 100755 --- a/apis/websocket/src/utils/checkEnv.ts +++ b/apis/websocket/src/utils/checkEnv.ts @@ -1,31 +1,31 @@ -import type { Logger } from './logger.js' - -export default function checkEnv(logger: Logger) { - if (!process.env['NODE_ENV']) - logger.warn('NODE_ENV not set, defaulting to `development`') - const environment = (process.env['NODE_ENV'] ?? - 'development') as NodeEnvironment - - if (!['development', 'production'].includes(environment)) { - logger.error( - 'NODE_ENV is neither `development` nor `production`, unable to determine environment' - ) - logger.info('Set NODE_ENV to blank to use `development` mode') - process.exit(1) - } - - logger.info(`Running in ${environment} mode...`) - - if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { - logger.warn( - 'You seem to be using .env files, this is generally not a good idea in production...' - ) - } - - if (!process.env['WIT_AI_TOKEN']) { - logger.error('WIT_AI_TOKEN is not defined in the environment variables') - process.exit(1) - } - - return environment -} +import type { Logger } from './logger.js' + +export default function checkEnv(logger: Logger) { + if (!process.env['NODE_ENV']) + logger.warn('NODE_ENV not set, defaulting to `development`') + const environment = (process.env['NODE_ENV'] ?? + 'development') as NodeEnvironment + + if (!['development', 'production'].includes(environment)) { + logger.error( + 'NODE_ENV is neither `development` nor `production`, unable to determine environment', + ) + logger.info('Set NODE_ENV to blank to use `development` mode') + process.exit(1) + } + + logger.info(`Running in ${environment} mode...`) + + if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { + logger.warn( + 'You seem to be using .env files, this is generally not a good idea in production...', + ) + } + + if (!process.env['WIT_AI_TOKEN']) { + logger.error('WIT_AI_TOKEN is not defined in the environment variables') + process.exit(1) + } + + return environment +} diff --git a/apis/websocket/src/utils/getConfig.ts b/apis/websocket/src/utils/getConfig.ts index 384b695..f3168cb 100755 --- a/apis/websocket/src/utils/getConfig.ts +++ b/apis/websocket/src/utils/getConfig.ts @@ -1,40 +1,40 @@ -import { existsSync } from 'node:fs' -import { resolve as resolvePath } from 'node:path' -import { pathToFileURL } from 'node:url' - -const configPath = resolvePath(process.cwd(), 'config.json') - -const userConfig: Partial = existsSync(configPath) - ? ( - await import(pathToFileURL(configPath).href, { - assert: { - type: 'json', - }, - }) - ).default - : {} - -type BaseTypeOf = T extends (infer U)[] - ? U[] - : T extends (...args: unknown[]) => infer U - ? (...args: unknown[]) => U - : T extends object - ? { [K in keyof T]: T[K] } - : T - -export type Config = Omit< - BaseTypeOf, - '$schema' -> - -export const defaultConfig: Config = { - address: '127.0.0.1', - port: 80, - ocrConcurrentQueues: 1, - clientHeartbeatInterval: 60000, - debugLogsInProduction: false, -} - -export default function getConfig() { - return Object.assign(defaultConfig, userConfig) satisfies Config -} +import { existsSync } from 'node:fs' +import { resolve as resolvePath } from 'node:path' +import { pathToFileURL } from 'node:url' + +const configPath = resolvePath(process.cwd(), 'config.json') + +const userConfig: Partial = existsSync(configPath) + ? ( + await import(pathToFileURL(configPath).href, { + assert: { + type: 'json', + }, + }) + ).default + : {} + +type BaseTypeOf = T extends (infer U)[] + ? U[] + : T extends (...args: unknown[]) => infer U + ? (...args: unknown[]) => U + : T extends object + ? { [K in keyof T]: T[K] } + : T + +export type Config = Omit< + BaseTypeOf, + '$schema' +> + +export const defaultConfig: Config = { + address: '127.0.0.1', + port: 80, + ocrConcurrentQueues: 1, + clientHeartbeatInterval: 60000, + debugLogsInProduction: false, +} + +export default function getConfig() { + return Object.assign(defaultConfig, userConfig) satisfies Config +} diff --git a/apis/websocket/src/utils/index.ts b/apis/websocket/src/utils/index.ts index 913395a..2dcb7a6 100755 --- a/apis/websocket/src/utils/index.ts +++ b/apis/websocket/src/utils/index.ts @@ -1,3 +1,3 @@ -export { default as getConfig } from './getConfig.js' -export { default as checkEnv } from './checkEnv.js' -export { default as logger } from './logger.js' +export { default as getConfig } from './getConfig.js' +export { default as checkEnv } from './checkEnv.js' +export { default as logger } from './logger.js' diff --git a/apis/websocket/src/utils/logger.ts b/apis/websocket/src/utils/logger.ts index 96ef157..88c870d 100755 --- a/apis/websocket/src/utils/logger.ts +++ b/apis/websocket/src/utils/logger.ts @@ -1,25 +1,25 @@ -import { Chalk } from 'chalk' - -const chalk = new Chalk() -const logger = { - debug: (...args) => console.debug(chalk.gray('DEBUG:', ...args)), - info: (...args) => - console.info(chalk.bgBlue.whiteBright(' INFO '), ...args), - warn: (...args) => - console.warn( - chalk.bgYellow.blackBright.bold(' WARN '), - chalk.yellowBright(...args) - ), - error: (...args) => - console.error( - chalk.bgRed.whiteBright.bold(' ERROR '), - chalk.redBright(...args) - ), - log: console.log, -} satisfies Logger - -export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'log' -export type LogFunction = (...x: unknown[]) => void -export type Logger = Record - -export default logger +import { Chalk } from 'chalk' + +const chalk = new Chalk() +const logger = { + debug: (...args) => console.debug(chalk.gray('DEBUG:', ...args)), + info: (...args) => + console.info(chalk.bgBlue.whiteBright(' INFO '), ...args), + warn: (...args) => + console.warn( + chalk.bgYellow.blackBright.bold(' WARN '), + chalk.yellowBright(...args), + ), + error: (...args) => + console.error( + chalk.bgRed.whiteBright.bold(' ERROR '), + chalk.redBright(...args), + ), + log: console.log, +} satisfies Logger + +export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'log' +export type LogFunction = (...x: unknown[]) => void +export type Logger = Record + +export default logger diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index e7e925a..5558a41 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -1,177 +1,177 @@ -import { ClientOperation, Packet, ServerOperation } from '@revanced/bot-shared' -import ClientGateway, { ClientGatewayEventHandlers } from './ClientGateway.js' - -/** - * The client that connects to the API. - */ -export default class Client { - ready: boolean = false - gateway: ClientGateway - #parseId: number = 0 - - constructor(options: ClientOptions) { - this.gateway = new ClientGateway({ - url: options.api.gatewayUrl, - }) - - this.gateway.on('ready', () => { - this.ready = true - }) - } - - /** - * Connects to the WebSocket API - * @returns A promise that resolves when the client is ready - */ - connect() { - return this.gateway.connect() - } - - /** - * Checks whether the client is ready - * @returns Whether the client is ready - */ - isReady(): this is ReadiedClient { - return this.ready - } - - /** - * Requests the API to parse the given text - * @param text The text to parse - * @returns An object containing the ID of the request and the labels - */ - async parseText(text: string) { - this.#throwIfNotReady() - - const currentId = (this.#parseId++).toString() - - this.gateway.send({ - op: ClientOperation.ParseText, - d: { - text, - id: currentId, - }, - }) - - type CorrectPacket = Packet - - const promise = new Promise((rs, rj) => { - const parsedTextListener = (packet: CorrectPacket) => { - if (packet.d.id !== currentId) return - this.gateway.off('parsedText', parsedTextListener) - rs(packet.d) - } - - const parseTextFailedListener = ( - packet: Packet - ) => { - if (packet.d.id !== currentId) return - this.gateway.off('parseTextFailed', parseTextFailedListener) - rj() - } - - this.gateway.on('parsedText', parsedTextListener) - this.gateway.on('parseTextFailed', parseTextFailedListener) - }) - - return await promise - } - - /** - * Requests the API to parse the given image and return the text - * @param url The URL of the image - * @returns An object containing the ID of the request and the parsed text - */ - async parseImage(url: string) { - this.#throwIfNotReady() - - const currentId = (this.#parseId++).toString() - - this.gateway.send({ - op: ClientOperation.ParseImage, - d: { - image_url: url, - id: currentId, - }, - }) - - type CorrectPacket = Packet - - const promise = new Promise((rs, rj) => { - const parsedImageListener = (packet: CorrectPacket) => { - if (packet.d.id !== currentId) return - this.gateway.off('parsedImage', parsedImageListener) - rs(packet.d) - } - - const parseImageFailedListener = ( - packet: Packet - ) => { - if (packet.d.id !== currentId) return - this.gateway.off('parseImageFailed', parseImageFailedListener) - rj() - } - - this.gateway.on('parsedImage', parsedImageListener) - this.gateway.on('parseImageFailed', parseImageFailedListener) - }) - - return await promise - } - - /** - * Adds an event listener - * @param name The event name to listen for - * @param handler The event handler - * @returns The event handler function - */ - on( - name: TOpName, - handler: ClientGatewayEventHandlers[TOpName] - ) { - this.gateway.on(name, handler) - return handler - } - - /** - * Removes an event listener - * @param name The event name to remove a listener from - * @param handler The event handler to remove - * @returns The removed event handler function - */ - off( - name: TOpName, - handler: ClientGatewayEventHandlers[TOpName] - ) { - this.gateway.off(name, handler) - return handler - } - - /** - * Adds an event listener that will only be called once - * @param name The event name to listen for - * @param handler The event handler - * @returns The event handler function - */ - once( - name: TOpName, - handler: ClientGatewayEventHandlers[TOpName] - ) { - this.gateway.once(name, handler) - return handler - } - - #throwIfNotReady() { - if (!this.isReady()) throw new Error('Client is not ready') - } -} - -export type ReadiedClient = Client & { ready: true } - -export interface ClientOptions { - api: ClientApiOptions -} - -export interface ClientApiOptions { - gatewayUrl: string -} +import { ClientOperation, Packet, ServerOperation } from '@revanced/bot-shared' +import ClientGateway, { ClientGatewayEventHandlers } from './ClientGateway.js' + +/** + * The client that connects to the API. + */ +export default class Client { + ready = false + gateway: ClientGateway + #parseId = 0 + + constructor(options: ClientOptions) { + this.gateway = new ClientGateway({ + url: options.api.gatewayUrl, + }) + + this.gateway.on('ready', () => { + this.ready = true + }) + } + + /** + * Connects to the WebSocket API + * @returns A promise that resolves when the client is ready + */ + connect() { + return this.gateway.connect() + } + + /** + * Checks whether the client is ready + * @returns Whether the client is ready + */ + isReady(): this is ReadiedClient { + return this.ready + } + + /** + * Requests the API to parse the given text + * @param text The text to parse + * @returns An object containing the ID of the request and the labels + */ + async parseText(text: string) { + this.#throwIfNotReady() + + const currentId = (this.#parseId++).toString() + + this.gateway.send({ + op: ClientOperation.ParseText, + d: { + text, + id: currentId, + }, + }) + + type CorrectPacket = Packet + + const promise = new Promise((rs, rj) => { + const parsedTextListener = (packet: CorrectPacket) => { + if (packet.d.id !== currentId) return + this.gateway.off('parsedText', parsedTextListener) + rs(packet.d) + } + + const parseTextFailedListener = ( + packet: Packet, + ) => { + if (packet.d.id !== currentId) return + this.gateway.off('parseTextFailed', parseTextFailedListener) + rj() + } + + this.gateway.on('parsedText', parsedTextListener) + this.gateway.on('parseTextFailed', parseTextFailedListener) + }) + + return await promise + } + + /** + * Requests the API to parse the given image and return the text + * @param url The URL of the image + * @returns An object containing the ID of the request and the parsed text + */ + async parseImage(url: string) { + this.#throwIfNotReady() + + const currentId = (this.#parseId++).toString() + + this.gateway.send({ + op: ClientOperation.ParseImage, + d: { + image_url: url, + id: currentId, + }, + }) + + type CorrectPacket = Packet + + const promise = new Promise((rs, rj) => { + const parsedImageListener = (packet: CorrectPacket) => { + if (packet.d.id !== currentId) return + this.gateway.off('parsedImage', parsedImageListener) + rs(packet.d) + } + + const parseImageFailedListener = ( + packet: Packet, + ) => { + if (packet.d.id !== currentId) return + this.gateway.off('parseImageFailed', parseImageFailedListener) + rj() + } + + this.gateway.on('parsedImage', parsedImageListener) + this.gateway.on('parseImageFailed', parseImageFailedListener) + }) + + return await promise + } + + /** + * Adds an event listener + * @param name The event name to listen for + * @param handler The event handler + * @returns The event handler function + */ + on( + name: TOpName, + handler: ClientGatewayEventHandlers[TOpName], + ) { + this.gateway.on(name, handler) + return handler + } + + /** + * Removes an event listener + * @param name The event name to remove a listener from + * @param handler The event handler to remove + * @returns The removed event handler function + */ + off( + name: TOpName, + handler: ClientGatewayEventHandlers[TOpName], + ) { + this.gateway.off(name, handler) + return handler + } + + /** + * Adds an event listener that will only be called once + * @param name The event name to listen for + * @param handler The event handler + * @returns The event handler function + */ + once( + name: TOpName, + handler: ClientGatewayEventHandlers[TOpName], + ) { + this.gateway.once(name, handler) + return handler + } + + #throwIfNotReady() { + if (!this.isReady()) throw new Error('Client is not ready') + } +} + +export type ReadiedClient = Client & { ready: true } + +export interface ClientOptions { + api: ClientApiOptions +} + +export interface ClientApiOptions { + gatewayUrl: string +} diff --git a/packages/api/src/classes/ClientGateway.ts b/packages/api/src/classes/ClientGateway.ts index ef4b2da..5ca560f 100755 --- a/packages/api/src/classes/ClientGateway.ts +++ b/packages/api/src/classes/ClientGateway.ts @@ -1,234 +1,235 @@ -import { type RawData, WebSocket } from 'ws' -import type TypedEmitter from 'typed-emitter' -import { - ClientOperation, - DisconnectReason, - Packet, - ServerOperation, - deserializePacket, - isServerPacket, - serializePacket, - uncapitalize, -} from '@revanced/bot-shared' -import { EventEmitter } from 'events' - -/** - * The class that handles the WebSocket connection to the server. - * This is the only relevant class for the time being. But in the future, there may be more classes to handle different protocols of the API. - */ -export default class ClientGateway { - readonly url: string - ready: boolean = false - disconnected: boolean | DisconnectReason = DisconnectReason.NeverConnected - config: Readonly['d']> | null = null! - - #hbTimeout: NodeJS.Timeout = null! - #socket: WebSocket = null! - #emitter = new EventEmitter() as TypedEmitter - - constructor(options: ClientGatewayOptions) { - this.url = options.url - } - - /** - * Connects to the WebSocket API - * @returns A promise that resolves when the client is ready - */ - connect() { - return new Promise((rs, rj) => { - try { - this.#socket = new WebSocket(this.url) - - this.#socket.on('open', () => { - this.disconnected = false - rs() - }) - - this.#socket.on('close', () => - this.#handleDisconnect(DisconnectReason.Generic) - ) - - this.#listen() - this.ready = true - this.#emitter.emit('ready') - } catch (e) { - rj(e) - } - }) - } - - /** - * Adds an event listener - * @param name The event name to listen for - * @param handler The event handler - * @returns The event handler function - */ - on( - name: TOpName, - handler: ClientGatewayEventHandlers[typeof name] - ) { - this.#emitter.on(name, handler) - } - - /** - * Removes an event listener - * @param name The event name to remove a listener from - * @param handler The event handler to remove - * @returns The removed event handler function - */ - off( - name: TOpName, - handler: ClientGatewayEventHandlers[typeof name] - ) { - this.#emitter.off(name, handler) - } - - /** - * Adds an event listener that will only be called once - * @param name The event name to listen for - * @param handler The event handler - * @returns The event handler function - */ - once( - name: TOpName, - handler: ClientGatewayEventHandlers[typeof name] - ) { - this.#emitter.once(name, handler) - } - - /** - * Sends a packet to the server - * @param packet The packet to send - * @returns A promise that resolves when the packet has been sent - */ - send(packet: Packet) { - this.#throwIfDisconnected( - 'Cannot send a packet when already disconnected from the server' - ) - - return new Promise((resolve, reject) => - this.#socket.send(serializePacket(packet), err => - err ? reject(err) : resolve() - ) - ) - } - - /** - * Disconnects from the WebSocket API - */ - disconnect() { - this.#throwIfDisconnected( - 'Cannot disconnect when already disconnected from the server' - ) - - this.#handleDisconnect(DisconnectReason.Generic) - } - - /** - * Checks whether the client is ready - * @returns Whether the client is ready - */ - isReady(): this is ReadiedClientGateway { - return this.ready - } - - #listen() { - this.#socket.on('message', data => { - const packet = deserializePacket(this._toBuffer(data)) - // TODO: maybe log this? - // Just ignore the invalid packet, we don't have to disconnect - if (!isServerPacket(packet)) return - - this.#emitter.emit('packet', packet) - - switch (packet.op) { - case ServerOperation.Hello: - // eslint-disable-next-line no-case-declarations - const data = Object.freeze( - (packet as Packet).d - ) - this.config = data - this.#emitter.emit('hello', data) - this.#startHeartbeating() - break - case ServerOperation.Disconnect: - return this.#handleDisconnect( - (packet as Packet).d.reason - ) - default: - return this.#emitter.emit( - uncapitalize( - ServerOperation[ - packet.op - ] as ClientGatewayServerEventName - ), - // @ts-expect-error TypeScript doesn't know that the lines above negate the type enough - packet - ) - } - }) - } - - #throwIfDisconnected(errorMessage: string) { - if (this.disconnected !== false) throw new Error(errorMessage) - if (this.#socket.readyState !== this.#socket.OPEN) - throw new Error(errorMessage) - } - - #handleDisconnect(reason: DisconnectReason) { - clearTimeout(this.#hbTimeout) - this.disconnected = reason - this.#socket.close() - - this.#emitter.emit('disconnect', reason) - } - - #startHeartbeating() { - this.on('heartbeatAck', packet => { - this.#hbTimeout = setTimeout(() => { - this.send({ - op: ClientOperation.Heartbeat, - d: null, - }) - }, packet.d.nextHeartbeat - Date.now()) - }) - - // Immediately send a heartbeat so we can get when to send the next one - this.send({ - op: ClientOperation.Heartbeat, - d: null, - }) - } - - protected _toBuffer(data: RawData) { - if (data instanceof Buffer) return data - else if (data instanceof ArrayBuffer) return Buffer.from(data) - else return Buffer.concat(data) - } -} - -export interface ClientGatewayOptions { - /** - * The gateway URL to connect to - */ - url: string -} - -export type ClientGatewayServerEventName = keyof typeof ServerOperation - -export type ClientGatewayEventHandlers = { - [K in Uncapitalize]: ( - packet: Packet<(typeof ServerOperation)[Capitalize]> - ) => Promise | void -} & { - hello: ( - config: NonNullable - ) => Promise | void - ready: () => Promise | void - packet: (packet: Packet) => Promise | void - disconnect: (reason: DisconnectReason) => Promise | void -} - -export type ReadiedClientGateway = RequiredProperty< - InstanceType -> +import { EventEmitter } from 'events' +import { + ClientOperation, + DisconnectReason, + Packet, + ServerOperation, + deserializePacket, + isServerPacket, + serializePacket, + uncapitalize, +} from '@revanced/bot-shared' +import type TypedEmitter from 'typed-emitter' +import { type RawData, WebSocket } from 'ws' + +/** + * The class that handles the WebSocket connection to the server. + * This is the only relevant class for the time being. But in the future, there may be more classes to handle different protocols of the API. + */ +export default class ClientGateway { + readonly url: string + ready = false + disconnected: boolean | DisconnectReason = DisconnectReason.NeverConnected + config: Readonly['d']> | null = null! + + #hbTimeout: NodeJS.Timeout = null! + #socket: WebSocket = null! + #emitter = new EventEmitter() as TypedEmitter + + constructor(options: ClientGatewayOptions) { + this.url = options.url + } + + /** + * Connects to the WebSocket API + * @returns A promise that resolves when the client is ready + */ + connect() { + return new Promise((rs, rj) => { + try { + this.#socket = new WebSocket(this.url) + + this.#socket.on('open', () => { + this.disconnected = false + rs() + }) + + this.#socket.on('close', () => + this.#handleDisconnect(DisconnectReason.Generic), + ) + + this.#listen() + this.ready = true + this.#emitter.emit('ready') + } catch (e) { + rj(e) + } + }) + } + + /** + * Adds an event listener + * @param name The event name to listen for + * @param handler The event handler + * @returns The event handler function + */ + on( + name: TOpName, + handler: ClientGatewayEventHandlers[typeof name], + ) { + this.#emitter.on(name, handler) + } + + /** + * Removes an event listener + * @param name The event name to remove a listener from + * @param handler The event handler to remove + * @returns The removed event handler function + */ + off( + name: TOpName, + handler: ClientGatewayEventHandlers[typeof name], + ) { + this.#emitter.off(name, handler) + } + + /** + * Adds an event listener that will only be called once + * @param name The event name to listen for + * @param handler The event handler + * @returns The event handler function + */ + once( + name: TOpName, + handler: ClientGatewayEventHandlers[typeof name], + ) { + this.#emitter.once(name, handler) + } + + /** + * Sends a packet to the server + * @param packet The packet to send + * @returns A promise that resolves when the packet has been sent + */ + send(packet: Packet) { + this.#throwIfDisconnected( + 'Cannot send a packet when already disconnected from the server', + ) + + return new Promise((resolve, reject) => + this.#socket.send(serializePacket(packet), err => + err ? reject(err) : resolve(), + ), + ) + } + + /** + * Disconnects from the WebSocket API + */ + disconnect() { + this.#throwIfDisconnected( + 'Cannot disconnect when already disconnected from the server', + ) + + this.#handleDisconnect(DisconnectReason.Generic) + } + + /** + * Checks whether the client is ready + * @returns Whether the client is ready + */ + isReady(): this is ReadiedClientGateway { + return this.ready + } + + #listen() { + this.#socket.on('message', data => { + const packet = deserializePacket(this._toBuffer(data)) + // TODO: maybe log this? + // Just ignore the invalid packet, we don't have to disconnect + if (!isServerPacket(packet)) return + + this.#emitter.emit('packet', packet) + + switch (packet.op) { + case ServerOperation.Hello: { + // eslint-disable-next-line no-case-declarations + const data = Object.freeze( + (packet as Packet).d, + ) + this.config = data + this.#emitter.emit('hello', data) + this.#startHeartbeating() + break + } + case ServerOperation.Disconnect: + return this.#handleDisconnect( + (packet as Packet).d.reason, + ) + default: + return this.#emitter.emit( + uncapitalize( + ServerOperation[ + packet.op + ] as ClientGatewayServerEventName, + ), + // @ts-expect-error TypeScript doesn't know that the lines above negate the type enough + packet, + ) + } + }) + } + + #throwIfDisconnected(errorMessage: string) { + if (this.disconnected !== false) throw new Error(errorMessage) + if (this.#socket.readyState !== this.#socket.OPEN) + throw new Error(errorMessage) + } + + #handleDisconnect(reason: DisconnectReason) { + clearTimeout(this.#hbTimeout) + this.disconnected = reason + this.#socket.close() + + this.#emitter.emit('disconnect', reason) + } + + #startHeartbeating() { + this.on('heartbeatAck', packet => { + this.#hbTimeout = setTimeout(() => { + this.send({ + op: ClientOperation.Heartbeat, + d: null, + }) + }, packet.d.nextHeartbeat - Date.now()) + }) + + // Immediately send a heartbeat so we can get when to send the next one + this.send({ + op: ClientOperation.Heartbeat, + d: null, + }) + } + + protected _toBuffer(data: RawData) { + if (data instanceof Buffer) return data + else if (data instanceof ArrayBuffer) return Buffer.from(data) + else return Buffer.concat(data) + } +} + +export interface ClientGatewayOptions { + /** + * The gateway URL to connect to + */ + url: string +} + +export type ClientGatewayServerEventName = keyof typeof ServerOperation + +export type ClientGatewayEventHandlers = { + [K in Uncapitalize]: ( + packet: Packet]>, + ) => Promise | void +} & { + hello: ( + config: NonNullable, + ) => Promise | void + ready: () => Promise | void + packet: (packet: Packet) => Promise | void + disconnect: (reason: DisconnectReason) => Promise | void +} + +export type ReadiedClientGateway = RequiredProperty< + InstanceType +> diff --git a/packages/api/src/classes/index.ts b/packages/api/src/classes/index.ts index e02f863..585b439 100755 --- a/packages/api/src/classes/index.ts +++ b/packages/api/src/classes/index.ts @@ -1,4 +1,4 @@ -export { default as Client } from './Client.js' -export * from './Client.js' -export { default as ClientGateway } from './ClientGateway.js' -export * from './ClientGateway.js' +export { default as Client } from './Client.js' +export * from './Client.js' +export { default as ClientGateway } from './ClientGateway.js' +export * from './ClientGateway.js' diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index d5e08ef..b090345 100755 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1 +1 @@ -export * from './classes/index.js' +export * from './classes/index.js' diff --git a/packages/api/utility-types.d.ts b/packages/api/utility-types.d.ts index 1710e17..778651e 100755 --- a/packages/api/utility-types.d.ts +++ b/packages/api/utility-types.d.ts @@ -1 +1 @@ -type RequiredProperty = { [P in keyof T]: Required> } +type RequiredProperty = { [P in keyof T]: Required> } diff --git a/packages/shared/src/constants/DisconnectReason.ts b/packages/shared/src/constants/DisconnectReason.ts index 051d5e3..6935095 100755 --- a/packages/shared/src/constants/DisconnectReason.ts +++ b/packages/shared/src/constants/DisconnectReason.ts @@ -1,27 +1,27 @@ -/** - * Disconnect reasons for clients - */ -enum DisconnectReason { - /** - * Unknown reason - */ - Generic = 1, - /** - * The client did not respond in time - */ - TimedOut, - /** - * The client sent an invalid packet (unserializable or invalid JSON) - */ - InvalidPacket, - /** - * The server has encountered an internal error - */ - ServerError, - /** - * The client had never connected to the server (**CLIENT-ONLY**) - */ - NeverConnected, -} - -export default DisconnectReason +/** + * Disconnect reasons for clients + */ +enum DisconnectReason { + /** + * Unknown reason + */ + Generic = 1, + /** + * The client did not respond in time + */ + TimedOut = 2, + /** + * The client sent an invalid packet (unserializable or invalid JSON) + */ + InvalidPacket = 3, + /** + * The server has encountered an internal error + */ + ServerError = 4, + /** + * The client had never connected to the server (**CLIENT-ONLY**) + */ + NeverConnected = 5, +} + +export default DisconnectReason diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts index fa88738..0687d98 100755 --- a/packages/shared/src/constants/HumanizedDisconnectReason.ts +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -1,15 +1,15 @@ -import DisconnectReason from './DisconnectReason.js' - -/** - * Humanized disconnect reasons for logs - */ -const HumanizedDisconnectReason = { - [DisconnectReason.InvalidPacket]: 'has sent invalid packet', - [DisconnectReason.Generic]: 'has been disconnected for unknown reasons', - [DisconnectReason.TimedOut]: 'has timed out', - [DisconnectReason.ServerError]: - 'has been disconnected due to an internal server error', - [DisconnectReason.NeverConnected]: 'had never connected to the server', -} as const satisfies Record - -export default HumanizedDisconnectReason +import DisconnectReason from './DisconnectReason.js' + +/** + * Humanized disconnect reasons for logs + */ +const HumanizedDisconnectReason = { + [DisconnectReason.InvalidPacket]: 'has sent invalid packet', + [DisconnectReason.Generic]: 'has been disconnected for unknown reasons', + [DisconnectReason.TimedOut]: 'has timed out', + [DisconnectReason.ServerError]: + 'has been disconnected due to an internal server error', + [DisconnectReason.NeverConnected]: 'had never connected to the server', +} as const satisfies Record + +export default HumanizedDisconnectReason diff --git a/packages/shared/src/constants/Operation.ts b/packages/shared/src/constants/Operation.ts index dcd0b96..9c4ee4e 100755 --- a/packages/shared/src/constants/Operation.ts +++ b/packages/shared/src/constants/Operation.ts @@ -1,57 +1,57 @@ -/** - * Client operation codes for the gateway - */ -export enum ClientOperation { - /** - * Client's heartbeat (to check if the connection is dead or not) - */ - Heartbeat = 100, - - /** - * Client's request to parse text - */ - ParseText = 110, - /** - * Client's request to parse image - */ - ParseImage, -} - -/** - * Server operation codes for the gateway - */ -export enum ServerOperation { - /** - * Server's acknowledgement of a client's heartbeat - */ - HeartbeatAck = 1, - /** - * Server's initial response to a client's connection - */ - Hello, - - /** - * Server's response to client's request to parse text - */ - ParsedText = 10, - /** - * Server's response to client's request to parse image - */ - ParsedImage, - /** - * Server's failure response to client's request to parse text - */ - ParseTextFailed, - /** - * Server's failure response to client's request to parse image - */ - ParseImageFailed, - - /** - * Server's disconnect message - */ - Disconnect = 20, -} - -export const Operation = { ...ClientOperation, ...ServerOperation } as const -export type Operation = ClientOperation | ServerOperation +/** + * Client operation codes for the gateway + */ +export enum ClientOperation { + /** + * Client's heartbeat (to check if the connection is dead or not) + */ + Heartbeat = 100, + + /** + * Client's request to parse text + */ + ParseText = 110, + /** + * Client's request to parse image + */ + ParseImage = 111, +} + +/** + * Server operation codes for the gateway + */ +export enum ServerOperation { + /** + * Server's acknowledgement of a client's heartbeat + */ + HeartbeatAck = 1, + /** + * Server's initial response to a client's connection + */ + Hello = 2, + + /** + * Server's response to client's request to parse text + */ + ParsedText = 10, + /** + * Server's response to client's request to parse image + */ + ParsedImage = 11, + /** + * Server's failure response to client's request to parse text + */ + ParseTextFailed = 12, + /** + * Server's failure response to client's request to parse image + */ + ParseImageFailed = 13, + + /** + * Server's disconnect message + */ + Disconnect = 20, +} + +export const Operation = { ...ClientOperation, ...ServerOperation } as const +export type Operation = ClientOperation | ServerOperation diff --git a/packages/shared/src/constants/index.ts b/packages/shared/src/constants/index.ts index 097859d..98ae4d4 100755 --- a/packages/shared/src/constants/index.ts +++ b/packages/shared/src/constants/index.ts @@ -1,3 +1,3 @@ -export { default as DisconnectReason } from './DisconnectReason.js' -export { default as HumanizedDisconnectReason } from './HumanizedDisconnectReason.js' -export * from './Operation.js' +export { default as DisconnectReason } from './DisconnectReason.js' +export { default as HumanizedDisconnectReason } from './HumanizedDisconnectReason.js' +export * from './Operation.js' diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index e5ef662..b143126 100755 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,3 @@ -export * from './constants/index.js' -export * from './schemas/index.js' -export * from './utils/index.js' +export * from './constants/index.js' +export * from './schemas/index.js' +export * from './utils/index.js' diff --git a/packages/shared/src/schemas/Packet.ts b/packages/shared/src/schemas/Packet.ts index 34148fa..6c133b0 100755 --- a/packages/shared/src/schemas/Packet.ts +++ b/packages/shared/src/schemas/Packet.ts @@ -1,101 +1,101 @@ -import DisconnectReason from '../constants/DisconnectReason.js' -import { - ClientOperation, - Operation, - ServerOperation, -} from '../constants/Operation.js' -import { - object, - enum_, - special, - ObjectSchema, - number, - string, - Output, - AnySchema, - null_, - NullSchema, - array, - url, - parse, - // merge -} from 'valibot' - -/** - * Schema to validate packets - */ -export const PacketSchema = special(input => { - if ( - typeof input === 'object' && - input && - 'op' in input && - typeof input.op === 'number' && - input.op in Operation && - 'd' in input && - typeof input.d === 'object' - ) { - try { - parse(PacketDataSchemas[input.op as Operation], input.d) - return true - } catch { - return false - } - } - return false -}, 'Invalid packet data') - -/** - * Schema to validate packet data for each possible operations - */ -export const PacketDataSchemas = { - [ServerOperation.Hello]: object({ - heartbeatInterval: number(), - }), - [ServerOperation.HeartbeatAck]: object({ - nextHeartbeat: number(), - }), - [ServerOperation.ParsedText]: object({ - id: string(), - labels: array( - object({ - name: string(), - confidence: special( - input => - typeof input === 'number' && input >= 0 && input <= 1 - ), - }) - ), - }), - [ServerOperation.ParsedImage]: object({ - id: string(), - text: string(), - }), - [ServerOperation.ParseTextFailed]: object({ - id: string(), - }), - [ServerOperation.ParseImageFailed]: object({ - id: string(), - }), - [ServerOperation.Disconnect]: object({ - reason: enum_(DisconnectReason), - }), - - [ClientOperation.Heartbeat]: null_(), - [ClientOperation.ParseText]: object({ - id: string(), - text: string(), - }), - [ClientOperation.ParseImage]: object({ - id: string(), - image_url: string([url()]), - }), -} as const satisfies Record< - Operation, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ObjectSchema | AnySchema | NullSchema -> - -export type Packet = { - op: TOp - d: Output<(typeof PacketDataSchemas)[TOp]> -} +import { + url, + AnySchema, + NullSchema, + ObjectSchema, + Output, + array, + enum_, + null_, + number, + object, + parse, + special, + string, + // merge +} from 'valibot' +import DisconnectReason from '../constants/DisconnectReason.js' +import { + ClientOperation, + Operation, + ServerOperation, +} from '../constants/Operation.js' + +/** + * Schema to validate packets + */ +export const PacketSchema = special(input => { + if ( + typeof input === 'object' && + input && + 'op' in input && + typeof input.op === 'number' && + input.op in Operation && + 'd' in input && + typeof input.d === 'object' + ) { + try { + parse(PacketDataSchemas[input.op as Operation], input.d) + return true + } catch { + return false + } + } + return false +}, 'Invalid packet data') + +/** + * Schema to validate packet data for each possible operations + */ +export const PacketDataSchemas = { + [ServerOperation.Hello]: object({ + heartbeatInterval: number(), + }), + [ServerOperation.HeartbeatAck]: object({ + nextHeartbeat: number(), + }), + [ServerOperation.ParsedText]: object({ + id: string(), + labels: array( + object({ + name: string(), + confidence: special( + input => + typeof input === 'number' && input >= 0 && input <= 1, + ), + }), + ), + }), + [ServerOperation.ParsedImage]: object({ + id: string(), + text: string(), + }), + [ServerOperation.ParseTextFailed]: object({ + id: string(), + }), + [ServerOperation.ParseImageFailed]: object({ + id: string(), + }), + [ServerOperation.Disconnect]: object({ + reason: enum_(DisconnectReason), + }), + + [ClientOperation.Heartbeat]: null_(), + [ClientOperation.ParseText]: object({ + id: string(), + text: string(), + }), + [ClientOperation.ParseImage]: object({ + id: string(), + image_url: string([url()]), + }), +} as const satisfies Record< + Operation, + // biome-ignore lint/suspicious/noExplicitAny: This is a schema, it's not possible to type it + ObjectSchema | AnySchema | NullSchema +> + +export type Packet = { + op: TOp + d: Output +} diff --git a/packages/shared/src/schemas/index.ts b/packages/shared/src/schemas/index.ts index b593970..8b93531 100755 --- a/packages/shared/src/schemas/index.ts +++ b/packages/shared/src/schemas/index.ts @@ -1 +1 @@ -export * from './Packet.js' +export * from './Packet.js' diff --git a/packages/shared/src/utils/guard.ts b/packages/shared/src/utils/guard.ts index 24a98e2..86ec59f 100755 --- a/packages/shared/src/utils/guard.ts +++ b/packages/shared/src/utils/guard.ts @@ -1,41 +1,41 @@ -import { Packet } from '../schemas/Packet.js' -import { - ClientOperation, - Operation, - ServerOperation, -} from '../constants/Operation.js' - -/** - * Checks whether a packet is trying to do the given operation - * @param op Operation code to check - * @param packet A packet - * @returns Whether this packet is trying to do the operation given - */ -export function packetMatchesOperation( - op: TOp, - packet: Packet -): packet is Packet { - return packet.op === op -} - -/** - * Checks whether this packet is a client packet **(this does NOT validate the data)** - * @param packet A packet - * @returns Whether this packet is a client packet - */ -export function isClientPacket( - packet: Packet -): packet is Packet { - return packet.op in ClientOperation -} - -/** - * Checks whether this packet is a server packet **(this does NOT validate the data)** - * @param packet A packet - * @returns Whether this packet is a server packet - */ -export function isServerPacket( - packet: Packet -): packet is Packet { - return packet.op in ServerOperation -} +import { + ClientOperation, + Operation, + ServerOperation, +} from '../constants/Operation.js' +import { Packet } from '../schemas/Packet.js' + +/** + * Checks whether a packet is trying to do the given operation + * @param op Operation code to check + * @param packet A packet + * @returns Whether this packet is trying to do the operation given + */ +export function packetMatchesOperation( + op: TOp, + packet: Packet, +): packet is Packet { + return packet.op === op +} + +/** + * Checks whether this packet is a client packet **(this does NOT validate the data)** + * @param packet A packet + * @returns Whether this packet is a client packet + */ +export function isClientPacket( + packet: Packet, +): packet is Packet { + return packet.op in ClientOperation +} + +/** + * Checks whether this packet is a server packet **(this does NOT validate the data)** + * @param packet A packet + * @returns Whether this packet is a server packet + */ +export function isServerPacket( + packet: Packet, +): packet is Packet { + return packet.op in ServerOperation +} diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index 61fcb8d..47ea1c6 100755 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -1,3 +1,3 @@ -export * from './guard.js' -export * from './serialization.js' -export * from './string.js' +export * from './guard.js' +export * from './serialization.js' +export * from './string.js' diff --git a/packages/shared/src/utils/serialization.ts b/packages/shared/src/utils/serialization.ts index 764af14..92b274f 100755 --- a/packages/shared/src/utils/serialization.ts +++ b/packages/shared/src/utils/serialization.ts @@ -1,23 +1,23 @@ -import * as BSON from 'bson' -import { Packet, PacketSchema } from '../schemas/index.js' -import { Operation } from '../constants/index.js' -import { parse } from 'valibot' - -/** - * Compresses a packet into a buffer - * @param packet The packet to compress - * @returns A buffer of the compressed packet - */ -export function serializePacket(packet: Packet) { - return BSON.serialize(packet) -} - -/** - * Decompresses a buffer into a packet - * @param buffer The buffer to decompress - * @returns A packet - */ -export function deserializePacket(buffer: Buffer) { - const data = BSON.deserialize(buffer) - return parse(PacketSchema, data) as Packet -} +import * as BSON from 'bson' +import { parse } from 'valibot' +import { Operation } from '../constants/index.js' +import { Packet, PacketSchema } from '../schemas/index.js' + +/** + * Compresses a packet into a buffer + * @param packet The packet to compress + * @returns A buffer of the compressed packet + */ +export function serializePacket(packet: Packet) { + return BSON.serialize(packet) +} + +/** + * Decompresses a buffer into a packet + * @param buffer The buffer to decompress + * @returns A packet + */ +export function deserializePacket(buffer: Buffer) { + const data = BSON.deserialize(buffer) + return parse(PacketSchema, data) as Packet +} diff --git a/packages/shared/src/utils/string.ts b/packages/shared/src/utils/string.ts index c9d44fe..e7e805e 100755 --- a/packages/shared/src/utils/string.ts +++ b/packages/shared/src/utils/string.ts @@ -1,8 +1,8 @@ -/** - * Uncapitalizes the first letter of a string - * @param str The string to uncapitalize - * @returns The uncapitalized string - */ -export function uncapitalize(str: T): Uncapitalize { - return (str.charAt(0).toLowerCase() + str.slice(1)) as Uncapitalize -} +/** + * Uncapitalizes the first letter of a string + * @param str The string to uncapitalize + * @returns The uncapitalized string + */ +export function uncapitalize(str: T): Uncapitalize { + return (str.charAt(0).toLowerCase() + str.slice(1)) as Uncapitalize +} diff --git a/tsconfig.apis.json b/tsconfig.apis.json index 89b3d2b..ffcbb94 100755 --- a/tsconfig.apis.json +++ b/tsconfig.apis.json @@ -1,3 +1,3 @@ -{ - "extends": "./tsconfig.base.json" -} +{ + "extends": "./tsconfig.base.json" +} diff --git a/tsconfig.packages.json b/tsconfig.packages.json index 5b6cb51..3406eb1 100755 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -1,15 +1,15 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "declaration": true, - "declarationMap": true - }, - "references": [ - { - "path": "./packages/shared" - }, - { - "path": "./packages/api" - } - ] -} +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true + }, + "references": [ + { + "path": "./packages/shared" + }, + { + "path": "./packages/api" + } + ] +} diff --git a/turbo.json b/turbo.json index cfafea5..7cacc10 100755 --- a/turbo.json +++ b/turbo.json @@ -1,14 +1,14 @@ -{ - "$schema": "https://turbo.build/schema.json", - "pipeline": { - "build": { - "dependsOn": ["^build"], - "outputs": ["dist/**"], - "outputMode": "errors-only" - }, - "watch": { - "dependsOn": ["^watch"], - "outputMode": "errors-only" - } - } -} +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"], + "outputMode": "errors-only" + }, + "watch": { + "dependsOn": ["^watch"], + "outputMode": "errors-only" + } + } +} From 17c6be7bee5b5c24fd4a5279e73374b0bb7a6229 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 29 Nov 2023 00:52:17 +0700 Subject: [PATCH 018/312] feat(packages/shared): add logger factory - @revanced/websocket-api now also utilizes the new logger from the shared package - @revanced/websocket-api/utils/checkEnv has been renamed to its full form - It also no longer returns anything as it's no longer needed --- apis/websocket/config.json | 2 +- apis/websocket/config.schema.json | 9 +- apis/websocket/src/classes/Client.ts | 49 +++-------- apis/websocket/src/events/index.ts | 2 +- apis/websocket/src/events/parseImage.ts | 19 +---- apis/websocket/src/events/parseText.ts | 5 +- apis/websocket/src/index.ts | 78 ++++++------------ apis/websocket/src/utils/checkEnv.ts | 31 ------- apis/websocket/src/utils/checkEnvironment.ts | 23 ++++++ apis/websocket/src/utils/getConfig.ts | 7 +- apis/websocket/src/utils/index.ts | 3 +- apis/websocket/src/utils/logger.ts | 25 ------ bun.lockb | Bin 332868 -> 340420 bytes package.json | 58 ++++++------- packages/shared/package.json | 2 + .../constants/HumanizedDisconnectReason.ts | 3 +- packages/shared/src/schemas/Packet.ts | 11 +-- packages/shared/src/utils/guard.ts | 19 +---- packages/shared/src/utils/index.ts | 1 + packages/shared/src/utils/logger.ts | 66 +++++++++++++++ 20 files changed, 179 insertions(+), 234 deletions(-) delete mode 100755 apis/websocket/src/utils/checkEnv.ts create mode 100755 apis/websocket/src/utils/checkEnvironment.ts delete mode 100755 apis/websocket/src/utils/logger.ts create mode 100644 packages/shared/src/utils/logger.ts diff --git a/apis/websocket/config.json b/apis/websocket/config.json index 8d54b6c..0217e3a 100755 --- a/apis/websocket/config.json +++ b/apis/websocket/config.json @@ -5,5 +5,5 @@ "port": 3000, "ocrConcurrentQueues": 1, "clientHeartbeatInterval": 5000, - "debugLogsInProduction": false + "consoleLogLevel": "silly" } diff --git a/apis/websocket/config.schema.json b/apis/websocket/config.schema.json index 30fc6f7..42e1214 100755 --- a/apis/websocket/config.schema.json +++ b/apis/websocket/config.schema.json @@ -22,10 +22,11 @@ "type": "integer", "default": 60000 }, - "debugLogsInProduction": { - "description": "Whether to print debug logs in production", - "type": "boolean", - "default": false + "consoleLogLevel": { + "description": "The log level to print to console", + "type": "string", + "enum": ["error", "warn", "info", "verbose", "debug", "silly", "none"], + "default": "info" } } } diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index dcfdbc1..6de63d0 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -47,43 +47,29 @@ export default class Client { this.#emitter.emit('ready') }) .catch(() => { - if (this.disconnected === false) - this.disconnect(DisconnectReason.ServerError) + if (this.disconnected === false) this.disconnect(DisconnectReason.ServerError) else this.forceDisconnect(DisconnectReason.ServerError) }) } - on( - name: TOpName, - handler: ClientEventHandlers[typeof name], - ) { + on(name: TOpName, handler: ClientEventHandlers[typeof name]) { this.#emitter.on(name, handler) } - once( - name: TOpName, - handler: ClientEventHandlers[typeof name], - ) { + once(name: TOpName, handler: ClientEventHandlers[typeof name]) { this.#emitter.once(name, handler) } - off( - name: TOpName, - handler: ClientEventHandlers[typeof name], - ) { + off(name: TOpName, handler: ClientEventHandlers[typeof name]) { this.#emitter.off(name, handler) } send(packet: Packet) { return new Promise((resolve, reject) => { try { - this.#throwIfDisconnected( - 'Cannot send packet to client that has already disconnected', - ) + this.#throwIfDisconnected('Cannot send packet to client that has already disconnected') - this.#socket.send(serializePacket(packet), err => - err ? reject(err) : resolve(), - ) + this.#socket.send(serializePacket(packet), err => (err ? reject(err) : resolve())) } catch (e) { reject(e) } @@ -91,16 +77,12 @@ export default class Client { } async disconnect(reason: DisconnectReason = DisconnectReason.Generic) { - this.#throwIfDisconnected( - 'Cannot disconnect client that has already disconnected', - ) + this.#throwIfDisconnected('Cannot disconnect client that has already disconnected') try { await this.send({ op: ServerOperation.Disconnect, d: { reason } }) } catch (err) { - throw new Error( - `Cannot send disconnect reason to client ${this.id}: ${err}`, - ) + throw new Error(`Cannot send disconnect reason to client ${this.id}: ${err}`) } finally { this.forceDisconnect(reason) } @@ -173,10 +155,7 @@ export default class Client { if (Date.now() - this.lastHeartbeat > 0) { // TODO: put into config // 5000 is extra time to account for latency - const interval = setTimeout( - () => this.disconnect(DisconnectReason.TimedOut), - 5000, - ) + const interval = setTimeout(() => this.disconnect(DisconnectReason.TimedOut), 5000) this.once('heartbeat', () => clearTimeout(interval)) // This should never happen but it did in my testing so I'm adding this just in case @@ -208,11 +187,9 @@ export type ClientEventName = keyof typeof ClientOperation export type ClientEventHandlers = { [K in Uncapitalize]: ( packet: ClientPacketObject]>, - ) => Promise | void + ) => Promise | unknown } & { - ready: () => Promise | void - packet: ( - packet: ClientPacketObject, - ) => Promise | void - disconnect: (reason: DisconnectReason) => Promise | void + ready: () => Promise | unknown + packet: (packet: ClientPacketObject) => Promise | unknown + disconnect: (reason: DisconnectReason) => Promise | unknown } diff --git a/apis/websocket/src/events/index.ts b/apis/websocket/src/events/index.ts index eb97665..9f1ed4f 100755 --- a/apis/websocket/src/events/index.ts +++ b/apis/websocket/src/events/index.ts @@ -3,7 +3,7 @@ import type { Wit } from 'node-wit' import type { Worker as TesseractWorker } from 'tesseract.js' import { ClientPacketObject } from '../classes/Client.js' import type { Config } from '../utils/getConfig.js' -import type { Logger } from '../utils/logger.js' +import type { Logger } from '@revanced/bot-shared' export { default as parseTextEventHandler } from './parseText.js' export { default as parseImageEventHandler } from './parseImage.js' diff --git a/apis/websocket/src/events/parseImage.ts b/apis/websocket/src/events/parseImage.ts index 742f097..61fa14a 100755 --- a/apis/websocket/src/events/parseImage.ts +++ b/apis/websocket/src/events/parseImage.ts @@ -14,13 +14,8 @@ const parseImageEventHandler: EventHandler = async ( d: { image_url: imageUrl, id }, } = packet - logger.debug( - `Client ${client.id} requested to parse image from URL:`, - imageUrl, - ) - logger.debug( - `Queue currently has ${queue.remaining}/${config.ocrConcurrentQueues} items in it`, - ) + logger.debug(`Client ${client.id} requested to parse image from URL:`, imageUrl) + logger.debug(`Queue currently has ${queue.remaining}/${config.ocrConcurrentQueues} items in it`) if (queue.remaining < config.ocrConcurrentQueues) queue.shift() await queue.wait() @@ -30,10 +25,7 @@ const parseImageEventHandler: EventHandler = async ( const { data, jobId } = await tesseractWorker.recognize(imageUrl) - logger.debug( - `Recognized image from URL for client ${client.id} (job ${jobId}):`, - data.text, - ) + logger.debug(`Recognized image from URL for client ${client.id} (job ${jobId}):`, data.text) await client.send({ op: ServerOperation.ParsedImage, d: { @@ -42,10 +34,7 @@ const parseImageEventHandler: EventHandler = async ( }, }) } catch { - logger.error( - `Failed to parse image from URL for client ${client.id}:`, - imageUrl, - ) + logger.error(`Failed to parse image from URL for client ${client.id}:`, imageUrl) await client.send({ op: ServerOperation.ParseImageFailed, d: { diff --git a/apis/websocket/src/events/parseText.ts b/apis/websocket/src/events/parseText.ts index 7818036..0d9246f 100755 --- a/apis/websocket/src/events/parseText.ts +++ b/apis/websocket/src/events/parseText.ts @@ -4,10 +4,7 @@ import { inspect as inspectObject } from 'node:util' import type { EventHandler } from './index.js' -const parseTextEventHandler: EventHandler = async ( - packet, - { witClient, logger }, -) => { +const parseTextEventHandler: EventHandler = async (packet, { witClient, logger }) => { const { client, d: { text, id }, diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index 108341a..d04bdfc 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -9,25 +9,21 @@ import { inspect as inspectObject } from 'node:util' import Client from './classes/Client.js' -import { - EventContext, - parseImageEventHandler, - parseTextEventHandler, -} from './events/index.js' +import { EventContext, parseImageEventHandler, parseTextEventHandler } from './events/index.js' -import { - DisconnectReason, - HumanizedDisconnectReason, -} from '@revanced/bot-shared' +import { DisconnectReason, HumanizedDisconnectReason, createLogger } from '@revanced/bot-shared' import { WebSocket } from 'ws' -import { checkEnv, getConfig, logger } from './utils/index.js' +import { checkEnvironment, getConfig } from './utils/index.js' + +// Load config, init logger, check environment -// Check environment variables and load config -const environment = checkEnv(logger) const config = getConfig() +const logger = createLogger('websocket-api', { + level: config.consoleLogLevel === 'none' ? 'error' : config.consoleLogLevel, + silent: config.consoleLogLevel === 'none', +}) -if (!config.debugLogsInProduction && environment === 'production') - logger.debug = () => {} +checkEnvironment(logger) // Workers and API clients @@ -71,46 +67,25 @@ const server = fastify() clients.add(client) logger.debug(`Client ${client.id}'s instance has been added`) - logger.info( - `New client connected (now ${clients.size} clients) with ID:`, - client.id, - ) + logger.info(`New client connected (now ${clients.size} clients) with ID:`, client.id) client.on('disconnect', reason => { clients.delete(client) - logger.info( - `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}`, - ) + logger.info(`Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}`) }) - client.on('parseText', async packet => - parseTextEventHandler(packet, eventContext), - ) + client.on('parseText', async packet => parseTextEventHandler(packet, eventContext)) - client.on('parseImage', async packet => - parseImageEventHandler(packet, eventContext), - ) + client.on('parseImage', async packet => parseImageEventHandler(packet, eventContext)) + + if (['debug', 'silly'].includes(config.consoleLogLevel)) { + logger.debug('Debug logs enabled, attaching debug events...') - if ( - environment === 'development' && - !config.debugLogsInProduction - ) { - logger.debug( - 'Running development mode or debug logs in production is enabled, attaching debug events...', - ) client.on('packet', ({ client, ...rawPacket }) => - logger.debug( - `Packet received from client ${client.id}:`, - inspectObject(rawPacket), - ), + logger.debug(`Packet received from client ${client.id}: ${inspectObject(rawPacket)}`), ) - client.on('heartbeat', () => - logger.debug( - 'Heartbeat received from client', - client.id, - ), - ) + client.on('heartbeat', () => logger.debug('Heartbeat received from client', client.id)) } } catch (e) { if (e instanceof Error) logger.error(e.stack ?? e.message) @@ -125,22 +100,19 @@ const server = fastify() return connection.socket.terminate() } - if (client.disconnected === false) - client.disconnect(DisconnectReason.ServerError) + if (client.disconnected === false) client.disconnect(DisconnectReason.ServerError) else client.forceDisconnect() clients.delete(client) - logger.debug( - `Client ${client.id} disconnected because of an internal error`, - ) + logger.debug(`Client ${client.id} disconnected because of an internal error`) } }) }) // Start the server -logger.debug('Starting with these configurations:', inspectObject(config)) +logger.debug(`Starting with these configurations: ${inspectObject(config)}`, ) await server.listen({ host: config.address ?? '0.0.0.0', @@ -150,8 +122,4 @@ await server.listen({ const addressInfo = server.server.address() if (!addressInfo || typeof addressInfo !== 'object') logger.debug('Server started, but cannot determine address information') -else - logger.info( - 'Server started at:', - `${addressInfo.address}:${addressInfo.port}`, - ) +else logger.info(`Server started at: ${addressInfo.address}:${addressInfo.port}`) diff --git a/apis/websocket/src/utils/checkEnv.ts b/apis/websocket/src/utils/checkEnv.ts deleted file mode 100755 index 3c503e2..0000000 --- a/apis/websocket/src/utils/checkEnv.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Logger } from './logger.js' - -export default function checkEnv(logger: Logger) { - if (!process.env['NODE_ENV']) - logger.warn('NODE_ENV not set, defaulting to `development`') - const environment = (process.env['NODE_ENV'] ?? - 'development') as NodeEnvironment - - if (!['development', 'production'].includes(environment)) { - logger.error( - 'NODE_ENV is neither `development` nor `production`, unable to determine environment', - ) - logger.info('Set NODE_ENV to blank to use `development` mode') - process.exit(1) - } - - logger.info(`Running in ${environment} mode...`) - - if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { - logger.warn( - 'You seem to be using .env files, this is generally not a good idea in production...', - ) - } - - if (!process.env['WIT_AI_TOKEN']) { - logger.error('WIT_AI_TOKEN is not defined in the environment variables') - process.exit(1) - } - - return environment -} diff --git a/apis/websocket/src/utils/checkEnvironment.ts b/apis/websocket/src/utils/checkEnvironment.ts new file mode 100755 index 0000000..803d94e --- /dev/null +++ b/apis/websocket/src/utils/checkEnvironment.ts @@ -0,0 +1,23 @@ +import type { Logger } from '@revanced/bot-shared' + +export default function checkEnvironment(logger: Logger) { + if (!process.env['NODE_ENV']) logger.warn('NODE_ENV not set, defaulting to `development`') + const environment = (process.env['NODE_ENV'] ?? 'development') as NodeEnvironment + + if (!['development', 'production'].includes(environment)) { + logger.error('NODE_ENV is neither `development` nor `production`, unable to determine environment') + logger.info('Set NODE_ENV to blank to use `development` mode') + process.exit(1) + } + + logger.info(`Running in ${environment} mode...`) + + if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { + logger.warn('You seem to be using .env files, this is generally not a good idea in production...') + } + + if (!process.env['WIT_AI_TOKEN']) { + logger.error('WIT_AI_TOKEN is not defined in the environment variables') + process.exit(1) + } +} diff --git a/apis/websocket/src/utils/getConfig.ts b/apis/websocket/src/utils/getConfig.ts index f3168cb..841c219 100755 --- a/apis/websocket/src/utils/getConfig.ts +++ b/apis/websocket/src/utils/getConfig.ts @@ -22,17 +22,14 @@ type BaseTypeOf = T extends (infer U)[] ? { [K in keyof T]: T[K] } : T -export type Config = Omit< - BaseTypeOf, - '$schema' -> +export type Config = Omit, '$schema'> export const defaultConfig: Config = { address: '127.0.0.1', port: 80, ocrConcurrentQueues: 1, clientHeartbeatInterval: 60000, - debugLogsInProduction: false, + consoleLogLevel: 'info', } export default function getConfig() { diff --git a/apis/websocket/src/utils/index.ts b/apis/websocket/src/utils/index.ts index 2dcb7a6..4aab21d 100755 --- a/apis/websocket/src/utils/index.ts +++ b/apis/websocket/src/utils/index.ts @@ -1,3 +1,2 @@ export { default as getConfig } from './getConfig.js' -export { default as checkEnv } from './checkEnv.js' -export { default as logger } from './logger.js' +export { default as checkEnvironment } from './checkEnvironment.js' diff --git a/apis/websocket/src/utils/logger.ts b/apis/websocket/src/utils/logger.ts deleted file mode 100755 index 88c870d..0000000 --- a/apis/websocket/src/utils/logger.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Chalk } from 'chalk' - -const chalk = new Chalk() -const logger = { - debug: (...args) => console.debug(chalk.gray('DEBUG:', ...args)), - info: (...args) => - console.info(chalk.bgBlue.whiteBright(' INFO '), ...args), - warn: (...args) => - console.warn( - chalk.bgYellow.blackBright.bold(' WARN '), - chalk.yellowBright(...args), - ), - error: (...args) => - console.error( - chalk.bgRed.whiteBright.bold(' ERROR '), - chalk.redBright(...args), - ), - log: console.log, -} satisfies Logger - -export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'log' -export type LogFunction = (...x: unknown[]) => void -export type Logger = Record - -export default logger diff --git a/bun.lockb b/bun.lockb index ecb2936d05f5cf49a1cbbd14d910bf728e9e3e3d..d3eac3eb57244a5e7255a9e836a936c8c1c4a1bd 100755 GIT binary patch delta 17154 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 Bbq@dl delta 13024 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 Hu)YESigtif diff --git a/package.json b/package.json index 210afe1..718ff0c 100755 --- a/package.json +++ b/package.json @@ -1,38 +1,11 @@ { "name": "revanced-helper", "version": "0.0.0", - "description": "🤖 Bots assisting ReVanced on multiple platforms", - "private": true, - "workspaces": [ - "apis/*", - "bots/*", - "packages/*" - ], - "scripts": { - "build": "turbo run build", - "watch": "turbo run watch", - "format": "prettier --ignore-path .gitignore --write .", - "format:check": "prettier --ignore-path .gitignore --cache --check .", - "lint": "eslint --ignore-path .gitignore --cache .", - "lint:apply": "eslint --ignore-path .gitignore --fix .", - "commitlint": "commitlint --edit", - "t": "turbo run", - "prepare": "lefthook install" - }, + "author": "Palm (https://github.com/PalmDevs)", "repository": { "type": "git", "url": "git+https://github.com/revanced/revanced-helper.git" }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", "devDependencies": { "@biomejs/biome": "1.3.3", "@commitlint/cli": "^18.4.3", @@ -48,9 +21,36 @@ "turbo": "^1.10.16", "typescript": "^5.3.2" }, + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "description": "🤖 Bots assisting ReVanced on multiple platforms", + "homepage": "https://github.com/revanced/revanced-helper#readme", + "license": "GPL-3.0-or-later", "overrides": { "uuid": ">=9.0.0", "isomorphic-fetch": ">=3.0.0" }, - "trustedDependencies": ["lefthook", "biome", "turbo"] + "private": true, + "scripts": { + "build": "turbo run build", + "watch": "turbo run watch", + "format": "prettier --ignore-path .gitignore --write .", + "format:check": "prettier --ignore-path .gitignore --cache --check .", + "lint": "eslint --ignore-path .gitignore --cache .", + "lint:apply": "eslint --ignore-path .gitignore --fix .", + "commitlint": "commitlint --edit", + "t": "turbo run", + "prepare": "lefthook install" + }, + "trustedDependencies": ["lefthook", "biome", "turbo"], + "workspaces": [ + "apis/*", + "bots/*", + "packages/*" + ] } diff --git a/packages/shared/package.json b/packages/shared/package.json index f877e75..19746ba 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -31,7 +31,9 @@ "homepage": "https://github.com/revanced/revanced-helper#readme", "dependencies": { "bson": "^6.2.0", + "chalk": "^5.3.0", "valibot": "^0.21.0", + "winston": "^3.11.0", "zod": "^3.22.4" } } diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts index 0687d98..fb0c070 100755 --- a/packages/shared/src/constants/HumanizedDisconnectReason.ts +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -7,8 +7,7 @@ const HumanizedDisconnectReason = { [DisconnectReason.InvalidPacket]: 'has sent invalid packet', [DisconnectReason.Generic]: 'has been disconnected for unknown reasons', [DisconnectReason.TimedOut]: 'has timed out', - [DisconnectReason.ServerError]: - 'has been disconnected due to an internal server error', + [DisconnectReason.ServerError]: 'has been disconnected due to an internal server error', [DisconnectReason.NeverConnected]: 'had never connected to the server', } as const satisfies Record diff --git a/packages/shared/src/schemas/Packet.ts b/packages/shared/src/schemas/Packet.ts index 6c133b0..9e41554 100755 --- a/packages/shared/src/schemas/Packet.ts +++ b/packages/shared/src/schemas/Packet.ts @@ -15,11 +15,7 @@ import { // merge } from 'valibot' import DisconnectReason from '../constants/DisconnectReason.js' -import { - ClientOperation, - Operation, - ServerOperation, -} from '../constants/Operation.js' +import { ClientOperation, Operation, ServerOperation } from '../constants/Operation.js' /** * Schema to validate packets @@ -59,10 +55,7 @@ export const PacketDataSchemas = { labels: array( object({ name: string(), - confidence: special( - input => - typeof input === 'number' && input >= 0 && input <= 1, - ), + confidence: special(input => typeof input === 'number' && input >= 0 && input <= 1), }), ), }), diff --git a/packages/shared/src/utils/guard.ts b/packages/shared/src/utils/guard.ts index 86ec59f..b58983b 100755 --- a/packages/shared/src/utils/guard.ts +++ b/packages/shared/src/utils/guard.ts @@ -1,8 +1,4 @@ -import { - ClientOperation, - Operation, - ServerOperation, -} from '../constants/Operation.js' +import { ClientOperation, Operation, ServerOperation } from '../constants/Operation.js' import { Packet } from '../schemas/Packet.js' /** @@ -11,10 +7,7 @@ import { Packet } from '../schemas/Packet.js' * @param packet A packet * @returns Whether this packet is trying to do the operation given */ -export function packetMatchesOperation( - op: TOp, - packet: Packet, -): packet is Packet { +export function packetMatchesOperation(op: TOp, packet: Packet): packet is Packet { return packet.op === op } @@ -23,9 +16,7 @@ export function packetMatchesOperation( * @param packet A packet * @returns Whether this packet is a client packet */ -export function isClientPacket( - packet: Packet, -): packet is Packet { +export function isClientPacket(packet: Packet): packet is Packet { return packet.op in ClientOperation } @@ -34,8 +25,6 @@ export function isClientPacket( * @param packet A packet * @returns Whether this packet is a server packet */ -export function isServerPacket( - packet: Packet, -): packet is Packet { +export function isServerPacket(packet: Packet): packet is Packet { return packet.op in ServerOperation } diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index 47ea1c6..9d276ca 100755 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './guard.js' +export * from './logger.js' export * from './serialization.js' export * from './string.js' diff --git a/packages/shared/src/utils/logger.ts b/packages/shared/src/utils/logger.ts new file mode 100644 index 0000000..085de1c --- /dev/null +++ b/packages/shared/src/utils/logger.ts @@ -0,0 +1,66 @@ +import { createLogger as createWinstonLogger, LoggerOptions, transports, format } from 'winston' +import { Chalk, ChalkInstance } from 'chalk' + +const chalk = new Chalk() + +const LevelPrefixes = { + error: `${chalk.bgRed.whiteBright(' ERR! ')} `, + warn: `${chalk.bgYellow.black(' WARN ')} `, + info: `${chalk.bgBlue.whiteBright(' INFO ')} `, + log: chalk.reset(''), + debug: chalk.gray('DEBUG: '), + silly: chalk.gray('SILLY: '), +} as Record + +const LevelColorFunctions = { + error: chalk.redBright, + warn: chalk.yellowBright, + info: chalk.cyanBright, + log: chalk.reset, + debug: chalk.gray, + silly: chalk.gray, +} as Record + +export function createLogger( + serviceName: string, + config: SafeOmit< + LoggerOptions, + | 'defaultMeta' + | 'exceptionHandlers' + | 'exitOnError' + | 'handleExceptions' + | 'handleRejections' + | 'levels' + | 'rejectionHandlers' + >, +) { + const logger = createWinstonLogger({ + exitOnError: false, + defaultMeta: { serviceName }, + handleExceptions: true, + handleRejections: true, + transports: config.transports ?? [ + new transports.Console(), + new transports.File({ + dirname: 'logs', + filename: `${serviceName}-${Date.now()}.log`, + format: format.combine( + format.uncolorize(), + format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + format.printf( + ({ level, message, timestamp }) => `[${timestamp}] ${level.toUpperCase()}: ${message}`, + ), + ), + }), + ], + format: format.printf(({ level, message }) => LevelPrefixes[level] + LevelColorFunctions[level]!(message)), + ...config, + }) + + logger.silly(`Logger for ${serviceName} created at ${Date.now()}`) + + return logger +} + +type SafeOmit = Omit +export type Logger = ReturnType From 756346ef0c4b78dd393083bbc55f6696af000687 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 29 Nov 2023 00:55:43 +0700 Subject: [PATCH 019/312] chore: change biome config --- biome.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/biome.json b/biome.json index c175a5e..55c5157 100644 --- a/biome.json +++ b/biome.json @@ -15,6 +15,9 @@ "style": { "noNonNullAssertion": { "level": "off" + }, + "useEnumInitializers": { + "level": "off" } } } @@ -32,6 +35,7 @@ "arrowParentheses": "asNeeded", "indentStyle": "space", "indentWidth": 4, + "lineWidth": 120, "quoteProperties": "asNeeded", "quoteStyle": "single", "semicolons": "asNeeded", From defc92d2562eb290ca58a52c3cc5446fdf9bee97 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 29 Nov 2023 00:56:03 +0700 Subject: [PATCH 020/312] chore: apply biome code fixes --- packages/api/src/classes/Client.ts | 18 +++------- packages/api/src/classes/ClientGateway.ts | 41 ++++++----------------- 2 files changed, 14 insertions(+), 45 deletions(-) diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index 5558a41..245fb91 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -62,9 +62,7 @@ export default class Client { rs(packet.d) } - const parseTextFailedListener = ( - packet: Packet, - ) => { + const parseTextFailedListener = (packet: Packet) => { if (packet.d.id !== currentId) return this.gateway.off('parseTextFailed', parseTextFailedListener) rj() @@ -104,9 +102,7 @@ export default class Client { rs(packet.d) } - const parseImageFailedListener = ( - packet: Packet, - ) => { + const parseImageFailedListener = (packet: Packet) => { if (packet.d.id !== currentId) return this.gateway.off('parseImageFailed', parseImageFailedListener) rj() @@ -125,10 +121,7 @@ export default class Client { * @param handler The event handler * @returns The event handler function */ - on( - name: TOpName, - handler: ClientGatewayEventHandlers[TOpName], - ) { + on(name: TOpName, handler: ClientGatewayEventHandlers[TOpName]) { this.gateway.on(name, handler) return handler } @@ -139,10 +132,7 @@ export default class Client { * @param handler The event handler to remove * @returns The removed event handler function */ - off( - name: TOpName, - handler: ClientGatewayEventHandlers[TOpName], - ) { + off(name: TOpName, handler: ClientGatewayEventHandlers[TOpName]) { this.gateway.off(name, handler) return handler } diff --git a/packages/api/src/classes/ClientGateway.ts b/packages/api/src/classes/ClientGateway.ts index 5ca560f..8d01271 100755 --- a/packages/api/src/classes/ClientGateway.ts +++ b/packages/api/src/classes/ClientGateway.ts @@ -44,9 +44,7 @@ export default class ClientGateway { rs() }) - this.#socket.on('close', () => - this.#handleDisconnect(DisconnectReason.Generic), - ) + this.#socket.on('close', () => this.#handleDisconnect(DisconnectReason.Generic)) this.#listen() this.ready = true @@ -102,14 +100,10 @@ export default class ClientGateway { * @returns A promise that resolves when the packet has been sent */ send(packet: Packet) { - this.#throwIfDisconnected( - 'Cannot send a packet when already disconnected from the server', - ) + this.#throwIfDisconnected('Cannot send a packet when already disconnected from the server') return new Promise((resolve, reject) => - this.#socket.send(serializePacket(packet), err => - err ? reject(err) : resolve(), - ), + this.#socket.send(serializePacket(packet), err => (err ? reject(err) : resolve())), ) } @@ -117,9 +111,7 @@ export default class ClientGateway { * Disconnects from the WebSocket API */ disconnect() { - this.#throwIfDisconnected( - 'Cannot disconnect when already disconnected from the server', - ) + this.#throwIfDisconnected('Cannot disconnect when already disconnected from the server') this.#handleDisconnect(DisconnectReason.Generic) } @@ -144,25 +136,17 @@ export default class ClientGateway { switch (packet.op) { case ServerOperation.Hello: { // eslint-disable-next-line no-case-declarations - const data = Object.freeze( - (packet as Packet).d, - ) + const data = Object.freeze((packet as Packet).d) this.config = data this.#emitter.emit('hello', data) this.#startHeartbeating() break } case ServerOperation.Disconnect: - return this.#handleDisconnect( - (packet as Packet).d.reason, - ) + return this.#handleDisconnect((packet as Packet).d.reason) default: return this.#emitter.emit( - uncapitalize( - ServerOperation[ - packet.op - ] as ClientGatewayServerEventName, - ), + uncapitalize(ServerOperation[packet.op] as ClientGatewayServerEventName), // @ts-expect-error TypeScript doesn't know that the lines above negate the type enough packet, ) @@ -172,8 +156,7 @@ export default class ClientGateway { #throwIfDisconnected(errorMessage: string) { if (this.disconnected !== false) throw new Error(errorMessage) - if (this.#socket.readyState !== this.#socket.OPEN) - throw new Error(errorMessage) + if (this.#socket.readyState !== this.#socket.OPEN) throw new Error(errorMessage) } #handleDisconnect(reason: DisconnectReason) { @@ -222,14 +205,10 @@ export type ClientGatewayEventHandlers = { packet: Packet]>, ) => Promise | void } & { - hello: ( - config: NonNullable, - ) => Promise | void + hello: (config: NonNullable) => Promise | void ready: () => Promise | void packet: (packet: Packet) => Promise | void disconnect: (reason: DisconnectReason) => Promise | void } -export type ReadiedClientGateway = RequiredProperty< - InstanceType -> +export type ReadiedClientGateway = RequiredProperty> From faa15879c9b8874a9e22b2f60564f75ae3537e49 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 29 Nov 2023 22:40:53 +0700 Subject: [PATCH 021/312] chore: remove eslint dependencies --- bun.lockb | Bin 340420 -> 274116 bytes package.json | 4 ---- 2 files changed, 4 deletions(-) diff --git a/bun.lockb b/bun.lockb index d3eac3eb57244a5e7255a9e836a936c8c1c4a1bd..9f26c64d1c5ba193b2883874effa159d7151e6b5 100755 GIT binary patch delta 47064 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 YX|FDe2$vzmBojkc@Evy3^jMAn0JtNrdH?_b diff --git a/package.json b/package.json index 718ff0c..1b07c73 100755 --- a/package.json +++ b/package.json @@ -12,10 +12,6 @@ "@commitlint/config-conventional": "^18.4.3", "@tsconfig/strictest": "^2.0.2", "conventional-changelog-conventionalcommits": "^7.0.2", - "eslint-config-prettier": "^9.0.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.0", - "eslint-plugin-prettier": "^5.0.1", "lefthook": "^1.5.3", "semantic-release": "^22.0.8", "turbo": "^1.10.16", From e9638a771378c7969cf14279876026bfea4b2f78 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 29 Nov 2023 22:41:38 +0700 Subject: [PATCH 022/312] feat(packages/shared)!: switch to less resource-intensive logger --- apis/websocket/config.schema.json | 2 +- apis/websocket/docs/1_configuration.md | 13 +++- apis/websocket/src/index.ts | 10 +-- packages/shared/package.json | 3 +- packages/shared/src/utils/logger.ts | 95 ++++++++++---------------- 5 files changed, 55 insertions(+), 68 deletions(-) diff --git a/apis/websocket/config.schema.json b/apis/websocket/config.schema.json index 42e1214..be812b1 100755 --- a/apis/websocket/config.schema.json +++ b/apis/websocket/config.schema.json @@ -25,7 +25,7 @@ "consoleLogLevel": { "description": "The log level to print to console", "type": "string", - "enum": ["error", "warn", "info", "verbose", "debug", "silly", "none"], + "enum": ["error", "warn", "info", "log", "debug", "none"], "default": "info" } } diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md index 5f2454b..1cc2d5e 100644 --- a/apis/websocket/docs/1_configuration.md +++ b/apis/websocket/docs/1_configuration.md @@ -28,9 +28,18 @@ Amount of concurrent queues that can be run at a time. Heartbeat interval for clients. See [**💓 Heartbeating**](./packets.md#💓-heartbeating). -### `config.debugLogsInProduction` +### `config.consoleLogLevel` -Whether to print debug logs at all in production mode (when `NODE_ENV` is `production`). +The level of logs to print to console. If the level is more important or equally important to set level, it will be forwarded to the console. + +The possible levels (sorted by their importance descendingly) are: +- `fatal` +- `error` +- `warn` +- `info` +- `log` +- `trace` +- `debug` ## ⏭️ What's next diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index d04bdfc..d5e287a 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -18,9 +18,8 @@ import { checkEnvironment, getConfig } from './utils/index.js' // Load config, init logger, check environment const config = getConfig() -const logger = createLogger('websocket-api', { - level: config.consoleLogLevel === 'none' ? 'error' : config.consoleLogLevel, - silent: config.consoleLogLevel === 'none', +const logger = createLogger({ + level: config.consoleLogLevel === 'none' ? Infinity : config.consoleLogLevel, }) checkEnvironment(logger) @@ -32,7 +31,8 @@ const witClient = new Wit({ accessToken: process.env['WIT_AI_TOKEN']!, }) -process.on('beforeExit', () => tesseractWorker.terminate()) +logger.fatal('test') +logger.error('test') // Server logic @@ -112,7 +112,7 @@ const server = fastify() // Start the server -logger.debug(`Starting with these configurations: ${inspectObject(config)}`, ) +logger.debug(`Starting with these configurations: ${inspectObject(config)}`) await server.listen({ host: config.address ?? '0.0.0.0', diff --git a/packages/shared/package.json b/packages/shared/package.json index 19746ba..c903ac0 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -32,8 +32,9 @@ "dependencies": { "bson": "^6.2.0", "chalk": "^5.3.0", + "supports-color": "^9.4.0", + "tracer": "^1.3.0", "valibot": "^0.21.0", - "winston": "^3.11.0", "zod": "^3.22.4" } } diff --git a/packages/shared/src/utils/logger.ts b/packages/shared/src/utils/logger.ts index 085de1c..ad065a8 100644 --- a/packages/shared/src/utils/logger.ts +++ b/packages/shared/src/utils/logger.ts @@ -1,66 +1,43 @@ -import { createLogger as createWinstonLogger, LoggerOptions, transports, format } from 'winston' -import { Chalk, ChalkInstance } from 'chalk' +import { colorConsole, console as uncoloredConsole, Tracer } from 'tracer' +import { Chalk, supportsColor, supportsColorStderr } from 'chalk' const chalk = new Chalk() +const DefaultConfig = { + dateformat: 'DD/MM/YYYY HH:mm:ss.sss Z', + format: [ + '{{message}}', + { + error: `${chalk.bgRedBright.whiteBright(' ERROR ')} {{message}}\n${chalk.gray( + '{{stack}}', + )}`, + debug: chalk.gray('DEBUG: {{message}}\n{{stack}}'), + warn: `${chalk.bgYellowBright.whiteBright(' WARN ')} ${chalk.yellowBright('{{message}}')}\n${chalk.gray( + '{{stack}}', + )}`, + info: `${chalk.bgBlueBright.whiteBright(' INFO ')} ${chalk.cyanBright('{{message}}')}`, + fatal: `${chalk.bgRedBright.whiteBright(' FATAL ')} ${chalk.redBright('{{message}}')}\n${chalk.white( + '{{stack}}', + )}`, + log: '{{message}}', + trace: chalk.gray('[{{timestamp}}] TRACE: {{message}}\n{{stack}}'), + }, + ], + methods: ['debug', 'trace', 'log', 'info', 'warn', 'error', 'fatal'], + filters: [], +} satisfies Tracer.LoggerConfig -const LevelPrefixes = { - error: `${chalk.bgRed.whiteBright(' ERR! ')} `, - warn: `${chalk.bgYellow.black(' WARN ')} `, - info: `${chalk.bgBlue.whiteBright(' INFO ')} `, - log: chalk.reset(''), - debug: chalk.gray('DEBUG: '), - silly: chalk.gray('SILLY: '), -} as Record +export function createLogger(config: Omit) { + const combinedConfig = { ...DefaultConfig, ...config } -const LevelColorFunctions = { - error: chalk.redBright, - warn: chalk.yellowBright, - info: chalk.cyanBright, - log: chalk.reset, - debug: chalk.gray, - silly: chalk.gray, -} as Record - -export function createLogger( - serviceName: string, - config: SafeOmit< - LoggerOptions, - | 'defaultMeta' - | 'exceptionHandlers' - | 'exitOnError' - | 'handleExceptions' - | 'handleRejections' - | 'levels' - | 'rejectionHandlers' - >, -) { - const logger = createWinstonLogger({ - exitOnError: false, - defaultMeta: { serviceName }, - handleExceptions: true, - handleRejections: true, - transports: config.transports ?? [ - new transports.Console(), - new transports.File({ - dirname: 'logs', - filename: `${serviceName}-${Date.now()}.log`, - format: format.combine( - format.uncolorize(), - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf( - ({ level, message, timestamp }) => `[${timestamp}] ${level.toUpperCase()}: ${message}`, - ), - ), - }), - ], - format: format.printf(({ level, message }) => LevelPrefixes[level] + LevelColorFunctions[level]!(message)), - ...config, - }) - - logger.silly(`Logger for ${serviceName} created at ${Date.now()}`) - - return logger + if ( + // biome-ignore lint/complexity/useOptionalChain: No Biome, this isn't a nullable check + supportsColor && + supportsColor.hasBasic && + supportsColorStderr && + supportsColorStderr.hasBasic + ) + return colorConsole(combinedConfig) + else return uncoloredConsole(combinedConfig) } -type SafeOmit = Omit export type Logger = ReturnType From 05176fbf266c257da1bc589fe77c4ed1399239d1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:39:05 +0700 Subject: [PATCH 023/312] docs(apis/websocket): update docs --- apis/websocket/README.md | 146 +++++++++--------- .../docs/0_development_environment.md | 78 +++++----- apis/websocket/docs/1_configuration.md | 97 ++++++------ apis/websocket/docs/2_running.md | 82 +++++----- apis/websocket/docs/3_packets.md | 66 ++++---- apis/websocket/docs/README.md | 32 ++-- 6 files changed, 251 insertions(+), 250 deletions(-) diff --git a/apis/websocket/README.md b/apis/websocket/README.md index b04c21a..36d6ab6 100755 --- a/apis/websocket/README.md +++ b/apis/websocket/README.md @@ -1,73 +1,73 @@ -

- - - - -
-
- - - - -     - - - - - -     - - - - - -     - - - - - -     - - - - - -     - - - - - -     - - - - - - -
-
- Continuing the legacy of Vanced -

- -# 🚙 ReVanced Bot WebSocket API - -![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) - -The WebSocket API for ReVanced bots utilizing BSON for packet transmission with a heartbeating system to monitor the statuses of clients. - -## 📚 Documentation - -Documentation are provided [here](./docs/README.md). - -## 📄 License - -**ReVanced Bot WebSocket API** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. +

+ + + + +
+ + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + + +
+
+ Continuing the legacy of Vanced +

+ +# 🚙 ReVanced Bot WebSocket API + +![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) + +The WebSocket API for ReVanced bots utilizing BSON for packet transmission with a heartbeating system to monitor the statuses of clients. + +## 📚 Documentation + +Documentation are provided [here](./docs/README.md). + +## 📄 License + +**ReVanced Bot WebSocket API** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. diff --git a/apis/websocket/docs/0_development_environment.md b/apis/websocket/docs/0_development_environment.md index 5443dd8..f4cb460 100644 --- a/apis/websocket/docs/0_development_environment.md +++ b/apis/websocket/docs/0_development_environment.md @@ -1,39 +1,39 @@ -# 🏗️ Setting up the development environment - -> [!IMPORTANT] -> **This project uses [Bun](https://bun.sh) to run and bundle the code.** -> Compatibility with other runtimes (Node.js, Deno, ...) are not guaranteed and most package scripts won't work. - -To start developing, you'll need to set up the development environment first. - -1. Install [Bun](https://bun.sh) - -2. Clone the mono-repository - - ```sh - git clone https://github.com/ReVanced/revanced-helper.git && - cd revanced-helper - ``` - -3. Install dependencies - - ```sh - bun install - ``` - -4. Build packages/libraries - - ```sh - bun build:libs - ``` - -5. Change your directory to this project's root - ```sh - cd apis/websocket - ``` - -## ⏭️ What's next - -The next page will tell you about server configurations. - -Continue: [⚙️ Configuration](./1_configuration.md) +# 🏗️ Setting up the development environment + +> [!IMPORTANT] +> **This project uses [Bun](https://bun.sh) to run and bundle the code.** +> Compatibility with other runtimes (Node.js, Deno, ...) are not guaranteed and most package scripts won't work. + +To start developing, you'll need to set up the development environment first. + +1. Install [Bun](https://bun.sh) + +2. Clone the mono-repository + + ```sh + git clone https://github.com/ReVanced/revanced-helper.git && + cd revanced-helper + ``` + +3. Install dependencies + + ```sh + bun install + ``` + +4. Build packages/libraries + + ```sh + bun build:libs + ``` + +5. Change your directory to this project's root + ```sh + cd apis/websocket + ``` + +## ⏭️ What's next + +The next page will tell you about server configurations. + +Continue: [⚙️ Configuration](./1_configuration.md) diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md index 1cc2d5e..76a1736 100644 --- a/apis/websocket/docs/1_configuration.md +++ b/apis/websocket/docs/1_configuration.md @@ -1,48 +1,49 @@ -# ⚙️ Configuration - -This is the default configuration: - -```json -{ - "address": "127.0.0.1", - "port": 3000, - "ocrConcurrentQueues": 1, - "clientHeartbeatInterval": 60000, - "debugLogsInProduction": false -} -``` - ---- - -### `config.address` & `config.port` - -The address and port for the server to listen on. - -### `config.ocrConcurrentQueues` - -Amount of concurrent queues that can be run at a time. - -> Setting this too high may cause performance issues. - -### `config.clientHeartbeatInterval` - -Heartbeat interval for clients. See [**💓 Heartbeating**](./packets.md#💓-heartbeating). - -### `config.consoleLogLevel` - -The level of logs to print to console. If the level is more important or equally important to set level, it will be forwarded to the console. - -The possible levels (sorted by their importance descendingly) are: -- `fatal` -- `error` -- `warn` -- `info` -- `log` -- `trace` -- `debug` - -## ⏭️ What's next - -The next page will tell you how to run and bundle the server. - -Continue: [🏃🏻‍♂️ Running the server](./2_running.md) +# ⚙️ Configuration + +This is the default configuration: + +```json +{ + "address": "127.0.0.1", + "port": 3000, + "ocrConcurrentQueues": 1, + "clientHeartbeatInterval": 60000, + "debugLogsInProduction": false +} +``` + +--- + +### `config.address` & `config.port` + +The address and port for the server to listen on. + +### `config.ocrConcurrentQueues` + +Amount of concurrent queues that can be run at a time. + +> Setting this too high may cause performance issues. + +### `config.clientHeartbeatInterval` + +Heartbeat interval for clients. See [**💓 Heartbeating**](./packets.md#💓-heartbeating). + +### `config.consoleLogLevel` + +The level of logs to print to console. If the level is more important or equally important to set level, it will be forwarded to the console. + +The possible levels (sorted by their importance descendingly) are: + +- `fatal` +- `error` +- `warn` +- `info` +- `log` +- `trace` +- `debug` + +## ⏭️ What's next + +The next page will tell you how to run and bundle the server. + +Continue: [🏃🏻‍♂️ Running the server](./2_running.md) diff --git a/apis/websocket/docs/2_running.md b/apis/websocket/docs/2_running.md index 3ffd782..0487200 100644 --- a/apis/websocket/docs/2_running.md +++ b/apis/websocket/docs/2_running.md @@ -1,41 +1,41 @@ -# 🏃🏻‍♂️ Running the server - -There are many methods to run the server. Choose one that suits best for the situation. - -> [!IMPORTANT] -> Make sure you've followed the [**🏗️ Setting up the environment**](./0_development_environment.md) steps. - -## 👷🏻 Development mode (recommended) - -There will be no compilation step, and Bun will automatically watch changes and restart the server for you. - -You can quickly start the server by running: - -```sh -bun dev -``` - -## 🌐 Production mode - -Production mode runs no different from the development server, it simply has less debugging information printed to console by default. However, more production-specific features may come. - -To start the server in production mode, you'll have to: - -1. Set the `NODE_ENV` environment variable to `production` - - > It is very possible to set the value in the `.env` file and let Bun load it, **but it is recommended to set the variable before Bun even starts**. - -2. Start the server - ```sh - bun dev - ``` - -## 📦 Building - -If you're looking to build and host the server somewhere else, you can run: - -```sh -bun bundle -``` - -The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** +# 🏃🏻‍♂️ Running the server + +There are many methods to run the server. Choose one that suits best for the situation. + +> [!IMPORTANT] +> Make sure you've followed the [**🏗️ Setting up the environment**](./0_development_environment.md) steps. + +## 👷🏻 Development mode (recommended) + +There will be no compilation step, and Bun will automatically watch changes and restart the server for you. + +You can quickly start the server by running: + +```sh +bun dev +``` + +## 🌐 Production mode + +Production mode runs no different from the development server, it simply has less debugging information printed to console by default. However, more production-specific features may come. + +To start the server in production mode, you'll have to: + +1. Set the `NODE_ENV` environment variable to `production` + + > It is very possible to set the value in the `.env` file and let Bun load it, **but it is recommended to set the variable before Bun even starts**. + +2. Start the server + ```sh + bun dev + ``` + +## 📦 Building + +If you're looking to build and host the server somewhere else, you can run: + +```sh +bun bundle +``` + +The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** diff --git a/apis/websocket/docs/3_packets.md b/apis/websocket/docs/3_packets.md index 96d5c7f..fe2ea75 100644 --- a/apis/websocket/docs/3_packets.md +++ b/apis/websocket/docs/3_packets.md @@ -1,33 +1,33 @@ -# 📨 Packets - -Packets are BSON messages sent to the server. They're structured like the following when decoded: - -```json -{ - "op": 12345, - "d": { - "some_field": "some data" - } -} -``` - -### `packet.op` - -Operation codes are numbers that communicate an action. - -### `packet.d` - -Data fields include additional information for the server to process. They are **either an object with specific fields or just `null`**. - -#### 📦 Schemas and constants - -Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well. - -[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../packages/shared/src/schemas/Packet.ts) -[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../packages/shared/src/constants/Operation.ts) - -## 💓 Heartbeating - -Heartbeating is a process where the client regularly send each other signals to confirm that they are still connected and functioning. If the server doesn't receive a heartbeat from the client within a specified timeframe, it assume the client has disconnected and closes the socket. - -You can configure the interval in the configuration file. See [**📝 Configuration > `config.clientHeartbeatInterval`**](./1_configuration.md#configclientheartbeatinterval). +# 📨 Packets + +Packets are BSON messages sent to the server. They're structured like the following when decoded: + +```json +{ + "op": 12345, + "d": { + "some_field": "some data" + } +} +``` + +### `packet.op` + +Operation codes are numbers that communicate an action. + +### `packet.d` + +Data fields include additional information for the server to process. They are **either an object with specific fields or just `null`**. + +#### 📦 Schemas and constants + +Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well. + +[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../packages/shared/src/schemas/Packet.ts) +[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../packages/shared/src/constants/Operation.ts) + +## 💓 Heartbeating + +Heartbeating is a process where the client regularly send each other signals to confirm that they are still connected and functioning. If the server doesn't receive a heartbeat from the client within a specified timeframe, it assume the client has disconnected and closes the socket. + +You can configure the interval in the configuration file. See [**📝 Configuration > `config.clientHeartbeatInterval`**](./1_configuration.md#configclientheartbeatinterval). diff --git a/apis/websocket/docs/README.md b/apis/websocket/docs/README.md index ae7573b..f14a3bb 100755 --- a/apis/websocket/docs/README.md +++ b/apis/websocket/docs/README.md @@ -1,16 +1,16 @@ -# 🚙 ReVanced Bot WebSocket API - -This documentation explains how the server works, how to start developing, and how to configure the server. - -## 📖 Table of contents - -0. [🏗️ Setting up the development environment](./0_development_environment.md) -1. [⚙️ Configuration](./1_configuration.md) -2. [🏃🏻‍♂️ Running the server](./2_running.md) -3. [📨 Packets](./3_packets.md) - -## ⏭️ Start here - -The next page will tell you how to set up the development environment. - -Continue: [🏗️ Setting up the development environment](./0_development_environment.md) +# 🚙 ReVanced Bot WebSocket API + +This documentation explains how the server works, how to start developing, and how to configure the server. + +## 📖 Table of contents + +0. [🏗️ Setting up the development environment](./0_development_environment.md) +1. [⚙️ Configuration](./1_configuration.md) +2. [🏃🏻‍♂️ Running the server](./2_running.md) +3. [📨 Packets](./3_packets.md) + +## ⏭️ Start here + +The next page will tell you how to set up the development environment. + +Continue: [🏗️ Setting up the development environment](./0_development_environment.md) From fd7388ac3dbf179a26e762deeae76c3291f0c5bc Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:39:20 +0700 Subject: [PATCH 024/312] chore(apis/websocket): bump dependencies --- apis/websocket/package.json | 86 ++++++++++++++++++------------------ apis/websocket/tsconfig.json | 22 ++++----- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index e95c042..2be8673 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -1,43 +1,43 @@ -{ - "name": "@revanced/bot-websocket-api", - "type": "module", - "private": true, - "version": "0.1.0", - "description": "🧦 WebSocket API server for bots assisting ReVanced", - "main": "dist/index.js", - "scripts": { - "bundle": "bun build src/index.ts --outdir=dist --target=node --minify --sourcemap=external", - "dev": "bun run src/index.ts --watch", - "build": "bun bundle", - "watch": "bun dev" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "apis/websocket" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "@fastify/websocket": "^8.2.0", - "@revanced/bot-shared": "workspace:*", - "@sapphire/async-queue": "^1.5.0", - "chalk": "^5.3.0", - "fastify": "^4.24.3", - "node-wit": "^6.6.0", - "tesseract.js": "^5.0.3" - }, - "devDependencies": { - "@types/node-wit": "^6.0.3", - "@types/ws": "^8.5.10", - "typed-emitter": "^2.1.0" - } -} +{ + "name": "@revanced/bot-websocket-api", + "type": "module", + "private": true, + "version": "0.1.0", + "description": "🧦 WebSocket API server for bots assisting ReVanced", + "main": "dist/index.js", + "scripts": { + "bundle": "bun build src/index.ts --outdir=dist --target=node --minify --sourcemap=external", + "dev": "bun run src/index.ts --watch", + "build": "bun bundle", + "watch": "bun dev" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "apis/websocket" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@fastify/websocket": "^8.3.1", + "@revanced/bot-shared": "workspace:*", + "@sapphire/async-queue": "^1.5.1", + "chalk": "^5.3.0", + "fastify": "^4.25.2", + "node-wit": "^6.6.0", + "tesseract.js": "^5.0.4" + }, + "devDependencies": { + "@types/node-wit": "^6.0.3", + "@types/ws": "^8.5.10", + "typed-emitter": "^2.1.0" + } +} diff --git a/apis/websocket/tsconfig.json b/apis/websocket/tsconfig.json index fd79f29..a4fa4d4 100755 --- a/apis/websocket/tsconfig.json +++ b/apis/websocket/tsconfig.json @@ -1,11 +1,11 @@ -{ - "extends": "../../tsconfig.apis.json", - "compilerOptions": { - "baseUrl": ".", - "outDir": "dist", - "module": "ESNext", - "composite": false - }, - "exclude": ["node_modules", "dist"], - "include": ["./*.json", "src/**/*.ts"] -} +{ + "extends": "../../tsconfig.apis.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "module": "ESNext", + "composite": false + }, + "exclude": ["node_modules", "dist"], + "include": ["./*.json", "src/**/*.ts"] +} From 3e84821c239bfe97a40f0ef7fa74612d1e7470d4 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:42:58 +0700 Subject: [PATCH 025/312] chore(apis/websocket): update config schema --- apis/websocket/{config.json => config.example.json} | 2 +- apis/websocket/config.revanced.json | 9 +++++++++ apis/websocket/config.schema.json | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) rename apis/websocket/{config.json => config.example.json} (83%) create mode 100755 apis/websocket/config.revanced.json diff --git a/apis/websocket/config.json b/apis/websocket/config.example.json similarity index 83% rename from apis/websocket/config.json rename to apis/websocket/config.example.json index 0217e3a..9a07cd7 100755 --- a/apis/websocket/config.json +++ b/apis/websocket/config.example.json @@ -5,5 +5,5 @@ "port": 3000, "ocrConcurrentQueues": 1, "clientHeartbeatInterval": 5000, - "consoleLogLevel": "silly" + "consoleLogLevel": "log" } diff --git a/apis/websocket/config.revanced.json b/apis/websocket/config.revanced.json new file mode 100755 index 0000000..ef47645 --- /dev/null +++ b/apis/websocket/config.revanced.json @@ -0,0 +1,9 @@ +{ + "$schema": "./config.schema.json", + + "address": "127.0.0.1", + "port": 3000, + "ocrConcurrentQueues": 3, + "clientHeartbeatInterval": 5000, + "consoleLogLevel": "log" +} diff --git a/apis/websocket/config.schema.json b/apis/websocket/config.schema.json index be812b1..51965c1 100755 --- a/apis/websocket/config.schema.json +++ b/apis/websocket/config.schema.json @@ -25,7 +25,7 @@ "consoleLogLevel": { "description": "The log level to print to console", "type": "string", - "enum": ["error", "warn", "info", "log", "debug", "none"], + "enum": ["error", "warn", "info", "log", "debug", "trace", "none"], "default": "info" } } From a90e59edb8698868bf6d52a33dd0208e70466c48 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:46:25 +0700 Subject: [PATCH 026/312] refactor(apis/websocket): clean up imports, format, and fix issues --- apis/websocket/src/classes/Client.ts | 8 ++++---- apis/websocket/src/events/index.ts | 10 +++++----- apis/websocket/src/events/parseImage.ts | 2 +- apis/websocket/src/events/parseText.ts | 5 ++--- apis/websocket/src/index.ts | 11 ++++------- apis/websocket/src/utils/getConfig.ts | 14 +++++++------- apis/websocket/src/utils/index.ts | 4 ++-- 7 files changed, 25 insertions(+), 29 deletions(-) diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index 6de63d0..40037c3 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from 'node:events' +import { EventEmitter } from 'events' import { ClientOperation, DisconnectReason, @@ -167,8 +167,8 @@ export default class Client { protected _toBuffer(data: RawData) { if (data instanceof Buffer) return data - else if (data instanceof ArrayBuffer) return Buffer.from(data) - else return Buffer.concat(data) + if (data instanceof ArrayBuffer) return Buffer.from(data) + return Buffer.concat(data) } } @@ -186,7 +186,7 @@ export type ClientEventName = keyof typeof ClientOperation export type ClientEventHandlers = { [K in Uncapitalize]: ( - packet: ClientPacketObject]>, + packet: ClientPacketObject<(typeof ClientOperation)[Capitalize]>, ) => Promise | unknown } & { ready: () => Promise | unknown diff --git a/apis/websocket/src/events/index.ts b/apis/websocket/src/events/index.ts index 9f1ed4f..6e81e88 100755 --- a/apis/websocket/src/events/index.ts +++ b/apis/websocket/src/events/index.ts @@ -1,12 +1,12 @@ import type { ClientOperation } from '@revanced/bot-shared' +import type { Logger } from '@revanced/bot-shared' import type { Wit } from 'node-wit' import type { Worker as TesseractWorker } from 'tesseract.js' -import { ClientPacketObject } from '../classes/Client.js' -import type { Config } from '../utils/getConfig.js' -import type { Logger } from '@revanced/bot-shared' +import { ClientPacketObject } from '../classes/Client' +import type { Config } from '../utils/getConfig' -export { default as parseTextEventHandler } from './parseText.js' -export { default as parseImageEventHandler } from './parseImage.js' +export { default as parseTextEventHandler } from './parseText' +export { default as parseImageEventHandler } from './parseImage' export type EventHandler = ( packet: ClientPacketObject, diff --git a/apis/websocket/src/events/parseImage.ts b/apis/websocket/src/events/parseImage.ts index 61fa14a..b48bf09 100755 --- a/apis/websocket/src/events/parseImage.ts +++ b/apis/websocket/src/events/parseImage.ts @@ -1,7 +1,7 @@ import { ClientOperation, ServerOperation } from '@revanced/bot-shared' import { AsyncQueue } from '@sapphire/async-queue' -import type { EventHandler } from './index.js' +import type { EventHandler } from './index' const queue = new AsyncQueue() diff --git a/apis/websocket/src/events/parseText.ts b/apis/websocket/src/events/parseText.ts index 0d9246f..690dbb5 100755 --- a/apis/websocket/src/events/parseText.ts +++ b/apis/websocket/src/events/parseText.ts @@ -1,8 +1,8 @@ import { ClientOperation, ServerOperation } from '@revanced/bot-shared' -import { inspect as inspectObject } from 'node:util' +import { inspect as inspectObject } from 'util' -import type { EventHandler } from './index.js' +import type { EventHandler } from './index' const parseTextEventHandler: EventHandler = async (packet, { witClient, logger }) => { const { @@ -14,7 +14,6 @@ const parseTextEventHandler: EventHandler = async (pa try { const { intents } = await witClient.message(text, {}) - // eslint-disable-next-line @typescript-eslint/no-unused-vars const intentsWithoutIds = intents.map(({ id, ...rest }) => rest) await client.send({ diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index d5e287a..1145d64 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -5,15 +5,15 @@ import witPkg from 'node-wit' import { createWorker as createTesseractWorker } from 'tesseract.js' const { Wit } = witPkg -import { inspect as inspectObject } from 'node:util' +import { inspect as inspectObject } from 'util' -import Client from './classes/Client.js' +import Client from './classes/Client' -import { EventContext, parseImageEventHandler, parseTextEventHandler } from './events/index.js' +import { EventContext, parseImageEventHandler, parseTextEventHandler } from './events/index' import { DisconnectReason, HumanizedDisconnectReason, createLogger } from '@revanced/bot-shared' import { WebSocket } from 'ws' -import { checkEnvironment, getConfig } from './utils/index.js' +import { checkEnvironment, getConfig } from './utils/index' // Load config, init logger, check environment @@ -31,9 +31,6 @@ const witClient = new Wit({ accessToken: process.env['WIT_AI_TOKEN']!, }) -logger.fatal('test') -logger.error('test') - // Server logic const clients = new Set() diff --git a/apis/websocket/src/utils/getConfig.ts b/apis/websocket/src/utils/getConfig.ts index 841c219..d26042e 100755 --- a/apis/websocket/src/utils/getConfig.ts +++ b/apis/websocket/src/utils/getConfig.ts @@ -1,6 +1,6 @@ -import { existsSync } from 'node:fs' -import { resolve as resolvePath } from 'node:path' -import { pathToFileURL } from 'node:url' +import { existsSync } from 'fs' +import { resolve as resolvePath } from 'path' +import { pathToFileURL } from 'url' const configPath = resolvePath(process.cwd(), 'config.json') @@ -17,10 +17,10 @@ const userConfig: Partial = existsSync(configPath) type BaseTypeOf = T extends (infer U)[] ? U[] : T extends (...args: unknown[]) => infer U - ? (...args: unknown[]) => U - : T extends object - ? { [K in keyof T]: T[K] } - : T + ? (...args: unknown[]) => U + : T extends object + ? { [K in keyof T]: T[K] } + : T export type Config = Omit, '$schema'> diff --git a/apis/websocket/src/utils/index.ts b/apis/websocket/src/utils/index.ts index 4aab21d..24a2c7c 100755 --- a/apis/websocket/src/utils/index.ts +++ b/apis/websocket/src/utils/index.ts @@ -1,2 +1,2 @@ -export { default as getConfig } from './getConfig.js' -export { default as checkEnvironment } from './checkEnvironment.js' +export { default as getConfig } from './getConfig' +export { default as checkEnvironment } from './checkEnvironment' From 26ad4afc18351ae2458ff2fc0ba8411409b2919a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:47:03 +0700 Subject: [PATCH 027/312] chore(apis/websocket): use `config.json` as example config --- apis/websocket/config.json | 9 +++++++++ apis/websocket/docs/1_configuration.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100755 apis/websocket/config.json diff --git a/apis/websocket/config.json b/apis/websocket/config.json new file mode 100755 index 0000000..9a07cd7 --- /dev/null +++ b/apis/websocket/config.json @@ -0,0 +1,9 @@ +{ + "$schema": "./config.schema.json", + + "address": "127.0.0.1", + "port": 3000, + "ocrConcurrentQueues": 1, + "clientHeartbeatInterval": 5000, + "consoleLogLevel": "log" +} diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md index 76a1736..77e8b11 100644 --- a/apis/websocket/docs/1_configuration.md +++ b/apis/websocket/docs/1_configuration.md @@ -1,6 +1,6 @@ # ⚙️ Configuration -This is the default configuration: +This is the default configuration (provided in [config.json](../config.json)): ```json { From 7b00b5c2b89b56ffc384425e5192c9c66280367f Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:48:02 +0700 Subject: [PATCH 028/312] chore: use biome instead of prettier and eslint in hooks --- biome.json | 12 +++++------- lefthook.yml | 28 ++++++++++++---------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/biome.json b/biome.json index 55c5157..8d77a63 100644 --- a/biome.json +++ b/biome.json @@ -27,6 +27,9 @@ "enabled": true, "indentStyle": "space", "indentWidth": 2 + }, + "parser": { + "allowComments": true } }, "javascript": { @@ -44,12 +47,7 @@ }, "files": { "ignoreUnknown": true, - "include": ["**/*.js", "**/*.json", "**/*.ts"], - "ignore": [ - "**/tsconfig.json", - "**/tsconfig.*.json", - "dist/**/*", - "node_modules/**/*" - ] + "include": ["*.js", "*.json", "*.ts"], + "ignore": ["dist/**/*", "node_modules/**/*"] } } diff --git a/lefthook.yml b/lefthook.yml index 6dfade6..8b6eb11 100755 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,16 +1,12 @@ -pre-commit: - parallel: true - commands: - format: - files: git diff --name-only --cached --diff-filter=AM @{push} - glob: '*.{js,ts,json,yml,md}' - run: bunx prettier --ignore-path .gitignore --check {files} - lint: - files: git diff --name-only --cached --diff-filter=AM @{push} - glob: '*.{js,ts}' - run: bunx eslint --cache {files} -commit-msg: - parallel: false - commands: - commitlint: - run: bunx commitlint --edit +pre-commit: + parallel: true + commands: + check: + files: git diff --name-only --cached --diff-filter=AM @{push} + glob: "*.{js,ts,json,yml,md}" + run: bunx biome check {files} +commit-msg: + parallel: false + commands: + commitlint: + run: bunx commitlint --edit From 9f36fbab084ff471eb7da51862922edeb93be2cc Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:48:24 +0700 Subject: [PATCH 029/312] chore: update global dependencies --- bun.lockb | Bin 274116 -> 277860 bytes package.json | 26 ++++++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/bun.lockb b/bun.lockb index 9f26c64d1c5ba193b2883874effa159d7151e6b5..e5e6d37600d78b892531cb394a6fe0b24d6da4c1 100755 GIT binary patch delta 63627 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
    2S=w<% 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

    {SWpqg2fb@~o>W+Nt@+0zf$ zFiXH`sMp{{gRC)wt{%kbd)rp?CkHKE0L`Ta3=B@wcY9zhFXEsD#=hzAJ(wjKAtAtM zJYCU~S(;I9x~(U(q_pl_NEv#wL+zhkbO$FWG$6%{H9sI#m$WwT;@Zn zvBrC^Q~fVn%79ctf;JQ?Gq3Mw~%elAO;Rezfq(={2{(felK@ z4BMyQ^4M(Ol8n!%YlA3=iOj|fkZC7KWrq}A zj?B!$)AxBZb0C87zBjXUeg0xdEwkp9nCZc9eiuP?31qQI>tY552?mCSi)Ndh_MiI{ zhme`O7}6)<|2%z~yxsW)2$?O585krP7#jAj@vxT(x0sKRISaMt=}+VAC(6uBP#Mss zkjGGq>~&XiWZjZ`50wJ##ajv~>vkpln0Kp?aT_SQAf@OXsLXTjKL*P8@10{}jDwCO zyk0tefgiJkIQnuMCdhJ|>A!uLWo3RWg*3btpPo@UKkHH^2VvKSwhlv8Ki{1U@8(4dFKne2&0`XD6JYzKX1is3Ys}) zvu18!db@0Thc&a$^a2}Z6Hu0U5XdZHdS?Z+rit2d(NDYuR9k^s8U_pu=neyqTbb%v zGBA9c{??CKl1X6Y^f|uF64Mp@nN4IAS3=C$DrC}o@aYjTa43M=k9U?%FYsrUU@}`d zJuZ-0LI$^uyH`%%=g%y`h-yC5xs}s-0+@Z^2H>_TV%7A%0A>j$>;}o;jwz_yn4mFc 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 Jud-oz1pvh_EbssT diff --git a/package.json b/package.json index 1b07c73..67f53fa 100755 --- a/package.json +++ b/package.json @@ -7,15 +7,17 @@ "url": "git+https://github.com/revanced/revanced-helper.git" }, "devDependencies": { - "@biomejs/biome": "1.3.3", - "@commitlint/cli": "^18.4.3", - "@commitlint/config-conventional": "^18.4.3", + "@biomejs/biome": "1.5.1", + "@commitlint/cli": "^18.4.4", + "@commitlint/config-conventional": "^18.4.4", "@tsconfig/strictest": "^2.0.2", "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.5.3", - "semantic-release": "^22.0.8", - "turbo": "^1.10.16", - "typescript": "^5.3.2" + "lefthook": "^1.5.6", + "semantic-release": "^23.0.0", + "turbo": "^1.11.3", + "typescript": "^5.3.3", + "@revanced/bot-shared": "workspace:*", + "@revanced/bot-api": "workspace:*" }, "bugs": { "url": "https://github.com/revanced/revanced-helper/issues" @@ -35,15 +37,19 @@ "scripts": { "build": "turbo run build", "watch": "turbo run watch", - "format": "prettier --ignore-path .gitignore --write .", - "format:check": "prettier --ignore-path .gitignore --cache --check .", + "format": "biome check --apply .", + "format:check": "biome check .", "lint": "eslint --ignore-path .gitignore --cache .", "lint:apply": "eslint --ignore-path .gitignore --fix .", "commitlint": "commitlint --edit", "t": "turbo run", "prepare": "lefthook install" }, - "trustedDependencies": ["lefthook", "biome", "turbo"], + "trustedDependencies": [ + "lefthook", + "biome", + "turbo" + ], "workspaces": [ "apis/*", "bots/*", From 59fc50fd41e0ae2018c3fca727c7bbee781574f9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:50:02 +0700 Subject: [PATCH 030/312] feat(packages/api): allow listening for invalid packets --- packages/api/src/classes/ClientGateway.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/api/src/classes/ClientGateway.ts b/packages/api/src/classes/ClientGateway.ts index 8d01271..670f67a 100755 --- a/packages/api/src/classes/ClientGateway.ts +++ b/packages/api/src/classes/ClientGateway.ts @@ -127,15 +127,13 @@ export default class ClientGateway { #listen() { this.#socket.on('message', data => { const packet = deserializePacket(this._toBuffer(data)) - // TODO: maybe log this? - // Just ignore the invalid packet, we don't have to disconnect - if (!isServerPacket(packet)) return + + if (!isServerPacket(packet)) return this.#emitter.emit('invalidPacket', packet) this.#emitter.emit('packet', packet) switch (packet.op) { case ServerOperation.Hello: { - // eslint-disable-next-line no-case-declarations const data = Object.freeze((packet as Packet).d) this.config = data this.#emitter.emit('hello', data) @@ -186,8 +184,8 @@ export default class ClientGateway { protected _toBuffer(data: RawData) { if (data instanceof Buffer) return data - else if (data instanceof ArrayBuffer) return Buffer.from(data) - else return Buffer.concat(data) + if (data instanceof ArrayBuffer) return Buffer.from(data) + return Buffer.concat(data) } } @@ -202,12 +200,13 @@ export type ClientGatewayServerEventName = keyof typeof ServerOperation export type ClientGatewayEventHandlers = { [K in Uncapitalize]: ( - packet: Packet]>, + packet: Packet<(typeof ServerOperation)[Capitalize]>, ) => Promise | void } & { hello: (config: NonNullable) => Promise | void ready: () => Promise | void packet: (packet: Packet) => Promise | void + invalidPacket: (packet: Packet) => Promise | void disconnect: (reason: DisconnectReason) => Promise | void } From 4d0bc4f19674fdb9904f3292e8802d395f3f0688 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:50:23 +0700 Subject: [PATCH 031/312] chore(packages/api): clean up imports --- packages/api/src/classes/Client.ts | 2 +- packages/api/src/classes/index.ts | 8 ++++---- packages/api/src/index.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index 245fb91..22193ee 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -1,5 +1,5 @@ import { ClientOperation, Packet, ServerOperation } from '@revanced/bot-shared' -import ClientGateway, { ClientGatewayEventHandlers } from './ClientGateway.js' +import ClientGateway, { ClientGatewayEventHandlers } from './ClientGateway' /** * The client that connects to the API. diff --git a/packages/api/src/classes/index.ts b/packages/api/src/classes/index.ts index 585b439..f2d3e72 100755 --- a/packages/api/src/classes/index.ts +++ b/packages/api/src/classes/index.ts @@ -1,4 +1,4 @@ -export { default as Client } from './Client.js' -export * from './Client.js' -export { default as ClientGateway } from './ClientGateway.js' -export * from './ClientGateway.js' +export { default as Client } from './Client' +export * from './Client' +export { default as ClientGateway } from './ClientGateway' +export * from './ClientGateway' diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index b090345..63e4228 100755 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1 +1 @@ -export * from './classes/index.js' +export * from './classes/index' From 41c59eba03c15642e0afd9105209d59be62451fe Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:50:37 +0700 Subject: [PATCH 032/312] chore(packages/api): update dependencies --- packages/api/package.json | 80 +++++++++++++++++++------------------- packages/api/tsconfig.json | 22 +++++------ 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 9604004..72cb873 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,40 +1,40 @@ -{ - "name": "@revanced/bot-api", - "type": "module", - "version": "0.1.0", - "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its API server", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "bun bundle && bun types", - "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", - "bundle:watch": "bun run bundle --watch", - "types": "tsc --declaration --emitDeclarationOnly", - "types:watch": "bun types --watch --preserveWatchOutput", - "types:clean": "bun types --build --clean" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "packages/api" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "@revanced/bot-shared": "workspace:*", - "ws": "^8.14.2" - }, - "devDependencies": { - "@types/ws": "^8.5.10", - "typed-emitter": "^2.1.0" - } -} +{ + "name": "@revanced/bot-api", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its API server", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/api" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@revanced/bot-shared": "workspace:*", + "ws": "^8.14.2" + }, + "devDependencies": { + "@types/ws": "^8.5.10", + "typed-emitter": "^2.1.0" + } +} diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index f4b850d..9ed99d0 100755 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,11 +1,11 @@ -{ - "extends": "../../tsconfig.packages.json", - "compilerOptions": { - "baseUrl": ".", - "rootDir": "./src", - "outDir": "dist", - "module": "ESNext", - "composite": true - }, - "exclude": ["node_modules", "dist"] -} +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "./src", + "outDir": "dist", + "module": "ESNext", + "composite": true + }, + "exclude": ["node_modules", "dist"] +} From ee5ffa6f246e6e2c12a0bd55da420c7ff31d0ce7 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:52:07 +0700 Subject: [PATCH 033/312] fix(packages/shared): fix weird log dates and clean up imports --- .../src/constants/HumanizedDisconnectReason.ts | 2 +- packages/shared/src/constants/index.ts | 6 +++--- packages/shared/src/index.ts | 6 +++--- packages/shared/src/schemas/Packet.ts | 6 +++--- packages/shared/src/schemas/index.ts | 2 +- packages/shared/src/utils/environment.ts | 13 +++++++++++++ packages/shared/src/utils/guard.ts | 4 ++-- packages/shared/src/utils/index.ts | 9 +++++---- packages/shared/src/utils/logger.ts | 12 +++++------- packages/shared/src/utils/serialization.ts | 4 ++-- 10 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 packages/shared/src/utils/environment.ts diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts index fb0c070..0db27c2 100755 --- a/packages/shared/src/constants/HumanizedDisconnectReason.ts +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -1,4 +1,4 @@ -import DisconnectReason from './DisconnectReason.js' +import DisconnectReason from './DisconnectReason' /** * Humanized disconnect reasons for logs diff --git a/packages/shared/src/constants/index.ts b/packages/shared/src/constants/index.ts index 98ae4d4..a353663 100755 --- a/packages/shared/src/constants/index.ts +++ b/packages/shared/src/constants/index.ts @@ -1,3 +1,3 @@ -export { default as DisconnectReason } from './DisconnectReason.js' -export { default as HumanizedDisconnectReason } from './HumanizedDisconnectReason.js' -export * from './Operation.js' +export { default as DisconnectReason } from './DisconnectReason' +export { default as HumanizedDisconnectReason } from './HumanizedDisconnectReason' +export * from './Operation' diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index b143126..ab1d8b0 100755 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,3 @@ -export * from './constants/index.js' -export * from './schemas/index.js' -export * from './utils/index.js' +export * from './constants/index' +export * from './schemas/index' +export * from './utils/index' diff --git a/packages/shared/src/schemas/Packet.ts b/packages/shared/src/schemas/Packet.ts index 9e41554..e1f93f9 100755 --- a/packages/shared/src/schemas/Packet.ts +++ b/packages/shared/src/schemas/Packet.ts @@ -14,8 +14,8 @@ import { string, // merge } from 'valibot' -import DisconnectReason from '../constants/DisconnectReason.js' -import { ClientOperation, Operation, ServerOperation } from '../constants/Operation.js' +import DisconnectReason from '../constants/DisconnectReason' +import { ClientOperation, Operation, ServerOperation } from '../constants/Operation' /** * Schema to validate packets @@ -90,5 +90,5 @@ export const PacketDataSchemas = { export type Packet = { op: TOp - d: Output + d: Output<(typeof PacketDataSchemas)[TOp]> } diff --git a/packages/shared/src/schemas/index.ts b/packages/shared/src/schemas/index.ts index 8b93531..e23a5a9 100755 --- a/packages/shared/src/schemas/index.ts +++ b/packages/shared/src/schemas/index.ts @@ -1 +1 @@ -export * from './Packet.js' +export * from './Packet' diff --git a/packages/shared/src/utils/environment.ts b/packages/shared/src/utils/environment.ts new file mode 100644 index 0000000..b0c1d61 --- /dev/null +++ b/packages/shared/src/utils/environment.ts @@ -0,0 +1,13 @@ +export function getMissingEnvironmentVariables(keys: string[]) { + return keys.filter(key => !process.env[key]) +} + +export function getEnvironmentType() { + const environmentValue = process.env['NODE_ENV'] + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/26255 + if (!NodeEnvironments.includes(environmentValue)) return null + return process.env['NODE_ENV'] as NodeEnvironment +} + +const NodeEnvironments = ['development', 'production'] as const +export type NodeEnvironment = (typeof NodeEnvironments)[number] diff --git a/packages/shared/src/utils/guard.ts b/packages/shared/src/utils/guard.ts index b58983b..9b92cba 100755 --- a/packages/shared/src/utils/guard.ts +++ b/packages/shared/src/utils/guard.ts @@ -1,5 +1,5 @@ -import { ClientOperation, Operation, ServerOperation } from '../constants/Operation.js' -import { Packet } from '../schemas/Packet.js' +import { ClientOperation, Operation, ServerOperation } from '../constants/Operation' +import { Packet } from '../schemas/Packet' /** * Checks whether a packet is trying to do the given operation diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index 9d276ca..7b06030 100755 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -1,4 +1,5 @@ -export * from './guard.js' -export * from './logger.js' -export * from './serialization.js' -export * from './string.js' +export * from './environment' +export * from './guard' +export * from './logger' +export * from './serialization' +export * from './string' diff --git a/packages/shared/src/utils/logger.ts b/packages/shared/src/utils/logger.ts index ad065a8..7125f7f 100644 --- a/packages/shared/src/utils/logger.ts +++ b/packages/shared/src/utils/logger.ts @@ -1,15 +1,13 @@ -import { colorConsole, console as uncoloredConsole, Tracer } from 'tracer' import { Chalk, supportsColor, supportsColorStderr } from 'chalk' +import { console as uncoloredConsole, Tracer, colorConsole } from 'tracer' const chalk = new Chalk() const DefaultConfig = { - dateformat: 'DD/MM/YYYY HH:mm:ss.sss Z', + dateformat: 'dd/mm/yyyy HH:mm:ss.sss Z', format: [ '{{message}}', { - error: `${chalk.bgRedBright.whiteBright(' ERROR ')} {{message}}\n${chalk.gray( - '{{stack}}', - )}`, + error: `${chalk.bgRedBright.whiteBright(' ERROR ')} {{message}}\n${chalk.gray('{{stack}}')}`, debug: chalk.gray('DEBUG: {{message}}\n{{stack}}'), warn: `${chalk.bgYellowBright.whiteBright(' WARN ')} ${chalk.yellowBright('{{message}}')}\n${chalk.gray( '{{stack}}', @@ -26,7 +24,7 @@ const DefaultConfig = { filters: [], } satisfies Tracer.LoggerConfig -export function createLogger(config: Omit) { +export function createLogger(config?: Omit) { const combinedConfig = { ...DefaultConfig, ...config } if ( @@ -37,7 +35,7 @@ export function createLogger(config: Omit diff --git a/packages/shared/src/utils/serialization.ts b/packages/shared/src/utils/serialization.ts index 92b274f..a906c17 100755 --- a/packages/shared/src/utils/serialization.ts +++ b/packages/shared/src/utils/serialization.ts @@ -1,7 +1,7 @@ import * as BSON from 'bson' import { parse } from 'valibot' -import { Operation } from '../constants/index.js' -import { Packet, PacketSchema } from '../schemas/index.js' +import { Operation } from '../constants/index' +import { Packet, PacketSchema } from '../schemas/index' /** * Compresses a packet into a buffer From 85fe5dd1ff28dbdac89ef20da4e9bfcc6016996b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:52:30 +0700 Subject: [PATCH 034/312] chore(packages/shared): update dependencies --- packages/shared/package.json | 80 +++++++++++++++++------------------ packages/shared/tsconfig.json | 22 +++++----- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/shared/package.json b/packages/shared/package.json index c903ac0..95e3b7a 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,40 +1,40 @@ -{ - "name": "@revanced/bot-shared", - "type": "module", - "version": "0.1.0", - "description": "🙌🏻 Shared components for bots assisting ReVanced", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "bun bundle && bun types", - "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", - "bundle:watch": "bun run bundle --watch", - "types": "tsc --declaration --emitDeclarationOnly", - "types:watch": "bun types --watch --preserveWatchOutput", - "types:clean": "bun types --build --clean" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "packages/shared" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "bson": "^6.2.0", - "chalk": "^5.3.0", - "supports-color": "^9.4.0", - "tracer": "^1.3.0", - "valibot": "^0.21.0", - "zod": "^3.22.4" - } -} +{ + "name": "@revanced/bot-shared", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Shared components for bots assisting ReVanced", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/shared" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "bson": "^6.2.0", + "chalk": "^5.3.0", + "supports-color": "^9.4.0", + "tracer": "^1.3.0", + "valibot": "^0.21.0", + "zod": "^3.22.4" + } +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index f4b850d..9ed99d0 100755 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -1,11 +1,11 @@ -{ - "extends": "../../tsconfig.packages.json", - "compilerOptions": { - "baseUrl": ".", - "rootDir": "./src", - "outDir": "dist", - "module": "ESNext", - "composite": true - }, - "exclude": ["node_modules", "dist"] -} +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "./src", + "outDir": "dist", + "module": "ESNext", + "composite": true + }, + "exclude": ["node_modules", "dist"] +} From be5ceff38550aa4c23d5ff29ba7666b6542bee1c Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jan 2024 21:53:07 +0700 Subject: [PATCH 035/312] chore: use two spaces for jsons --- tsconfig.base.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tsconfig.base.json b/tsconfig.base.json index a176582..4183ad5 100755 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,17 +1,17 @@ -{ - // `bun-types` will not be added until https://github.com/oven-sh/bun/issues/7247 is fixed - "extends": ["@tsconfig/strictest" /* "bun-types" */], - "compilerOptions": { - "lib": ["ESNext"], - "module": "NodeNext", - "moduleResolution": "Bundler", - "target": "ESNext", - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "resolveJsonModule": true, - "esModuleInterop": true, - "declaration": false, - "allowSyntheticDefaultImports": true, - "isolatedModules": true - } -} +{ + // `bun-types` will not be added until https://github.com/oven-sh/bun/issues/7247 is fixed + "extends": ["@tsconfig/strictest" /* "bun-types" */], + "compilerOptions": { + "lib": ["ESNext"], + "module": "NodeNext", + "moduleResolution": "Bundler", + "target": "ESNext", + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "declaration": false, + "allowSyntheticDefaultImports": true, + "isolatedModules": true + } +} From d40a9d78cb1cbb76c4f423b31b50f6c53408aec3 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Wed, 17 Jan 2024 17:49:29 +0700 Subject: [PATCH 036/312] docs(apis/websocket): minor improvements --- apis/websocket/docs/1_configuration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md index 77e8b11..95e9f74 100644 --- a/apis/websocket/docs/1_configuration.md +++ b/apis/websocket/docs/1_configuration.md @@ -22,11 +22,12 @@ The address and port for the server to listen on. Amount of concurrent queues that can be run at a time. +> [!WARNING] > Setting this too high may cause performance issues. ### `config.clientHeartbeatInterval` -Heartbeat interval for clients. See [**💓 Heartbeating**](./packets.md#💓-heartbeating). +Heartbeat interval for clients. See [**💓 Heartbeating**](./3_packets.md#💓-heartbeating). ### `config.consoleLogLevel` From e600cc56f73cdce3f3987c2d83f43398dfab4019 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Wed, 17 Jan 2024 17:49:29 +0700 Subject: [PATCH 037/312] chore(apis/websocket): remove unneccessary packages and edit build target --- apis/websocket/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 2be8673..fe8eef9 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -6,7 +6,7 @@ "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { - "bundle": "bun build src/index.ts --outdir=dist --target=node --minify --sourcemap=external", + "bundle": "bun build src/index.ts --outdir=dist --target=bun --minify --sourcemap=external", "dev": "bun run src/index.ts --watch", "build": "bun bundle", "watch": "bun dev" @@ -27,17 +27,14 @@ }, "homepage": "https://github.com/revanced/revanced-helper#readme", "dependencies": { - "@fastify/websocket": "^8.3.1", "@revanced/bot-shared": "workspace:*", "@sapphire/async-queue": "^1.5.1", "chalk": "^5.3.0", - "fastify": "^4.25.2", "node-wit": "^6.6.0", "tesseract.js": "^5.0.4" }, "devDependencies": { "@types/node-wit": "^6.0.3", - "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } } From a6907b16c4607444d37aa95d41a97e1c98c1b4cd Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:36:39 +0700 Subject: [PATCH 038/312] chore(apis/websocket)!: change default port --- apis/websocket/docs/1_configuration.md | 2 +- apis/websocket/src/utils/getConfig.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md index 95e9f74..aead5c5 100644 --- a/apis/websocket/docs/1_configuration.md +++ b/apis/websocket/docs/1_configuration.md @@ -8,7 +8,7 @@ This is the default configuration (provided in [config.json](../config.json)): "port": 3000, "ocrConcurrentQueues": 1, "clientHeartbeatInterval": 60000, - "debugLogsInProduction": false + "consoleLogLevel": "log" } ``` diff --git a/apis/websocket/src/utils/getConfig.ts b/apis/websocket/src/utils/getConfig.ts index d26042e..90bf638 100755 --- a/apis/websocket/src/utils/getConfig.ts +++ b/apis/websocket/src/utils/getConfig.ts @@ -26,7 +26,7 @@ export type Config = Omit, '$sche export const defaultConfig: Config = { address: '127.0.0.1', - port: 80, + port: 8080, ocrConcurrentQueues: 1, clientHeartbeatInterval: 60000, consoleLogLevel: 'info', From 168f40def64ca213cd2b549f4bafed4c0e1e3695 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:37:11 +0700 Subject: [PATCH 039/312] fix(apis/websocket): fix forever stuck Promise --- apis/websocket/src/classes/Client.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index 40037c3..8025c5b 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -68,8 +68,8 @@ export default class Client { return new Promise((resolve, reject) => { try { this.#throwIfDisconnected('Cannot send packet to client that has already disconnected') - - this.#socket.send(serializePacket(packet), err => (err ? reject(err) : resolve())) + this.#socket.send(serializePacket(packet)) + resolve() } catch (e) { reject(e) } @@ -91,11 +91,13 @@ export default class Client { forceDisconnect(reason: DisconnectReason = DisconnectReason.Generic) { if (this.disconnected !== false) return - if (this.#hbTimeout) clearTimeout(this.#hbTimeout) - this.#socket.terminate() - - this.ready = false + // It's so weird because if I moved this down a few lines + // it would just fire the disconnect event twice because of a race condition this.disconnected = reason + this.ready = false + + if (this.#hbTimeout) clearTimeout(this.#hbTimeout) + this.#socket.close() this.#emitter.emit('disconnect', reason) } @@ -111,6 +113,7 @@ export default class Client { #listen() { this.#socket.on('message', data => { + this.#emitter.emit('message', data) try { const rawPacket = deserializePacket(this._toBuffer(data)) if (!isClientPacket(rawPacket)) throw null @@ -192,4 +195,5 @@ export type ClientEventHandlers = { ready: () => Promise | unknown packet: (packet: ClientPacketObject) => Promise | unknown disconnect: (reason: DisconnectReason) => Promise | unknown + message: (data: RawData) => Promise | unknown } From 97c5bd5634afb4baa4a5f301d2705310c90bd04d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:38:13 +0700 Subject: [PATCH 040/312] chore(apis/websocket): stop using fastify as a wrapper --- apis/websocket/src/index.ts | 144 ++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index 1145d64..638b9bc 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -1,6 +1,3 @@ -import fastifyWebsocket from '@fastify/websocket' -import { fastify } from 'fastify' - import witPkg from 'node-wit' import { createWorker as createTesseractWorker } from 'tesseract.js' const { Wit } = witPkg @@ -12,14 +9,16 @@ import Client from './classes/Client' import { EventContext, parseImageEventHandler, parseTextEventHandler } from './events/index' import { DisconnectReason, HumanizedDisconnectReason, createLogger } from '@revanced/bot-shared' -import { WebSocket } from 'ws' import { checkEnvironment, getConfig } from './utils/index' +import { createServer } from 'http' +import { WebSocket, WebSocketServer } from 'ws' + // Load config, init logger, check environment const config = getConfig() const logger = createLogger({ - level: config.consoleLogLevel === 'none' ? Infinity : config.consoleLogLevel, + level: config['consoleLogLevel'] === 'none' ? Infinity : config['consoleLogLevel'], }) checkEnvironment(logger) @@ -42,81 +41,84 @@ const eventContext: EventContext = { config, } -const server = fastify() - .register(fastifyWebsocket, { - options: { - // 16 KiB max payload - // A Discord message can not be longer than 4000 characters - // OCR should not be longer than 16000 characters - maxPayload: 16 * 1024, - }, - }) - .register(async instance => { - instance.get('/', { websocket: true }, async (connection, request) => { - try { - const client = new Client({ - socket: connection.socket, - id: request.hostname, - heartbeatInterval: config.clientHeartbeatInterval, - }) +const server = createServer() - clientSocketMap.set(connection.socket, client) - clients.add(client) +const wss = new WebSocketServer({ + // 16 KiB max payload + // A Discord message can not be longer than 4000 characters + // OCR should not be longer than 16000 characters + maxPayload: 16 * 1024, + server, +}) - logger.debug(`Client ${client.id}'s instance has been added`) - logger.info(`New client connected (now ${clients.size} clients) with ID:`, client.id) +wss.on('connection', async (socket, request) => { + try { + if (!request.socket.remoteAddress) { + socket.close() + return logger.warn('Connection failed because client is missing remote address') + } - client.on('disconnect', reason => { - clients.delete(client) - logger.info(`Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}`) - }) - - client.on('parseText', async packet => parseTextEventHandler(packet, eventContext)) - - client.on('parseImage', async packet => parseImageEventHandler(packet, eventContext)) - - if (['debug', 'silly'].includes(config.consoleLogLevel)) { - logger.debug('Debug logs enabled, attaching debug events...') - - client.on('packet', ({ client, ...rawPacket }) => - logger.debug(`Packet received from client ${client.id}: ${inspectObject(rawPacket)}`), - ) - - client.on('heartbeat', () => logger.debug('Heartbeat received from client', client.id)) - } - } catch (e) { - if (e instanceof Error) logger.error(e.stack ?? e.message) - else logger.error(inspectObject(e)) - - const client = clientSocketMap.get(connection.socket) - - if (!client) { - logger.error( - 'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!', - ) - return connection.socket.terminate() - } - - if (client.disconnected === false) client.disconnect(DisconnectReason.ServerError) - else client.forceDisconnect() - - clients.delete(client) - - logger.debug(`Client ${client.id} disconnected because of an internal error`) - } + const client = new Client({ + socket, + id: `${request.socket.remoteAddress}:${request.socket.remotePort}`, + heartbeatInterval: config['clientHeartbeatInterval'], }) - }) + + clientSocketMap.set(socket, client) + clients.add(client) + + logger.debug(`Client ${client.id}'s instance has been added`) + logger.info(`New client connected (now ${clients.size} clients) with ID:`, client.id) + + client.on('disconnect', reason => { + clients.delete(client) + logger.info(`Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}`) + }) + + client.on('parseText', async packet => parseTextEventHandler(packet, eventContext)) + + client.on('parseImage', async packet => parseImageEventHandler(packet, eventContext)) + + if (['debug', 'trace'].includes(config['consoleLogLevel'])) { + logger.debug('Debug logs enabled, attaching debug events...') + + client.on('packet', ({ client, ...rawPacket }) => + logger.debug(`Packet received from client ${client.id}: ${inspectObject(rawPacket)}`), + ) + + client.on('message', d => logger.debug(`Message from client ${client.id}:`, d)) + + client.on('heartbeat', () => logger.debug('Heartbeat received from client', client.id)) + } + } catch (e) { + if (e instanceof Error) logger.error(e.stack ?? e.message) + else logger.error(inspectObject(e)) + + const client = clientSocketMap.get(socket) + + if (!client) { + logger.error( + 'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!', + ) + return socket.terminate() + } + + if (client.disconnected === false) client.disconnect(DisconnectReason.ServerError) + else client.forceDisconnect() + + clients.delete(client) + + logger.debug(`Client ${client.id} disconnected because of an internal error`) + } +}) // Start the server +server.listen(config['port'], config['address']) + logger.debug(`Starting with these configurations: ${inspectObject(config)}`) -await server.listen({ - host: config.address ?? '0.0.0.0', - port: config.port ?? 80, -}) - -const addressInfo = server.server.address() +const addressInfo = wss.address() if (!addressInfo || typeof addressInfo !== 'object') logger.debug('Server started, but cannot determine address information') else logger.info(`Server started at: ${addressInfo.address}:${addressInfo.port}`) From 526d514443e787c4ad4fec6cadd74a41a32e4329 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:44:59 +0700 Subject: [PATCH 041/312] docs: move environment setup to monorepo root --- apis/websocket/docs/2_running.md | 12 ++++++++---- apis/websocket/docs/README.md | 6 +++--- .../docs => docs}/0_development_environment.md | 17 ++++++++++++----- docs/README.md | 13 +++++++++++++ 4 files changed, 36 insertions(+), 12 deletions(-) rename {apis/websocket/docs => docs}/0_development_environment.md (73%) create mode 100644 docs/README.md diff --git a/apis/websocket/docs/2_running.md b/apis/websocket/docs/2_running.md index 0487200..27d0909 100644 --- a/apis/websocket/docs/2_running.md +++ b/apis/websocket/docs/2_running.md @@ -2,9 +2,6 @@ There are many methods to run the server. Choose one that suits best for the situation. -> [!IMPORTANT] -> Make sure you've followed the [**🏗️ Setting up the environment**](./0_development_environment.md) steps. - ## 👷🏻 Development mode (recommended) There will be no compilation step, and Bun will automatically watch changes and restart the server for you. @@ -38,4 +35,11 @@ If you're looking to build and host the server somewhere else, you can run: bun bundle ``` -The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** +The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** +You can run these files after using a runtime, eg. `bun run .` or `node .`. + +## ⏭️ What's next + +The next page will tell you about packets. + +Continue: [📨 Packets](./3_packets.md) diff --git a/apis/websocket/docs/README.md b/apis/websocket/docs/README.md index f14a3bb..710432d 100755 --- a/apis/websocket/docs/README.md +++ b/apis/websocket/docs/README.md @@ -4,13 +4,13 @@ This documentation explains how the server works, how to start developing, and h ## 📖 Table of contents -0. [🏗️ Setting up the development environment](./0_development_environment.md) +0. [🏗️ Set up the development environment (if you haven't already)](../../../docs/0_development_environment.md) 1. [⚙️ Configuration](./1_configuration.md) 2. [🏃🏻‍♂️ Running the server](./2_running.md) 3. [📨 Packets](./3_packets.md) ## ⏭️ Start here -The next page will tell you how to set up the development environment. +The next page will tell you how to configure the server. -Continue: [🏗️ Setting up the development environment](./0_development_environment.md) +Continue: [⚙️ Configuration](./1_configuration.md) diff --git a/apis/websocket/docs/0_development_environment.md b/docs/0_development_environment.md similarity index 73% rename from apis/websocket/docs/0_development_environment.md rename to docs/0_development_environment.md index f4cb460..29c2588 100644 --- a/apis/websocket/docs/0_development_environment.md +++ b/docs/0_development_environment.md @@ -24,16 +24,23 @@ To start developing, you'll need to set up the development environment first. 4. Build packages/libraries ```sh - bun build:libs + bun run build ``` -5. Change your directory to this project's root +5. Change your directory to a project's root ```sh + # WebSocket API cd apis/websocket + + # Discord bot + cd bots/discord + + # Programmatic API + cd packages/api + + # Etc. ``` ## ⏭️ What's next -The next page will tell you about server configurations. - -Continue: [⚙️ Configuration](./1_configuration.md) +You'll need to go to the respective project's documentation to continue. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d53d4de --- /dev/null +++ b/docs/README.md @@ -0,0 +1,13 @@ +# 🚙 ReVanced Bot + +This documentation are steps required to start developing ReVanced bots. + +## 📖 Table of contents + +0. [🏗️ Setting up the development environment](./0_development_environment.md) + +## ⏭️ Start here + +The next page will tell you how to set up the development environment. + +Continue: [🏗️ Setting up the development environment](./0_development_environment.md) From 68fa36a6db1f62f5654e24e0106ba6bbb8cd8fc6 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:47:10 +0700 Subject: [PATCH 042/312] fix(packages/api): ready event triggering even when not ready --- packages/api/src/classes/ClientGateway.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/api/src/classes/ClientGateway.ts b/packages/api/src/classes/ClientGateway.ts index 670f67a..1e4a302 100755 --- a/packages/api/src/classes/ClientGateway.ts +++ b/packages/api/src/classes/ClientGateway.ts @@ -10,7 +10,7 @@ import { uncapitalize, } from '@revanced/bot-shared' import type TypedEmitter from 'typed-emitter' -import { type RawData, WebSocket } from 'ws' +import { RawData, WebSocket } from 'ws' /** * The class that handles the WebSocket connection to the server. @@ -41,14 +41,16 @@ export default class ClientGateway { this.#socket.on('open', () => { this.disconnected = false + this.#listen() + this.ready = true + this.#emitter.emit('ready') rs() }) - this.#socket.on('close', () => this.#handleDisconnect(DisconnectReason.Generic)) + const errorHandler = () => this.#handleDisconnect(DisconnectReason.Generic) - this.#listen() - this.ready = true - this.#emitter.emit('ready') + this.#socket.on('close', errorHandler) + this.#socket.on('error', errorHandler) } catch (e) { rj(e) } @@ -112,7 +114,6 @@ export default class ClientGateway { */ disconnect() { this.#throwIfDisconnected('Cannot disconnect when already disconnected from the server') - this.#handleDisconnect(DisconnectReason.Generic) } From 25e265cc4e96772bcdc550c8187b302dfe56a5df Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:47:51 +0700 Subject: [PATCH 043/312] chore(packages/api): remove unneccesary packages and change build target --- packages/api/package.json | 74 +++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 72cb873..e30e8b4 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,40 +1,38 @@ { - "name": "@revanced/bot-api", - "type": "module", - "version": "0.1.0", - "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its API server", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "bun bundle && bun types", - "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", - "bundle:watch": "bun run bundle --watch", - "types": "tsc --declaration --emitDeclarationOnly", - "types:watch": "bun types --watch --preserveWatchOutput", - "types:clean": "bun types --build --clean" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "packages/api" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "@revanced/bot-shared": "workspace:*", - "ws": "^8.14.2" - }, - "devDependencies": { - "@types/ws": "^8.5.10", - "typed-emitter": "^2.1.0" - } + "name": "@revanced/bot-api", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its API server", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "bunx conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=bun --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/api" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@revanced/bot-shared": "workspace:*" + }, + "devDependencies": { + "typed-emitter": "^2.1.0" + } } From d806b001e0cfbf6dbc6c5db8af055a3bcc1190d2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:48:24 +0700 Subject: [PATCH 044/312] chore(packages/shared): change build target --- packages/shared/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/package.json b/packages/shared/package.json index 95e3b7a..9d6141d 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "bun bundle && bun types", "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=bun --minify", "bundle:watch": "bun run bundle --watch", "types": "tsc --declaration --emitDeclarationOnly", "types:watch": "bun types --watch --preserveWatchOutput", From 4c38f2534036e042fd90aceeafc796dbb1ba9ffc Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:50:52 +0700 Subject: [PATCH 045/312] chore: change commitlint config to use esm --- .commitlintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.commitlintrc.js b/.commitlintrc.js index 3e16e7f..df53c3d 100755 --- a/.commitlintrc.js +++ b/.commitlintrc.js @@ -1,3 +1,3 @@ -module.exports = { +export default { extends: ['@commitlint/config-conventional'], } From 1d54f5b9f8332b326278f1339e65823a5c62a784 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:51:11 +0700 Subject: [PATCH 046/312] chore(README): add monorepo document --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3055ae1 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +

    + + + + +
    +
    + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + + +
    +
    + Continuing the legacy of Vanced +

    + +# 🤖 ReVanced Helper + +Bots assisting ReVanced on multiple platforms. + +## 👷🏻 Contributing + +Thank you for considering! This project is made in TypeScript and we use the [Bun](https://bun.sh) JavaScript runtime. + +Please refer to the [documentation](./docs) for more information. +You can find projects under the [`apis`](./apis/), [`bots`](./bots), and [`packages`](./packages/) directories. \ No newline at end of file From 680f00722f8c2f43b815202727894f3f87aa310d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:53:06 +0700 Subject: [PATCH 047/312] chore: clean up tsconfigs and use `bun-types` --- apis/websocket/config.example.json | 9 --------- apis/websocket/tsconfig.json | 2 +- packages/api/tsconfig.json | 3 ++- packages/shared/tsconfig.json | 3 ++- tsconfig.json | 27 +++++++++++++++++++++++++++ tsconfig.packages.json | 2 +- 6 files changed, 33 insertions(+), 13 deletions(-) delete mode 100755 apis/websocket/config.example.json create mode 100644 tsconfig.json mode change 100755 => 100644 tsconfig.packages.json diff --git a/apis/websocket/config.example.json b/apis/websocket/config.example.json deleted file mode 100755 index 9a07cd7..0000000 --- a/apis/websocket/config.example.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "./config.schema.json", - - "address": "127.0.0.1", - "port": 3000, - "ocrConcurrentQueues": 1, - "clientHeartbeatInterval": 5000, - "consoleLogLevel": "log" -} diff --git a/apis/websocket/tsconfig.json b/apis/websocket/tsconfig.json index a4fa4d4..29a7c65 100755 --- a/apis/websocket/tsconfig.json +++ b/apis/websocket/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.apis.json", + "extends": "../../tsconfig.json", "compilerOptions": { "baseUrl": ".", "outDir": "dist", diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 9ed99d0..01f29d1 100755 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -5,7 +5,8 @@ "rootDir": "./src", "outDir": "dist", "module": "ESNext", - "composite": true + "composite": true, + "noEmit": false }, "exclude": ["node_modules", "dist"] } diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index 9ed99d0..01f29d1 100755 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -5,7 +5,8 @@ "rootDir": "./src", "outDir": "dist", "module": "ESNext", - "composite": true + "composite": true, + "noEmit": false }, "exclude": ["node_modules", "dist"] } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..050bea4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": ["@tsconfig/strictest", "bun-types"], + "compilerOptions": { + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + + "moduleResolution": "Bundler", + + "strict": true, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "useUnknownInCatchVariables": true, + "noPropertyAccessFromIndexSignature": true, + + "composite": false, + "resolveJsonModule": true, + "esModuleInterop": true, + "declaration": false, + "allowSyntheticDefaultImports": true, + "isolatedModules": true, + "allowImportingTsExtensions": false + } +} diff --git a/tsconfig.packages.json b/tsconfig.packages.json old mode 100755 new mode 100644 index 3406eb1..685fcf3 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.json", "compilerOptions": { "declaration": true, "declarationMap": true From 4792fde5a39dceebe86f134e9b4dc1a407b0f00c Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 17 Jan 2024 22:55:15 +0700 Subject: [PATCH 048/312] chore: remove unused dev packages and scripts --- bun.lockb | Bin 277860 -> 131048 bytes package.json | 72 +++++++++++++++++++++------------------------------ 2 files changed, 29 insertions(+), 43 deletions(-) mode change 100755 => 100644 package.json diff --git a/bun.lockb b/bun.lockb index e5e6d37600d78b892531cb394a6fe0b24d6da4c1..045174840c87f94959ab698351bce5126e11b537 100755 GIT binary patch delta 31065 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

    %6**- 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 (https://github.com/PalmDevs)", + "workspaces": ["apis/*", "bots/*", "packages/*"], + "scripts": { + "build": "turbo run build", + "watch": "turbo run watch", + "flint": "biome check --apply .", + "flint:check": "biome check .", + "clint": "commitlint --edit", + "prepare": "lefthook install" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", "repository": { "type": "git", "url": "git+https://github.com/revanced/revanced-helper.git" }, - "devDependencies": { - "@biomejs/biome": "1.5.1", - "@commitlint/cli": "^18.4.4", - "@commitlint/config-conventional": "^18.4.4", - "@tsconfig/strictest": "^2.0.2", - "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.5.6", - "semantic-release": "^23.0.0", - "turbo": "^1.11.3", - "typescript": "^5.3.3", - "@revanced/bot-shared": "workspace:*", - "@revanced/bot-api": "workspace:*" - }, "bugs": { "url": "https://github.com/revanced/revanced-helper/issues" }, @@ -26,33 +27,18 @@ "Palm (https://github.com/PalmDevs)", "ReVanced (https://github.com/revanced)" ], - "description": "🤖 Bots assisting ReVanced on multiple platforms", - "homepage": "https://github.com/revanced/revanced-helper#readme", - "license": "GPL-3.0-or-later", - "overrides": { - "uuid": ">=9.0.0", - "isomorphic-fetch": ">=3.0.0" - }, - "private": true, - "scripts": { - "build": "turbo run build", - "watch": "turbo run watch", - "format": "biome check --apply .", - "format:check": "biome check .", - "lint": "eslint --ignore-path .gitignore --cache .", - "lint:apply": "eslint --ignore-path .gitignore --fix .", - "commitlint": "commitlint --edit", - "t": "turbo run", - "prepare": "lefthook install" - }, - "trustedDependencies": [ - "lefthook", - "biome", - "turbo" - ], - "workspaces": [ - "apis/*", - "bots/*", - "packages/*" - ] -} + "devDependencies": { + "@biomejs/biome": "^1.5.2", + "@commitlint/cli": "^18.4.4", + "@commitlint/config-conventional": "^18.4.4", + "@revanced/bot-api": "workspace:*", + "@revanced/bot-shared": "workspace:*", + "@tsconfig/strictest": "^2.0.2", + "@types/bun": "latest", + "concurrently": "^8.2.2", + "conventional-changelog-conventionalcommits": "^7.0.2", + "lefthook": "^1.5.6", + "turbo": "^1.11.3", + "typescript": "^5.0.0" + } +} \ No newline at end of file From 56e364cedb3f9a53342c48fd70e9ce30d3a846f9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 18 Jan 2024 22:43:19 +0700 Subject: [PATCH 049/312] fix(packages/api)!: handle dead connections better --- packages/api/src/classes/Client.ts | 56 +++++++--------- .../{ClientGateway.ts => ClientWebSocket.ts} | 64 ++++++++++++------- packages/api/src/classes/index.ts | 3 +- .../shared/src/constants/DisconnectReason.ts | 4 ++ .../constants/HumanizedDisconnectReason.ts | 1 + 5 files changed, 71 insertions(+), 57 deletions(-) rename packages/api/src/classes/{ClientGateway.ts => ClientWebSocket.ts} (75%) diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index 22193ee..3db2488 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -1,30 +1,22 @@ import { ClientOperation, Packet, ServerOperation } from '@revanced/bot-shared' -import ClientGateway, { ClientGatewayEventHandlers } from './ClientGateway' +import { ClientWebSocketManager, ClientWebSocketEvents, ClientWebSocketManagerOptions } from './ClientWebSocket' /** * The client that connects to the API. */ export default class Client { ready = false - gateway: ClientGateway + ws: ClientWebSocketManager #parseId = 0 constructor(options: ClientOptions) { - this.gateway = new ClientGateway({ - url: options.api.gatewayUrl, - }) - - this.gateway.on('ready', () => { + this.ws = new ClientWebSocketManager(options.api.websocket) + this.ws.on('ready', () => { this.ready = true }) - } + this.ws.on('disconnect', () => { - /** - * Connects to the WebSocket API - * @returns A promise that resolves when the client is ready - */ - connect() { - return this.gateway.connect() + }) } /** @@ -45,7 +37,7 @@ export default class Client { const currentId = (this.#parseId++).toString() - this.gateway.send({ + this.ws.send({ op: ClientOperation.ParseText, d: { text, @@ -58,18 +50,18 @@ export default class Client { const promise = new Promise((rs, rj) => { const parsedTextListener = (packet: CorrectPacket) => { if (packet.d.id !== currentId) return - this.gateway.off('parsedText', parsedTextListener) + this.ws.off('parsedText', parsedTextListener) rs(packet.d) } const parseTextFailedListener = (packet: Packet) => { if (packet.d.id !== currentId) return - this.gateway.off('parseTextFailed', parseTextFailedListener) + this.ws.off('parseTextFailed', parseTextFailedListener) rj() } - this.gateway.on('parsedText', parsedTextListener) - this.gateway.on('parseTextFailed', parseTextFailedListener) + this.ws.on('parsedText', parsedTextListener) + this.ws.on('parseTextFailed', parseTextFailedListener) }) return await promise @@ -85,7 +77,7 @@ export default class Client { const currentId = (this.#parseId++).toString() - this.gateway.send({ + this.ws.send({ op: ClientOperation.ParseImage, d: { image_url: url, @@ -98,18 +90,18 @@ export default class Client { const promise = new Promise((rs, rj) => { const parsedImageListener = (packet: CorrectPacket) => { if (packet.d.id !== currentId) return - this.gateway.off('parsedImage', parsedImageListener) + this.ws.off('parsedImage', parsedImageListener) rs(packet.d) } const parseImageFailedListener = (packet: Packet) => { if (packet.d.id !== currentId) return - this.gateway.off('parseImageFailed', parseImageFailedListener) + this.ws.off('parseImageFailed', parseImageFailedListener) rj() } - this.gateway.on('parsedImage', parsedImageListener) - this.gateway.on('parseImageFailed', parseImageFailedListener) + this.ws.on('parsedImage', parsedImageListener) + this.ws.on('parseImageFailed', parseImageFailedListener) }) return await promise @@ -121,8 +113,8 @@ export default class Client { * @param handler The event handler * @returns The event handler function */ - on(name: TOpName, handler: ClientGatewayEventHandlers[TOpName]) { - this.gateway.on(name, handler) + on(name: TOpName, handler: ClientWebSocketEvents[TOpName]) { + this.ws.on(name, handler) return handler } @@ -132,8 +124,8 @@ export default class Client { * @param handler The event handler to remove * @returns The removed event handler function */ - off(name: TOpName, handler: ClientGatewayEventHandlers[TOpName]) { - this.gateway.off(name, handler) + off(name: TOpName, handler: ClientWebSocketEvents[TOpName]) { + this.ws.off(name, handler) return handler } @@ -143,11 +135,11 @@ export default class Client { * @param handler The event handler * @returns The event handler function */ - once( + once( name: TOpName, - handler: ClientGatewayEventHandlers[TOpName], + handler: ClientWebSocketEvents[TOpName], ) { - this.gateway.once(name, handler) + this.ws.once(name, handler) return handler } @@ -163,5 +155,5 @@ export interface ClientOptions { } export interface ClientApiOptions { - gatewayUrl: string + websocket: ClientWebSocketManagerOptions } diff --git a/packages/api/src/classes/ClientGateway.ts b/packages/api/src/classes/ClientWebSocket.ts similarity index 75% rename from packages/api/src/classes/ClientGateway.ts rename to packages/api/src/classes/ClientWebSocket.ts index 1e4a302..2d71c9c 100755 --- a/packages/api/src/classes/ClientGateway.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -16,18 +16,21 @@ import { RawData, WebSocket } from 'ws' * The class that handles the WebSocket connection to the server. * This is the only relevant class for the time being. But in the future, there may be more classes to handle different protocols of the API. */ -export default class ClientGateway { +export class ClientWebSocketManager { readonly url: string + timeout: number + ready = false disconnected: boolean | DisconnectReason = DisconnectReason.NeverConnected - config: Readonly['d']> | null = null! + config: Readonly['d']> | null = null #hbTimeout: NodeJS.Timeout = null! #socket: WebSocket = null! - #emitter = new EventEmitter() as TypedEmitter + #emitter = new EventEmitter() as TypedEmitter - constructor(options: ClientGatewayOptions) { + constructor(options: ClientWebSocketManagerOptions) { this.url = options.url + this.timeout = options.timeout ?? 10000 } /** @@ -39,6 +42,11 @@ export default class ClientGateway { try { this.#socket = new WebSocket(this.url) + setTimeout(() => { + if (!this.ready) throw new Error('WebSocket connection timed out') + this.#socket.close() + }, this.timeout) + this.#socket.on('open', () => { this.disconnected = false this.#listen() @@ -47,10 +55,14 @@ export default class ClientGateway { rs() }) - const errorHandler = () => this.#handleDisconnect(DisconnectReason.Generic) + this.#socket.on('error', (err) => { + throw err + }) - this.#socket.on('close', errorHandler) - this.#socket.on('error', errorHandler) + this.#socket.on('close', (code, reason) => { + if (code === 1006) throw new Error(`Failed to connect to WebSocket server: ${reason}`) + this.#handleDisconnect(DisconnectReason.Generic) + }) } catch (e) { rj(e) } @@ -63,9 +75,9 @@ export default class ClientGateway { * @param handler The event handler * @returns The event handler function */ - on( + on( name: TOpName, - handler: ClientGatewayEventHandlers[typeof name], + handler: ClientWebSocketEvents[typeof name], ) { this.#emitter.on(name, handler) } @@ -76,9 +88,9 @@ export default class ClientGateway { * @param handler The event handler to remove * @returns The removed event handler function */ - off( + off( name: TOpName, - handler: ClientGatewayEventHandlers[typeof name], + handler: ClientWebSocketEvents[typeof name], ) { this.#emitter.off(name, handler) } @@ -89,9 +101,9 @@ export default class ClientGateway { * @param handler The event handler * @returns The event handler function */ - once( + once( name: TOpName, - handler: ClientGatewayEventHandlers[typeof name], + handler: ClientWebSocketEvents[typeof name], ) { this.#emitter.once(name, handler) } @@ -114,14 +126,14 @@ export default class ClientGateway { */ disconnect() { this.#throwIfDisconnected('Cannot disconnect when already disconnected from the server') - this.#handleDisconnect(DisconnectReason.Generic) + this.#handleDisconnect(DisconnectReason.PlannedDisconnect) } /** * Checks whether the client is ready * @returns Whether the client is ready */ - isReady(): this is ReadiedClientGateway { + isReady(): this is ReadiedClientWebSocketManager { return this.ready } @@ -145,7 +157,7 @@ export default class ClientGateway { return this.#handleDisconnect((packet as Packet).d.reason) default: return this.#emitter.emit( - uncapitalize(ServerOperation[packet.op] as ClientGatewayServerEventName), + uncapitalize(ServerOperation[packet.op] as ClientWebSocketEventName), // @ts-expect-error TypeScript doesn't know that the lines above negate the type enough packet, ) @@ -162,6 +174,7 @@ export default class ClientGateway { clearTimeout(this.#hbTimeout) this.disconnected = reason this.#socket.close() + this.#socket = null! this.#emitter.emit('disconnect', reason) } @@ -190,25 +203,30 @@ export default class ClientGateway { } } -export interface ClientGatewayOptions { +export interface ClientWebSocketManagerOptions { /** - * The gateway URL to connect to + * The URL to connect to */ url: string + /** + * The timeout for the connection + * @default 10000 + */ + timeout?: number } -export type ClientGatewayServerEventName = keyof typeof ServerOperation +export type ClientWebSocketEventName = keyof typeof ServerOperation -export type ClientGatewayEventHandlers = { - [K in Uncapitalize]: ( +export type ClientWebSocketEvents = { + [K in Uncapitalize]: ( packet: Packet<(typeof ServerOperation)[Capitalize]>, ) => Promise | void } & { - hello: (config: NonNullable) => Promise | void + hello: (config: NonNullable) => Promise | void ready: () => Promise | void packet: (packet: Packet) => Promise | void invalidPacket: (packet: Packet) => Promise | void disconnect: (reason: DisconnectReason) => Promise | void } -export type ReadiedClientGateway = RequiredProperty> +export type ReadiedClientWebSocketManager = RequiredProperty> diff --git a/packages/api/src/classes/index.ts b/packages/api/src/classes/index.ts index f2d3e72..6762ec9 100755 --- a/packages/api/src/classes/index.ts +++ b/packages/api/src/classes/index.ts @@ -1,4 +1,3 @@ export { default as Client } from './Client' export * from './Client' -export { default as ClientGateway } from './ClientGateway' -export * from './ClientGateway' +export * from './ClientWebSocket' diff --git a/packages/shared/src/constants/DisconnectReason.ts b/packages/shared/src/constants/DisconnectReason.ts index 6935095..37588ba 100755 --- a/packages/shared/src/constants/DisconnectReason.ts +++ b/packages/shared/src/constants/DisconnectReason.ts @@ -22,6 +22,10 @@ enum DisconnectReason { * The client had never connected to the server (**CLIENT-ONLY**) */ NeverConnected = 5, + /** + * The client disconnected on its own (**CLIENT-ONLY**) + */ + PlannedDisconnect = 6, } export default DisconnectReason diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts index 0db27c2..0f3c536 100755 --- a/packages/shared/src/constants/HumanizedDisconnectReason.ts +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -9,6 +9,7 @@ const HumanizedDisconnectReason = { [DisconnectReason.TimedOut]: 'has timed out', [DisconnectReason.ServerError]: 'has been disconnected due to an internal server error', [DisconnectReason.NeverConnected]: 'had never connected to the server', + [DisconnectReason.PlannedDisconnect]: 'has disconnected on its own', } as const satisfies Record export default HumanizedDisconnectReason From 55199a3bb0336adaeaea9544a217be5f6647fbbb Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 18 Jan 2024 22:45:15 +0700 Subject: [PATCH 050/312] chore: remove useless tsconfigs --- tsconfig.apis.json | 3 --- tsconfig.base.json | 17 ----------------- 2 files changed, 20 deletions(-) delete mode 100755 tsconfig.apis.json delete mode 100755 tsconfig.base.json diff --git a/tsconfig.apis.json b/tsconfig.apis.json deleted file mode 100755 index ffcbb94..0000000 --- a/tsconfig.apis.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./tsconfig.base.json" -} diff --git a/tsconfig.base.json b/tsconfig.base.json deleted file mode 100755 index 4183ad5..0000000 --- a/tsconfig.base.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - // `bun-types` will not be added until https://github.com/oven-sh/bun/issues/7247 is fixed - "extends": ["@tsconfig/strictest" /* "bun-types" */], - "compilerOptions": { - "lib": ["ESNext"], - "module": "NodeNext", - "moduleResolution": "Bundler", - "target": "ESNext", - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "resolveJsonModule": true, - "esModuleInterop": true, - "declaration": false, - "allowSyntheticDefaultImports": true, - "isolatedModules": true - } -} From da02140f19f831e070df3ea6d1a55959b3189c6f Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 18 Jan 2024 22:49:40 +0700 Subject: [PATCH 051/312] docs(apis/websocket): fix broken reference links --- apis/websocket/docs/3_packets.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apis/websocket/docs/3_packets.md b/apis/websocket/docs/3_packets.md index fe2ea75..49eb54e 100644 --- a/apis/websocket/docs/3_packets.md +++ b/apis/websocket/docs/3_packets.md @@ -23,8 +23,8 @@ Data fields include additional information for the server to process. They are * Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well. -[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../packages/shared/src/schemas/Packet.ts) -[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../packages/shared/src/constants/Operation.ts) +[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../../packages/shared/src/schemas/Packet.ts) +[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../../packages/shared/src/constants/Operation.ts) ## 💓 Heartbeating From 58e23b221fefb5eaf829719209341ac56cd42259 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 28 Mar 2024 21:36:22 +0700 Subject: [PATCH 052/312] chore: turn off `useNodeProtocol` rule in biome --- biome.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/biome.json b/biome.json index 8d77a63..b2a4e61 100644 --- a/biome.json +++ b/biome.json @@ -18,6 +18,9 @@ }, "useEnumInitializers": { "level": "off" + }, + "useNodejsImportProtocol": { + "level": "off" } } } From 3e5d8f8e7b8942e24c9b29e2eda34ec1feb69e5d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 28 Mar 2024 21:36:56 +0700 Subject: [PATCH 053/312] chore: update deps --- bun.lockb | Bin 131048 -> 5032 bytes package.json | 21 +++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bun.lockb b/bun.lockb index 045174840c87f94959ab698351bce5126e11b537..a606d23bc62e02d641e312cf59df3629ca0af7f4 100755 GIT binary patch delta 1316 zcmaFypM8b;1U;>q^>w0NezqMln~pA?xv|kMf;n2rKt#Vr|6S2cenm5O31J2>;AWT@ zA;%xj4dy@z{>iaS+6LT=3=CWh3=PToIr&A!3=I4X3=DiAenw(WHUk4gQgMDBNQ2Od zU!1#3xA0BY;0>58Am+hT$S_$-)sAxk0|SFO149GDWJM)&&g%>e3?>W=4UCf&rOi1- z85tPNz;cN)=1ifClcm(`m^v9JN2%E{9c7$6OU;h+CnE!c3j;#~=j4TI=B&0%3=Bq- zWu>h-^O+bJ3>X+1*d`~M>Q7b>W2t9jU|;}YkYkW(76z~fK*Au8kVJzNgUkY97#l{T znX3*GC&Gfsw;H2)7#SEC8UFGA1JRQ^r};24PVQ?G_j&TUv2@E4o=v=IeBjmd1jE=KHmmPV+$NT4%+(XyW5QKD>$n3B zv>!dRhs91x_Q|mF`~ z=?$TbHq#9v8Chf*82BrL;Ri``TF^V#pPH!k;6rY}($H>ma z&oI3#kI|jU*kJnpJVtXSZpP_9@)&)%pYcL$dB)2y{r5jcspso_nvQ~@LSjy4dR~4SS5azNVqS7;if%?~PC;r>acMz8eo;xWE;u(P z7GxIdm!~Eb=O<^UmL%nu6zivC7ANNyr4%G4XD6no7V9S#WWw1=rFpsqIi=~DGI_e` zIr&MtnYkdt;PS;8iAAX?(;Me7N>6{chS8bPc>2f9j7r->)-o<*R5H~w&?`x=f;gCg zq1?a7yEq`x`2*Ao5`qlVSN~&FHDO@Vk8IC7z%IagXYK3iN!L2hFlv=QGpd}TaZjYi z_-^z$@oS5A>_6}GCqWQmR)7#RXk`B}HiL5V^f|(e1|UKQL>O>42tgFC5CU6aD-AON sX2S=l0tsQTNw$iz&@xJ!fq{XCfuX@1N;@+!Fvv47G$c(A{LeTY0F(|n+a literal 131048 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 mrdd+hbA2A(dP z4N5Coj%-`HWkIX*x!&^?AMU>Vy0;_d`^D$KlTOtyQeBlNZCsH$?WSEDWK9{wTt|?B zP|RXsE1dqI`ozDRO_SoEtZi1@@m+NG(ftyvLC;n(f2}-gabOFBczU2sp11q41h&mR zmHC_RPg`KC`^4tK3+sd3u5XdT7t|+!8q2_v{Zz|OPNi{%y;tD++C7KmW(e-yz~Jz5 z*-F8a^CbdP8oESX{$Aa6wdHc|^8-DR{{9=p-rxCfDAn!^jltDZ+%_*42_@~nbZ}9VmnFv=kIR=f%buO8T9o5lcO{d5&#KoOd(0c& zKU%Jna?M?Ny2qxAH`9JV#(yE<-~uxg)Q>IMu~vG2?2pt^)BRTy4>XHupR8D$*!5EP zE5}9C;59*4K3&^VnlM{!N#f-6s?J5{*B#!vEF#Vzm?8F?P4dEnPM|e;F!#D5o2y_O zqx|l6w(;Q;`wNow-7@(V1iTcZzKcx%CV%mg?5<0N^ODpBe3~}ylRB(_u0#CRw7AbK zywzX6E$ps7R1$OVE>ie{#*mPVrkDwHFR1Q-8OGB5X>)GVZ21Y>_WYEJ$=<^=(|t`+ zyWU~5r!#D$9_(FmaQ?0@|Kjs^?p$5|6dz>y))Kj;w|?@d<%CjG%`KP z5VmF6Hl|ex=Mui3-C&v7{~+|efMJ2l(S-Bo{%~B#-=l7R_p;;@7S4Z2;R|Z}K#gT! zITN<^W*nd1m7lS$|D9XC_nIH9f4t;XGJ}C2kThx;ik&a|8Z2dXNcortD>t?oBZXWu{xH|u- z$EL{mcCP%j<%#`=oo6^6Oq}Ab`u5s)MbO$^n7IK^GeHy!+pheY3oLi9>#yFKdS5he zw#BL5DVAG$a|E5Z)|YBbIx8L&XX$nK=+xDrE1&;ew$q|l;B#x-tF6?PPggg%k z+K&J?cah~hbFaC-b(J?8>MrcFS?_#r_4EP*PmU?d>t6E3KaPx_bVhu~>WFm`8h(?W zpJ->FohQ<^rtYcLvKyV34(xD29@hZvZ-ASdx%>Z$=X$n3%>#w+^IOgg4mvgKjm<>+ zEpr;*Yi(K}wnwS1v%ai#P6?mns-qJ=IA@EjlzqKfep&zNMc+;Y9eWE}s|^d^aG0T> z@}VV4NloSH>&oZ9yme&~a;sS;@HuF8IJEy#=+I8mx4 zxpP%Mqo5zOCj@406tcOU0_Rnh@`pG(zrA?g`qY!0m*&2p{KU%A}K2d$~>fnvMe*v>@HJ#16J~6NK+%e?wYEasM8O_47$=okCIJ)@#^u4P0 zwYDd(Q~J3^^I+8)2{ExrUOn5&t~YIF{;lzQo#>WH51Ee}MI{z47R<|NT;*kQkjv!6 zBgh&)NcxR|848Lwml_|v`Kfaj9+Q=P@Rc#qMOiw5U36L9-c2cOMQiWhUz6Mz8s;bQ zW7VSDK0CACYAb(`pRdU4vO|$gIn!&iiX3Q+4;Buw$mUL{$-ZrtnRGpE&D|_MtJm@0 zO4rJ!NFVt4i&IlOa7Sh3`nWS{1-^;C31NvoPuDIpxc+D6@@0CweZP}#-!^woL#~hG zkj;Jney*5w%<hCuWJ>Em{5(T$}5snmLFZYJc}&gL_t`%ryIs zj~~ixG%)!UG%~y^}K)8r}wUUUhOdT)}jv zQ{3#RM2&51#`^!dh60}@I`THY1?|m(xi<~j+%}i+kG^3&6JtN@W>(Lc@Sk~F8DZ^&$;}tzxl)F zmph&(+F-lwny9aaDBO8Sth4Y{mNo> z`U>&9vxYCvO_4mqt)g)v^2Yj>ITDkM?;P`LVy!q8t}V8sw0))`*F4BxB}lmgnp1}v z&GO~S!@e1k^VBa-m{sgM@5s*gmfrmVhbAYcTJ4e)DzM!4tFP#&NkX+#-uLi{_YRu) z+3Pr8(A|4%ETOi!DpaRHvk$uv+N%@WS~R{zUE{e{LmlBy#MzcyV$%^7sR29W=~nmeaB6 z5}v_|@lF0^`QPqc)ry~RPvpWv`>3lt_s&VAzLYAv##FBua_#NhPhVW8w>@R#PFXnj ze{;Os@~zJ`nOXXg=VL)-G2C2>28lR-J4H3SmBt$;RC}#9&^vk}!${$8;GH^A#R+^b zCb~a9sWI`+jY|%3VGk$d2yg#qKWAB?z&6vvC2 z>F8u~PBuLHo8R?@&&Jxq6%Tzni@RL)-?tiBzPeBp^!mzo9dEtt7^FhfD%`|84hPy98JZ;i_x*M~k9{<`XjRmq2Mi%stAk5Mga>f5pM z%H!RyTbx-o#IKQ)Ic)64XXnQ}W5ub2y;2^V8DewQ{Cj8U0;ALA#oHBYI1l|^H}AX%`^S5b^+u5N3)+VXH&^h&;k0)QH{Nt;Z+iVd z=h$z)U6*T(LdUqNdY0A_^CFa+QR2?l>G5wfp_mumAN5 zL3{UL?gh0i;pU!ME6l$2?u%JYdYRs{p1(Tf*z)_!Q_IJPUoC2ya&>o0!6`BMlkfjM z;7pwO&Wgpsb!*P5?b$U`Qk7?0wRVg0X&~ndP#FX__u;kA%2w<9ZYUYg`f$xJTcFyJ z;nK3Q=ZVq^E?LGUS3erHF5qZ8#bvIy>HQ<#??v)Uq6?C5tm@v${9A^rm75oNe6j>) zC@4Jy@$H>)d1c+FX2!DJ&cw-VjGpdtaweM^}3krY*#0r z66N+dwBYi-hUldepQ&Z?J0*d}4q@R?ifr!c$BduK1g|VOy^iaL^qhG!e`gx#{dd?k zb?${U0r#wP$;nTzvs%34TYPHi!HMPpTfYCCvFu7b*Xnl0d78X!0X@k5P0-pdn9(fR z3SIm1U$!W2v~m;lP`0_H@S*6wy-?>RA%;mubDZCO-_=v0upzRdc4`327vDxMuCAx` z3valt$+-|aN4oRF9?)0@%)OwocDT9kHH)UcmURAn_4bX20+!{0KVH0?`ny4^bN$3> zn^muh@4kQg=hCU{QnooP!6y#{R@csb57L$>l<90wSOtusNXV^thBiP@|TG$bvgU~@>fW4?!?k0@nw8J*&RyC6{O&i;j+b0w zhx`BY>|a#i5MS(Sy<)qtSdaWyYQssiTk7o%FITIPwFwtb4^48@))+kKZt}?~L@5$8LPOmi6PFMMsYUBu7Hwg=0&>9Q4xkfkk)!)oixo#ld z9TtB6+**^j;t`VF&;N+6EMmN+w{~TWXktJ_>^?Jwb}xx1oclXOgkF8!J@bHXVUdf- zqv?|%dw3!41?{(no9kqneDLat?Y%Yz9Sti~e=Kxb)9Bl>f^qZa83q#WBG&wl0- z;V$-2{ivzcj~$02XRehy-TreL)9VScQt>W4kUh;1bL(J+g5qt%qWJ62-xG&_e($SEWZ$WTR3yao=ew%hR*wYD|zbv_FarK&gQ*yEd2YS z_z0*?1`7w!+C-SqEPJ-;A@o&@dRzE>tD(74 zpzU*%icZh{M%e?=-nU<0%g+7vDcSa>j)HK=j0IvJ4MTW7c|BDXxy#}5<)S!fO&=_L zL32eQGeMXo^Wfavmifz57go&Toc)oJMd;z5hCdqSRu5WcelgQ!OW5<5cgurso;o#~ zE9A0_jViZo=bNl{_x)ju<4MytdhPpxV%Ldo-t?+OaQh&8q5^Z?=3A+kR!g^c@+w0~0h|*3113cO;i@T>R=q zs(O%U{uhSFlU-gtK5o!3oyjjXdtz0W}juu@vrV-&HLTRpcwYXkng_+0Onbo()H{IwP)oOWH4~KJI0j zbtj2U*>2*_#x3`og#UofDu9JU7Xt#Y{JZix;lXa3Po{Gn6pq&^2F;qjTgmN*$#GT7 z69F4{tg%_p-IMq4`h!_(uZpAF_-(3m33XqEz*=X3eJdS*>|y`l1^Fw^S^Jz1N!z4jFoD|=G7 zVnfv0kEj17W(1{fl2Bh}rEeHiC;R=!*ZXWjwbGwA>|A1EdI`zBu=6TFX0t3w!SBfVGj`2?63?Gr+wEPv zWaY7!ou;qPbb!t{fVsC1WFQo?EV?T;w`6Y}drCvQob9`bGB-^l`0ZvrzV-6R;!4@8 zAJ*<(eZleCmLqp%Gv8EyIUiQUcwXma^!pD+Mv?#aTsfe78Ogn%IUT663@kcE#kPSg z0$=J*_ZxpNFcG|K6uV!OL8dLL|H;!f*(9!r+kaa&yjIyT>(#s9_G5cihrIk+Ww>hr zS521mIYov)n~}_&05cR+U$j)6==+vacX`d8CChJ`L`+}%dB3!Ru+zRvX*>(+7Vvke zxla;fPuet3`=H~Q7x&+=UOlj+-}>dYB3ox~{@`_sAnTPO`FJ9-xqH2GHA7x1%!nyA zYRu!j^iw)!XEWpHSG;FFZTnMI-n#PhhnXuG&C_NvJ@8yMH&jrCFMr-Gj`oASH{E;| zdo&ydt)Yd5?<8b%-xi)dQuiwUj@z>&gJT|fGp!8&91=7=ynt^{*oM<(OSrz>O?>&{ z)Ws?Hj-t$Er_mt=k$x#*qEl03HPa$eex(_ZZSXLn}G$i7~qRLJmx z;~B$Tt-L#nI^Sg0`qevecbcoGncY8vs=Gsl(z<9+zDfGgvAAhndYLv8`ceW(m zXO|0QVN1;MRk$v*x|d---@$1I#W{|>y*j(jVuO{~`kWnI9uc;Dn~H>x;|w`g{`(-SlI@pdg>w@>Nt_Sig$EhX&~^BZYS(XhMnscW^?T{ixew^r4F zy>^O?SY_jbgFAF{cK=6mFUStKx!ZRXe4At!^?QTK)DM&AT$P)%P-)GBTT7l!3i7M> zoO&c>c6DLq&4_ua$MZ}(Zs}MVy_%mVHHAeoanIWAPE{?~w6 zsBL$Zgi*|&e*GtT(!6W+E@{p?f5h=%!@N52%(bGq^R?6Ywn&r~l&Lr!`z#uF;)j=@ z=DQi&y|~^WnL86^C@9|k|F@YDI!Rq#^@z@g5(Y+}N#BfB8?WcjQ|vp#vv<;poedKo zP5HJaaBudclNQhJgsLqO5TF@E*n9(fmo#z=t4g@>Y^4HZ% z`My*A|2wwrLa~;j$MSq;AwiEDiehnc?;}DNWF9o-`CFJa{m9#n>rRuo-##|H{eHn3 zD^MQ@mM=hkd$_sFQ@3v7TGU^2##qJa^18##akJ}9-GB2vJo!RxN%zeMlap6n>-Q?*F3Of$x_~mGjH5 z^srY|_+7Iz;^KM%k?lWkZ)TJEb!k^}+S^X2$L0O==dG=@tzW*9;n17sZ{KDbfcn}n z_s)eG3JPDfzPajtOfxm!ovY#W)O{KFbXobwFSTEuPAq+JaK^r>wIN(t6Aoz9RqU3q zY}k-|zqC6ss=cF>#hK}N*W{M8H=sHgW-e&WIm~F5`|NI=*NRq8K6v?@xQ@$CzsZwa zct7+Os46Ba?#f-_diww4kmWaDPPlkyj$@u)YSF!ZruEGWb@pyg4cXtC;I50D9zbhV z;pR?OSSRpwvD&gc-YxED_KSWmE_RaPVBeb|nzHF^l%2*mk6$w{*!;;Wys2$D`-!gP z>-wpJ8=37C=GyY}?kyDH2Ceypxfe912shWc>raz-&1DV0{~lkh3(DARHX<;CN^$d%Of;-gEXXD>sV(q`W&qLrflmdXU5fwJYEet2MKO&>5;9w z;`eS#Z~i{z-|>9=DxnAEH(N~Cp5FA~@^;JZ`xRWQxA-r+eSrUU#lzI_OpnW~;@V5X zTc_xYaqXVs-d6=`JHgxwJ5LE@Hp{7>f~O^0HJm3rnjFrR*JHsjkb5CxH&2cWM}jo`xg5@WLKBMnp01$uM4j# zs9e+MCOhR0XigL6-X$Odp_nD4&GDDo;dZ%mc>;28p5`uaduQKKZ}fPw%>2$z^7mM) zbX7Y`Y9s{T=QxWfEl`r(W--mfGPscC=(-lJub1|-gT^vo<}QVr38Gk{ls`6jtX7P* zuW0TmDs*J7KcPB7a>dFI%&eRCeNTE6t$j72_jcyP(*NH}_a0t2U8L8v%xn2)W#*#g zYqlLb`y5mzz|36+G7ySc-rRq6FJ|VCtxn4~f8H&1a(z=zj-Xj<-a-Fuyq#;mUwBxk z_)qrUzT&VMbL{hWUWz+pb8n*YopY*^xw~R;!tB5Sk9;Hi@y5%o`6V@ z$3FA-k=dD!(_a4hZn3~xarMSEb($_#2bS(gYh5YWkRm0be1PLIkEYl$F_Vymij&V6 zCvU7o&c~oW7u?*WJyOixM>N-*XY>5hHRX}flv%wEm#QBvcKr3Y^?=Wj+4bkj-cKp7 zvFE>Bg=^O!wXe{O(GV@O>{6CxA&yyY^8tW~< z8tZo5`i4m5*BD=`Vvd6w9+_#Es?M{GKpqDNof8H(S3h6KN4EM>jhOe^D}6UzEPU@y zy~40`QFxkj$F;|-_2%E#O7~YW-jaQ7RQZ0!QT}N!wJ-9rRt0VME0vgdeeMU)8gQ6< z*T4(~g+p8SpIq-ndOEE(IjuInsRuLC&bB>T^YntrEsM)<$p{Uu*8Cot^d%%P zzT|o=^N}5)e}#+vwyn46UBCGjXzUwiF6cZnn9(e^bhZjP-JK$HLF(Iw$`;pSiBa3+ z?X>qdXvhjr6TCLzcKc7u6?+%6|IMln-%_!A`wy?u?^aB2f^V0a^d~K_yaGzIFmu%Ftp}B>pMFuB%f5-<)zfO7EB~MO%0fQLQ}kEVy(nMKD}83Vga4xqZ_Y*( zUs~(CztDHp6!z2W-L7rQDccV@D-F_~1odHHMzg#>e?Hc>V9KV$jW&lj?^a%Yqxih{ zb^bTcfA29`;=ZG{#E>gJZRN)`0$&VgDNJ(cz10(1dv$Bjrq8)+rX)qLJO-*GVD8-j zGZf_Bj&EKspDE{yJhK zD&+IFrv5vt(?e>4=O??fDqIDvA%K|+ItLDBG>h)yXfUv==uvZkn}mCur>*%-l^d zLqYEKZJssf><4LPw~dp_3PWz#aD8|@&v0(V&sC|dS2t=fp0g|Te=&XI)b?GnRX<){ z33e}gc<0VCpMvnbqkiGVt2>d$!9jfjn9(e2BX#zjJj;2hev#mu*Ao1Pm20ZRn~t66 zHa@v=hyUAu$x~NM-(dH6>nx?0J|#+SET=fOZ`5y=V+f6Ex@|3&9ErSc8??p^Zm#Qs z6WO;mSo3alnR{}5!ok?P73(sOtF(TLz2^GJbA@=+gH8PZ-kyFfEOaU0f{LSa(rk@G z^TYn%Mzoxz4P}IYb!6;xO`0MMNxwFV$dEOn7P|Q20}5*LeW60n1sRUt9bTh_IG! zKKXmmwzBWeCTt1&`&c3$edl~(a-hxXjc14K#{8o{4*G%iZNkh2oeu~#mVxE>ajthJ z4zABsjG2|BpM7jS?A1}^Y`pB^mw^9s62dLhee|A59Wtye$a<@%>G@;h+`BUl#T6yH zPk2(B{6aA@3whigG*$^WcjB?CpLqdi50%>o>;67_F6jRYnW&jEJvDr9++IGbbV+7$ zYkIZ3Ay3M}UAM98$MT=CkplY`1kK>tKPR>K-hu}LNbPFS`Gs(E8;oXuT;{|3;Q!XC zYrg#3x7w-Jm{WH88lUhZkLM(RUoqA5^0K=|oYHJTS3b1ITOHC7w{47$IvZ+xeaA)B z&&CJ#Bbf_22N7=Wp*fM8FJ~N`)+wmura0vjkK2r8v8>VIZi`!me|b_DxvuZ_ zwKYl|axQ1?NL}DCz2zfQ)6g6%e|g#@(SYazueSQn*AE9OGtOUi``ZyV-oyJ|okCvU zw-?#mz0V|C+4k{=Flt-AZ*}~bfBe?5s1HFkITZ%dO!F+?_!{WkSaEanB&~`Ye8S2< zI~rE6EnIVOp_6}7Y^0^Qg#c*nJ}f=#LpFEm{xeCbZ?v_E>abn4Gj%u-%g zG$$Ae7rod%Q)Z)N;xDA|1)cK)H`o3^GSkubsrpkDOgXMcxN|bNJK^0rpgCh$IDqDo;pR3(H7M@x zI<2kozi`_siM|_;tu~2S34H$V?!Rn;*{4l&ys|B7SPKq4iD=#E@wC`N*4+Mm-L@^I zYd8H|_R zu~=R&l01F+r;T4vU$Rcxz4OTO2MPZdNI%Frm~r~C6Y_qM!!ScZ=}cnngQvnbBTa%T zwybRUFW$Co-?EB>-1Am_35%!>uXMS4S-mbc&vDb;u11ezpSX5LcR%R)I``z{Ig|Cb zIBix}@DXng+o!%rqbL%r-7WT9| z!Kba36bGBQ?K*DnzlMFjjMBf;CZ7F!%r4sXfZFJ=@CEHrf}6Whdh4X=eBYJcCJT7& zTYkAp-s|i{1M8B>Cu`mP-!Wy_9C}d|EZO1F^2xaW>N`!pQwcWenJ+3^4{zw#4cIZ~ zAo4nM&|V_Ax$ibjIj#HMIcCoHQvp{D)b#F%U-x%?Ya|~Px;62ZO;l`BcgQ?B^?B`% z$7{ZK-~24Dz2?j2<8!pm$FI@uR{MDud7cb*jw#4&mc8B80k$DuGpw%H>$L3lw)*a{ z`#tjp{YO)xT|&y)fAh>O)bbV1WvEr2yYpYqM8T+*eXhw{zb^E9t{LXK_17Nce#;4v zfl$m6`&T{k-NcP=9*LaZ$232>OI3Q-%&eFC^~>LVc-wmZ&jk0jt<1b01=+6;m}}48 zwfttJ^}XQ#hZ6(W@Z??8*#91RTp1LHP-7WbOwQZe-~1kD`cQG^*C{(vL>DmmJQZ}T zxYioS$owVU>c8PWuC=Qsh<|C`F;m3QTQ+c7pYCe&Rr%h0({|6Y*1ZcVb7AoYTKfq% zx4vq^w-%#swhb-f`{l1XeUIUuvA~Jb{{hd-QzmR|&!i2kH+XB=+RC1DPq_29Aj$5x zWG0W20`H2oVQ0-5O=^(Gt3hL+aC5Wk&X$)Qc2>`AzI;e$YtPpS`y}37f2L=w_Ul2z z?4LanQF2G`IbRDadDq9=QdjTvk+t+}!rBLwttZ#0PT}~^iQI2F12YtqFT8ptY`R)# z*cbUA^QhaE;?s}A68CQkJ3hTh>)#uh^d)k&3u-O7uYTSt^^l*hZ=Q2poWu!JtE4MM z&v*7+TKr%Z^13U~d9*O2Szh`)-BejG6>&uQy@_eY{f8EQweq}5$DUn3Bp=(|BzPkA zz`;w$v}Z_n9rsx0Y?iqH$03c6Pbx#Nm^AXNedh{pI>EvhG$#o+cQ*Srx3-;ME^Pki z^}K`K=@esB9^b!Oh9yd(uUKkKE|%CN?<{yUOHyY~oJ8--yPIAXI8QHI#Jt9_`#@#e zvw&Do83r>Kw6_6n?v%pXXUWV-3>;TBgztS6a*9t*cd_`1SDl3iy6*j}OnE4>;qYrd zeZ6H19+!VkJDb_!*|9L=F{2OX$Jy^z@Q1DdoqYi_7j*6}+*~b#uixe^-W5<8wT1hK zdd>0&a`s%QS8I+j8I<%@w;C*6+o$jS?m1JcQ}d>ai>En6O5_~>SACcL(7rr5uiG0% zk=JWngc%Bow~gXmmQ5b2|9+GS`I(7YRQtbax7fOEYqV#=xt3)tPfN?JAO7~-+uS9i zSG*_eXk;B9qyI^(4)dBbwe~TqXG}w0?+F^ihZ)WCGilX^wR;~F3l(P^$+@8^H@!sl z6Jy)F*Mf0UD@#|2?Q57?rT|-fL*;ct--3x`*KAl{u^)5W&MCdU#36saK1oeuHKlKN{o~ZcynC!Y%gZbyI z&YnnNzSF$HvXV12Dm?QQ^7t=kOa^YQ`O%9$%{;3XJn+^N)mi*p{r|!xX}&)`Ug(&4 zsj5uNc4_FJ3#)`Wgy*nF>TyUpg2LCIyJe2% z$K128nSR9o*>>#Rr4W9Lta!!r{pGo5HupCu?r(W;`fXzR#yu+A9Dbj4Epgj*&;Dnr z4b!W?Eel>oKL(9a!_vbwWOKh?++D8u->RoKvM9aVY0-tXw^Z+%E^+&>lz*ap&5roZ zXMZ{NWl4O#ZGR`WjeY(e=dvy>(eR|Ot=<(H@l8`_-a@K}t|Ob9@>%ubr0B!@l)Nv! zi@(Ofq`GJ6XUpX^3vczG%`Ln=A$sQ7zT~|(cJ{=~5qPP_n&X~>U-hLj8d?8@#*-puL_E-G%7?jZi>E6v&o)4gE#u`$%_-h8Q2Y0 zwU^AecQxeNvWR0B(=<6OvN;M9hYZ(46WVVA=DP)E0&r&2spBR`cc)pBZHL)@{i-aQ^4U4E_o4+wwE? z#9Nn7-jSWcdhtl3)Pe9v%hED0pV=F=RA+)?k^Zz5iG2*e@9;hj0tw^Zsw)KYL{TtOPlw zzhBQ(de3#i;6ll=HQLty?(93jJ|l2WZ?>+4p>u?&iTC^DC!n*lVCFu6843#DwWaem z-!jk;d0cDt%ktihsXqgoo9pIr^2t9BNs}~LvH!Z`-d!_lz6h}+D!M#)J@A=SN^P=@_*ygo0rzF`KOjX zb;skE*C6W#A?Jxbh8YS9hxGiQ-o=}&TjaB>QcX)1>&sSq@6WKgXU6Yean8`}vE89} zbI%_8^iX5Zg00Gbt1_hG+%*K%w-pI*7H`s))_)C}!vdWj1u7>%bJQ@SS<*VQc6?Nh zY*mwdDCm7!U*_@TH&2gLbv?2xtywiYA!6nX@r!(4O4;0ML$+w&4Yz1cSikNH-}z(4 zb0_vXUiK~mnFTwK3AFYTZfmVl8#eUh=?Yz4me|q9$FthS{YvR8TtIkf%)V|H$ zlVr8$sm8*f)6*hWrLFXz9T-wKdxk;nA@S7E86iqOptD6_?tKO`6ci5KQk`2J-->%= zTz~8AJB6FUhh0)@{j84<85qM;xuiTVFwTD@qiDu*EHIS0?ARWbP5-6+dnJO8_bv=) zeyh*S4(h9b+zX1o=g8*XnVNd}Si9EYPxY>+zbYzUi`G#%x!K}?`mMXg2PZXl>pv@; z9)0QeCPS4o&%}D1|Cb!_Ts^V5`rMo9_nVIkWE}&|^@H3CGWP|txemz;CT-_2udHw#+G?G)b- zGGoq!=&7JR$FO`1n&U$562{zo z&lm~6iz@qD8xC=1z2Fm1T%GpYb~bx{S>uiS|GM^uhUe9qU0WBn_9#-h4%*WSH}~%v zE3Ov~vp>e>?OQA>T`tp+|3Yv|V{Jk87MrOoD#9l=r_cWT)h*p|>(6F^>UHxs_o{F& zui7N}Dg4y+;Hl}Ypt%s3dqHFEaC85Oik@?lTQAgpQfS4JIXjQOc=_NM7tfbBXEG0iH=Q?kgYgovQ0H*uF;2dhMTU7Gl3yWBn!z{hzmr>zDY6kcdksWA<;i4?b*k z;|~XW{s+MYiXgwk%mtm33pZC$IJztAS?#``5{F&-b)K&{n>(TJ_}a=VYL^ZupxD}y#2 z7mUrFS&6(p3e*OK8O@St+7Yws{*!2JoilTm9JX~{{XDIue#+g3$lC3Hv{!t3+_zO< zPDXa_Nw!&1Wvw5V`6pH9)~7wuIW9i`$1hdqMUeak8D9sjIft9usc-q_yPvTBr~8~f z_f;}~FX?sU+ZgP*EcJ|_*N#<_pRfIUMvUo>nnL=oC@*I7M6N%~vK;U4*4CUjq`z9^ z)ML=v8dx}h+zvPQ$13-qJdwehcf8)0doS~e!*a zZRL1fW-UH1?&8vHlCg&OY9~WVy}#BXX7VW4{N?58=*SKlQYB zL;cyFfZPuLvSuY8>y!27I=Xr3nH6d>31>ljhG6D?2N?*(ENrhs?Co8?9zCPSeIWW+ zH^ZV%wZreOUNij{f8?{wX|XqJ7PEe8I@iW}X5F0i*>e(hoV8Y0JiE-P@9UZz+u&y^ zyO8oRXssXASOyk$aS3S)mW?;vE?#Ol@p4O*(6QMGvue8TJDRygams79J^nS>cT?BR zH(t6sXD=yE7Jlz&61Xcfbay7(u9atF9)r&6fVmfRmK5CFtKylP5-zxZn^repL*4l8 z{B~cbZ7)|U{j_N?*}+gB&%WBWRQ~%tk>;>Uu( zJD{;YxVZ-$+^+r(e`KyO>{d!kst0&)UUh-_o#NgcbZmG=^IE?QacJKGg zwa-aQIeaQAttjI0qzdiin`^ddf${>(y}x0Gg7VuzbB?QgG0V=zd1R!%e%El`EQLif zbd7k-msUm99gE#qlQ}MNC{BK`s#N)2wX20S``T;Af)BVzUwy{Qxj?{89XVh8K{i*y zI$GnT!sFUIma|uXu>Mt?=OQnjXVQ>!^x^A`QXIzT8xAE(e%h-mG+pKRZ}x*m9~?fn z-hJB{6(!kzOP+JFJo0!SXiO1iG|S}PGw&X>UEvfyEulW6&&Qr=MbMZ1xzeZiy8Zs~ zL$fS$#pDATt5T)XMMVyuIb}SdblS4)?Y(Zj9qM~Hw$#na0+eY-8!`%AoH^1zH{@eKQM`*2TrY2USJQ}6BEwN>NHV}^a2&!w^hMI`Iq z7q7oxdi%rwl?mT{emGrNH9NO>Z}{F@pLzK1*YP>&_ZF#v&Srp_3%*1PW-t|Kn7NFg z!5*l2ED}l@p3IWdzMQ|o*1pb^Pd>kXvGVS8#jDj15`Eve6ukRC$6%>zz{S4UH3^f> zMXL1X8oZu8YuoJj1+~k+PxZP9YU{zwWkNRh$Na!`ET^n83q=)>LvkM!gJWhltOevR-n(A01l|NTv)9gu}N~KBcB4+M9r{Wc@D?n)$W-c?bx$Y5r z1?)wp?A~A3#BCxQGx_SJ*VT8oEw$hB*N5wcXqBVz@;{TfHblDldHgrBdV9$9P>4jO zmbzK4zn;PI6ytsytHT2tEsP72H$sB9dqqSzZ{z|XU5^`U+ea%ai0FQZ0G$$B3rKQR5%fy zY;z%^{Mzd`yTZfcTV~0c)>OW_k)8mWlZTlLyO#s5~*f`)PH#=sPusg6ZzQ&in?BcUCHI`Yu+xB7C`n_M_i9%JSE?&aG+(&4s|s zh23ujHdo~R#kM~`x4p42P+hX*%yE&W%{O+-%Nt3*)9WeuCi!RSZl;*1!%9z9-r3jl z&Hrll>=h2o!o@p89eJlrJY35lguEYz7dae0s(s)4dIq=FP5q~i+b^B0opCzg>(qTK z|7^+K6Sj<3M9bpN!aG{8_T@J!#iXyx*dP$GcgAdm2S#&ec}@JQQq2N769(p9*u7g| z_wFeVx_)7Kaz#PM^^~lYws))_hitzsc*th9wndQjv~{6hTNgZA*JZP~$LXA$0sn$y z;R`>y=*-&9;qJCrxW*+HwB{FPF6jO)keMLNlCY*F`*p@K_ABnkH)$}~C4KqFS^C0u zPhiXcX*(PGeYEG=DOvtLm&t6X_ETb!aCpUA{`NPHZnGQ`=XB^C;Q9tyQvx$r0684; z-^QuD%yGZ8M`l*W;mb?z{+MQ&(oxWJec|o*m$oQ}HBU90+Ou5z>%qyH+k5u5$7+-) zXRj*Lyz-#f%{yj-_;%#>nIN*c>tj!pN|Ne5RHbC$3jT_tz`OYoNHeZ?9c&m+#>&40s z4^^*xLf(HNifryq=4YSWbsq2dc>ma1#-o$NWtMJb)3CMMbNbq}#H&>&Q!ky&S39e= z_M*`*F4eiI59W#&t!DZax~=fG-#Y!sn!8%wZ&lPLmq%KvD|R>q)c18gQV4!Ok==jqgPYF!XQQ^fI~G4L zFJSe&#omQ7xATRStoXOiHAXU50@>WeS?1bjC-HAdI;G2U#f5*7(dTzwamz|3C|@$N z-FUn?^m^>;+fOffyt`Fop1^!0T_C4$_uexT<$PuGx#l?Sn6eGYTuEeeOTroYrd$x? z;x;ze7S3wN{b9EE?uq)IVY3BJ_+>47qrY3+dG0K`HQ(Z9O`UGV;B>uh=Yxi`O_eFr zWs>K{r|E*`xMAr*3fWv+(N?)9Q|>-^>oEEKoNLyqC*`(BeAzyEoz=l&n?G-=9Dd%M zS+mmpsKQ63&8)AKK3aGleLx zuQ+yMPu+u+D$kGdy|JB8#^~XorN2{qrl97%mx8;H*YC(8hr{#FZ}eBR*);N?DHz2(c)xiIe=xrg-Weqj3?7Pi|Jk8s6PK z$m7a#$mX(KoE@?K?!~A4nLAQqUDGbv>+SmDa?+x5(U+*dcUBz!kj1#o^MwA+TK|r- z%$sL0ZBAMy$k4LFO*~+Ic}M!^>!7uQuyBw^Hh0Of;60tfSMN_2$v$kgO4jmZ;YHRZ zlQ?$y-je#nu&4H81Jjzn%Z)FmCK%=JW|wPZ{%`HG&HH~?n{cwq!k~In(4H)qxuE-I zpux+);^*SE{dGv zt#4{#{-g8zd~V7#hE2Z2x9_3sI^||%C!0?j3QEsQPt%{Pcy{@8$avpB@Of>ZdmZ8K z-SE|7XWZSS=(iDb3Jwb%zVLfdYjBeAmQRdZEIo6aIposQ)YQy5uf-AWSw9EtLe zEalm7>#2po>PbC5f{&B_mU$d{lAqjq_Fyw7Xl*Xcy=utj#$-?Axp6&6<4wR`{pl+& z@ozT>l-?}Fp_=oob?0Q;=1K$J;%TAgDwm8t_xw4=|K!%$wA%}(={?W?`?vUR5a&J6 zSO&~ob!2l_E#181#U+u~tLzNp9$a2yux-9yi@*VqKA|c5#2Iz(@J_w=XI{)ft zx+a=p%;L)rsEQ`$*++F{O!M3FD-?9b3d~#$WOLv0iZ!MkgU->k$=x1P;4xpxY<@2ybCKD7VaX%S1-@Onwh$}Hv$$mcO>BAdG~ zvUn2j5fcul$vJoTZ{j?7|3ZMB@lBb)Lo;P0`!zSH#&EXzuI`bT8`_{#Y?rq?PtCkP zN{#i8X4(#`KU{Kkpglt{_i7=V8*xVM%Z|XH{%L_re1Cn(6SQjkd`Tf~MySV4Suyt6 zua4{&ZeGpPT)n-YiSdn)Y`GQp$+#MOF3n#*_x5eg+oFoR9!4A4+!v9>JGY!p$vt^< z{n-!&)e4W~bL%pWF@DNWwLRJTzF@ch!mxI=^>3bwo_PKBy8Y_nxAKJyuifV=@84SO zdN(B)c|KML+1%r<>7Pz{{O0bw@@~>Q&fA$BHue$~CKqqNS<+#aD5j-+gkPJ zR{dBM*AzDIBm2#{nN!5W8}}^UU#BT+GW)xgoXnZ#)3yJ2b9P4E4_o}?ireKGptI^= z=ISAvyLh&c%|eyt4<{UMwm&W{`S*Oa-mc=%=P5z*x)Qfd!wxR@~Kx({f+kr z{lY@FxbIWRd-Jrz<@{P3y8w3Nb$I&7<~Hv<{^H-<6BaAfIv5zTvZW@h4e9J`oBZV8 zE~c~BzSneAUH@%A=eNB3D)kd*R5)1btpc{65ZE%sWqnW!3)5*1UmsvXeKUBFMt?3d{ZmyddzPyFk()Zg=~ zsqa2FujdOFwVZXVGv+pU?^FnhKP5PIU5)r%u3#C^nVv9njgZYXQ(3ok9$(eN4eQsd zO0;j9{ZmJAGUvjpY?cZ$|AZ{}7uT+Gd%tRjY_O!K#i^v;MKi+;IM-$!+x)Nd^x>Y} z8h??~uQ9T@KA*x@AIcJ48ZkRz+Kk`N?URfaw*?vRzo2&JW9_faC z$CjRd{=rVmL#f~+S8HVX@1h3^a~wai&5_Ft=db#(ZFDSDSd+!y+6?89%?_S{P>(0BFtE%X?X0yljb0xtV7SI0p z9VlaT41K^KayUjKn;mopG|atb$maUZs=Dy6BVfYi9k2F3oi9^%(pc@q{X@&o`FVX% z=J>5BB($uObFST_6Z7~vejYPwOy<5G+q$w+Ge}|!ckMf!63BcZq~0_~HkV6hp0!}` zpJ_$rGq;?-d_SH~h(oB#Tf_K@RqAD~#nS|iNF?}Q$$S$S$bD(v_O`~YwI{yU)GUwF z_f2@G9`WcOa(l-D*<7O^FAS6l1Xo?kbyIore)+7H&0epAHrlX$o^wa$i>=j8*M*a3 zD4DHdVe(kf`gqx$=4W3X9?9ByvwFYbqGC5E70{e9EF3`hG{fup)l<)(y45Ps)IK3S z$)M}Tb($Agb%>F-I!4?>HcW-yhMziBWnJM9E-+SaPh^+`ODNt8j)jUV&?W;n+ zzmlM}qcC%=klp)imGV_lC5^{A9LnC|0gWBsSgv~;cUr5@xRb)r|2j>g`E^0-o36Pc z3H#$MuJuh>p!=8mXzBMHLyvYx(~tkAfcBun%(X@~_Y%vBN164P>}!%M)0-S!7Ty2c zrZ6w+*bSwyx}B4!HXD7?U25>2Cq)0{A)mkEYM&CWu)doz#pAA1(uZtmzBjSR<6Jh# z=1wm28EI7DZ#Y`KtXC7v*9kRLHl^2eF zj64z&x?mQY#v+^3#e71&WnZq?iLsb`(wX<;q{W$Qk&-v=Z}yuVYW0stWGVBy2{!{} zj6C)iI`ltOUky6L24=23vbhfo-1&Oir=0b0`8cDfC+-+W)q6d?O1W~8rKP8J3Pg@s zXK2ka|NF-{e$v7DuT=S!^#1)T^8NFyyMpt?8Vlyjpt%s3xemzYh8_J=xk&ENw>6g~ z-**+recfXb`AN>g$}LG#DYEJK&zrL@%(2oa(~44^-^!U_8MKFg@%z4Kr=Gf7*n4!e zwDX1`_1_$k%`IOSZ5AX|F8wn9gIVU**lCMah0ojG_ar?%bnWETrW;yncH9kfvE8a{tW<*<9h>Z4+&tOb#z$SGwnUZsvxN-CuYT ze&zcAjaEoHRD&J#yb2ogM`@q#HYy_C1;Kw*NNM+>N~X$m8~~d(T06 zj%8Yij=`2Gyv~;&cidg;fA0RSrSsGuZ883r&h>2ObPbO+-CDmRkIk8Ssdj&k{np71#BrH;~UEazS?Qg+=zS{XQ-hnY^IxSkGP8TD8*)eeLqDJSwsl z@Jvx{{}Fl0xkhVdPyOy02fl81f3_rjN&Vq?$*CpntgVj{ozH>R*1+NobniOItsu;j z_r&euFUPHdmwc2PJl`$k&#T$uJzK2BJHO4Bd2yj(;Mv0&e8r3}zVpn=i#c=Vf#vx- z4qx_qnOWMK-RtqcHUqgH0^Q>dG82SZo*1U=>RP%#dWOTAqEDTztdIO1k7o$`o!Xx* zU$x=T8m_I2gs1#C(!81BL|#pK0<+bnMSOGHcHU=5jLl`qIi(KT;{dp8BN=byPHwPUG8gn)V2$NzgiCRN0}s+BAVoNC9z@Bel4bw&M> zT`?I!4eht9MQtaB6g zu6qAwpX>TNDR*ytztXb)yUk7s6ZYMryE={T{=7c%))BvXF8h8+-q!BY(d$#&P`aR$ zZ6XJ9yV?uc++{Y~ayx=HJ^v6Dw88fL(z}PNe{*+ES-0-Io2-iSQU|lAudM8of*(Cf z`;*105~9y2zO}~e&Ak(!v)sHH?#aAG9*^=yHrIjS^VEfRKBUIo^SRx_|4QphpsCPPuI#`cHZ)1)1k!ZPTjIFD_zB+*BWPEAjg{zvbl+K zj|FS~UwlGdV`lo)x(Dy2b~G#RveaHE@o82@wvL4Css>Y`OUw_pTE93s>GJjq(l0OU zX#H+m8W*+B`_7*S%aGfBzR2dzwRlx~z*R`gtF&PK!-fkH>dy+cdCfkXS`w?t)D^s6 z>!@Vd9rwlmLl|VAM*V=9b&ZY>Tb6!#LT^-$pN;nK3c*UEoQpC~k#2-t(Ut-Y)RAOSPJC zIckfQZJLX)_S)s6W7JzK-szr`le5N+)O3wH=mm~O^dSP}| z*Lvw@QK!iN6Es-gyv+JON!n_zJ!?+S!R@*8TZ8vG+a&K)+ zIh^zZKj`#s&GDMM%-$Y(ygCTk+&|ZU`|DgjzGVAJA-|jlqVMljtv{}P`|;ur3_Hv2 z8+iOZ=)ffxKc9C&O;hmUKW8#|W3`igHFnh-?-KWNEIlTTy#6;B*<2e{2d6V)RoS}& z@8s|VhW~#dTG`db)LzbCDmwdavOLf7rpFtYzVWxqtD6Pe{kS!;U|rL3sgTSc&sy5O zIx1!(j~j&`n`_&k+iJFLcBLlRvXOC+_-VXTmTk^XU=i z$L@uJ)oUK7zFIDIH1^8Qmm9J)JF>6P-Js|xazD(KE2`m;{WDOtux-ttt)MA zeJvB*5zqddGx*q4PW#C-l(O8pHobAO+z_~X!KK-^G!K8?wJCJA>J3GQbJEEBB*Ku* zofV(y`e5Cib6Y25W-YYbwku`2Ur6wdl#ds;-{^nc5wK{+$%8x1uFS4{9eVfMq~Imh zS;qZ`J?zzk-)ndEp5AQfDdS$XDP$L zz|X+YFdFByi}O*Fhj$2o@^o=(Zdqy(14EW3ByWSnic*X7bIMW~R%kLXa1VhxV4z|N z6jreCd!fs~z&jdN0~Hm+-d<2z%PQ8bOw7$;$TnkO;GwveAxgq>BHZbRKGqwXIKfzc2c4S~@R7!85Z5Eu=C(GVC70eXhOXj7V= zp*^JR8ciBQD!N7;I~oF`Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiSqLSS@KekjG(s9Q%vfCeEjS_se} zP)Cg%4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GZ|d2<(8JDjx|M;^49= zE=tzR%quQQ%*oNq$xqHs%gjmD3n@xXwNo%MP$&9Jpa4D#pMe2%KMzO@bcTH@h{3?X0J>WT#AIh+U`PWo7#J8p_u_z<91IK$ z=^zFJ0|V$z8xRxZHrTyop!;Y*Vj!Qu?i>T%B?A)UW?*0d-9HO*JLn!5kQfgG1H%;t z1_qGZL3g--#CRDP7>YpmWil`@fbL%biSdEl0QCpx?i7$1KLZ0pIU@rD$nBtePe5V< z3=9mtAO-^i1L)2Y5L1wWfuRt@U|?VX-8TYag6=LTVq{zP1_lPu9jG9^pcD&I2TB)985kHq=0-CzFo47q85kJmF)%QI%#CGa z0AD$;#K6F?04f#-6;ozlU;y0#3$iO7DyG7~zyP|B4`f#Y)I3$Fc_6!zpz1((lYz_w zi6ukD)EO8U7BMg|fb36!ifJ$~FzjGpU;x<#O0OV)fbLNNsRNk{O0OU>1CWOp7#Plg z9LK=Gz-3cvY-Fckf-BE~!T_WX}v^1d0>TeHEa10NtYsy7LrtFEZ#3W6*uWpu2%V_xOVD+y&i_3%bh|l!wEi`8JY) zfdQ0PLHCQqFfcHH?t2B@6&Vjow+svnpnGV%85kIR7#J9Q85kIxpy-4P0&487Z?~AE;2AMTw-8g zxXQr5aE*b10d&vL4F(1V(0vo2gd=zb7C1_lPuJ@NsdyvV@7 z0J`(s0b2IhK+`)YeLrMmV0gsH!0?!nf#C@w1H)5B28L&h3=Gd185mwLGBCVkWMFv3 z$iVQLk%8e2BLhPzBLhPjBLhPPBLf5IzKd!`28KFD28Mb@28ISk28KpP28Jd^28I?! z28LEf28MP<28Iqs28K>X28M1%28JF+NV(R>$iUFg$iOgxk%3_nBLl-^Mo8I~!^pq@ z%3q*z6;u|2$}~`U4=T$+ca*b&%1K5BhMx=!48Ir{7=AM_FuaGBW3L$)7#=V%Fo5o_ zd&I!N0J@*)6sT;4mT91SFRK|C7;+gH81fhx7(jP+WHT@@Br-5Cm@+Uhm@zOgfbIY^ z1l2nX3=E*WzK)TBVHG0-!&*iLhI@<*3@aEJ7}hf~Fic@&V3^9t!0>>Pf#C=v1H(~9 z28P9q3=E($5mXj{@;>OkJ9bbx%*ep-ADS_l7#SFt85tNrcSM5j>HW;W!0?oTfdOVt42KyQ7>+P7FzjYvVA#XJzyP|l7gXl< zGB7aoF)%QI?mDk$U|`tJ$iT3Zk%3_gBLl-$Mh1p$j0_A*7#J9qF)%R9XJBAB$H2g_ znt_1vBeAw3_BSZ7(nz7P{9HUKTv^0s4NHNIR*v>P&x+P|KtJ9!=N~|f~HqcSqLiY zK;=28ya$#0pz~$Pb{f0+kJC7#J9~K+8~2odhZ; zLFFZ=E&|m}Aiu%tCr~{Gs?R{}QBeH`s^>x&7#M<}^0Fo5gZU)iIzl5Y)!20OcJ91_n^M z2r6Gd_bGucascJyT4gP+0-WOQ13ily{KJIZ(L(QVVMTfy@K7`9S#%M1%4TEH8ob7N{Hnl^>wH zwLtj{beG!(1_lO@nH!PH3s6}FDnCJG7bxsO@c_z)pf=ZbX!~v}wEb}qnx;VVpggn} zTF!yO3smla#6V>ps5}Rid!RBDWIo7TkXlgL2unL4F;JNas&_!;C#W0+l_j7u6;wBY z>U~fd3sMJa6M)J)P?-qggVG_02DJ}B+Z=m7v zmH|?4f$l2>w?;sRh+} zpt=vF7NiDbHcSnuy#!JRG8ZHdQVXh|Aic={|3T*bV_;wa(I9vHg|>G-p!x@7k02uh1E@^`Y8Qjr$DnpGsC^7;8_O^|(oh=IrUtdGL2YeN z-wM>{0^Mr}>W_i$F$T3ALH93%?sf*<8x3mvfbOLRwJkyYJy1Uo)b0fJok49QP(KrN z?=Pr53Mwx^ZBtMi71UM*wOK*!P*B?))P@JOV?k|FP@5KJ2gn?d9+=&rJDOo~Ap2o< zgW9*Cb}q27gYCx`thKCHmKbO>c4~XJ*fQ#>Z8NT0gxQ1 z-wkT#f#gAaP~8pccZ1k43=;$Ol|g-IkXvDH1i1&~2atO}{ajEOfch_>a0RIYxew$g zQ22rRqagL5@B;O7Kw$&Y15yu@2k}9D8c;Za`aU2zLS}*b5xC6y0Ilb)7M)|Cxs7`4iu8C6bEV1w4!4WLvk%D~W|aZjYi_-^z$1vXIr z0^RWjs-Hl204$o5v!d{@+By!#I72;CJtGDNP>6tT##wJObKj$*PfvklEc8tDOc+3A zn>+(UL*Tv{atd2pcOqm!Eg3-uh6X16$o9Mg>;fEYpt=NP52&0JVPI%ry|eap^`vW^ z9BiQ08OVH4i6zXy(7^ZPd(^s9EEXJ$aYlLuAQynj9MIiSr`R<2dK)x;;b6*1EJ-ac zVPF8|Zjh<|9ovf1FHM@y!5C+uX9Us%%HvuL3=O$Ey&t|Pxi*P|G0q%f8tisE{z<KQYDQY^>?^Hm#kv|Ce?I2hwh^$he3 zAZ=5S>$}xv%|E~J#XJ^9Lp`t-aBE(OfuSM6p21rB{JrG}nd_i3gn^;qCcn-3Ll0Qb zB4j{4Cs4Rad4`FnGF_g8kog8GCm0wSgkA=%bgh{lfskQi0+-kg-Bm0zuDBV?B4i|4 z7#KjYy}0T3``P6VmLM5ZJy7dMj|HMfMe+K<)7#hABV-&|!0oq&*v6+9q3a#L81~14DynA1|NX&1@-zOfA$jb+(v}TXXl{LC8#m>S5iM zUT9Js2lgq%ZOfs0roXQpJ8bZ%*cF28%PfyoO2}#^|hY6BG3>X;LIUuL@uiyU9 ze94vXf0#h27aTt+`6apqMfnB0&Ej8+wxl0n0>zvmDCRPYb@P+57#RHbuIRUW>G=p@ zT5?8W4yY}3{&Ay$k66qNCdN1uJws5cN=Yn9)lJJQww=`H^4D?SU$7oykVQH9DT&1y z3=9VzmD$d<^t%GlW3FdpSX2SIDq*UQFl(2A&0Ho>O0>{3WMJUqgxr6@r6*GF;Tc>5 zb~RW=k`r>;`Qks0jz>EA^O+b8Ac>bjjT3TLhwI_w?S>}S?eY& zsmiGan+DDcm5D{^ps|7e*N;}2H@y1|k}=ma*E0g0l3faFJxZvzBwkry6aY5O0AyNf zadKi#VoK_toWp;8qbuT>7~_of!1feyLT<`hpSk+#8R30m;4m=IGi6|?=LC=MH9WZ+ zcs%p1Y#dkytY-ozBFZ$4uInaY}y>C z9^3Q_DK+g`Dj?Gg!A^+ef`r?fl&H|VHV0gwt~Ov`Na12&5NBX$Sd(wKCFfo8Jg}=l z(Zf*91-Vy3R_USg0ZyfzOpI}6dSIU(gvvxQ&bjUOeSQJd1%?a^I9zSOfXB3{Jdl(a zdijk?RlYzZIJUtqxWNN)wV(c>MUtlui-5xbEQ8e^a2mzoY6AuaMP5h>Nh(yab>QZ= z0EG_3KLxxHpI(eub;0w?{HIKeb)aA~VPMEeEY?lSNlc%7Cu0|H#O+fE7nBs|WP*AY zZu9T1S|q;v2iP>Q3-nCD;AyFZY4{V+4}o6o8Z=JgY8Tuwt2$!NeG6pl4{J zXTX5frywoZQm?rlSO&M-!~`KBTKy*dY=_WmB_>8gP+l-%V2Bcggy=;5s9v?Lww6qc zpq?IRkR%nVXYuJ7mGiSMWg=wq1R*JrGvDUetH4>i!Le;*re}i3w4~yEa2k~tI$)`H z=*UTM+A#u^Ay`TZQ$1sl43>OqsRs%ihLWPhWY7&+4{y1>NC{Y)3d)TTJ!OeGnMwI2 z40rzo9?8s>1eGBMdf>3G$_I_(F)-+wJ$~)(QhyMfu8oZJOc;{$bMlLd85kTUct~hu zr>cTN2W$-kEA+-iaeuwd&Oh!NGBKLynS!!51ME)5{lC+8mF=6q8teiCJrf3olFYoy zlFVF&KdO@^iS5ph1(iGC49qZ71d`swYdKXK^K=ZswKF*8&}(!ic#V!;^0C0<2fcO% zrz&t=-ysUAWn8}SZ(V(Ie;vqekk-a5QOGTU-!3I6_jNL;BV<-VWde@dOj-767APee zLiFr{%H03DLY|XPVi!WsDN#sSwePutO|e@74?-rjA~hM*!>)~tluh_DSq7X!!1?qx zRF8CbrHifYHzRN?80c9tFuW0kq^dVRlJ3R}bC@tOLYoH+KSUwKUnEv$iR@Fms$e4mLc3~ zb$qWN<8*L{g6q=*P*?8@$~?^U@A3p_h#E66oEC?aE(M|IBc6vB)g$c5&CJbA)=kS> zAM2vbJ$=qAu#6!n&lMz=Wawt6R$eOJ|L9Sm@PtsEh#vL!>k$M7dsu z%i1sK`Gs%+mXZS0rZ8Y&P?3SO(vnTKRal)5X9LA4B=zD>qp;g5*B5%8(~CT}9_j)^ zLx$YUyv*Fp;*yznr)&#sdu9MKP0t9_x=n$$0`^{VS?6H#5M&RiMQp&pfUE6dXquav zUk)lyr~7=;&YUax4pGw<7vyA?7%?y$G1FuE+Pxf<-i#qF;*yM_{L=Icqr^r|x7FL0 zM<8mM|Ij-&Prqf2a8%oG#>5C3Spb=znU|4Tlvz?NIO~T!zCnHcL#^}yvdrVHYr zuD&bBzyNB;7g)}}QmrxvTv9;vype;rAcy7fwfpP#YYv>>sd=BiJ(#$EWw~I4{1gJ4vv$&d9Ujp zBJJ49L;MpOekL&WO`QicZyPc&6s4x5=9Mro{4;+ak+IWZ6+(}PJR~J1pJv~|%$pBt zsX~1kDi6uq2ZgM^#`{b<4JlnL^b8oVwYUuQK+UuO1&C?eb(xp)pFjKw5vQ>Vkn$ky z-r>$on^wWfL<0tfl?sq@Cx>Tu>3WX~pz;7zaT_o&q-Ex%=#~~R@IA}qliC_s3^olM z2t}!hDY^yO>4%)&zB>MR$4qcZ0ggGWt_HW2A1Z=pV;erztaaEQ$kYZ7Yj72aB}9$% zOhF~ZKShY!GEC!_>Fd0m3bqHF>#^En2GUcf1gSB+w*G%`FXllgG!_gP7*dOh@{4p+ zEAGC0)^SH~>I`r?Fb0*^{YntGS$8eEW2E>KRBszW>iRWM)2eRE3!F*Ttpw{a1m)9( z%8=OpmN%_Qglm~HIDU*krs=6b($4-u_1kaiy+AEUb3G$a@sFi+G14;u^;od9#fMLnN}@lH~C%*0r$4hiF4b@04@!|V^j zs}J9q$qUU{h71gx8juuU<)Hn@ST}?lDr3OFAgTc=$*tD=#6I_REJ4WBXh7V?ZlBY& zOJQFM)NKX~4BZ+K`_xXW?A-t01*i`Q4N=^t;nwp?6H@Lh&MGUd{1+_?u?N&3{QG6*ahJ7pg^~z2sHoD z7_Ke8Y-?;9SO#nwmUIA0A%+YLSVIR~9$=<}ICDt53U@lt)PtnN4@YVX=3GB>8C)-b zT~L{rlapCo!tiPOhHFwaib$m;Zas-bMVV=p3=Ed#LI=e=R!#w@QE+W$pbtrJ5^_$_ zN{yOZ!7|{|G8O^&NELzp#y1)IhcUg3pFtGemlI}={IOh z2&xBnD-BC222Iz|CJ?vnub=Vfp<@(ibj3o?7*zVFnLuicgtPy16_leutxl*=^B0PR)o>c)kQ!rs$9z5a9na2! zeF~0kEN+9Q9n4;69MnI!U67as8sBGN`077}&*6?2sCQ@q8rn8sNU?;}W|Ai+a^7v- z02)7ljtv%DLel8X4z+)F(H)>37}TD2OGtk%TKf353e$g(UK_Z_Us{x@3mPF+=WRF^ zqum85{lV?)g3`RoUA+5B= zd#_XdFI$3o?@*c9P?>psKPyie9QcOF+qlEvwKZfkV&|6UIs8X{f!qc)?XNY&Ki6$- zqQ%bI9YUBUU;_yQTdw#A&u+15AY^dcgL}*Yx1P5)kaTe7@S}xKPOkx#*Cvp*mQ1{gY|%0(Ym&f z5N&RZbkKSG8ss04J%$VnxZU>Lj)6fARF|C6vOe^$3Dh@(_BCO52)`G&IsHmS1ITUA zGzz=%8Lm;?vJH^Y6zM%GGxHgKZWK~ zO9x2moly0PEw1w`Gs3h}4v-w&khI;~i+vS%>tCiiL29#$W}BV%pZgSt zkXh>l87JreJbjwH-T4IwnIlf%wXzL+*Lc{=gj>u<$lQh6^Yo{2_7i1hCWOpqs6F<& zD><@m$-PI&a63b4&0VunCETZE9Y)BgIYa8X(0+%ebzYB@5i;m=hfFx<4iO`bSV9Nd zV>#>!iKn-%!DXI;HztE}qX}r3+VHF^q^<1i{-cnOO9V8E11hVGEimdiCPqUGJ##%n zGlrDZwAAbp28M9f(h}}V3*wjBS(Qd{Vmf`$_rcDX_FoXI40<1Ww3b3o&;#^!ovhFHp8P|*wO zO=0G3Xx3)H9Njn416LRfrFkit$(alc^=H!TIgB{$K;xE1=AeqZ95i9Xz`(%H;#s?+ z&*n4}qXj6`j2IYBL;X{+?atL3_mx27JBE7Zpq}16Xc(-%nf@yGN4Gq<=V}ZJgwJk} zJU1(8@sa6!JSKu-0W^ruz`*1Vk$KMj$3Xf1y>sB)2rlCV-63`G_Ki=&maA@n^fACS zMyNX^tdpa5T=Wxf0gW0OLi+WXAsPn_(kypKFK4B3+4n1+a)Mxcz_kqSu*M9xIB39O zhE5zbcreoecoy3LpHFdz4sM@frm8q-vLeT)|J@;ZVaA_1kIp<=%K?rba61uq7~oDj zxc!4WbUHmC>0sC0tU9?Zg`g1`P(g0Mz);}@$-x=L>;C`ZU!5renvDhbjGDY4uD)O@ z5)ygm3p=P~2dYer@Z=vUZ%FO@h}YfkqkE4KxYcP1N~5^b0gm>LF#|(Mv2Jc=MQV{z z`RjEHi50Oh-;G!R-QEW4A_zpS>X=dXL9oJ&*1Z9S%nDjHfXJS#vo! zQlcRP1MadnFFz$!w>-0?HNNe}mCm1P;Lrh&r7`(HYV;THSMXi>vZ@r^*96Z%CFK@? zTZ2_f{FQF|o14L92)I1(^MRy;Ti5ui7KI%Hjg*){YUc79STMvhzhIJ^30k+xz_6z3TBml6(@$_LfM+wuFrHVTn0Vy=}-~$t5UJ%6ZBEb+n zsee~n$y(NbWnu(J3TPc}YEmg^B~@QDgULgwQ^%MXA-!M*hFoY(yJX9et6Q)4doVE? zK^B`Zq~O$(n4FwiP*Tir+E>vZs4z#pwG?RJ$Ltw1g?01a(m@eimv1+BzRNi6_RaGUgPaN1*$ zQveDBaA%i6I|R}u-?HLS6~FnqFHjd4F))~fKvM5X`&(!BbuNG(YO1vSI& zbjjq?j61c!GT^xad#Hc1qV=QpUS?4NxeYvgz>rpy3R=Fy@cV#J`_Y8_xe)(=TG8H6 zdwgf^Gk?t0vku|5unka~{0EWQ5f{eRjZ zw;Aez)^Rn2K+;?8k;QfP_ih9-G1h^~Dsu*gLTIWIuV_xoo%{JL6QdcV^Ulx(4Fk3p zif4Secs7FV0nZ3v&YOXz%|NT^i&OK!@nd%Oh1K$PFaCmE0Gcvlm%XF3yx+|1lm-IOGT!$+5V{CjL^ z9>^Y3u(QcYiMZnwcZg!t{A*tZ!V7eLBH+~p4D zj5H`?fy!)h(sgPCq#V85b8I3v54$eN1(4K>t@MYKd|RR*dU(ZtM1AOa02;pr)l48K zL_^9%`?ybKzntfT=H5Xv1`G_G(U8*r&3fB~B3na2V>KWdBL)VsXh<32TIn=v_d!?C zd=_+m4)$sHNfLG#W~pFWF$jG-*zo3XRBceexB9&ps*HVvx`Xc;l~_yPMgom3YX zFfa(kLDE6>mur^ws|zAPi7W`&0CY;Z{q&J4I~rliV}R8Up7;)LlD-(AmgL8B|+!k+~ z$6cS26FSt3ZBTo3Fvs=|=q?qDS+LzI$cKJF#=Z4iD@sy}@)C1`ONuh{((M!s5gS=7 zP&TrZ6hnH42Y!nzEt)bxBq=jbA0)eq~iSK?9>tmn5=$Meu*wzSU0f% zwC1;1KP9s`Ilm|cp$saZo0MPTkd&F9o0?Uu52hWG^K)}EOL8*vO7xR+GEunsd1;yH zy2<%@WvO{3nfZB%IR%Ny*@@|?#rlZ_nFzx{Zb&T1gi97@Bo?J2Sq_qiij@>6gH6>h zE-A`P2JO3X042d<{iM=7PzaV56{Y5t#xl9GaAD}DW<)bz~alA=nzyn@`UV!iyLbbW*deO)AuE@+J3Sg$0#3T#ekQBpoW zLqPH%{UBNw)cMx~>jm%n(M?G#D$mT*H8VlsCKlzwL~=6oN-N;<5FV0rd1hWpet9um z0gMk;LdaFRD5~LZL=i=DG111EnP8Y@W$g0wcQd5(PQ*=?)!9A{QGyXDhbHIe79>ZX^QW+fICC06QYrd8rG2(~RzUsoT-1lML@1MujBY+BUU)rT+;mg3O^caOfVKAf!! z?g)UKo03^vq7N$NkSlZig&nd=a4_U0!&(8l;QhBCbBi-lb8>VGOY=)oiwhFV^K?PA ze_~!racMz8eo;xWE_mxwNl|L5Zgyr)4jvDHcgpDN>Vs)rL-5u`kPYdnCA!IpIXS6C zx}dY8zEd>Bk<4z z$YOj37+U0kG#Xmu>4K(!EC{GacB8&7xQzvKvaShe?LA0;YHog3rfyMcdTK>xv2I#v zP7c^lx}e2Tx&`@}c_koY3Um|miZk)J4WS5X6GVqDX!)ff$R3b>h&6ajK_~*Hr@UgQ z!@#};n}SC#4%b7{Gd6$ef~TrLZo^|LQt;^O>Lc-VEkIL71axC08+~1U3^85M@Gv3Y zp`}NCU41kWaCS8%;2zk{TaZ_vn{ahOYdi?qo}80enhDB{AR6p*u#WPgL`XE_2|&0S zeO-Mx8(cVo%Oh}%7nfA#q~dD75jn;BgO%I*^w@n;c>K zz<~(%8bn!MVs0uyvp|aA27x%B7M`97fvA8NPN23Xtf0~b&EJA64Lr7@7IL8MhEnS3 zT7Wh(6R;h&*+O4eAI8)LuVEvg52dsPS&dx!>Vjs43A!Fpbc6IGc)EsqAeWbzWgx9=yZ^6vCiQ$hx4(4nppOEP4R7D8Re!bxrgPK|AdUgbQr*KB&%3 zgzV$jHPQp^-Un%PNJ>mf&CxF^%>(UK#N$0wg&>bYwCIBO;(*J0P)VJaT9jClU!+Ue zKrE_3rlzK+qM4x!-d_z)`gq(2UXKAv>!4E%KtZENC^!ivR&&rY3s4y0u@x~Upsx!W z834C0z!oQ!=IMev@OX5?)j&0a)kBN`n*lW)w4emaEH8$vKO$%}vNC;LePph#sh$z2 zxq?4A!z~0Aj>Wp*^<=~a2FSVky80kW7rKIwphk?r7ElI+4ZMJhK5&5G3CpVd6n$O& zs{9mPW6)+QLS-Cu)49H`K9r>k*>g_7rI20hpj->y&aMkustYdTLAfj~zbH2mUjYx@ zXAaU1*>Mg^+IrxW4Q`3%rr>XYLUyq0>*_-o;FLnhqX>sV3jl~Kb-}yh3HTOV(-ea; z6{sdE)-?oeRsPfS7PgZ7{z^Fe`945@MSGK)bSM9{!0bWfpfT2X#3jGdm7pOlEN zBZI0CT*w#a=ai-DLUe+YF*t;w{FKZhd@U{{RiL&e*mh9wrx=l&@PuM!9w_88^D@DC z7hG@QQ4MoC$iGMq2XVnIf3UOh=*I{^cF$ug^0@OGLoztLe3TsFu z7v&ch>w?=ppjLNrNl|KIZboTtVxDesW_n&?PH|?jE<`jpwJ1Fm%FZju1y#|ypc4Wh z>U2{pGNJvcw9LH39NoOs@|?^(d~Hlf#|lL?wAe1tMG*xT;Dmw-$r&J1z>Wty3LHG( z0YyB)13hd(UsoT>0tXSHN&|MN0;ouZp07Yuen$>IP*sN%n&7r7xJbw2T2Qd-W~Wx_ z>*~XpM0IwMqY&g~q!=YCjNxek()EO-3$RW?E=EaGpr$!;5+f?i5XlpiYg0?0DG|{P z#}i&isT@?6BGNp#w+5ciD9%VtO3VXIyyWI4=B0pJAK`jg7txO4#2@NGAy}HJr z4Jx3D093T*rN^hFg2uXvN=q_xbW>6bic&%Q#qrpHsu0xfN6`tcX~B-hV+N|#pyULe zu!gw>Ty28WQ*mNis%}zgS{mNQ6_P5DSqMF#ZV069i^nmr^=P0h20bhV9A$)By^wuy zAPd0;5VtrF)H}!lsfA8I>!u`@B!bce=;#mK(t?tr%v@Nl3!W_2 zO-n7t*PTEf+yxE3f=;@D4&v$>fle?X)TRKn@xYdV5;kZJD#$`mD~j-F2a01s+L0X( zE^`QFD##%&;7kBI%>}FnoUTCAj(OnfAu}%>lo|_kAwqc439>R!td|sJ!gL`F&dtmt zY!X5dC@sKsfU^!EFF~9Ms@O8~(!og<>@$$#^3uU22H1mm{DG7lz##)U7e=?V09?@# zvL9D`5{^!oy~vh=RzZW^3(W)A6EJv|KsT#6KM#+$a5)S#1do3h0%1J3tyEN;3bqSe zct9Kn8NdRSle%d|iJ=A}dCZ3^)Ci_n0CDQFKR$Xq;Tzy_j04PWSKl89mjk3MWp z1w~PDaw579zy%CAgz%V$d$Iw1x)A|ifQNHn^9kVi$;mH*@pVD#qQNa`JoZCO08J#o zwSzd|Sp`CR(IXxdmas$sNgv>B1XBiHOQs7NoJ#=@C})C-(#*8X)S|3n-IDz5RJ?f? zt_GaDpz4V#feG6WayV|+fxH4b%ZWf-U@uHTeMzj%1YPh6USKoxi%U{dbU`iK%)GRG zBZZn81!E9l0wPSI0hX!@>Y~CL8abKCsd>fty7Kt6LB~iz1Buv71!n|s)D;(`ROlw> z7lG!X!EES$x=dJsj3;GbRS&AkL1vgMLBl;)&@M=rqi zGrWieZJz|KP1P-~%+1Nn%Z41hi^nzsIzhgG%^+eo8(eIH0|$>S2$zBz5hWSW${4g5 zgwVhes-Gb>V{$$`5I{p8plNFStr*Dm5KzGZ-X{X?!hjtO8&}m$%FIi_;{z0R`nvik zd~op#uIcfZkbzomfZT#AqHCmQp+~64fVU}#uk?ZrbmMU#^ms&&=RgO1l@@>}@xY-3 zIVTM~SP72G)QZgF5`38(Nfj){A$o|a0+6f&HTl4Yw1F)rYRDIMtQ*K;=m~EKpWyL7 z^hhs=Hqh~JM3o!h1L44WN{T9VK|7DZA%@2uQfnU2QWS9A4k|w&r4RmDDu^0T)`b*6 z;8R{e1yD{=scv#&at6MN2(AVc@-X$_F+xKAf*wBy?g)bqq9ZD<31kB})-A|M%*+EFNQWn$pvT*_;U;7|a^1Rh<;hC>P~xLx34f{;%j z2l9bdvVc$K0~fF0nih|psN)`>k_BZT1XM)o5waI;CciRKlno9GJUTH$0KOmzk1n|5P&@#df+b`hv}%B51yIN%57iRt+JMh#28B9!6brOM zl~90V9a9DkMZw0Gk;b3#_!zU^0WDx8WI66S0yL&UNH?}Rf*4yd>j+TW18iGmesW2^ zZeeL^DZb(rNfoG|MCbvH`Vq{e$RVDSp9orT0iuw?8CeBn{|urFhb#x0%`7cR)h#N` zNktZiwmx-JD@s!HQi_xFi*qv}<{&X4i(kMyX>>vMrxwAdS;2)}eiCR17AgjwGJ~>9 z^D+zZqy!{YpxOt#$Vv~S2fSDU9Lj`sgY83T2Xl2zVB`9P^=B3%`2k@LiZD3;6Y7`X z4KPsdMuaV9fD+ZxL9zkOJn*TWpejEz7qsYE7rcQU+$aN$1mXzNE;%|dM6+wFcm5I4Ii0T)Q?XZ(E!PQ?nxN8G$suL>ukbMcNbdfx*YYaM^ z5EK-kh4y(R(7qEMN5HZGXpI3P|1g#)1&Md*ZwiT`h z6upT#IbikRiE2b_WR_$Sh>FY-(29$!%o5O&3(&nDpwPhM0X*daQg}d12&A$Hntj1J zt~|2@-&iNQQbaz3>n1ACB0CjQOd*OpP?&*t5F&(-9E->QiCJZ!1ejF@nuP*wuP8~c zN=qy*(M?HBEznIZEKSS-4;U9GXQbvP>XsBGmZcUIr@|I*gA)p5$^nlhkmE-|GlbxC zNOeK|GeXH6b}%U@Q9;ir1@}=1>BHhOa6}e@_bni~7o6F^Z6rJ{$L4o%KF`n7MGF#e zY6mA(Jm#SVEvWyA8ra|jPpI9EoDz^LH_&KrMQSpvt|Kl5AR7i+w*^X`nRz8hnvqpx z7VBoF=jDSovO;Fk!CJtj7qT)?Rg16BjG-13N2NKb$ZG(Q)uiSXmlmN^-nps8#fj-C zLM8dS;As>*;g4)4OgXp}2hoY-X(A1QIs?=Ig*b?GBOrbQ#U0p_NCrSl4tO&e%Fj+s zN=yPZ{-ONhyu|EOWPV9vQ3(>iw4flh2z)vV9#28dLZ}6$18A!Rt{=S3fKU#Enp6yP z1;QY>5V!;&lviNpzsx=D>x(;X}w@$Y~IqaX>{J z=!|Tnn8&fdE-^2?G$*kLRTjKcy%;jeh;QX79$k>?1HPXfyp1fg1ZF0pedZPH?-vYS z^_N+!TasE)0v@Ttld8~{Mu4_k5egsVr4fe2>v)0 z<7PCKpyC1KQ-oe!(Ci@CP&}p}f=XXkAHe}nl!CjbpsEEN{dTsXku-A!8)H1iL01KV z90OSt1g;Fh1Lb&h!R>(7Sz!CXP9T(NP!kLMqycCG#7Hmb3cxD}2n7*xMnKdnkQjtk zGDukhk2jGGf-FV`Rji=7E#ztp)Sd?)?tsrEumadA5THfgpi?2h`)9#T1^6iu$vK&z z&DzFBx)o+7SVY15Zc)Uc8*Sqgpq&v^WQs7O*5S-UoRP$ttiu zP^9N%f-1_=3dnwVurMNUNHe&?%ml0-#aNgixrTxR2E|0M2)V{V_Glvl1LQo=i38xY zK&*F>V;Jf*WFgYs2Gxyl972F}!^$)BjEx}C33VKj2)V`~oCYxrCETEguIObImnY_A zgSQpyf_4Go2~SjophdGNI>AkEV?u=?CGmu}xF9(RUT2U!YPdJIa=r6p;)7O-I!e8b;JNf42l;SCc|&IW}!X{K-LW$u4^e_T1J0WJ8 z2(8Htfe#@d6dsVX`yoB9(xM!q))!|YatcHvEZ=}jc|sLDBFBL=!?PcF!kSPH zfZSUED*V6~7!Wm!2#)dc%o139muHsfn(2Y2O+iQ6<)>64kDKTg6y;Y`>L#Xx4)!a~ z&&@9?$N=4g3@QuYNBDqF&Bqf3AZbv0p)@l^7j&9FAy4IICWC7A%w%wLfMD>Uxe64B zXhPs<0NV;pr{E-x$HmCXKuHghpuxHzdxXGg8jk_sFb1oKg)*1}u9}EQfsl=XV4r|? zGo&Wwf|p+qvK3S`gYIbn51b~0uWSIP2t%-ypo!cv-JHx4d^I#e5y*DXkOx?Yu7REz zq*}JKRR9e@87bH(fL10Mfc6&^5w{HqrVLa!CZ?srxJ1P=IINKqBWRfmXi+G#7`V6r z$1^y+>Lw-PU33dRkQdbO16_Ck9;XD?fMBbzp9q-?>L%nC6Xjxr6F{vkQ2qhC2i&3{ zl%}BzZb9(~xe^038Vfx$0=$(#H!UZzqy!Y-c_pcNCAuk@#Rd7rnV^kM$@vf=(9A95 zx&+8hWKcB>(vXr`T9gA_7@tv+n*-h{oROH9k^|XC2HmLvo`};;Ni7D?^XC_VcJ`-b z=4BRVq^3Y-iSwar|Dh#BN=gx^XOs#pJ;0}H>Vi&3&rB{(Eh#93qs=iJnk z%tUbivy z-#7wYX#lo5F9lTCfzIB-uK_ugf|_NZBV(b77VKg|zRu4B?XCxB1W@w}!2=J16VeU4 z6#$gCp%(;z{7JBNid^)7i*tObAu~4>lng+ZN`P9vgyH~X6g1mIi&ZEOoC?6YOY#vL zKtRfI9hr-y4AkmE;u4h)L1$xwOXPwSgxz@T121d=bs$0asOTE#f%eSdkA2WBEugS3 zNlXWi6cCKBV(?xeP)P~WhTwtyLntQT1ujT0=r$lw;S1JDs62z71r81j@GTqQv@LWl0Q4VZ&4$?jbt+2tbuMkwQ78dJ5?t%d|*o!k$ zQo*M+=cVF1xdd4mD32rQ0tX_vz6Pz50k7W&op6wu2ih%NoS6(+gr=LBS6qO<|g55EsltOjj!Pc6}fI|UqbkoZn42DMj8@w66oC2kd!5X%D0uTjg4tl5gwi}bL_wp2i8(po&;{!RS6{`cCEycFL9Gql zoYcH@ylZ{1s0KwPrWxR10w?g4)U?dJRPg?6P;eEb7L`;&eFJ7AH%stj23%SY;elZq z*pq~U2Xtu$D30?BQixKI%{$X9gu)HIJrByC;2Y5pO?0|DOEN(leet*%p$Ign0oQ>T8^EI%6xev;i;(AW#TOxcXanUS zcY`*?Lx#KzAR`)}Tf#uN+`q`XI3Usa1@zvHA5fahrqs;DPQe0x0SD@JAq-6Vk?na0 z*acYctbJWQ=~}15Kghiu3!pTY4VK$T!cvPsqo8&QCOFLnxd~zth(#PGMg0Y+`#wNv zLjF9%s8#;VsB((NJ&_vYyV2*+{cZ6dVtWLX#_Dg_?JEdh8=_pk!T`QKmVu9fp}_)r SkIGaiy$K@MKwL=BPZa=Hg3#vx diff --git a/package.json b/package.json index bc3bc9d..09bb462 100644 --- a/package.json +++ b/package.json @@ -28,17 +28,18 @@ "ReVanced (https://github.com/revanced)" ], "devDependencies": { - "@biomejs/biome": "^1.5.2", - "@commitlint/cli": "^18.4.4", - "@commitlint/config-conventional": "^18.4.4", + "@biomejs/biome": "^1.6.3", + "@commitlint/cli": "^19.2.1", + "@commitlint/config-conventional": "^19.1.0", "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", - "@tsconfig/strictest": "^2.0.2", - "@types/bun": "latest", + "@tsconfig/strictest": "^2.0.4", + "@types/bun": "^1.0.10", "concurrently": "^8.2.2", "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.5.6", - "turbo": "^1.11.3", - "typescript": "^5.0.0" - } -} \ No newline at end of file + "lefthook": "^1.6.7", + "turbo": "^1.13.0", + "typescript": "^5.4.3" + }, + "trustedDependencies": ["@biomejs/biome", "tesseract.js"] +} From 77f1a9cb3e4dfb2d8c61e3ce715867af2672df5c Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 28 Mar 2024 21:37:41 +0700 Subject: [PATCH 054/312] chore: remove `bun-types` from tsconfig --- tsconfig.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 050bea4..9962c1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,9 @@ { - "extends": ["@tsconfig/strictest", "bun-types"], + "extends": ["@tsconfig/strictest"], "compilerOptions": { "lib": ["ESNext"], "target": "ESNext", "module": "ESNext", - "moduleDetection": "force", - "moduleResolution": "Bundler", "strict": true, @@ -15,8 +13,8 @@ "noFallthroughCasesInSwitch": true, "useUnknownInCatchVariables": true, "noPropertyAccessFromIndexSignature": true, - - "composite": false, + + "noEmitOnError": true, "resolveJsonModule": true, "esModuleInterop": true, "declaration": false, From b3b7723b4fed4e1ad6e3f6258159af05f12ba4cc Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 28 Mar 2024 21:41:59 +0700 Subject: [PATCH 055/312] feat!: big feature changes BREAKING CHANGES: - Heartbeating removed - `config.consoleLogLevel` -> `config.logLevel` NEW FEATURES: - Training messages - Sequence number system - WebSocket close codes used instead of disconnect packets FIXES: - Improved error handling - Some performance improvements - Made code more clean - Updated dependencies --- apis/websocket/config.json | 3 +- apis/websocket/config.revanced.json | 3 +- apis/websocket/config.schema.json | 7 +- apis/websocket/docs/1_configuration.md | 9 +- apis/websocket/docs/2_running.md | 2 +- apis/websocket/docs/3_packets.md | 10 +- apis/websocket/package.json | 76 ++++----- apis/websocket/src/classes/Client.ts | 107 +++--------- apis/websocket/src/events/index.ts | 23 ++- apis/websocket/src/events/parseImage.ts | 37 ++-- apis/websocket/src/events/parseText.ts | 40 +++-- apis/websocket/src/events/trainMessage.ts | 43 +++++ apis/websocket/src/index.ts | 108 ++++++++---- apis/websocket/src/utils/checkEnvironment.ts | 23 --- .../src/utils/{getConfig.ts => config.ts} | 7 +- apis/websocket/src/utils/index.ts | 2 - apis/websocket/tsconfig.json | 21 ++- packages/api/package.json | 2 +- packages/api/src/classes/Client.ts | 158 +++++++++++------- packages/api/src/classes/ClientWebSocket.ts | 101 +++++------ packages/api/src/index.ts | 2 +- packages/api/src/utils/packets.ts | 26 +++ packages/api/tsconfig.json | 20 +-- packages/shared/package.json | 76 ++++----- .../shared/src/constants/DisconnectReason.ts | 28 ++-- .../constants/HumanizedDisconnectReason.ts | 16 +- packages/shared/src/constants/Operation.ts | 27 +-- packages/shared/src/index.ts | 6 +- packages/shared/src/schemas/Packet.ts | 44 +++-- packages/shared/src/utils/guard.ts | 6 +- packages/shared/src/utils/logger.ts | 11 +- packages/shared/src/utils/serialization.ts | 4 +- packages/shared/tsconfig.json | 20 +-- 33 files changed, 562 insertions(+), 506 deletions(-) create mode 100644 apis/websocket/src/events/trainMessage.ts delete mode 100755 apis/websocket/src/utils/checkEnvironment.ts rename apis/websocket/src/utils/{getConfig.ts => config.ts} (87%) delete mode 100755 apis/websocket/src/utils/index.ts create mode 100644 packages/api/src/utils/packets.ts diff --git a/apis/websocket/config.json b/apis/websocket/config.json index 9a07cd7..2b929d4 100755 --- a/apis/websocket/config.json +++ b/apis/websocket/config.json @@ -4,6 +4,5 @@ "address": "127.0.0.1", "port": 3000, "ocrConcurrentQueues": 1, - "clientHeartbeatInterval": 5000, - "consoleLogLevel": "log" + "logLevel": "debug" } diff --git a/apis/websocket/config.revanced.json b/apis/websocket/config.revanced.json index ef47645..ffb38a0 100755 --- a/apis/websocket/config.revanced.json +++ b/apis/websocket/config.revanced.json @@ -4,6 +4,5 @@ "address": "127.0.0.1", "port": 3000, "ocrConcurrentQueues": 3, - "clientHeartbeatInterval": 5000, - "consoleLogLevel": "log" + "logLevel": "log" } diff --git a/apis/websocket/config.schema.json b/apis/websocket/config.schema.json index 51965c1..a6343ef 100755 --- a/apis/websocket/config.schema.json +++ b/apis/websocket/config.schema.json @@ -17,12 +17,7 @@ "type": "integer", "default": 1 }, - "clientHeartbeatInterval": { - "description": "Time in milliseconds to wait for a client to send a heartbeat packet, if no packet is received, the server will wait for `clientHeartbeatExtraTime` milliseconds before disconnecting the client", - "type": "integer", - "default": 60000 - }, - "consoleLogLevel": { + "logLevel": { "description": "The log level to print to console", "type": "string", "enum": ["error", "warn", "info", "log", "debug", "trace", "none"], diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md index aead5c5..54999e5 100644 --- a/apis/websocket/docs/1_configuration.md +++ b/apis/websocket/docs/1_configuration.md @@ -7,7 +7,6 @@ This is the default configuration (provided in [config.json](../config.json)): "address": "127.0.0.1", "port": 3000, "ocrConcurrentQueues": 1, - "clientHeartbeatInterval": 60000, "consoleLogLevel": "log" } ``` @@ -25,22 +24,18 @@ Amount of concurrent queues that can be run at a time. > [!WARNING] > Setting this too high may cause performance issues. -### `config.clientHeartbeatInterval` - -Heartbeat interval for clients. See [**💓 Heartbeating**](./3_packets.md#💓-heartbeating). - -### `config.consoleLogLevel` +### `config.logLevel` The level of logs to print to console. If the level is more important or equally important to set level, it will be forwarded to the console. The possible levels (sorted by their importance descendingly) are: +- `none` (no messages) - `fatal` - `error` - `warn` - `info` - `log` -- `trace` - `debug` ## ⏭️ What's next diff --git a/apis/websocket/docs/2_running.md b/apis/websocket/docs/2_running.md index 27d0909..d96f449 100644 --- a/apis/websocket/docs/2_running.md +++ b/apis/websocket/docs/2_running.md @@ -36,7 +36,7 @@ bun bundle ``` The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** -You can run these files after using a runtime, eg. `bun run .` or `node .`. +You can run these files using the command `bun run index.js`. ## ⏭️ What's next diff --git a/apis/websocket/docs/3_packets.md b/apis/websocket/docs/3_packets.md index 49eb54e..e2d64b1 100644 --- a/apis/websocket/docs/3_packets.md +++ b/apis/websocket/docs/3_packets.md @@ -19,15 +19,13 @@ Operation codes are numbers that communicate an action. Data fields include additional information for the server to process. They are **either an object with specific fields or just `null`**. +### `packet.s` (server packets) + +A sequence number, exclusively for server packets. The WebSocket server contacts other APIs and they may not be reliable at all times, this makes race conditions. A sequence number cleanly solves this issue by letting the client know what the next packet sequence number would be by giving the current number. + #### 📦 Schemas and constants Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well. [^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../../packages/shared/src/schemas/Packet.ts) [^2]: [`@revanced/bot-shared/src/constants/Operation`](../../../packages/shared/src/constants/Operation.ts) - -## 💓 Heartbeating - -Heartbeating is a process where the client regularly send each other signals to confirm that they are still connected and functioning. If the server doesn't receive a heartbeat from the client within a specified timeframe, it assume the client has disconnected and closes the socket. - -You can configure the interval in the configuration file. See [**📝 Configuration > `config.clientHeartbeatInterval`**](./1_configuration.md#configclientheartbeatinterval). diff --git a/apis/websocket/package.json b/apis/websocket/package.json index fe8eef9..e803cc2 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -1,40 +1,40 @@ { - "name": "@revanced/bot-websocket-api", - "type": "module", - "private": true, - "version": "0.1.0", - "description": "🧦 WebSocket API server for bots assisting ReVanced", - "main": "dist/index.js", - "scripts": { - "bundle": "bun build src/index.ts --outdir=dist --target=bun --minify --sourcemap=external", - "dev": "bun run src/index.ts --watch", - "build": "bun bundle", - "watch": "bun dev" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "apis/websocket" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "@revanced/bot-shared": "workspace:*", - "@sapphire/async-queue": "^1.5.1", - "chalk": "^5.3.0", - "node-wit": "^6.6.0", - "tesseract.js": "^5.0.4" - }, - "devDependencies": { - "@types/node-wit": "^6.0.3", - "typed-emitter": "^2.1.0" - } + "name": "@revanced/bot-websocket-api", + "type": "module", + "private": true, + "version": "0.1.0", + "description": "🧦 WebSocket API server for bots assisting ReVanced", + "main": "dist/index.js", + "scripts": { + "bundle": "bun build src/index.ts --outdir=dist --target=bun", + "dev": "bun run src/index.ts --watch", + "build": "bun bundle", + "watch": "bun dev" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "apis/websocket" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@revanced/bot-shared": "workspace:*", + "@sapphire/async-queue": "^1.5.2", + "chalk": "^5.3.0", + "node-wit": "^6.6.0", + "tesseract.js": "^5.0.5" + }, + "devDependencies": { + "@types/node-wit": "^6.1.0", + "typed-emitter": "^2.1.0" + } } diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index 8025c5b..8eda488 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -2,7 +2,7 @@ import { EventEmitter } from 'events' import { ClientOperation, DisconnectReason, - Packet, + type Packet, ServerOperation, deserializePacket, isClientPacket, @@ -17,38 +17,30 @@ export default class Client { id: string disconnected: DisconnectReason | false = false ready = false + currentSequence = 0 - lastHeartbeat: number = null! - heartbeatInterval: number - - #hbTimeout: NodeJS.Timeout = null! #emitter = new EventEmitter() as TypedEmitter #socket: WebSocket constructor(options: ClientOptions) { this.#socket = options.socket - this.heartbeatInterval = options.heartbeatInterval ?? 60000 this.id = options.id - this.#socket.on('error', () => this.forceDisconnect()) - this.#socket.on('close', () => this.forceDisconnect()) - this.#socket.on('unexpected-response', () => this.forceDisconnect()) + this.#socket.on('error', () => this.disconnect(DisconnectReason.ServerError)) + this.#socket.on('close', code => this._handleDisconnect(code)) + this.#socket.on('unexpected-response', () => this.disconnect(DisconnectReason.InvalidPacket)) this.send({ op: ServerOperation.Hello, - d: { - heartbeatInterval: this.heartbeatInterval, - }, + d: null, }) .then(() => { - this.#listen() - this.#listenHeartbeat() + this._listen() this.ready = true this.#emitter.emit('ready') }) .catch(() => { - if (this.disconnected === false) this.disconnect(DisconnectReason.ServerError) - else this.forceDisconnect(DisconnectReason.ServerError) + this.disconnect(DisconnectReason.ServerError) }) } @@ -64,54 +56,40 @@ export default class Client { this.#emitter.off(name, handler) } - send(packet: Packet) { + send(packet: Omit, 's'>, sequence?: number) { return new Promise((resolve, reject) => { - try { - this.#throwIfDisconnected('Cannot send packet to client that has already disconnected') - this.#socket.send(serializePacket(packet)) - resolve() - } catch (e) { - reject(e) - } + this.#throwIfDisconnected('Cannot send packet to client that has already disconnected') + this.#socket.send( + serializePacket({ ...packet, s: sequence ?? this.currentSequence++ } as Packet), + err => (err ? reject(err) : resolve()), + ) }) } - async disconnect(reason: DisconnectReason = DisconnectReason.Generic) { + async disconnect(reason: DisconnectReason | number = DisconnectReason.Generic) { this.#throwIfDisconnected('Cannot disconnect client that has already disconnected') - try { - await this.send({ op: ServerOperation.Disconnect, d: { reason } }) - } catch (err) { - throw new Error(`Cannot send disconnect reason to client ${this.id}: ${err}`) - } finally { - this.forceDisconnect(reason) - } - } - - forceDisconnect(reason: DisconnectReason = DisconnectReason.Generic) { - if (this.disconnected !== false) return - - // It's so weird because if I moved this down a few lines - // it would just fire the disconnect event twice because of a race condition - this.disconnected = reason - this.ready = false - - if (this.#hbTimeout) clearTimeout(this.#hbTimeout) - this.#socket.close() - - this.#emitter.emit('disconnect', reason) + this.#socket.close(reason) + this._handleDisconnect(reason) } #throwIfDisconnected(errorMessage: string) { if (this.disconnected !== false) throw new Error(errorMessage) if (this.#socket.readyState !== this.#socket.OPEN) { - this.forceDisconnect(DisconnectReason.Generic) + this.#socket.close(DisconnectReason.NoOpenSocket) throw new Error(errorMessage) } } - #listen() { + protected _handleDisconnect(code: number) { + this.disconnected = code + this.ready = false + + this.#emitter.emit('disconnect', code) + } + + protected _listen() { this.#socket.on('message', data => { this.#emitter.emit('message', data) try { @@ -136,38 +114,6 @@ export default class Client { }) } - #listenHeartbeat() { - this.lastHeartbeat = Date.now() - this.#startHeartbeatTimeout() - - this.on('heartbeat', () => { - this.lastHeartbeat = Date.now() - this.#hbTimeout.refresh() - - this.send({ - op: ServerOperation.HeartbeatAck, - d: { - nextHeartbeat: this.lastHeartbeat + this.heartbeatInterval, - }, - }).catch(() => {}) - }) - } - - #startHeartbeatTimeout() { - this.#hbTimeout = setTimeout(() => { - if (Date.now() - this.lastHeartbeat > 0) { - // TODO: put into config - // 5000 is extra time to account for latency - const interval = setTimeout(() => this.disconnect(DisconnectReason.TimedOut), 5000) - - this.once('heartbeat', () => clearTimeout(interval)) - // This should never happen but it did in my testing so I'm adding this just in case - this.once('disconnect', () => clearTimeout(interval)) - // Technically we don't have to do this, but JUST IN CASE! - } else this.#hbTimeout.refresh() - }, this.heartbeatInterval) - } - protected _toBuffer(data: RawData) { if (data instanceof Buffer) return data if (data instanceof ArrayBuffer) return Buffer.from(data) @@ -178,7 +124,6 @@ export default class Client { export interface ClientOptions { id: string socket: WebSocket - heartbeatInterval?: number } export type ClientPacketObject = Packet & { diff --git a/apis/websocket/src/events/index.ts b/apis/websocket/src/events/index.ts index 6e81e88..53392b1 100755 --- a/apis/websocket/src/events/index.ts +++ b/apis/websocket/src/events/index.ts @@ -1,20 +1,33 @@ import type { ClientOperation } from '@revanced/bot-shared' import type { Logger } from '@revanced/bot-shared' -import type { Wit } from 'node-wit' import type { Worker as TesseractWorker } from 'tesseract.js' -import { ClientPacketObject } from '../classes/Client' -import type { Config } from '../utils/getConfig' +import type { ClientPacketObject } from '../classes/Client' +import type { Config } from '../utils/config' export { default as parseTextEventHandler } from './parseText' export { default as parseImageEventHandler } from './parseImage' +export { default as trainMessageEventHandler } from './trainMessage' export type EventHandler = ( packet: ClientPacketObject, context: EventContext, ) => void | Promise + export type EventContext = { - witClient: Wit - tesseractWorker: TesseractWorker + wit: { + train(text: string, label: string): Promise + message(text: string): Promise + } + tesseract: TesseractWorker logger: Logger config: Config } + +export interface WitMessageResponse { + text: string + intents: Array<{ + id: string + name: string + confidence: number + }> +} diff --git a/apis/websocket/src/events/parseImage.ts b/apis/websocket/src/events/parseImage.ts index b48bf09..a94f689 100755 --- a/apis/websocket/src/events/parseImage.ts +++ b/apis/websocket/src/events/parseImage.ts @@ -1,19 +1,21 @@ -import { ClientOperation, ServerOperation } from '@revanced/bot-shared' +import { type ClientOperation, ServerOperation } from '@revanced/bot-shared' import { AsyncQueue } from '@sapphire/async-queue' -import type { EventHandler } from './index' +import type { EventHandler } from '.' const queue = new AsyncQueue() const parseImageEventHandler: EventHandler = async ( packet, - { tesseractWorker, logger, config }, + { tesseract, logger, config }, ) => { const { client, - d: { image_url: imageUrl, id }, + d: { image_url: imageUrl }, } = packet + const nextSeq = client.currentSequence++ + logger.debug(`Client ${client.id} requested to parse image from URL:`, imageUrl) logger.debug(`Queue currently has ${queue.remaining}/${config.ocrConcurrentQueues} items in it`) @@ -23,24 +25,27 @@ const parseImageEventHandler: EventHandler = async ( try { logger.debug(`Recognizing image from URL for client ${client.id}`) - const { data, jobId } = await tesseractWorker.recognize(imageUrl) + const { data, jobId } = await tesseract.recognize(imageUrl) logger.debug(`Recognized image from URL for client ${client.id} (job ${jobId}):`, data.text) - await client.send({ - op: ServerOperation.ParsedImage, - d: { - id, - text: data.text, + await client.send( + { + op: ServerOperation.ParsedImage, + d: { + text: data.text, + }, }, - }) + nextSeq, + ) } catch { logger.error(`Failed to parse image from URL for client ${client.id}:`, imageUrl) - await client.send({ - op: ServerOperation.ParseImageFailed, - d: { - id, + await client.send( + { + op: ServerOperation.ParseImageFailed, + d: null, }, - }) + nextSeq, + ) } finally { queue.shift() logger.debug( diff --git a/apis/websocket/src/events/parseText.ts b/apis/websocket/src/events/parseText.ts index 690dbb5..46ace12 100755 --- a/apis/websocket/src/events/parseText.ts +++ b/apis/websocket/src/events/parseText.ts @@ -1,35 +1,41 @@ -import { ClientOperation, ServerOperation } from '@revanced/bot-shared' +import { type ClientOperation, ServerOperation } from '@revanced/bot-shared' import { inspect as inspectObject } from 'util' -import type { EventHandler } from './index' +import type { EventHandler } from '.' -const parseTextEventHandler: EventHandler = async (packet, { witClient, logger }) => { +const parseTextEventHandler: EventHandler = async (packet, { wit, logger }) => { const { client, - d: { text, id }, + d: { text }, } = packet - logger.debug(`Client ${client.id} requested to parse text:`, text) + const nextSeq = client.currentSequence++ + const actualText = text.slice(0, 279) + + logger.debug(`Client ${client.id} requested to parse text:`, actualText) try { - const { intents } = await witClient.message(text, {}) + const { intents } = await wit.message(actualText) const intentsWithoutIds = intents.map(({ id, ...rest }) => rest) - await client.send({ - op: ServerOperation.ParsedText, - d: { - id, - labels: intentsWithoutIds, + await client.send( + { + op: ServerOperation.ParsedText, + d: { + labels: intentsWithoutIds, + }, }, - }) + nextSeq, + ) } catch (e) { - await client.send({ - op: ServerOperation.ParseTextFailed, - d: { - id, + await client.send( + { + op: ServerOperation.ParseTextFailed, + d: null, }, - }) + nextSeq, + ) if (e instanceof Error) logger.error(e.stack ?? e.message) else logger.error(inspectObject(e)) diff --git a/apis/websocket/src/events/trainMessage.ts b/apis/websocket/src/events/trainMessage.ts new file mode 100644 index 0000000..b028660 --- /dev/null +++ b/apis/websocket/src/events/trainMessage.ts @@ -0,0 +1,43 @@ +import { type ClientOperation, ServerOperation } from '@revanced/bot-shared' + +import { inspect as inspectObject } from 'util' + +import type { EventHandler } from '.' + +const trainMessageEventHandler: EventHandler = async (packet, { wit, logger }) => { + const { + client, + d: { text, label }, + } = packet + + const nextSeq = client.currentSequence++ + const actualText = text.slice(0, 279) + + logger.debug(`Client ${client.id} requested to train label ${label} with:`, actualText) + + try { + await wit.train(actualText, label) + await client.send( + { + op: ServerOperation.TrainedMessage, + d: null, + }, + nextSeq, + ) + + logger.debug(`Trained label ${label} with:`, actualText) + } catch (e) { + await client.send( + { + op: ServerOperation.TrainMessageFailed, + d: null, + }, + nextSeq, + ) + + if (e instanceof Error) logger.error(e.stack ?? e.message) + else logger.error(inspectObject(e)) + } +} + +export default trainMessageEventHandler diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index 638b9bc..110c82e 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -1,43 +1,93 @@ -import witPkg from 'node-wit' import { createWorker as createTesseractWorker } from 'tesseract.js' -const { Wit } = witPkg import { inspect as inspectObject } from 'util' import Client from './classes/Client' -import { EventContext, parseImageEventHandler, parseTextEventHandler } from './events/index' +import { + type EventContext, + type WitMessageResponse, + parseImageEventHandler, + parseTextEventHandler, + trainMessageEventHandler, +} from './events' import { DisconnectReason, HumanizedDisconnectReason, createLogger } from '@revanced/bot-shared' -import { checkEnvironment, getConfig } from './utils/index' +import { getConfig } from './utils/config' import { createServer } from 'http' -import { WebSocket, WebSocketServer } from 'ws' +import { type WebSocket, WebSocketServer } from 'ws' // Load config, init logger, check environment const config = getConfig() const logger = createLogger({ - level: config['consoleLogLevel'] === 'none' ? Infinity : config['consoleLogLevel'], + level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, }) -checkEnvironment(logger) +if (!process.env['NODE_ENV']) logger.warn('NODE_ENV not set, defaulting to `development`') +const environment = (process.env['NODE_ENV'] ?? 'development') as NodeEnvironment + +if (!['development', 'production'].includes(environment)) { + logger.error('NODE_ENV is neither `development` nor `production`, unable to determine environment') + logger.info('Set NODE_ENV to blank to use `development` mode') + process.exit(1) +} + +logger.info(`Running in ${environment} mode...`) + +if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { + logger.warn('You seem to be using .env files, this is generally not a good idea in production...') +} + +if (!process.env['WIT_AI_TOKEN']) { + logger.error('WIT_AI_TOKEN is not defined in the environment variables') + process.exit(1) +} // Workers and API clients -const tesseractWorker = await createTesseractWorker('eng') -const witClient = new Wit({ - accessToken: process.env['WIT_AI_TOKEN']!, -}) +const tesseract = await createTesseractWorker('eng') +const wit = { + token: process.env['WIT_AI_TOKEN']!, + async fetch(route: string, options?: RequestInit) { + const res = await fetch(`https://api.wit.ai${route}`, { + headers: { + Authorization: `Bearer ${this.token}`, + 'Content-Type': 'application/json', + }, + ...options, + }) + + if (!res.ok) throw new Error(`Failed to fetch from Wit.ai: ${res.statusText} (${res.status})`) + + return await res.json() + }, + message(text: string) { + return this.fetch(`/message?q=${encodeURIComponent(text)}&n=8`) as Promise + }, + async train(text: string, label: string) { + await this.fetch('/utterances', { + body: JSON.stringify([ + { + text, + intent: label, + entities: [], + traits: [], + }, + ]), + method: 'POST', + }) + }, +} as const // Server logic -const clients = new Set() -const clientSocketMap = new WeakMap() +const clientMap = new WeakMap() const eventContext: EventContext = { - tesseractWorker, + tesseract, logger, - witClient, + wit, config, } @@ -61,25 +111,24 @@ wss.on('connection', async (socket, request) => { const client = new Client({ socket, id: `${request.socket.remoteAddress}:${request.socket.remotePort}`, - heartbeatInterval: config['clientHeartbeatInterval'], }) - clientSocketMap.set(socket, client) - clients.add(client) + clientMap.set(socket, client) logger.debug(`Client ${client.id}'s instance has been added`) - logger.info(`New client connected (now ${clients.size} clients) with ID:`, client.id) + logger.info(`New client connected with ID: ${client.id}`) client.on('disconnect', reason => { - clients.delete(client) - logger.info(`Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}`) + logger.info( + `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]} (${reason})`, + ) }) - client.on('parseText', async packet => parseTextEventHandler(packet, eventContext)) + client.on('parseText', packet => parseTextEventHandler(packet, eventContext)) + client.on('parseImage', packet => parseImageEventHandler(packet, eventContext)) + client.on('trainMessage', packet => trainMessageEventHandler(packet, eventContext)) - client.on('parseImage', async packet => parseImageEventHandler(packet, eventContext)) - - if (['debug', 'trace'].includes(config['consoleLogLevel'])) { + if (['debug', 'trace'].includes(config.logLevel)) { logger.debug('Debug logs enabled, attaching debug events...') client.on('packet', ({ client, ...rawPacket }) => @@ -87,14 +136,12 @@ wss.on('connection', async (socket, request) => { ) client.on('message', d => logger.debug(`Message from client ${client.id}:`, d)) - - client.on('heartbeat', () => logger.debug('Heartbeat received from client', client.id)) } } catch (e) { if (e instanceof Error) logger.error(e.stack ?? e.message) else logger.error(inspectObject(e)) - const client = clientSocketMap.get(socket) + const client = clientMap.get(socket) if (!client) { logger.error( @@ -104,9 +151,6 @@ wss.on('connection', async (socket, request) => { } if (client.disconnected === false) client.disconnect(DisconnectReason.ServerError) - else client.forceDisconnect() - - clients.delete(client) logger.debug(`Client ${client.id} disconnected because of an internal error`) } @@ -114,7 +158,7 @@ wss.on('connection', async (socket, request) => { // Start the server -server.listen(config['port'], config['address']) +server.listen(config.port, config.address) logger.debug(`Starting with these configurations: ${inspectObject(config)}`) diff --git a/apis/websocket/src/utils/checkEnvironment.ts b/apis/websocket/src/utils/checkEnvironment.ts deleted file mode 100755 index 803d94e..0000000 --- a/apis/websocket/src/utils/checkEnvironment.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Logger } from '@revanced/bot-shared' - -export default function checkEnvironment(logger: Logger) { - if (!process.env['NODE_ENV']) logger.warn('NODE_ENV not set, defaulting to `development`') - const environment = (process.env['NODE_ENV'] ?? 'development') as NodeEnvironment - - if (!['development', 'production'].includes(environment)) { - logger.error('NODE_ENV is neither `development` nor `production`, unable to determine environment') - logger.info('Set NODE_ENV to blank to use `development` mode') - process.exit(1) - } - - logger.info(`Running in ${environment} mode...`) - - if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { - logger.warn('You seem to be using .env files, this is generally not a good idea in production...') - } - - if (!process.env['WIT_AI_TOKEN']) { - logger.error('WIT_AI_TOKEN is not defined in the environment variables') - process.exit(1) - } -} diff --git a/apis/websocket/src/utils/getConfig.ts b/apis/websocket/src/utils/config.ts similarity index 87% rename from apis/websocket/src/utils/getConfig.ts rename to apis/websocket/src/utils/config.ts index 90bf638..7beb09b 100755 --- a/apis/websocket/src/utils/getConfig.ts +++ b/apis/websocket/src/utils/config.ts @@ -7,7 +7,7 @@ const configPath = resolvePath(process.cwd(), 'config.json') const userConfig: Partial = existsSync(configPath) ? ( await import(pathToFileURL(configPath).href, { - assert: { + with: { type: 'json', }, }) @@ -28,10 +28,9 @@ export const defaultConfig: Config = { address: '127.0.0.1', port: 8080, ocrConcurrentQueues: 1, - clientHeartbeatInterval: 60000, - consoleLogLevel: 'info', + logLevel: 'info', } -export default function getConfig() { +export function getConfig() { return Object.assign(defaultConfig, userConfig) satisfies Config } diff --git a/apis/websocket/src/utils/index.ts b/apis/websocket/src/utils/index.ts deleted file mode 100755 index 24a2c7c..0000000 --- a/apis/websocket/src/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as getConfig } from './getConfig' -export { default as checkEnvironment } from './checkEnvironment' diff --git a/apis/websocket/tsconfig.json b/apis/websocket/tsconfig.json index 29a7c65..b80117e 100755 --- a/apis/websocket/tsconfig.json +++ b/apis/websocket/tsconfig.json @@ -1,11 +1,14 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": ".", - "outDir": "dist", - "module": "ESNext", - "composite": false - }, - "exclude": ["node_modules", "dist"], - "include": ["./*.json", "src/**/*.ts"] + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "module": "ESNext", + "target": "ESNext", + "lib": ["ESNext"], + "composite": false, + "skipLibCheck": true + }, + "exclude": ["node_modules", "dist"], + "include": ["./*.json", "src/**/*.ts"] } diff --git a/packages/api/package.json b/packages/api/package.json index e30e8b4..ca31c12 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "bun bundle && bun types", "watch": "bunx conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=bun --minify", + "bundle": "bun build src/index.ts --outdir=dist --target=bun", "bundle:watch": "bun run bundle --watch", "types": "tsc --declaration --emitDeclarationOnly", "types:watch": "bun types --watch --preserveWatchOutput", diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index 3db2488..c645aa5 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -1,5 +1,10 @@ -import { ClientOperation, Packet, ServerOperation } from '@revanced/bot-shared' -import { ClientWebSocketManager, ClientWebSocketEvents, ClientWebSocketManagerOptions } from './ClientWebSocket' +import { ClientOperation, ServerOperation } from '@revanced/bot-shared' +import { awaitPacket } from 'src/utils/packets' +import { + type ClientWebSocketEvents, + ClientWebSocketManager, + type ClientWebSocketManagerOptions, +} from './ClientWebSocket' /** * The client that connects to the API. @@ -7,7 +12,6 @@ import { ClientWebSocketManager, ClientWebSocketEvents, ClientWebSocketManagerOp export default class Client { ready = false ws: ClientWebSocketManager - #parseId = 0 constructor(options: ClientOptions) { this.ws = new ClientWebSocketManager(options.api.websocket) @@ -15,7 +19,7 @@ export default class Client { this.ready = true }) this.ws.on('disconnect', () => { - + this.ready = false }) } @@ -35,36 +39,34 @@ export default class Client { async parseText(text: string) { this.#throwIfNotReady() - const currentId = (this.#parseId++).toString() + return await this.ws + .send({ + op: ClientOperation.ParseText, + d: { + text, + }, + }) + .then(() => { + // Since we don't have heartbeats anymore, this is fine. + // But if we add anything similar, this will cause another race condition + // To fix this, we can try adding a instanced function that would return the currentSequence + // and it would be updated every time a "heartbeat ack" packet is received + const expectedNextSeq = this.ws.currentSequence + 1 + const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => + awaitPacket(this.ws, op, expectedNextSeq, timeout) - this.ws.send({ - op: ClientOperation.ParseText, - d: { - text, - id: currentId, - }, - }) - - type CorrectPacket = Packet - - const promise = new Promise((rs, rj) => { - const parsedTextListener = (packet: CorrectPacket) => { - if (packet.d.id !== currentId) return - this.ws.off('parsedText', parsedTextListener) - rs(packet.d) - } - - const parseTextFailedListener = (packet: Packet) => { - if (packet.d.id !== currentId) return - this.ws.off('parseTextFailed', parseTextFailedListener) - rj() - } - - this.ws.on('parsedText', parsedTextListener) - this.ws.on('parseTextFailed', parseTextFailedListener) - }) - - return await promise + return Promise.race([ + awaitPkt(ServerOperation.ParsedText), + awaitPkt(ServerOperation.ParseTextFailed, this.ws.timeout + 5000), + ]) + .then(pkt => { + if (pkt.op === ServerOperation.ParsedText) return pkt.d + throw new Error('Failed to parse text, the API encountered an error') + }) + .catch(() => { + throw new Error('Failed to parse text, the API did not respond in time') + }) + }) } /** @@ -75,36 +77,62 @@ export default class Client { async parseImage(url: string) { this.#throwIfNotReady() - const currentId = (this.#parseId++).toString() + return await this.ws + .send({ + op: ClientOperation.ParseImage, + d: { + image_url: url, + }, + }) + .then(() => { + // See line 48 + const expectedNextSeq = this.ws.currentSequence + 1 + const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => + awaitPacket(this.ws, op, expectedNextSeq, timeout) - this.ws.send({ - op: ClientOperation.ParseImage, - d: { - image_url: url, - id: currentId, - }, - }) + return Promise.race([ + awaitPkt(ServerOperation.ParsedImage), + awaitPkt(ServerOperation.ParseImageFailed, this.ws.timeout + 5000), + ]) + .then(pkt => { + if (pkt.op === ServerOperation.ParsedImage) return pkt.d + throw new Error('Failed to parse image, the API encountered an error') + }) + .catch(() => { + throw new Error('Failed to parse image, the API did not respond in time') + }) + }) + } - type CorrectPacket = Packet + async trainMessage(text: string, label: string) { + this.#throwIfNotReady() - const promise = new Promise((rs, rj) => { - const parsedImageListener = (packet: CorrectPacket) => { - if (packet.d.id !== currentId) return - this.ws.off('parsedImage', parsedImageListener) - rs(packet.d) - } + return await this.ws + .send({ + op: ClientOperation.TrainMessage, + d: { + label, + text, + }, + }) + .then(() => { + // See line 48 + const expectedNextSeq = this.ws.currentSequence + 1 + const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => + awaitPacket(this.ws, op, expectedNextSeq, timeout) - const parseImageFailedListener = (packet: Packet) => { - if (packet.d.id !== currentId) return - this.ws.off('parseImageFailed', parseImageFailedListener) - rj() - } - - this.ws.on('parsedImage', parsedImageListener) - this.ws.on('parseImageFailed', parseImageFailedListener) - }) - - return await promise + return Promise.race([ + awaitPkt(ServerOperation.TrainedMessage), + awaitPkt(ServerOperation.TrainMessageFailed, this.ws.timeout + 5000), + ]) + .then(pkt => { + if (pkt.op === ServerOperation.TrainedMessage) return + throw new Error('Failed to train message, the API encountered an error') + }) + .catch(() => { + throw new Error('Failed to train message, the API did not respond in time') + }) + }) } /** @@ -135,14 +163,18 @@ export default class Client { * @param handler The event handler * @returns The event handler function */ - once( - name: TOpName, - handler: ClientWebSocketEvents[TOpName], - ) { + once(name: TOpName, handler: ClientWebSocketEvents[TOpName]) { this.ws.once(name, handler) return handler } + /** + * Disconnects the client from the API + */ + disconnect() { + this.ws.disconnect() + } + #throwIfNotReady() { if (!this.isReady()) throw new Error('Client is not ready') } diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index 2d71c9c..34847f6 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -1,8 +1,8 @@ import { EventEmitter } from 'events' import { - ClientOperation, + type ClientOperation, DisconnectReason, - Packet, + type Packet, ServerOperation, deserializePacket, isServerPacket, @@ -10,7 +10,7 @@ import { uncapitalize, } from '@revanced/bot-shared' import type TypedEmitter from 'typed-emitter' -import { RawData, WebSocket } from 'ws' +import { type RawData, WebSocket } from 'ws' /** * The class that handles the WebSocket connection to the server. @@ -21,10 +21,9 @@ export class ClientWebSocketManager { timeout: number ready = false - disconnected: boolean | DisconnectReason = DisconnectReason.NeverConnected - config: Readonly['d']> | null = null + disconnected: false | DisconnectReason = false + currentSequence = 0 - #hbTimeout: NodeJS.Timeout = null! #socket: WebSocket = null! #emitter = new EventEmitter() as TypedEmitter @@ -42,26 +41,27 @@ export class ClientWebSocketManager { try { this.#socket = new WebSocket(this.url) - setTimeout(() => { - if (!this.ready) throw new Error('WebSocket connection timed out') - this.#socket.close() + const timeout = setTimeout(() => { + if (!this.ready) { + this.#socket?.close(DisconnectReason.TooSlow) + throw new Error('WebSocket connection was not readied in time') + } }, this.timeout) this.#socket.on('open', () => { - this.disconnected = false + clearTimeout(timeout) this.#listen() - this.ready = true - this.#emitter.emit('ready') rs() }) - this.#socket.on('error', (err) => { + this.#socket.on('error', err => { + clearTimeout(timeout) throw err }) this.#socket.on('close', (code, reason) => { - if (code === 1006) throw new Error(`Failed to connect to WebSocket server: ${reason}`) - this.#handleDisconnect(DisconnectReason.Generic) + clearTimeout(timeout) + this._handleDisconnect(code, reason.toString()) }) } catch (e) { rj(e) @@ -75,10 +75,7 @@ export class ClientWebSocketManager { * @param handler The event handler * @returns The event handler function */ - on( - name: TOpName, - handler: ClientWebSocketEvents[typeof name], - ) { + on(name: TOpName, handler: ClientWebSocketEvents[typeof name]) { this.#emitter.on(name, handler) } @@ -88,10 +85,7 @@ export class ClientWebSocketManager { * @param handler The event handler to remove * @returns The removed event handler function */ - off( - name: TOpName, - handler: ClientWebSocketEvents[typeof name], - ) { + off(name: TOpName, handler: ClientWebSocketEvents[typeof name]) { this.#emitter.off(name, handler) } @@ -101,10 +95,7 @@ export class ClientWebSocketManager { * @param handler The event handler * @returns The event handler function */ - once( - name: TOpName, - handler: ClientWebSocketEvents[typeof name], - ) { + once(name: TOpName, handler: ClientWebSocketEvents[typeof name]) { this.#emitter.once(name, handler) } @@ -126,7 +117,7 @@ export class ClientWebSocketManager { */ disconnect() { this.#throwIfDisconnected('Cannot disconnect when already disconnected from the server') - this.#handleDisconnect(DisconnectReason.PlannedDisconnect) + this._handleDisconnect(DisconnectReason.PlannedDisconnect) } /** @@ -143,22 +134,22 @@ export class ClientWebSocketManager { if (!isServerPacket(packet)) return this.#emitter.emit('invalidPacket', packet) + this.currentSequence = packet.s this.#emitter.emit('packet', packet) switch (packet.op) { case ServerOperation.Hello: { - const data = Object.freeze((packet as Packet).d) - this.config = data - this.#emitter.emit('hello', data) - this.#startHeartbeating() + this.#emitter.emit('hello') + this.ready = true + this.#emitter.emit('ready') break } case ServerOperation.Disconnect: - return this.#handleDisconnect((packet as Packet).d.reason) + return this._handleDisconnect((packet as Packet).d.reason) default: return this.#emitter.emit( uncapitalize(ServerOperation[packet.op] as ClientWebSocketEventName), - // @ts-expect-error TypeScript doesn't know that the lines above negate the type enough + // @ts-expect-error: TS at it again packet, ) } @@ -170,30 +161,12 @@ export class ClientWebSocketManager { if (this.#socket.readyState !== this.#socket.OPEN) throw new Error(errorMessage) } - #handleDisconnect(reason: DisconnectReason) { - clearTimeout(this.#hbTimeout) - this.disconnected = reason - this.#socket.close() + protected _handleDisconnect(reason: DisconnectReason | number, message?: string) { + this.disconnected = reason in DisconnectReason ? reason : DisconnectReason.Generic + this.#socket?.close(reason) this.#socket = null! - this.#emitter.emit('disconnect', reason) - } - - #startHeartbeating() { - this.on('heartbeatAck', packet => { - this.#hbTimeout = setTimeout(() => { - this.send({ - op: ClientOperation.Heartbeat, - d: null, - }) - }, packet.d.nextHeartbeat - Date.now()) - }) - - // Immediately send a heartbeat so we can get when to send the next one - this.send({ - op: ClientOperation.Heartbeat, - d: null, - }) + this.#emitter.emit('disconnect', reason, message) } protected _toBuffer(data: RawData) { @@ -217,16 +190,18 @@ export interface ClientWebSocketManagerOptions { export type ClientWebSocketEventName = keyof typeof ServerOperation -export type ClientWebSocketEvents = { - [K in Uncapitalize]: ( - packet: Packet<(typeof ServerOperation)[Capitalize]>, - ) => Promise | void -} & { - hello: (config: NonNullable) => Promise | void +type ClientWebSocketPredefinedEvents = { + hello: () => Promise | void ready: () => Promise | void packet: (packet: Packet) => Promise | void invalidPacket: (packet: Packet) => Promise | void - disconnect: (reason: DisconnectReason) => Promise | void + disconnect: (reason: DisconnectReason | number, message?: string) => Promise | void } +export type ClientWebSocketEvents = { + [K in Exclude, keyof ClientWebSocketPredefinedEvents>]: ( + packet: Packet<(typeof ServerOperation)[Capitalize]>, + ) => Promise | void +} & ClientWebSocketPredefinedEvents + export type ReadiedClientWebSocketManager = RequiredProperty> diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 63e4228..f50cdd7 100755 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1 +1 @@ -export * from './classes/index' +export * from './classes' diff --git a/packages/api/src/utils/packets.ts b/packages/api/src/utils/packets.ts new file mode 100644 index 0000000..70e14de --- /dev/null +++ b/packages/api/src/utils/packets.ts @@ -0,0 +1,26 @@ +import type { Packet, ServerOperation } from '@revanced/bot-shared' +import type { ClientWebSocketManager } from 'src/classes' + +export function awaitPacket( + ws: ClientWebSocketManager, + op: TOp, + expectedSeq: number, + timeout = 10000, +): Promise> { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + ws.off('packet', handler) + reject('Awaiting packet timed out') + }, timeout) + + function handler(packet: Packet) { + if (packet.op === op && packet.s === expectedSeq) { + clearTimeout(timer) + ws.off('packet', handler) + resolve(packet as Packet) + } + } + + ws.on('packet', handler) + }) +} diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 01f29d1..ef4e094 100755 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,12 +1,12 @@ { - "extends": "../../tsconfig.packages.json", - "compilerOptions": { - "baseUrl": ".", - "rootDir": "./src", - "outDir": "dist", - "module": "ESNext", - "composite": true, - "noEmit": false - }, - "exclude": ["node_modules", "dist"] + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "./src", + "outDir": "dist", + "module": "ESNext", + "composite": true, + "noEmit": false + }, + "exclude": ["node_modules", "dist"] } diff --git a/packages/shared/package.json b/packages/shared/package.json index 9d6141d..f1c9023 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,40 +1,40 @@ { - "name": "@revanced/bot-shared", - "type": "module", - "version": "0.1.0", - "description": "🙌🏻 Shared components for bots assisting ReVanced", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "bun bundle && bun types", - "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=bun --minify", - "bundle:watch": "bun run bundle --watch", - "types": "tsc --declaration --emitDeclarationOnly", - "types:watch": "bun types --watch --preserveWatchOutput", - "types:clean": "bun types --build --clean" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "packages/shared" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "bson": "^6.2.0", - "chalk": "^5.3.0", - "supports-color": "^9.4.0", - "tracer": "^1.3.0", - "valibot": "^0.21.0", - "zod": "^3.22.4" - } + "name": "@revanced/bot-shared", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Shared components for bots assisting ReVanced", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=bun --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/shared" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "bson": "^6.5.0", + "chalk": "^5.3.0", + "supports-color": "^9.4.0", + "tracer": "^1.3.0", + "valibot": "^0.30.0", + "zod": "^3.22.4" + } } diff --git a/packages/shared/src/constants/DisconnectReason.ts b/packages/shared/src/constants/DisconnectReason.ts index 37588ba..048f49b 100755 --- a/packages/shared/src/constants/DisconnectReason.ts +++ b/packages/shared/src/constants/DisconnectReason.ts @@ -3,29 +3,33 @@ */ enum DisconnectReason { /** - * Unknown reason + * The client disconnected on its own (**CLIENT-ONLY**) */ - Generic = 1, - /** - * The client did not respond in time - */ - TimedOut = 2, + PlannedDisconnect = 1000, /** * The client sent an invalid packet (unserializable or invalid JSON) */ - InvalidPacket = 3, + InvalidPacket = 1007, /** * The server has encountered an internal error */ - ServerError = 4, + ServerError = 1011, /** - * The client had never connected to the server (**CLIENT-ONLY**) + * Unknown reason */ - NeverConnected = 5, + Generic = 4000, /** - * The client disconnected on its own (**CLIENT-ONLY**) + * The client did not respond with a heartbeat in time */ - PlannedDisconnect = 6, + TimedOut = 4001, + /** + * The receiving end didn't have an open socket + */ + NoOpenSocket = 4003, + /** + * The client was not ready in time (**CLIENT-ONLY**) + */ + TooSlow = 4002, } export default DisconnectReason diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts index 0f3c536..e3d18b6 100755 --- a/packages/shared/src/constants/HumanizedDisconnectReason.ts +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -4,12 +4,14 @@ import DisconnectReason from './DisconnectReason' * Humanized disconnect reasons for logs */ const HumanizedDisconnectReason = { - [DisconnectReason.InvalidPacket]: 'has sent invalid packet', - [DisconnectReason.Generic]: 'has been disconnected for unknown reasons', - [DisconnectReason.TimedOut]: 'has timed out', - [DisconnectReason.ServerError]: 'has been disconnected due to an internal server error', - [DisconnectReason.NeverConnected]: 'had never connected to the server', - [DisconnectReason.PlannedDisconnect]: 'has disconnected on its own', -} as const satisfies Record + [1006]: 'the receiving end had unexpectedly closed the connection', + [DisconnectReason.InvalidPacket]: 'the client has sent invalid packet', + [DisconnectReason.Generic]: '(unknown reason)', + [DisconnectReason.TimedOut]: 'the client did not respond with a heartbeat in time', + [DisconnectReason.ServerError]: 'the server had an internal server error', + [DisconnectReason.TooSlow]: 'the client was not ready in time', + [DisconnectReason.PlannedDisconnect]: 'the client has disconnected on its own', + [DisconnectReason.NoOpenSocket]: 'the receiving end did not have an open socket', +} as const satisfies Record export default HumanizedDisconnectReason diff --git a/packages/shared/src/constants/Operation.ts b/packages/shared/src/constants/Operation.ts index 9c4ee4e..d3104ad 100755 --- a/packages/shared/src/constants/Operation.ts +++ b/packages/shared/src/constants/Operation.ts @@ -2,33 +2,28 @@ * Client operation codes for the gateway */ export enum ClientOperation { - /** - * Client's heartbeat (to check if the connection is dead or not) - */ - Heartbeat = 100, - /** * Client's request to parse text */ - ParseText = 110, + ParseText = 100, /** * Client's request to parse image */ - ParseImage = 111, + ParseImage = 101, + /** + * Client's request to train a message + */ + TrainMessage = 102, } /** * Server operation codes for the gateway */ export enum ServerOperation { - /** - * Server's acknowledgement of a client's heartbeat - */ - HeartbeatAck = 1, /** * Server's initial response to a client's connection */ - Hello = 2, + Hello = 1, /** * Server's response to client's request to parse text @@ -46,6 +41,14 @@ export enum ServerOperation { * Server's failure response to client's request to parse image */ ParseImageFailed = 13, + /** + * Server's response to client's request to train a message + */ + TrainedMessage = 14, + /** + * Server's failure response to client's request to train a message + */ + TrainMessageFailed = 15, /** * Server's disconnect message diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index ab1d8b0..8d84460 100755 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,3 @@ -export * from './constants/index' -export * from './schemas/index' -export * from './utils/index' +export * from './constants' +export * from './schemas' +export * from './utils' diff --git a/packages/shared/src/schemas/Packet.ts b/packages/shared/src/schemas/Packet.ts index e1f93f9..2e91901 100755 --- a/packages/shared/src/schemas/Packet.ts +++ b/packages/shared/src/schemas/Packet.ts @@ -1,13 +1,12 @@ import { url, - AnySchema, - NullSchema, - ObjectSchema, - Output, + type AnySchema, + type NullSchema, + type ObjectSchema, + type Output, array, enum_, null_, - number, object, parse, special, @@ -30,6 +29,8 @@ export const PacketSchema = special(input => { 'd' in input && typeof input.d === 'object' ) { + if (input.op in ServerOperation && !('s' in input && typeof input.s === 'number')) return false + try { parse(PacketDataSchemas[input.op as Operation], input.d) return true @@ -44,14 +45,8 @@ export const PacketSchema = special(input => { * Schema to validate packet data for each possible operations */ export const PacketDataSchemas = { - [ServerOperation.Hello]: object({ - heartbeatInterval: number(), - }), - [ServerOperation.HeartbeatAck]: object({ - nextHeartbeat: number(), - }), + [ServerOperation.Hello]: null_(), [ServerOperation.ParsedText]: object({ - id: string(), labels: array( object({ name: string(), @@ -60,35 +55,38 @@ export const PacketDataSchemas = { ), }), [ServerOperation.ParsedImage]: object({ - id: string(), text: string(), }), - [ServerOperation.ParseTextFailed]: object({ - id: string(), - }), - [ServerOperation.ParseImageFailed]: object({ - id: string(), - }), + [ServerOperation.ParseTextFailed]: null_(), + [ServerOperation.ParseImageFailed]: null_(), [ServerOperation.Disconnect]: object({ reason: enum_(DisconnectReason), }), + [ServerOperation.TrainedMessage]: null_(), + [ServerOperation.TrainMessageFailed]: null_(), - [ClientOperation.Heartbeat]: null_(), [ClientOperation.ParseText]: object({ - id: string(), text: string(), }), [ClientOperation.ParseImage]: object({ - id: string(), image_url: string([url()]), }), + [ClientOperation.TrainMessage]: object({ + text: string(), + label: string(), + }), } as const satisfies Record< Operation, // biome-ignore lint/suspicious/noExplicitAny: This is a schema, it's not possible to type it ObjectSchema | AnySchema | NullSchema > -export type Packet = { +export type Packet = TOp extends ServerOperation + ? PacketWithSequenceNumber + : Omit, 's'> + +type PacketWithSequenceNumber = { op: TOp d: Output<(typeof PacketDataSchemas)[TOp]> + s: number } diff --git a/packages/shared/src/utils/guard.ts b/packages/shared/src/utils/guard.ts index 9b92cba..91825cb 100755 --- a/packages/shared/src/utils/guard.ts +++ b/packages/shared/src/utils/guard.ts @@ -1,5 +1,5 @@ -import { ClientOperation, Operation, ServerOperation } from '../constants/Operation' -import { Packet } from '../schemas/Packet' +import { ClientOperation, type Operation, ServerOperation } from '../constants/Operation' +import type { Packet } from '../schemas/Packet' /** * Checks whether a packet is trying to do the given operation @@ -21,7 +21,7 @@ export function isClientPacket(packet: Packet): packet is Packet Date: Thu, 28 Mar 2024 21:52:23 +0700 Subject: [PATCH 056/312] feat(bots/discord): add source --- bots/discord/.env.example | 1 + bots/discord/.gitignore | 178 ++++++++++++++++++ bots/discord/config.example.ts | 80 ++++++++ bots/discord/config.ts | 80 ++++++++ bots/discord/package.json | 36 ++++ bots/discord/scripts/reload-slash-commands.ts | 42 +++++ bots/discord/src/classes/Database.ts | 102 ++++++++++ bots/discord/src/commands/index.ts | 50 +++++ bots/discord/src/commands/reply.ts | 54 ++++++ bots/discord/src/commands/stop.ts | 28 +++ bots/discord/src/constants.ts | 9 + bots/discord/src/context.ts | 56 ++++++ bots/discord/src/events/api/disconnect.ts | 30 +++ bots/discord/src/events/api/ready.ts | 6 + .../discord/src/events/discord/guildCreate.ts | 7 + .../interactionCreate/chat-commmand.ts | 78 ++++++++ .../interactionCreate/correct-response.ts | 111 +++++++++++ .../src/events/discord/messageCreate/scan.ts | 70 +++++++ .../messageReactionAdd/correct-response.ts | 130 +++++++++++++ bots/discord/src/events/discord/ready.ts | 8 + bots/discord/src/index.ts | 25 +++ bots/discord/src/types.d.ts | 4 + bots/discord/src/utils/api/events.ts | 15 ++ bots/discord/src/utils/discord/commands.ts | 19 ++ bots/discord/src/utils/discord/embeds.ts | 48 +++++ bots/discord/src/utils/discord/events.ts | 19 ++ bots/discord/src/utils/discord/messageScan.ts | 140 ++++++++++++++ bots/discord/src/utils/discord/security.ts | 14 ++ bots/discord/src/utils/fs.ts | 17 ++ bots/discord/tsconfig.json | 25 +++ 30 files changed, 1482 insertions(+) create mode 100644 bots/discord/.env.example create mode 100644 bots/discord/.gitignore create mode 100644 bots/discord/config.example.ts create mode 100644 bots/discord/config.ts create mode 100644 bots/discord/package.json create mode 100644 bots/discord/scripts/reload-slash-commands.ts create mode 100644 bots/discord/src/classes/Database.ts create mode 100644 bots/discord/src/commands/index.ts create mode 100644 bots/discord/src/commands/reply.ts create mode 100644 bots/discord/src/commands/stop.ts create mode 100644 bots/discord/src/constants.ts create mode 100644 bots/discord/src/context.ts create mode 100644 bots/discord/src/events/api/disconnect.ts create mode 100644 bots/discord/src/events/api/ready.ts create mode 100644 bots/discord/src/events/discord/guildCreate.ts create mode 100644 bots/discord/src/events/discord/interactionCreate/chat-commmand.ts create mode 100644 bots/discord/src/events/discord/interactionCreate/correct-response.ts create mode 100644 bots/discord/src/events/discord/messageCreate/scan.ts create mode 100644 bots/discord/src/events/discord/messageReactionAdd/correct-response.ts create mode 100644 bots/discord/src/events/discord/ready.ts create mode 100644 bots/discord/src/index.ts create mode 100644 bots/discord/src/types.d.ts create mode 100644 bots/discord/src/utils/api/events.ts create mode 100644 bots/discord/src/utils/discord/commands.ts create mode 100644 bots/discord/src/utils/discord/embeds.ts create mode 100644 bots/discord/src/utils/discord/events.ts create mode 100644 bots/discord/src/utils/discord/messageScan.ts create mode 100644 bots/discord/src/utils/discord/security.ts create mode 100644 bots/discord/src/utils/fs.ts create mode 100755 bots/discord/tsconfig.json diff --git a/bots/discord/.env.example b/bots/discord/.env.example new file mode 100644 index 0000000..2a9d1fb --- /dev/null +++ b/bots/discord/.env.example @@ -0,0 +1 @@ +DISCORD_TOKEN="YOUR-TOKEN-HERE" \ No newline at end of file diff --git a/bots/discord/.gitignore b/bots/discord/.gitignore new file mode 100644 index 0000000..e036eb1 --- /dev/null +++ b/bots/discord/.gitignore @@ -0,0 +1,178 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# 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 + +# Optional stylelint cache + +.stylelintcache + +# 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 variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# 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 + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# 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.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +# DB +*.db \ No newline at end of file diff --git a/bots/discord/config.example.ts b/bots/discord/config.example.ts new file mode 100644 index 0000000..c4d1787 --- /dev/null +++ b/bots/discord/config.example.ts @@ -0,0 +1,80 @@ +export default { + owners: ['USER_ID_HERE'], + allowedGuilds: ['GUILD_ID_HERE'], + messageScan: { + channels: ['CHANNEL_ID_HERE'], + roles: ['ROLE_ID_HERE'], + users: ['USER_ID_HERE'], + whitelist: false, + humanCorrections: { + falsePositiveLabel: 'false_positive', + allowUsers: ['USER_ID_HERE'], + memberRequirements: { + permissions: 8n, + roles: ['ROLE_ID_HERE'], + }, + }, + allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], + responses: [ + { + triggers: [/^regexp?$/, { label: 'label', threshold: 0.85 }], + response: { + title: 'Embed title', + description: 'Embed description', + fields: [ + { + name: 'Field name', + value: 'Field value', + }, + ], + }, + }, + ], + }, + logLevel: 'log', + api: { + websocketUrl: 'ws://127.0.0.1:3000', + }, +} as Config + +export type Config = { + owners: string[] + allowedGuilds: string[] + messageScan?: Partial<{ + roles: string[] + users: string[] + channels: string[] + humanCorrections: { + falsePositiveLabel: string + allowUsers?: string[] + memberRequirements?: { + permissions?: bigint + roles?: string[] + } + } + responses: ConfigMessageScanResponse[] + }> & { whitelist: boolean; allowedAttachmentMimeTypes: string[] } + logLevel: 'none' | 'error' | 'warn' | 'info' | 'log' | 'trace' | 'debug' + api: { + websocketUrl: string + } +} + +export type ConfigMessageScanResponse = { + triggers: Array + response: ConfigMessageScanResponseMessage | null +} + +export type ConfigMessageScanResponseLabelConfig = { + label: string + threshold: number +} + +export type ConfigMessageScanResponseMessage = { + title: string + description?: string + fields?: Array<{ + name: string + value: string + }> +} diff --git a/bots/discord/config.ts b/bots/discord/config.ts new file mode 100644 index 0000000..9e3eeff --- /dev/null +++ b/bots/discord/config.ts @@ -0,0 +1,80 @@ +import type { Config } from './config.example' + +export default { + owners: ['629368283354628116'], + allowedGuilds: ['1205207689832038522'], + messageScan: { + roles: [], + users: ['629368283354628116'], + channels: [], + whitelist: true, + humanCorrections: { + falsePositiveLabel: 'false_positive', + memberRequirements: { + permissions: 8n, + }, + }, + allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], + responses: [ + { + triggers: [ + { + label: 'apps_youtube_buffer', + threshold: 0.85, + }, + ], + response: { + title: 'buffering :jawdroppinbro:', + }, + }, + { + triggers: [/eol/], + response: { + title: 'revenge eol', + description: 'eol', + }, + }, + { + triggers: [/free robux/i], + response: { + title: 'OMG FREE ROBUX????', + }, + }, + { + triggers: [/(hello|hi) world/], + response: { + title: 'Hello, World!', + description: 'This is a test response.', + }, + }, + { + triggers: [ + /how to download revanced/i, + { + label: 'download', + threshold: 0.85, + }, + ], + response: { + title: 'Where or how to get ReVanced ❓', + description: + 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'You can use ReVanced CLI or ReVanced Manager to get ReVanced. Refer to the documentation in <#953993848374325269> `3`.', + }, + ], + }, + }, + { + triggers: [{ label: 'false_positive', threshold: 0 }], + response: null, + }, + ], + }, + logLevel: 'debug', + api: { + websocketUrl: 'ws://127.0.0.1:3000', + }, +} as Config diff --git a/bots/discord/package.json b/bots/discord/package.json new file mode 100644 index 0000000..fd4ac1e --- /dev/null +++ b/bots/discord/package.json @@ -0,0 +1,36 @@ +{ + "name": "@revanced/discord-bot", + "type": "module", + "private": true, + "version": "0.1.0", + "description": "🤖 Discord bot assisting ReVanced", + "main": "dist/index.js", + "scripts": { + "register": "bun run scripts/reload-slash-commands.ts", + "dev": "bun --watch src/index.ts", + "reload-slash": "bun run scripts/reload-slash-commands.ts", + "build": "echo This command is not available yet && exit 1", + "watch": "bun dev" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "bots/discord" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@revanced/bot-api": "workspace:*", + "@revanced/bot-shared": "workspace:*", + "chalk": "^5.3.0", + "discord.js": "^14.14.1" + } +} diff --git a/bots/discord/scripts/reload-slash-commands.ts b/bots/discord/scripts/reload-slash-commands.ts new file mode 100644 index 0000000..ec53ea6 --- /dev/null +++ b/bots/discord/scripts/reload-slash-commands.ts @@ -0,0 +1,42 @@ +import { REST } from '@discordjs/rest' +import { getMissingEnvironmentVariables } from '@revanced/bot-shared' +import { Routes } from 'discord-api-types/v9' +import type { RESTGetCurrentApplicationResult, RESTPutAPIApplicationCommandsResult } from 'discord.js' +import { config, discord } from '../src/context' + +// Check if token exists + +const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN']) +if (missingEnvs.length) { + for (const env of missingEnvs) console.error(`${env} is not defined in environment variables`) + process.exit(1) +} + +const commands = Object.values(discord.commands) +const globalCommands = commands.filter(x => x.global && x.data.dm_permission) +const guildCommands = commands.filter(x => !x.global) + +const rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!) + +try { + const app = (await rest.get(Routes.currentApplication())) as RESTGetCurrentApplicationResult + + if (typeof app === 'object' && app && 'id' in app && typeof app.id === 'string') { + const data = (await rest.put(Routes.applicationCommands(app.id), { + body: globalCommands.map(x => x.data), + })) as RESTPutAPIApplicationCommandsResult + + console.info(`Reloaded ${data.length} global commands.`) + + const guildCommandsMapped = guildCommands.map(x => x.data) + for (const guildId of config.allowedGuilds) { + const data = (await rest.put(Routes.applicationGuildCommands(app.id, guildId), { + body: guildCommandsMapped, + })) as RESTPutAPIApplicationCommandsResult + + console.info(`Reloaded ${data.length} guild commands for guild ${guildId}.`) + } + } +} catch (error) { + console.error(error) +} diff --git a/bots/discord/src/classes/Database.ts b/bots/discord/src/classes/Database.ts new file mode 100644 index 0000000..46db0bc --- /dev/null +++ b/bots/discord/src/classes/Database.ts @@ -0,0 +1,102 @@ +import { Database, type SQLQueryBindings, type Statement } from 'bun:sqlite' + +export class LabeledResponseDatabase { + readonly tableName = 'labeledResponses' + readonly tableStruct = ` + reply TEXT PRIMARY KEY NOT NULL, + channel TEXT NOT NULL, + guild TEXT NOT NULL, + referenceMessage TEXT NOT NULL, + label TEXT NOT NULL, + text TEXT NOT NULL, + correctedBy TEXT, + CHECK ( + typeof("text") = 'text' AND + length("text") > 0 AND + length("text") <= 280 + ) + ` + + #statements: { + save: Statement + edit: Statement + get: Statement + } + + constructor() { + // TODO: put in config + const db = new Database('responses.db', { + create: true, + readwrite: true, + }) + + db.run(`CREATE TABLE IF NOT EXISTS ${this.tableName} ( + ${this.tableStruct} + );`) + + this.#statements = { + save: db.prepare( + `INSERT INTO ${this.tableName} VALUES ($reply, $channel, $guild, $reference, $label, $text, NULL);`, + ), + edit: db.prepare( + `UPDATE ${this.tableName} SET label = $label, correctedBy = $correctedBy WHERE reply = $reply`, + ), + get: db.prepare(`SELECT * FROM ${this.tableName} WHERE reply = $reply`), + } as const + } + + save({ reply, channel, guild, referenceMessage, label, text }: Omit) { + const actualText = text.slice(0, 280) + this.#statements.save.run({ + $reply: reply, + $channel: channel, + $guild: guild, + $reference: referenceMessage, + $label: label, + $text: actualText, + }) + } + + get(reply: string) { + return this.#statements.get.get({ $reply: reply }) + } + + edit(reply: string, { label, correctedBy }: Pick) { + this.#statements.edit.run({ + $reply: reply, + $label: label, + $correctedBy: correctedBy, + }) + } +} + +export type LabeledResponse = { + /** + * The label of the response + */ + label: string + /** + * The ID of the user who corrected the response + */ + correctedBy: string | null + /** + * The text content of the response + */ + text: string + /** + * The ID of the message that triggered the response + */ + referenceMessage: string + /** + * The ID of the channel where the response was sent + */ + channel: string + /** + * The ID of the guild where the response was sent + */ + guild: string + /** + * The ID of the reply + */ + reply: string +} diff --git a/bots/discord/src/commands/index.ts b/bots/discord/src/commands/index.ts new file mode 100644 index 0000000..d9c2f87 --- /dev/null +++ b/bots/discord/src/commands/index.ts @@ -0,0 +1,50 @@ +import type { SlashCommandBuilder } from '@discordjs/builders' +import type { ChatInputCommandInteraction } from 'discord.js' + +// Temporary system +export type Command = { + data: ReturnType + // The function has to return void or Promise + // because TS may complain about some code paths not returning a value + /** + * The function to execute when this command is triggered + * @param interaction The interaction that triggered this command + */ + execute: (context: typeof import('../context'), interaction: ChatInputCommandInteraction) => Promise | void + memberRequirements?: { + /** + * The mode to use when checking for requirements. + * - `all` means that the user needs meet all requirements specified. + * - `any` means that the user needs to meet any of the requirements specified. + * + * @default "all" + */ + mode?: 'all' | 'any' + /** + * The permissions required to use this command (in BitFields). + * For safety reasons, this is set to `-1n` and only bot owners can use this command unless explicitly specified. + * + * - **0n** means that everyone can use this command. + * - **-1n** means that only bot owners can use this command. + * @default -1n + */ + permissions?: bigint + /** + * The roles required to use this command. + * By default, this is set to `[]`. + */ + roles?: string[] + } + /** + * Whether this command can only be used by bot owners. + * For safety reasons, this is set to `true` and only bot owners can use this command unless explicitly specified. + * @default true + */ + ownerOnly?: boolean + /** + * Whether to register this command as a global slash command. + * For safety reasons, this is set to `false` and commands will be registered in allowed guilds only. + * @default false + */ + global?: boolean +} diff --git a/bots/discord/src/commands/reply.ts b/bots/discord/src/commands/reply.ts new file mode 100644 index 0000000..b73e1c2 --- /dev/null +++ b/bots/discord/src/commands/reply.ts @@ -0,0 +1,54 @@ +import { PermissionFlagsBits, SlashCommandBuilder, type TextBasedChannel } from 'discord.js' +import type { Command } from '.' + +export default { + data: new SlashCommandBuilder() + .setName('reply') + .setDescription('Send a message as the bot') + .addStringOption(option => option.setName('message').setDescription('The message to send').setRequired(true)) + .addStringOption(option => + option + .setName('reference') + .setDescription('The message ID to reply to (use `latest` to reply to the latest message)') + .setRequired(false), + ) + .toJSON(), + + memberRequirements: { + mode: 'all', + roles: ['955220417969262612', '973886585294704640'], + permissions: PermissionFlagsBits.ManageMessages, + }, + + global: false, + + async execute({ logger }, interaction) { + const msg = interaction.options.getString('message', true) + const ref = interaction.options.getString('reference') + + const resolvedRef = ref?.startsWith('latest') + ? (await interaction.channel?.messages.fetch({ limit: 1 }))?.at(0)?.id + : ref + + try { + const channel = (await interaction.guild!.channels.fetch(interaction.channelId)) as TextBasedChannel | null + if (!channel) throw new Error('Channel not found (or not cached)') + + await channel.send({ + content: msg, + reply: { + messageReference: resolvedRef!, + failIfNotExists: true, + }, + }) + + logger.warn(`User ${interaction.user.tag} made the bot say: ${msg}`) + await interaction.reply({ + content: 'OK!', + ephemeral: true, + }) + } catch (e) { + await interaction.reply({}) + } + }, +} satisfies Command diff --git a/bots/discord/src/commands/stop.ts b/bots/discord/src/commands/stop.ts new file mode 100644 index 0000000..14199a6 --- /dev/null +++ b/bots/discord/src/commands/stop.ts @@ -0,0 +1,28 @@ +import { SlashCommandBuilder } from 'discord.js' +import type { Command } from '.' + +export default { + data: new SlashCommandBuilder().setName('stop').setDescription('Stops the bot').toJSON(), + + ownerOnly: true, + global: true, + + async execute({ api, logger }, interaction) { + api.isStopping = true + + logger.fatal('Stopping bot...') + await interaction.reply({ + content: 'Stopping... (I will go offline once done)', + ephemeral: true, + }) + + api.client.disconnect() + logger.warn('Disconnected from API') + + await interaction.client.destroy() + logger.warn('Disconnected from Discord API') + + logger.info(`Bot stopped, requested by ${interaction.user.id}`) + process.exit(0) + }, +} satisfies Command diff --git a/bots/discord/src/constants.ts b/bots/discord/src/constants.ts new file mode 100644 index 0000000..8e25126 --- /dev/null +++ b/bots/discord/src/constants.ts @@ -0,0 +1,9 @@ +export const MessageScanLabeledResponseReactions = { + train: '👍', + edit: '🔧', + delete: '❌', +} as const + +export const DefaultEmbedColor = '#4E98F0' +export const ReVancedLogoURL = + 'https://media.discordapp.net/attachments/1095487869923119144/1115436493050224660/revanced-logo.png' diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts new file mode 100644 index 0000000..c09b7d0 --- /dev/null +++ b/bots/discord/src/context.ts @@ -0,0 +1,56 @@ +import { loadCommands } from '$utils/discord/commands' +import { Client as APIClient } from '@revanced/bot-api' +import { createLogger } from '@revanced/bot-shared' +import { ActivityType, Client as DiscordClient, Partials } from 'discord.js' +import config from '../config' +import { LabeledResponseDatabase } from './classes/Database' + +export { config } +export const logger = createLogger({ + level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, +}) + +export const api = { + client: new APIClient({ + api: { + websocket: { + url: config.api.websocketUrl, + }, + }, + }), + isStopping: false, + disconnectCount: 0, +} + +export const database = { + labeledResponses: new LabeledResponseDatabase(), +} as const + +export const discord = { + client: new DiscordClient({ + intents: [ + 'Guilds', + 'GuildMembers', + 'GuildModeration', + 'GuildMessages', + 'GuildMessageReactions', + 'DirectMessages', + 'DirectMessageReactions', + 'MessageContent', + ], + allowedMentions: { + parse: ['users'], + repliedUser: true, + }, + partials: [Partials.Message, Partials.Reaction], + presence: { + activities: [ + { + type: ActivityType.Watching, + name: 'cat videos', + }, + ], + }, + }), + commands: await loadCommands(), +} as const diff --git a/bots/discord/src/events/api/disconnect.ts b/bots/discord/src/events/api/disconnect.ts new file mode 100644 index 0000000..98a3c2c --- /dev/null +++ b/bots/discord/src/events/api/disconnect.ts @@ -0,0 +1,30 @@ +import { on } from '$utils/api/events' +import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared' +import { api, logger } from 'src/context' + +on('disconnect', (reason, msg) => { + if (reason === DisconnectReason.PlannedDisconnect && api.isStopping) return + + const ws = api.client.ws + if (!ws.disconnected) ws.disconnect() + + logger.fatal( + `Disconnected from the bot API ${ + reason in HumanizedDisconnectReason + ? `because ${HumanizedDisconnectReason[reason as keyof typeof HumanizedDisconnectReason]}` + : 'for an unknown reason' + }`, + ) + + // TODO: move to config + if (api.disconnectCount >= 3) { + console.error(new Error('Disconnected from bot API too many times')) + // We don't want the process hanging + process.exit(1) + } + + logger.info( + `Disconnected from bot API ${++api.disconnectCount} times (this time because: ${reason}, ${msg}), reconnecting again...`, + ) + setTimeout(() => ws.connect(), 10000) +}) diff --git a/bots/discord/src/events/api/ready.ts b/bots/discord/src/events/api/ready.ts new file mode 100644 index 0000000..4df8ab9 --- /dev/null +++ b/bots/discord/src/events/api/ready.ts @@ -0,0 +1,6 @@ +import { on } from '$utils/api/events' +import { logger } from 'src/context' + +on('ready', () => { + logger.info('Connected to the bot API') +}) diff --git a/bots/discord/src/events/discord/guildCreate.ts b/bots/discord/src/events/discord/guildCreate.ts new file mode 100644 index 0000000..5d04774 --- /dev/null +++ b/bots/discord/src/events/discord/guildCreate.ts @@ -0,0 +1,7 @@ +import { on } from '$utils/discord/events' +import { leaveDisallowedGuild } from '$utils/discord/security' + +on('guildCreate', async ({ config }, guild) => { + if (config.allowedGuilds.includes(guild.id)) return + await leaveDisallowedGuild(guild) +}) diff --git a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts new file mode 100644 index 0000000..bd1536d --- /dev/null +++ b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts @@ -0,0 +1,78 @@ +import { createErrorEmbed } from '$utils/discord/embeds' +import { on } from '$utils/discord/events' + +export default on('interactionCreate', async (context, interaction) => { + if (!interaction.isChatInputCommand()) return + + const { logger, discord, config } = context + const command = discord.commands[interaction.commandName] + + logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag}`) + + if (!command) { + logger.error(`Command ${interaction.commandName} not implemented but registered!!!`) + return void interaction.reply({ + embeds: [ + createErrorEmbed( + 'Command not implemented', + 'This command has not been implemented yet. Please report this to the developers.', + ), + ], + ephemeral: true, + }) + } + + const userIsOwner = config.owners.includes(interaction.user.id) + + if ((command.ownerOnly ?? true) && !userIsOwner) + return void (await interaction.reply({ + embeds: [createErrorEmbed('Massive skill issue', 'This command can only be used by the bot owners.')], + ephemeral: true, + })) + + if (interaction.inGuild()) { + // Bot owners get bypass + if (command.memberRequirements && !userIsOwner) { + const { permissions = -1n, roles = [], mode } = command.memberRequirements + + const member = await interaction.guild!.members.fetch(interaction.user.id) + + const [missingPermissions, missingRoles] = [ + // This command is an owner-only command (the user is not an owner, checked above) + permissions < 0n || + // or the user doesn't have the required permissions + (permissions >= 0n && !interaction.memberPermissions.has(permissions)), + + // If not: + !roles.some(x => member.roles.cache.has(x)), + ] + + if ((mode === 'any' && missingPermissions && missingRoles) || missingPermissions || missingRoles) + return void interaction.reply({ + embeds: [ + createErrorEmbed( + 'Missing roles or permissions', + "You don't have the required roles or permissions to use this command.", + ), + ], + ephemeral: true, + }) + } + } + + try { + logger.debug(`Command ${interaction.commandName} being executed`) + await command.execute(context, interaction) + } catch (err) { + logger.error(`Error while executing command ${interaction.commandName}:`, err) + await interaction.reply({ + embeds: [ + createErrorEmbed( + 'An error occurred while executing this command', + 'Please report this to the developers.', + ), + ], + ephemeral: true, + }) + } +}) diff --git a/bots/discord/src/events/discord/interactionCreate/correct-response.ts b/bots/discord/src/events/discord/interactionCreate/correct-response.ts new file mode 100644 index 0000000..9ded5dc --- /dev/null +++ b/bots/discord/src/events/discord/interactionCreate/correct-response.ts @@ -0,0 +1,111 @@ +import { handleUserResponseCorrection } from '$/utils/discord/messageScan' +import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds' +import { on } from '$utils/discord/events' + +import type { ButtonInteraction, StringSelectMenuInteraction, TextBasedChannel } from 'discord.js' + +// No permission check required as it is already done when the user reacts to a bot response +export default on('interactionCreate', async (context, interaction) => { + const { + logger, + database: db, + config: { messageScan: msConfig }, + } = context + + if (!msConfig?.humanCorrections) return + if (!interaction.isStringSelectMenu() && !interaction.isButton()) return + if (!interaction.customId.startsWith('cr_')) return + + const [, key, action] = interaction.customId.split('_') as ['cr', string, 'select' | 'cancel' | 'delete'] + if (!key || !action) return + + const response = db.labeledResponses.get(key) + // If the message isn't saved in my DB (unrelated message) + if (!response) + return void (await interaction.reply({ + content: "I don't recall having sent this response, so I cannot correct it.", + ephemeral: true, + })) + + try { + // We're gonna pretend reactionChannel is a text-based channel, but it can be many more + // But `messages` should always exist as a property + const reactionGuild = await interaction.client.guilds.fetch(response.guild) + const reactionChannel = (await reactionGuild.channels.fetch(response.channel)) as TextBasedChannel | null + const reactionMessage = await reactionChannel?.messages.fetch(key) + + if (!reactionMessage) { + await interaction.deferUpdate() + await interaction.message.edit({ + content: null, + embeds: [ + createErrorEmbed( + 'Response not found', + 'Thank you for your feedback! Unfortunately, the response message could not be found (most likely deleted).', + ), + ], + components: [], + }) + + return + } + + const editMessage = (content: string, description?: string) => + editInteractionMessage(interaction, reactionMessage.url, content, description) + const handleCorrection = (label: string) => + handleUserResponseCorrection(context, response, reactionMessage, label, interaction.user) + + if (response.correctedBy) + return await editMessage( + 'Response already corrected', + 'Thank you for your feedback! Unfortunately, this response has already been corrected by someone else.', + ) + + // We immediately know that the action is `select` + if (interaction.isStringSelectMenu()) { + const selectedLabel = interaction.values[0]! + + await handleCorrection(selectedLabel) + await editMessage( + 'Message being trained', + `Thank you for your feedback! I've edited the response according to the selected label (\`${selectedLabel}\`). The message is now being trained. 🎉`, + ) + } else { + switch (action) { + case 'cancel': + await editMessage('Canceled', 'You canceled this interaction. 😞') + break + case 'delete': + await handleCorrection(msConfig.humanCorrections.falsePositiveLabel) + await editMessage( + 'Marked as false positive', + 'The response has been deleted and marked as a false positive. Thank you for your feedback. 🎉', + ) + + break + } + } + } catch (e) { + logger.error('Failed to handle correct response interaction:', e) + await interaction.reply({ + embeds: [createStackTraceEmbed(e)], + ephemeral: true, + }) + } +}) + +const editInteractionMessage = async ( + interaction: StringSelectMenuInteraction | ButtonInteraction, + replyUrl: string, + title: string, + description?: string, +) => { + if (!interaction.deferred) await interaction.deferUpdate() + await interaction.message.edit({ + content: null, + embeds: [ + createSuccessEmbed(title, `${description ?? ''}\n\n**⬅️ Back to bot response**: ${replyUrl}`.trimStart()), + ], + components: [], + }) +} diff --git a/bots/discord/src/events/discord/messageCreate/scan.ts b/bots/discord/src/events/discord/messageCreate/scan.ts new file mode 100644 index 0000000..67bd10e --- /dev/null +++ b/bots/discord/src/events/discord/messageCreate/scan.ts @@ -0,0 +1,70 @@ +import { MessageScanLabeledResponseReactions } from '$/constants' +import { getResponseFromContent, shouldScanMessage } from '$/utils/discord/messageScan' +import { createMessageScanResponseEmbed } from '$utils/discord/embeds' +import { on } from '$utils/discord/events' + +on('messageCreate', async (ctx, msg) => { + const { + api, + config: { messageScan: config }, + database: db, + logger, + } = ctx + + if (!config || !config.responses) return + if (!shouldScanMessage(msg, config)) return + + if (msg.content.length) { + logger.debug(`Classifying message ${msg.id}`) + + const { response, label } = await getResponseFromContent(msg.content, ctx) + + if (response) { + logger.debug('Response found') + + const reply = await msg.reply({ + embeds: [createMessageScanResponseEmbed(response)], + }) + + if (label) + db.labeledResponses.save({ + reply: reply.id, + channel: reply.channel.id, + guild: reply.guild.id, + referenceMessage: msg.id, + label, + text: msg.content, + }) + + if (label) { + for (const reaction of Object.values(MessageScanLabeledResponseReactions)) { + await reply.react(reaction) + } + } + } + } + + if (msg.attachments.size > 0) { + logger.debug(`Classifying message attachments for ${msg.id}`) + + for (const attachment of msg.attachments.values()) { + if (attachment.contentType && !config.allowedAttachmentMimeTypes.includes(attachment.contentType)) continue + + try { + const { text: content } = await api.client.parseImage(attachment.url) + const { response } = await getResponseFromContent(content, ctx) + + if (response) { + logger.debug(`Response found for attachment: ${attachment.url}`) + await msg.reply({ + embeds: [createMessageScanResponseEmbed(response)], + }) + + break + } + } catch { + logger.error(`Failed to parse image: ${attachment.url}`) + } + } + } +}) diff --git a/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts b/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts new file mode 100644 index 0000000..5ef962b --- /dev/null +++ b/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts @@ -0,0 +1,130 @@ +import { MessageScanLabeledResponseReactions as Reactions } from '$/constants' +import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$/utils/discord/embeds' +import { on } from '$/utils/discord/events' + +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, +} from 'discord.js' + +import { handleUserResponseCorrection } from '$/utils/discord/messageScan' +import type { ConfigMessageScanResponseLabelConfig } from 'config.example' + +const PossibleReactions = Object.values(Reactions) as string[] + +on('messageReactionAdd', async (context, rct, user) => { + if (user.bot) return + + const { database: db, logger, config } = context + const { messageScan: msConfig } = config + + // If there's no config, we can't do anything + if (!msConfig?.humanCorrections) return + + const reaction = await rct.fetch() + const reactionMessage = await reaction.message.fetch() + + if (reactionMessage.author.id !== reaction.client.user!.id) return + if (!PossibleReactions.includes(reaction.emoji.name!)) return + + if (reactionMessage.inGuild() && msConfig.humanCorrections.memberRequirements) { + const { + memberRequirements: { roles, permissions }, + } = msConfig.humanCorrections + + if (!roles && !permissions) + return void logger.warn( + 'No member requirements specified for human corrections, ignoring this request for security reasons', + ) + + const member = await reactionMessage.guild.members.fetch(user.id) + + if ( + permissions && + !member.permissions.has(permissions) && + roles && + !roles.some(role => member.roles.cache.has(role)) + ) + return + // User is not owner, and not included in allowUsers + } else if (!config.owners.includes(user.id) && !msConfig.humanCorrections.allowUsers?.includes(user.id)) return + + // Sanity check + const response = db.labeledResponses.get(rct.message.id) + if (!response || response.correctedBy) return + + const handleCorrection = (label: string) => + handleUserResponseCorrection(context, response, reactionMessage, label, user) + + try { + if (reaction.emoji.name === Reactions.train) { + // Bot is right, nice! + + await handleCorrection(response.label) + await user.send({ embeds: [createSuccessEmbed('Trained message', 'Thank you for your feedback.')] }) + } else if (reaction.emoji.name === Reactions.edit) { + // Bot is wrong :( + + const labels = msConfig.responses!.flatMap(r => + r.triggers.filter((t): t is ConfigMessageScanResponseLabelConfig => 'label' in t).map(t => t.label), + ) + + const componentPrefix = `cr_${reactionMessage.id}` + const select = new StringSelectMenuBuilder().setCustomId(`${componentPrefix}_select`) + + for (const label of labels) { + const opt = new StringSelectMenuOptionBuilder().setLabel(label).setValue(label) + + if (label === response.label) { + opt.setDefault(true) + opt.setLabel(`${label} (current)`) + opt.setDescription('This is the current label of the message') + } + + select.addOptions(opt) + } + + const rows = [ + new ActionRowBuilder().addComponents(select), + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setEmoji('⬅️') + .setLabel('Cancel') + .setStyle(ButtonStyle.Secondary) + .setCustomId(`${componentPrefix}_cancel`), + new ButtonBuilder() + .setEmoji(Reactions.delete) + .setLabel('Delete (mark as false positive)') + .setStyle(ButtonStyle.Danger) + .setCustomId(`${componentPrefix}_delete`), + ), + ] + + await user.send({ + content: 'Please pick the right label for the message (you can only do this once!)', + components: rows, + }) + } else if (reaction.emoji.name === Reactions.delete) { + await handleCorrection(msConfig.humanCorrections.falsePositiveLabel) + await user.send({ content: 'The response has been deleted and marked as a false positive.' }) + } + } catch (e) { + logger.error('Failed to correct response:', e) + user.send({ + embeds: [createStackTraceEmbed(e)], + }).catch(() => { + reactionMessage.reply({ + content: `<@${user.id}>`, + embeds: [ + createErrorEmbed( + 'Enable your DMs!', + 'I cannot send you messages. Please enable your DMs to use this feature.', + ), + ], + }) + }) + } +}) diff --git a/bots/discord/src/events/discord/ready.ts b/bots/discord/src/events/discord/ready.ts new file mode 100644 index 0000000..72f4bf1 --- /dev/null +++ b/bots/discord/src/events/discord/ready.ts @@ -0,0 +1,8 @@ +import { on } from 'src/utils/discord/events' + +export default on('ready', ({ logger }, client) => { + logger.info(`Connected to Discord API, logged in as ${client.user.displayName} (@${client.user.tag})!`) + logger.info( + `Bot is in ${client.guilds.cache.size} guilds, if this is not expected, please run the /leave-unknowns command`, + ) +}) diff --git a/bots/discord/src/index.ts b/bots/discord/src/index.ts new file mode 100644 index 0000000..239a244 --- /dev/null +++ b/bots/discord/src/index.ts @@ -0,0 +1,25 @@ +import { listAllFilesRecursive } from '$utils/fs' +import { getMissingEnvironmentVariables } from '@revanced/bot-shared' +import { api, discord, logger } from './context' + +for (const apiEvents of await listAllFilesRecursive('src/events/api')) { + await import(apiEvents) +} + +const { client: apiClient } = api +await apiClient.ws.connect() + +for (const discordEvents of await listAllFilesRecursive('src/events/discord')) { + await import(discordEvents) +} + +const { client: discordClient } = discord + +// Check if token exists +const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN']) +if (missingEnvs.length) { + for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`) + process.exit(1) +} + +await discordClient.login() diff --git a/bots/discord/src/types.d.ts b/bots/discord/src/types.d.ts new file mode 100644 index 0000000..5e2602f --- /dev/null +++ b/bots/discord/src/types.d.ts @@ -0,0 +1,4 @@ +type IfExtends = T extends U ? True : False +type IfTrue = IfExtends +type EmptyObject = Record +type ValuesOf = T[keyof T] diff --git a/bots/discord/src/utils/api/events.ts b/bots/discord/src/utils/api/events.ts new file mode 100644 index 0000000..140fbf9 --- /dev/null +++ b/bots/discord/src/utils/api/events.ts @@ -0,0 +1,15 @@ +import type { ClientWebSocketEvents } from '@revanced/bot-api' +import { api } from '../../context' + +const { client } = api + +export function on(event: Event, listener: ListenerOf) { + client.on(event, listener) +} + +export function once(event: Event, listener: ListenerOf) { + client.once(event, listener) +} + +export type EventName = keyof ClientWebSocketEvents +export type ListenerOf = ClientWebSocketEvents[Event] diff --git a/bots/discord/src/utils/discord/commands.ts b/bots/discord/src/utils/discord/commands.ts new file mode 100644 index 0000000..008d80c --- /dev/null +++ b/bots/discord/src/utils/discord/commands.ts @@ -0,0 +1,19 @@ +import type { Command } from '$commands' +import { listAllFilesRecursive } from '$utils/fs' + +export const loadCommands = async () => { + const commandsMap: Record = {} + const files = await listAllFilesRecursive('src/commands') + const commands = await Promise.all( + files.map(async file => { + const command = await import(file) + return command.default + }), + ) + + for (const command of commands) { + if (command) commandsMap[command.data.name] = command + } + + return commandsMap +} diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts new file mode 100644 index 0000000..035fd11 --- /dev/null +++ b/bots/discord/src/utils/discord/embeds.ts @@ -0,0 +1,48 @@ +import { DefaultEmbedColor, ReVancedLogoURL } from '$/constants' +import { EmbedBuilder } from 'discord.js' +import type { ConfigMessageScanResponseMessage } from '../../../config.example' + +export const createErrorEmbed = (title: string, description?: string) => + applyCommonStyles( + new EmbedBuilder() + .setTitle(title) + .setDescription(description ?? null) + .setAuthor({ name: 'Error' }) + .setColor('Red'), + false, + ) + +export const createStackTraceEmbed = (stack: unknown) => + // biome-ignore lint/style/useTemplate: shut + createErrorEmbed('An exception was thrown', '```js' + stack + '```') + +export const createSuccessEmbed = (title: string, description?: string) => + applyCommonStyles( + new EmbedBuilder() + .setTitle(title) + .setDescription(description ?? null) + .setAuthor({ name: 'Success' }) + .setColor('Green'), + false, + ) + +export const createMessageScanResponseEmbed = (response: ConfigMessageScanResponseMessage) => { + const embed = new EmbedBuilder().setTitle(response.title) + + if (response.description) embed.setDescription(response.description) + if (response.fields) embed.addFields(response.fields) + + return applyCommonStyles(embed) +} + +const applyCommonStyles = (embed: EmbedBuilder, setColor = true, setThumbnail = true) => { + embed.setFooter({ + text: 'ReVanced', + iconURL: ReVancedLogoURL, + }) + + if (setColor) embed.setColor(DefaultEmbedColor) + if (setThumbnail) embed.setThumbnail(ReVancedLogoURL) + + return embed +} diff --git a/bots/discord/src/utils/discord/events.ts b/bots/discord/src/utils/discord/events.ts new file mode 100644 index 0000000..97e0205 --- /dev/null +++ b/bots/discord/src/utils/discord/events.ts @@ -0,0 +1,19 @@ +import * as context from '$/context' +import type { ClientEvents } from 'discord.js' + +const { client } = context.discord + +export const on = (event: Event, listener: ListenerOf) => + client.on(event, (...args) => listener(context, ...args)) + +export const once = (event: Event, listener: ListenerOf) => + client.once(event, (...args) => listener(context, ...args)) + +export type EventName = keyof ClientEvents +export type EventMap = { + [K in EventName]: ListenerOf +} + +type ListenerOf = ( + ...args: [typeof import('$/context'), ...ClientEvents[Event]] +) => void | Promise diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts new file mode 100644 index 0000000..4cd025a --- /dev/null +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -0,0 +1,140 @@ +import type { LabeledResponse } from '$/classes/Database' +import type { Config, ConfigMessageScanResponseLabelConfig, ConfigMessageScanResponseMessage } from 'config.example' +import type { Message, PartialUser, User } from 'discord.js' +import { createMessageScanResponseEmbed } from './embeds' + +export const getResponseFromContent = async ( + content: string, + { api, logger, config: { messageScan: config } }: typeof import('src/context'), +) => { + if (!config || !config.responses) { + logger.warn('No message scan config found') + + return { + response: null, + label: undefined, + } + } + + let label: string | undefined + let response: ConfigMessageScanResponseMessage | undefined | null + const firstLabelIndexes: number[] = [] + + // Test if all regexes before a label trigger is matched + for (const trigger of config.responses) { + const { triggers, response: resp } = trigger + if (response) break + + for (let i = 0; i < triggers.length; i++) { + const trigger = triggers[i]! + + if (trigger instanceof RegExp) { + if (trigger.test(content)) { + logger.debug(`Message matched regex (before mode): ${trigger.source}`) + response = resp + break + } + } else { + firstLabelIndexes.push(i) + break + } + } + } + + // If none of the regexes match, we can search for labels immediately + if (!response) { + const scan = await api.client.parseText(content) + if (scan.labels.length) { + const matchedLabel = scan.labels[0]! + logger.debug(`Message matched label with confidence: ${matchedLabel.name}, ${matchedLabel.confidence}`) + + let triggerConfig: ConfigMessageScanResponseLabelConfig | undefined + const labelConfig = config.responses.find(x => { + const config = x.triggers.find( + (x): x is ConfigMessageScanResponseLabelConfig => 'label' in x && x.label === matchedLabel.name, + ) + if (config) triggerConfig = config + return config + }) + + if (!labelConfig) { + logger.warn(`No label config found for label ${matchedLabel.name}`) + return { response: null, label: undefined } + } + + if (matchedLabel.confidence >= triggerConfig!.threshold) { + logger.debug('Label confidence is enough') + label = matchedLabel.name + response = labelConfig.response + } + } + } + + // If we still don't have a label, we can match all regexes after the initial label trigger + if (!response) + for (let i = 0; i < config.responses.length; i++) { + const { triggers, response: resp } = config.responses[i]! + const firstLabelIndex = firstLabelIndexes[i] ?? -1 + + for (let i = firstLabelIndex + 1; i < triggers.length; i++) { + const trigger = triggers[i]! + + if (trigger instanceof RegExp) { + if (trigger.test(content)) { + logger.debug(`Message matched regex (after mode): ${trigger.source}`) + response = resp + break + } + } + } + } + + return { + response, + label, + } +} + +export const shouldScanMessage = ( + message: Message, + config: NonNullable, +): message is Message => { + if (message.author.bot) return false + if (!message.guild) return false + + const filters = [ + config.users?.includes(message.author.id), + message.member?.roles.cache.some(x => config.roles?.includes(x.id)), + config.channels?.includes(message.channel.id), + ] + + if (config.whitelist && filters.every(x => !x)) return false + if (!config.whitelist && filters.some(x => x)) return false + + return true +} + +export const handleUserResponseCorrection = async ( + { api, database: db, config: { messageScan: msConfig }, logger }: typeof import('$/context'), + response: LabeledResponse, + reply: Message, + label: string, + user: User | PartialUser, +) => { + const correctLabelResponse = msConfig!.responses!.find(r => r.triggers.some(t => 'label' in t && t.label === label)) + + if (!correctLabelResponse) throw new Error('Cannot find label config for the selected label') + if (!correctLabelResponse.response) return void (await reply.delete()) + + if (response.label !== label) { + db.labeledResponses.edit(response.reply, { label, correctedBy: user.id }) + await reply.edit({ + embeds: [createMessageScanResponseEmbed(correctLabelResponse.response)], + }) + } + + await api.client.trainMessage(response.text, label) + logger.debug(`User ${user.id} trained message ${response.reply} as ${label} (positive)`) + + await reply.reactions.removeAll() +} diff --git a/bots/discord/src/utils/discord/security.ts b/bots/discord/src/utils/discord/security.ts new file mode 100644 index 0000000..a99ddfc --- /dev/null +++ b/bots/discord/src/utils/discord/security.ts @@ -0,0 +1,14 @@ +import type { Guild, GuildManager } from 'discord.js' +import { config, logger } from '../../context' + +export function leaveDisallowedGuild(guild: Guild) { + logger.warn(`Server ${guild.name} (${guild.id}) is not allowed to use this bot.`) + return guild.leave().then(() => logger.debug(`Left guild ${guild.name} (${guild.id})`)) +} + +export async function leaveDisallowedGuilds(guildManager: GuildManager) { + const guilds = await guildManager.fetch() + for (const [id, guild] of guilds) { + if (!config.allowedGuilds.includes(id)) await leaveDisallowedGuild(await guild.fetch()) + } +} diff --git a/bots/discord/src/utils/fs.ts b/bots/discord/src/utils/fs.ts new file mode 100644 index 0000000..6bc0e4f --- /dev/null +++ b/bots/discord/src/utils/fs.ts @@ -0,0 +1,17 @@ +import { join } from 'path' +import { readdir, stat } from 'fs/promises' + +export async function listAllFilesRecursive(dir: string): Promise { + const files = await readdir(dir) + const result: string[] = [] + for (const file of files) { + const filePath = join(dir, file) + const fileStat = await stat(filePath) + if (fileStat.isDirectory()) { + result.push(...(await listAllFilesRecursive(filePath))) + } else { + result.push(filePath) + } + } + return result +} diff --git a/bots/discord/tsconfig.json b/bots/discord/tsconfig.json new file mode 100755 index 0000000..4729956 --- /dev/null +++ b/bots/discord/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "module": "ESNext", + "target": "ESNext", + "lib": ["ESNext"], + "composite": false, + "exactOptionalPropertyTypes": false, + "esModuleInterop": true, + "paths": { + "$/*": ["./src/*"], + "$constants": ["./src/constants"], + "$utils/*": ["./src/utils/*"], + "$classes": ["./src/classes/index.ts"], + "$classes/*": ["./src/classes/*"], + "$commands": ["./src/commands/index.ts"], + "$commands/*": ["./src/commands/*"] + }, + "skipLibCheck": true + }, + "exclude": ["node_modules", "dist"], + "include": ["./*.json", "src/**/*.ts", "scripts/**/*.ts"] +} From a68d72687584332587455962b0202a306288057d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 17:17:14 +0700 Subject: [PATCH 057/312] feat(bots/discord): add a better way to manage databases --- bots/discord/src/classes/Database.ts | 127 ++++++++++++++++----------- 1 file changed, 76 insertions(+), 51 deletions(-) diff --git a/bots/discord/src/classes/Database.ts b/bots/discord/src/classes/Database.ts index 46db0bc..d0b7b54 100644 --- a/bots/discord/src/classes/Database.ts +++ b/bots/discord/src/classes/Database.ts @@ -1,72 +1,97 @@ -import { Database, type SQLQueryBindings, type Statement } from 'bun:sqlite' +import { Database } from 'bun:sqlite' -export class LabeledResponseDatabase { - readonly tableName = 'labeledResponses' - readonly tableStruct = ` - reply TEXT PRIMARY KEY NOT NULL, - channel TEXT NOT NULL, - guild TEXT NOT NULL, - referenceMessage TEXT NOT NULL, - label TEXT NOT NULL, - text TEXT NOT NULL, - correctedBy TEXT, - CHECK ( - typeof("text") = 'text' AND - length("text") > 0 AND - length("text") <= 280 - ) - ` +type BasicSQLBindings = string | number | null - #statements: { - save: Statement - edit: Statement - get: Statement - } +export class BasicDatabase> { + #db: Database + #table: string - constructor() { - // TODO: put in config - const db = new Database('responses.db', { + constructor(file: string, struct: string, tableName = 'data') { + const db = new Database(file, { create: true, readwrite: true, }) - db.run(`CREATE TABLE IF NOT EXISTS ${this.tableName} ( - ${this.tableStruct} - );`) + this.#db = db + this.#table = tableName - this.#statements = { - save: db.prepare( - `INSERT INTO ${this.tableName} VALUES ($reply, $channel, $guild, $reference, $label, $text, NULL);`, - ), - edit: db.prepare( - `UPDATE ${this.tableName} SET label = $label, correctedBy = $correctedBy WHERE reply = $reply`, - ), - get: db.prepare(`SELECT * FROM ${this.tableName} WHERE reply = $reply`), - } as const + db.run(`CREATE TABLE IF NOT EXISTS ${tableName} (${struct});`) + } + + run(statement: string) { + this.#db.run(statement) + } + + prepare(statement: string) { + return this.#db.prepare(statement) + } + + query(statement: string) { + return this.#db.query(statement) + } + + insert(...values: BasicSQLBindings[]) { + this.run(`INSERT INTO ${this.#table} VALUES (${values.map(this.#encodeValue).join(', ')});`) + } + + update(what: Partial, where: string) { + const set = Object.entries(what) + .map(([key, value]) => `${key} = ${this.#encodeValue(value)}`) + .join(', ') + + this.run(`UPDATE ${this.#table} SET ${set} WHERE ${where};`) + } + + delete(where: string) { + this.run(`DELETE FROM ${this.#table} WHERE ${where};`) + } + + select(columns: string[] | string, where: string) { + const realColumns = Array.isArray(columns) ? columns.join(', ') : columns + return this.query(`SELECT ${realColumns} FROM ${this.#table} WHERE ${where};`).get() + } + + #encodeValue(value: unknown) { + if (typeof value === 'string') return `'${value}'` + if (typeof value === 'number') return value + if (typeof value === 'boolean') return value ? 1 : 0 + if (value === null) return 'NULL' + return null + } +} + +export class LabeledResponseDatabase { + #db: BasicDatabase + + constructor() { + this.#db = new BasicDatabase( + 'responses.db', + `reply TEXT PRIMARY KEY NOT NULL, + channel TEXT NOT NULL, + guild TEXT NOT NULL, + referenceMessage TEXT KEY NOT NULL, + label TEXT NOT NULL, + text TEXT NOT NULL, + correctedBy TEXT, + CHECK ( + typeof("text") = 'text' AND + length("text") > 0 AND + length("text") <= 280 + )`, + ) } save({ reply, channel, guild, referenceMessage, label, text }: Omit) { const actualText = text.slice(0, 280) - this.#statements.save.run({ - $reply: reply, - $channel: channel, - $guild: guild, - $reference: referenceMessage, - $label: label, - $text: actualText, - }) + this.#db.insert(reply, channel, guild, referenceMessage, label, actualText, null) } get(reply: string) { - return this.#statements.get.get({ $reply: reply }) + return this.#db.select('*', `reply = ${reply}`) } edit(reply: string, { label, correctedBy }: Pick) { - this.#statements.edit.run({ - $reply: reply, - $label: label, - $correctedBy: correctedBy, - }) + this.#db.update({ label, correctedBy }, `reply = ${reply}`) } } From 7eeb6312707c46fc74809bb83a13c2d082d6c865 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 17:22:02 +0700 Subject: [PATCH 058/312] chore(bots/discord): reduce footprint of functions --- bots/discord/src/utils/api/events.ts | 4 ++-- bots/discord/src/utils/discord/security.ts | 13 +++---------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/bots/discord/src/utils/api/events.ts b/bots/discord/src/utils/api/events.ts index 140fbf9..5f6dbfa 100644 --- a/bots/discord/src/utils/api/events.ts +++ b/bots/discord/src/utils/api/events.ts @@ -3,11 +3,11 @@ import { api } from '../../context' const { client } = api -export function on(event: Event, listener: ListenerOf) { +export const on = (event: Event, listener: ListenerOf) => { client.on(event, listener) } -export function once(event: Event, listener: ListenerOf) { +export const once = (event: Event, listener: ListenerOf) => { client.once(event, listener) } diff --git a/bots/discord/src/utils/discord/security.ts b/bots/discord/src/utils/discord/security.ts index a99ddfc..ea0e3d0 100644 --- a/bots/discord/src/utils/discord/security.ts +++ b/bots/discord/src/utils/discord/security.ts @@ -1,14 +1,7 @@ -import type { Guild, GuildManager } from 'discord.js' -import { config, logger } from '../../context' +import type { Guild } from 'discord.js' +import { logger } from '../../context' -export function leaveDisallowedGuild(guild: Guild) { +export const leaveDisallowedGuild = (guild: Guild) => { logger.warn(`Server ${guild.name} (${guild.id}) is not allowed to use this bot.`) return guild.leave().then(() => logger.debug(`Left guild ${guild.name} (${guild.id})`)) } - -export async function leaveDisallowedGuilds(guildManager: GuildManager) { - const guilds = await guildManager.fetch() - for (const [id, guild] of guilds) { - if (!config.allowedGuilds.includes(id)) await leaveDisallowedGuild(await guild.fetch()) - } -} From 8b690b879bb5c6023c8fc863afbd9fd1d02719bb Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 17:24:53 +0700 Subject: [PATCH 059/312] feat(bots/discord/utils): allow loading commands from custom dir --- bots/discord/src/context.ts | 3 ++- bots/discord/src/index.ts | 11 ++++++----- bots/discord/src/types.d.ts | 1 + bots/discord/src/utils/discord/commands.ts | 4 ++-- bots/discord/src/utils/fs.ts | 16 ++++++++++------ 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index c09b7d0..900c66f 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -4,6 +4,7 @@ import { createLogger } from '@revanced/bot-shared' import { ActivityType, Client as DiscordClient, Partials } from 'discord.js' import config from '../config' import { LabeledResponseDatabase } from './classes/Database' +import { pathJoinCurrentDir } from './utils/fs' with { type: 'macro' } export { config } export const logger = createLogger({ @@ -52,5 +53,5 @@ export const discord = { ], }, }), - commands: await loadCommands(), + commands: await loadCommands(pathJoinCurrentDir(import.meta.url, 'commands')), } as const diff --git a/bots/discord/src/index.ts b/bots/discord/src/index.ts index 239a244..0e4b836 100644 --- a/bots/discord/src/index.ts +++ b/bots/discord/src/index.ts @@ -1,16 +1,17 @@ -import { listAllFilesRecursive } from '$utils/fs' +// import { listAllFilesRecursive, pathJoinCurrentDir } from '$utils/fs' import { getMissingEnvironmentVariables } from '@revanced/bot-shared' import { api, discord, logger } from './context' +import { listAllFilesRecursive, pathJoinCurrentDir } from './utils/fs' with { type: 'macro' } -for (const apiEvents of await listAllFilesRecursive('src/events/api')) { - await import(apiEvents) +for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'api'))) { + await import(event) } const { client: apiClient } = api await apiClient.ws.connect() -for (const discordEvents of await listAllFilesRecursive('src/events/discord')) { - await import(discordEvents) +for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'discord'))) { + await import(event) } const { client: discordClient } = discord diff --git a/bots/discord/src/types.d.ts b/bots/discord/src/types.d.ts index 5e2602f..24ab764 100644 --- a/bots/discord/src/types.d.ts +++ b/bots/discord/src/types.d.ts @@ -2,3 +2,4 @@ type IfExtends = T extends U ? True : False type IfTrue = IfExtends type EmptyObject = Record type ValuesOf = T[keyof T] +type MaybeArray = T | T[] diff --git a/bots/discord/src/utils/discord/commands.ts b/bots/discord/src/utils/discord/commands.ts index 008d80c..5c4e454 100644 --- a/bots/discord/src/utils/discord/commands.ts +++ b/bots/discord/src/utils/discord/commands.ts @@ -1,9 +1,9 @@ import type { Command } from '$commands' import { listAllFilesRecursive } from '$utils/fs' -export const loadCommands = async () => { +export const loadCommands = async (dir: string) => { const commandsMap: Record = {} - const files = await listAllFilesRecursive('src/commands') + const files = listAllFilesRecursive(dir) const commands = await Promise.all( files.map(async file => { const command = await import(file) diff --git a/bots/discord/src/utils/fs.ts b/bots/discord/src/utils/fs.ts index 6bc0e4f..07ea43c 100644 --- a/bots/discord/src/utils/fs.ts +++ b/bots/discord/src/utils/fs.ts @@ -1,17 +1,21 @@ -import { join } from 'path' -import { readdir, stat } from 'fs/promises' +import { readdirSync, statSync } from 'fs' +import { dirname, join } from 'path' +import { fileURLToPath } from 'bun' -export async function listAllFilesRecursive(dir: string): Promise { - const files = await readdir(dir) +export const listAllFilesRecursive = (dir: string): string[] => { + const files = readdirSync(dir) const result: string[] = [] for (const file of files) { const filePath = join(dir, file) - const fileStat = await stat(filePath) + const fileStat = statSync(filePath) if (fileStat.isDirectory()) { - result.push(...(await listAllFilesRecursive(filePath))) + result.push(...listAllFilesRecursive(filePath)) } else { result.push(filePath) } } return result } + +export const pathJoinCurrentDir = (importMetaUrl: string, ...objects: [string, ...string[]]) => + join(dirname(fileURLToPath(importMetaUrl)), ...objects) From 9f1ac379276c11da65235577a9c6717e01cb02eb Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 17:25:36 +0700 Subject: [PATCH 060/312] feat(bots/discord/commands/reply): send stacktrace when failed --- bots/discord/src/commands/reply.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/commands/reply.ts b/bots/discord/src/commands/reply.ts index b73e1c2..a5fe0b2 100644 --- a/bots/discord/src/commands/reply.ts +++ b/bots/discord/src/commands/reply.ts @@ -1,3 +1,4 @@ +import { createStackTraceEmbed } from '$/utils/discord/embeds' import { PermissionFlagsBits, SlashCommandBuilder, type TextBasedChannel } from 'discord.js' import type { Command } from '.' @@ -48,7 +49,9 @@ export default { ephemeral: true, }) } catch (e) { - await interaction.reply({}) + await interaction.reply({ + embeds: [createStackTraceEmbed(e)], + }) } }, } satisfies Command From b104472e471f4b54c37515de90f5d3259ddf55b8 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 17:26:04 +0700 Subject: [PATCH 061/312] docs(bots/discord): add docs --- bots/discord/config.example.ts | 6 + bots/discord/docs/1_configuration.md | 124 +++++++++++++++++++++ bots/discord/docs/2_running.md | 24 ++++ bots/discord/docs/3_commands_and_events.md | 110 ++++++++++++++++++ bots/discord/docs/4_databases.md | 88 +++++++++++++++ bots/discord/docs/README.md | 17 +++ 6 files changed, 369 insertions(+) create mode 100644 bots/discord/docs/1_configuration.md create mode 100644 bots/discord/docs/2_running.md create mode 100644 bots/discord/docs/3_commands_and_events.md create mode 100644 bots/discord/docs/4_databases.md create mode 100644 bots/discord/docs/README.md diff --git a/bots/discord/config.example.ts b/bots/discord/config.example.ts index c4d1787..ed8a747 100644 --- a/bots/discord/config.example.ts +++ b/bots/discord/config.example.ts @@ -66,7 +66,13 @@ export type ConfigMessageScanResponse = { } export type ConfigMessageScanResponseLabelConfig = { + /** + * Label name + */ label: string + /** + * Confidence threshold + */ threshold: number } diff --git a/bots/discord/docs/1_configuration.md b/bots/discord/docs/1_configuration.md new file mode 100644 index 0000000..27e180c --- /dev/null +++ b/bots/discord/docs/1_configuration.md @@ -0,0 +1,124 @@ +# ⚙️ Configuration + +This is the default configuration (provided in [config.ts](../config.ts)): + +```ts +export default { + owners: ["USER_ID_HERE"], + allowedGuilds: ["GUILD_ID_HERE"], + messageScan: { + channels: ["CHANNEL_ID_HERE"], + roles: ["ROLE_ID_HERE"], + users: ["USER_ID_HERE"], + whitelist: false, + humanCorrections: { + falsePositiveLabel: "false_positive", + allowUsers: ["USER_ID_HERE"], + memberRequirements: { + permissions: 8n, + roles: ["ROLE_ID_HERE"], + }, + }, + allowedAttachmentMimeTypes: ["image/jpeg", "image/png", "image/webp"], + responses: [ + { + triggers: [/^regexp?$/, { label: "label", threshold: 0.85 }], + response: { + title: "Embed title", + description: "Embed description", + fields: [ + { + name: "Field name", + value: "Field value", + }, + ], + }, + }, + ], + }, + logLevel: "log", + api: { + websocketUrl: "ws://127.0.0.1:3000", + }, +} as Config; +``` + +This may look very overwhelming but configurating it is pretty easy. + +--- + +### `config.owners` + +User IDs of the owners of the bot. They'll be able to execute specific commands that others can't and take control of the bot. + +### `config.allowedGuilds` + +Servers the bot is allowed to be and register commands in. The bot will leave servers that are not in this list automatically once detected. + +### `config.logLevel` + +The level of logs to print to console. If the level is more important or equally important to set level, it will be forwarded to the console. + +The possible levels (sorted by their importance descendingly) are: + +- `none` +- `fatal` +- `error` +- `warn` +- `info` +- `log` +- `debug` + +### `config.api.websocketUrl` + +The WebSocket URL to connect to (including port). + +### `config.messageScan` + +Message scan configuration. + +##### `config.messageScan.roles` & `config.messageScan.users` & `config.messageScan.channels` + +Roles, users, and channels which will be affected by the blacklist/whitelist rule. + +##### `config.messageScan.whitelist` + +Whether to use whitelist (`true`) or blacklist (`false`) mode. + +- Blacklist mode **will refuse** to scan messages of any roles or users who **are** in the list above. +- Whitelist mode **will refuse** to scan messages of any roles or users who **aren't** in the list above. + +##### `config.messageScan.responses` + +An array containing response configurations. A response can be triggered by multiple ways[^1], which can be specified in the `response.triggers` field. +The `response` field contains the embed data that the bot should send. If it is set to `null`, the bot will not send a response or delete the current response if editing. + +```ts +{ + triggers: [ + /cool regex/i, + { + label: 'some_label', + threshold: 0.8, + }, + ], + response: { + title: 'Embed title', + description: 'Embed description', + fields: [ + { + name: 'Field name', + value: 'Field value', + }, + ], + } +} +``` + +[^1]: Possible triggers are regular expressions or [label configurations](../config.example.ts#68). + +## ⏭️ What's next + +The next page will tell you how to run and bundle the bot. + +Continue: [🏃🏻‍♂️ Running the bot](./2_running.md) diff --git a/bots/discord/docs/2_running.md b/bots/discord/docs/2_running.md new file mode 100644 index 0000000..835e8bd --- /dev/null +++ b/bots/discord/docs/2_running.md @@ -0,0 +1,24 @@ +# 🏃🏻‍♂️ Running the bot + +There are two methods to run the bot. Choose one that suits best for the situation. + +## 👷🏻 Development mode (recommended) + +There will be no compilation step, and Bun will automatically watch changes and restart the bot for you. + +You can quickly start the bot by running: + +```sh +bun dev +``` + +## 📦 Building + +There's unfortunately no way to build/bundle the bot yet due to how dynamic imports currently work, though we have a few ideas that may work. +As a workaround, you can zip up the whole project, unzip, and run it in development mode using Bun. + +## ⏭️ What's next + +The next page will tell you how to add commands and listen to events to the bot. + +Continue: [✨ Adding commands and listening to events](./3_commands_and_events.md) diff --git a/bots/discord/docs/3_commands_and_events.md b/bots/discord/docs/3_commands_and_events.md new file mode 100644 index 0000000..da9ac83 --- /dev/null +++ b/bots/discord/docs/3_commands_and_events.md @@ -0,0 +1,110 @@ +# ✨ Adding commands and listening to events + +Adding commands and listening to new events is easy once you learn the project's structure. + +## 🗄️ Project structure + +In the source directory, you'll find multiple other directories: + +- [Commands](#💬-commands) are located in `src/commands` +- [Events](#🚩-events) are located in `src/events` +- [Utility functions](../src/utils) are located in `src/utils` + +You'll also find multiple files: + +- [`index.ts`](../src/index.ts) is the entry of the bot +- [`context.ts`](../src/context.ts) is the context object that will be referenced in multiple places + +## 💬 Commands + +> [!IMPORTANT] +> You are currently developing with the temporary system which isn't very great in terms of development experience. +> A new system will be made and pushed soon and all commands will be migrated to it. + +If you feel the need to categorize commands into directories, you absolutely can, as the system does not restrict subdirectories. + +You can start developing commands with this template: + +```ts +// src/commands/my-command.ts + +import { SlashCommandBuilder } from "discord.js"; +import type { Command } from "."; + +export default { + data: new SlashCommandBuilder() + .setName("my-command") + .setDescription("My cool command") + // Allowing this command to be used in DMs + .setDMPermission(true) + // DO NOT forget this line! + .toJSON(), + + // Member requirements, will only apply to + memberRequirements: { + // Match mode, can be `all` or `any` (`all` by default) + // - All mode means all of the following conditions have to match + // - Any mode means one of the following conditions have to match + mode: "all", + // This will always match in Any mode, which means the member must have one of these roles to pass + roles: ["955220417969262612", "973886585294704640"], + // Permissions required to execute this command + // -1n means bot owners only (default for security reasons) + permissions: -1n, + }, + + // Whether this command should be able to be executed by only bot owners + // (true by default) + ownerOnly: false, + + // Whether to register this command globally + // This is turned off by default for security reasons + global: true, + + // What to do when this command executes + async execute(_context, interaction) { + await interaction.reply({ + content: "Hello!", + }); + }, +} satisfies Command; +``` + +## 🚩 Events + +Events are a bit different. We have 2 different event systems for both Discord API and our own bot API. This means the [`src/events`](../src/events) directory will have 2 separate directories inside. They are specific to the respective API, but the utility functions make the experience with both of them very similar. + +To start adding events, you can use this template: + +```ts +// For Discord events (remove functions you do not use) +import { on, once } from '$utils/discord/events' + +// You will have auto-complete and types for all of them, don't worry! +// WARNING: The first argument is the `context` object for Discord events +// This is intended by design because Discord events usually always use it. +on('eventName', async (context, arg1, arg2, ...) => { + // Do something in here when the event is triggered +}) +``` + +```ts +// For "Helper" events (remove functions you do not use) +import { on, once } from '$utils/api/events' + +// You will have auto-complete and types for all of them, don't worry! +on('eventName', async (arg1, arg2, ...) => { + // Do something in here when the event is triggered +}) +``` + +API events are stored in [`src/events/api`](../src/events/api), and Discord events are in [`src/events/discord`](../src/events/discord). + +> [!NOTE] +> If you need multiple event listeners for the same exact event, you can put them in a directory with the event name and rename the listeners to what they handle specifically. You can see how we do it in [`src/events/discord/interactionCreate`](../src/events/discord/interactionCreate). + +## ⏭️ What's next + +The next page will tell you how to create and interact with a database. + +Continue: [🫙 Storing data](./4_databases.md) diff --git a/bots/discord/docs/4_databases.md b/bots/discord/docs/4_databases.md new file mode 100644 index 0000000..d08e974 --- /dev/null +++ b/bots/discord/docs/4_databases.md @@ -0,0 +1,88 @@ +# 🫙 Storing data + +We use SQLite to store every piece of persistent data. By using Bun, we get access to the `bun:sqlite` module which allows us to easily do SQLite operations. + +## 🪄 Creating a database + +You can easily create a database by initializing the `BasicDatabase` class: + +```ts +interface MyDatabase { + field: string + key: string +} + +const db = new BasicDatabase( + // File path + 'database_file.db', + // Database schema, in SQL + `field TEXT NOT NULL, key TEXT PRIMARY KEY NOT NULL`, + // Custom table name (optional, defaults to 'data'), + 'data' +) +``` + +## 📝 Writing data + +Initializing `MyDatabase` will immediately create/open the `database_file.db` file. To write data, you can use the `insert` or `update` method: + +```ts +const key = 'my key' +const field = 'some data' + +// Order is according to the schema +// db.insert(...columns) +db.insert(field, key) + +const field2 = 'some other data' + +// db.update(data, filter) +db.update({ + field: field2 +}, `key = ${key}`) +``` + +You can also delete a row: + +```ts +db.delete(`key = ${key}`) + +console.log(db.select(`key = ${key}`)) // null +``` + +## 👀 Reading data + +To get data using a filter, you can use the `select` method: + +```ts +// We insert it back +db.insert(field, key) + +const data = db.select('*', `key = ${key}`) +console.log(data) // { key: 'my key', field: 'some other data' } + +const { key: someKey } = db.select('key', `field = '${field2}'`) +console.log(someKey) // 'my key' +``` + + +If the existing abstractions aren't enough, you can also use the `run`, `prepare`, or `query` method: + +```ts +// Enable WAL +db.run('PRAGMA journal_mode=WAL') + +const selectFromKey = db.prepare('SELECT * FROM data WHERE key = $key') + +console.log( + selectFromKey.get({ + $key: key + }) +) // { key: 'my key', field: 'some other data' } + +console.log( + selectFromKey.get({ + $key: 'non existent key' + }) +) // null +``` diff --git a/bots/discord/docs/README.md b/bots/discord/docs/README.md new file mode 100644 index 0000000..6386ce1 --- /dev/null +++ b/bots/discord/docs/README.md @@ -0,0 +1,17 @@ +# 🤖 ReVanced Discord Bot + +This documentation explains how to start developing, and how to configure the bot. + +## 📖 Table of contents + +0. [🏗️ Set up the development environment (if you haven't already)](../../../docs/0_development_environment.md) +1. [⚙️ Configuration](./1_configuration.md) +2. [🏃🏻‍♂️ Running the server](./2_running.md) +3. [🗣️ Command and events](./3_commands_and_events.md) +4. [🫙 Storing data](./4_databases.md) + +## ⏭️ Start here + +The next page will tell you how to configure the bot. + +Continue: [⚙️ Configuration](./1_configuration.md) From 744a56a4fdc8844e37959a88bcf81ee39fe726ef Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 18:47:55 +0700 Subject: [PATCH 062/312] feat(bots/discord): add `ocrTriggers` resp config, embed footer scan mode --- bots/discord/src/constants.ts | 6 +++++ .../src/events/discord/messageCreate/scan.ts | 6 ++--- bots/discord/src/utils/discord/embeds.ts | 12 +++++++-- bots/discord/src/utils/discord/messageScan.ts | 25 +++++++++++++------ 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/bots/discord/src/constants.ts b/bots/discord/src/constants.ts index 8e25126..7ab26d6 100644 --- a/bots/discord/src/constants.ts +++ b/bots/discord/src/constants.ts @@ -4,6 +4,12 @@ export const MessageScanLabeledResponseReactions = { delete: '❌', } as const +export const MessageScanHumanizedMode = { + ocr: 'image recognition', + nlp: 'text analysis', + match: 'pattern matching', +} as const + export const DefaultEmbedColor = '#4E98F0' export const ReVancedLogoURL = 'https://media.discordapp.net/attachments/1095487869923119144/1115436493050224660/revanced-logo.png' diff --git a/bots/discord/src/events/discord/messageCreate/scan.ts b/bots/discord/src/events/discord/messageCreate/scan.ts index 67bd10e..5453940 100644 --- a/bots/discord/src/events/discord/messageCreate/scan.ts +++ b/bots/discord/src/events/discord/messageCreate/scan.ts @@ -23,7 +23,7 @@ on('messageCreate', async (ctx, msg) => { logger.debug('Response found') const reply = await msg.reply({ - embeds: [createMessageScanResponseEmbed(response)], + embeds: [createMessageScanResponseEmbed(response, label ? 'nlp' : 'match')], }) if (label) @@ -52,12 +52,12 @@ on('messageCreate', async (ctx, msg) => { try { const { text: content } = await api.client.parseImage(attachment.url) - const { response } = await getResponseFromContent(content, ctx) + const { response } = await getResponseFromContent(content, ctx, true) if (response) { logger.debug(`Response found for attachment: ${attachment.url}`) await msg.reply({ - embeds: [createMessageScanResponseEmbed(response)], + embeds: [createMessageScanResponseEmbed(response, 'ocr')], }) break diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index 035fd11..1149943 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -1,4 +1,4 @@ -import { DefaultEmbedColor, ReVancedLogoURL } from '$/constants' +import { DefaultEmbedColor, MessageScanHumanizedMode, ReVancedLogoURL } from '$/constants' import { EmbedBuilder } from 'discord.js' import type { ConfigMessageScanResponseMessage } from '../../../config.example' @@ -26,12 +26,20 @@ export const createSuccessEmbed = (title: string, description?: string) => false, ) -export const createMessageScanResponseEmbed = (response: ConfigMessageScanResponseMessage) => { +export const createMessageScanResponseEmbed = ( + response: ConfigMessageScanResponseMessage, + mode: 'ocr' | 'nlp' | 'match', +) => { const embed = new EmbedBuilder().setTitle(response.title) if (response.description) embed.setDescription(response.description) if (response.fields) embed.addFields(response.fields) + embed.setFooter({ + text: `ReVanced • Done via ${MessageScanHumanizedMode[mode]}`, + iconURL: ReVancedLogoURL, + }) + return applyCommonStyles(embed) } diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index 4cd025a..b4d6d8e 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -6,6 +6,7 @@ import { createMessageScanResponseEmbed } from './embeds' export const getResponseFromContent = async ( content: string, { api, logger, config: { messageScan: config } }: typeof import('src/context'), + ocrMode = false, ) => { if (!config || !config.responses) { logger.warn('No message scan config found') @@ -21,12 +22,22 @@ export const getResponseFromContent = async ( const firstLabelIndexes: number[] = [] // Test if all regexes before a label trigger is matched - for (const trigger of config.responses) { - const { triggers, response: resp } = trigger + for (let i = 0; i < config.responses.length; i++) { + const trigger = config.responses[i]! + + const { triggers, ocrTriggers, response: resp } = trigger if (response) break - for (let i = 0; i < triggers.length; i++) { - const trigger = triggers[i]! + if (ocrMode && ocrTriggers) + for (const regex of ocrTriggers) + if (regex.test(content)) { + logger.debug(`Message matched regex (OCR mode): ${regex.source}`) + response = resp + break + } + + for (let j = 0; j < triggers.length; j++) { + const trigger = triggers[j]! if (trigger instanceof RegExp) { if (trigger.test(content)) { @@ -35,14 +46,14 @@ export const getResponseFromContent = async ( break } } else { - firstLabelIndexes.push(i) + firstLabelIndexes[i] = j break } } } // If none of the regexes match, we can search for labels immediately - if (!response) { + if (!response && !ocrMode) { const scan = await api.client.parseText(content) if (scan.labels.length) { const matchedLabel = scan.labels[0]! @@ -129,7 +140,7 @@ export const handleUserResponseCorrection = async ( if (response.label !== label) { db.labeledResponses.edit(response.reply, { label, correctedBy: user.id }) await reply.edit({ - embeds: [createMessageScanResponseEmbed(correctLabelResponse.response)], + embeds: [createMessageScanResponseEmbed(correctLabelResponse.response, 'nlp')], }) } From e4054ee4af3c7a7b44056315ef6ae0cd5e528b68 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 18:48:37 +0700 Subject: [PATCH 063/312] chore(bots/discord): include all ts files in tsconfig --- bots/discord/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/tsconfig.json b/bots/discord/tsconfig.json index 4729956..8176edc 100755 --- a/bots/discord/tsconfig.json +++ b/bots/discord/tsconfig.json @@ -21,5 +21,5 @@ "skipLibCheck": true }, "exclude": ["node_modules", "dist"], - "include": ["./*.json", "src/**/*.ts", "scripts/**/*.ts"] + "include": ["./**/*.ts", "./*.ts"] } From b09dc24f33ffb27ee9ba77740680b7e7fc586d5f Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 18:50:00 +0700 Subject: [PATCH 064/312] chore(bots/discord): add revanced config, remove dev config --- bots/discord/.gitignore | 3 + bots/discord/config.example.ts | 7 + bots/discord/config.revanced.ts | 271 ++++++++++++++++++++++++++++++++ bots/discord/config.ts | 80 ---------- 4 files changed, 281 insertions(+), 80 deletions(-) create mode 100644 bots/discord/config.revanced.ts delete mode 100644 bots/discord/config.ts diff --git a/bots/discord/.gitignore b/bots/discord/.gitignore index e036eb1..0614777 100644 --- a/bots/discord/.gitignore +++ b/bots/discord/.gitignore @@ -174,5 +174,8 @@ dist # Finder (MacOS) folder config .DS_Store +# Config +config.ts + # DB *.db \ No newline at end of file diff --git a/bots/discord/config.example.ts b/bots/discord/config.example.ts index ed8a747..d6d12e9 100644 --- a/bots/discord/config.example.ts +++ b/bots/discord/config.example.ts @@ -47,6 +47,9 @@ export type Config = { humanCorrections: { falsePositiveLabel: string allowUsers?: string[] + /** + * Match mode is set to Any + */ memberRequirements?: { permissions?: bigint roles?: string[] @@ -62,6 +65,10 @@ export type Config = { export type ConfigMessageScanResponse = { triggers: Array + /** + * Extra triggers for text done via OCR + */ + ocrTriggers?: Array response: ConfigMessageScanResponseMessage | null } diff --git a/bots/discord/config.revanced.ts b/bots/discord/config.revanced.ts new file mode 100644 index 0000000..899be4d --- /dev/null +++ b/bots/discord/config.revanced.ts @@ -0,0 +1,271 @@ +import { PermissionFlagsBits } from 'discord.js' +import type { Config } from './config.example' + +export default { + owners: ['629368283354628116', '737323631117598811', '282584705218510848'], + allowedGuilds: ['952946952348270622'], + messageScan: { + // Team, Mod, Immunity + roles: ['952987191401926697', '955220417969262612', '1027874293192863765'], + users: [], + // Team, Development + channels: ['952987428786941952', '953965039105232906'], + whitelist: false, + humanCorrections: { + falsePositiveLabel: 'false_positive', + memberRequirements: { + // Team, Supporter + roles: ['952987191401926697', '1019903194941362198'], + permissions: PermissionFlagsBits.ManageMessages, + }, + }, + allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], + responses: [ + { + triggers: [ + { + label: 'suggested_version', + threshold: 0.85, + }, + ], + reply: { + title: 'Which version is suggested ❓', + fields: [ + { + name: '🔸 Regarding your question', + value: 'The suggested version can be seen in ReVanced Manager in the app selector screen. Refer to the ReVanced Manager documentation in <#953993848374325269> `3`.', + }, + ], + }, + }, + { + triggers: [ + /(re)?v[ae]nced? crash/i, + { + label: 'revanced_crash', + threshold: 0.85, + }, + ], + response: { + title: 'Why am I experiencing crashes ❓', + fields: [ + { + name: '🔸 Regarding your question', + value: 'You may have patched an unsuggested version of the app, changed the selection of patches or used a faulty APK. Refer to the documentation in <#953993848374325269> `3` in order to correctly patch your app correctly using ReVanced CLI or ReVanced Manager.', + }, + ], + }, + }, + { + triggers: [ + /manager abort(ed)?/i, + { + label: 'rvmanager_abort', + threshold: 0.85, + }, + ], + response: { + title: 'Why is ReVanced Manager aborting ❓', + fields: [ + { + name: '🔸 Regarding your question', + value: 'Your device may be unsupported by ReVanced Manager. Refer to the documentation in <#953993848374325269> `3` in order to use ReVanced CLI or check if your device is supported by ReVanced Manager.', + }, + ], + }, + }, + { + triggers: [ + /(how|where|what).{0,15}(download|install|get) (re)?v[ae]nced?/i, + { + label: 'revanced_download', + threshold: 0.85, + }, + ], + response: { + title: 'Where or how to get ReVanced ❓', + description: + 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'You can use ReVanced CLI or ReVanced Manager to get ReVanced. Refer to the documentation in <#953993848374325269> `3`.', + }, + ], + }, + }, + { + triggers: [ + /(re)?v[ae]nced?( on)?( android)? tv/i, + { + label: 'androidtv_support', + threshold: 0.85, + }, + ], + response: { + title: 'Does ReVanced support YouTube for Android TVs ❓', + description: + 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'Please refer to <#953993848374325269> `5`. Alternative, there is [SmartTubeNext](https://github.com/yuliskov/SmartTubeNext#smarttube).', + }, + ], + }, + }, + { + triggers: [ + { + label: 'revanced_nodownloader', + threshold: 0.85, + }, + ], + response: { + title: 'How do I download videos on YouTube ❓', + description: + 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'In order to be able to download videos on YouTube without YouTube Premium, you can patch YouTube with the `External downloads` patch. You can configure the downloader in the settings of the patched app. NewPipe is the default downloader. Please refer to <#953993848374325269> `24`.', + }, + ], + }, + }, + { + triggers: [ + { + label: 'revanced_casting', + threshold: 0.85, + }, + ], + response: { + title: 'Why can I not cast videos on YouTube ❓', + fields: [ + { + name: '🔸 Regarding your question', + value: 'You may have patched YouTube with the `GmsCore support` patch which makes YouTube use Vanced MicroG instead of Google Services, but Vanced MicroG does not reliably support casting. In order to be able to cast videos on the patched app, you should not patch the app with the `GmsCore support` patch, but then you are forced to mount the patched app with root permissions, because you will not be able to install the app in normal circumstances and Google Services will reject the patched app.', + }, + ], + }, + }, + { + triggers: [ + /(where|what|how).{0,15}(get|install|download) ((vanced )?microg|gms(core)?)/i, + { + label: 'microg_download', + threshold: 0.85, + }, + ], + response: { + title: 'Where can I get GmsCore ❓', + description: + 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'If you patched YouTube using the `GmsCore support` patch, the patched app will redirect you to the download link of GmsCore if you open it. In case it does not, please refer to <#953993848374325269> `17`.', + }, + ], + }, + }, + { + triggers: [ + { + label: 'microg_nointernet', + threshold: 0.85, + }, + ], + response: { + title: 'Why does YouTube say, I am offline ❓', + description: + 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'Please refer to <#953993848374325269> `15`.', + }, + ], + }, + }, + { + triggers: [ + /revanced\.[^a][^p]?[^p]?/i, + { + label: 'rvdownload_unofficial', + threshold: 0.85, + }, + ], + response: { + title: 'What are the official links of ReVanced ❓', + description: 'A list of official links can be found in <#954066838856273960>.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'ReVanced is always available at [revanced.app](https://revanced.app).', + }, + ], + }, + }, + { + triggers: [ + /(re)?v[ae]nced?( videos?)? ((not )?loading|buffering)/i, + { + label: 'yt_buffering', + threshold: 0.85, + }, + ], + response: { + title: 'Why do videos fail to play❓', + description: + 'You might have asked a question that has been answered in the <#953993848374325269> channel already. Make sure to read it as it will answer a lot of your questions, guaranteed.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'Please refer to <#953993848374325269> `32`.', + }, + ], + }, + }, + { + triggers: [], + ocrTriggers: [/is not installed/], + response: { + title: 'How do I download videos on YouTube ❓', + description: + 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'In order to be able to download videos on YouTube without YouTube Premium, you can patch YouTube with the `External downloads` patch. You can configure the downloader in the settings of the patched app. NewPipe is the default downloader. Please refer to <#953993848374325269> `24`.', + }, + ], + }, + }, + { + triggers: [], + ocrTriggers: [/You're offline|Please check your/], + response: { + title: 'Why does YouTube say, I am offline ❓', + description: + 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', + fields: [ + { + name: '🔸 Regarding your question', + value: 'Please refer to <#953993848374325269> `15`.', + }, + ], + }, + }, + { + triggers: [{ label: 'false_positive', threshold: 0 }], + response: null, + }, + ], + }, + logLevel: 'debug', + api: { + websocketUrl: 'ws://127.0.0.1:3000', + }, +} as Config diff --git a/bots/discord/config.ts b/bots/discord/config.ts deleted file mode 100644 index 9e3eeff..0000000 --- a/bots/discord/config.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { Config } from './config.example' - -export default { - owners: ['629368283354628116'], - allowedGuilds: ['1205207689832038522'], - messageScan: { - roles: [], - users: ['629368283354628116'], - channels: [], - whitelist: true, - humanCorrections: { - falsePositiveLabel: 'false_positive', - memberRequirements: { - permissions: 8n, - }, - }, - allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], - responses: [ - { - triggers: [ - { - label: 'apps_youtube_buffer', - threshold: 0.85, - }, - ], - response: { - title: 'buffering :jawdroppinbro:', - }, - }, - { - triggers: [/eol/], - response: { - title: 'revenge eol', - description: 'eol', - }, - }, - { - triggers: [/free robux/i], - response: { - title: 'OMG FREE ROBUX????', - }, - }, - { - triggers: [/(hello|hi) world/], - response: { - title: 'Hello, World!', - description: 'This is a test response.', - }, - }, - { - triggers: [ - /how to download revanced/i, - { - label: 'download', - threshold: 0.85, - }, - ], - response: { - title: 'Where or how to get ReVanced ❓', - description: - 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'You can use ReVanced CLI or ReVanced Manager to get ReVanced. Refer to the documentation in <#953993848374325269> `3`.', - }, - ], - }, - }, - { - triggers: [{ label: 'false_positive', threshold: 0 }], - response: null, - }, - ], - }, - logLevel: 'debug', - api: { - websocketUrl: 'ws://127.0.0.1:3000', - }, -} as Config From a277ac53fd7726cfe4f6ea64cabd592c57fe0f8d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 18:50:22 +0700 Subject: [PATCH 065/312] docs(bots/discord): add note for `ocrTriggers` config --- bots/discord/docs/1_configuration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bots/discord/docs/1_configuration.md b/bots/discord/docs/1_configuration.md index 27e180c..86474f4 100644 --- a/bots/discord/docs/1_configuration.md +++ b/bots/discord/docs/1_configuration.md @@ -93,6 +93,9 @@ Whether to use whitelist (`true`) or blacklist (`false`) mode. An array containing response configurations. A response can be triggered by multiple ways[^1], which can be specified in the `response.triggers` field. The `response` field contains the embed data that the bot should send. If it is set to `null`, the bot will not send a response or delete the current response if editing. +> [!NOTE] +> If you want only OCR results to match a certain regular expression, you can put them into the `response.ocrTriggers` array. + ```ts { triggers: [ From 7f27c5607ceeeef56d67097e88f68caa1b8791b3 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 18:53:02 +0700 Subject: [PATCH 066/312] fix(bots/discord): remove usage of macros --- bots/discord/src/context.ts | 2 +- bots/discord/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 900c66f..f3216d0 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -4,7 +4,7 @@ import { createLogger } from '@revanced/bot-shared' import { ActivityType, Client as DiscordClient, Partials } from 'discord.js' import config from '../config' import { LabeledResponseDatabase } from './classes/Database' -import { pathJoinCurrentDir } from './utils/fs' with { type: 'macro' } +import { pathJoinCurrentDir } from './utils/fs' export { config } export const logger = createLogger({ diff --git a/bots/discord/src/index.ts b/bots/discord/src/index.ts index 0e4b836..ea4a601 100644 --- a/bots/discord/src/index.ts +++ b/bots/discord/src/index.ts @@ -1,7 +1,7 @@ // import { listAllFilesRecursive, pathJoinCurrentDir } from '$utils/fs' import { getMissingEnvironmentVariables } from '@revanced/bot-shared' import { api, discord, logger } from './context' -import { listAllFilesRecursive, pathJoinCurrentDir } from './utils/fs' with { type: 'macro' } +import { listAllFilesRecursive, pathJoinCurrentDir } from './utils/fs' for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'api'))) { await import(event) From faf1e5fc78266210049e84a15103620c558bd13e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 19:49:20 +0700 Subject: [PATCH 067/312] chore(bots/discord): remove dupe response --- bots/discord/config.revanced.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/bots/discord/config.revanced.ts b/bots/discord/config.revanced.ts index 899be4d..5d0ad06 100644 --- a/bots/discord/config.revanced.ts +++ b/bots/discord/config.revanced.ts @@ -177,6 +177,7 @@ export default { threshold: 0.85, }, ], + ocrTriggers: [/is not installed/], response: { title: 'Why does YouTube say, I am offline ❓', description: @@ -228,21 +229,6 @@ export default { ], }, }, - { - triggers: [], - ocrTriggers: [/is not installed/], - response: { - title: 'How do I download videos on YouTube ❓', - description: - 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'In order to be able to download videos on YouTube without YouTube Premium, you can patch YouTube with the `External downloads` patch. You can configure the downloader in the settings of the patched app. NewPipe is the default downloader. Please refer to <#953993848374325269> `24`.', - }, - ], - }, - }, { triggers: [], ocrTriggers: [/You're offline|Please check your/], From 6abb7409945c10bd3af451fb45ef4b4d4ebe9489 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 19:50:22 +0700 Subject: [PATCH 068/312] feat(bots/discord): improve logs --- bots/discord/src/events/api/disconnect.ts | 2 +- bots/discord/src/utils/discord/messageScan.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/events/api/disconnect.ts b/bots/discord/src/events/api/disconnect.ts index 98a3c2c..f6306e5 100644 --- a/bots/discord/src/events/api/disconnect.ts +++ b/bots/discord/src/events/api/disconnect.ts @@ -18,7 +18,7 @@ on('disconnect', (reason, msg) => { // TODO: move to config if (api.disconnectCount >= 3) { - console.error(new Error('Disconnected from bot API too many times')) + console.error('Disconnected from bot API too many times') // We don't want the process hanging process.exit(1) } diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index b4d6d8e..eb45b09 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -54,6 +54,7 @@ export const getResponseFromContent = async ( // If none of the regexes match, we can search for labels immediately if (!response && !ocrMode) { + logger.debug('No match from before regexes, doing NLP') const scan = await api.client.parseText(content) if (scan.labels.length) { const matchedLabel = scan.labels[0]! @@ -82,7 +83,8 @@ export const getResponseFromContent = async ( } // If we still don't have a label, we can match all regexes after the initial label trigger - if (!response) + if (!response) { + logger.debug('No match from NLP, doing after regexes') for (let i = 0; i < config.responses.length; i++) { const { triggers, response: resp } = config.responses[i]! const firstLabelIndex = firstLabelIndexes[i] ?? -1 @@ -99,6 +101,7 @@ export const getResponseFromContent = async ( } } } + } return { response, From 29544d4e0127173465796b7e3c62161f4db59c8b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 19:51:19 +0700 Subject: [PATCH 069/312] fix: remove error cb handling for `socket.send()` calls Fixes promise not ever being resolved --- apis/websocket/src/classes/Client.ts | 21 +-- packages/api/src/classes/Client.ts | 140 ++++++++++---------- packages/api/src/classes/ClientWebSocket.ts | 7 +- 3 files changed, 78 insertions(+), 90 deletions(-) diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index 8eda488..defe268 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -34,14 +34,10 @@ export default class Client { op: ServerOperation.Hello, d: null, }) - .then(() => { - this._listen() - this.ready = true - this.#emitter.emit('ready') - }) - .catch(() => { - this.disconnect(DisconnectReason.ServerError) - }) + + this._listen() + this.ready = true + this.#emitter.emit('ready') } on(name: TOpName, handler: ClientEventHandlers[typeof name]) { @@ -57,12 +53,9 @@ export default class Client { } send(packet: Omit, 's'>, sequence?: number) { - return new Promise((resolve, reject) => { - this.#throwIfDisconnected('Cannot send packet to client that has already disconnected') - this.#socket.send( - serializePacket({ ...packet, s: sequence ?? this.currentSequence++ } as Packet), - err => (err ? reject(err) : resolve()), - ) + this.#throwIfDisconnected('Cannot send packet to client that has already disconnected') + this.#socket.send(serializePacket({ ...packet, s: sequence ?? this.currentSequence++ } as Packet), err => { + throw err }) } diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index c645aa5..6fc2bb3 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -39,33 +39,31 @@ export default class Client { async parseText(text: string) { this.#throwIfNotReady() - return await this.ws - .send({ - op: ClientOperation.ParseText, - d: { - text, - }, - }) - .then(() => { - // Since we don't have heartbeats anymore, this is fine. - // But if we add anything similar, this will cause another race condition - // To fix this, we can try adding a instanced function that would return the currentSequence - // and it would be updated every time a "heartbeat ack" packet is received - const expectedNextSeq = this.ws.currentSequence + 1 - const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => - awaitPacket(this.ws, op, expectedNextSeq, timeout) + this.ws.send({ + op: ClientOperation.ParseText, + d: { + text, + }, + }) - return Promise.race([ - awaitPkt(ServerOperation.ParsedText), - awaitPkt(ServerOperation.ParseTextFailed, this.ws.timeout + 5000), - ]) - .then(pkt => { - if (pkt.op === ServerOperation.ParsedText) return pkt.d - throw new Error('Failed to parse text, the API encountered an error') - }) - .catch(() => { - throw new Error('Failed to parse text, the API did not respond in time') - }) + // Since we don't have heartbeats anymore, this is fine. + // But if we add anything similar, this will cause another race condition + // To fix this, we can try adding a instanced function that would return the currentSequence + // and it would be updated every time a "heartbeat ack" packet is received + const expectedNextSeq = this.ws.currentSequence + 1 + const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => + awaitPacket(this.ws, op, expectedNextSeq, timeout) + + return Promise.race([ + awaitPkt(ServerOperation.ParsedText), + awaitPkt(ServerOperation.ParseTextFailed, this.ws.timeout + 5000), + ]) + .then(pkt => { + if (pkt.op === ServerOperation.ParsedText) return pkt.d + throw new Error('Failed to parse text, the API encountered an error') + }) + .catch(() => { + throw new Error('Failed to parse text, the API did not respond in time') }) } @@ -77,61 +75,57 @@ export default class Client { async parseImage(url: string) { this.#throwIfNotReady() - return await this.ws - .send({ - op: ClientOperation.ParseImage, - d: { - image_url: url, - }, - }) - .then(() => { - // See line 48 - const expectedNextSeq = this.ws.currentSequence + 1 - const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => - awaitPacket(this.ws, op, expectedNextSeq, timeout) + this.ws.send({ + op: ClientOperation.ParseImage, + d: { + image_url: url, + }, + }) - return Promise.race([ - awaitPkt(ServerOperation.ParsedImage), - awaitPkt(ServerOperation.ParseImageFailed, this.ws.timeout + 5000), - ]) - .then(pkt => { - if (pkt.op === ServerOperation.ParsedImage) return pkt.d - throw new Error('Failed to parse image, the API encountered an error') - }) - .catch(() => { - throw new Error('Failed to parse image, the API did not respond in time') - }) + // See line 48 + const expectedNextSeq = this.ws.currentSequence + 1 + const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => + awaitPacket(this.ws, op, expectedNextSeq, timeout) + + return Promise.race([ + awaitPkt(ServerOperation.ParsedImage), + awaitPkt(ServerOperation.ParseImageFailed, this.ws.timeout + 5000), + ]) + .then(pkt => { + if (pkt.op === ServerOperation.ParsedImage) return pkt.d + throw new Error('Failed to parse image, the API encountered an error') + }) + .catch(() => { + throw new Error('Failed to parse image, the API did not respond in time') }) } async trainMessage(text: string, label: string) { this.#throwIfNotReady() - return await this.ws - .send({ - op: ClientOperation.TrainMessage, - d: { - label, - text, - }, - }) - .then(() => { - // See line 48 - const expectedNextSeq = this.ws.currentSequence + 1 - const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => - awaitPacket(this.ws, op, expectedNextSeq, timeout) + this.ws.send({ + op: ClientOperation.TrainMessage, + d: { + label, + text, + }, + }) - return Promise.race([ - awaitPkt(ServerOperation.TrainedMessage), - awaitPkt(ServerOperation.TrainMessageFailed, this.ws.timeout + 5000), - ]) - .then(pkt => { - if (pkt.op === ServerOperation.TrainedMessage) return - throw new Error('Failed to train message, the API encountered an error') - }) - .catch(() => { - throw new Error('Failed to train message, the API did not respond in time') - }) + // See line 48 + const expectedNextSeq = this.ws.currentSequence + 1 + const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => + awaitPacket(this.ws, op, expectedNextSeq, timeout) + + return Promise.race([ + awaitPkt(ServerOperation.TrainedMessage), + awaitPkt(ServerOperation.TrainMessageFailed, this.ws.timeout + 5000), + ]) + .then(pkt => { + if (pkt.op === ServerOperation.TrainedMessage) return + throw new Error('Failed to train message, the API encountered an error') + }) + .catch(() => { + throw new Error('Failed to train message, the API did not respond in time') }) } diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index 34847f6..f5b960e 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -49,6 +49,7 @@ export class ClientWebSocketManager { }, this.timeout) this.#socket.on('open', () => { + this.disconnected = false clearTimeout(timeout) this.#listen() rs() @@ -107,9 +108,9 @@ export class ClientWebSocketManager { send(packet: Packet) { this.#throwIfDisconnected('Cannot send a packet when already disconnected from the server') - return new Promise((resolve, reject) => - this.#socket.send(serializePacket(packet), err => (err ? reject(err) : resolve())), - ) + this.#socket.send(serializePacket(packet), err => { + throw err + }) } /** From 7b1012c02593f02b5c15a2854df975e4e70d994a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 19:51:55 +0700 Subject: [PATCH 070/312] chore: ignore dist files in tsconfig --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 9962c1d..c7d6ee5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,5 +21,6 @@ "allowSyntheticDefaultImports": true, "isolatedModules": true, "allowImportingTsExtensions": false - } + }, + "exclude": ["**/dist"] } From dcac10e5cc0435843c800b114b4ad0ce20c2d7de Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 19:52:19 +0700 Subject: [PATCH 071/312] chore: add bunfig --- bun.lockb | Bin 5032 -> 113640 bytes bunfig.toml | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 bunfig.toml diff --git a/bun.lockb b/bun.lockb index a606d23bc62e02d641e312cf59df3629ca0af7f4..6e07953731827d6a9fc26619ff4d4bc52dfdb524 100755 GIT binary patch literal 113640 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

      %P31DZTczdZ*e{esFbV#_X&Q84}G91IL5 z6xhF-gMq;c8h#*iiN!Gc|8hY551OF>iGeUNX28_ta6-yoL1^BEi4!sdrtUf?1A`+2 zLxU&-0|TfG2Vp{bLGm#D5?l-n2GIHgBo6Wih$e(#`aQTH{s+}PATeU&5Tq7l#}+O~ z{RJw!Kzvf&{|l-glzzz3Z^q5Q5XQjJK&-n#c7QO<{XN_a40a3*4a9~UNG}M(^q=Bp zVDN&bUy$EH7$i>!!}N>rFfh15^@H@l!U4p_hhh4ocp&)?fsSouRpA50#mUx*hH{-E>+@&ins5Dn8G%geyvgcN?Hx_>1v zB>jT)fiTD&gzN^%!|Z>`3-LclFGvi8LGpw!Or1O*1A_}R{6S`bFd@Ald6@oWK1lo% z>u!)<5QgcW%?AlTkQfNV#6fI)7^dznA0+-j?gr%{5RFeCOb(`Bnjcbrfzl5-`n~xf z=@(=ks0;$pFgx(kFm<*33=C!r3=N?01C`;Rau}aJm>f+1W_|_+OKAF&gGmxW!}R~+ zhlC%<{}RwTfC%mI`rBIo(tZc&;Uh!;ECERPf&34OJD9sb=HbIIb!P-1=@;aFm>52N zFgci70YOOkgX{*$af1jVFigL{AOnLnQvI(?gkF#mnErW!koE^C?SaZ(E|4S<7^eS{ zASC~bFo4JDL33|J=w^V&pM(&k{s-AD#lXM-qKPp8W`Cp*B>Z7~V)c7KEja|8ABS>@ zreOBX6=Gn}ht|Jh&^|0NhQRcn5n^C4XJ7y?2q7o`FbhNC50rk$sXrWrA?Xj~W{?>m z3}O?)F!whi*$>hK%10oY5QgdBDh#QAK>9#&N2>iFk?aSVLC9X1Jj{Mo5lH&sBq8oV zX2A4kia^Q_klV=#|Jfpt@*h-ogUkS#O$>&m-=z$Y`95OwAgh77|Aq*p{sZxe4SSF| zAoUint`D~n1r$yqz|UQO_YJb9;zQCPHY&$^gkD6U`R!3|AWjR2E+7+i9yOw zko!Sm#D)P#Ey$kBVvzU;m7gF!sru8zA^m4!{SC4Mgkkn?5r@>DptMh{-N^bsi9_-~ zDE)!NiN!GcH6$o&e|a2k{WqZY!^|Vq{}Pgr`i~U1gY1CW7bFSE ze<1&Z#EHc){Zk|%i(&dzWg+D!EdP+IzfKmC{y_T3 zasL5XNd6_a{P+je4{|@uPawAt!Z5Wqa**^-Zv9ar2MIq=`XOW|vOLWGt#T0mlM;3y zb71;E%c15!kQ^}>rvH*W#D0+bNr?lPegOqY{Rh$y3PYGUAveI(1t>tq??L)OdO&7? zXhImKe}Mu6LlOn;CniOR|3U3%LUtm{!|ab&gw$W4u!D&qtHZ^HslBBL8Gi)DKS&G~ z4!HEe#6fC7Y!xMl|3Q3M84eOBghA>-YTA?_=^tc2NIy&*B!>^f)SXmRLI_!Da{%uJa6h02ifi(LEpR3PmqV&e_n zeh(E$`44gr$P5q-Q-hC&sq0gLgdZsVfiONj$Z|0KmsB9}4~lzac|vTMelAr={)L4< zv2K9rk5YxCACUbZF_<~T+CM`T5`Q3lpgasRhmc;7JjgB(eM=RQen4iBs$Wcvfgv3_ ze+L>r0gWSp%q4_j_IIlx>JO0pg!IAWVfqiMLE;a_C)Ny@`k!i$@*CuD5Qdpgh=%D8 zQitdVrCmaLkmX_eC#fUi4mJKzJr+!(;uwKz~BKLKL^#lFmaeV zTr^DGY)wf2pWOI=iDW;>E@Hy~X1}2p1A`va{UE&{3^IohhUt&jVqge`hCfITC>?=l zLKvohixy=36x4nP^#KU!gUQ46OKKzfAD}QK)&0KOi2M(7JE{6xwIT5ba}SJ$xf2%+ zQ+HV#wfx7W9$g%!Us(sze}lOlM5C+4#)s+8)Pb!3A-DgtO$X6_CN}Iq?t$5_txMVX z-(p?J`X_ST|3a66!5Jz2g4{+7hPz)6GJgTHpVaW*s|RVng3>NXFR@_=v!7ibQT~9; zAja(=H8B1DNcur`gD^;*5QgcWPNDw$NcutkCuA>79%jF~0cGiDz5xS690mO!VM9p$ z0do(`eS~P3`Xob0{sr;LiN6(wi1rsK{D`f`K=y&$_}viFegg49`a$I|NSqJ`sRyai zHG%qN6l?iV*@V2Fgy zKa-=sz!cJcAlLq*rjYr6a`lUtA=clL|Ha*Bn~{r#JUQ6dO>n9 z{ZB0+?Kcn~6ow!Sk|%^g>OpFhtRU?#kUnD9j)3&R^ru@v)-Qv^R2jf-0Wk?;nEuUH z3=C1w^+zBvV#`sGT975Y){yZZP`L-HJ3%x^oDhcT5447~zd-&6>4%9EtAD071A{Jf z{hI(GO-S-Ed+u37(ho=qfUX|`@rlJS`$3C;LF?Z^ zc7yc7Xb>A8hUuSc2WdZn)Pmd&@&|}b2*dPWw1f1YLHa;(526X_1+g2jGcd$J%O8*#pfCW@gfPs0Q3pu-9mFRm|3yRflk5ML z4v_K}=5A8M|EmLJ{SwT-FmqshTr|kPAURb>NcsWU3lbx>{D^dfv>!lvVHjjCAq-Lr zQZwBV68@mH2NDCh10+regVci5T!y+I#3oiRNG(i1yAz`P1i1kuM+}DPuW*8dKdjs$ z)(tTIE1V$Xe;__cFNh|^{%cN<`VXWJgh6JIqF>7y(tiP^8<77=9skdEhQuGpUQqr4 znL~>G%bg+pH;{f1CPptv4alz``k^yq{WFLS(hn*_Kx{%7reDPc;(m~NkpD?_e~k-b z`~hS?%v_Lp_%KY}9v9T|6J!QHeK0wgegRj={2M6Wg4_nLHa;pAcRj7k{nEbkvq7}-2mc)%pi6B_4TX;iv9&2kn#(p zpPv|wP&F{S9(q9Lzd*vo#vMp4OuvvPB>#i#2Z@1bkUSv_QVUWOQV=JTFA} zgXBPA0HO(DnErKMknn@?L2*DxFGwDy{+SoV|1dsCju;G6FXjy?zd?FHZAh3nh>s7$ z)OmPA><9T9)Q1Am`1HZ#VEQY(A>j|xM{4|U^G4L)AhTiSg4p;lOx*`>$oMPBewY|O zeK0wgS~VX?_<{JK^apE)!PMcRLFz$r5k83Y58@MxVftHqAn6C>eo)wfFfnGp^zZS3 zgg-3qLFSO6|CbM>{|)jxv3)0y`7ry9eNpod$Q+P7Aq>;Mz!x$81ZsPc>i(y`i1ia7 zGfB;V9)6Ji3rH`>%`h6o#)o0*YWxuPgWL`i$EO!02UEAt4-$VMy&!Q?d$-~rn1~4#0K*#Tq#X)R* z7^Z(=0Hps63x9likmX?d-v%JcPmnyZ=D_sJ213R!K>9$K)byJh2x&jV%p*1YcLyTk zA5`{%+yL?iAq=zcD^x#7JxmP5CWK*X^@AYoH;{Ue-K4s|I0#YxgY?791(}Br!_=(| zg7lw2dO_-8G>DB4!}Na+LX>|nF?{-9axk^V!I1KYSbNd+rw2p&?=bzOmcQ$v_JhnN zRxd~`%>9pp5&j2>gX$p=O$fvED~CYxKS(`DFRAIjFa)xG0OWtr*aOU5ka_qpOx=bM zNc{`qgX{;<`1HZ#VCr6kK>A-Gagf_VYj{CyLKvpsFci}M0F}KUKfuI^)t?-STK|B| zAfy*053^@!C?fxW+yH6=g5(KdnEt1skn#tl9+ZASW`Nj)FigK<7$p8cdWm&A%xutn z0SJTmAR1%_C=G!4q|hKg%YZa7fX`3{v&i9t+%F5A!vl@$gZK(y(-;_#X;3+<2|aJc z04k46gTl}lx}OHtCntpl={JX-#bpIG#~Mo8Ki{T zDi5MTaq9#XcZSj~&~R~s^4*~{Y#s!e2F0Tv)I4Mwl)r+Y;^fdEcY)4%2Dv*F>Yi{Y z9SL<$6f|ByXRU+cDGO>JG7ZuXI#V7b1)@Rx91w?rfq@tr6hEN50YLf+p!z^Gh+hch zBh#RA339480|ST#=?5Q%%)r2ajRu)h1&#k&sCmdVNWKp0-+Cww-oVZPnTP_-19w67 zVWUCu&nGgTj9nln}dw;aS_U|;~zAo&$gJ~kSZ?$$x&vC$y?>!I=>8e~4`9Dk5M zwnE*t4JuCz4bry@8jky+`jBamdyYcIiJ?K^cLEw8XQ1vm3)K&zLGC>djKpX}J1`rKWX9g8Fhl+z} z5EFEN38?N02XPn}7?5d@zDTIPD5yL>8Wg{AjNtRV8M2}3K{SY&10q1>B^DY~-W5W{ zDWO3|6oCi^1_o?2NUE3-a&Bcgh{M3Z0HQ(m*FgPQ3l+ylgVZ-MGBAMhYX?+cCzS4j z(%p=ZvoZUi>0koXTw-WY{7;9f$47&d%z)hI!mtGDuN6@HSArxN7#NUgP<*b2io z-ye=GZPMYK!@JWgpPebaz`SxsU|WLpAEh&0wt_2`n}%?F|I?iHJ5*G-ti94^=ig&G z)eCt}u*e~q3(Dg#$Fi*VZQF8liN$w;y*^ikS0+5(ClIn{%H^!@r(~Z7WUUq{|Jgpx zrH|oQMAI8aua#4y7O&Hd_}9F|@3hM1hQ3?_DNq}jk%0w#SRSZMg`2DYQTu3@c-4B{ zS*G?3jf)EOLr(~op1o%9VW!VxlrpVgYU0@g8+BV(b*`A&HoG}XeCmJY-A67RKJrNa zafM|~z311~!%G@wa&}z3dQ9TqYW=^z+lzBPo$|l2EE8-l3+UcPlyePPBG#IA=P;+R zWQRXIvbn#Kxx%+1*}zdQXJ@Ci^mDmYOLyl#_wx&p-RtpOxmw{pgL=vj<;;&c|D+f* zr+fB>?Vk@emw^McUK~^x!QI=&cKKbe*#*I=5B6OuKanVEUU@dCUt8?WdUyZYm-Sz6 z%KByHo=sS`n}sM%M`G=EZ}qaL45|exqtcVmt0H{ zzhiUna^Znf30ZTv6R()wny-Prf#{ho-^sfzS+rPPhKi5JP{z_ z*ZAk(CaYzZNpm&yu7(`QN_}`N!8&n?I4n+wTr$ZN4!?i`S3{?5?A z_xt*SEe|V&7OS7(t>jCwshymVl6rZ=_PSs;o8p&0mFBk3v-)i;Au_+>p|3-cL_!36 zwxN`0%%D}E2@78SSS}eks z`uTsoPOoI{$5mb)SGKQ<=0EYr?i+8+uc;flUry*KUJklrm5~9G-#};Yg3JVAmOneT zZaNnGF-?=@)HbHo){GOUZCT~%FjH*euOnyemQ?RhHmhLVwIwBFI?J+-iE48mPF_?e zJLSopAIsX0-b(1@k_WpNk}r_gl(CpBy)olRz1&{ssxNX40TssVXP!oXncd29WLdCp zck}m?nSP$P(n2P#F>ZTU_d&7BZQ^fe%;U$1Qy4*OJW(|GtqOIyon z3Rmf#o3iU?O>c5RG8feE2bl@NEDqe^Q-9XHF_9})&;9UuUm=gs zt!9CV8lREO1)W&}aw`b46i?u(uanlieZH0NElWn$B&ObAW9^5jIzJQMa;JqzJXJ5; z<7H_5>S()e*YRohAN|PTmn(iaCw={aKcPEjpI$N#$y{-efl$nH_41FVsYhF1>`{1~ z8Y^h;|BwCiK~*`HN5K}=S6kJ#nH-ThGSNG&zK(gLtWM#sgYApB1?z>guKj5^EYkGD zwFr{VAn5^lev#$vmCa{G?0zUMH|c+}X5|gnxRV--uf!Zwd>yTkQMBM&)-@rSqfvtC zNB+1?f48^5)^GRPL)VK#Y&4%Ko_fDRyXQ5MdnKW61yL+pqjFU94wsqFVN%S@X(+9( z+Nj9Q^5PbM@%($fw{GV&>}Yqox-lbt;o1kka@&gnJa!p=-YixTGS%Tns$OyaX?7%Y zL1zMk%miVUOVf{@eIdu2LKgI_clY@i?< zPMlHAmRdeb&`iUOyN%`Wx(>#rW#rD^|vUv7)>7`UdM=nt$ zbCJ&+W;w69?D^|`8?=R@75=WcHm^7Ku-FeL&68ebIyW+=>dEk%yX?6Au4J85c`Qfa z`b&~_ntcZiICCN&?Ff8fB^Wo|3CUd07%<4KAk1Q@apUaKUJG8i1B@@vI~y)-%s<4p zsbAMux+b@(YxC`wCBerEt4|m&*s$N0>$lqxXHTOm&EIY^ z&UXE2>M^0*>VJ!e*6u|=k<3*D83@HJ{EGf}gzfJ(UUqmeMgNb9<;07ZqP!}yJ}$b> z9(k%)@aEHP3kzby9}0-rK76Eczd_z|@@Xl(}qcahbI(uVQNU@0iGM+#EoTSH- zrL+FE9bIp0mvRg=HVaE<$m_IOSk-&yYp;1fVaNZ^0pD13uYcF^ty%H^$n2lXPc!)( zSavbb=#=c2!i)*Yd8MGo@y1 znS7kDcjJr~?BbUWo&0k0c-*FRi{($uuAVEldsezEzPDa9_Q>y8xu3luPpo3q*dDOz zp1Oaud&Ae4Nalj}c0!G1U^%;?c8!vQf7INqANQMe>A*kz5G;c?zY>k4muriDN}?%b1JZKP=gr?nzwQlWU@Q)yX*bO z{8?Oyr&(pSGDU8_>Hk+|8FEo`pRxTc^>|j-`=)6#t#}KbALD*7k#G8`zkW3jKP%in z=FYiLRTIg*>d5AnyxDh$d)nQ#8%(#kD?OH6YOc_e-5vb9QUCYs!ilw(Wrr6ZzyIL9 zQP@1kdiVL>r&|_wS*~s@e0$B-iiItXLB|2fTn%J%^_^EQ)7O%k;`aY@?2r1AJppz1 z6rb;WTkNU0%;a%XdtkTvt}B``qBp7^_04c|)SWU*RI-|7Zy5K-g!Y8>&yQU}G8eQS z0A@6c(Nq@mhm>rVvh??R~I4In_4hKLE$T`$?5;aRLkUA(XGse28CMXEnAl6rFuMU zIQ8|N*W_KvsxR2z-*FO}`qX;mRO5vOM|~JC^XTzfHSSLrH5dBnV~gZoZDe!L=o?El zE}40r^ILiTta$NBr@k^}+OCjZ+_NKE$m8@*$e*S3*NHOj&u@-9-MTGQ^0|ZBfARF_{QimgUP$gW zKsL9olzY#s!Z~{NVZ3L#ROd~x=I(U2&6Q!WJO5Aj%W1RkYelr3eC}^@wyos;{^kLX z_*HqYCrNL0fBtdIpBtqu1sV&4g|8v9xj%%A=gNFqA^z_+!{nofTJAGEu1RlN7-)56 zNxRfc<5im;eOi+x&JY(6(@~yoIMH(1^XZ>?GM`<3({yxAQPZX?bCKL@glukU=R-MJ zb+u;sd2Tr`GMBYPsxzcy>sy}NJ@d|&L;nQkxb53hCBHjv=j&Ht3)a~lKK1|aJ-%I! zox~T$#?%&7vVrFIVD1I2uYnoOvS8)ymY9i(RUtdy{OJ%@JK?rt?v5V{PmFen+aK0X zN#1`gKHwp>$8UPtUeF4tk}bUSeyrBQf3`O=YkZV~$kJ4J8V*VCUi+$wrp z*xlwntzEU$gH>Gkx%HWu))ibOQzt3+b|$~bpPh|dzkx6PhMB7tm^#&As^PT5C(>{4 zzwQ`WUba;{(%Yv0ifgmxk;|c?NeS1)KQQXlP3HagdzYL)6PwWL)@=tTEQ>KSirarg zKo-fpp!G*kV;NZXr3eKczcp==)1`58vXYUo21Oa|8M8F zz{QrK3&WQxc5FNzy?{fgxvk!IUp0%wgNg{muNb z>Mno4oo+wY+>2`e@30tkx2_h`5Q~d4@-7KEAhNY3fL~3Yzc)-}_mZ~t53|ek{E^JH zL^f9~pvU_8!V)L0Re^<*7s-b;h=gSrzddSw#{S{gvXa&2D>f=V3z+J6Rz-hr%Z$iM z))@VcnoW}wzdzjXEt>YK3c1_?t-pd9%~E+zw`cFf^BbzvYgSvvIhDD_%Qy&m%`HOu*eH-J$UIqo^=TooSxDx>)|G+Hee$`nbjuRsx6k*?n>k&DeG`Y$ z#xJ=i?Q}z}Oa+>R+Rm-wyn1rJ>XfHORiA$S>9nvsH@UmF=wgRd@A3D=2j~1>gJdpf zz7%982(x_1T`{41im8Rq)qM8gYa(1n6pjjS_!<>=z<2&fYlA!TCHZc1_DD4Nt-PZ2 z`^&q9`!ht|ykR=Lez$k1tMliT{5DADg63aAW`Zz_NmtJDn~uuDXV1v*lPgvWyeECy zC@6H>xd-tLn*(QGY22U4^3&;OBcD~_M=fXX;}0eD*Z$p95Kj-Z$@6wUmcX{Tr!s%@{b>tqb)VQgcwv38+x0DyxuA7?ATvRj zCHtwCpPWkL412G@^|gBr%gqqny@A2u<+7E6C+AB9rZjYky8OMm>uSs8+~)^+BK`e0 zh`qn_;ZUmM?QrSlj@o5gkjw?G3j~=7!Ym>Cmzr(VP^>YydWzfTbn#}|kA5U`T|fpxF-ysg zwbJ`zf25Y0?!TINpjk}&WX0OVu9v!BIWC$8uL-*H>DrdkgxP9K5+|oubuK!;?(o)S z5pf2=46)yAk{2Fyx{YM6E7VL7#llAohZUGK2j(;2o=5B4rOIDgldfAM*_sjMrWe%i`r zkoNhhc8I?>kM^x(hY6FPU*|zG7q(6lWHyUNQ}b=9moL4S{Jbt1X8Z5Q$zrAbmv*l? zEqq*Cz=SE~_BEL+LX{6^pLyxK?MV4eQ==EPCv*Nik7CbbD45leVkLlNt|!PqC}!C< zXU^{(yK+A6o%O9T@6wbij`)*Pnj5p$DVU#WTBhr}bLQ9n&@-Ofdf&Sw9x=)4`MG#= zzNG%@0`ocFYx*u9W=}ve*9&SUh+>)6Q2zSHbh8A>rWi3sqkoa&`#6QwW(dDs)){f8 zeYWV*9|^o4e+bI!tq5U^56h~VqtSa=I*4cev@2$;>i zQcA7xaN)kD;8XvK=DS@w?dQm*bM-_!H-n~NZm3pL*P;7!oxAz3i>fyL$<92aBYgAm zdMC!n?7AO$Nap%L%>+>_;Tq|cTkmv)mhrgE`8BWcw=Q?D=9WoYEv%S)A9uAf2}E2p zs=FGub!)@7-;D1z&TIWv=k@Dn&17%k)0>af#%Up^GhdK_P|Q;DP-vBz_V*Rjk~Q5d z_uRdIQP0$DXULqQy#Xw3(4d7*xSIrh?ytiJ3^D$@w^Y0Kv2L5g zh~(Y?sF@&&W#5XX%L~@Eh+HY1_|LVoW<%8J%lFnZ_xVkpBxJps<9|wXAgkqLubA-1kMXq+Vst+TEODHBYdxboY(7nvzzE2siq^I!*%1&4&`Xm4H zqpB?lj%E8;E`Rds5m{xrqw?=_uOFUB;SdZm5Q5n(}eiC`qKtVp$kr5V!XMgV9ungs~10=y>wn($ijAs=d~eG=>@H#OA3;fOZu13 zo_Hzdzqft%8mm34*{*0DKnjO&WOF+O&Z{it4{>&Wd-1&WsV6xv&o5W_)Og_N){K{x z$xhAsV)j|ri$za=v{rF_qWa?1!5ex10%qN6I-7NUVqWRFW60yQ5yy&=3(L7kSMnX(%l2^~Rvg=KonSX2iUMIR`(nIFsMp21{iv{yC8drIl z9ON=N@d&gA6BfRp_3tpFSzKy-^ya6|S$IrV^1)ZeL>Fc01a{G7b$d6Zv=yzre}7GK zV`!M4#E(^rZu{)adaJGcL4Lj>ugeZaHswsO%_?%BHL);rLF?(^=1!=|zHOG7bUkg& z-7G$<*YV#<*UF|yANcr-Q&T%|M`h*uxHD=6zKOmGVTnFZ*Df=-{%7X$WqQ1Qzmsm? zHg``$u7{#whJxbl{rkCM(lN(tf8Ki#wfewe_vXAK;%nIrcBe8goN2-_r%?9gmesL0 zT233Rm-f5)xK*NL%fd*X>DwpUpOX1gnd+H)LqzTv)=jM z>gfdro*Yw@*S+M6e;gS<>5TY})e-9?H2fw#Khe%UJ5Qu-P2E$gWj8u69oXT5Jbo67 zZ0-t|JC7fqzTm3=miO0`Y3HRMdv*x7HE)^3y`hrr@HMOSML%p7)t_Zq`*p&lr7xmy zF|}`;ywo^w;nKv8e!)EHS&+55ka98(+1%SY4eqBJC)~ZUOT06yR5L;^^fI}ox1oq%fvHi^>J(d%c83SelqTUdiMIiD~I`n zC%JrVdYd?8@#*-puL_E-G%7?jZi>E6v&o)4gE#u` z$%_-h8Q2Y0wU^AecQxeNvWR0B(=<6OvNB%S?NpLgxJ;@s^BiuEvT>t@qYlUCd_Yuq3li zN;=}4lol!kcsu5?};X*{>UWX&wjq%YzFp@T|Hw@4^* z_@*G6o2=&Z{YLw`MW_0*D|9A2etxLq=QpFBU%Xk>WLB#i*8e}XMABBQeZzv(FOTP{ zeJiMVwCd)(?$6v=a`rPqI`_mN&x@oYn>$tf>gwZ1KJol=IFQG4Zq|>@+ZA{&cl)`& zVwjrgm9#$5)SWBi;Z%+0KCdNLPjP=%zuWdVz`A``daGZ5Quv3z$nH%;HrLG}x1P!G zxH;>L%Zpw`$K;;gq4d60zvf8)*}IcF8N8ogU1}4q^EUeak$g3l{}*}m!#{n`>e0ZG{cQVfzUkm5I6I&`+b&C0s*stI}wJfzZ`a3`TNG{#@=J1*Cg_Y7T zi?3VUFTdF$w){W$n%)g-R&yMCRW`ZzF+@C_7DW_J>Qsx$Gbl$!9))H=!!;;1QE5EHevHE|Cip?ILS4Xlpe?2XC zx%ax+?xaG+m0A8xeNx+|HYi$GsBxY|G8eRt7i1;~vmE^p70X_`UcI&~q2v?H8LJqx+qUfpt6DZ)`$pn|^N&tUw`7c;UU&36@_1A( z$UrD&xv(mEWl8A1ZAM&2ziwu!k2Tt|cimJc7oHb~Gv54~_I^WA4@U;0fQ{g<^H=WK zX$39**s>+nVXdqEji47r3D%2|!#59VCWvBLsQNiGXC=qMCpNlg_XtYalz-7WU^(ma zYl|NO5!TYpCx0*6R`%W5ge_rzA4}w;@0>484zyXl@$8V@n1A%gK|dCxZ~(1i2AK)M zEWeL)y)$ueeWqf}tR(&HW9wnBjv{B{Wf#8${GXE$Zkg_*_e|=LVP!$qTRlzB9~f)CVh`oyp81E5|DvV%<}f4yIIyeQAORG6LPqAtXQ-1`Q5oI`=8(a=GpO*OYCs} zf1dq|3LN5#U9DG4ShQ)?<%8WfNQb!-DJLzRxaO4MUS@()9HhPhsAHQK{-x=vAlb^|` zompl!13BJ6`w2m2f-uYJ`D=A~rhb}XUUy>Y{B;LIHhi^@J<{Bn_*8W7j9BBK*QeM0 z&r{^%;n?5gYTxR2AeGIWBde}o>d+mQyJ~kdLy+@vImkdLX1V%j!Mc{yQ>u5pw%GPE zN|^7zksIe0(c^MOr*#)UR@^w3`H$hWTPODjA5f0o9P%_Y*zKO%(d{#zEyz>b-+p47 z3S|8bq&%yDnhBy<5|^81^~q0j`RI4OiFeYGy>}9Bev3aJ{l~aPGb3l^>aYELE6lqL zZ{(Q2`LS_l+n-B|mQ6~(fAd?=B>RHJY1aRc$7x~vD8c5sUJ9uTXSsXSSW8*UiJNJ+ zU#s2wkdh|9i`!@9{BSq#w91;nxlR4f0`1_h+YPOzF>agiZGy6u=bi@-)YfgxKaV^Q zSp_l>idl?q?5n?-sdC*wygMxX{JFIzZ^a`dyPy9NTUo?-OK7^`oX%KXx3BoVix+boBpVr7b0&9XYSZ@>H5#md4F#u zPu<_Xi*d%;ymyX;e?Jr-Nk%dkw*M4lG|S#C8EfQR16kKkU0W4=q<$4!tFqd*6*(XH zHm7mUo}snvuBS8Gi{40%6v4`$f??8q6B?(^eRt5g&e`*q>8ejz$nzJVeXJleL6{}f zR#JJTr0bQ3AA33vm-OUz?%(}5tL}iI(A2&Q-~Vn}Z(n^+?6~ksn>DqfvaZt=)>;Of zwdCJ3?`D>`#KhuPKOuXiAn6RWFBW7b2(vhT5ZQWvz32s9QFf*e3Y(Jd#wX?kt>Eao zd8GNZ!NCuA?<_mW$n>Rf`JHo8d~JUfkE=Eae?EWf{3ku01nv!2qot6-0klRHWF`o+ zyidPWRpE0u@us1vdxe6nhUQ9vw$D*2Iz9IrWe-Gq-+p~9JNMV8WZRoM3c?{X7KnW` z4B`3Y^;A{lE{DsPi{hX?i?H&n5o90~vm`gq{H>jub)|-(e~;$$Q#dMLIwLjX*|KxwPq+~*y-THqYt(VA1MqGCcN1ktKf|?1USPspJ z+pLpD6EQ@8;RIn3~k5bc_H)Dm*A)kQewGSWKda1Fp$xV4>V;oqN zIYaM-wwqBZ+tu|hNa4^7G7yScKEDrJ`Rum1Rrr@Db&>1(ZeLrY)FJ0`=8n__4%1sc zGBpj&vGSLvO%e@=F7Rrr|9t&$pfcn9Rkyz#VdFi#@71ZhNanUc%>+>_d!I?PvhCv! zVbr#K-|F}=|M;zAQ6GY8aw-g@ndVu(@iox7vEt_DNm>;*_=J^zb~LPBTe#-nLMQ*E z*houp3jxU9Nl1EV1sMp%EKB#FNlJaQh0|!GVBx13y~@#UB3C#>c;8R(J+``0k2&1+ z730hQuOFnXW|Ek;a=$+BUH%=5Hx}$$-LHD`=36l%qWOlnMwx|KbOa zP15T4)XDcvMQ0|tJu2}}dA2L{pTPEg7mu@kTy~@V(W|9Xf1YBN^17lq!BDv9#rByp z8zmEeA%#Ob$UrD&u|JT^bo70y{!|51j_VQboQ(SK+k{_l6X~AhDRQ-A#oq1D4y5(g z*{zoM+4VE$nf50W=a}tsR!5mG++Ffcc()E@9W*2yI-q8PD3)KcsS`FWOgVC+ufsIN z%hM$9Y|7L*JI^-r`M=t z5v?0No)%lkn%lpx+qR{2?WUg#U++3}>@SkJu>I>Gqgl>pJyL$pc1Kb8iSmP($xqMz zcK<0cEn&07{)W9lbk!^2ZJllAx$v40vD{`)$EFX_;R}zxB z-5>*@m}O;$t;XLSC3aa8*LyzRUXmGUbm9K&hhMIIzI67R!t)-vf-G+(?*8mO2TJU= zayn!Q&!{Vj`LW9CgEH^8MLjtY=}6}GK+ObEED~!UJQcnfX%bYiWo5&E@wRRImQ@_& zp10~tSVVPrrOVyR>UFVsj+^dwHF_NT#I-ZJ`$5mwxhE&jnXJFXY16V7$n|kA$UrD& zS>z_-zAeC}eqQbC9+k|jhyV3*J@}q`GM*IvwEe@TD_s`0DmivGKElsbLl^WNf9&Dc zy1PYN(j=(J=cB9Ut3VGV_kzv=fEvrda-if&fvHA%;LraaQ&)J;P0X{O!0G$+Sb6`q z>+>C6=IK2BrZy$?=_I)oeSIfXUOe5F=X7W4hUBwv7|*YfbB;NHy#ApdW+ZVZ+AfJ?kqT799Bh z=$88tMy^lI%u6dm*Bsc@b}733bC=4Wy3HD5L#pQCj?evNjw+RwYl?O)Kj4KSlw_I6hX*oJ(~u)1Ea)3V#!>bt}4 z_skpgA5DpN2`Oj)%`>-9%U3v;p;meB&VM}<1*2N_xh8M@y3p^rW|-^NUwe@2<0&vh zLGc#*S3UCG#EowriJacYG(WmaReILUte5)r%in!?+j{=b1oyVB%)A~2*{=_nYtP=b z{AQ%}z2N_c69d=qW+R{st6ajji7LHtYej+r8c-m-zy`gB*Dugdr4o3?wFweH#qV-gb+<$jtc zE-Q5Najl09K2Z~m&cs+Bo}zK%%kR=Y8Mz~Jd7Q}WRzT-yfXoD87D0_$Y)SvWo=N-p zy#IvPFONvafHu{nNj8`2ySL??U@m4T+PTQyFT_YFmceeZ_+OOMVEkE6|M>2O7)Jzb?vd<@M@za3dIjn8Ue*^ai zt$oXy_}28x9M>+d1G-VnPaS7{{#}0Jk;@W~LhdZQ-vzZ!n7k~Jv0Y-=t-LQ*SA6FgIa!va=K7Cc2ydO>P#v@1Q*XbO_|3CO=FSBf z2*oTL#k(wEC6Sb)Jf75QUb=%fx&xCU=%UGV4mRUdi?YXzPOGdAFPuS7O zIzC4KlU5z(HD_w=V^+_YhP)mew7(Z>ECb8Wq*WW%?tM@!RGe`n=Z2=-^b*xijBWE? z3&u&UEL|nGuVHGH{!h)A23gnrzD2hj=iN!SnR9e^2(QqZ>`4>FPav<$oDVY;l#dyU)xA(??bFGnTJORWPJ|v~lQ4PwN>H!J_)~xI>xqiLkIAlUHJE?S>g6oAe(D`^rBBQ&#DCvy!AwN7C%@2zi>&K?~jicI%Zy~D$}xE z8v5tLDxnVHIqZ?T9Bo=M;Xfv<`OG$Lb#`e?X_r-I#3!Wi1?_K!8O`F)-7-h>WA544 zOh4lPY&-VuQV73AR=i^R{_@;2oBJCS_qRMa{WdXu;~tf54!=*jmbmS@XaBR*hUwMc zmIW`PA5TFt7k2&($ZQs+__gNBvp)uWG%x-Y*Wh?CDOY3RX1R#9LQfAo5&qS+|NFGo zr5m-H*H4>Q6t^(-!2V4$UOk%l%1lenIO!v_5IoH3$blwltSO%8E=d+qOpZLrmySHvj&Vlnk zH)ilpc;A+vp(ozDeDaR$6xNGJ8l?_|KU$WSdHKxVsHHj+9EaZnhN zdtv7Wfy`#Hc_O}Vy7%k*P1By~bVYi;RaMmbvo^_e@1Fc)S~Kte7XA~MAt7-7V#b-{ za_zSnHaybYcK&LFK8x$1sQGd13wtKsz2eHr+4S^38=wO^i2EPZfr#=fexAzWD#4rtU>?3S=>*pPg` zv^z1Xy`z-Hndx}f%3O9dh)@`=frhfcKS`8XbhM4m5P4>AynS>medeY!jfEiW=ReW==$w;)YG#cM;{&7TZ=MJ_zP zJ}v%&MRO?A)q( zdZI;h@r1&|hZpSthXZ6EAm|({keMLNBKgu`PqxUxJ@I0bx~1R!EBo*)Z~GIem0vy` z`onQVTdE>iI8vKw;kC{Zo!*~|E;_T8oK|@!cIQz`gZhbYxOX$uOc2GgU-OSI+vGI9t?wjWiD?%|rG53f7asR(mvZ&9 zqy85}SNgnf$UbpoeS+4F$h|WTe|d7s-R0nvGm>3(o{1Op{p9_?;Q-kOxCLY&6ti6L z=a+i>|3k?Vmg%{DRd(}~T&^$&{f}*aclgxn*&m*T98AibmCO`dd(wIN(sLIY#NR!Q zz5h)&E}CWMTA{GbxiI&F_T7Tc7lRthz!I3fCG!0(Md`3OCa>x>3!?6BboZIQc^%`y zthK4zKeBaPoL})E`lzf3r|5HyDVDXDouWlwh`C>JkqThVDLg7y0d+5EENUCfP*6QD zEzf(?V&lYjD;atn=V`}rh9v}g&JuCBvFLc%v9_jNXDeAJ*}oNMJSHt@?B@M1VAo6S zAm`J^ryp`$w=?#dU-d<>xsZL>+mX$k`FeT7r-Iils}{y2Llej>CcPR4)yy0HWyN!?Lab@VkVNg zJCO`y`6nuR&Pi^)Q2R-t6-(yqJo@70gJWDgU*4SIe3>kBHSC|3YWqJw@4CK{Z|3>_ z3cqFA)TIQx!VknYxNW&1+avHC$z0I+ZZM-+eiqr>l-$;`^5_AcNips#AMl;3>oVBB zM$UTepKBIkzgc7bCJX(aw~FhR_=%8+ODALYZ?_LVY;@xf2Ydbp!3B!Q>oRx43rk$WOrC{ay9%OUpR_;A$ud!+ytII(x-x9+wrot88;Q_{KbDcU` zXa2HIl;iCW<-DVOyY!IVy8DwB^J`wLo_ayPGHBCr!PwlHmB{0Wdy&meH0_Alb^l4U zw$7P3OAgyQuYR7^Qa|NxLuBptKiVrkJ?`78FDD~A_axgasj}9O%lwn7bL-Qd=o}ZH z|Kpdc^CIN+M*EP>?bNq?^W9Ha|I>X=pZhAAznAnn@@)+ET$Xx9&}+x4${jno_>UX*2K*TZz}^9ThwjYfRr8% zAe;NocZ(kHst+42kEzY_au%Q5Ex)IKuRx;FgpeKi4(&3jt9V;Ya#C5n*%eJ5Gdy4L z+eh10nl*UR^1WHnrvIvK0+Gx;h-~iS@8Krl580-%FJ+ANUl?}w@$MzdZSEhDtDGPE zJz#zacjo%3r?ngE&-Mi5cJP-qEBRQTtT)%u%}dX$P?JeGi@d(?5VE;!uS4wZUA`VY zqsM(9`dBx^qE5BL@2*}m{TF}av&?C+H)|HNerh_`#(HMmob}mr5_X)mR#!Z`%&G6| znjG8UXDZ13?!(CD_E~nGW;!6&I*&~^?EU?UvvU@iCZFh95@^CXNl>P!`R+%ToU=(E z7f+q5;J&6ryo29Oci)9~lio-2rS`18dnO8bKK2N*xo_)^iZEF4Rfq@0SN;-{4v4MI zo9iv5e!pjS-g^!0v~71ewPkq>qPz^MnS4xAH?Mo;Yq+##Xh zb7gg9{(m|Drt{Y4E%!UV8eiLDY$W)4`j@s_+^bxVoKtjTzO`Pj)!kQi8fV}Vn@q$0 zGa7ZptE3Jbns$HY(dprZ$m4Rykj*`4&T*A5X4%;|kBrpU?;6gVrLahbt`U#<(yFMs zW3d}+GRGwj#mNs=l`7w>cD1l(UwiFX@BtU;tIv2j7YMkiBj=0b$mU8|M{Ar^cwBqO za`x&E*1wALT;#>`Od4{IK774Vio^JP!=XgUPkVKRrmGzP&3@46gTv?6yKg(Aq9ogI z$#X82N8aai0@>W1pH}8&y=%5}SoF@IXkS=ysEBBCciZ~b*{f!~_-k-sUBKa-g_&MT zQD+`DZK7Jha_fOtUMY*L@qQBe9<{eFZ zent1d@5bh@q8S#m@2>ux+FOwyXLsJe_$6!lg@4Wam-huHJNunD(d{{V8}rXsNamhG zHur#o+tuIUkIWUO{7Dc|=A1dVU+>Cn_2hfaOP(#67@YgwEwy<9hw)v*?)`qb_Bm-O zhfhVN6-7LrRH2=GbImp_C5Q^x4wo!3}5A=01;pyz^gjt=opk3)7I?3p%$GW;6@CxP-I? z%f_2-7cVuOc)6uY=-BLpSv6hv9nIXLIOR3l9{-x`yQ%Bu8!z3RvzHVn3%_?X3EY(# zx;v9?*UGaok3nY=!OC^e`JZreJ;Q$7KHO7Y+IMZ*)O$O3ZPhsQm|>shbE)h=5y`su z#p~~v-v01^Wx{u#A5Irm&CV^}8@~6}XCA)$b$m|xy+vw{NbWrcGn5)MlDV++NkPW4 z9J_MA@*KyhLaTcxt0pj}NX4dAS^6H^pXFF{Rf3hhd=q~u>-(y%WQq4K%j7mr)SmY2 zC=>Su;g{i#GafwrIA@s@xcv)hKY-3T1(^xLEI;N4u46f6m02jNaD1_s$8}*VmaQTC zUzw!ZJD6SADCKb?L}5y~eAHCO8maub5}RgE@>D8KViz%U=Q$OxXkGCe$-Ngr20}55 zd&FJ=dyy%-_t!OXo5;pYzIy3(_1$et?YI2(;d&ujXK*~lH~~5RUV@qlqFAQPUj1ax!Nyqi zv@VVDOUvpv^YVAzUuLxY{byH$SlxGrU#wEO5M0~*`PcWy8<#0idjCe)^E_xJL^7q@d*L#SJz*^7! zrQ5~Qx7uCJ+QQ7RNo4=_d3R?E^I9JK6LkK;wWZlg+c+mRc5b?PMK)Tg>H7K#fz7Xs z&fhre(!1E`EpmGjbe=89Ob}+-xX|)l^~z&@>+h7AKY#I=olWY}rb(w=FMgP^G3Aef z-o8(pofyvvuJ7Qj_jT;lOPRXryk`Cd^*s&Kg&w!-&ANfSpX@rwKqzLpF+u&lJ%jtI z$J$4hed@Z%?NOh5vYv0z@2$djCFgNm(YP!AP|9HEj3a`d;vRK>?vLAf_-NaQj3cMz zr+%LO$tS83DI9J<%>+>_=A}KGUQKX>Us`sLV!IWrDd|5~?Cjq~)cWjpU5 z64`QXr^1QwWSa{S<=0-n*%clh-!eTpPfYKAT3s&sPR*fUy1TD4zk%bOmCBpGi`A|OU+$p&=y#5?{I#uf ztJ;yz^|=K#6GX9yyuaA?=jXOJ76z(ImYg{*vb6ceZh3hl>34cPCEq0fEZxl%6LnbW z$;vzXdcOHz&7Qr&fmyhCho~d(l!=FH8HAA61>FW22*oTP)xPh2J%d~8rv6jM?Uzp0 z&Nva@N8X|&Eg)X zb8-g!3yy^^{OF=HYd43x+hXAwmt5q1Nq0d8LNQCinwIR>8OPYKxF6r7!C;s4s_5-#EI>a!8!hp>u%i8}k0Qdr&h$ z6ifcwIF*+AynS=P<0*v1|npQpoYX3#5cX*vCv3HQQ)Cgsw* zGKbGk$y@h&c|}{Bo(bcvIfq^yjuMn)u(Y~#`P|XHM{bNhvzfz6~D=OT^1EQ$!Vh<)_t)zPzRN>T@T z*bmN$)t|WDY#oD3`0F`76<4RO7O;{n{5E@i1{O213a&#N z-yT!D^<^~+*EhS5e$}hRc>jtdY{_C?tsmH7{rk(M+5o-BH*Rn@g|JYi_qm#pBmTqO!u(jKB`r5R_t5qjc zFP+R+JFB+#qR}rd)w!t;=86}sX8IMnt?;(rKSA?XuS${J`vlosL4&AWJ*NJ35r5Ns zCTZFBDVwCvNtUVXd|0_S<<-{d-^0&WX5TuJ{(0xg)477XW=(1jESp_;+*Yf`ONifL z*2z0a<~~I>_u+>n@9*fe94|JSyIS6FRn#VzM_Q^Yb~pvp_jNr|2!212-GA?co6h=Y zqqe*|7C$d9VD-Gk-i0!^^M#eH__xkQURUu9+1$ig=Gtc`@oz~wrOR@~g@2LJ=XYLl z%St9FUox`Yc)U6EdhF}lPcM1AyH#YKzv<~Z$`vJJ_-&yme7 z31{e=azTuX+t^@RIIA7^huPk{C+d5K%@#P}m$mGT{%&#SxwGune2bekb-ERU)AhEU z4;s!kRi;drNuC>@ri;9<;{~$0wxX?aPo~^`@YZ4S`#IOFRZq%okNC2E@;a-7#WsK5 zR5|>-IkRS^`%#6DN}E|T+c7 zZc`h#cI5SluaM1kom^mg()Imgq5sDYH}932#rv&XeWv7`B)%tV=|bI1AZRzQ+=Sf~JlzuCd72H{vG9~+p~{9SH*IW@s3cQ?CSBlCZ2pKad%!`g(CRTc)-o3j(T&u$#Hw zQ7YFemZ1o_fAJpK+!QmroS!?-njHPv<+O&?cFtOnhK3oowgOvieNz+jAD!Rlb5o`< zZ1N?(eGg^VDK{%S*?ihiP=E+bqYI1uyFxT?73^J**r!ud|dpaNm9?E!wNE5!RT@lXBtfJn3-! z*44|;d3D7_x6c0Y%3j&>*$b_8oLNrg~94cJwAeulm3=@ z9D0(U+o*UPLG~NX4)t|od690CCKs8FlaQPQCYOU9QzS|LSMDCYoZ*;>!=HiYDgSM|EXP^V{+( z6m%v(tiApX*<86DI>%pWuj*MaFT*uwS-bFZv+s-QWDNx3dR)X;@a~%8H!HEzt!Hyh z?wtbedn**O5AFYUTEvnyyk63>GK+Zw@;M{lksJr0-XRomxF3TZP!J#Na1vCn>WWWR9pYM$om?fp!QZ-ivat+-Fd z)!1`s{`$GMZ)@HbRpfQAp!+6ZMzg$#EZ({0bV}~Yo9oYpD5zF=B%fQCag6a(hN|t! z*7pUw^%sViOfY>7Pz{ z{O0bw@@~>Q&fA$BHue$~CKqqNS<+#aD5j-+q^|dRaq6k?2WSK%covF^*7!h^a~5w;=WHM@6FQ=m-B0F>;l-4 z*L(g&Hh1#xW0`gKNnzWgZF;!AT(B&YQ>cDru{wQ)c-~pVm*=KPp5a!}I1zbceajq) zNyc}Mc{Q1&f_os%{^hULal>=AuC&I!rG9|&bG-< z{_SEqd+mEoN7eP;_H%yAyRT9|aYluMrQRxF`w4+9Q(V>uwXiUq=0NTb{YN(U%-m&; z&8D9=vRXZl3vZm%YS`htCHf5Ci?ds8mA@WbmiJ^8v(X{WO;gT@%Zqu=a?MEcy!G|& z%Fxnq+iSx1*B5_8axVkYLA@*)O+Uruo@KK}-S?H6o{%o!r)2iabcM@Dz0D{7^;+uh z`PI~SpPSe7g^OCwI@TF;8@zWagv6f`oVupgqnoC$pHTtXn#duj=83 z_3Kq7+BePqsiQcVbKzAsONE(#LYDiBYgf6wU$sLvSklwtR8sGvnPCQ;YqO4R{?~c> zaL;ayzsUIlbngn>T%S+js}E&~E{&L-Fm1-~=k`fPi`#;X_g~OFvTVnp!(-?*Z=lTj|zIDsk2rG$CoWhD24#uVY^-a6!_YBpV=ZQ^x4B~|4N zbB}bxzGF+zKmTB-<)KvYk*hVb{CCjj`QH^U-K1YLH}1Vx6j#u_AijHnr>{HjVy>#9- zy{2Y)oW5_uJN1Z1|B%-|fX-lqyLa8(ON;$Z72aH?xp95t;zCEI?6(`IdPZ7$U(~u1 zzjEo8jQ9RCDi?g5%;#Kw*x&qN^UEF2lX9o-_}vtjm6!Ch3Ax<|+9L`#*XYL!1Em7N zRhM$zRGz$FKC5N3*Xy8-HmslL+>!ZWYqisL;p7=gW~*43JXW+mUUsMX*_Ve$vUc9A z-fy_5*v&}=dA%GbayZnO?iZXl;j2X7$A@koD*nBD|04S7Yg;3xc7{IV{I%@~R+c-R zx3+OC`@VPfqxH(yD!6Y=60D4pl$-72#_sxh0do1vg>3G#RmxXIl{6mfa436+2Q+qk zW4Z2a+-a>o<4y`g|LZh~=GO(SZ@T7+BJoOm z%M;4_{+GZmnRhdbyxvV~gq+h0X`g}iIK#vDl2FK3h5miX#tPz*VvGG_({`1usE)k& zrJ%|7^n*>_r;gn#`tdKv5yBrB$>wdZc0)1LD0F+-s3%ISrL zXG7VM&xr)Bm4drB^`HBVHMzGXF7MrxZ=V*XyQ*(A4J&EOg>$)gui@RzKD?Ida!fx17c=^t>746dBE-7zs?0wF{W!6A zj63dMIXL5#(=+>Pe_tDKJcB&00a{xNH~0A@U#&-Czkcn|eCycrz>(2CaHIb9US2d$o<;&sXQ`B`ukruns94M3BRwUu=`k zl7(x$R-KtEw<|?DOyq{c+1;F9zWUeIHE1x4HT}6U=}p{{lkeBOf59uE^MYm0R-Qe3 z-tZmlSQpPLHsxC*lDVKg!*KVOuZuPdk}8*eng78ob8GChMXSQ+ZSQ-Mo*ue(@@mr! zEj2rCi@WbNT+wrNwP^g+C$IQke(+CLDK?bLd~-xT25;(FyNc`j(;J{Vm=StDtpu`rFD$Zu?e}r9$m9ie$9nF% z)~cOe=xdjEFD*TZ75w( z$~KV$xt|5Ps{wBAGMjC=9YLF(e~1d&V0(V)-NV(txjUzgU#gW1zpR`yB3 zj~=D{$zoLr(PtFjT4VO+-igmyZr%*{WZoi=PlC=Mhnwrb@OkROJD=1=KlHuNT@qZQC23aMdiPA1K6~Kf4yiZ)1tT8R7p0y1t3So6H0#RWO1)S< zV+pzEnzCD$Z(j7FzQF7JYJKE&@t`#TaQDu&cvXAARY=RLv|#fbPJ92tg7u%FDa0Hw%1k6xP`(EpiOB{nVBI$_$Q#K#|PpIG%Av|N~7G}An+A%b5yCg$ zGfp1dX?A6HRpF2TjU(JSzrYvcl9gL?_tG9&5KA&qT`}Htqby&=bn0Er* z*H-UUUvA2}ygKBLJBO2g;0K-FtvOzEm)YAR&tHJr?r?MeT>tH_bNTp^?I(r&avq4j zzgM;Xxc2SGi$5^zEW2;u@%Nwumt6dO-UT&H!H55x$>fdIPWIK}_m^7%a zgvA?Z?K0e48&wCVGh$WQy8`dz@C1hce<51g)y33a&R;4z`);y4&+?|n8<@WFx67-W z1>60&HL+k_({ZVg%pcEM+Pyj|X0JjD2My$Svu)6AHQP2j)$7>nOZ)}rxj%0D>A%K; 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 Date: Fri, 29 Mar 2024 19:57:22 +0700 Subject: [PATCH 072/312] ci: add `codeql` (#38) --- .github/workflows/codeql.yml | 84 ++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..b5be0f2 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,84 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main", "*" ] + pull_request: + branches: [ "main", "*" ] + schedule: + - cron: '30 0 * * 1' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + language: [ 'javascript-typescript' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" From fd76e0af72fe28b414ae3b5e8d3886e58561e57e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 29 Mar 2024 20:12:44 +0700 Subject: [PATCH 073/312] feat(bots/discord): sanitize `BasicDatabase` inputs --- bots/discord/src/classes/Database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/classes/Database.ts b/bots/discord/src/classes/Database.ts index d0b7b54..7da2bb0 100644 --- a/bots/discord/src/classes/Database.ts +++ b/bots/discord/src/classes/Database.ts @@ -52,7 +52,7 @@ export class BasicDatabase> { } #encodeValue(value: unknown) { - if (typeof value === 'string') return `'${value}'` + if (typeof value === 'string') return `'${value.replaceAll("'", "\\'")}'` if (typeof value === 'number') return value if (typeof value === 'boolean') return value ? 1 : 0 if (value === null) return 'NULL' From 3f8d0fd8eca8b0cc0900fcb99fad52dbdfd6dd44 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 30 Mar 2024 14:42:41 +0700 Subject: [PATCH 074/312] chore: remove unused dependencies --- apis/websocket/package.json | 2 -- bots/discord/package.json | 3 +-- packages/shared/package.json | 4 +--- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index e803cc2..734c3a6 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -30,11 +30,9 @@ "@revanced/bot-shared": "workspace:*", "@sapphire/async-queue": "^1.5.2", "chalk": "^5.3.0", - "node-wit": "^6.6.0", "tesseract.js": "^5.0.5" }, "devDependencies": { - "@types/node-wit": "^6.1.0", "typed-emitter": "^2.1.0" } } diff --git a/bots/discord/package.json b/bots/discord/package.json index fd4ac1e..64497e6 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -8,8 +8,7 @@ "scripts": { "register": "bun run scripts/reload-slash-commands.ts", "dev": "bun --watch src/index.ts", - "reload-slash": "bun run scripts/reload-slash-commands.ts", - "build": "echo This command is not available yet && exit 1", + "build": "tsc && bun run scripts/after-build", "watch": "bun dev" }, "repository": { diff --git a/packages/shared/package.json b/packages/shared/package.json index f1c9023..6c1a25a 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -32,9 +32,7 @@ "dependencies": { "bson": "^6.5.0", "chalk": "^5.3.0", - "supports-color": "^9.4.0", "tracer": "^1.3.0", - "valibot": "^0.30.0", - "zod": "^3.22.4" + "valibot": "^0.30.0" } } From 669e24ca8103ea051b4e61160dd0f978e36707ea Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 30 Mar 2024 14:45:13 +0700 Subject: [PATCH 075/312] fix(types): fix issues with typings --- apis/websocket/src/classes/Client.ts | 10 +++++----- packages/api/{utility-types.d.ts => src/types.d.ts} | 0 packages/api/tsconfig.json | 3 +-- packages/shared/tsconfig.json | 3 +-- tsconfig.json | 3 +-- tsconfig.packages.json | 3 ++- 6 files changed, 10 insertions(+), 12 deletions(-) rename packages/api/{utility-types.d.ts => src/types.d.ts} (100%) diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index defe268..93b44f7 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -40,21 +40,21 @@ export default class Client { this.#emitter.emit('ready') } - on(name: TOpName, handler: ClientEventHandlers[typeof name]) { + on(name: T, handler: ClientEventHandlers[typeof name]) { this.#emitter.on(name, handler) } - once(name: TOpName, handler: ClientEventHandlers[typeof name]) { + once(name: T, handler: ClientEventHandlers[typeof name]) { this.#emitter.once(name, handler) } - off(name: TOpName, handler: ClientEventHandlers[typeof name]) { + off(name: T, handler: ClientEventHandlers[typeof name]) { this.#emitter.off(name, handler) } - send(packet: Omit, 's'>, sequence?: number) { + send(packet: Omit, 's'>, sequence?: number) { this.#throwIfDisconnected('Cannot send packet to client that has already disconnected') - this.#socket.send(serializePacket({ ...packet, s: sequence ?? this.currentSequence++ } as Packet), err => { + this.#socket.send(serializePacket({ ...packet, s: sequence ?? this.currentSequence++ } as Packet), err => { throw err }) } diff --git a/packages/api/utility-types.d.ts b/packages/api/src/types.d.ts similarity index 100% rename from packages/api/utility-types.d.ts rename to packages/api/src/types.d.ts diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index ef4e094..6c0164e 100755 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -7,6 +7,5 @@ "module": "ESNext", "composite": true, "noEmit": false - }, - "exclude": ["node_modules", "dist"] + } } diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index ef4e094..6c0164e 100755 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -7,6 +7,5 @@ "module": "ESNext", "composite": true, "noEmit": false - }, - "exclude": ["node_modules", "dist"] + } } diff --git a/tsconfig.json b/tsconfig.json index c7d6ee5..9962c1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,5 @@ "allowSyntheticDefaultImports": true, "isolatedModules": true, "allowImportingTsExtensions": false - }, - "exclude": ["**/dist"] + } } diff --git a/tsconfig.packages.json b/tsconfig.packages.json index 685fcf3..f900901 100644 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -11,5 +11,6 @@ { "path": "./packages/api" } - ] + ], + "exclude": ["**/node_modules", "**/dist"] } From e25fc7d3c542321cb9dc791abbf3075f53ad1585 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 30 Mar 2024 14:49:17 +0700 Subject: [PATCH 076/312] chore: update contributor email --- apis/websocket/package.json | 4 ++-- bots/discord/package.json | 4 ++-- .../interactionCreate/chat-commmand.ts | 9 ++------- bun.lockb | Bin 113640 -> 83488 bytes package.json | 4 ++-- packages/api/package.json | 4 ++-- packages/shared/package.json | 4 ++-- 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 734c3a6..3f73800 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -16,9 +16,9 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "apis/websocket" }, - "author": "Palm (https://github.com/PalmDevs)", + "author": "Palm (https://github.com/PalmDevs)", "contributors": [ - "Palm (https://github.com/PalmDevs)", + "Palm (https://github.com/PalmDevs)", "ReVanced (https://github.com/revanced)" ], "license": "GPL-3.0-or-later", diff --git a/bots/discord/package.json b/bots/discord/package.json index 64497e6..2c82215 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -16,9 +16,9 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "bots/discord" }, - "author": "Palm (https://github.com/PalmDevs)", + "author": "Palm (https://github.com/PalmDevs)", "contributors": [ - "Palm (https://github.com/PalmDevs)", + "Palm (https://github.com/PalmDevs)", "ReVanced (https://github.com/revanced)" ], "license": "GPL-3.0-or-later", diff --git a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts index bd1536d..c91ecf5 100644 --- a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts @@ -1,4 +1,4 @@ -import { createErrorEmbed } from '$utils/discord/embeds' +import { createErrorEmbed, createStackTraceEmbed } from '$utils/discord/embeds' import { on } from '$utils/discord/events' export default on('interactionCreate', async (context, interaction) => { @@ -66,12 +66,7 @@ export default on('interactionCreate', async (context, interaction) => { } catch (err) { logger.error(`Error while executing command ${interaction.commandName}:`, err) await interaction.reply({ - embeds: [ - createErrorEmbed( - 'An error occurred while executing this command', - 'Please report this to the developers.', - ), - ], + embeds: [createStackTraceEmbed(err)], ephemeral: true, }) } diff --git a/bun.lockb b/bun.lockb index 6e07953731827d6a9fc26619ff4d4bc52dfdb524..be6dc631bf0434b0382b0f8bc2091176740c9bd4 100755 GIT binary patch delta 10633 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 Si!qq111dPdb-LDW#(e-@rN&zT delta 29150 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 (https://github.com/PalmDevs)", + "author": "Palm (https://github.com/PalmDevs)", "workspaces": ["apis/*", "bots/*", "packages/*"], "scripts": { "build": "turbo run build", @@ -24,7 +24,7 @@ "url": "https://github.com/revanced/revanced-helper/issues" }, "contributors": [ - "Palm (https://github.com/PalmDevs)", + "Palm (https://github.com/PalmDevs)", "ReVanced (https://github.com/revanced)" ], "devDependencies": { diff --git a/packages/api/package.json b/packages/api/package.json index ca31c12..2eb4e35 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -19,9 +19,9 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "packages/api" }, - "author": "Palm (https://github.com/PalmDevs)", + "author": "Palm (https://github.com/PalmDevs)", "contributors": [ - "Palm (https://github.com/PalmDevs)", + "Palm (https://github.com/PalmDevs)", "ReVanced (https://github.com/revanced)" ], "license": "GPL-3.0-or-later", diff --git a/packages/shared/package.json b/packages/shared/package.json index 6c1a25a..09f222a 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -19,9 +19,9 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "packages/shared" }, - "author": "Palm (https://github.com/PalmDevs)", + "author": "Palm (https://github.com/PalmDevs)", "contributors": [ - "Palm (https://github.com/PalmDevs)", + "Palm (https://github.com/PalmDevs)", "ReVanced (https://github.com/revanced)" ], "license": "GPL-3.0-or-later", From b6cbe9d64c01ff11feab8351fb801bc1aee48325 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:10:51 +0700 Subject: [PATCH 077/312] fix(apis/websocket): improve logging and error handling --- apis/websocket/src/events/parseImage.ts | 35 +++++++++++------------ apis/websocket/src/events/parseText.ts | 24 +++++++--------- apis/websocket/src/events/trainMessage.ts | 28 +++++++++--------- 3 files changed, 41 insertions(+), 46 deletions(-) diff --git a/apis/websocket/src/events/parseImage.ts b/apis/websocket/src/events/parseImage.ts index a94f689..cc0bd2f 100755 --- a/apis/websocket/src/events/parseImage.ts +++ b/apis/websocket/src/events/parseImage.ts @@ -16,19 +16,18 @@ const parseImageEventHandler: EventHandler = async ( const nextSeq = client.currentSequence++ - logger.debug(`Client ${client.id} requested to parse image from URL:`, imageUrl) - logger.debug(`Queue currently has ${queue.remaining}/${config.ocrConcurrentQueues} items in it`) + logger.debug(`Client ${client.id} requested to parse image from URL (${nextSeq})`, imageUrl) if (queue.remaining < config.ocrConcurrentQueues) queue.shift() await queue.wait() - try { - logger.debug(`Recognizing image from URL for client ${client.id}`) + logger.debug(`Process queued (${nextSeq}), queue has ${queue.remaining} items`) + try { const { data, jobId } = await tesseract.recognize(imageUrl) - logger.debug(`Recognized image from URL for client ${client.id} (job ${jobId}):`, data.text) - await client.send( + logger.debug(`Image parsed (job ${jobId}) (${nextSeq}):`, data.text) + client.send( { op: ServerOperation.ParsedImage, d: { @@ -37,20 +36,20 @@ const parseImageEventHandler: EventHandler = async ( }, nextSeq, ) - } catch { - logger.error(`Failed to parse image from URL for client ${client.id}:`, imageUrl) - await client.send( - { - op: ServerOperation.ParseImageFailed, - d: null, - }, - nextSeq, - ) + } catch (e) { + if (!client.disconnected) + client.send( + { + op: ServerOperation.ParseImageFailed, + d: null, + }, + nextSeq, + ) + else logger.warn(`Client disconnected before the failed packet could be sent (${nextSeq})`) + logger.error(`Failed to parse image (${nextSeq}):`, e) } finally { queue.shift() - logger.debug( - `Finished processing image from URL for client ${client.id}, queue has ${queue.remaining}/${config.ocrConcurrentQueues} remaining items in it`, - ) + logger.debug(`Finished parsing image (${nextSeq}), queue has ${queue.remaining} items`) } } diff --git a/apis/websocket/src/events/parseText.ts b/apis/websocket/src/events/parseText.ts index 46ace12..fa3e645 100755 --- a/apis/websocket/src/events/parseText.ts +++ b/apis/websocket/src/events/parseText.ts @@ -1,7 +1,5 @@ import { type ClientOperation, ServerOperation } from '@revanced/bot-shared' -import { inspect as inspectObject } from 'util' - import type { EventHandler } from '.' const parseTextEventHandler: EventHandler = async (packet, { wit, logger }) => { @@ -19,7 +17,7 @@ const parseTextEventHandler: EventHandler = async (pa const { intents } = await wit.message(actualText) const intentsWithoutIds = intents.map(({ id, ...rest }) => rest) - await client.send( + client.send( { op: ServerOperation.ParsedText, d: { @@ -29,16 +27,16 @@ const parseTextEventHandler: EventHandler = async (pa nextSeq, ) } catch (e) { - await client.send( - { - op: ServerOperation.ParseTextFailed, - d: null, - }, - nextSeq, - ) - - if (e instanceof Error) logger.error(e.stack ?? e.message) - else logger.error(inspectObject(e)) + if (!client.disconnected) + client.send( + { + op: ServerOperation.ParseTextFailed, + d: null, + }, + nextSeq, + ) + else logger.warn(`Client disconnected before the failed packet could be sent (${nextSeq})`) + logger.error(`Failed to parse text (${nextSeq}):`, e) } } diff --git a/apis/websocket/src/events/trainMessage.ts b/apis/websocket/src/events/trainMessage.ts index b028660..66698ed 100644 --- a/apis/websocket/src/events/trainMessage.ts +++ b/apis/websocket/src/events/trainMessage.ts @@ -1,7 +1,5 @@ import { type ClientOperation, ServerOperation } from '@revanced/bot-shared' -import { inspect as inspectObject } from 'util' - import type { EventHandler } from '.' const trainMessageEventHandler: EventHandler = async (packet, { wit, logger }) => { @@ -13,11 +11,11 @@ const trainMessageEventHandler: EventHandler = asy const nextSeq = client.currentSequence++ const actualText = text.slice(0, 279) - logger.debug(`Client ${client.id} requested to train label ${label} with:`, actualText) + logger.debug(`${client.id} requested to train label ${label} (${nextSeq}) with:`, actualText) try { await wit.train(actualText, label) - await client.send( + client.send( { op: ServerOperation.TrainedMessage, d: null, @@ -25,18 +23,18 @@ const trainMessageEventHandler: EventHandler = asy nextSeq, ) - logger.debug(`Trained label ${label} with:`, actualText) + logger.debug(`Trained label (${nextSeq})`) } catch (e) { - await client.send( - { - op: ServerOperation.TrainMessageFailed, - d: null, - }, - nextSeq, - ) - - if (e instanceof Error) logger.error(e.stack ?? e.message) - else logger.error(inspectObject(e)) + if (!client.disconnected) + client.send( + { + op: ServerOperation.TrainMessageFailed, + d: null, + }, + nextSeq, + ) + else logger.warn(`Client ${client.id} disconnected before the failed packet could be sent`) + logger.error(`Failed to train (${nextSeq})`, e) } } From 4b2557f1f15f006015e0703516da50874a324243 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:12:28 +0700 Subject: [PATCH 078/312] fix(packages/api): improve packet awaiting and reconnection --- packages/api/package.json | 6 +- packages/api/src/classes/Client.ts | 81 +++++++++++++++------ packages/api/src/classes/ClientWebSocket.ts | 15 +++- packages/api/src/types.d.ts | 1 + packages/api/src/utils/packets.ts | 26 ------- 5 files changed, 75 insertions(+), 54 deletions(-) delete mode 100644 packages/api/src/utils/packets.ts diff --git a/packages/api/package.json b/packages/api/package.json index 2eb4e35..4801219 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -7,11 +7,8 @@ "types": "dist/index.d.ts", "scripts": { "build": "bun bundle && bun types", - "watch": "bunx conc --raw \"bun bundle:watch\" \"bun types:watch\"", "bundle": "bun build src/index.ts --outdir=dist --target=bun", - "bundle:watch": "bun run bundle --watch", "types": "tsc --declaration --emitDeclarationOnly", - "types:watch": "bun types --watch --preserveWatchOutput", "types:clean": "bun types --build --clean" }, "repository": { @@ -30,7 +27,8 @@ }, "homepage": "https://github.com/revanced/revanced-helper#readme", "dependencies": { - "@revanced/bot-shared": "workspace:*" + "@revanced/bot-shared": "workspace:*", + "ws": "^8.16.0" }, "devDependencies": { "typed-emitter": "^2.1.0" diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index 6fc2bb3..b0ccb43 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -1,5 +1,4 @@ -import { ClientOperation, ServerOperation } from '@revanced/bot-shared' -import { awaitPacket } from 'src/utils/packets' +import { ClientOperation, type Packet, ServerOperation } from '@revanced/bot-shared' import { type ClientWebSocketEvents, ClientWebSocketManager, @@ -12,6 +11,7 @@ import { export default class Client { ready = false ws: ClientWebSocketManager + #awaiter: ClientWebSocketPacketAwaiter constructor(options: ClientOptions) { this.ws = new ClientWebSocketManager(options.api.websocket) @@ -21,6 +21,8 @@ export default class Client { this.ws.on('disconnect', () => { this.ready = false }) + + this.#awaiter = new ClientWebSocketPacketAwaiter(this.ws) } /** @@ -50,13 +52,9 @@ export default class Client { // But if we add anything similar, this will cause another race condition // To fix this, we can try adding a instanced function that would return the currentSequence // and it would be updated every time a "heartbeat ack" packet is received - const expectedNextSeq = this.ws.currentSequence + 1 - const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => - awaitPacket(this.ws, op, expectedNextSeq, timeout) - return Promise.race([ - awaitPkt(ServerOperation.ParsedText), - awaitPkt(ServerOperation.ParseTextFailed, this.ws.timeout + 5000), + this.#awaiter.await(ServerOperation.ParsedText, this.ws.currentSequence), + this.#awaiter.await(ServerOperation.ParseTextFailed, this.ws.timeout + 5000), ]) .then(pkt => { if (pkt.op === ServerOperation.ParsedText) return pkt.d @@ -82,14 +80,11 @@ export default class Client { }, }) - // See line 48 - const expectedNextSeq = this.ws.currentSequence + 1 - const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => - awaitPacket(this.ws, op, expectedNextSeq, timeout) + // See line 50 return Promise.race([ - awaitPkt(ServerOperation.ParsedImage), - awaitPkt(ServerOperation.ParseImageFailed, this.ws.timeout + 5000), + this.#awaiter.await(ServerOperation.ParsedImage, this.ws.currentSequence), + this.#awaiter.await(ServerOperation.ParseImageFailed, this.ws.timeout + 5000), ]) .then(pkt => { if (pkt.op === ServerOperation.ParsedImage) return pkt.d @@ -111,17 +106,13 @@ export default class Client { }, }) - // See line 48 - const expectedNextSeq = this.ws.currentSequence + 1 - const awaitPkt = (op: ServerOperation, timeout = this.ws.timeout) => - awaitPacket(this.ws, op, expectedNextSeq, timeout) - + // See line 50 return Promise.race([ - awaitPkt(ServerOperation.TrainedMessage), - awaitPkt(ServerOperation.TrainMessageFailed, this.ws.timeout + 5000), + this.#awaiter.await(ServerOperation.TrainedMessage, this.ws.currentSequence), + this.#awaiter.await(ServerOperation.TrainMessageFailed, this.ws.timeout + 5000), ]) .then(pkt => { - if (pkt.op === ServerOperation.TrainedMessage) return + if (pkt.op === ServerOperation.TrainedMessage) return pkt.d throw new Error('Failed to train message, the API encountered an error') }) .catch(() => { @@ -162,6 +153,13 @@ export default class Client { return handler } + /** + * Connects the client to the API + */ + connect() { + return this.ws.connect() + } + /** * Disconnects the client from the API */ @@ -174,6 +172,45 @@ export default class Client { } } +export class ClientWebSocketPacketAwaiter { + #ws: ClientWebSocketManager + #resolvers: Map) => void> + + constructor(ws: ClientWebSocketManager) { + this.#ws = ws + this.#resolvers = new Map() + + this.#ws.on('packet', packet => { + const key = this.keyFor(packet.op, packet.s) + const resolve = this.#resolvers.get(key) + if (resolve) { + resolve(packet) + this.#resolvers.delete(key) + } + }) + } + + keyFor(op: ServerOperation, seq: number) { + return `${op}-${seq}` + } + + await( + op: TOp, + expectedSeq: number, + timeout = 10000, + ): Promise> { + return new Promise((resolve, reject) => { + const key = this.keyFor(op, expectedSeq) + this.#resolvers.set(key, resolve) + + setTimeout(() => { + this.#resolvers.delete(key) + reject('Awaiting packet timed out') + }, timeout) + }) + } +} + export type ReadiedClient = Client & { ready: true } export interface ClientOptions { diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index f5b960e..2383868 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -20,6 +20,7 @@ export class ClientWebSocketManager { readonly url: string timeout: number + connecting = false ready = false disconnected: false | DisconnectReason = false currentSequence = 0 @@ -36,8 +37,12 @@ export class ClientWebSocketManager { * Connects to the WebSocket API * @returns A promise that resolves when the client is ready */ - connect() { - return new Promise((rs, rj) => { + async connect() { + if (this.connecting) throw new Error('Cannot connect when already connecting to the server') + + this.connecting = true + + await new Promise((rs, rj) => { try { this.#socket = new WebSocket(this.url) @@ -63,10 +68,13 @@ export class ClientWebSocketManager { this.#socket.on('close', (code, reason) => { clearTimeout(timeout) this._handleDisconnect(code, reason.toString()) + throw new Error('WebSocket connection closed before ready') }) } catch (e) { rj(e) } + }).finally(() => { + this.connecting = false }) } @@ -108,6 +116,8 @@ export class ClientWebSocketManager { send(packet: Packet) { this.#throwIfDisconnected('Cannot send a packet when already disconnected from the server') + this.currentSequence++ + this.#socket.send(serializePacket(packet), err => { throw err }) @@ -164,6 +174,7 @@ export class ClientWebSocketManager { protected _handleDisconnect(reason: DisconnectReason | number, message?: string) { this.disconnected = reason in DisconnectReason ? reason : DisconnectReason.Generic + this.connecting = false this.#socket?.close(reason) this.#socket = null! diff --git a/packages/api/src/types.d.ts b/packages/api/src/types.d.ts index 778651e..f0639d5 100755 --- a/packages/api/src/types.d.ts +++ b/packages/api/src/types.d.ts @@ -1 +1,2 @@ type RequiredProperty = { [P in keyof T]: Required> } +type IfTrueElseNever = T extends true ? U : never diff --git a/packages/api/src/utils/packets.ts b/packages/api/src/utils/packets.ts deleted file mode 100644 index 70e14de..0000000 --- a/packages/api/src/utils/packets.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Packet, ServerOperation } from '@revanced/bot-shared' -import type { ClientWebSocketManager } from 'src/classes' - -export function awaitPacket( - ws: ClientWebSocketManager, - op: TOp, - expectedSeq: number, - timeout = 10000, -): Promise> { - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - ws.off('packet', handler) - reject('Awaiting packet timed out') - }, timeout) - - function handler(packet: Packet) { - if (packet.op === op && packet.s === expectedSeq) { - clearTimeout(timer) - ws.off('packet', handler) - resolve(packet as Packet) - } - } - - ws.on('packet', handler) - }) -} From 5d9b4d31c660e679fbd2d5ff2566b1bcbf62345e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:17:29 +0700 Subject: [PATCH 079/312] chore: update deps --- bun.lockb | Bin 83488 -> 83212 bytes package.json | 10 +++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bun.lockb b/bun.lockb index be6dc631bf0434b0382b0f8bc2091176740c9bd4..934c38ec1e6dddaac53ed2fe60b23600b7f85a75 100755 GIT binary patch delta 12264 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 e`deqlVD1j6*aTO`$@w=`rx&;|a!f8 Date: Tue, 2 Apr 2024 19:29:29 +0700 Subject: [PATCH 080/312] fix(bots/discord): remove useless feature --- bots/discord/src/events/discord/guildCreate.ts | 7 ------- bots/discord/src/utils/discord/security.ts | 7 ------- 2 files changed, 14 deletions(-) delete mode 100644 bots/discord/src/events/discord/guildCreate.ts delete mode 100644 bots/discord/src/utils/discord/security.ts diff --git a/bots/discord/src/events/discord/guildCreate.ts b/bots/discord/src/events/discord/guildCreate.ts deleted file mode 100644 index 5d04774..0000000 --- a/bots/discord/src/events/discord/guildCreate.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { on } from '$utils/discord/events' -import { leaveDisallowedGuild } from '$utils/discord/security' - -on('guildCreate', async ({ config }, guild) => { - if (config.allowedGuilds.includes(guild.id)) return - await leaveDisallowedGuild(guild) -}) diff --git a/bots/discord/src/utils/discord/security.ts b/bots/discord/src/utils/discord/security.ts deleted file mode 100644 index ea0e3d0..0000000 --- a/bots/discord/src/utils/discord/security.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Guild } from 'discord.js' -import { logger } from '../../context' - -export const leaveDisallowedGuild = (guild: Guild) => { - logger.warn(`Server ${guild.name} (${guild.id}) is not allowed to use this bot.`) - return guild.leave().then(() => logger.debug(`Left guild ${guild.name} (${guild.id})`)) -} From da21e1a6f76deaeb477203b04263bd170863825b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:30:57 +0700 Subject: [PATCH 081/312] feat(bots/discord/utils/fs): use `recursive` option for listing files --- bots/discord/src/utils/fs.ts | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/bots/discord/src/utils/fs.ts b/bots/discord/src/utils/fs.ts index 07ea43c..b5fc051 100644 --- a/bots/discord/src/utils/fs.ts +++ b/bots/discord/src/utils/fs.ts @@ -1,21 +1,11 @@ -import { readdirSync, statSync } from 'fs' +import { readdirSync } from 'fs' import { dirname, join } from 'path' import { fileURLToPath } from 'bun' -export const listAllFilesRecursive = (dir: string): string[] => { - const files = readdirSync(dir) - const result: string[] = [] - for (const file of files) { - const filePath = join(dir, file) - const fileStat = statSync(filePath) - if (fileStat.isDirectory()) { - result.push(...listAllFilesRecursive(filePath)) - } else { - result.push(filePath) - } - } - return result -} +export const listAllFilesRecursive = (dir: string): string[] => + readdirSync(dir, { recursive: true, withFileTypes: true }) + .filter(x => x.isFile()) + .map(x => join(dir, x.name)) export const pathJoinCurrentDir = (importMetaUrl: string, ...objects: [string, ...string[]]) => join(dirname(fileURLToPath(importMetaUrl)), ...objects) From f3e4408aa28fb6a9d21365af8c1bea3d07b481de Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:31:39 +0700 Subject: [PATCH 082/312] fix(bots/discord): check token before connecting to bot api --- bots/discord/src/index.ts | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/bots/discord/src/index.ts b/bots/discord/src/index.ts index ea4a601..a5a2026 100644 --- a/bots/discord/src/index.ts +++ b/bots/discord/src/index.ts @@ -3,19 +3,6 @@ import { getMissingEnvironmentVariables } from '@revanced/bot-shared' import { api, discord, logger } from './context' import { listAllFilesRecursive, pathJoinCurrentDir } from './utils/fs' -for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'api'))) { - await import(event) -} - -const { client: apiClient } = api -await apiClient.ws.connect() - -for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'discord'))) { - await import(event) -} - -const { client: discordClient } = discord - // Check if token exists const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN']) if (missingEnvs.length) { @@ -23,4 +10,14 @@ if (missingEnvs.length) { process.exit(1) } -await discordClient.login() +for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'api'))) { + await import(event) +} + +await api.client.connect() + +for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'discord'))) { + await import(event) +} + +await discord.client.login() From 2d794ede7d7a208bd3616c45e8e6d2a2cd83e9ed Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:32:49 +0700 Subject: [PATCH 083/312] feat(bots/discord/utils/discord/embeds): expose `applyCommonEmbedStyles` fn --- bots/discord/src/utils/discord/embeds.ts | 27 +++++++++++++----------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index 1149943..4ca387d 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -3,27 +3,24 @@ import { EmbedBuilder } from 'discord.js' import type { ConfigMessageScanResponseMessage } from '../../../config.example' export const createErrorEmbed = (title: string, description?: string) => - applyCommonStyles( + applyCommonEmbedStyles( new EmbedBuilder() .setTitle(title) .setDescription(description ?? null) .setAuthor({ name: 'Error' }) .setColor('Red'), - false, ) export const createStackTraceEmbed = (stack: unknown) => // biome-ignore lint/style/useTemplate: shut - createErrorEmbed('An exception was thrown', '```js' + stack + '```') + createErrorEmbed('An exception was thrown', '```js\n' + stack + '```') export const createSuccessEmbed = (title: string, description?: string) => - applyCommonStyles( + applyCommonEmbedStyles( new EmbedBuilder() .setTitle(title) .setDescription(description ?? null) - .setAuthor({ name: 'Success' }) .setColor('Green'), - false, ) export const createMessageScanResponseEmbed = ( @@ -40,14 +37,20 @@ export const createMessageScanResponseEmbed = ( iconURL: ReVancedLogoURL, }) - return applyCommonStyles(embed) + return applyCommonEmbedStyles(embed, true, true, true) } -const applyCommonStyles = (embed: EmbedBuilder, setColor = true, setThumbnail = true) => { - embed.setFooter({ - text: 'ReVanced', - iconURL: ReVancedLogoURL, - }) +export const applyCommonEmbedStyles = ( + embed: EmbedBuilder, + setThumbnail = false, + setFooter = false, + setColor = false, +) => { + if (setFooter) + embed.setFooter({ + text: 'ReVanced', + iconURL: ReVancedLogoURL, + }) if (setColor) embed.setColor(DefaultEmbedColor) if (setThumbnail) embed.setThumbnail(ReVancedLogoURL) From a9add9ea9affb42bdfcb17cf4b268feec5729854 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:33:18 +0700 Subject: [PATCH 084/312] feat(bots/discord/utils): add duration utility --- bots/discord/src/utils/duration.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 bots/discord/src/utils/duration.ts diff --git a/bots/discord/src/utils/duration.ts b/bots/discord/src/utils/duration.ts new file mode 100644 index 0000000..e2edafb --- /dev/null +++ b/bots/discord/src/utils/duration.ts @@ -0,0 +1,23 @@ +export const parseDuration = (duration: string) => { + if (!duration.length) return Number.NaN + const matches = duration.match(/(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?/)! + + const [, days, hours, minutes, seconds] = matches.map(Number) + return ( + (days || 0) * 24 * 60 * 60 * 1000 + + (hours || 0) * 60 * 60 * 1000 + + (minutes || 0) * 60 * 1000 + + (seconds || 0) * 1000 + ) +} + +export const durationToString = (duration: number) => { + const days = Math.floor(duration / (24 * 60 * 60 * 1000)) + const hours = Math.floor((duration % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000)) + const minutes = Math.floor((duration % (60 * 60 * 1000)) / (60 * 1000)) + const seconds = Math.floor((duration % (60 * 1000)) / 1000) + + return `${days ? `${days}d` : ''}${hours ? `${hours}h` : ''}${minutes ? `${minutes}m` : ''}${ + seconds ? `${seconds}s` : '' + }` +} From 7e5f6481c5a3d680afbb8c53ec8208e4d7a28d7b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:34:45 +0700 Subject: [PATCH 085/312] feat(bots/discord)!: read commit description FEATURES: - Updated documentation - Improved configuration format - Allow filter overriding for each response config (closes #29) - Improved commands directory structure - Improved slash command reload script - New commands - New command exception handling --- bots/discord/config.example.ts | 66 +++-- bots/discord/config.revanced.ts | 253 ++---------------- bots/discord/docs/1_configuration.md | 102 +------ bots/discord/docs/2_adding_autoresponses.md | 88 ++++++ .../docs/{2_running.md => 3_running.md} | 2 +- ...and_events.md => 4_commands_and_events.md} | 2 +- .../docs/{4_databases.md => 5_databases.md} | 0 bots/discord/docs/README.md | 9 +- bots/discord/scripts/reload-slash-commands.ts | 50 ++-- bots/discord/src/classes/CommandError.ts | 31 +++ .../commands/development/exception-test.ts | 45 ++++ .../src/commands/{ => development}/stop.ts | 5 +- bots/discord/src/commands/fun/coinflip.ts | 34 +++ bots/discord/src/commands/fun/reply.ts | 43 +++ bots/discord/src/commands/index.ts | 6 +- .../src/commands/moderation/slowmode.ts | 58 ++++ bots/discord/src/commands/reply.ts | 57 ---- .../interactionCreate/chat-commmand.ts | 47 ++-- .../src/events/discord/messageCreate/scan.ts | 50 ++-- .../messageReactionAdd/correct-response.ts | 48 ++-- bots/discord/src/utils/discord/messageScan.ts | 104 +++---- 21 files changed, 542 insertions(+), 558 deletions(-) create mode 100644 bots/discord/docs/2_adding_autoresponses.md rename bots/discord/docs/{2_running.md => 3_running.md} (91%) rename bots/discord/docs/{3_commands_and_events.md => 4_commands_and_events.md} (98%) rename bots/discord/docs/{4_databases.md => 5_databases.md} (100%) create mode 100644 bots/discord/src/classes/CommandError.ts create mode 100644 bots/discord/src/commands/development/exception-test.ts rename bots/discord/src/commands/{ => development}/stop.ts (89%) create mode 100644 bots/discord/src/commands/fun/coinflip.ts create mode 100644 bots/discord/src/commands/fun/reply.ts create mode 100644 bots/discord/src/commands/moderation/slowmode.ts delete mode 100644 bots/discord/src/commands/reply.ts diff --git a/bots/discord/config.example.ts b/bots/discord/config.example.ts index d6d12e9..70f9451 100644 --- a/bots/discord/config.example.ts +++ b/bots/discord/config.example.ts @@ -1,23 +1,28 @@ export default { owners: ['USER_ID_HERE'], - allowedGuilds: ['GUILD_ID_HERE'], + guilds: ['GUILD_ID_HERE'], messageScan: { - channels: ['CHANNEL_ID_HERE'], - roles: ['ROLE_ID_HERE'], - users: ['USER_ID_HERE'], - whitelist: false, + filter: { + channels: ['CHANNEL_ID_HERE'], + roles: ['ROLE_ID_HERE'], + users: ['USER_ID_HERE'], + whitelist: false, + }, humanCorrections: { falsePositiveLabel: 'false_positive', - allowUsers: ['USER_ID_HERE'], - memberRequirements: { - permissions: 8n, - roles: ['ROLE_ID_HERE'], + allow: { + members: { + permissions: 8n, + roles: ['ROLE_ID_HERE'], + }, }, }, allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], responses: [ { - triggers: [/^regexp?$/, { label: 'label', threshold: 0.85 }], + triggers: { + text: [/^regexp?$/, { label: 'label', threshold: 0.85 }], + }, response: { title: 'Embed title', description: 'Embed description', @@ -35,28 +40,31 @@ export default { api: { websocketUrl: 'ws://127.0.0.1:3000', }, -} as Config +} satisfies Config as Config export type Config = { owners: string[] - allowedGuilds: string[] - messageScan?: Partial<{ - roles: string[] - users: string[] - channels: string[] + guilds: string[] + messageScan?: { + allowedAttachmentMimeTypes: string[] + filter: { + roles?: string[] + users?: string[] + channels?: string[] + whitelist: boolean + } humanCorrections: { falsePositiveLabel: string - allowUsers?: string[] - /** - * Match mode is set to Any - */ - memberRequirements?: { - permissions?: bigint - roles?: string[] + allow?: { + users?: string[] + members?: { + permissions?: bigint + roles?: string[] + } } } responses: ConfigMessageScanResponse[] - }> & { whitelist: boolean; allowedAttachmentMimeTypes: string[] } + } logLevel: 'none' | 'error' | 'warn' | 'info' | 'log' | 'trace' | 'debug' api: { websocketUrl: string @@ -64,11 +72,11 @@ export type Config = { } export type ConfigMessageScanResponse = { - triggers: Array - /** - * Extra triggers for text done via OCR - */ - ocrTriggers?: Array + triggers: { + text?: Array + image?: Array + } + filterOverride?: NonNullable['filter'] response: ConfigMessageScanResponseMessage | null } diff --git a/bots/discord/config.revanced.ts b/bots/discord/config.revanced.ts index 5d0ad06..641ef16 100644 --- a/bots/discord/config.revanced.ts +++ b/bots/discord/config.revanced.ts @@ -3,249 +3,32 @@ import type { Config } from './config.example' export default { owners: ['629368283354628116', '737323631117598811', '282584705218510848'], - allowedGuilds: ['952946952348270622'], + guilds: ['952946952348270622'], messageScan: { - // Team, Mod, Immunity - roles: ['952987191401926697', '955220417969262612', '1027874293192863765'], - users: [], - // Team, Development - channels: ['952987428786941952', '953965039105232906'], - whitelist: false, + filter: { + // Team, Mod, Immunity + roles: ['952987191401926697', '955220417969262612', '1027874293192863765'], + users: [], + // Team, Development + channels: ['952987428786941952', '953965039105232906'], + whitelist: false, + }, humanCorrections: { falsePositiveLabel: 'false_positive', - memberRequirements: { - // Team, Supporter - roles: ['952987191401926697', '1019903194941362198'], - permissions: PermissionFlagsBits.ManageMessages, + allow: { + members: { + // Team, Supporter + roles: ['952987191401926697', '1019903194941362198'], + permissions: PermissionFlagsBits.ManageMessages, + }, }, }, allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], responses: [ { - triggers: [ - { - label: 'suggested_version', - threshold: 0.85, - }, - ], - reply: { - title: 'Which version is suggested ❓', - fields: [ - { - name: '🔸 Regarding your question', - value: 'The suggested version can be seen in ReVanced Manager in the app selector screen. Refer to the ReVanced Manager documentation in <#953993848374325269> `3`.', - }, - ], + triggers: { + text: [{ label: 'false_positive', threshold: 0 }], }, - }, - { - triggers: [ - /(re)?v[ae]nced? crash/i, - { - label: 'revanced_crash', - threshold: 0.85, - }, - ], - response: { - title: 'Why am I experiencing crashes ❓', - fields: [ - { - name: '🔸 Regarding your question', - value: 'You may have patched an unsuggested version of the app, changed the selection of patches or used a faulty APK. Refer to the documentation in <#953993848374325269> `3` in order to correctly patch your app correctly using ReVanced CLI or ReVanced Manager.', - }, - ], - }, - }, - { - triggers: [ - /manager abort(ed)?/i, - { - label: 'rvmanager_abort', - threshold: 0.85, - }, - ], - response: { - title: 'Why is ReVanced Manager aborting ❓', - fields: [ - { - name: '🔸 Regarding your question', - value: 'Your device may be unsupported by ReVanced Manager. Refer to the documentation in <#953993848374325269> `3` in order to use ReVanced CLI or check if your device is supported by ReVanced Manager.', - }, - ], - }, - }, - { - triggers: [ - /(how|where|what).{0,15}(download|install|get) (re)?v[ae]nced?/i, - { - label: 'revanced_download', - threshold: 0.85, - }, - ], - response: { - title: 'Where or how to get ReVanced ❓', - description: - 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'You can use ReVanced CLI or ReVanced Manager to get ReVanced. Refer to the documentation in <#953993848374325269> `3`.', - }, - ], - }, - }, - { - triggers: [ - /(re)?v[ae]nced?( on)?( android)? tv/i, - { - label: 'androidtv_support', - threshold: 0.85, - }, - ], - response: { - title: 'Does ReVanced support YouTube for Android TVs ❓', - description: - 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'Please refer to <#953993848374325269> `5`. Alternative, there is [SmartTubeNext](https://github.com/yuliskov/SmartTubeNext#smarttube).', - }, - ], - }, - }, - { - triggers: [ - { - label: 'revanced_nodownloader', - threshold: 0.85, - }, - ], - response: { - title: 'How do I download videos on YouTube ❓', - description: - 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'In order to be able to download videos on YouTube without YouTube Premium, you can patch YouTube with the `External downloads` patch. You can configure the downloader in the settings of the patched app. NewPipe is the default downloader. Please refer to <#953993848374325269> `24`.', - }, - ], - }, - }, - { - triggers: [ - { - label: 'revanced_casting', - threshold: 0.85, - }, - ], - response: { - title: 'Why can I not cast videos on YouTube ❓', - fields: [ - { - name: '🔸 Regarding your question', - value: 'You may have patched YouTube with the `GmsCore support` patch which makes YouTube use Vanced MicroG instead of Google Services, but Vanced MicroG does not reliably support casting. In order to be able to cast videos on the patched app, you should not patch the app with the `GmsCore support` patch, but then you are forced to mount the patched app with root permissions, because you will not be able to install the app in normal circumstances and Google Services will reject the patched app.', - }, - ], - }, - }, - { - triggers: [ - /(where|what|how).{0,15}(get|install|download) ((vanced )?microg|gms(core)?)/i, - { - label: 'microg_download', - threshold: 0.85, - }, - ], - response: { - title: 'Where can I get GmsCore ❓', - description: - 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'If you patched YouTube using the `GmsCore support` patch, the patched app will redirect you to the download link of GmsCore if you open it. In case it does not, please refer to <#953993848374325269> `17`.', - }, - ], - }, - }, - { - triggers: [ - { - label: 'microg_nointernet', - threshold: 0.85, - }, - ], - ocrTriggers: [/is not installed/], - response: { - title: 'Why does YouTube say, I am offline ❓', - description: - 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'Please refer to <#953993848374325269> `15`.', - }, - ], - }, - }, - { - triggers: [ - /revanced\.[^a][^p]?[^p]?/i, - { - label: 'rvdownload_unofficial', - threshold: 0.85, - }, - ], - response: { - title: 'What are the official links of ReVanced ❓', - description: 'A list of official links can be found in <#954066838856273960>.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'ReVanced is always available at [revanced.app](https://revanced.app).', - }, - ], - }, - }, - { - triggers: [ - /(re)?v[ae]nced?( videos?)? ((not )?loading|buffering)/i, - { - label: 'yt_buffering', - threshold: 0.85, - }, - ], - response: { - title: 'Why do videos fail to play❓', - description: - 'You might have asked a question that has been answered in the <#953993848374325269> channel already. Make sure to read it as it will answer a lot of your questions, guaranteed.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'Please refer to <#953993848374325269> `32`.', - }, - ], - }, - }, - { - triggers: [], - ocrTriggers: [/You're offline|Please check your/], - response: { - title: 'Why does YouTube say, I am offline ❓', - description: - 'You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.', - fields: [ - { - name: '🔸 Regarding your question', - value: 'Please refer to <#953993848374325269> `15`.', - }, - ], - }, - }, - { - triggers: [{ label: 'false_positive', threshold: 0 }], response: null, }, ], @@ -254,4 +37,4 @@ export default { api: { websocketUrl: 'ws://127.0.0.1:3000', }, -} as Config +} satisfies Config as Config diff --git a/bots/discord/docs/1_configuration.md b/bots/discord/docs/1_configuration.md index 86474f4..35408fc 100644 --- a/bots/discord/docs/1_configuration.md +++ b/bots/discord/docs/1_configuration.md @@ -1,59 +1,16 @@ # ⚙️ Configuration -This is the default configuration (provided in [config.ts](../config.ts)): - -```ts -export default { - owners: ["USER_ID_HERE"], - allowedGuilds: ["GUILD_ID_HERE"], - messageScan: { - channels: ["CHANNEL_ID_HERE"], - roles: ["ROLE_ID_HERE"], - users: ["USER_ID_HERE"], - whitelist: false, - humanCorrections: { - falsePositiveLabel: "false_positive", - allowUsers: ["USER_ID_HERE"], - memberRequirements: { - permissions: 8n, - roles: ["ROLE_ID_HERE"], - }, - }, - allowedAttachmentMimeTypes: ["image/jpeg", "image/png", "image/webp"], - responses: [ - { - triggers: [/^regexp?$/, { label: "label", threshold: 0.85 }], - response: { - title: "Embed title", - description: "Embed description", - fields: [ - { - name: "Field name", - value: "Field value", - }, - ], - }, - }, - ], - }, - logLevel: "log", - api: { - websocketUrl: "ws://127.0.0.1:3000", - }, -} as Config; -``` - -This may look very overwhelming but configurating it is pretty easy. +You will need to copy `config.example.ts` to `config.ts` to be able to start the bot, as it is the default configuration. --- ### `config.owners` -User IDs of the owners of the bot. They'll be able to execute specific commands that others can't and take control of the bot. +User IDs of the owners of the bot. Only add owners when needed. -### `config.allowedGuilds` +### `config.guilds` -Servers the bot is allowed to be and register commands in. The bot will leave servers that are not in this list automatically once detected. +Servers the bot is allowed to be and register commands in. ### `config.logLevel` @@ -71,57 +28,14 @@ The possible levels (sorted by their importance descendingly) are: ### `config.api.websocketUrl` -The WebSocket URL to connect to (including port). +The WebSocket URL to connect to (including port). Soon auto-discovery will be implemented. ### `config.messageScan` -Message scan configuration. - -##### `config.messageScan.roles` & `config.messageScan.users` & `config.messageScan.channels` - -Roles, users, and channels which will be affected by the blacklist/whitelist rule. - -##### `config.messageScan.whitelist` - -Whether to use whitelist (`true`) or blacklist (`false`) mode. - -- Blacklist mode **will refuse** to scan messages of any roles or users who **are** in the list above. -- Whitelist mode **will refuse** to scan messages of any roles or users who **aren't** in the list above. - -##### `config.messageScan.responses` - -An array containing response configurations. A response can be triggered by multiple ways[^1], which can be specified in the `response.triggers` field. -The `response` field contains the embed data that the bot should send. If it is set to `null`, the bot will not send a response or delete the current response if editing. - -> [!NOTE] -> If you want only OCR results to match a certain regular expression, you can put them into the `response.ocrTriggers` array. - -```ts -{ - triggers: [ - /cool regex/i, - { - label: 'some_label', - threshold: 0.8, - }, - ], - response: { - title: 'Embed title', - description: 'Embed description', - fields: [ - { - name: 'Field name', - value: 'Field value', - }, - ], - } -} -``` - -[^1]: Possible triggers are regular expressions or [label configurations](../config.example.ts#68). +[Please see the next page.](./2_adding_autoresponses.md) ## ⏭️ What's next -The next page will tell you how to run and bundle the bot. +The next page will tell you how to configure auto-responses. -Continue: [🏃🏻‍♂️ Running the bot](./2_running.md) +Continue: [🗣️ Adding auto-responses](./2_adding_autoresponses.md) diff --git a/bots/discord/docs/2_adding_autoresponses.md b/bots/discord/docs/2_adding_autoresponses.md new file mode 100644 index 0000000..a958941 --- /dev/null +++ b/bots/discord/docs/2_adding_autoresponses.md @@ -0,0 +1,88 @@ +# 🗣️ Adding auto-responses + +This is referring to `config.messageScan`. + +## 🧱 Filters + +You can add filters to blacklist or whitelist a user from message scanning preventing auto-responses. + +### `filter.roles` & `filter.users` & `filter.channels` + +Roles, users, and channels which will be affected by the blacklist/whitelist rule. + +### `filter.whitelist` + +Whether to use whitelist (`true`) or blacklist (`false`) mode. + +- Blacklist mode **will refuse** to scan messages that match any of the filters above +- Whitelist mode **will refuse** to scan messages that match any of the filters above. + +## 💬 Responses + +The `responses` field is array containing response configurations. + +### Adding a message response + +The `responses[n].response` field contains the embed data that the bot should send. If it is set to `null`, the bot will not send a response or delete the current response if editing (useful for catching false positives). + +```ts +response: { + title: 'Embed title', + description: 'Embed description', + fields: [ + { + name: 'Field name', + value: 'Field value', + }, + ], +} + +// or if it's a false positive label (for example) +response: null +``` + +### Adding triggers + +A response can be triggered by multiple ways[^1], which can be specified in the `response[n].triggers` object. + +You can add a trigger for text messages which can either be a regular expression, or a label match config (NLP) into the `responses.triggers.text` array. +However, if you want **only OCR results** to match a certain regular expression, you can put them into the `response.triggers.image` array instead. + +```ts +triggers: { + // Text messages + text: [ + /cool regex/i, + { + label: 'some_label', + threshold: 0.8, + }, + ], + // Text messages with image attachments (OCR results) + image: [ + /image regex/i + ] +}, +``` + +### Override a filter + +You can also override the filter of the current response by supplying the [filter object](#configmessagescanfilter) into the `response.filterOverride` field. + +```ts +filterOverride: { + // will only respond to members with this role + roles: ['ROLE_ID'], + // or in this channel + channels: ['CHANNEL_ID'], + whitelist: true, +}, +``` + +[^1]: Possible triggers are regular expressions or [label configurations](../config.example.ts#68). + +## ⏭️ What's next + +The next page will tell you how to run and bundle the bot. + +Continue: [🏃🏻‍♂️ Running the bot](./3_running.md) diff --git a/bots/discord/docs/2_running.md b/bots/discord/docs/3_running.md similarity index 91% rename from bots/discord/docs/2_running.md rename to bots/discord/docs/3_running.md index 835e8bd..f82e2ad 100644 --- a/bots/discord/docs/2_running.md +++ b/bots/discord/docs/3_running.md @@ -21,4 +21,4 @@ As a workaround, you can zip up the whole project, unzip, and run it in developm The next page will tell you how to add commands and listen to events to the bot. -Continue: [✨ Adding commands and listening to events](./3_commands_and_events.md) +Continue: [✨ Adding commands and listening to events](./4_commands_and_events.md) diff --git a/bots/discord/docs/3_commands_and_events.md b/bots/discord/docs/4_commands_and_events.md similarity index 98% rename from bots/discord/docs/3_commands_and_events.md rename to bots/discord/docs/4_commands_and_events.md index da9ac83..fd1df88 100644 --- a/bots/discord/docs/3_commands_and_events.md +++ b/bots/discord/docs/4_commands_and_events.md @@ -107,4 +107,4 @@ API events are stored in [`src/events/api`](../src/events/api), and Discord even The next page will tell you how to create and interact with a database. -Continue: [🫙 Storing data](./4_databases.md) +Continue: [🫙 Storing data](./5_databases.md) diff --git a/bots/discord/docs/4_databases.md b/bots/discord/docs/5_databases.md similarity index 100% rename from bots/discord/docs/4_databases.md rename to bots/discord/docs/5_databases.md diff --git a/bots/discord/docs/README.md b/bots/discord/docs/README.md index 6386ce1..2405c34 100644 --- a/bots/discord/docs/README.md +++ b/bots/discord/docs/README.md @@ -4,11 +4,12 @@ This documentation explains how to start developing, and how to configure the bo ## 📖 Table of contents -0. [🏗️ Set up the development environment (if you haven't already)](../../../docs/0_development_environment.md) +0. [🏗️ Set up the development environment (if you haven't already)](../../../docs/0_development_environment.md) 1. [⚙️ Configuration](./1_configuration.md) -2. [🏃🏻‍♂️ Running the server](./2_running.md) -3. [🗣️ Command and events](./3_commands_and_events.md) -4. [🫙 Storing data](./4_databases.md) +2. [🗣️ Adding auto-responses](./2_adding_autoresponses.md) +3. [🏃🏻‍♂️ Running the bot](./3_running.md) +4. [✨ Command and events](./4_commands_and_events.md) +5. [🫙 Storing data](./5_databases.md) ## ⏭️ Start here diff --git a/bots/discord/scripts/reload-slash-commands.ts b/bots/discord/scripts/reload-slash-commands.ts index ec53ea6..b24e367 100644 --- a/bots/discord/scripts/reload-slash-commands.ts +++ b/bots/discord/scripts/reload-slash-commands.ts @@ -1,42 +1,50 @@ import { REST } from '@discordjs/rest' import { getMissingEnvironmentVariables } from '@revanced/bot-shared' import { Routes } from 'discord-api-types/v9' -import type { RESTGetCurrentApplicationResult, RESTPutAPIApplicationCommandsResult } from 'discord.js' -import { config, discord } from '../src/context' +import type { + RESTGetCurrentApplicationResult, + RESTPutAPIApplicationCommandsResult, + RESTPutAPIApplicationGuildCommandsResult, +} from 'discord.js' +import { config, discord, logger } from '../src/context' // Check if token exists const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN']) if (missingEnvs.length) { - for (const env of missingEnvs) console.error(`${env} is not defined in environment variables`) + for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`) process.exit(1) } -const commands = Object.values(discord.commands) -const globalCommands = commands.filter(x => x.global && x.data.dm_permission) -const guildCommands = commands.filter(x => !x.global) +// Group commands by global and guild + +const { global: globalCommands = [], guild: guildCommands = [] } = Object.groupBy(Object.values(discord.commands), c => + c.global ? 'global' : 'guild', +) + +// Set commands const rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!) try { const app = (await rest.get(Routes.currentApplication())) as RESTGetCurrentApplicationResult + const data = (await rest.put(Routes.applicationCommands(app.id), { + body: globalCommands.map(({ data }) => { + if (!data.dm_permission) data.dm_permission = true + logger.warn(`Command ${data.name} has no dm_permission set, forcing to true as it is a global command`) + return data + }), + })) as RESTPutAPIApplicationCommandsResult - if (typeof app === 'object' && app && 'id' in app && typeof app.id === 'string') { - const data = (await rest.put(Routes.applicationCommands(app.id), { - body: globalCommands.map(x => x.data), - })) as RESTPutAPIApplicationCommandsResult + logger.info(`Reloaded ${data.length} global commands`) - console.info(`Reloaded ${data.length} global commands.`) + for (const guildId of config.guilds) { + const data = (await rest.put(Routes.applicationGuildCommands(app.id, guildId), { + body: guildCommands.map(x => x.data), + })) as RESTPutAPIApplicationGuildCommandsResult - const guildCommandsMapped = guildCommands.map(x => x.data) - for (const guildId of config.allowedGuilds) { - const data = (await rest.put(Routes.applicationGuildCommands(app.id, guildId), { - body: guildCommandsMapped, - })) as RESTPutAPIApplicationCommandsResult - - console.info(`Reloaded ${data.length} guild commands for guild ${guildId}.`) - } + logger.info(`Reloaded ${data.length} guild commands for guild ${guildId}`) } -} catch (error) { - console.error(error) +} catch (e) { + logger.fatal(e) } diff --git a/bots/discord/src/classes/CommandError.ts b/bots/discord/src/classes/CommandError.ts new file mode 100644 index 0000000..48ee202 --- /dev/null +++ b/bots/discord/src/classes/CommandError.ts @@ -0,0 +1,31 @@ +import { createErrorEmbed } from '$/utils/discord/embeds' + +export default class CommandError extends Error { + type: CommandErrorType + + constructor(type: CommandErrorType, message?: string) { + super(message) + this.name = 'CommandError' + this.type = type + } + + toEmbed() { + return createErrorEmbed(ErrorTitleMap[this.type], this.message ?? '') + } +} + +export enum CommandErrorType { + Generic, + MissingArgument, + InvalidUser, + InvalidChannel, + InvalidDuration, +} + +const ErrorTitleMap: Record = { + [CommandErrorType.Generic]: 'An exception was thrown', + [CommandErrorType.MissingArgument]: 'Missing argument', + [CommandErrorType.InvalidUser]: 'Invalid user', + [CommandErrorType.InvalidChannel]: 'Invalid channel', + [CommandErrorType.InvalidDuration]: 'Invalid duration', +} diff --git a/bots/discord/src/commands/development/exception-test.ts b/bots/discord/src/commands/development/exception-test.ts new file mode 100644 index 0000000..038b17a --- /dev/null +++ b/bots/discord/src/commands/development/exception-test.ts @@ -0,0 +1,45 @@ +import { SlashCommandBuilder } from 'discord.js' + +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import type { Command } from '..' + +export default { + data: new SlashCommandBuilder() + .setName('exception-test') + .setDescription('throw up pls') + .addStringOption(option => + option + .setName('type') + .setDescription('The type of exception to throw') + .addChoices({ + name: 'generic error', + value: 'Generic', + }) + .addChoices({ + name: 'invalid argument', + value: 'InvalidArgument', + }) + .addChoices({ + name: 'invalid channel', + value: 'InvalidChannel', + }) + .addChoices({ + name: 'invalid user', + value: 'InvalidUser', + }) + .addChoices({ + name: 'invalid duration', + value: 'InvalidDuration', + }) + .setRequired(true), + ) + .setDMPermission(true) + .toJSON(), + + global: true, + + async execute(_, interaction) { + const type = interaction.options.getString('type', true) + throw new CommandError(CommandErrorType[type as keyof typeof CommandErrorType], '[INTENTIONAL BOT DESIGN]') + }, +} satisfies Command diff --git a/bots/discord/src/commands/stop.ts b/bots/discord/src/commands/development/stop.ts similarity index 89% rename from bots/discord/src/commands/stop.ts rename to bots/discord/src/commands/development/stop.ts index 14199a6..1627d54 100644 --- a/bots/discord/src/commands/stop.ts +++ b/bots/discord/src/commands/development/stop.ts @@ -1,8 +1,9 @@ import { SlashCommandBuilder } from 'discord.js' -import type { Command } from '.' + +import type { Command } from '..' export default { - data: new SlashCommandBuilder().setName('stop').setDescription('Stops the bot').toJSON(), + data: new SlashCommandBuilder().setName('stop').setDescription('Stops the bot').setDMPermission(true).toJSON(), ownerOnly: true, global: true, diff --git a/bots/discord/src/commands/fun/coinflip.ts b/bots/discord/src/commands/fun/coinflip.ts new file mode 100644 index 0000000..5b724f5 --- /dev/null +++ b/bots/discord/src/commands/fun/coinflip.ts @@ -0,0 +1,34 @@ +import { applyCommonEmbedStyles } from '$/utils/discord/embeds' + +import { EmbedBuilder, SlashCommandBuilder } from 'discord.js' + +import type { Command } from '..' + +export default { + data: new SlashCommandBuilder().setName('coinflip').setDescription('Do a coinflip!').setDMPermission(true).toJSON(), + global: true, + + async execute(_, interaction) { + const result = Math.random() < 0.5 ? ('heads' as const) : ('tails' as const) + const embed = applyCommonEmbedStyles(new EmbedBuilder().setTitle('Flipping... 🪙'), true, false, false) + + await interaction.reply({ + embeds: [embed.toJSON()], + }) + + embed.setTitle(`The coin landed on... **${result.toUpperCase()}**! ${EmojiMap[result]}`) + + setTimeout( + () => + interaction.editReply({ + embeds: [embed.toJSON()], + }), + 1500, + ) + }, +} satisfies Command + +const EmojiMap: Record<'heads' | 'tails', string> = { + heads: '🤯', + tails: '🐈', +} diff --git a/bots/discord/src/commands/fun/reply.ts b/bots/discord/src/commands/fun/reply.ts new file mode 100644 index 0000000..a68c849 --- /dev/null +++ b/bots/discord/src/commands/fun/reply.ts @@ -0,0 +1,43 @@ +import { SlashCommandBuilder, type TextBasedChannel } from 'discord.js' + +import type { Command } from '..' + +export default { + data: new SlashCommandBuilder() + .setName('reply') + .setDescription('Send a message as the bot') + .addStringOption(option => option.setName('message').setDescription('The message to send').setRequired(true)) + .addStringOption(option => + option + .setName('reference') + .setDescription('The message ID to reply to (use `latest` to reply to the latest message)') + .setRequired(false), + ) + .toJSON(), + + memberRequirements: { + roles: ['955220417969262612', '973886585294704640'], + }, + + global: false, + + async execute({ logger }, interaction) { + const msg = interaction.options.getString('message', true) + const ref = interaction.options.getString('reference') + + const channel = (await interaction.guild!.channels.fetch(interaction.channelId)) as TextBasedChannel + const refMsg = ref?.startsWith('latest') ? (await channel.messages.fetch({ limit: 1 })).at(0)?.id : ref + + await channel.send({ + content: msg, + reply: refMsg ? { messageReference: refMsg, failIfNotExists: true } : undefined, + }) + + logger.info(`User ${interaction.user.tag} made the bot say: ${msg}`) + + await interaction.reply({ + content: 'OK!', + ephemeral: true, + }) + }, +} satisfies Command diff --git a/bots/discord/src/commands/index.ts b/bots/discord/src/commands/index.ts index d9c2f87..2f94f85 100644 --- a/bots/discord/src/commands/index.ts +++ b/bots/discord/src/commands/index.ts @@ -22,7 +22,6 @@ export type Command = { mode?: 'all' | 'any' /** * The permissions required to use this command (in BitFields). - * For safety reasons, this is set to `-1n` and only bot owners can use this command unless explicitly specified. * * - **0n** means that everyone can use this command. * - **-1n** means that only bot owners can use this command. @@ -37,13 +36,12 @@ export type Command = { } /** * Whether this command can only be used by bot owners. - * For safety reasons, this is set to `true` and only bot owners can use this command unless explicitly specified. - * @default true + * @default false */ ownerOnly?: boolean /** * Whether to register this command as a global slash command. - * For safety reasons, this is set to `false` and commands will be registered in allowed guilds only. + * This is set to `false` and commands will be registered in allowed guilds only by default. * @default false */ global?: boolean diff --git a/bots/discord/src/commands/moderation/slowmode.ts b/bots/discord/src/commands/moderation/slowmode.ts new file mode 100644 index 0000000..fc3adfe --- /dev/null +++ b/bots/discord/src/commands/moderation/slowmode.ts @@ -0,0 +1,58 @@ +import { createSuccessEmbed } from '$/utils/discord/embeds' +import { durationToString, parseDuration } from '$/utils/duration' + +import { SlashCommandBuilder } from 'discord.js' + +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import type { Command } from '..' + +export default { + data: new SlashCommandBuilder() + .setName('slowmode') + .setDescription('Set a slowmode for the current channel') + .addStringOption(option => option.setName('duration').setDescription('The duration to set').setRequired(true)) + .addStringOption(option => + option + .setName('channel') + .setDescription('The channel to set the slowmode on (defaults to current channel)') + .setRequired(false), + ) + .toJSON(), + + memberRequirements: { + roles: ['955220417969262612', '973886585294704640'], + }, + + global: false, + + async execute({ logger }, interaction) { + const durationStr = interaction.options.getString('duration', true) + const id = interaction.options.getChannel('channel')?.id ?? interaction.channelId + + const duration = parseDuration(durationStr) + const channel = await interaction.guild!.channels.fetch(id) + + if (!channel?.isTextBased()) + throw new CommandError( + CommandErrorType.InvalidChannel, + 'The supplied channel is not a text channel or does not exist.', + ) + + if (Number.isNaN(duration)) throw new CommandError(CommandErrorType.InvalidDuration, 'Invalid duration.') + if (duration < 0 || duration > 36e4) + throw new CommandError( + CommandErrorType.InvalidDuration, + 'Duration out of range, must be between 0s and 6h.', + ) + + logger.info(`Setting slowmode to ${duration}ms on ${channel.id}`) + + await channel.setRateLimitPerUser( + duration / 1000, + `Slowmode set by @${interaction.user.username} (${interaction.user.id})`, + ) + await interaction.reply({ + embeds: [createSuccessEmbed(`Slowmode set to ${durationToString(duration)} on ${channel.toString()}`)], + }) + }, +} satisfies Command diff --git a/bots/discord/src/commands/reply.ts b/bots/discord/src/commands/reply.ts deleted file mode 100644 index a5fe0b2..0000000 --- a/bots/discord/src/commands/reply.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createStackTraceEmbed } from '$/utils/discord/embeds' -import { PermissionFlagsBits, SlashCommandBuilder, type TextBasedChannel } from 'discord.js' -import type { Command } from '.' - -export default { - data: new SlashCommandBuilder() - .setName('reply') - .setDescription('Send a message as the bot') - .addStringOption(option => option.setName('message').setDescription('The message to send').setRequired(true)) - .addStringOption(option => - option - .setName('reference') - .setDescription('The message ID to reply to (use `latest` to reply to the latest message)') - .setRequired(false), - ) - .toJSON(), - - memberRequirements: { - mode: 'all', - roles: ['955220417969262612', '973886585294704640'], - permissions: PermissionFlagsBits.ManageMessages, - }, - - global: false, - - async execute({ logger }, interaction) { - const msg = interaction.options.getString('message', true) - const ref = interaction.options.getString('reference') - - const resolvedRef = ref?.startsWith('latest') - ? (await interaction.channel?.messages.fetch({ limit: 1 }))?.at(0)?.id - : ref - - try { - const channel = (await interaction.guild!.channels.fetch(interaction.channelId)) as TextBasedChannel | null - if (!channel) throw new Error('Channel not found (or not cached)') - - await channel.send({ - content: msg, - reply: { - messageReference: resolvedRef!, - failIfNotExists: true, - }, - }) - - logger.warn(`User ${interaction.user.tag} made the bot say: ${msg}`) - await interaction.reply({ - content: 'OK!', - ephemeral: true, - }) - } catch (e) { - await interaction.reply({ - embeds: [createStackTraceEmbed(e)], - }) - } - }, -} satisfies Command diff --git a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts index c91ecf5..0cfb65e 100644 --- a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts @@ -1,3 +1,4 @@ +import CommandError from '$/classes/CommandError' import { createErrorEmbed, createStackTraceEmbed } from '$utils/discord/embeds' import { on } from '$utils/discord/events' @@ -8,40 +9,46 @@ export default on('interactionCreate', async (context, interaction) => { const command = discord.commands[interaction.commandName] logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag}`) + if (!command) return void logger.error(`Command ${interaction.commandName} not implemented but registered!!!`) - if (!command) { - logger.error(`Command ${interaction.commandName} not implemented but registered!!!`) - return void interaction.reply({ - embeds: [ - createErrorEmbed( - 'Command not implemented', - 'This command has not been implemented yet. Please report this to the developers.', - ), - ], - ephemeral: true, - }) - } + const isOwner = config.owners.includes(interaction.user.id) - const userIsOwner = config.owners.includes(interaction.user.id) - - if ((command.ownerOnly ?? true) && !userIsOwner) + /** + * Owner check + */ + if (command.ownerOnly && !isOwner) return void (await interaction.reply({ embeds: [createErrorEmbed('Massive skill issue', 'This command can only be used by the bot owners.')], ephemeral: true, })) + /** + * Sanity check + */ + if (!command.global && !interaction.inGuild()) { + logger.error(`Command ${interaction.commandName} cannot be run in DMs, but was registered as global`) + await interaction.reply({ + embeds: [createErrorEmbed('Cannot run that here', 'This command can only be used in a server.')], + ephemeral: true, + }) + return + } + + /** + * Permission checks + */ if (interaction.inGuild()) { // Bot owners get bypass - if (command.memberRequirements && !userIsOwner) { - const { permissions = -1n, roles = [], mode } = command.memberRequirements + if (command.memberRequirements && !isOwner) { + const { permissions = 0n, roles = [], mode } = command.memberRequirements const member = await interaction.guild!.members.fetch(interaction.user.id) const [missingPermissions, missingRoles] = [ // This command is an owner-only command (the user is not an owner, checked above) - permissions < 0n || + permissions <= 0n || // or the user doesn't have the required permissions - (permissions >= 0n && !interaction.memberPermissions.has(permissions)), + (permissions > 0n && !interaction.memberPermissions.has(permissions)), // If not: !roles.some(x => member.roles.cache.has(x)), @@ -66,7 +73,7 @@ export default on('interactionCreate', async (context, interaction) => { } catch (err) { logger.error(`Error while executing command ${interaction.commandName}:`, err) await interaction.reply({ - embeds: [createStackTraceEmbed(err)], + embeds: [err instanceof CommandError ? err.toEmbed() : createStackTraceEmbed(err)], ephemeral: true, }) } diff --git a/bots/discord/src/events/discord/messageCreate/scan.ts b/bots/discord/src/events/discord/messageCreate/scan.ts index 5453940..f3195fe 100644 --- a/bots/discord/src/events/discord/messageCreate/scan.ts +++ b/bots/discord/src/events/discord/messageCreate/scan.ts @@ -1,5 +1,5 @@ import { MessageScanLabeledResponseReactions } from '$/constants' -import { getResponseFromContent, shouldScanMessage } from '$/utils/discord/messageScan' +import { getResponseFromText, shouldScanMessage } from '$/utils/discord/messageScan' import { createMessageScanResponseEmbed } from '$utils/discord/embeds' import { on } from '$utils/discord/events' @@ -12,35 +12,41 @@ on('messageCreate', async (ctx, msg) => { } = ctx if (!config || !config.responses) return - if (!shouldScanMessage(msg, config)) return + + const filteredResponses = config.responses.filter(x => shouldScanMessage(msg, x.filterOverride ?? config.filter)) + if (!filteredResponses.length) return if (msg.content.length) { - logger.debug(`Classifying message ${msg.id}`) + try { + logger.debug(`Classifying message ${msg.id}`) - const { response, label } = await getResponseFromContent(msg.content, ctx) + const { response, label } = await getResponseFromText(msg.content, filteredResponses, ctx) - if (response) { - logger.debug('Response found') + if (response) { + logger.debug('Response found') - const reply = await msg.reply({ - embeds: [createMessageScanResponseEmbed(response, label ? 'nlp' : 'match')], - }) - - if (label) - db.labeledResponses.save({ - reply: reply.id, - channel: reply.channel.id, - guild: reply.guild.id, - referenceMessage: msg.id, - label, - text: msg.content, + const reply = await msg.reply({ + embeds: [createMessageScanResponseEmbed(response, label ? 'nlp' : 'match')], }) - if (label) { - for (const reaction of Object.values(MessageScanLabeledResponseReactions)) { - await reply.react(reaction) + if (label) + db.labeledResponses.save({ + reply: reply.id, + channel: reply.channel.id, + guild: reply.guild!.id, + referenceMessage: msg.id, + label, + text: msg.content, + }) + + if (label) { + for (const reaction of Object.values(MessageScanLabeledResponseReactions)) { + await reply.react(reaction) + } } } + } catch (e) { + logger.error('Failed to classify message:', e) } } @@ -52,7 +58,7 @@ on('messageCreate', async (ctx, msg) => { try { const { text: content } = await api.client.parseImage(attachment.url) - const { response } = await getResponseFromContent(content, ctx, true) + const { response } = await getResponseFromText(content, filteredResponses, ctx, true) if (response) { logger.debug(`Response found for attachment: ${attachment.url}`) diff --git a/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts b/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts index 5ef962b..e242fca 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts @@ -30,27 +30,31 @@ on('messageReactionAdd', async (context, rct, user) => { if (reactionMessage.author.id !== reaction.client.user!.id) return if (!PossibleReactions.includes(reaction.emoji.name!)) return - if (reactionMessage.inGuild() && msConfig.humanCorrections.memberRequirements) { - const { - memberRequirements: { roles, permissions }, - } = msConfig.humanCorrections - - if (!roles && !permissions) - return void logger.warn( - 'No member requirements specified for human corrections, ignoring this request for security reasons', - ) - - const member = await reactionMessage.guild.members.fetch(user.id) - + if (!config.owners.includes(user.id)) { + // User is in guild, and config has member requirements if ( - permissions && - !member.permissions.has(permissions) && - roles && - !roles.some(role => member.roles.cache.has(role)) - ) - return - // User is not owner, and not included in allowUsers - } else if (!config.owners.includes(user.id) && !msConfig.humanCorrections.allowUsers?.includes(user.id)) return + reactionMessage.inGuild() && + (msConfig.humanCorrections.allow?.members || msConfig.humanCorrections.allow?.users) + ) { + const { + allow: { users: allowedUsers, members: allowedMembers }, + } = msConfig.humanCorrections + + if (allowedMembers) { + const member = await reactionMessage.guild.members.fetch(user.id) + const { permissions, roles } = allowedMembers + + if (!(member.permissions.has(permissions ?? 0n) || roles?.some(role => member.roles.cache.has(role)))) + return + } else if (allowedUsers) { + if (!allowedUsers.includes(user.id)) return + } else { + return void logger.warn( + 'No member or user requirements set for human corrections, all requests will be ignored', + ) + } + } + } // Sanity check const response = db.labeledResponses.get(rct.message.id) @@ -69,7 +73,9 @@ on('messageReactionAdd', async (context, rct, user) => { // Bot is wrong :( const labels = msConfig.responses!.flatMap(r => - r.triggers.filter((t): t is ConfigMessageScanResponseLabelConfig => 'label' in t).map(t => t.label), + r.triggers + .text!.filter((t): t is ConfigMessageScanResponseLabelConfig => 'label' in t) + .map(t => t.label), ) const componentPrefix = `cr_${reactionMessage.id}` diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index eb45b09..c798767 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -1,55 +1,59 @@ import type { LabeledResponse } from '$/classes/Database' -import type { Config, ConfigMessageScanResponseLabelConfig, ConfigMessageScanResponseMessage } from 'config.example' +import type { + Config, + ConfigMessageScanResponse, + ConfigMessageScanResponseLabelConfig, + ConfigMessageScanResponseMessage, +} from 'config.example' import type { Message, PartialUser, User } from 'discord.js' import { createMessageScanResponseEmbed } from './embeds' -export const getResponseFromContent = async ( +export const getResponseFromText = async ( content: string, - { api, logger, config: { messageScan: config } }: typeof import('src/context'), + responses: ConfigMessageScanResponse[], + // Just to be safe that we will never use data from the context parameter + { api, logger }: Omit, ocrMode = false, ) => { - if (!config || !config.responses) { - logger.warn('No message scan config found') - - return { - response: null, - label: undefined, - } - } - let label: string | undefined let response: ConfigMessageScanResponseMessage | undefined | null const firstLabelIndexes: number[] = [] // Test if all regexes before a label trigger is matched - for (let i = 0; i < config.responses.length; i++) { - const trigger = config.responses[i]! + for (let i = 0; i < responses.length; i++) { + const trigger = responses[i]! - const { triggers, ocrTriggers, response: resp } = trigger + // Filter override check is not neccessary here, we are already passing responses that match the filter + // from the messageCreate handler + const { + triggers: { text: textTriggers, image: imageTriggers }, + response: resp, + } = trigger if (response) break - if (ocrMode && ocrTriggers) - for (const regex of ocrTriggers) - if (regex.test(content)) { - logger.debug(`Message matched regex (OCR mode): ${regex.source}`) - response = resp + if (ocrMode) { + if (imageTriggers) + for (const regex of imageTriggers) + if (regex.test(content)) { + logger.debug(`Message matched regex (OCR mode): ${regex.source}`) + response = resp + break + } + } else + for (let j = 0; j < textTriggers!.length; j++) { + const trigger = textTriggers![j]! + + if (trigger instanceof RegExp) { + if (trigger.test(content)) { + logger.debug(`Message matched regex (before mode): ${trigger.source}`) + response = resp + break + } + } else { + firstLabelIndexes[i] = j break } - - for (let j = 0; j < triggers.length; j++) { - const trigger = triggers[j]! - - if (trigger instanceof RegExp) { - if (trigger.test(content)) { - logger.debug(`Message matched regex (before mode): ${trigger.source}`) - response = resp - break - } - } else { - firstLabelIndexes[i] = j - break } - } } // If none of the regexes match, we can search for labels immediately @@ -61,8 +65,8 @@ export const getResponseFromContent = async ( logger.debug(`Message matched label with confidence: ${matchedLabel.name}, ${matchedLabel.confidence}`) let triggerConfig: ConfigMessageScanResponseLabelConfig | undefined - const labelConfig = config.responses.find(x => { - const config = x.triggers.find( + const labelConfig = responses.find(x => { + const config = x.triggers.text!.find( (x): x is ConfigMessageScanResponseLabelConfig => 'label' in x && x.label === matchedLabel.name, ) if (config) triggerConfig = config @@ -85,12 +89,15 @@ export const getResponseFromContent = async ( // If we still don't have a label, we can match all regexes after the initial label trigger if (!response) { logger.debug('No match from NLP, doing after regexes') - for (let i = 0; i < config.responses.length; i++) { - const { triggers, response: resp } = config.responses[i]! + for (let i = 0; i < responses.length; i++) { + const { + triggers: { text: textTriggers }, + response: resp, + } = responses[i]! const firstLabelIndex = firstLabelIndexes[i] ?? -1 - for (let i = firstLabelIndex + 1; i < triggers.length; i++) { - const trigger = triggers[i]! + for (let i = firstLabelIndex + 1; i < textTriggers!.length; i++) { + const trigger = textTriggers![i]! if (trigger instanceof RegExp) { if (trigger.test(content)) { @@ -111,19 +118,20 @@ export const getResponseFromContent = async ( export const shouldScanMessage = ( message: Message, - config: NonNullable, + filter: NonNullable['filter'], ): message is Message => { if (message.author.bot) return false if (!message.guild) return false + if (!filter) return true const filters = [ - config.users?.includes(message.author.id), - message.member?.roles.cache.some(x => config.roles?.includes(x.id)), - config.channels?.includes(message.channel.id), + filter.users?.includes(message.author.id), + message.member?.roles.cache.some(x => filter.roles?.includes(x.id)), + filter.channels?.includes(message.channel.id), ] - if (config.whitelist && filters.every(x => !x)) return false - if (!config.whitelist && filters.some(x => x)) return false + if (filter.whitelist && filters.every(x => !x)) return false + if (!filter.whitelist && filters.some(x => x)) return false return true } @@ -135,7 +143,9 @@ export const handleUserResponseCorrection = async ( label: string, user: User | PartialUser, ) => { - const correctLabelResponse = msConfig!.responses!.find(r => r.triggers.some(t => 'label' in t && t.label === label)) + const correctLabelResponse = msConfig!.responses!.find(r => + r.triggers.text!.some(t => 'label' in t && t.label === label), + ) if (!correctLabelResponse) throw new Error('Cannot find label config for the selected label') if (!correctLabelResponse.response) return void (await reply.delete()) From ca475356ad95fec86e8e8b5bf4bbf17b70add5fe Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:47:30 +0700 Subject: [PATCH 086/312] feat(bots/discord/commands): allow process exception in `exception-test` --- bots/discord/src/commands/development/exception-test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/commands/development/exception-test.ts b/bots/discord/src/commands/development/exception-test.ts index 038b17a..fb9440a 100644 --- a/bots/discord/src/commands/development/exception-test.ts +++ b/bots/discord/src/commands/development/exception-test.ts @@ -11,6 +11,10 @@ export default { option .setName('type') .setDescription('The type of exception to throw') + .addChoices({ + name: 'process exception', + value: 'Process', + }) .addChoices({ name: 'generic error', value: 'Generic', @@ -36,10 +40,12 @@ export default { .setDMPermission(true) .toJSON(), + ownerOnly: true, global: true, async execute(_, interaction) { const type = interaction.options.getString('type', true) - throw new CommandError(CommandErrorType[type as keyof typeof CommandErrorType], '[INTENTIONAL BOT DESIGN]') + if (type === 'Process') throw new Error('Intentional process exception') + throw new CommandError(CommandErrorType[type as keyof typeof CommandErrorType], 'Intentional bot design') // ;) }, } satisfies Command From 154bdbde3e8fd6d2bd07a7a4e255866c2f35259b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:56:00 +0700 Subject: [PATCH 087/312] chore: update and add README for projects --- apis/websocket/README.md | 2 +- bots/discord/README.md | 73 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100755 bots/discord/README.md diff --git a/apis/websocket/README.md b/apis/websocket/README.md index 36d6ab6..a8e9bad 100755 --- a/apis/websocket/README.md +++ b/apis/websocket/README.md @@ -62,7 +62,7 @@ ![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) -The WebSocket API for ReVanced bots utilizing BSON for packet transmission with a heartbeating system to monitor the statuses of clients. +The WebSocket API for ReVanced bots utilizing BSON for packet transmission. ## 📚 Documentation diff --git a/bots/discord/README.md b/bots/discord/README.md new file mode 100755 index 0000000..f87bbc6 --- /dev/null +++ b/bots/discord/README.md @@ -0,0 +1,73 @@ +

      + + + + +
      + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + + +
      +
      + Continuing the legacy of Vanced +

      + +# 🤖 ReVanced Discord Bot + +![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) + +Discord bot assisting ReVanced's Discord server. + +## 📚 Documentation + +Documentation are provided [here](./docs/README.md). + +## 📄 License + +**ReVanced Discord Bot** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. From 815a14ee8d8b3dee46a494ae7e81ba18fa6211ba Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 19:56:44 +0700 Subject: [PATCH 088/312] chore(lint): remove `md` and `yml` files from checks --- lefthook.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lefthook.yml b/lefthook.yml index 8b6eb11..c2581aa 100755 --- a/lefthook.yml +++ b/lefthook.yml @@ -3,7 +3,7 @@ pre-commit: commands: check: files: git diff --name-only --cached --diff-filter=AM @{push} - glob: "*.{js,ts,json,yml,md}" + glob: "*.{js,ts,json}" run: bunx biome check {files} commit-msg: parallel: false From 513c6b18f0257e847ebe8939175e865745b3cd81 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 2 Apr 2024 20:00:26 +0700 Subject: [PATCH 089/312] docs(bots/discord): fix oversight --- bots/discord/docs/2_adding_autoresponses.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bots/discord/docs/2_adding_autoresponses.md b/bots/discord/docs/2_adding_autoresponses.md index a958941..3fd25d7 100644 --- a/bots/discord/docs/2_adding_autoresponses.md +++ b/bots/discord/docs/2_adding_autoresponses.md @@ -15,7 +15,7 @@ Roles, users, and channels which will be affected by the blacklist/whitelist rul Whether to use whitelist (`true`) or blacklist (`false`) mode. - Blacklist mode **will refuse** to scan messages that match any of the filters above -- Whitelist mode **will refuse** to scan messages that match any of the filters above. +- Whitelist mode **will only** scan messages that match any of the filters above. ## 💬 Responses @@ -79,7 +79,8 @@ filterOverride: { }, ``` -[^1]: Possible triggers are regular expressions or [label configurations](../config.example.ts#68). +[^1]: Possible triggers are regular expressions or [label configurations](../config.example.ts#L83). + Label configurations are only allowed for **text scans** currently. However in the future, it may also come for image scans. There is nothing preventing this from happening. ## ⏭️ What's next From c1e1953762d2c1a51c6fbf9c43c21312543479aa Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 3 Apr 2024 15:34:33 +0700 Subject: [PATCH 090/312] chore(bots/discord): add default config --- bots/discord/config.revanced.ts | 2 +- .../{config.example.ts => config.schema.ts} | 44 ------------------ bots/discord/config.ts | 45 +++++++++++++++++++ bots/discord/docs/1_configuration.md | 2 +- bots/discord/docs/2_adding_autoresponses.md | 2 +- .../messageReactionAdd/correct-response.ts | 2 +- bots/discord/src/utils/discord/embeds.ts | 2 +- bots/discord/src/utils/discord/messageScan.ts | 2 +- bots/discord/src/utils/discord/modLogs.ts | 0 9 files changed, 51 insertions(+), 50 deletions(-) rename bots/discord/{config.example.ts => config.schema.ts} (51%) create mode 100644 bots/discord/config.ts create mode 100644 bots/discord/src/utils/discord/modLogs.ts diff --git a/bots/discord/config.revanced.ts b/bots/discord/config.revanced.ts index 641ef16..acd2afe 100644 --- a/bots/discord/config.revanced.ts +++ b/bots/discord/config.revanced.ts @@ -1,5 +1,5 @@ import { PermissionFlagsBits } from 'discord.js' -import type { Config } from './config.example' +import type { Config } from './config.schema' export default { owners: ['629368283354628116', '737323631117598811', '282584705218510848'], diff --git a/bots/discord/config.example.ts b/bots/discord/config.schema.ts similarity index 51% rename from bots/discord/config.example.ts rename to bots/discord/config.schema.ts index 70f9451..da36dd0 100644 --- a/bots/discord/config.example.ts +++ b/bots/discord/config.schema.ts @@ -1,47 +1,3 @@ -export default { - owners: ['USER_ID_HERE'], - guilds: ['GUILD_ID_HERE'], - messageScan: { - filter: { - channels: ['CHANNEL_ID_HERE'], - roles: ['ROLE_ID_HERE'], - users: ['USER_ID_HERE'], - whitelist: false, - }, - humanCorrections: { - falsePositiveLabel: 'false_positive', - allow: { - members: { - permissions: 8n, - roles: ['ROLE_ID_HERE'], - }, - }, - }, - allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], - responses: [ - { - triggers: { - text: [/^regexp?$/, { label: 'label', threshold: 0.85 }], - }, - response: { - title: 'Embed title', - description: 'Embed description', - fields: [ - { - name: 'Field name', - value: 'Field value', - }, - ], - }, - }, - ], - }, - logLevel: 'log', - api: { - websocketUrl: 'ws://127.0.0.1:3000', - }, -} satisfies Config as Config - export type Config = { owners: string[] guilds: string[] diff --git a/bots/discord/config.ts b/bots/discord/config.ts new file mode 100644 index 0000000..9749e31 --- /dev/null +++ b/bots/discord/config.ts @@ -0,0 +1,45 @@ +import type { Config } from './config.schema' + +export default { + owners: ['USER_ID_HERE'], + guilds: ['GUILD_ID_HERE'], + messageScan: { + filter: { + channels: ['CHANNEL_ID_HERE'], + roles: ['ROLE_ID_HERE'], + users: ['USER_ID_HERE'], + whitelist: false, + }, + humanCorrections: { + falsePositiveLabel: 'false_positive', + allow: { + members: { + permissions: 8n, + roles: ['ROLE_ID_HERE'], + }, + }, + }, + allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], + responses: [ + { + triggers: { + text: [/^regexp?$/, { label: 'label', threshold: 0.85 }], + }, + response: { + title: 'Embed title', + description: 'Embed description', + fields: [ + { + name: 'Field name', + value: 'Field value', + }, + ], + }, + }, + ], + }, + logLevel: 'log', + api: { + websocketUrl: 'ws://127.0.0.1:3000', + }, +} satisfies Config as Config diff --git a/bots/discord/docs/1_configuration.md b/bots/discord/docs/1_configuration.md index 35408fc..8cc2631 100644 --- a/bots/discord/docs/1_configuration.md +++ b/bots/discord/docs/1_configuration.md @@ -1,6 +1,6 @@ # ⚙️ Configuration -You will need to copy `config.example.ts` to `config.ts` to be able to start the bot, as it is the default configuration. +See [`config.ts`](../config.ts). --- diff --git a/bots/discord/docs/2_adding_autoresponses.md b/bots/discord/docs/2_adding_autoresponses.md index 3fd25d7..bb028a8 100644 --- a/bots/discord/docs/2_adding_autoresponses.md +++ b/bots/discord/docs/2_adding_autoresponses.md @@ -79,7 +79,7 @@ filterOverride: { }, ``` -[^1]: Possible triggers are regular expressions or [label configurations](../config.example.ts#L83). +[^1]: Possible triggers are regular expressions or [label configurations](../config.schema.ts#L83). Label configurations are only allowed for **text scans** currently. However in the future, it may also come for image scans. There is nothing preventing this from happening. ## ⏭️ What's next diff --git a/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts b/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts index e242fca..d562c0c 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts @@ -11,7 +11,7 @@ import { } from 'discord.js' import { handleUserResponseCorrection } from '$/utils/discord/messageScan' -import type { ConfigMessageScanResponseLabelConfig } from 'config.example' +import type { ConfigMessageScanResponseLabelConfig } from 'config.schema' const PossibleReactions = Object.values(Reactions) as string[] diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index 4ca387d..cbb1173 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -1,6 +1,6 @@ import { DefaultEmbedColor, MessageScanHumanizedMode, ReVancedLogoURL } from '$/constants' import { EmbedBuilder } from 'discord.js' -import type { ConfigMessageScanResponseMessage } from '../../../config.example' +import type { ConfigMessageScanResponseMessage } from '../../../config.schema' export const createErrorEmbed = (title: string, description?: string) => applyCommonEmbedStyles( diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index c798767..ce9c180 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -4,7 +4,7 @@ import type { ConfigMessageScanResponse, ConfigMessageScanResponseLabelConfig, ConfigMessageScanResponseMessage, -} from 'config.example' +} from 'config.schema' import type { Message, PartialUser, User } from 'discord.js' import { createMessageScanResponseEmbed } from './embeds' diff --git a/bots/discord/src/utils/discord/modLogs.ts b/bots/discord/src/utils/discord/modLogs.ts new file mode 100644 index 0000000..e69de29 From 35b944800a3943c187d5b0e0d3e465ad7d2056fe Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 3 Apr 2024 15:38:36 +0700 Subject: [PATCH 091/312] fix(bots/discord): use `APIEmbed` for response config --- bots/discord/config.schema.ts | 11 +++-------- bots/discord/src/utils/discord/embeds.ts | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index da36dd0..d94eb4f 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -1,3 +1,5 @@ +import type { APIEmbed } from 'discord.js' + export type Config = { owners: string[] guilds: string[] @@ -47,11 +49,4 @@ export type ConfigMessageScanResponseLabelConfig = { threshold: number } -export type ConfigMessageScanResponseMessage = { - title: string - description?: string - fields?: Array<{ - name: string - value: string - }> -} +export type ConfigMessageScanResponseMessage = APIEmbed diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index cbb1173..77eb72c 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -27,7 +27,7 @@ export const createMessageScanResponseEmbed = ( response: ConfigMessageScanResponseMessage, mode: 'ocr' | 'nlp' | 'match', ) => { - const embed = new EmbedBuilder().setTitle(response.title) + const embed = new EmbedBuilder().setTitle(response.title ?? null) if (response.description) embed.setDescription(response.description) if (response.fields) embed.addFields(response.fields) From 32a7fea2941022639d3de11f34b952cd3a309847 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 22 Jun 2024 20:07:45 +0700 Subject: [PATCH 092/312] chore: update dependencies --- apis/websocket/package.json | 2 +- bots/discord/package.json | 2 +- bun.lockb | Bin 83212 -> 83140 bytes package.json | 14 +++++++------- packages/api/package.json | 2 +- packages/shared/package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 3f73800..3954292 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -30,7 +30,7 @@ "@revanced/bot-shared": "workspace:*", "@sapphire/async-queue": "^1.5.2", "chalk": "^5.3.0", - "tesseract.js": "^5.0.5" + "tesseract.js": "^5.1.0" }, "devDependencies": { "typed-emitter": "^2.1.0" diff --git a/bots/discord/package.json b/bots/discord/package.json index 2c82215..952dec7 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -30,6 +30,6 @@ "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", - "discord.js": "^14.14.1" + "discord.js": "^14.15.3" } } diff --git a/bun.lockb b/bun.lockb index 934c38ec1e6dddaac53ed2fe60b23600b7f85a75..833871b1e2299c0fc9e24e8345415e6ceefb527e 100755 GIT binary patch delta 19934 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

      9>AVWczMe?P?o@|kWd*a0= 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

      e>EC|Yy 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 Z05zj#I-?7tHV3GL (https://github.com/revanced)" ], "devDependencies": { - "@biomejs/biome": "^1.6.3", - "@commitlint/cli": "^19.2.1", - "@commitlint/config-conventional": "^19.1.0", + "@biomejs/biome": "^1.8.2", + "@commitlint/cli": "^19.3.0", + "@commitlint/config-conventional": "^19.2.2", "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", "@tsconfig/strictest": "^2.0.5", - "@types/bun": "^1.0.12", + "@types/bun": "^1.1.5", "concurrently": "^8.2.2", "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.6.8", - "turbo": "^1.13.1", - "typescript": "^5.4.3" + "lefthook": "^1.6.18", + "turbo": "^1.13.4", + "typescript": "^5.5.2" }, "trustedDependencies": ["@biomejs/biome", "lefthook", "tesseract.js"] } diff --git a/packages/api/package.json b/packages/api/package.json index 4801219..dbcecce 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -28,7 +28,7 @@ "homepage": "https://github.com/revanced/revanced-helper#readme", "dependencies": { "@revanced/bot-shared": "workspace:*", - "ws": "^8.16.0" + "ws": "^8.17.1" }, "devDependencies": { "typed-emitter": "^2.1.0" diff --git a/packages/shared/package.json b/packages/shared/package.json index 09f222a..a25c89f 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -30,7 +30,7 @@ }, "homepage": "https://github.com/revanced/revanced-helper#readme", "dependencies": { - "bson": "^6.5.0", + "bson": "^6.7.0", "chalk": "^5.3.0", "tracer": "^1.3.0", "valibot": "^0.30.0" From 6109842897a1bad99fb8995c6209c2a92b5c7422 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 22 Jun 2024 20:30:52 +0700 Subject: [PATCH 093/312] chore: change old contact info --- apis/websocket/README.md | 2 +- apis/websocket/package.json | 6 +++--- bots/discord/README.md | 2 +- bots/discord/package.json | 6 +++--- package.json | 6 +++--- packages/api/package.json | 6 +++--- packages/shared/package.json | 6 +++--- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apis/websocket/README.md b/apis/websocket/README.md index a8e9bad..b66086f 100755 --- a/apis/websocket/README.md +++ b/apis/websocket/README.md @@ -17,7 +17,7 @@     - + diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 3954292..b5159cf 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -16,10 +16,10 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "apis/websocket" }, - "author": "Palm (https://github.com/PalmDevs)", + "author": "Palm (https://palmdevs.me)", "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" + "Palm (https://palmdevs.me)", + "ReVanced (https://revanced.app)" ], "license": "GPL-3.0-or-later", "bugs": { diff --git a/bots/discord/README.md b/bots/discord/README.md index f87bbc6..0b73e04 100755 --- a/bots/discord/README.md +++ b/bots/discord/README.md @@ -17,7 +17,7 @@     - + diff --git a/bots/discord/package.json b/bots/discord/package.json index 952dec7..e839061 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -16,10 +16,10 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "bots/discord" }, - "author": "Palm (https://github.com/PalmDevs)", + "author": "Palm (https://palmdevs.me)", "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" + "Palm (https://palmdevs.me)", + "ReVanced (https://revanced.app)" ], "license": "GPL-3.0-or-later", "bugs": { diff --git a/package.json b/package.json index bc73f2c..477e9c9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "version": "0.0.0", "license": "GPL-3.0-or-later", "type": "module", - "author": "Palm (https://github.com/PalmDevs)", + "author": "Palm (https://palmdevs.me)", "workspaces": ["apis/*", "bots/*", "packages/*"], "scripts": { "build": "turbo run build", @@ -24,8 +24,8 @@ "url": "https://github.com/revanced/revanced-helper/issues" }, "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" + "Palm (https://palmdevs.me)", + "ReVanced (https://revanced.app)" ], "devDependencies": { "@biomejs/biome": "^1.8.2", diff --git a/packages/api/package.json b/packages/api/package.json index dbcecce..aa22783 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -16,10 +16,10 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "packages/api" }, - "author": "Palm (https://github.com/PalmDevs)", + "author": "Palm (https://palmdevs.me)", "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" + "Palm (https://palmdevs.me)", + "ReVanced (https://revanced.app)" ], "license": "GPL-3.0-or-later", "bugs": { diff --git a/packages/shared/package.json b/packages/shared/package.json index a25c89f..110a3d1 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -19,10 +19,10 @@ "url": "git+https://github.com/revanced/revanced-helper.git", "directory": "packages/shared" }, - "author": "Palm (https://github.com/PalmDevs)", + "author": "Palm (https://palmdevs.me)", "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" + "Palm (https://palmdevs.me)", + "ReVanced (https://revanced.app)" ], "license": "GPL-3.0-or-later", "bugs": { From f2abae23509cd9d8302a3d0be9c0a787a88b3916 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 22 Jun 2024 22:04:58 +0700 Subject: [PATCH 094/312] chore: revert mistakenly replaced links --- README.md | 2 +- apis/websocket/README.md | 2 +- bots/discord/package.json | 2 +- docs/0_development_environment.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3055ae1..88003ed 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@     - + diff --git a/apis/websocket/README.md b/apis/websocket/README.md index b66086f..1bdbe4c 100755 --- a/apis/websocket/README.md +++ b/apis/websocket/README.md @@ -17,7 +17,7 @@     - + diff --git a/bots/discord/package.json b/bots/discord/package.json index e839061..a9bf588 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -8,7 +8,7 @@ "scripts": { "register": "bun run scripts/reload-slash-commands.ts", "dev": "bun --watch src/index.ts", - "build": "tsc && bun run scripts/after-build", + "build": "tsc", "watch": "bun dev" }, "repository": { diff --git a/docs/0_development_environment.md b/docs/0_development_environment.md index 29c2588..87af6c1 100644 --- a/docs/0_development_environment.md +++ b/docs/0_development_environment.md @@ -11,7 +11,7 @@ To start developing, you'll need to set up the development environment first. 2. Clone the mono-repository ```sh - git clone https://github.com/ReVanced/revanced-helper.git && + git clone https://github.com/revanced/revanced-helper.git && cd revanced-helper ``` From c2a81c6258d73416aab14aff511203e7b9c7aa27 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 22 Jun 2024 22:05:47 +0700 Subject: [PATCH 095/312] feat(patches): add patch for `tesseract.js` --- bun.lockb | Bin 83140 -> 81496 bytes package.json | 5 ++++- patches/tesseract.js@5.1.0.patch | 11 +++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 patches/tesseract.js@5.1.0.patch diff --git a/bun.lockb b/bun.lockb index 833871b1e2299c0fc9e24e8345415e6ceefb527e..e0a218b796c7aefc377e2aab2a38ebb5cd6b706b 100755 GIT binary patch delta 12275 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 c0V? Date: Sat, 22 Jun 2024 22:09:19 +0700 Subject: [PATCH 096/312] fix(apis/websocket): builds not working due to dynamic import requirement --- apis/websocket/after_build.ts | 3 +++ apis/websocket/package.json | 2 +- apis/websocket/src/utils/config.ts | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 apis/websocket/after_build.ts diff --git a/apis/websocket/after_build.ts b/apis/websocket/after_build.ts new file mode 100644 index 0000000..d7c41f7 --- /dev/null +++ b/apis/websocket/after_build.ts @@ -0,0 +1,3 @@ +import * as fs from 'fs' + +fs.cpSync('../../node_modules/tesseract.js', './dist/tesseract.js', { recursive: true }) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index b5159cf..c9fd4d2 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -6,7 +6,7 @@ "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { - "bundle": "bun build src/index.ts --outdir=dist --target=bun", + "bundle": "bun build src/index.ts --outdir=dist --target=bun && bun run after_build.ts", "dev": "bun run src/index.ts --watch", "build": "bun bundle", "watch": "bun dev" diff --git a/apis/websocket/src/utils/config.ts b/apis/websocket/src/utils/config.ts index 7beb09b..4415c0d 100755 --- a/apis/websocket/src/utils/config.ts +++ b/apis/websocket/src/utils/config.ts @@ -19,8 +19,8 @@ type BaseTypeOf = T extends (infer U)[] : T extends (...args: unknown[]) => infer U ? (...args: unknown[]) => U : T extends object - ? { [K in keyof T]: T[K] } - : T + ? { [K in keyof T]: T[K] } + : T export type Config = Omit, '$schema'> From 348a18a40119a40c0f79bc193836e15d9462ec11 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 22 Jun 2024 22:09:43 +0700 Subject: [PATCH 097/312] chore: format workspace --- biome.json | 5 +++-- bots/discord/README.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/biome.json b/biome.json index b2a4e61..21f22fe 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.3.3/schema.json", + "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", "organizeImports": { "enabled": true }, @@ -38,6 +38,7 @@ "javascript": { "formatter": { "enabled": true, + "lineEnding": "crlf", "arrowParentheses": "asNeeded", "indentStyle": "space", "indentWidth": 4, @@ -45,7 +46,7 @@ "quoteProperties": "asNeeded", "quoteStyle": "single", "semicolons": "asNeeded", - "trailingComma": "all" + "trailingCommas": "all" } }, "files": { diff --git a/bots/discord/README.md b/bots/discord/README.md index 0b73e04..fdf73ec 100755 --- a/bots/discord/README.md +++ b/bots/discord/README.md @@ -17,7 +17,7 @@     - + From fa0159c3a8dd4dad8778ccdb75b9e7c02ebbb64f Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 23 Jun 2024 14:01:06 +0700 Subject: [PATCH 098/312] fix(bots/discord): wrong command file path being imported --- bots/discord/src/utils/fs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/utils/fs.ts b/bots/discord/src/utils/fs.ts index b5fc051..0fa0b7f 100644 --- a/bots/discord/src/utils/fs.ts +++ b/bots/discord/src/utils/fs.ts @@ -5,7 +5,7 @@ import { fileURLToPath } from 'bun' export const listAllFilesRecursive = (dir: string): string[] => readdirSync(dir, { recursive: true, withFileTypes: true }) .filter(x => x.isFile()) - .map(x => join(dir, x.name)) + .map(x => join(x.parentPath, x.name)) export const pathJoinCurrentDir = (importMetaUrl: string, ...objects: [string, ...string[]]) => join(dirname(fileURLToPath(importMetaUrl)), ...objects) From 3bca6e5c31850312661f86ecb97be77b919e687f Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 23 Jun 2024 14:02:31 +0700 Subject: [PATCH 099/312] fix(patches/tesseract.js): check if original file exists before using new path --- patches/tesseract.js@5.1.0.patch | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/patches/tesseract.js@5.1.0.patch b/patches/tesseract.js@5.1.0.patch index c217d0f..93ecfc3 100644 --- a/patches/tesseract.js@5.1.0.patch +++ b/patches/tesseract.js@5.1.0.patch @@ -1,11 +1,19 @@ diff --git a/src/worker/node/defaultOptions.js b/src/worker/node/defaultOptions.js -index 053c1e3e3b561b2e5b5e3609e9c6b509b1661f11..d4fe84893786d340de3527bcf7f090e1006c0f09 100644 +index 053c1e3e3b561b2e5b5e3609e9c6b509b1661f11..407242f5576f8d888644ab28ff169599a79bf4dd 100644 --- a/src/worker/node/defaultOptions.js +++ b/src/worker/node/defaultOptions.js -@@ -6,5 +6,5 @@ const defaultOptions = require('../../constants/defaultOptions'); +@@ -1,10 +1,13 @@ + const path = require('path'); ++const fs = require('fs'); + const defaultOptions = require('../../constants/defaultOptions'); + ++const defaultPath = path.join(import.meta.dir, '..', '..', 'worker-script', 'node', 'index.js') ++ + /* + * Default options for node worker */ module.exports = { ...defaultOptions, - workerPath: path.join(__dirname, '..', '..', 'worker-script', 'node', 'index.js'), -+ workerPath: path.join(import.meta.dir, 'tesseract.js', 'src', 'worker-script', 'node', 'index.js'), ++ workerPath: fs.existsSync(defaultPath) ? defaultPath : path.join(import.meta.dir, 'tesseract.js', 'src', 'worker-script', 'node', 'index.js'), }; From e204b7b7566fd7fa423baef32977a8575d44a9e0 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 23 Jun 2024 17:00:17 +0700 Subject: [PATCH 100/312] feat(bots/discord): switch to `drizzle-orm` --- bots/discord/.gitignore | 3 +- bots/discord/package.json | 6 +- bots/discord/src/classes/Database.ts | 127 ------------------ bots/discord/src/context.ts | 18 ++- bots/discord/src/database/schemas.ts | 21 +++ .../interactionCreate/correct-response.ts | 10 +- .../src/events/discord/messageCreate/scan.ts | 13 +- .../messageReactionAdd/correct-response.ts | 6 +- bots/discord/src/utils/discord/messageScan.ts | 16 ++- 9 files changed, 68 insertions(+), 152 deletions(-) delete mode 100644 bots/discord/src/classes/Database.ts create mode 100644 bots/discord/src/database/schemas.ts diff --git a/bots/discord/.gitignore b/bots/discord/.gitignore index 0614777..7f0bd6c 100644 --- a/bots/discord/.gitignore +++ b/bots/discord/.gitignore @@ -178,4 +178,5 @@ dist config.ts # DB -*.db \ No newline at end of file +*.db +*.sqlite \ No newline at end of file diff --git a/bots/discord/package.json b/bots/discord/package.json index a9bf588..042cfaf 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -30,6 +30,10 @@ "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", - "discord.js": "^14.15.3" + "discord.js": "^14.15.3", + "drizzle-orm": "^0.31.2" + }, + "devDependencies": { + "drizzle-kit": "^0.22.7" } } diff --git a/bots/discord/src/classes/Database.ts b/bots/discord/src/classes/Database.ts deleted file mode 100644 index 7da2bb0..0000000 --- a/bots/discord/src/classes/Database.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Database } from 'bun:sqlite' - -type BasicSQLBindings = string | number | null - -export class BasicDatabase> { - #db: Database - #table: string - - constructor(file: string, struct: string, tableName = 'data') { - const db = new Database(file, { - create: true, - readwrite: true, - }) - - this.#db = db - this.#table = tableName - - db.run(`CREATE TABLE IF NOT EXISTS ${tableName} (${struct});`) - } - - run(statement: string) { - this.#db.run(statement) - } - - prepare(statement: string) { - return this.#db.prepare(statement) - } - - query(statement: string) { - return this.#db.query(statement) - } - - insert(...values: BasicSQLBindings[]) { - this.run(`INSERT INTO ${this.#table} VALUES (${values.map(this.#encodeValue).join(', ')});`) - } - - update(what: Partial, where: string) { - const set = Object.entries(what) - .map(([key, value]) => `${key} = ${this.#encodeValue(value)}`) - .join(', ') - - this.run(`UPDATE ${this.#table} SET ${set} WHERE ${where};`) - } - - delete(where: string) { - this.run(`DELETE FROM ${this.#table} WHERE ${where};`) - } - - select(columns: string[] | string, where: string) { - const realColumns = Array.isArray(columns) ? columns.join(', ') : columns - return this.query(`SELECT ${realColumns} FROM ${this.#table} WHERE ${where};`).get() - } - - #encodeValue(value: unknown) { - if (typeof value === 'string') return `'${value.replaceAll("'", "\\'")}'` - if (typeof value === 'number') return value - if (typeof value === 'boolean') return value ? 1 : 0 - if (value === null) return 'NULL' - return null - } -} - -export class LabeledResponseDatabase { - #db: BasicDatabase - - constructor() { - this.#db = new BasicDatabase( - 'responses.db', - `reply TEXT PRIMARY KEY NOT NULL, - channel TEXT NOT NULL, - guild TEXT NOT NULL, - referenceMessage TEXT KEY NOT NULL, - label TEXT NOT NULL, - text TEXT NOT NULL, - correctedBy TEXT, - CHECK ( - typeof("text") = 'text' AND - length("text") > 0 AND - length("text") <= 280 - )`, - ) - } - - save({ reply, channel, guild, referenceMessage, label, text }: Omit) { - const actualText = text.slice(0, 280) - this.#db.insert(reply, channel, guild, referenceMessage, label, actualText, null) - } - - get(reply: string) { - return this.#db.select('*', `reply = ${reply}`) - } - - edit(reply: string, { label, correctedBy }: Pick) { - this.#db.update({ label, correctedBy }, `reply = ${reply}`) - } -} - -export type LabeledResponse = { - /** - * The label of the response - */ - label: string - /** - * The ID of the user who corrected the response - */ - correctedBy: string | null - /** - * The text content of the response - */ - text: string - /** - * The ID of the message that triggered the response - */ - referenceMessage: string - /** - * The ID of the channel where the response was sent - */ - channel: string - /** - * The ID of the guild where the response was sent - */ - guild: string - /** - * The ID of the reply - */ - reply: string -} diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index f3216d0..3c8bfc7 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -1,10 +1,14 @@ -import { loadCommands } from '$utils/discord/commands' +import { Database } from 'bun:sqlite' import { Client as APIClient } from '@revanced/bot-api' import { createLogger } from '@revanced/bot-shared' import { ActivityType, Client as DiscordClient, Partials } from 'discord.js' +import { drizzle } from 'drizzle-orm/bun-sqlite' + import config from '../config' -import { LabeledResponseDatabase } from './classes/Database' -import { pathJoinCurrentDir } from './utils/fs' +import * as schemas from './database/schemas' + +import { loadCommands } from '$utils/discord/commands' +import { pathJoinCurrentDir } from '$utils/fs' export { config } export const logger = createLogger({ @@ -23,9 +27,11 @@ export const api = { disconnectCount: 0, } -export const database = { - labeledResponses: new LabeledResponseDatabase(), -} as const +const db = new Database('db.sqlite') + +export const database = drizzle(db, { + schema: schemas, +}) export const discord = { client: new DiscordClient({ diff --git a/bots/discord/src/database/schemas.ts b/bots/discord/src/database/schemas.ts new file mode 100644 index 0000000..23abaa3 --- /dev/null +++ b/bots/discord/src/database/schemas.ts @@ -0,0 +1,21 @@ +import type { InferSelectModel } from 'drizzle-orm' +import { sqliteTable, text } from 'drizzle-orm/sqlite-core' + +export const responses = sqliteTable('responses', { + replyId: text('reply').primaryKey().notNull(), + channelId: text('channel').notNull(), + guildId: text('guild').notNull(), + referenceId: text('ref').notNull(), + label: text('label').notNull(), + content: text('text').notNull(), + correctedById: text('by'), +}) + +export const appliedPresets = sqliteTable('applied_presets', { + memberId: text('member').primaryKey().notNull(), + guildId: text('guild').notNull(), + presets: text('presets', { mode: 'json' }).$type().notNull().default([]), +}) + +export type Response = InferSelectModel +export type AppliedPreset = InferSelectModel diff --git a/bots/discord/src/events/discord/interactionCreate/correct-response.ts b/bots/discord/src/events/discord/interactionCreate/correct-response.ts index 9ded5dc..9c1c810 100644 --- a/bots/discord/src/events/discord/interactionCreate/correct-response.ts +++ b/bots/discord/src/events/discord/interactionCreate/correct-response.ts @@ -1,8 +1,10 @@ +import { responses } from '$/database/schemas' import { handleUserResponseCorrection } from '$/utils/discord/messageScan' import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds' import { on } from '$utils/discord/events' import type { ButtonInteraction, StringSelectMenuInteraction, TextBasedChannel } from 'discord.js' +import { eq } from 'drizzle-orm' // No permission check required as it is already done when the user reacts to a bot response export default on('interactionCreate', async (context, interaction) => { @@ -19,7 +21,7 @@ export default on('interactionCreate', async (context, interaction) => { const [, key, action] = interaction.customId.split('_') as ['cr', string, 'select' | 'cancel' | 'delete'] if (!key || !action) return - const response = db.labeledResponses.get(key) + const response = await db.query.responses.findFirst({ where: eq(responses.replyId, key) }) // If the message isn't saved in my DB (unrelated message) if (!response) return void (await interaction.reply({ @@ -30,8 +32,8 @@ export default on('interactionCreate', async (context, interaction) => { try { // We're gonna pretend reactionChannel is a text-based channel, but it can be many more // But `messages` should always exist as a property - const reactionGuild = await interaction.client.guilds.fetch(response.guild) - const reactionChannel = (await reactionGuild.channels.fetch(response.channel)) as TextBasedChannel | null + const reactionGuild = await interaction.client.guilds.fetch(response.guildId) + const reactionChannel = (await reactionGuild.channels.fetch(response.channelId)) as TextBasedChannel | null const reactionMessage = await reactionChannel?.messages.fetch(key) if (!reactionMessage) { @@ -55,7 +57,7 @@ export default on('interactionCreate', async (context, interaction) => { const handleCorrection = (label: string) => handleUserResponseCorrection(context, response, reactionMessage, label, interaction.user) - if (response.correctedBy) + if (response.correctedById) return await editMessage( 'Response already corrected', 'Thank you for your feedback! Unfortunately, this response has already been corrected by someone else.', diff --git a/bots/discord/src/events/discord/messageCreate/scan.ts b/bots/discord/src/events/discord/messageCreate/scan.ts index f3195fe..4400172 100644 --- a/bots/discord/src/events/discord/messageCreate/scan.ts +++ b/bots/discord/src/events/discord/messageCreate/scan.ts @@ -1,4 +1,5 @@ import { MessageScanLabeledResponseReactions } from '$/constants' +import { responses } from '$/database/schemas' import { getResponseFromText, shouldScanMessage } from '$/utils/discord/messageScan' import { createMessageScanResponseEmbed } from '$utils/discord/embeds' import { on } from '$utils/discord/events' @@ -30,13 +31,13 @@ on('messageCreate', async (ctx, msg) => { }) if (label) - db.labeledResponses.save({ - reply: reply.id, - channel: reply.channel.id, - guild: reply.guild!.id, - referenceMessage: msg.id, + db.insert(responses).values({ + replyId: reply.id, + channelId: reply.channel.id, + guildId: reply.guild!.id, + referenceId: msg.id, label, - text: msg.content, + content: msg.content, }) if (label) { diff --git a/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts b/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts index d562c0c..b90a21a 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts @@ -10,8 +10,10 @@ import { StringSelectMenuOptionBuilder, } from 'discord.js' +import { responses } from '$/database/schemas' import { handleUserResponseCorrection } from '$/utils/discord/messageScan' import type { ConfigMessageScanResponseLabelConfig } from 'config.schema' +import { eq } from 'drizzle-orm' const PossibleReactions = Object.values(Reactions) as string[] @@ -57,8 +59,8 @@ on('messageReactionAdd', async (context, rct, user) => { } // Sanity check - const response = db.labeledResponses.get(rct.message.id) - if (!response || response.correctedBy) return + const response = await db.query.responses.findFirst({ where: eq(responses.replyId, rct.message.id) }) + if (!response || response.correctedById) return const handleCorrection = (label: string) => handleUserResponseCorrection(context, response, reactionMessage, label, user) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index ce9c180..41f6f18 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -1,4 +1,4 @@ -import type { LabeledResponse } from '$/classes/Database' +import { type Response, responses } from '$/database/schemas' import type { Config, ConfigMessageScanResponse, @@ -6,6 +6,7 @@ import type { ConfigMessageScanResponseMessage, } from 'config.schema' import type { Message, PartialUser, User } from 'discord.js' +import { eq } from 'drizzle-orm' import { createMessageScanResponseEmbed } from './embeds' export const getResponseFromText = async ( @@ -138,7 +139,7 @@ export const shouldScanMessage = ( export const handleUserResponseCorrection = async ( { api, database: db, config: { messageScan: msConfig }, logger }: typeof import('$/context'), - response: LabeledResponse, + response: Response, reply: Message, label: string, user: User | PartialUser, @@ -151,14 +152,19 @@ export const handleUserResponseCorrection = async ( if (!correctLabelResponse.response) return void (await reply.delete()) if (response.label !== label) { - db.labeledResponses.edit(response.reply, { label, correctedBy: user.id }) + db.update(responses) + .set({ + label, + correctedById: user.id, + }) + .where(eq(responses.replyId, response.replyId)) await reply.edit({ embeds: [createMessageScanResponseEmbed(correctLabelResponse.response, 'nlp')], }) } - await api.client.trainMessage(response.text, label) - logger.debug(`User ${user.id} trained message ${response.reply} as ${label} (positive)`) + await api.client.trainMessage(response.content, label) + logger.debug(`User ${user.id} trained message ${response.replyId} as ${label} (positive)`) await reply.reactions.removeAll() } From c53a89ff99f36dd165f2957578ef4ab283869815 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 01:14:15 +0700 Subject: [PATCH 101/312] chore(bots/discord): setup drizzle kit --- bots/discord/drizzle.config.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 bots/discord/drizzle.config.ts diff --git a/bots/discord/drizzle.config.ts b/bots/discord/drizzle.config.ts new file mode 100644 index 0000000..719d1d8 --- /dev/null +++ b/bots/discord/drizzle.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'drizzle-kit' + +export default defineConfig({ + dialect: 'sqlite', + schema: './src/database/schemas.ts', + dbCredentials: { + url: 'file:./db.sqlite', + }, +}) From 6658b582dbeba7e072a7a04c4efa255e7f634aef Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 23 Jun 2024 17:01:28 +0700 Subject: [PATCH 102/312] fix(bots/discord): connect to discord API even if initial bot API connection fails --- bots/discord/src/events/api/disconnect.ts | 2 +- bots/discord/src/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bots/discord/src/events/api/disconnect.ts b/bots/discord/src/events/api/disconnect.ts index f6306e5..29d1ae4 100644 --- a/bots/discord/src/events/api/disconnect.ts +++ b/bots/discord/src/events/api/disconnect.ts @@ -26,5 +26,5 @@ on('disconnect', (reason, msg) => { logger.info( `Disconnected from bot API ${++api.disconnectCount} times (this time because: ${reason}, ${msg}), reconnecting again...`, ) - setTimeout(() => ws.connect(), 10000) + setTimeout(() => api.client.connect(), 10000) }) diff --git a/bots/discord/src/index.ts b/bots/discord/src/index.ts index a5a2026..f385732 100644 --- a/bots/discord/src/index.ts +++ b/bots/discord/src/index.ts @@ -14,10 +14,10 @@ for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'e await import(event) } -await api.client.connect() +api.client.connect() for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'discord'))) { await import(event) } -await discord.client.login() +discord.client.login() From 4aa138a9af8db7093ef637470fcfdea1f5341236 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 01:05:43 +0700 Subject: [PATCH 103/312] fix(bots/discord/database): fix schema for role presets --- bots/discord/src/database/schemas.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/bots/discord/src/database/schemas.ts b/bots/discord/src/database/schemas.ts index 23abaa3..e889383 100644 --- a/bots/discord/src/database/schemas.ts +++ b/bots/discord/src/database/schemas.ts @@ -1,5 +1,5 @@ import type { InferSelectModel } from 'drizzle-orm' -import { sqliteTable, text } from 'drizzle-orm/sqlite-core' +import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core' export const responses = sqliteTable('responses', { replyId: text('reply').primaryKey().notNull(), @@ -11,11 +11,18 @@ export const responses = sqliteTable('responses', { correctedById: text('by'), }) -export const appliedPresets = sqliteTable('applied_presets', { - memberId: text('member').primaryKey().notNull(), - guildId: text('guild').notNull(), - presets: text('presets', { mode: 'json' }).$type().notNull().default([]), -}) +export const appliedPresets = sqliteTable( + 'applied_presets', + { + memberId: text('member').notNull(), + guildId: text('guild').notNull(), + preset: text('preset').notNull(), + until: integer('until'), + }, + table => ({ + uniqueComposite: uniqueIndex('unique_composite').on(table.memberId, table.preset, table.guildId), + }), +) export type Response = InferSelectModel export type AppliedPreset = InferSelectModel From 197d2acea89c38e43858d52736508d449152e804 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 01:14:56 +0700 Subject: [PATCH 104/312] feat(bots/discord): update config --- bots/discord/config.schema.ts | 15 +++++++++++++++ bots/discord/config.ts | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index d94eb4f..2aec333 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -3,6 +3,16 @@ import type { APIEmbed } from 'discord.js' export type Config = { owners: string[] guilds: string[] + moderation?: { + log?: { + channel: string + thread?: string + } + } + rolePresets?: { + checkExpiredEvery: number + guilds: Record> + } messageScan?: { allowedAttachmentMimeTypes: string[] filter: { @@ -29,6 +39,11 @@ export type Config = { } } +export type RolePresetData = { + give: string[] + take: string[] +} + export type ConfigMessageScanResponse = { triggers: { text?: Array diff --git a/bots/discord/config.ts b/bots/discord/config.ts index 9749e31..f49fc6c 100644 --- a/bots/discord/config.ts +++ b/bots/discord/config.ts @@ -3,6 +3,28 @@ import type { Config } from './config.schema' export default { owners: ['USER_ID_HERE'], guilds: ['GUILD_ID_HERE'], + moderation: { + log: { + channel: 'CHANNEL_ID_HERE', + // Optional + thread: 'THREAD_ID_HERE', + }, + }, + rolePresets: { + guilds: { + GUILD_ID_HERE: { + preset: { + give: ['ROLE_ID_HERE'], + take: ['ROLE_ID_HERE'], + }, + anotherPreset: { + give: ['ROLE_ID_HERE'], + take: ['ROLE_ID_HERE'], + }, + }, + }, + checkExpiredEvery: 3600, + }, messageScan: { filter: { channels: ['CHANNEL_ID_HERE'], From fb32a04ad38be8d0836dc99259b6ef05a0825830 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 01:06:40 +0700 Subject: [PATCH 105/312] feat(bots/discord/utils): add functions for role presets --- bots/discord/src/utils/discord/rolePresets.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 bots/discord/src/utils/discord/rolePresets.ts diff --git a/bots/discord/src/utils/discord/rolePresets.ts b/bots/discord/src/utils/discord/rolePresets.ts new file mode 100644 index 0000000..a0623d4 --- /dev/null +++ b/bots/discord/src/utils/discord/rolePresets.ts @@ -0,0 +1,58 @@ +import { config, database } from '$/context' +import { appliedPresets } from '$/database/schemas' +import type { GuildMember } from 'discord.js' +import { and, eq } from 'drizzle-orm' + +// TODO: Fix this type +type PresetKey = string + +export const applyRolePreset = async (member: GuildMember, presetName: PresetKey, untilMs: number | null) => { + const afterInsert = await commonOperations(presetName, member, true) + const until = untilMs ? Math.ceil(untilMs / 1000) : null + + await database + .insert(appliedPresets) + .values({ + memberId: member.id, + guildId: member.guild.id, + preset: presetName, + until, + }) + .onConflictDoUpdate({ + target: [appliedPresets.memberId, appliedPresets.preset, appliedPresets.guildId], + set: { until }, + }) + .then(afterInsert) +} + +export const removeRolePreset = async (member: GuildMember, presetName: PresetKey) => { + const afterDelete = await commonOperations(presetName, member, false) + + await database + .delete(appliedPresets) + .where( + and( + eq(appliedPresets.memberId, member.id), + eq(appliedPresets.preset, presetName), + eq(appliedPresets.guildId, member.guild.id), + ), + ) + .execute() + .then(afterDelete) +} + +/** + * Inserts (if not already present) an entry in the database, sets the member's roles + * @returns The currently applied presets AND a callback function to run after correcting the presets in the database + */ +const commonOperations = async (presetName: string, member: GuildMember, applying: boolean) => { + const preset = config.rolePresets?.guilds[presetName]?.[member.guild.id] + if (!preset) throw new Error(`The preset "${presetName}" does not exist for this server`) + + const roles = new Set(member.roles.cache.keys()) + + for (const role of preset.give) roles[applying ? 'add' : 'delete'](role) + for (const role of preset.take) roles[applying ? 'delete' : 'add'](role) + + return () => member.roles.set(Array.from(roles)) +} From faa81f4d887eaeae809651f5b68187d033a260f2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 01:09:54 +0700 Subject: [PATCH 106/312] fix(bots/discord): clear role presets after they expire --- bots/discord/src/events/discord/ready.ts | 31 +++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/events/discord/ready.ts b/bots/discord/src/events/discord/ready.ts index 72f4bf1..e5f9a73 100644 --- a/bots/discord/src/events/discord/ready.ts +++ b/bots/discord/src/events/discord/ready.ts @@ -1,8 +1,37 @@ +import { database, logger } from '$/context' +import { appliedPresets } from '$/database/schemas' +import { removeRolePreset } from '$/utils/discord/rolePresets' +import type { Client } from 'discord.js' +import { lt } from 'drizzle-orm' import { on } from 'src/utils/discord/events' -export default on('ready', ({ logger }, client) => { +export default on('ready', ({ config, logger }, client) => { logger.info(`Connected to Discord API, logged in as ${client.user.displayName} (@${client.user.tag})!`) logger.info( `Bot is in ${client.guilds.cache.size} guilds, if this is not expected, please run the /leave-unknowns command`, ) + + if (config.rolePresets) { + removeExpiredPresets(client) + setTimeout(() => removeExpiredPresets(client), config.rolePresets.checkExpiredEvery) + } }) + +const removeExpiredPresets = async (client: Client) => { + logger.debug('Checking for expired role presets...') + + const expireds = await database.query.appliedPresets.findMany({ + where: lt(appliedPresets.until, Math.floor(Date.now() / 1000)), + }) + + for (const expired of expireds) + try { + const guild = await client.guilds.fetch(expired.guildId) + const member = await guild.members.fetch(expired.memberId) + + logger.debug(`Removing role preset for ${expired.memberId} in ${expired.guildId}`) + await removeRolePreset(member, expired.preset) + } catch (e) { + logger.error(`Error while removing role preset for ${expired.memberId} in ${expired.guildId}: ${e}`) + } +} From 467acff57a59e7ced2eaa9ca661f09620fbba1c1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 01:13:51 +0700 Subject: [PATCH 107/312] chore: install dependencies --- bots/discord/package.json | 5 ++++- bun.lockb | Bin 81496 -> 113192 bytes 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bots/discord/package.json b/bots/discord/package.json index 042cfaf..8089293 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -8,6 +8,7 @@ "scripts": { "register": "bun run scripts/reload-slash-commands.ts", "dev": "bun --watch src/index.ts", + "prepare": "drizzle-kit push", "build": "tsc", "watch": "bun dev" }, @@ -31,9 +32,11 @@ "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", "discord.js": "^14.15.3", - "drizzle-orm": "^0.31.2" + "drizzle-orm": "^0.31.2", + "simple-duration": "^1.1.0" }, "devDependencies": { + "@libsql/client": "^0.6.2", "drizzle-kit": "^0.22.7" } } diff --git a/bun.lockb b/bun.lockb index e0a218b796c7aefc377e2aab2a38ebb5cd6b706b..738b73da7ebaad5500ce0151424c223ac66268f6 100755 GIT binary patch delta 23592 zcmccdhh@bVwh4M#dcKkwRx#Cw|89^@R1BPU=bs&$+{H7+E04|B(OU4{(Q(F3Mg|a2 zXPg)z$IPw5xUq6OYrU8`0|P$;Lj$)t0|Pe$Lj$8Z0|OTWLqmfl1A{07LxYJW1A`y~ zLqj%{A7RPBAk4tf;0mR`Surq(FfcTzT0+bzDb~%+tVk^qgYp$^7#QRk7#etNAm+i$ z`De|*z{kK)-%y&Dl9`;zz`$wAz`)DE(D2O+;t)+c1_p5khK2=p3=BLB3=MDW7#IW? z7#cdE;=T3^3_=VH4d3k{4t`?~(U)4BRGOKS!VvES(eLTRz#zuJ&|nOuWuY`Xn677N z_~Zz2**!;yPfkJUZBTjJtu&H zfs=uup(6mI?qCQcEhvXV60Kw?gnuR!BEK6d&K?ReZ%s%&#G)x75Es=1KoVOn0|SFJ z14F}%a0tIDCo?Iln1Mkh0usl<5fBTM1R(sM;S3B?3=9o02fU7iSZo*tv8bfT#0(S` znZ>%LML7%%@4_J-c)}RIS&DrhtH5RJnXKaXi>CIgEv)IB7r1Nk4W5R2<%=6~yTa0w z_?Dkp8aVxKk|>{NRC303D>-Ag#1~almHhT?vM;Our#Vfa!O=K&o$He~yQB^%{Jq%n zf9t&85~6tm%C^qs?4P$^Tqq!WMB}A@PQs%9*Cy_CT6&oEsLZj=`ehF$)N@`_T${_td98g)@`7I1|ApO? zzPt?-(yipbkZot786I=^h^%wQ&zc4n`>pOfJ%t>kekF2u4eK$ueis|3if748fS zt{9;Sl6i|&W`YQ(fCpABCb%T{I5_usK+-Am!lM}_vIUNF^W#HsQ zNpr!a0SpYL(0stmz`!63Pm>*i3=Dx-Wo`x{QY)$+jUYtQLzOATDRU@@fdQ8IQ1$Qz zBjOQBMwn9|7!g)T5_}w-vx6BJEJ2P2*IQSDAq6|r`0awr2RG&oO(K*9!GkMNp=LREghPyAnEX)1oHHRD zT1ZSzl+>RbVDG`XBpgx$vV!V=PV)!`1{1Ik6Sd4avm+Q7{2*1Aq&er#2#B>{l}yZ$ zlV>^DaRx^+FvNgk|Dn7&)B4ECQjT_2WiZHGklRy01Oo#Dhz4cL3`THYkD&-6UJqkXg$7wr22DGa&@@v8 zO(QiB3mH)9dIqonD9G!e3P3c735ps}W3&b8;y#cd0|NswG|0ROP>Ux)^Ny@21Jx4|cOtE8Rn*6R$yuL0r zq?@v*dTwyxE{FV^SD%0E5!`vE=*3^D5TT5JU)N4c5}M(u z`FPv)d8>4KrrmjVDjHlK{p0_~$iT22l)#{v#i@LG)wkz!c%J3lxo~ht%Ct`sYYc4? zTxzs;3aw$C;29gSCt$88^JmHa7oS&(2YVeYE3lfhl$SRqr~692$K#^IdXwk&OH5|% z_h#HVIj}!`a>NAQ$y@uq8Fx=+oDe>lV3iROvY zrPVv6HI6WCP3l#wobBA$U$vn0(Xt&=A8J;Ji6e!^NoYubC>C)+9wjHQdF@5a!LvBM zSoUrcoYa=8J5ToS)1$jDCVo&m$j1CrD6LR(?U#!u?rJS9tJw4U)f~TwQt_B|sUmSt z?Mfz}ohnhEqCJzVGRxfJ_Cv00X46w;v+A4Vy8J~tck`ZLJN!KS-~I6BLhkD-?61%J z-8Xpq$>E~eB>wG|Pedx-O!#Hs@Cnq=VPt^B)oGBkpqORjlWl&syL?3bxRX7Kv(8oL zY)g4}uU<%a%eELv<>fNPe$E?~bUMUK9(FRgdD7~blcGJ-Lyb$9J_zq?@k(#x%sM~0 zcA7-}hA$5D`JGC&1!s7tZ;G83n_jf)l|tBr@{@D#HZ#RnpLuO<@+N#c=e`_m&9Hr! z(sr_Sw5dPHSmmsr&35a=Qk|7ZVRshlG7!bGL-5L-y?4$XGD<9({`vUbHCg&cuQV+* zY<8VDDX}`pUFc@8;e#jte@VWmuG-bAD)-(ct-Y?R^r2SoD<>ZDK>JVEC+kj^s1LG` zTK00<)Xj<$B#iyHsT5BW545uq_vqSvq%Cf(bTRYKa{GrDekQ+sd2*G_*VJ9xrca%< zvh#K59x=;gBk{tDcSx=~4{{b1vj{)Ez$qh?yKE7cbp9*9=0ogu`SOjMSHIJkczpUy zjpHApGT?V39$|uKVmc_`;=Re!CGpzLL-H^FAY`=Y+_V4ROttW+6 z`F#7#&vk};G^}64*Z2Bx|Dm?$TQ_@NnsaEWW29g2hYLCLwt~G4o^)Zj1TqndSt4~Z zUdhE3^o7gUadGdl-1|6P%j4aBgFqb@-sGL@pJhAF+WglcaczBNgogWiveC0v3Q_ZlF7Ncz5=o+P~MJW(*?(3j?SFa0TjE z5XEw8!3B9e0om6t4cIoe2^l1O*1vL*>A>p~OA=;IaHwDYnRl8<>0@E8gYygKnY8J2 zZmaK{C{mU2@5k9)(dE4EGwS@oE@lCT^Hq?EP|RX(r>%NiQNr5AG*-@FDR1_oueWW! zoe{XcOX7c+Md@e5RqTC?-J2N=RUbFKJlSND$d%OBSNtw*9#8(_u2q-WL7f4{dIlB- zP@QrOYAA?e$ro{tp2;3uZ|t4v@AG-nZk1<0_2(_zd}Xe2O36fd#_JIc?yA#!Qv44b z{CaoG6V_G_dS_OGd~HnhF4$Z(@KYxb5Z zH9z&OEBIgKa_oB6JHhK}rnT~d|H}2n2~!>WjzJ7&0;S@cAQPdO<@ed`SCVz^C9rIh z>&%Ymy0|vSxk|D2RPj=wk|P9hF@E^4w%W>jj_H7wE^Vx%XxJ zQKL=lp=g_b^9UE#B!3HkboE&u|}PA{4Vk z{ky=XdxHPhMN3;74~Agx?Nc9#etUH^cGrS)zHfc@BrBWVJ<{{$NB*9B)k1==6DKP; zt*)7RO8?T)qh<<|)z?AFb8s=i@Bpd&VPNTa8}s|A|7%gkpv+v^Sv*@*W$hx*OJ2yB zy-3@-!${+!YlzUI1x;#UU4l>g&F7kLcJ|w3a)KX_;VBi3i6OTY9LNUwIa|y?SnU7oUyk6BWxnVZnOTMy$ zYqi8b^2eOitT@DZ)Vl9oiS=eV#`!Xjye_F8?ode#{I#a+x-w5_kCM}$$tM{Z7(l%P z4p2ey7-}epVwq?j?|Nhr?-!ZuKYJZI)th~1?U~=pxkP`>@x{{)l)Fm*+~A+>*4}8- zGAnJ;Mj5Bi?qWrHpK~_{e|wwzA^g1pw5!VDz`(%p1Y{r-vrI5M>Jn9wH045Q)T_XI zuD+eq>KFGFOD?^4v7Y5h{%MVXE&I=u?9F;;nbNIj&NP4R)0vYDYaWYlQGeV0rO^DZ z!CY_TZ#r*%l2rQR>i+9sgPFkj`5DMWC}#OT?f8VF%U1^~ zXzdOC+4lL~*~G{ym3Ik81CCe}ND56BtM1O98Zu81`Tt;4bEW6YFe+oARzhj zJg&W5KkK7ecRT!glE18TiqnocofmS;(|M}*imiS+BmLG*0du2z@dBx{VWbe<;ra^V`Z#d`w20Iwio_K{Yx1NFJ#_4%%JN+GW zEK7qtm$HW>v9D4#xX{ddVVeSbfSw?`y=%;`HKIX+}$jl?=J>( zt#&ia>Xbt=_%*^{1{OWlyD_{vce*CJS?*dJH$5{!*z-_BoRNil(+j@nbMN0wSP`DO zzCIx8_}Vq@oT7V$v=7$gZ*63k>64v3SDCR=4^&jb8WeAk4Su*JU!?Wf0d{6~eute8 zF5K)qHrvkoRolI1p8ftuV}JkO_-C@HhK6jR4Eqb_*Ljn}H~jghy{_DepPl`e_}@oR_#h{@jjbwT?GiCCn|J#d&vHbhX_m4|$y|>fOk`)OYXW z#WNQgtGhZrnUd{UGOheB`{y#l2F{A~D~t^FpilreC_W$??7Hi|k9<(nsV@^VXMa0& zU;NSE`_(M=*S=;gT)*E*!%pVc{2478->(#;Bp$t& z4|XsExIOU^+1wvLmR`C2Jt%$6quc{2s~T>utiRtfW9p0xGbVgjJP_n7FuBrMa`NWr z&8PK*_a(gM6b`<@Rp7bm(c4K1Yrao8-YN+hL54IaJ|P=Cx5?r57yhn<3s$n(n(1>* zo^O}@ZezRb%A1n!ouz9{-7cOj+577Unoxpwz+X48?HN0YPaS8RJ^^Y!>6 zNDBzkp!kezuu#k6lRe!#gYTDodLtPc%6)9h@&H${-9_o2l%rEm>rMvgtqidGmpRLhHZcltcHdnAqJiWck*8?8Fi_R;F?TWjS{9*V2v=34Z@rU??n9H997 zifr&bzncqPcR4T9YQ5o+wfWkFtgCy*x4M(K)S%Ig3nQ2w%Otici(SA%;=D zeg~uLtgr8wUQY>_cqVSKnB1vqu)&a0=o_-ZtN2g%N3Of09x%m_r8Da(cl#{cxnAe) zu&H0DQh)zT`u&e;9?#|-TK&hTCT8DS9Cm%(!bQeqGFzAiBHhb@1ZfPakf# z+%&6UQOm^y|8pWtN$E$XKdXOqjHy%Ua_nrE%dTNblg}icpXroPb#%+zFj>wBu)!SQ zqWCAW!G6+GkF#Fe$4^m6n|8kM-N}oWSY8MR)i55owey=tf-UUgQxNxIWxLA<# zk^aY??KA7E7gQzONDKFf=}FwrTvfHnZEBtFGda_Dig~Tis;55_o$u#+2Wl{=ocN7w zu78M;t-_%bM{ikI6|*_kzVJ6R@|c*ol_iCt{_gE7n{3i%uND^VxTVznI7WN>G!;gT z;3bhKJaFBkbQ`S%mb1|qv@dw#pnH47wN?rT(bS)?61#A1PLf>bBGlB!TA3a}U z!}H~|j(l@Amy+X+H%aRk|FC`YrC(I+!s{RLPJOJWH1u>fpZBO>tOs}NzzvGO$Og0T zO}NTcSkI7ew*8apRp)B)ITQxhl(wao@2bYD(=xQ zMRTtRPp92y#O?Pk{c_mkjLGM-FP4OBwF)n_Nnox0cd@A7I|)*aHh`z6{v#Wlm&KH< zI#*!t2VN%8+%ucq)w}dK&7!V$`{sO2X^<5E$d+??g9qQd!^QWv%=KS!qR%3Gi?yQ9 znfknPeVr&In z{#svm9{=&l`l@T3Q;5Cad*|$xv-$j|E)vf8H0OTAsq3Y6ec+IQw160)W7Z(ESvF+y zAAg!$Z*;Ta#nEr?8Z1NvU33Cwrnl^fTi#|_u>SSw;9q9n_xWEuxoDcb^JQ(-wKKN# zT--ma)aA(D-KI8f?YEH(22JpR3u3V_s3c3Odhsbzh^EvUUndN z3d3)kxOCZ!-|wDyJt^MWa_;d7v$wGb zS+5R=o7Bkri1~`Zz8=tw5vbk=HC|a@=7Q>fmJqkxV;n`=Yd%Pu+S*hp?zxlOwtLbM zL3OvSzgd-j_VR7pa!>un8+81xeZ(p125~Srv9ZDo z1~u7cu{Q+zW~^FK`Ofde-6KZ9OI|O%ExUcKbwy0ejJg2p?fLh8>)&=idK~nlblKdk zj;U3Vbsy3-?y1JshZ}OtkY5Zjm;u~BU_&-ITQ~d*m$Yz0=9ye^d)|a)G2JO2+m)F7 z7X7|X>pmf)A?{y5mdkZi||?BI#U z4@>7vb{43+&6VVOFsy!!xm@o#PUY1pc29N(PH1}+H*@c~l;9t1&cBTJlsvpzQn^)s z%G3|?Qjc%-*<9$+Xa^YvD<^o64GyeZBlU9qz9qBb*u5mZ6;iY21S^TB|Gw$BGtmFX ziae!-5_^sH+shXne0=ZK(y2*azx3C13tX!IU(grg+?w~w^)!-;d65l%w_*9z7bWKt zmc$AEdNZHvYIpI)y)%C61l#Q2ukk1&J6Ex;G~j>z<(K)V>cTi4ZgF>A!CP5A<>pRa ziQf0uTJ}#zG8fc*1BEUKvl!+i@b9dju5#f`n0lwI6x!OgerDU*bw}KF zTU9Rm#NIop@cJL`D@W$Q0H5{~9}WE;e_>8yo51a%VUA=lXn-ALCIrg8pv-GBi;m`H&W_qd5Shcw29P`|7XZBwG zwdm@r&(#`^NCpc*4F)^7JSj{^WBtr&!X8};)t7Y|0%oqeSA9idNnzrQNd{XdNv7u3 zn>SC%nt8)spZ}o!lxce+QfKFHemeX7b=B5tg^4%Qkqj0_Hu#^>%mt?kF2u9Bb5{I) zbkX92dEs~QEgLlLU5xcoh5uCdb57mFzDeTitTPpB%&dPNRa$(3z4)r}wHdP=%daPh zgVGly5!W*?fX4Dbt_5M1i}Fbm#eTg$cnnLZ~MWqt%zDgG| z1Ma%V_zLV2_@`I@o&VvNJ(+=%!dwmrPF@~xa9-!c`xDApGj}krE|)I-^q1?_BMJBN z51=I}5Enz1r+^#_!Yp2YtsVY496n%a@#>itO9;#IZ8Z_Dx1`+Eq}{8_OEV=e3#vIu zdH(vJZN}(C3pGfsAz5yqs#IC<8L zFeX*T$)9F~F{v?b_L}*QfAR%B7GBsclBCQ$z2wP^3;V&Mu$>jv4h#(3lP}J!-kh)` zgb|07!C58d$qlo+K+;SELLW^VYWGLRV4RjkR*giSdzlHxnTQxChhprsrjHpoa&%{t$+fl11h14A<-0|RI@_yKfi5i|<;Z1Tk`tLs6_T3Q(y7}^*a7}^;b7&;gk z7&;jl7`hl47`ho578Lm49j11PgrFfuTJ z7C9L*GBD^*X1pHHSH;M{0P2l{R;3v;PL8|oRA0)-z@W~^zyKO_1MQM=gr<*`3=9mQ z9)u|)1A`eO0|TgT1XW{}j0_A`j0_Ccj0_Amj0_C6j0_B*StNT#1_lR41_n^g;l#+m z;LOOt;KInj;L6Cr0BUu*GcquEFfuTBGBPlL(iJEfEe5S11ErHC3=9m*7#J8jKvBoQ zz;M9;ltz@eTo}L`2pA+7H#6NRVy*|}x)TiGJqMtT1q@CM3=BIMAgdL&F)%RbGcYhf z@+eKndK6Rj7#J9iF)%P3XJBAB$iTp`pMim4Dgy(XgC|x1qD@epq3yg2ZKso5C-LFP|gPBa8OPM<#S&1>u&_vJBRL>Z; z5fru)O$lV8v7w$318l44W$0ctB~axBt_E`QQxc0a7#I#bDzlwy>34;RG0sHK&|J^Z zv;edZkb!~Wsq>?Kq3uFKB8+yn7NEL`fgw9DwF0DDmN$msspd>K5k@T#mw|zyC^a#; zgn@x!!t)#VBqto10@7q?0g}wFEKbd-WMJU+QFu~%=#DT#Pi|##VU7_4gGqe<(I2m$ zh=TMOfwV9b7v|`J_U@HG{Nuf=nYjogQ*Q{9$}CAWW?0yF(78l+T0cn26f9GaUtE%2 zlv=E0^l|kKMVnb=>y36jaZl4gw zp+`DG=DIm#r{%}5I;+}P7XAd8W)61tQ|Lb5y=S(%am=iDi$-YrVh)~^X($WPVO2fX z+6U5Ppl6|Hz`$Yw-T>1Oy@4YlHM1p0gfY%g&qU9VfkDgyvZK<~)uX3Ige3!_$579Z zLCXTNrLrMDrk{QB1yzs?B(&|JG6KHw_0vBF&9g>0Ap|P5=trIb%L%DmgiNLdWM?L8 zx>xpnZtaI4;~){y2$k{VXP6YAwy_W-V*qj+!%PbX21y2nhOYfP1#aJY+zOI`=-C97 z5z=%y>>zk_1vr2p$z<}n&n7akO~~6fJ`G#0x`CSs{SyF4$?VPZ7UGd9<=FlKPIoZRq5!VI=m`b5;TCsF6({xLBc>zV3- z90J<}y;aDh_u$hbVoZ#NP?-kH$>+XEFqv3Re(}WzVVlt8gs&WCxNU^(!hXc-?)TBX zM~I0r&REY<547FQa`L>d4NU*6Co6n|IUkSHBP=K9eUp&EbYPs3o}r!r0|SrE_k2rZLJ1W-?jz1TiF#NA9k%b22~_wPf;(e3r+$CAd4^^ zS7)GSXrX6h0NaZW+lUKxwyBkzI&4cW!Zg^vb=am|ke+%yGmu5F4dJluuq=#F zdlBV=Iv{U=+$sR!?NMo7Vq1b}Xco{W%z9U1`JGL4XdoGZZ40NYfJ zkb#{%0NZwrkb#{|P!HRgjgW#JUjW;?jgWyIZ~)ugjgW!uzlUu;2B&UgJrf27^dv06 z$l?j9w7`4!L8lSGb}oaBv(z&*V!)al!7^BrBUlD&asU5*5iEl>If7-dCP&M9P;euq zM?`W2Yr>iw!7^BrBUlD&asU5*5iEl>If7+i$3VdLz%wy| z>Jm_`3p;K>Uwb2XuX#PF5QH>eVMjW^_OnA}3>X;ZKo5O@?RW>5>gJ&G9(KM&ICOJ8 zSdX!up#cLf8B>N^(4!>4`{?WKx4MH|X8@`r7+|MOz_!|hjWYmcBiMNqu+8|Oyke|p zXvx3;JFx<`T^}I>JJSNTp&uawJM}`ptp33S(4K#U6zpIO*s%ZznaDs;XPBX(9C~m7 zLI!ra2JC17gbeJI3)miiu!oF5O{O&=;6_`01MC<8u#_PvP;m$MLFj24u%iaR5dp4V zVdra@Lk|}K7eL@T6m}v9>^K6j9z#%=g3j!y2c0y-3q7#_s>#rR0f#0d1_s!H9vMHIx$@xVodRfIV!Th4!;>_HFoK)SE(xSwY%;^WD8D$kwg!1$B zb@kCiraPE0s@5YJqOYru#M3p<1NABOO46$wN-7Idi}efAQ!5Jcb&CsgGD}jSV&IM- zlnv>|C8d^>q!vL{89P)YmL%$B=0pAAP?lPhoSLH#>%2MC=NF{rm89mR=BAbuRq7`e zWICkgq$ZaXWhR4+&@V^_TUL;hn3q~ooSc}Gs-Kcrl9-fOoa#`LnUa*8lV6&mUz}Q0 zmRgjPT3ifu3YsGl%Zqi3Q?j9s&@DY;0OiVo|Dod45rL zYEiK+I1C)}3v^TS(lhf?Q;Rb5()IHTK(5RzNu7R?iBV?zGDF7CEYnx9FiLyFLNYtE z1SwpzGfQ+0^gw-lP`E~MwtNYa{9XZ z2!*u7Mt?Ne>Euq|&ss)FR!qqWoNh zy6GD^7-gj3R)DeuoTF=?XEyzz2&1ANl2(0PeI%Z)p`M|h5!m$1Jbh?-dJS3s| z6sXymiN;27qw-Qql8RH1l%?oH%tBF>n3qzNpP7QQ1T-%6VUC0d!$cu2%E`~qOUH0i zQD$*+8HQ+XW*}NMk#%Un{DB+`pz;io1i|5qtX>1A9w{dEb)o4Fw`!!c zps%Zs#KUSfBtF5Z2_8OR9v;=;^axiC=HXEdb}3vnn1@w0EaWT9OpwC8!psD#YFH>k zR3q{5s!lA*MU5e3)oQ5W4a(W@;0AH9sznqG`nviE4pz00um%|g4-OCqt6FFpfp`zW zK~}4R>RyOaNLoP)HMmN6i3lpLVWlHhGa+t;m9y+t7fF4 z9adZGg2oCBKqb7Lt%07Qk%EmOs4{?63`jZ*Ae}alQh4!~m|39)aH8?uqs=+)ws$t;*(F^8bRSgSyNV-PiVO0$ab%<&t z9$wXuoB&e|%?q$FA*PJMP>U#5Fv=JVwU7`16)W(-196aD40i*fSV7c6(2@gYF1+M` zmn-lh5tmAwWekRy5Vt}?7tTg@Dcp95EF@&%Y^*BbMFA+6z={PVl?sC3;X;syQH*cnBM zd5OA^Ze~`oE~qDDW->idf>EX(d>AM=PLhfXb3h$JP(fCbUX_wslA2tio0FN8?2wn5 zpQl??tPkp|z|2I%5}F7&nZv{&p#qAN^t@6`!CZ^d;vASTJT7%}i_4N>q7W0nhUy}j zG5v!cqmCgoFp$;cmKNuL{R3@?o0;f>grGi~ZfMA;ppQjcdR{4-uJpW8WKE!uC@#!_ zwsSx}0=W_;*?|PGX@NKotOeO|FacyO+9*x~sX%rZl#i@I6RH6k9pG>Rr6SbE8)#Sz z>~&RC<&Z=U)eAD+Ko71|6gk#GVUwSjqH8dnpN~;R0+BXAb|HAv?+G)iO2d)Gf)^g>?DA2I(f{D>YL_xJ8 zx=LLWJp(;MP?tc#4PhWiCCHgz({({zUvM)5?7)=#+|-KX)Pj=C{JiqiB;DeYqSVCP zVqIjr^|48SQX3ZapuY8V0aZptbqwP{B{L{YkgbGv+cm-dMF|g3WG0p*q6U(#$@BxP zjLMp@42crp`nvikd{B`FD!7zj=@Lm695|>NKtj{wWf|2aph*HWU;rxAK*Jf}B^1-A znlYwsXEA5I!OV4G8))eo1H*@H)9q{-K?@(Ivs*JNa#?JLi2v9Q9(tSZYt8tWOJfH_ z&|?Q!@$?uQMis6JP{9p5Kq}L?UO@Rjc7T>HX~}_RJozX6Ui9Mq3cdoz66Xxp6c+>N ziQN`E!3K$g#x0NL7e9_tI&9Mbm7B2>wAfH)dcQ5BqQt^z1_lMtv1uTc3=9nY=873b MXS{SK7tWpl0Qix?`~Uy| delta 3125 zcmZ4Sh3&>4mI-=V7efSIY)QKy_u^~W&4M4tmVW5H9KOZPTmRLutB>AJeARHmmXQGj zt};xFkYi@N%&@U?J8S(7GX@4O28M>|<_rv?3=9q4<_rvi3=9oTP=3BS1A{OFLqiOd z=Cx#C5Mf|wurX&~;AdcHC@I#>&8$c*(uMLZtQZ*N85kOrtsv&X%n`L>VBlk5XeiA~ z$xO~ zChu~z<7~5FU~pkzXkeav(9xXrlm!EW^W?v_)=Y|)ldGKUIMXc|7_1o>8W<-xs+e;w zvSeVeU|?uqn|x5oobwh`j%hNZggIxp6$3*Q149FZx6z7$!2_(%QP`aIrxgQ(`{Y^` zYbFQl$yP3QOs&?Ft6c0jcUdzqm_t-a>QDX<;lcUWnt{QAfuVsFY~L&!1_l$b!yEO@ zInUWJF!+I0H_DlF%Gfe6c)?UMrP)rlin8O}X3M}31960^Ig^mxAhyk}rw042u{pr~SCV8BMRGcYiGXJBApVqjqS z2~~$obAseRh7d>BGk`R3Gcqu+GB7X*FoKf}gA~*NVrY;%AFwPA*&@K3QTt@8qlNy%~2; zR@@Li`NVqO$*mi_8TU@UxFLLU#RlHVrW?H(_fKxz7(Q8JBk$y=8@(A1PBz>WKKaH* z-pNxpc{3iK{BTqF|elU+A^Gaj8hadY@&i_JWfYnpwSjx$U?bUtkIi_N@~RWEom zon)9?bRlf=j4iyAuU_zGI?XUy>0;PqkFC6uTQ7Puon@GO>0;RAA6t1Rn_lu}I?ph< z=~CF_729|xKfUD5bdh1Q(d96vOAM2{E{8E)W|;iwav0MUhRs%2-tkXvxD_zjK!k-U z#%A-gSH;ScgatP9f8WWt`NFdfMlfsgg=gQmJuX8Mw$BxY>0&C3cJ&Ml|NcV&Na6%E zn{b0T3=9k~J}4zBLFGEcXm5b{Z-MO8r?-u`^IHHU@Ap!oX0^ zz`$@8DhSGdlb{OELB%*27#QY2#m+;;K-p?80|Ns{?gA(;GC;D{LIwtKmb(O12lB*X zs2IrGpd7@*z`(E+s{RTC0|P%uumZ$lU|=|NoniWHRYuk6M^qVoIC&W%(vPNVsxht( z17%=AMg|5UMg|69Mg|5EMg|5^Mg|5kMg|6PMg|56Mg|5+Mo5YC1R6A;pm@%}!0>{B zf#D?s1H&r@28P!R3=D4=7#Q9%FfhDhnC_;|I7yjWME)e$-uw>3JH+UK|TffYzYGc!!ia2 zh9B1?uVq7Y1-u!ElUW`y?&KBIbISfD;1)!!`y620aD_h9e9N3`ZFl81^$T zFzjVuV3^9lz%Y-2fnf&&1H*0x28LY>3=BIN7#Oxg#Xx%YA*lta+XI!?hw2BZ0hxOk zst%-P7bxlvF)%P3WME)8z`(%Z3Uz=blI0+U$ZFi6J^<+fIr=yQ0|O}PK#>cI^o!8M z1WIL~)CWqHpwtRV#h?TQO6Q;i14>8>?tqdJ0|UdFpA6e?8!(<0n|{lk(N7B2K6%?3 zT;?fwW3m!soS})Hp{btnbQ5nz6EkQ#qG9{Sr(w%gH*hmC#u@4v>KQUHz*;2Fx&Ig_ z-@kW`i80Pd&sfiZfx*Un`T<`?iRs(C8BLhH&8L6xVU&=8HE=4n-MM<>zET9(APYSs z28Mj|=?XrK5@xWb%vK?j-h)q%h%qr5>Y3^pGcYi~8c(ZlroYPl(Jjx!7-yho0^fw^0%50`9 zL^4V+wcAX$iexlm^0S+s7s=?uG{tWEo=8Rs?jO*`L&YzK>GvZU4|1*e1<8_UthTR; zV*GE$mRX#fUz9Sva1*24_HC0HzpzZt*u8rOfDowwzjZuNC04iu;GyPs9qx5vq?TnAP5^NwU8*D%-Ri~@(U{vHf z0Tp}!Rhhek(VI&F+D6y0ojyyIQB7{qoSYT>lYTFH@qPtgfn$kthHHvT0#r?gEyy$_ xp`-c5kE4_h+iZY}pRk?ow3E?Jg2jP>K>^fMgLslbMYdP&W!{&b$%VHj001TAvPS>_ From c0fa2fe1c36acdc7c52cde277aa7da867065f55e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 01:15:12 +0700 Subject: [PATCH 108/312] feat(bots/discord/commands): add `mute` and `unmute` commands --- bots/discord/src/commands/moderation/mute.ts | 65 +++++++++++++++++++ .../discord/src/commands/moderation/ีืunmute.ts | 44 +++++++++++++ bots/discord/src/types.d.ts | 5 ++ bots/discord/src/utils/discord/embeds.ts | 31 ++++++++- 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 bots/discord/src/commands/moderation/mute.ts create mode 100644 bots/discord/src/commands/moderation/ีืunmute.ts diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts new file mode 100644 index 0000000..cbecd9c --- /dev/null +++ b/bots/discord/src/commands/moderation/mute.ts @@ -0,0 +1,65 @@ +import { SlashCommandBuilder } from 'discord.js' + +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { applyRolePreset } from '$/utils/discord/rolePresets' +import type { Command } from '..' + +import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { parse } from 'simple-duration' + +export default { + data: new SlashCommandBuilder() + .setName('mute') + .setDescription('Mute a member') + .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to mute')) + .addStringOption(option => option.setName('reason').setDescription('The reason for muting the member')) + .addStringOption(option => option.setName('duration').setDescription('The duration of the mute')) + .toJSON(), + + memberRequirements: { + permissions: 8n, + }, + + global: false, + + async execute({ config, logger }, interaction) { + const user = interaction.options.getUser('member', true) + const reason = interaction.options.getString('reason') + const duration = interaction.options.getString('duration') + const durationMs = duration ? parse(duration) : null + + if (Number.isInteger(durationMs) && durationMs! < 1) + throw new CommandError( + CommandErrorType.InvalidDuration, + 'The duration must be at least 1 millisecond long.', + ) + + const member = await interaction.guild!.members.fetch(user.id) + if (!member) + throw new CommandError( + CommandErrorType.InvalidUser, + 'The provided member is not in the server or does not exist.', + ) + + await applyRolePreset(member, 'mute', durationMs ? Date.now() + durationMs : null) + + const embed = createModerationActionEmbed( + 'Muted', + user, + interaction.user, + reason ?? 'No reason provided', + durationMs, + ) + + const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) + + const logConfig = config.moderation?.log + if (logConfig) { + const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) + if (!channel || !channel.isTextBased()) + return void logger.warn('The moderation log channel does not exist, skipping logging') + + await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) + } + }, +} satisfies Command diff --git a/bots/discord/src/commands/moderation/ีืunmute.ts b/bots/discord/src/commands/moderation/ีืunmute.ts new file mode 100644 index 0000000..0016e9d --- /dev/null +++ b/bots/discord/src/commands/moderation/ีืunmute.ts @@ -0,0 +1,44 @@ +import { SlashCommandBuilder } from 'discord.js' + +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { removeRolePreset } from '$/utils/discord/rolePresets' +import type { Command } from '..' + +export default { + data: new SlashCommandBuilder() + .setName('unmute') + .setDescription('Unmute a member') + .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to mute')) + .toJSON(), + + memberRequirements: { + permissions: 8n, + }, + + global: false, + + async execute({ config, logger }, interaction) { + const user = interaction.options.getUser('member', true) + const member = await interaction.guild!.members.fetch(user.id) + if (!member) + throw new CommandError( + CommandErrorType.InvalidUser, + 'The provided member is not in the server or does not exist.', + ) + + await removeRolePreset(member, 'mute') + const embed = createModerationActionEmbed('Muted', user, interaction.user) + + const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) + + const logConfig = config.moderation?.log + if (logConfig) { + const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) + if (!channel || !channel.isTextBased()) + return void logger.warn('The moderation log channel does not exist, skipping logging') + + await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) + } + }, +} satisfies Command diff --git a/bots/discord/src/types.d.ts b/bots/discord/src/types.d.ts index 24ab764..9beda5e 100644 --- a/bots/discord/src/types.d.ts +++ b/bots/discord/src/types.d.ts @@ -3,3 +3,8 @@ type IfTrue = IfExtends type EmptyObject = Record type ValuesOf = T[keyof T] type MaybeArray = T | T[] + +declare module 'simple-duration' { + export function parse(duration: string): number + export function stringify(duration: number): string +} diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index 77eb72c..602c9b8 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -1,5 +1,5 @@ import { DefaultEmbedColor, MessageScanHumanizedMode, ReVancedLogoURL } from '$/constants' -import { EmbedBuilder } from 'discord.js' +import { EmbedBuilder, type EmbedField, type User } from 'discord.js' import type { ConfigMessageScanResponseMessage } from '../../../config.schema' export const createErrorEmbed = (title: string, description?: string) => @@ -40,6 +40,35 @@ export const createMessageScanResponseEmbed = ( return applyCommonEmbedStyles(embed, true, true, true) } +export const createModerationActionEmbed = ( + action: string, + user: User, + moderator: User, + reason?: string, + expires?: number | null, +) => { + const fields: EmbedField[] = [] + if (reason) fields.push({ name: 'Reason', value: reason, inline: true }) + if (Number.isInteger(expires) || expires === null) + fields.push({ + name: 'Expires', + value: Number.isInteger(expires) ? new Date(expires! * 1000).toLocaleString() : 'Never', + inline: true, + }) + + const embed = new EmbedBuilder() + .setTitle(`${action} ${user.tag}`) + .setDescription(`${user.toString()} was ${action.toLowerCase()} by ${moderator.toString()}`) + .addFields(fields) + + return applyCommonEmbedStyles(embed, true, true, true) +} + +export const applyReferenceToModerationActionEmbed = (embed: EmbedBuilder, reference: string) => { + embed.addFields({ name: 'Reference', value: `[Jump to message](${reference})`, inline: true }) + return embed +} + export const applyCommonEmbedStyles = ( embed: EmbedBuilder, setThumbnail = false, From 8c7620cab844073dea53cca7e3e48b836dd0d324 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 01:16:32 +0700 Subject: [PATCH 109/312] chore: trust `esbuild` as a dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8339cfa..906f196 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "turbo": "^1.13.4", "typescript": "^5.5.2" }, - "trustedDependencies": ["@biomejs/biome", "lefthook", "tesseract.js"], + "trustedDependencies": ["@biomejs/biome", "esbuild", "lefthook", "tesseract.js"], "patchedDependencies": { "tesseract.js@5.1.0": "patches/tesseract.js@5.1.0.patch" } From 399dca71538fe5c8831977694a97058254a17578 Mon Sep 17 00:00:00 2001 From: Palm Date: Mon, 24 Jun 2024 08:43:51 +0700 Subject: [PATCH 110/312] fix(bots/discord): messed up file name for `unmute` command --- bots/discord/src/commands/moderation/{ีืunmute.ts => unmute.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bots/discord/src/commands/moderation/{ีืunmute.ts => unmute.ts} (100%) diff --git a/bots/discord/src/commands/moderation/ีืunmute.ts b/bots/discord/src/commands/moderation/unmute.ts similarity index 100% rename from bots/discord/src/commands/moderation/ีืunmute.ts rename to bots/discord/src/commands/moderation/unmute.ts From 7fdf8c0dc722e21fe5a3ad6ef8d3a306ef85f532 Mon Sep 17 00:00:00 2001 From: Palm Date: Mon, 24 Jun 2024 08:49:38 +0700 Subject: [PATCH 111/312] fix(bots/discord/commands/unmute): fix option description and embeds --- bots/discord/src/commands/moderation/unmute.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/commands/moderation/unmute.ts b/bots/discord/src/commands/moderation/unmute.ts index 0016e9d..99a6d27 100644 --- a/bots/discord/src/commands/moderation/unmute.ts +++ b/bots/discord/src/commands/moderation/unmute.ts @@ -9,7 +9,7 @@ export default { data: new SlashCommandBuilder() .setName('unmute') .setDescription('Unmute a member') - .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to mute')) + .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to unmute')) .toJSON(), memberRequirements: { @@ -28,7 +28,7 @@ export default { ) await removeRolePreset(member, 'mute') - const embed = createModerationActionEmbed('Muted', user, interaction.user) + const embed = createModerationActionEmbed('Unmuted', user, interaction.user) const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) From 39d5b3a479b4d856aabe12cc31177c24f88ae23e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 18:32:04 +0700 Subject: [PATCH 112/312] feat(bots/discord): add `moderation.roles` config to be used in `moderation` commands --- bots/discord/config.schema.ts | 1 + bots/discord/config.ts | 1 + bots/discord/src/commands/moderation/mute.ts | 3 ++- bots/discord/src/commands/moderation/slowmode.ts | 3 ++- bots/discord/src/commands/moderation/unmute.ts | 3 ++- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 2aec333..9be18e2 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -4,6 +4,7 @@ export type Config = { owners: string[] guilds: string[] moderation?: { + roles: string[] log?: { channel: string thread?: string diff --git a/bots/discord/config.ts b/bots/discord/config.ts index f49fc6c..c254919 100644 --- a/bots/discord/config.ts +++ b/bots/discord/config.ts @@ -4,6 +4,7 @@ export default { owners: ['USER_ID_HERE'], guilds: ['GUILD_ID_HERE'], moderation: { + roles: ['ROLE_ID_HERE'], log: { channel: 'CHANNEL_ID_HERE', // Optional diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index cbecd9c..3724e97 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -4,6 +4,7 @@ import CommandError, { CommandErrorType } from '$/classes/CommandError' import { applyRolePreset } from '$/utils/discord/rolePresets' import type { Command } from '..' +import { config } from '$/context' import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' import { parse } from 'simple-duration' @@ -17,7 +18,7 @@ export default { .toJSON(), memberRequirements: { - permissions: 8n, + roles: config.moderation?.roles ?? [], }, global: false, diff --git a/bots/discord/src/commands/moderation/slowmode.ts b/bots/discord/src/commands/moderation/slowmode.ts index fc3adfe..eb5534d 100644 --- a/bots/discord/src/commands/moderation/slowmode.ts +++ b/bots/discord/src/commands/moderation/slowmode.ts @@ -4,6 +4,7 @@ import { durationToString, parseDuration } from '$/utils/duration' import { SlashCommandBuilder } from 'discord.js' import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { config } from '$/context' import type { Command } from '..' export default { @@ -20,7 +21,7 @@ export default { .toJSON(), memberRequirements: { - roles: ['955220417969262612', '973886585294704640'], + roles: config.moderation?.roles ?? [], }, global: false, diff --git a/bots/discord/src/commands/moderation/unmute.ts b/bots/discord/src/commands/moderation/unmute.ts index 99a6d27..41c37df 100644 --- a/bots/discord/src/commands/moderation/unmute.ts +++ b/bots/discord/src/commands/moderation/unmute.ts @@ -1,6 +1,7 @@ import { SlashCommandBuilder } from 'discord.js' import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { config } from '$/context' import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' import { removeRolePreset } from '$/utils/discord/rolePresets' import type { Command } from '..' @@ -13,7 +14,7 @@ export default { .toJSON(), memberRequirements: { - permissions: 8n, + roles: config.moderation?.roles ?? [], }, global: false, From 3e07429664f7dbb6ce653083e0adb1a232737fde Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 18:45:39 +0700 Subject: [PATCH 113/312] fix(bots/discord/commands/mute): use existing `parseDuration` util function' --- bots/discord/src/commands/moderation/mute.ts | 4 ++-- bots/discord/src/types.d.ts | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index 3724e97..f4ebd28 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -6,7 +6,7 @@ import type { Command } from '..' import { config } from '$/context' import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' -import { parse } from 'simple-duration' +import { parseDuration } from '$/utils/duration' export default { data: new SlashCommandBuilder() @@ -27,7 +27,7 @@ export default { const user = interaction.options.getUser('member', true) const reason = interaction.options.getString('reason') const duration = interaction.options.getString('duration') - const durationMs = duration ? parse(duration) : null + const durationMs = duration ? parseDuration(duration) : null if (Number.isInteger(durationMs) && durationMs! < 1) throw new CommandError( diff --git a/bots/discord/src/types.d.ts b/bots/discord/src/types.d.ts index 9beda5e..24ab764 100644 --- a/bots/discord/src/types.d.ts +++ b/bots/discord/src/types.d.ts @@ -3,8 +3,3 @@ type IfTrue = IfExtends type EmptyObject = Record type ValuesOf = T[keyof T] type MaybeArray = T | T[] - -declare module 'simple-duration' { - export function parse(duration: string): number - export function stringify(duration: number): string -} From dc4863dc208b3fede4d4def323306ab58daffe04 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 18:46:27 +0700 Subject: [PATCH 114/312] feat(bots/discord/commands): add `ban` and `unban` commands --- bots/discord/src/commands/moderation/ban.ts | 49 +++++++++++++++++++ bots/discord/src/commands/moderation/unban.ts | 41 ++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 bots/discord/src/commands/moderation/ban.ts create mode 100644 bots/discord/src/commands/moderation/unban.ts diff --git a/bots/discord/src/commands/moderation/ban.ts b/bots/discord/src/commands/moderation/ban.ts new file mode 100644 index 0000000..ee5ec14 --- /dev/null +++ b/bots/discord/src/commands/moderation/ban.ts @@ -0,0 +1,49 @@ +import { SlashCommandBuilder } from 'discord.js' + +import type { Command } from '..' + +import { config } from '$/context' +import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { parseDuration } from '$/utils/duration' + +export default { + data: new SlashCommandBuilder() + .setName('ban') + .setDescription('Ban a user') + .addUserOption(option => option.setName('user').setRequired(true).setDescription('The user to ban')) + .addStringOption(option => option.setName('reason').setDescription('The reason for banning the user')) + .addStringOption(option => + option.setName('dmd').setDescription('Duration to delete messages (must be from 0 to 7 days)'), + ) + .toJSON(), + + memberRequirements: { + roles: config.moderation?.roles ?? [], + }, + + global: false, + + async execute({ config, logger }, interaction) { + const user = interaction.options.getUser('member', true) + const reason = interaction.options.getString('reason') ?? undefined + const dmd = interaction.options.getString('dmd') + + const dms = Math.floor(dmd ? parseDuration(dmd) : 0 / 1000) + await interaction.guild!.members.ban(user, { + reason: `Banned by moderator ${interaction.user.tag} (${interaction.user.id}): ${reason}`, + deleteMessageSeconds: dms, + }) + + const embed = createModerationActionEmbed('Banned', user, interaction.user, reason ?? 'No reason provided') + const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) + + const logConfig = config.moderation?.log + if (logConfig) { + const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) + if (!channel || !channel.isTextBased()) + return void logger.warn('The moderation log channel does not exist, skipping logging') + + await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) + } + }, +} satisfies Command diff --git a/bots/discord/src/commands/moderation/unban.ts b/bots/discord/src/commands/moderation/unban.ts new file mode 100644 index 0000000..0e9946f --- /dev/null +++ b/bots/discord/src/commands/moderation/unban.ts @@ -0,0 +1,41 @@ +import { SlashCommandBuilder } from 'discord.js' + +import type { Command } from '..' + +import { config } from '$/context' +import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' + +export default { + data: new SlashCommandBuilder() + .setName('unban') + .setDescription('Unban a user') + .addUserOption(option => option.setName('user').setRequired(true).setDescription('The user to unban')) + .toJSON(), + + memberRequirements: { + roles: config.moderation?.roles ?? [], + }, + + global: false, + + async execute({ config, logger }, interaction) { + const user = interaction.options.getUser('member', true) + + await interaction.guild!.members.unban( + user, + `Unbanned by moderator ${interaction.user.tag} (${interaction.user.id})`, + ) + + const embed = createModerationActionEmbed('Unbanned', user, interaction.user) + const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) + + const logConfig = config.moderation?.log + if (logConfig) { + const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) + if (!channel || !channel.isTextBased()) + return void logger.warn('The moderation log channel does not exist, skipping logging') + + await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) + } + }, +} satisfies Command From 9dea90712e54ee0940bdfe52332c08f1dcbabb2c Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 20:19:57 +0700 Subject: [PATCH 115/312] chore: remove unused dependencies --- bots/discord/package.json | 3 +-- bun.lockb | Bin 113192 -> 112792 bytes 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bots/discord/package.json b/bots/discord/package.json index 8089293..6b562b2 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -32,8 +32,7 @@ "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", "discord.js": "^14.15.3", - "drizzle-orm": "^0.31.2", - "simple-duration": "^1.1.0" + "drizzle-orm": "^0.31.2" }, "devDependencies": { "@libsql/client": "^0.6.2", diff --git a/bun.lockb b/bun.lockb index 738b73da7ebaad5500ce0151424c223ac66268f6..72a95e3009309189b46a303c752d61e225883a99 100755 GIT binary patch delta 7978 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 aW?)bNjgCW`8I3c&w;nv9=`^|U)&u|(x32L3 delta 8078 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 Date: Mon, 24 Jun 2024 20:20:45 +0700 Subject: [PATCH 116/312] fix(bots/discord): follow-up if reply is already sent when error --- .../src/events/discord/interactionCreate/chat-commmand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts index 0cfb65e..79d262a 100644 --- a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts @@ -72,7 +72,7 @@ export default on('interactionCreate', async (context, interaction) => { await command.execute(context, interaction) } catch (err) { logger.error(`Error while executing command ${interaction.commandName}:`, err) - await interaction.reply({ + await interaction[interaction.replied ? 'followUp' : 'reply']({ embeds: [err instanceof CommandError ? err.toEmbed() : createStackTraceEmbed(err)], ephemeral: true, }) From 49ce9a7ca3d8558b73a9b94dfe7a01d809db6fff Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 20:22:26 +0700 Subject: [PATCH 117/312] feat(utils/discord/embeds): allow adding extra fields for moderation embeds --- bots/discord/src/utils/discord/embeds.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index 602c9b8..0a14fc2 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -46,15 +46,18 @@ export const createModerationActionEmbed = ( moderator: User, reason?: string, expires?: number | null, + extraFields?: EmbedField[][], ) => { const fields: EmbedField[] = [] + if (extraFields?.[0]) fields.push(...extraFields[0]) if (reason) fields.push({ name: 'Reason', value: reason, inline: true }) if (Number.isInteger(expires) || expires === null) fields.push({ name: 'Expires', - value: Number.isInteger(expires) ? new Date(expires! * 1000).toLocaleString() : 'Never', + value: Number.isInteger(expires) ? `` : 'Never', inline: true, }) + if (extraFields?.[1]) fields.push(...extraFields[1]) const embed = new EmbedBuilder() .setTitle(`${action} ${user.tag}`) From 4c6ad11be30c1d6af97c4ae40fc62d05fa7bdd57 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 20:23:23 +0700 Subject: [PATCH 118/312] fix(bots/discord/utils/discord/rolePresets): correct property access for presets --- bots/discord/src/utils/discord/rolePresets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/utils/discord/rolePresets.ts b/bots/discord/src/utils/discord/rolePresets.ts index a0623d4..34c99a4 100644 --- a/bots/discord/src/utils/discord/rolePresets.ts +++ b/bots/discord/src/utils/discord/rolePresets.ts @@ -46,7 +46,7 @@ export const removeRolePreset = async (member: GuildMember, presetName: PresetKe * @returns The currently applied presets AND a callback function to run after correcting the presets in the database */ const commonOperations = async (presetName: string, member: GuildMember, applying: boolean) => { - const preset = config.rolePresets?.guilds[presetName]?.[member.guild.id] + const preset = config.rolePresets?.guilds[member.guild.id]?.[presetName] if (!preset) throw new Error(`The preset "${presetName}" does not exist for this server`) const roles = new Set(member.roles.cache.keys()) From 7e8270f7d260322e1950e058b221ab088bd595d0 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 20:25:16 +0700 Subject: [PATCH 119/312] fix(bots/discord/utils/discord): add `moderation` module related functions --- bots/discord/src/utils/discord/moderation.ts | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 bots/discord/src/utils/discord/moderation.ts diff --git a/bots/discord/src/utils/discord/moderation.ts b/bots/discord/src/utils/discord/moderation.ts new file mode 100644 index 0000000..d74563b --- /dev/null +++ b/bots/discord/src/utils/discord/moderation.ts @@ -0,0 +1,45 @@ +import { config, logger } from '$/context' +import type { ChatInputCommandInteraction, EmbedBuilder, Guild, User } from 'discord.js' +import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from './embeds' + +const PresetLogAction = { + apply: 'Applied role preset to', + remove: 'Removed role preset from', +} as const + +export const sendPresetReplyAndLogs = ( + action: keyof typeof PresetLogAction, + interaction: ChatInputCommandInteraction, + user: User, + preset: string, + expires?: number | null, +) => + sendModerationReplyAndLogs( + interaction, + createModerationActionEmbed(PresetLogAction[action], user, interaction.user, undefined, expires, [ + [{ name: 'Preset', value: preset, inline: true }], + ]), + ) + +export const sendModerationReplyAndLogs = async (interaction: ChatInputCommandInteraction, embed: EmbedBuilder) => { + const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) + const logChannel = await getLogChannel(interaction.guild!) + await logChannel?.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) +} + +export const getLogChannel = async (guild: Guild) => { + const logConfig = config.moderation?.log + if (!logConfig) return + + try { + const channel = await guild.channels.fetch(logConfig.thread ?? logConfig.channel) + if (!channel || !channel.isTextBased()) + return void logger.warn('The moderation log channel does not exist, skipping logging') + + return channel + } catch (error) { + logger.warn('Failed to fetch the moderation log channel:', error) + } + + return +} From b056291ad0f2e2eac5eec8aa71f15dbc769aa0f9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 20:46:31 +0700 Subject: [PATCH 120/312] fix(bots/discord/utils/discord/embeds): set thumbnail on moderation embeds --- bots/discord/src/utils/discord/embeds.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index 0a14fc2..1e5cce2 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -63,6 +63,7 @@ export const createModerationActionEmbed = ( .setTitle(`${action} ${user.tag}`) .setDescription(`${user.toString()} was ${action.toLowerCase()} by ${moderator.toString()}`) .addFields(fields) + .setThumbnail(user.displayAvatarURL({ forceStatic: true })) return applyCommonEmbedStyles(embed, true, true, true) } From 83c314ef5f721abc355272db0e4c182dcfe5d943 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 20:47:14 +0700 Subject: [PATCH 121/312] fix(bots/discord/utils/duration): fix empty string returning with duration is 0 --- bots/discord/src/utils/duration.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bots/discord/src/utils/duration.ts b/bots/discord/src/utils/duration.ts index e2edafb..4b5205c 100644 --- a/bots/discord/src/utils/duration.ts +++ b/bots/discord/src/utils/duration.ts @@ -12,6 +12,8 @@ export const parseDuration = (duration: string) => { } export const durationToString = (duration: number) => { + if (duration === 0) return '0s' + const days = Math.floor(duration / (24 * 60 * 60 * 1000)) const hours = Math.floor((duration % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000)) const minutes = Math.floor((duration % (60 * 60 * 1000)) / (60 * 1000)) From fb01ce57400130c93751a11573eb444c0ba103eb Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 20:48:04 +0700 Subject: [PATCH 122/312] feat(bots/discord/commands): add `purge` and `role-preset` commands --- bots/discord/src/commands/moderation/purge.ts | 73 ++++++++++++++++ .../src/commands/moderation/role-preset.ts | 84 +++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 bots/discord/src/commands/moderation/purge.ts create mode 100644 bots/discord/src/commands/moderation/role-preset.ts diff --git a/bots/discord/src/commands/moderation/purge.ts b/bots/discord/src/commands/moderation/purge.ts new file mode 100644 index 0000000..b1eb0e4 --- /dev/null +++ b/bots/discord/src/commands/moderation/purge.ts @@ -0,0 +1,73 @@ +import { EmbedBuilder, GuildChannel, SlashCommandBuilder } from 'discord.js' + +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { config } from '$/context' +import { applyCommonEmbedStyles } from '$/utils/discord/embeds' + +import type { Command } from '..' + +export default { + data: new SlashCommandBuilder() + .setName('purge') + .setDescription('Purge messages from a channel') + .addIntegerOption(option => + option.setName('amount').setDescription('The amount of messages to remove').setMaxValue(100).setMinValue(1), + ) + .addUserOption(option => + option.setName('user').setDescription('The user to remove messages from (needs `until`)'), + ) + .addStringOption(option => + option.setName('until').setDescription('The message ID to remove messages until (overrides `amount`)'), + ) + .toJSON(), + + memberRequirements: { + roles: config.moderation?.roles ?? [], + }, + + global: false, + + async execute({ logger }, interaction) { + const amount = interaction.options.getInteger('amount') + const user = interaction.options.getUser('user') + const until = interaction.options.getString('until') + + if (!amount && !until) + throw new CommandError(CommandErrorType.MissingArgument, 'Either `amount` or `until` must be provided.') + + const channel = interaction.channel! + if (!(channel.isTextBased() && channel instanceof GuildChannel)) + throw new CommandError(CommandErrorType.InvalidChannel, 'The supplied channel is not a text channel.') + + const embed = applyCommonEmbedStyles( + new EmbedBuilder({ + title: 'Purging messages', + description: 'Accumulating messages...', + }), + true, + true, + true, + ) + + const msgsPromise = channel.messages.fetch(until ? { after: until } : { limit: amount! }) + const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) + + const messages = ( + user ? (await msgsPromise).filter(msg => msg.author.id === user.id) : await msgsPromise + ).filter(msg => msg.id !== reply.id) + + await channel.bulkDelete(messages, true) + + logger.info( + `Moderator ${interaction.user.tag} (${interaction.user.id}) purged ${messages.size} messages in #${channel.name} (${channel.id})`, + ) + await reply.edit({ + embeds: [ + embed.setTitle('Purged messages').setDescription(null).addFields({ + name: 'Deleted messages', + value: messages.size.toString(), + }), + ], + }) + }, +} satisfies Command diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts new file mode 100644 index 0000000..db7b7cd --- /dev/null +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -0,0 +1,84 @@ +import { SlashCommandBuilder } from 'discord.js' + +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { sendPresetReplyAndLogs } from '$/utils/discord/moderation' +import { applyRolePreset, removeRolePreset } from '$/utils/discord/rolePresets' +import { parseDuration } from '$/utils/duration' +import type { Command } from '..' + +export default { + data: new SlashCommandBuilder() + .setName('role-preset') + .setDescription('Manage role presets for a member') + .addStringOption(option => + option + .setName('action') + .setRequired(true) + .setDescription('The action to perform') + .addChoices([ + { name: 'apply', value: 'apply' }, + { name: 'remove', value: 'remove' }, + ]), + ) + .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to manage')) + .addStringOption(option => + option.setName('preset').setRequired(true).setDescription('The preset to apply or remove'), + ) + .addStringOption(option => + option.setName('duration').setDescription('The duration to apply the preset for (only for apply action)'), + ) + .toJSON(), + + memberRequirements: { + roles: ['955220417969262612', '973886585294704640'], + }, + + global: false, + + async execute({ logger }, interaction) { + const action = interaction.options.getString('action', true) as 'apply' | 'remove' + const user = interaction.options.getUser('member', true) + const preset = interaction.options.getString('preset', true) + const duration = interaction.options.getString('duration') + + let expires: number | null | undefined = undefined + const moderator = await interaction.guild!.members.fetch(interaction.user.id) + const member = await interaction.guild!.members.fetch(user.id) + if (!member) + throw new CommandError( + CommandErrorType.InvalidUser, + 'The provided member is not in the server or does not exist.', + ) + + if (member.manageable) + throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') + + if (action === 'apply') { + const durationMs = duration ? parseDuration(duration) : null + if (Number.isInteger(durationMs) && durationMs! < 1) + throw new CommandError( + CommandErrorType.InvalidDuration, + 'The duration must be at least 1 millisecond long.', + ) + + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) + throw new CommandError( + CommandErrorType.InvalidUser, + 'You cannot apply a role preset to a user with a role equal to or higher than yours.', + ) + + expires = durationMs ? Date.now() + durationMs : null + await applyRolePreset(member, preset, expires) + logger.info( + `Moderator ${interaction.user.tag} (${interaction.user.id}) applied role preset ${preset} to ${user.id} until ${expires}`, + ) + } else if (action === 'remove') { + await removeRolePreset(member, preset) + logger.info( + `Moderator ${interaction.user.tag} (${interaction.user.id}) removed role preset ${preset} from ${user.id}`, + ) + } + + await sendPresetReplyAndLogs(action, interaction, user, preset, expires) + }, +} satisfies Command From a2bf3eade99b46f9ffb55d45b8caf1bcf3d22a9b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 20:50:46 +0700 Subject: [PATCH 123/312] fix(bots/discord/commands): refactor and add checks --- bots/discord/src/commands/fun/reply.ts | 3 +- bots/discord/src/commands/moderation/ban.ts | 39 +++++++++++------- bots/discord/src/commands/moderation/mute.ts | 41 ++++++++++--------- .../src/commands/moderation/slowmode.ts | 16 +++++--- bots/discord/src/commands/moderation/unban.ts | 20 +++------ .../discord/src/commands/moderation/unmute.ts | 27 ++++++------ bots/discord/src/utils/discord/modLogs.ts | 0 7 files changed, 78 insertions(+), 68 deletions(-) delete mode 100644 bots/discord/src/utils/discord/modLogs.ts diff --git a/bots/discord/src/commands/fun/reply.ts b/bots/discord/src/commands/fun/reply.ts index a68c849..73fe802 100644 --- a/bots/discord/src/commands/fun/reply.ts +++ b/bots/discord/src/commands/fun/reply.ts @@ -1,5 +1,6 @@ import { SlashCommandBuilder, type TextBasedChannel } from 'discord.js' +import { config } from '$/context' import type { Command } from '..' export default { @@ -16,7 +17,7 @@ export default { .toJSON(), memberRequirements: { - roles: ['955220417969262612', '973886585294704640'], + roles: config.moderation?.roles ?? [], }, global: false, diff --git a/bots/discord/src/commands/moderation/ban.ts b/bots/discord/src/commands/moderation/ban.ts index ee5ec14..42d8f57 100644 --- a/bots/discord/src/commands/moderation/ban.ts +++ b/bots/discord/src/commands/moderation/ban.ts @@ -2,8 +2,10 @@ import { SlashCommandBuilder } from 'discord.js' import type { Command } from '..' +import CommandError, { CommandErrorType } from '$/classes/CommandError' import { config } from '$/context' -import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { createModerationActionEmbed } from '$/utils/discord/embeds' +import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { parseDuration } from '$/utils/duration' export default { @@ -23,27 +25,34 @@ export default { global: false, - async execute({ config, logger }, interaction) { - const user = interaction.options.getUser('member', true) - const reason = interaction.options.getString('reason') ?? undefined + async execute({ logger }, interaction) { + const user = interaction.options.getUser('user', true) + const reason = interaction.options.getString('reason') ?? 'No reason provided' const dmd = interaction.options.getString('dmd') + const member = await interaction.guild!.members.fetch(user.id) + const moderator = await interaction.guild!.members.fetch(interaction.user.id) + + if (member.bannable) throw new CommandError(CommandErrorType.Generic, 'This user cannot be banned by the bot.') + + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) + throw new CommandError( + CommandErrorType.InvalidUser, + 'You cannot ban a user with a role equal to or higher than yours.', + ) + const dms = Math.floor(dmd ? parseDuration(dmd) : 0 / 1000) await interaction.guild!.members.ban(user, { reason: `Banned by moderator ${interaction.user.tag} (${interaction.user.id}): ${reason}`, deleteMessageSeconds: dms, }) - const embed = createModerationActionEmbed('Banned', user, interaction.user, reason ?? 'No reason provided') - const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) - - const logConfig = config.moderation?.log - if (logConfig) { - const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) - if (!channel || !channel.isTextBased()) - return void logger.warn('The moderation log channel does not exist, skipping logging') - - await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) - } + await sendModerationReplyAndLogs( + interaction, + createModerationActionEmbed('Banned', user, interaction.user, reason), + ) + logger.info( + `${interaction.user.tag} (${interaction.user.id}) banned ${user.tag} (${user.id}) because ${reason}, deleting their messages sent in the previous ${dms}s`, + ) }, } satisfies Command diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index f4ebd28..8328c6f 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -5,7 +5,8 @@ import { applyRolePreset } from '$/utils/discord/rolePresets' import type { Command } from '..' import { config } from '$/context' -import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { createModerationActionEmbed } from '$/utils/discord/embeds' +import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { parseDuration } from '$/utils/duration' export default { @@ -23,9 +24,9 @@ export default { global: false, - async execute({ config, logger }, interaction) { + async execute({ logger }, interaction) { const user = interaction.options.getUser('member', true) - const reason = interaction.options.getString('reason') + const reason = interaction.options.getString('reason') ?? 'No reason provided' const duration = interaction.options.getString('duration') const durationMs = duration ? parseDuration(duration) : null @@ -35,6 +36,8 @@ export default { 'The duration must be at least 1 millisecond long.', ) + const expires = durationMs ? Date.now() + durationMs : null + const moderator = await interaction.guild!.members.fetch(interaction.user.id) const member = await interaction.guild!.members.fetch(user.id) if (!member) throw new CommandError( @@ -42,25 +45,23 @@ export default { 'The provided member is not in the server or does not exist.', ) - await applyRolePreset(member, 'mute', durationMs ? Date.now() + durationMs : null) + if (member.manageable) + throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') - const embed = createModerationActionEmbed( - 'Muted', - user, - interaction.user, - reason ?? 'No reason provided', - durationMs, + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) + throw new CommandError( + CommandErrorType.InvalidUser, + 'You cannot mute a user with a role equal to or higher than yours.', + ) + + await applyRolePreset(member, 'mute', durationMs ? Date.now() + durationMs : null) + await sendModerationReplyAndLogs( + interaction, + createModerationActionEmbed('Muted', user, interaction.user, reason, durationMs), ) - const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) - - const logConfig = config.moderation?.log - if (logConfig) { - const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) - if (!channel || !channel.isTextBased()) - return void logger.warn('The moderation log channel does not exist, skipping logging') - - await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) - } + logger.info( + `Moderator ${interaction.user.tag} (${interaction.user.id}) muted ${user.tag} (${user.id}) until ${expires} because ${reason}`, + ) }, } satisfies Command diff --git a/bots/discord/src/commands/moderation/slowmode.ts b/bots/discord/src/commands/moderation/slowmode.ts index eb5534d..61b49da 100644 --- a/bots/discord/src/commands/moderation/slowmode.ts +++ b/bots/discord/src/commands/moderation/slowmode.ts @@ -48,12 +48,18 @@ export default { logger.info(`Setting slowmode to ${duration}ms on ${channel.id}`) - await channel.setRateLimitPerUser( - duration / 1000, - `Slowmode set by @${interaction.user.username} (${interaction.user.id})`, - ) + await channel.setRateLimitPerUser(duration / 1000, `Set by ${interaction.user.tag} (${interaction.user.id})`) + await interaction.reply({ - embeds: [createSuccessEmbed(`Slowmode set to ${durationToString(duration)} on ${channel.toString()}`)], + embeds: [ + createSuccessEmbed( + `Slowmode ${duration ? `set to ${durationToString(duration)}` : 'removed'} on ${channel.toString()}`, + ), + ], }) + + logger.info( + `${interaction.user.tag} (${interaction.user.id}) set the slowmode on ${channel.name} (${channel.id}) to ${duration}ms`, + ) }, } satisfies Command diff --git a/bots/discord/src/commands/moderation/unban.ts b/bots/discord/src/commands/moderation/unban.ts index 0e9946f..9adbe89 100644 --- a/bots/discord/src/commands/moderation/unban.ts +++ b/bots/discord/src/commands/moderation/unban.ts @@ -3,7 +3,8 @@ import { SlashCommandBuilder } from 'discord.js' import type { Command } from '..' import { config } from '$/context' -import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { createModerationActionEmbed } from '$/utils/discord/embeds' +import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' export default { data: new SlashCommandBuilder() @@ -18,24 +19,15 @@ export default { global: false, - async execute({ config, logger }, interaction) { - const user = interaction.options.getUser('member', true) + async execute({ logger }, interaction) { + const user = interaction.options.getUser('user', true) await interaction.guild!.members.unban( user, `Unbanned by moderator ${interaction.user.tag} (${interaction.user.id})`, ) - const embed = createModerationActionEmbed('Unbanned', user, interaction.user) - const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) - - const logConfig = config.moderation?.log - if (logConfig) { - const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) - if (!channel || !channel.isTextBased()) - return void logger.warn('The moderation log channel does not exist, skipping logging') - - await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) - } + await sendModerationReplyAndLogs(interaction, createModerationActionEmbed('Unbanned', user, interaction.user)) + logger.info(`${interaction.user.tag} (${interaction.user.id}) unbanned ${user.tag} (${user.id})`) }, } satisfies Command diff --git a/bots/discord/src/commands/moderation/unmute.ts b/bots/discord/src/commands/moderation/unmute.ts index 41c37df..29860ea 100644 --- a/bots/discord/src/commands/moderation/unmute.ts +++ b/bots/discord/src/commands/moderation/unmute.ts @@ -2,8 +2,11 @@ import { SlashCommandBuilder } from 'discord.js' import CommandError, { CommandErrorType } from '$/classes/CommandError' import { config } from '$/context' -import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { appliedPresets } from '$/database/schemas' +import { createModerationActionEmbed } from '$/utils/discord/embeds' +import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { removeRolePreset } from '$/utils/discord/rolePresets' +import { and, eq } from 'drizzle-orm' import type { Command } from '..' export default { @@ -19,7 +22,7 @@ export default { global: false, - async execute({ config, logger }, interaction) { + async execute({ logger, database }, interaction) { const user = interaction.options.getUser('member', true) const member = await interaction.guild!.members.fetch(user.id) if (!member) @@ -28,18 +31,16 @@ export default { 'The provided member is not in the server or does not exist.', ) + if ( + !(await database.query.appliedPresets.findFirst({ + where: and(eq(appliedPresets.memberId, member.id), eq(appliedPresets.preset, 'mute')), + })) + ) + throw new CommandError(CommandErrorType.Generic, 'This user is not muted.') + await removeRolePreset(member, 'mute') - const embed = createModerationActionEmbed('Unmuted', user, interaction.user) + await sendModerationReplyAndLogs(interaction, createModerationActionEmbed('Unmuted', user, interaction.user)) - const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) - - const logConfig = config.moderation?.log - if (logConfig) { - const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) - if (!channel || !channel.isTextBased()) - return void logger.warn('The moderation log channel does not exist, skipping logging') - - await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) - } + logger.info(`Moderator ${interaction.user.tag} (${interaction.user.id}) unmuted ${user.tag} (${user.id})`) }, } satisfies Command diff --git a/bots/discord/src/utils/discord/modLogs.ts b/bots/discord/src/utils/discord/modLogs.ts deleted file mode 100644 index e69de29..0000000 From 1723e8cacf96e8c6bdee22cfd30e89524fdcef74 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 22:53:21 +0700 Subject: [PATCH 124/312] feat(bots/discord): support nickname decancering --- bots/discord/config.schema.ts | 3 ++ bots/discord/config.ts | 3 ++ bots/discord/package.json | 1 + bots/discord/src/commands/moderation/cure.ts | 26 ++++++++++++++++++ .../src/events/discord/cureRequired.ts | 18 ++++++++++++ bots/discord/src/utils/discord/moderation.ts | 16 ++++++++++- bun.lockb | Bin 112792 -> 118328 bytes 7 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 bots/discord/src/commands/moderation/cure.ts create mode 100644 bots/discord/src/events/discord/cureRequired.ts diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 9be18e2..0dbe458 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -5,6 +5,9 @@ export type Config = { guilds: string[] moderation?: { roles: string[] + cure?: { + defaultName: string + } log?: { channel: string thread?: string diff --git a/bots/discord/config.ts b/bots/discord/config.ts index c254919..d02748e 100644 --- a/bots/discord/config.ts +++ b/bots/discord/config.ts @@ -4,6 +4,9 @@ export default { owners: ['USER_ID_HERE'], guilds: ['GUILD_ID_HERE'], moderation: { + cure: { + defaultName: 'Server member', + }, roles: ['ROLE_ID_HERE'], log: { channel: 'CHANNEL_ID_HERE', diff --git a/bots/discord/package.json b/bots/discord/package.json index 6b562b2..077ac98 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -31,6 +31,7 @@ "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", + "decancer": "^3.2.2", "discord.js": "^14.15.3", "drizzle-orm": "^0.31.2" }, diff --git a/bots/discord/src/commands/moderation/cure.ts b/bots/discord/src/commands/moderation/cure.ts new file mode 100644 index 0000000..e1895a6 --- /dev/null +++ b/bots/discord/src/commands/moderation/cure.ts @@ -0,0 +1,26 @@ +import { SlashCommandBuilder } from 'discord.js' + +import type { Command } from '..' + +import { config } from '$/context' +import { cureNickname } from '$/utils/discord/moderation' + +export default { + data: new SlashCommandBuilder() + .setName('cure') + .setDescription("Cure a member's nickname") + .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to cure')) + .toJSON(), + + memberRequirements: { + roles: config.moderation?.roles ?? [], + }, + + global: false, + + async execute(_, interaction) { + const user = interaction.options.getUser('user', true) + const member = await interaction.guild!.members.fetch(user.id) + await cureNickname(member) + }, +} satisfies Command diff --git a/bots/discord/src/events/discord/cureRequired.ts b/bots/discord/src/events/discord/cureRequired.ts new file mode 100644 index 0000000..c273a3a --- /dev/null +++ b/bots/discord/src/events/discord/cureRequired.ts @@ -0,0 +1,18 @@ +import { on } from '$/utils/discord/events' +import { cureNickname } from '$/utils/discord/moderation' + +on('guildMemberUpdate', async (_, oldMember, newMember) => { + if (newMember.user.bot) return + if (oldMember.nickname !== newMember.nickname) await cureNickname(newMember) +}) + +on('guildMemberAdd', (_, member) => { + if (member.user.bot) return + cureNickname(member) +}) + +on('messageCreate', async (_, msg) => { + if (msg.author.bot) return + if (!msg.member) return + await cureNickname(msg.member) +}) diff --git a/bots/discord/src/utils/discord/moderation.ts b/bots/discord/src/utils/discord/moderation.ts index d74563b..f306fea 100644 --- a/bots/discord/src/utils/discord/moderation.ts +++ b/bots/discord/src/utils/discord/moderation.ts @@ -1,5 +1,6 @@ import { config, logger } from '$/context' -import type { ChatInputCommandInteraction, EmbedBuilder, Guild, User } from 'discord.js' +import decancer from 'decancer' +import type { ChatInputCommandInteraction, EmbedBuilder, Guild, GuildMember, User } from 'discord.js' import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from './embeds' const PresetLogAction = { @@ -43,3 +44,16 @@ export const getLogChannel = async (guild: Guild) => { return } + +export const cureNickname = async (member: GuildMember) => { + if (!member.manageable) throw new Error('Member is not manageable') + const name = member.displayName + let cured = decancer(name) + .toString() + .replace(/[^a-zA-Z0-9]/g, '') + + if (!cured || !/^[a-zA-Z]/.test(cured)) cured = config.moderation?.cure?.defaultName ?? 'ReVanced member' + + await member.setNickname(cured, 'Nickname cured') + logger.log(`Cured nickname for ${member.user.tag} (${member.id}) from "${name}"`) +} diff --git a/bun.lockb b/bun.lockb index 72a95e3009309189b46a303c752d61e225883a99..6ee688bd593127a4800e3e919ceebc9f6aeee04a 100755 GIT binary patch delta 10666 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 DIK?sH delta 6927 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 X>;Rb|J3ap_qau?}^z=z*8J7V7Cr5-6 From f50b26b82d66c88fd1dbb8c07d77c177c0e781df Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 22:54:17 +0700 Subject: [PATCH 125/312] fix(bots/discord): apply active role presets if members rejoin --- .../discord/guildMemberAdd/apply-role-presets.ts | 16 ++++++++++++++++ bots/discord/src/utils/discord/rolePresets.ts | 10 +++------- 2 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 bots/discord/src/events/discord/guildMemberAdd/apply-role-presets.ts diff --git a/bots/discord/src/events/discord/guildMemberAdd/apply-role-presets.ts b/bots/discord/src/events/discord/guildMemberAdd/apply-role-presets.ts new file mode 100644 index 0000000..034d094 --- /dev/null +++ b/bots/discord/src/events/discord/guildMemberAdd/apply-role-presets.ts @@ -0,0 +1,16 @@ +import { appliedPresets } from '$/database/schemas' +import { on } from '$/utils/discord/events' +import { applyRolesUsingPreset } from '$/utils/discord/rolePresets' +import { and, eq, gt } from 'drizzle-orm' + +on('guildMemberAdd', async ({ database }, member) => { + const applieds = await database.query.appliedPresets.findMany({ + where: and( + eq(appliedPresets.memberId, member.id), + eq(appliedPresets.guildId, member.guild.id), + gt(appliedPresets.until, Date.now() / 1000), + ), + }) + + for (const { preset } of applieds) await applyRolesUsingPreset(preset, member, true) +}) diff --git a/bots/discord/src/utils/discord/rolePresets.ts b/bots/discord/src/utils/discord/rolePresets.ts index 34c99a4..925d436 100644 --- a/bots/discord/src/utils/discord/rolePresets.ts +++ b/bots/discord/src/utils/discord/rolePresets.ts @@ -7,7 +7,7 @@ import { and, eq } from 'drizzle-orm' type PresetKey = string export const applyRolePreset = async (member: GuildMember, presetName: PresetKey, untilMs: number | null) => { - const afterInsert = await commonOperations(presetName, member, true) + const afterInsert = await applyRolesUsingPreset(presetName, member, true) const until = untilMs ? Math.ceil(untilMs / 1000) : null await database @@ -26,7 +26,7 @@ export const applyRolePreset = async (member: GuildMember, presetName: PresetKey } export const removeRolePreset = async (member: GuildMember, presetName: PresetKey) => { - const afterDelete = await commonOperations(presetName, member, false) + const afterDelete = await applyRolesUsingPreset(presetName, member, false) await database .delete(appliedPresets) @@ -41,11 +41,7 @@ export const removeRolePreset = async (member: GuildMember, presetName: PresetKe .then(afterDelete) } -/** - * Inserts (if not already present) an entry in the database, sets the member's roles - * @returns The currently applied presets AND a callback function to run after correcting the presets in the database - */ -const commonOperations = async (presetName: string, member: GuildMember, applying: boolean) => { +export const applyRolesUsingPreset = async (presetName: string, member: GuildMember, applying: boolean) => { const preset = config.rolePresets?.guilds[member.guild.id]?.[presetName] if (!preset) throw new Error(`The preset "${presetName}" does not exist for this server`) From e64d1da00cc2ba718da5a4b0da141fe86a0e48d2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 22:56:14 +0700 Subject: [PATCH 126/312] feat(bots/discord/commands): add `eval` command --- bots/discord/src/commands/development/eval.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 bots/discord/src/commands/development/eval.ts diff --git a/bots/discord/src/commands/development/eval.ts b/bots/discord/src/commands/development/eval.ts new file mode 100644 index 0000000..f145a79 --- /dev/null +++ b/bots/discord/src/commands/development/eval.ts @@ -0,0 +1,33 @@ +import { inspect } from 'util' +import { SlashCommandBuilder } from 'discord.js' + +import { createSuccessEmbed } from '$/utils/discord/embeds' +import type { Command } from '..' + +export default { + data: new SlashCommandBuilder() + .setName('eval') + .setDescription('Evaluates something') + .addStringOption(option => option.setName('code').setDescription('The code to evaluate').setRequired(true)) + .setDMPermission(true) + .toJSON(), + + ownerOnly: true, + global: true, + + // @ts-expect-error: Needed for science + async execute(context, interaction) { + const code = interaction.options.getString('code', true) + + await interaction.reply({ + ephemeral: true, + embeds: [ + createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``).addFields({ + name: 'Result', + // biome-ignore lint/security/noGlobalEval: Deal with it + value: `\`\`\`js\n${inspect(eval(code), { depth: 1 })}\`\`\``, + }), + ], + }) + }, +} satisfies Command From 39cba973418027ba6ed67e1ae5ab5c6458807562 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 23:02:08 +0700 Subject: [PATCH 127/312] fix(bots/discord): owners cannot bypass checks on some commands --- bots/discord/src/commands/index.ts | 10 +++++++++- bots/discord/src/commands/moderation/mute.ts | 4 ++-- bots/discord/src/commands/moderation/role-preset.ts | 4 ++-- .../events/discord/interactionCreate/chat-commmand.ts | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/bots/discord/src/commands/index.ts b/bots/discord/src/commands/index.ts index 2f94f85..e782811 100644 --- a/bots/discord/src/commands/index.ts +++ b/bots/discord/src/commands/index.ts @@ -10,7 +10,11 @@ export type Command = { * The function to execute when this command is triggered * @param interaction The interaction that triggered this command */ - execute: (context: typeof import('../context'), interaction: ChatInputCommandInteraction) => Promise | void + execute: ( + context: typeof import('../context'), + interaction: ChatInputCommandInteraction, + info: Info, + ) => Promise | void memberRequirements?: { /** * The mode to use when checking for requirements. @@ -46,3 +50,7 @@ export type Command = { */ global?: boolean } + +export interface Info { + userIsOwner: boolean +} diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index 8328c6f..8cd8bbc 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -24,7 +24,7 @@ export default { global: false, - async execute({ logger }, interaction) { + async execute({ logger }, interaction, { userIsOwner }) { const user = interaction.options.getUser('member', true) const reason = interaction.options.getString('reason') ?? 'No reason provided' const duration = interaction.options.getString('duration') @@ -48,7 +48,7 @@ export default { if (member.manageable) throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') - if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !userIsOwner) throw new CommandError( CommandErrorType.InvalidUser, 'You cannot mute a user with a role equal to or higher than yours.', diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts index db7b7cd..959b664 100644 --- a/bots/discord/src/commands/moderation/role-preset.ts +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -35,7 +35,7 @@ export default { global: false, - async execute({ logger }, interaction) { + async execute({ logger }, interaction, { userIsOwner }) { const action = interaction.options.getString('action', true) as 'apply' | 'remove' const user = interaction.options.getUser('member', true) const preset = interaction.options.getString('preset', true) @@ -61,7 +61,7 @@ export default { 'The duration must be at least 1 millisecond long.', ) - if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !userIsOwner) throw new CommandError( CommandErrorType.InvalidUser, 'You cannot apply a role preset to a user with a role equal to or higher than yours.', diff --git a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts index 79d262a..9300b75 100644 --- a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts @@ -69,7 +69,7 @@ export default on('interactionCreate', async (context, interaction) => { try { logger.debug(`Command ${interaction.commandName} being executed`) - await command.execute(context, interaction) + await command.execute(context, interaction, { userIsOwner: isOwner }) } catch (err) { logger.error(`Error while executing command ${interaction.commandName}:`, err) await interaction[interaction.replied ? 'followUp' : 'reply']({ From 2e1e009b4272495798313bd3bd61f258875c62e1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 23:15:33 +0700 Subject: [PATCH 128/312] feat(bots/discord): add more fallbacks for decancering --- bots/discord/src/utils/discord/moderation.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/utils/discord/moderation.ts b/bots/discord/src/utils/discord/moderation.ts index f306fea..0250676 100644 --- a/bots/discord/src/utils/discord/moderation.ts +++ b/bots/discord/src/utils/discord/moderation.ts @@ -52,7 +52,13 @@ export const cureNickname = async (member: GuildMember) => { .toString() .replace(/[^a-zA-Z0-9]/g, '') - if (!cured || !/^[a-zA-Z]/.test(cured)) cured = config.moderation?.cure?.defaultName ?? 'ReVanced member' + if (cured.length < 3 || !/^[a-zA-Z]/.test(cured)) + cured = + member.user.username.length >= 3 + ? member.user.username + : config.moderation?.cure?.defaultName ?? 'Server member' + + if (cured.toLowerCase() === name.toLowerCase()) return await member.setNickname(cured, 'Nickname cured') logger.log(`Cured nickname for ${member.user.tag} (${member.id}) from "${name}"`) From ee885ca7585a55fdc31e137ae29dc13a37ce2fb2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 23:16:27 +0700 Subject: [PATCH 129/312] feat(bots/discord/utils/embeds): make title parameter nullable --- bots/discord/src/utils/discord/embeds.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index 1e5cce2..6248bef 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -2,7 +2,7 @@ import { DefaultEmbedColor, MessageScanHumanizedMode, ReVancedLogoURL } from '$/ import { EmbedBuilder, type EmbedField, type User } from 'discord.js' import type { ConfigMessageScanResponseMessage } from '../../../config.schema' -export const createErrorEmbed = (title: string, description?: string) => +export const createErrorEmbed = (title: string | null, description?: string) => applyCommonEmbedStyles( new EmbedBuilder() .setTitle(title) @@ -15,7 +15,7 @@ export const createStackTraceEmbed = (stack: unknown) => // biome-ignore lint/style/useTemplate: shut createErrorEmbed('An exception was thrown', '```js\n' + stack + '```') -export const createSuccessEmbed = (title: string, description?: string) => +export const createSuccessEmbed = (title: string | null, description?: string) => applyCommonEmbedStyles( new EmbedBuilder() .setTitle(title) From 0303fe3e367c07e92f831365d5548ca5b03435b2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 23:17:28 +0700 Subject: [PATCH 130/312] fix(bots/discord): do decancer after resetting nickname --- bots/discord/src/events/discord/cureRequired.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/events/discord/cureRequired.ts b/bots/discord/src/events/discord/cureRequired.ts index c273a3a..4c810a8 100644 --- a/bots/discord/src/events/discord/cureRequired.ts +++ b/bots/discord/src/events/discord/cureRequired.ts @@ -3,7 +3,7 @@ import { cureNickname } from '$/utils/discord/moderation' on('guildMemberUpdate', async (_, oldMember, newMember) => { if (newMember.user.bot) return - if (oldMember.nickname !== newMember.nickname) await cureNickname(newMember) + if (oldMember.displayName !== newMember.displayName) await cureNickname(newMember) }) on('guildMemberAdd', (_, member) => { From 3b2596e748cf2cde1500ef2ded55f0faabc2c272 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 23:18:27 +0700 Subject: [PATCH 131/312] fix(bots/discord/commands): minor issues --- bots/discord/src/commands/moderation/cure.ts | 7 ++++++- bots/discord/src/commands/moderation/mute.ts | 2 +- bots/discord/src/commands/moderation/role-preset.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bots/discord/src/commands/moderation/cure.ts b/bots/discord/src/commands/moderation/cure.ts index e1895a6..60d9a0b 100644 --- a/bots/discord/src/commands/moderation/cure.ts +++ b/bots/discord/src/commands/moderation/cure.ts @@ -3,6 +3,7 @@ import { SlashCommandBuilder } from 'discord.js' import type { Command } from '..' import { config } from '$/context' +import { createSuccessEmbed } from '$/utils/discord/embeds' import { cureNickname } from '$/utils/discord/moderation' export default { @@ -19,8 +20,12 @@ export default { global: false, async execute(_, interaction) { - const user = interaction.options.getUser('user', true) + const user = interaction.options.getUser('member', true) const member = await interaction.guild!.members.fetch(user.id) await cureNickname(member) + await interaction.reply({ + embeds: [createSuccessEmbed(null, `Cured nickname for ${member.toString()}`)], + ephemeral: true, + }) }, } satisfies Command diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index 8cd8bbc..fa6ea26 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -45,7 +45,7 @@ export default { 'The provided member is not in the server or does not exist.', ) - if (member.manageable) + if (!member.manageable) throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !userIsOwner) diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts index 959b664..f8eac4b 100644 --- a/bots/discord/src/commands/moderation/role-preset.ts +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -50,7 +50,7 @@ export default { 'The provided member is not in the server or does not exist.', ) - if (member.manageable) + if (!member.manageable) throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') if (action === 'apply') { From 51a6fb65f0df3409eacffb297430840a0e326989 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 24 Jun 2024 23:33:31 +0700 Subject: [PATCH 132/312] fix(apis/websocket): don't bundle `tesseract.js` --- apis/websocket/after_build.ts | 3 --- apis/websocket/docs/2_running.md | 13 +++++++++++-- apis/websocket/package.json | 2 +- package.json | 5 +---- patches/tesseract.js@5.1.0.patch | 19 ------------------- 5 files changed, 13 insertions(+), 29 deletions(-) delete mode 100644 apis/websocket/after_build.ts delete mode 100644 patches/tesseract.js@5.1.0.patch diff --git a/apis/websocket/after_build.ts b/apis/websocket/after_build.ts deleted file mode 100644 index d7c41f7..0000000 --- a/apis/websocket/after_build.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as fs from 'fs' - -fs.cpSync('../../node_modules/tesseract.js', './dist/tesseract.js', { recursive: true }) diff --git a/apis/websocket/docs/2_running.md b/apis/websocket/docs/2_running.md index d96f449..5d3b99a 100644 --- a/apis/websocket/docs/2_running.md +++ b/apis/websocket/docs/2_running.md @@ -35,8 +35,17 @@ If you're looking to build and host the server somewhere else, you can run: bun bundle ``` -The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** -You can run these files using the command `bun run index.js`. +The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** + +To start up the server, you'll need to install `tesseract.js` first. +```sh +bun install tesseract.js +# or +bun install tesseract.js -g + +# Run the server +bun run index.js +``` ## ⏭️ What's next diff --git a/apis/websocket/package.json b/apis/websocket/package.json index c9fd4d2..5257e09 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -6,7 +6,7 @@ "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { - "bundle": "bun build src/index.ts --outdir=dist --target=bun && bun run after_build.ts", + "bundle": "bun build src/index.ts --outdir=dist --target=bun -e tesseract.js", "dev": "bun run src/index.ts --watch", "build": "bun bundle", "watch": "bun dev" diff --git a/package.json b/package.json index 906f196..99d2a3c 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,5 @@ "turbo": "^1.13.4", "typescript": "^5.5.2" }, - "trustedDependencies": ["@biomejs/biome", "esbuild", "lefthook", "tesseract.js"], - "patchedDependencies": { - "tesseract.js@5.1.0": "patches/tesseract.js@5.1.0.patch" - } + "trustedDependencies": ["@biomejs/biome", "esbuild", "lefthook"] } diff --git a/patches/tesseract.js@5.1.0.patch b/patches/tesseract.js@5.1.0.patch deleted file mode 100644 index 93ecfc3..0000000 --- a/patches/tesseract.js@5.1.0.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/src/worker/node/defaultOptions.js b/src/worker/node/defaultOptions.js -index 053c1e3e3b561b2e5b5e3609e9c6b509b1661f11..407242f5576f8d888644ab28ff169599a79bf4dd 100644 ---- a/src/worker/node/defaultOptions.js -+++ b/src/worker/node/defaultOptions.js -@@ -1,10 +1,13 @@ - const path = require('path'); -+const fs = require('fs'); - const defaultOptions = require('../../constants/defaultOptions'); - -+const defaultPath = path.join(import.meta.dir, '..', '..', 'worker-script', 'node', 'index.js') -+ - /* - * Default options for node worker - */ - module.exports = { - ...defaultOptions, -- workerPath: path.join(__dirname, '..', '..', 'worker-script', 'node', 'index.js'), -+ workerPath: fs.existsSync(defaultPath) ? defaultPath : path.join(import.meta.dir, 'tesseract.js', 'src', 'worker-script', 'node', 'index.js'), - }; From af3759caf428fada3b3f4a51852543d6fb280018 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 4 Jul 2024 20:51:30 +0700 Subject: [PATCH 133/312] fix(bots/discord): use env for initializing database --- bots/discord/.env.example | 3 ++- bots/discord/drizzle.config.ts | 2 +- bots/discord/src/context.ts | 4 ++-- bots/discord/src/index.ts | 7 +++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bots/discord/.env.example b/bots/discord/.env.example index 2a9d1fb..f1eefc5 100644 --- a/bots/discord/.env.example +++ b/bots/discord/.env.example @@ -1 +1,2 @@ -DISCORD_TOKEN="YOUR-TOKEN-HERE" \ No newline at end of file +DISCORD_TOKEN="YOUR-TOKEN-HERE" +DATABASE_URL=file:./db.sqlite3 \ No newline at end of file diff --git a/bots/discord/drizzle.config.ts b/bots/discord/drizzle.config.ts index 719d1d8..f50247d 100644 --- a/bots/discord/drizzle.config.ts +++ b/bots/discord/drizzle.config.ts @@ -4,6 +4,6 @@ export default defineConfig({ dialect: 'sqlite', schema: './src/database/schemas.ts', dbCredentials: { - url: 'file:./db.sqlite', + url: process.env['DATABASE_URL'], }, }) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 3c8bfc7..9568561 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -19,7 +19,7 @@ export const api = { client: new APIClient({ api: { websocket: { - url: config.api.websocketUrl, + url: config.api.url, }, }, }), @@ -27,7 +27,7 @@ export const api = { disconnectCount: 0, } -const db = new Database('db.sqlite') +const db = new Database(process.env['DATABASE_URL']) export const database = drizzle(db, { schema: schemas, diff --git a/bots/discord/src/index.ts b/bots/discord/src/index.ts index f385732..8b80c9f 100644 --- a/bots/discord/src/index.ts +++ b/bots/discord/src/index.ts @@ -1,10 +1,9 @@ -// import { listAllFilesRecursive, pathJoinCurrentDir } from '$utils/fs' +import { api, discord, logger } from '$/context' +import { listAllFilesRecursive, pathJoinCurrentDir } from '$utils/fs' import { getMissingEnvironmentVariables } from '@revanced/bot-shared' -import { api, discord, logger } from './context' -import { listAllFilesRecursive, pathJoinCurrentDir } from './utils/fs' // Check if token exists -const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN']) +const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN', 'DATABASE_URL']) if (missingEnvs.length) { for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`) process.exit(1) From 0bfd03583d89ce3e80f900479b4b24ee7d126a05 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 4 Jul 2024 20:53:29 +0700 Subject: [PATCH 134/312] feat(events)!: use better api for events --- bots/discord/src/events/api/disconnect.ts | 9 ++++---- bots/discord/src/events/api/ready.ts | 7 ++----- .../src/events/discord/cureRequired.ts | 9 ++++---- ...ly-role-presets.ts => applyRolePresets.ts} | 4 ++-- .../{chat-commmand.ts => chatCommand.ts} | 4 ++-- ...correct-response.ts => correctResponse.ts} | 4 ++-- .../{scan.ts => messageScanRequired.ts} | 10 ++++----- ...correct-response.ts => correctResponse.ts} | 6 +++--- bots/discord/src/events/discord/ready.ts | 4 ++-- bots/discord/src/utils/api/events.ts | 21 ++++++++++++------- bots/discord/src/utils/discord/events.ts | 14 ++++++++----- 11 files changed, 48 insertions(+), 44 deletions(-) rename bots/discord/src/events/discord/guildMemberAdd/{apply-role-presets.ts => applyRolePresets.ts} (80%) rename bots/discord/src/events/discord/interactionCreate/{chat-commmand.ts => chatCommand.ts} (96%) rename bots/discord/src/events/discord/interactionCreate/{correct-response.ts => correctResponse.ts} (97%) rename bots/discord/src/events/discord/messageCreate/{scan.ts => messageScanRequired.ts} (92%) rename bots/discord/src/events/discord/messageReactionAdd/{correct-response.ts => correctResponse.ts} (96%) diff --git a/bots/discord/src/events/api/disconnect.ts b/bots/discord/src/events/api/disconnect.ts index 29d1ae4..6b6c230 100644 --- a/bots/discord/src/events/api/disconnect.ts +++ b/bots/discord/src/events/api/disconnect.ts @@ -1,8 +1,7 @@ -import { on } from '$utils/api/events' +import { on, withContext } from '$utils/api/events' import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared' -import { api, logger } from 'src/context' -on('disconnect', (reason, msg) => { +withContext(on, 'disconnect', ({ api, config, logger }, reason, msg) => { if (reason === DisconnectReason.PlannedDisconnect && api.isStopping) return const ws = api.client.ws @@ -16,8 +15,7 @@ on('disconnect', (reason, msg) => { }`, ) - // TODO: move to config - if (api.disconnectCount >= 3) { + if (api.disconnectCount >= (config.api.disconnectLimit ?? 3)) { console.error('Disconnected from bot API too many times') // We don't want the process hanging process.exit(1) @@ -26,5 +24,6 @@ on('disconnect', (reason, msg) => { logger.info( `Disconnected from bot API ${++api.disconnectCount} times (this time because: ${reason}, ${msg}), reconnecting again...`, ) + setTimeout(() => api.client.connect(), 10000) }) diff --git a/bots/discord/src/events/api/ready.ts b/bots/discord/src/events/api/ready.ts index 4df8ab9..94e55df 100644 --- a/bots/discord/src/events/api/ready.ts +++ b/bots/discord/src/events/api/ready.ts @@ -1,6 +1,3 @@ -import { on } from '$utils/api/events' -import { logger } from 'src/context' +import { on, withContext } from '$utils/api/events' -on('ready', () => { - logger.info('Connected to the bot API') -}) +withContext(on, 'ready', ({ logger }) => void logger.info('Connected to the bot API')) diff --git a/bots/discord/src/events/discord/cureRequired.ts b/bots/discord/src/events/discord/cureRequired.ts index 4c810a8..b8e930b 100644 --- a/bots/discord/src/events/discord/cureRequired.ts +++ b/bots/discord/src/events/discord/cureRequired.ts @@ -1,18 +1,17 @@ import { on } from '$/utils/discord/events' import { cureNickname } from '$/utils/discord/moderation' -on('guildMemberUpdate', async (_, oldMember, newMember) => { +on('guildMemberUpdate', async (oldMember, newMember) => { if (newMember.user.bot) return if (oldMember.displayName !== newMember.displayName) await cureNickname(newMember) }) -on('guildMemberAdd', (_, member) => { +on('guildMemberAdd', member => { if (member.user.bot) return cureNickname(member) }) -on('messageCreate', async (_, msg) => { - if (msg.author.bot) return - if (!msg.member) return +on('messageCreate', async msg => { + if (msg.author.bot || !msg.member) return await cureNickname(msg.member) }) diff --git a/bots/discord/src/events/discord/guildMemberAdd/apply-role-presets.ts b/bots/discord/src/events/discord/guildMemberAdd/applyRolePresets.ts similarity index 80% rename from bots/discord/src/events/discord/guildMemberAdd/apply-role-presets.ts rename to bots/discord/src/events/discord/guildMemberAdd/applyRolePresets.ts index 034d094..90f176f 100644 --- a/bots/discord/src/events/discord/guildMemberAdd/apply-role-presets.ts +++ b/bots/discord/src/events/discord/guildMemberAdd/applyRolePresets.ts @@ -1,9 +1,9 @@ import { appliedPresets } from '$/database/schemas' -import { on } from '$/utils/discord/events' +import { on, withContext } from '$/utils/discord/events' import { applyRolesUsingPreset } from '$/utils/discord/rolePresets' import { and, eq, gt } from 'drizzle-orm' -on('guildMemberAdd', async ({ database }, member) => { +withContext(on, 'guildMemberAdd', async ({ database }, member) => { const applieds = await database.query.appliedPresets.findMany({ where: and( eq(appliedPresets.memberId, member.id), diff --git a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts similarity index 96% rename from bots/discord/src/events/discord/interactionCreate/chat-commmand.ts rename to bots/discord/src/events/discord/interactionCreate/chatCommand.ts index 9300b75..0972b16 100644 --- a/bots/discord/src/events/discord/interactionCreate/chat-commmand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts @@ -1,8 +1,8 @@ import CommandError from '$/classes/CommandError' import { createErrorEmbed, createStackTraceEmbed } from '$utils/discord/embeds' -import { on } from '$utils/discord/events' +import { on, withContext } from '$utils/discord/events' -export default on('interactionCreate', async (context, interaction) => { +withContext(on, 'interactionCreate', async (context, interaction) => { if (!interaction.isChatInputCommand()) return const { logger, discord, config } = context diff --git a/bots/discord/src/events/discord/interactionCreate/correct-response.ts b/bots/discord/src/events/discord/interactionCreate/correctResponse.ts similarity index 97% rename from bots/discord/src/events/discord/interactionCreate/correct-response.ts rename to bots/discord/src/events/discord/interactionCreate/correctResponse.ts index 9c1c810..81b67a8 100644 --- a/bots/discord/src/events/discord/interactionCreate/correct-response.ts +++ b/bots/discord/src/events/discord/interactionCreate/correctResponse.ts @@ -1,13 +1,13 @@ import { responses } from '$/database/schemas' import { handleUserResponseCorrection } from '$/utils/discord/messageScan' import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds' -import { on } from '$utils/discord/events' +import { on, withContext } from '$utils/discord/events' import type { ButtonInteraction, StringSelectMenuInteraction, TextBasedChannel } from 'discord.js' import { eq } from 'drizzle-orm' // No permission check required as it is already done when the user reacts to a bot response -export default on('interactionCreate', async (context, interaction) => { +withContext(on, 'interactionCreate', async (context, interaction) => { const { logger, database: db, diff --git a/bots/discord/src/events/discord/messageCreate/scan.ts b/bots/discord/src/events/discord/messageCreate/messageScanRequired.ts similarity index 92% rename from bots/discord/src/events/discord/messageCreate/scan.ts rename to bots/discord/src/events/discord/messageCreate/messageScanRequired.ts index 4400172..1ba7ef6 100644 --- a/bots/discord/src/events/discord/messageCreate/scan.ts +++ b/bots/discord/src/events/discord/messageCreate/messageScanRequired.ts @@ -2,15 +2,15 @@ import { MessageScanLabeledResponseReactions } from '$/constants' import { responses } from '$/database/schemas' import { getResponseFromText, shouldScanMessage } from '$/utils/discord/messageScan' import { createMessageScanResponseEmbed } from '$utils/discord/embeds' -import { on } from '$utils/discord/events' +import { on, withContext } from '$utils/discord/events' -on('messageCreate', async (ctx, msg) => { +withContext(on, 'messageCreate', async (context, msg) => { const { api, config: { messageScan: config }, database: db, logger, - } = ctx + } = context if (!config || !config.responses) return @@ -21,7 +21,7 @@ on('messageCreate', async (ctx, msg) => { try { logger.debug(`Classifying message ${msg.id}`) - const { response, label } = await getResponseFromText(msg.content, filteredResponses, ctx) + const { response, label } = await getResponseFromText(msg.content, filteredResponses, context) if (response) { logger.debug('Response found') @@ -59,7 +59,7 @@ on('messageCreate', async (ctx, msg) => { try { const { text: content } = await api.client.parseImage(attachment.url) - const { response } = await getResponseFromText(content, filteredResponses, ctx, true) + const { response } = await getResponseFromText(content, filteredResponses, context, true) if (response) { logger.debug(`Response found for attachment: ${attachment.url}`) diff --git a/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts similarity index 96% rename from bots/discord/src/events/discord/messageReactionAdd/correct-response.ts rename to bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index b90a21a..2e8bd2a 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correct-response.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -1,6 +1,6 @@ import { MessageScanLabeledResponseReactions as Reactions } from '$/constants' import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$/utils/discord/embeds' -import { on } from '$/utils/discord/events' +import { on, withContext } from '$/utils/discord/events' import { ActionRowBuilder, @@ -10,14 +10,14 @@ import { StringSelectMenuOptionBuilder, } from 'discord.js' +import type { ConfigMessageScanResponseLabelConfig } from '$/../config.schema' import { responses } from '$/database/schemas' import { handleUserResponseCorrection } from '$/utils/discord/messageScan' -import type { ConfigMessageScanResponseLabelConfig } from 'config.schema' import { eq } from 'drizzle-orm' const PossibleReactions = Object.values(Reactions) as string[] -on('messageReactionAdd', async (context, rct, user) => { +withContext(on, 'messageReactionAdd', async (context, rct, user) => { if (user.bot) return const { database: db, logger, config } = context diff --git a/bots/discord/src/events/discord/ready.ts b/bots/discord/src/events/discord/ready.ts index e5f9a73..73e8742 100644 --- a/bots/discord/src/events/discord/ready.ts +++ b/bots/discord/src/events/discord/ready.ts @@ -3,9 +3,9 @@ import { appliedPresets } from '$/database/schemas' import { removeRolePreset } from '$/utils/discord/rolePresets' import type { Client } from 'discord.js' import { lt } from 'drizzle-orm' -import { on } from 'src/utils/discord/events' +import { on, withContext } from 'src/utils/discord/events' -export default on('ready', ({ config, logger }, client) => { +export default withContext(on, 'ready', ({ config, logger }, client) => { logger.info(`Connected to Discord API, logged in as ${client.user.displayName} (@${client.user.tag})!`) logger.info( `Bot is in ${client.guilds.cache.size} guilds, if this is not expected, please run the /leave-unknowns command`, diff --git a/bots/discord/src/utils/api/events.ts b/bots/discord/src/utils/api/events.ts index 5f6dbfa..363eda3 100644 --- a/bots/discord/src/utils/api/events.ts +++ b/bots/discord/src/utils/api/events.ts @@ -1,15 +1,20 @@ import type { ClientWebSocketEvents } from '@revanced/bot-api' -import { api } from '../../context' +import * as context from '../../context' -const { client } = api +const { client } = context.api -export const on = (event: Event, listener: ListenerOf) => { - client.on(event, listener) -} +export const withContext = ( + fn: typeof on | typeof once, + event: Event, + listener: ListenerWithContextOf, + // @ts-expect-error: Not smart enough, sorry! +) => fn(event, (...args) => listener(context, ...args)) -export const once = (event: Event, listener: ListenerOf) => { - client.once(event, listener) -} +export const on = (event: Event, listener: ListenerOf) => client.on(event, listener) +export const once = (event: Event, listener: ListenerOf) => client.once(event, listener) export type EventName = keyof ClientWebSocketEvents export type ListenerOf = ClientWebSocketEvents[Event] +export type ListenerWithContextOf = ( + ...args: [typeof import('../../context'), ...Parameters] +) => void | Promise diff --git a/bots/discord/src/utils/discord/events.ts b/bots/discord/src/utils/discord/events.ts index 97e0205..0283471 100644 --- a/bots/discord/src/utils/discord/events.ts +++ b/bots/discord/src/utils/discord/events.ts @@ -3,17 +3,21 @@ import type { ClientEvents } from 'discord.js' const { client } = context.discord -export const on = (event: Event, listener: ListenerOf) => - client.on(event, (...args) => listener(context, ...args)) +export const withContext = ( + fn: typeof on | typeof once, + event: Event, + listener: ListenerWithContextOf, +) => fn(event, (...args) => listener(context, ...args)) -export const once = (event: Event, listener: ListenerOf) => - client.once(event, (...args) => listener(context, ...args)) +export const on = (event: Event, listener: ListenerOf) => client.on(event, listener) +export const once = (event: Event, listener: ListenerOf) => client.once(event, listener) export type EventName = keyof ClientEvents export type EventMap = { [K in EventName]: ListenerOf } -type ListenerOf = ( +type ListenerOf = (...args: ClientEvents[Event]) => void | Promise +type ListenerWithContextOf = ( ...args: [typeof import('$/context'), ...ClientEvents[Event]] ) => void | Promise From ebf1ac7c0891c060c3e9f0894e28e1ac07390b12 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 4 Jul 2024 20:54:17 +0700 Subject: [PATCH 135/312] feat(bots/discord)!: update config --- bots/discord/config.revanced.ts | 3 ++- bots/discord/config.schema.ts | 7 ++++--- bots/discord/config.ts | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/bots/discord/config.revanced.ts b/bots/discord/config.revanced.ts index acd2afe..09c4314 100644 --- a/bots/discord/config.revanced.ts +++ b/bots/discord/config.revanced.ts @@ -35,6 +35,7 @@ export default { }, logLevel: 'debug', api: { - websocketUrl: 'ws://127.0.0.1:3000', + url: 'ws://127.0.0.1:3000', + disconnectLimit: 3, }, } satisfies Config as Config diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 0dbe458..2563266 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -15,7 +15,7 @@ export type Config = { } rolePresets?: { checkExpiredEvery: number - guilds: Record> + guilds: Record> } messageScan?: { allowedAttachmentMimeTypes: string[] @@ -39,11 +39,12 @@ export type Config = { } logLevel: 'none' | 'error' | 'warn' | 'info' | 'log' | 'trace' | 'debug' api: { - websocketUrl: string + url: string + disconnectLimit?: number } } -export type RolePresetData = { +export type RolePresetConfig = { give: string[] take: string[] } diff --git a/bots/discord/config.ts b/bots/discord/config.ts index d02748e..40727a4 100644 --- a/bots/discord/config.ts +++ b/bots/discord/config.ts @@ -66,6 +66,7 @@ export default { }, logLevel: 'log', api: { - websocketUrl: 'ws://127.0.0.1:3000', + url: 'ws://127.0.0.1:3000', + disconnectLimit: 3, }, } satisfies Config as Config From 9b6ba56d999b6a7d24532fcddd56bb12a2aa666d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 4 Jul 2024 21:02:01 +0700 Subject: [PATCH 136/312] chore: use alternative ways to bundle --- apis/websocket/.env.example | 3 - apis/websocket/config.revanced.json | 8 -- apis/websocket/docs/1_configuration.md | 2 +- apis/websocket/docs/2_running.md | 54 ----------- .../websocket/docs/2_running_and_deploying.md | 59 ++++++++++++ apis/websocket/package.json | 6 +- apis/websocket/scripts/build.ts | 23 +++++ apis/websocket/src/index.ts | 17 ++-- apis/websocket/tsconfig.json | 5 +- bots/discord/config.revanced.ts | 41 -------- bots/discord/docs/1_configuration.md | 44 +++++++-- bots/discord/docs/2_adding_autoresponses.md | 2 +- bots/discord/docs/3_running.md | 24 ----- bots/discord/docs/3_running_and_deploying.md | 68 ++++++++++++++ bots/discord/docs/4_commands_and_events.md | 39 +++++--- bots/discord/docs/5_databases.md | 88 ------------------ bots/discord/package.json | 19 ++-- bots/discord/scripts/generate-indexes.ts | 6 ++ bots/discord/src/commands/development/eval.ts | 7 +- .../commands/development/exception-test.ts | 39 +++----- bots/discord/src/commands/development/stop.ts | 10 +- bots/discord/src/commands/fun/coinflip.ts | 2 +- bots/discord/src/commands/fun/reply.ts | 2 +- bots/discord/src/commands/index.ts | 70 +++----------- bots/discord/src/commands/moderation/ban.ts | 2 +- bots/discord/src/commands/moderation/cure.ts | 2 +- bots/discord/src/commands/moderation/mute.ts | 2 +- bots/discord/src/commands/moderation/purge.ts | 2 +- .../src/commands/moderation/role-preset.ts | 2 +- .../src/commands/moderation/slowmode.ts | 2 +- bots/discord/src/commands/moderation/unban.ts | 2 +- .../discord/src/commands/moderation/unmute.ts | 2 +- bots/discord/src/commands/types.ts | 56 +++++++++++ bots/discord/src/context.ts | 12 ++- bots/discord/src/events/api/index.ts | 5 + bots/discord/src/events/discord/index.ts | 10 ++ bots/discord/src/events/register.ts | 2 + bots/discord/src/index.ts | 12 +-- bots/discord/src/types.d.ts | 5 - bots/discord/src/utils/discord/commands.ts | 2 +- bots/discord/src/utils/fs.ts | 21 ++++- bots/discord/tsconfig.json | 16 +++- bun.lockb | Bin 118328 -> 119372 bytes docs/0_development_environment.md | 19 ++-- package.json | 11 ++- packages/api/package.json | 1 + packages/api/tsconfig.json | 3 +- packages/shared/tsconfig.json | 3 +- tsconfig.json | 4 +- turbo.json | 6 +- 50 files changed, 443 insertions(+), 399 deletions(-) delete mode 100755 apis/websocket/config.revanced.json delete mode 100644 apis/websocket/docs/2_running.md create mode 100644 apis/websocket/docs/2_running_and_deploying.md create mode 100644 apis/websocket/scripts/build.ts delete mode 100644 bots/discord/config.revanced.ts delete mode 100644 bots/discord/docs/3_running.md create mode 100644 bots/discord/docs/3_running_and_deploying.md delete mode 100644 bots/discord/docs/5_databases.md create mode 100644 bots/discord/scripts/generate-indexes.ts create mode 100644 bots/discord/src/commands/types.ts create mode 100644 bots/discord/src/events/api/index.ts create mode 100644 bots/discord/src/events/discord/index.ts create mode 100644 bots/discord/src/events/register.ts delete mode 100644 bots/discord/src/types.d.ts mode change 100755 => 100644 bun.lockb diff --git a/apis/websocket/.env.example b/apis/websocket/.env.example index 9e40ca4..abfe5e7 100755 --- a/apis/websocket/.env.example +++ b/apis/websocket/.env.example @@ -1,5 +1,2 @@ -# Safety measures, do not remove -IS_USING_DOT_ENV=1 - # Your Wit.ai token WIT_AI_TOKEN="YOUR_TOKEN_HERE" diff --git a/apis/websocket/config.revanced.json b/apis/websocket/config.revanced.json deleted file mode 100755 index ffb38a0..0000000 --- a/apis/websocket/config.revanced.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "./config.schema.json", - - "address": "127.0.0.1", - "port": 3000, - "ocrConcurrentQueues": 3, - "logLevel": "log" -} diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md index 54999e5..3becf24 100644 --- a/apis/websocket/docs/1_configuration.md +++ b/apis/websocket/docs/1_configuration.md @@ -42,4 +42,4 @@ The possible levels (sorted by their importance descendingly) are: The next page will tell you how to run and bundle the server. -Continue: [🏃🏻‍♂️ Running the server](./2_running.md) +Continue: [🏃🏻‍♂️ Running the server](./2_running_and_deploying.md) diff --git a/apis/websocket/docs/2_running.md b/apis/websocket/docs/2_running.md deleted file mode 100644 index 5d3b99a..0000000 --- a/apis/websocket/docs/2_running.md +++ /dev/null @@ -1,54 +0,0 @@ -# 🏃🏻‍♂️ Running the server - -There are many methods to run the server. Choose one that suits best for the situation. - -## 👷🏻 Development mode (recommended) - -There will be no compilation step, and Bun will automatically watch changes and restart the server for you. - -You can quickly start the server by running: - -```sh -bun dev -``` - -## 🌐 Production mode - -Production mode runs no different from the development server, it simply has less debugging information printed to console by default. However, more production-specific features may come. - -To start the server in production mode, you'll have to: - -1. Set the `NODE_ENV` environment variable to `production` - - > It is very possible to set the value in the `.env` file and let Bun load it, **but it is recommended to set the variable before Bun even starts**. - -2. Start the server - ```sh - bun dev - ``` - -## 📦 Building - -If you're looking to build and host the server somewhere else, you can run: - -```sh -bun bundle -``` - -The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** - -To start up the server, you'll need to install `tesseract.js` first. -```sh -bun install tesseract.js -# or -bun install tesseract.js -g - -# Run the server -bun run index.js -``` - -## ⏭️ What's next - -The next page will tell you about packets. - -Continue: [📨 Packets](./3_packets.md) diff --git a/apis/websocket/docs/2_running_and_deploying.md b/apis/websocket/docs/2_running_and_deploying.md new file mode 100644 index 0000000..ae57227 --- /dev/null +++ b/apis/websocket/docs/2_running_and_deploying.md @@ -0,0 +1,59 @@ +# 🏃🏻‍♂️ Running and deploying the server + +There are many methods to run the server. Choose one that suits best for the situation. + +## 👷🏻 Development mode + +There will be no compilation step, and Bun will automatically watch changes and restart the server for you. + +You can quickly start the server by running: + +```sh +bun dev +``` + +## 📦 Building + +If you're looking to build and host the server somewhere else, you can run: + +```sh +bun run build +``` + +The distribution files will be placed inside the `dist` directory. Inside will include: + +- The default configuration for the API +- Compiled source files of the API + +You'll need to also copy the `node_modules` directory dereferenced if you want to run the distribution files somewhere else. + +## ✈️ Deploying + +To deploy the API, you'll need to: + +1. [Build the API as seen in the previous step](#-building) + +2. Copy contents of the `dist` directory + + ```sh + # For instance, we'll copy them both to /usr/src/api + cp -R ./dist/* /usr/src/api + ``` + +3. Replace the default configuration *(optional)* + +4. Configure environment variables + As seen in [`.env.example`](../.env.example). You can also optionally use a `.env` file which **Bun will automatically load**. + +5. Finally, you can run the API using these commands + + ```sh + cd /usr/src/api + bun run index.js + ``` + +## ⏭️ What's next + +The next page will tell you about packets. + +Continue: [📨 Packets](./3_packets.md) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 5257e09..75718a1 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -6,7 +6,7 @@ "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { - "bundle": "bun build src/index.ts --outdir=dist --target=bun -e tesseract.js", + "bundle": "bun run scripts/build.ts", "dev": "bun run src/index.ts --watch", "build": "bun bundle", "watch": "bun dev" @@ -30,9 +30,11 @@ "@revanced/bot-shared": "workspace:*", "@sapphire/async-queue": "^1.5.2", "chalk": "^5.3.0", - "tesseract.js": "^5.1.0" + "tesseract.js": "^5.1.0", + "ws": "^8.17.1" }, "devDependencies": { + "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } } diff --git a/apis/websocket/scripts/build.ts b/apis/websocket/scripts/build.ts new file mode 100644 index 0000000..6a2d348 --- /dev/null +++ b/apis/websocket/scripts/build.ts @@ -0,0 +1,23 @@ +import { createLogger } from '@revanced/bot-shared' +import { cp } from 'fs/promises' + +async function build(): Promise { + const logger = createLogger() + + logger.info('Building Tesseract.js worker...') + await Bun.build({ + entrypoints: ['../../node_modules/tesseract.js/src/worker-script/node/index.js'], + target: 'bun', + outdir: './dist/worker', + }) + + logger.info('Building WebSocket API...') + await Bun.build({ + entrypoints: ['./src/index.ts'], + outdir: './dist', + target: 'bun', + }) +} + +await build() +await cp('config.json', 'dist/config.json') diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index 110c82e..571ad1a 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -1,6 +1,8 @@ -import { createWorker as createTesseractWorker } from 'tesseract.js' +import { OEM, createWorker as createTesseractWorker } from 'tesseract.js' +import { join as joinPath } from 'path' import { inspect as inspectObject } from 'util' +import { exists as pathExists } from 'fs/promises' import Client from './classes/Client' @@ -36,10 +38,6 @@ if (!['development', 'production'].includes(environment)) { logger.info(`Running in ${environment} mode...`) -if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { - logger.warn('You seem to be using .env files, this is generally not a good idea in production...') -} - if (!process.env['WIT_AI_TOKEN']) { logger.error('WIT_AI_TOKEN is not defined in the environment variables') process.exit(1) @@ -47,7 +45,14 @@ if (!process.env['WIT_AI_TOKEN']) { // Workers and API clients -const tesseract = await createTesseractWorker('eng') +const TesseractWorkerPath = joinPath(import.meta.dir, 'worker', 'index.js') +const TesseractCompiledWorkerExists = await pathExists(TesseractWorkerPath) +const tesseract = await createTesseractWorker( + 'eng', + OEM.DEFAULT, + TesseractCompiledWorkerExists ? { workerPath: TesseractWorkerPath } : undefined, +) + const wit = { token: process.env['WIT_AI_TOKEN']!, async fetch(route: string, options?: RequestInit) { diff --git a/apis/websocket/tsconfig.json b/apis/websocket/tsconfig.json index b80117e..1810ebe 100755 --- a/apis/websocket/tsconfig.json +++ b/apis/websocket/tsconfig.json @@ -7,8 +7,9 @@ "target": "ESNext", "lib": ["ESNext"], "composite": false, - "skipLibCheck": true + "skipLibCheck": true, + "resolveJsonModule": true }, "exclude": ["node_modules", "dist"], - "include": ["./*.json", "src/**/*.ts"] + "include": ["./*.json", "src/**/*.ts", "scripts/**/*.ts"] } diff --git a/bots/discord/config.revanced.ts b/bots/discord/config.revanced.ts deleted file mode 100644 index 09c4314..0000000 --- a/bots/discord/config.revanced.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { PermissionFlagsBits } from 'discord.js' -import type { Config } from './config.schema' - -export default { - owners: ['629368283354628116', '737323631117598811', '282584705218510848'], - guilds: ['952946952348270622'], - messageScan: { - filter: { - // Team, Mod, Immunity - roles: ['952987191401926697', '955220417969262612', '1027874293192863765'], - users: [], - // Team, Development - channels: ['952987428786941952', '953965039105232906'], - whitelist: false, - }, - humanCorrections: { - falsePositiveLabel: 'false_positive', - allow: { - members: { - // Team, Supporter - roles: ['952987191401926697', '1019903194941362198'], - permissions: PermissionFlagsBits.ManageMessages, - }, - }, - }, - allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], - responses: [ - { - triggers: { - text: [{ label: 'false_positive', threshold: 0 }], - }, - response: null, - }, - ], - }, - logLevel: 'debug', - api: { - url: 'ws://127.0.0.1:3000', - disconnectLimit: 3, - }, -} satisfies Config as Config diff --git a/bots/discord/docs/1_configuration.md b/bots/discord/docs/1_configuration.md index 8cc2631..09106d0 100644 --- a/bots/discord/docs/1_configuration.md +++ b/bots/discord/docs/1_configuration.md @@ -1,18 +1,22 @@ # ⚙️ Configuration +This page tells you how to configure the bot. + +## 📄 JSON config + See [`config.ts`](../config.ts). --- -### `config.owners` +#### `config.owners` User IDs of the owners of the bot. Only add owners when needed. -### `config.guilds` +#### `config.guilds` Servers the bot is allowed to be and register commands in. -### `config.logLevel` +#### `config.logLevel` The level of logs to print to console. If the level is more important or equally important to set level, it will be forwarded to the console. @@ -26,14 +30,42 @@ The possible levels (sorted by their importance descendingly) are: - `log` - `debug` -### `config.api.websocketUrl` +#### `config.api.url` -The WebSocket URL to connect to (including port). Soon auto-discovery will be implemented. +WebSocket URL to connect to (including port). Soon auto-discovery will be implemented. -### `config.messageScan` +#### `config.api.disconnectLimit` + +Amount of times to allow disconnecting before exiting with code `1`. + +#### `config.messageScan` [Please see the next page.](./2_adding_autoresponses.md) +#### `config.moderation` + +TBD. + +#### `config.rolePresets` + +TBD. + +## 🟰 Environment variables + +See [`.env.example`](../.env.example). +You can set environment variables in your shell or use a `.env` file which **Bun will automatically load**. + +--- + +#### `DISCORD_TOKEN` + +The Discord bot token. + +#### `DATABASE_URL` + +The database URL, since we're using SQLite, we'll be using the `file` protocol. +Example values are: `file:./revanced.db`, `file:./db.sqlite`, `file:./discord_bot.sqlite` + ## ⏭️ What's next The next page will tell you how to configure auto-responses. diff --git a/bots/discord/docs/2_adding_autoresponses.md b/bots/discord/docs/2_adding_autoresponses.md index bb028a8..5d6a504 100644 --- a/bots/discord/docs/2_adding_autoresponses.md +++ b/bots/discord/docs/2_adding_autoresponses.md @@ -86,4 +86,4 @@ filterOverride: { The next page will tell you how to run and bundle the bot. -Continue: [🏃🏻‍♂️ Running the bot](./3_running.md) +Continue: [🏃🏻‍♂️ Running the bot](./3_running_and_deploying.md) diff --git a/bots/discord/docs/3_running.md b/bots/discord/docs/3_running.md deleted file mode 100644 index f82e2ad..0000000 --- a/bots/discord/docs/3_running.md +++ /dev/null @@ -1,24 +0,0 @@ -# 🏃🏻‍♂️ Running the bot - -There are two methods to run the bot. Choose one that suits best for the situation. - -## 👷🏻 Development mode (recommended) - -There will be no compilation step, and Bun will automatically watch changes and restart the bot for you. - -You can quickly start the bot by running: - -```sh -bun dev -``` - -## 📦 Building - -There's unfortunately no way to build/bundle the bot yet due to how dynamic imports currently work, though we have a few ideas that may work. -As a workaround, you can zip up the whole project, unzip, and run it in development mode using Bun. - -## ⏭️ What's next - -The next page will tell you how to add commands and listen to events to the bot. - -Continue: [✨ Adding commands and listening to events](./4_commands_and_events.md) diff --git a/bots/discord/docs/3_running_and_deploying.md b/bots/discord/docs/3_running_and_deploying.md new file mode 100644 index 0000000..e191359 --- /dev/null +++ b/bots/discord/docs/3_running_and_deploying.md @@ -0,0 +1,68 @@ +# 🏃🏻‍♂️ Running and deploying + +There are two methods to run the bot. Choose one that suits best for the situation. + +## 👷🏻 Development mode (recommended) + +There will be no compilation step, and Bun will automatically watch changes and restart the bot for you. + +You can quickly start the bot by running: + +```sh +bun dev +``` + +## 📦 Building + +To build the bot, you can run: + +```sh +bun run build +``` + +The distribution files will be placed inside the `dist` directory. Inside will include: + +- The default configuration for the bot +- An empty database for the bot with schemas configured +- Compiled source files of the bot + +## ✈️ Deploying + +To deploy the bot, you'll need to: + +1. Replace the `config.ts` file with your own configuration _(optional)_ +2. [Build the bot as seen in the previous step](#-building) +3. Run the `reload-slash-commands` script + This is to ensure all commands are registered, so they can be used. + **It may take up to 2 hours until **global** commands are updated. This is a Discord limitation.** + + ```sh + # Assuming you're in the workspace's root (NOT REPOSITORY ROOT) + bun run scripts/reload-slash-commands.ts + ``` + +4. Copy contents of the `dist` directory + + ```sh + # For instance, we'll copy them both to /usr/src/discord-bot + # Assuming you're in the workspace's root (NOT REPOSITORY ROOT) + cp -R ./dist/* /usr/src/discord-bot + ``` + +5. Replace the default empty database with your own _(optional)_ + +6. Configure environment variables + As seen in [`.env.example`](../.env.example). You can also optionally use a `.env` file which **Bun will automatically load**. + +7. Finally, run the bot + + ```sh + cd /usr/src/discord-bot + bun run src/index.js + ``` + +## ⏭️ What's next + +The next page will tell you how to add commands and listen to events to the bot. + +Continue: [✨ Adding commands and listening to events](./4_commands_and_events.md) diff --git a/bots/discord/docs/4_commands_and_events.md b/bots/discord/docs/4_commands_and_events.md index fd1df88..e1bb033 100644 --- a/bots/discord/docs/4_commands_and_events.md +++ b/bots/discord/docs/4_commands_and_events.md @@ -74,34 +74,49 @@ export default { Events are a bit different. We have 2 different event systems for both Discord API and our own bot API. This means the [`src/events`](../src/events) directory will have 2 separate directories inside. They are specific to the respective API, but the utility functions make the experience with both of them very similar. -To start adding events, you can use this template: +To start adding events, you can use these templates: + +##### Discord event template ```ts -// For Discord events (remove functions you do not use) -import { on, once } from '$utils/discord/events' +import { on, once, withContext } from '$utils/discord/events' -// You will have auto-complete and types for all of them, don't worry! -// WARNING: The first argument is the `context` object for Discord events -// This is intended by design because Discord events usually always use it. -on('eventName', async (context, arg1, arg2, ...) => { - // Do something in here when the event is triggered +on('eventName', async (arg1, arg2, ...) => { + // Do something when the event is triggered +}) + +once('eventName', async (arg1, arg2, ...) => { + // Do something for only a single time after it's triggered, never again +}) + +withContext(on, 'eventName', async (context, arg1, arg2, ...) => { + // Do some other thing that requires the context object }) ``` +##### API events template + ```ts -// For "Helper" events (remove functions you do not use) import { on, once } from '$utils/api/events' -// You will have auto-complete and types for all of them, don't worry! on('eventName', async (arg1, arg2, ...) => { - // Do something in here when the event is triggered + // Do something when the event is triggered +}) + +once('eventName', async (arg1, arg2, ...) => { + // Do something for only a single time after it's triggered, never again }) ``` API events are stored in [`src/events/api`](../src/events/api), and Discord events are in [`src/events/discord`](../src/events/discord). +### 📛 Event file naming conventions + +Since a single event file can have multiple listeners, you should name exactly what the file handles. +For example, when a nickname change happens, a member joins, or a member sends a message, the bot is required to cure their nickname. Therefore we would name the event file `curedRequired.ts`. + > [!NOTE] -> If you need multiple event listeners for the same exact event, you can put them in a directory with the event name and rename the listeners to what they handle specifically. You can see how we do it in [`src/events/discord/interactionCreate`](../src/events/discord/interactionCreate). +> If you need multiple event listeners for the same exact event **but also need more abstraction**, you can put them in a directory with the event name and rename the listeners to what they handle specifically. You can see how we do it in [`src/events/discord/interactionCreate`](../src/events/discord/interactionCreate). ## ⏭️ What's next diff --git a/bots/discord/docs/5_databases.md b/bots/discord/docs/5_databases.md deleted file mode 100644 index d08e974..0000000 --- a/bots/discord/docs/5_databases.md +++ /dev/null @@ -1,88 +0,0 @@ -# 🫙 Storing data - -We use SQLite to store every piece of persistent data. By using Bun, we get access to the `bun:sqlite` module which allows us to easily do SQLite operations. - -## 🪄 Creating a database - -You can easily create a database by initializing the `BasicDatabase` class: - -```ts -interface MyDatabase { - field: string - key: string -} - -const db = new BasicDatabase( - // File path - 'database_file.db', - // Database schema, in SQL - `field TEXT NOT NULL, key TEXT PRIMARY KEY NOT NULL`, - // Custom table name (optional, defaults to 'data'), - 'data' -) -``` - -## 📝 Writing data - -Initializing `MyDatabase` will immediately create/open the `database_file.db` file. To write data, you can use the `insert` or `update` method: - -```ts -const key = 'my key' -const field = 'some data' - -// Order is according to the schema -// db.insert(...columns) -db.insert(field, key) - -const field2 = 'some other data' - -// db.update(data, filter) -db.update({ - field: field2 -}, `key = ${key}`) -``` - -You can also delete a row: - -```ts -db.delete(`key = ${key}`) - -console.log(db.select(`key = ${key}`)) // null -``` - -## 👀 Reading data - -To get data using a filter, you can use the `select` method: - -```ts -// We insert it back -db.insert(field, key) - -const data = db.select('*', `key = ${key}`) -console.log(data) // { key: 'my key', field: 'some other data' } - -const { key: someKey } = db.select('key', `field = '${field2}'`) -console.log(someKey) // 'my key' -``` - - -If the existing abstractions aren't enough, you can also use the `run`, `prepare`, or `query` method: - -```ts -// Enable WAL -db.run('PRAGMA journal_mode=WAL') - -const selectFromKey = db.prepare('SELECT * FROM data WHERE key = $key') - -console.log( - selectFromKey.get({ - $key: key - }) -) // { key: 'my key', field: 'some other data' } - -console.log( - selectFromKey.get({ - $key: 'non existent key' - }) -) // null -``` diff --git a/bots/discord/package.json b/bots/discord/package.json index 077ac98..780b8a9 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -4,13 +4,15 @@ "private": true, "version": "0.1.0", "description": "🤖 Discord bot assisting ReVanced", - "main": "dist/index.js", + "main": "src/index.ts", "scripts": { "register": "bun run scripts/reload-slash-commands.ts", - "dev": "bun --watch src/index.ts", - "prepare": "drizzle-kit push", - "build": "tsc", - "watch": "bun dev" + "start": "bun run scripts/generate-indexes.ts && bun run src/index.ts", + "dev": "bun run scripts/generate-indexes.ts && bun --watch src/index.ts", + "build:config": "bun build config.ts --outdir=dist", + "build": "bun prepare && bun build:config && bun build src/index.ts -e ./config.js --target=bun --outdir=dist/src && DATABASE_URL=dist/db.sqlite3 drizzle-kit push", + "watch": "bun dev", + "prepare": "bun run scripts/generate-indexes.ts" }, "repository": { "type": "git", @@ -28,15 +30,18 @@ }, "homepage": "https://github.com/revanced/revanced-helper#readme", "dependencies": { + "@discordjs/builders": "^1.8.2", + "@discordjs/rest": "^2.3.0", + "@libsql/client": "^0.6.2", "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", "decancer": "^3.2.2", "discord.js": "^14.15.3", + "drizzle-kit": "^0.22.7", "drizzle-orm": "^0.31.2" }, "devDependencies": { - "@libsql/client": "^0.6.2", - "drizzle-kit": "^0.22.7" + "discord-api-types": "^0.37.91" } } diff --git a/bots/discord/scripts/generate-indexes.ts b/bots/discord/scripts/generate-indexes.ts new file mode 100644 index 0000000..bf2762d --- /dev/null +++ b/bots/discord/scripts/generate-indexes.ts @@ -0,0 +1,6 @@ +import { join } from 'path' +import { generateCommandsIndex, generateEventsIndex } from '../src/utils/fs' + +await generateCommandsIndex(join(import.meta.dir, '../src/commands')) +await generateEventsIndex(join(import.meta.dir, '../src/events/discord')) +await generateEventsIndex(join(import.meta.dir, '../src/events/api')) diff --git a/bots/discord/src/commands/development/eval.ts b/bots/discord/src/commands/development/eval.ts index f145a79..223d66d 100644 --- a/bots/discord/src/commands/development/eval.ts +++ b/bots/discord/src/commands/development/eval.ts @@ -2,12 +2,12 @@ import { inspect } from 'util' import { SlashCommandBuilder } from 'discord.js' import { createSuccessEmbed } from '$/utils/discord/embeds' -import type { Command } from '..' +import type { Command } from '../types' export default { data: new SlashCommandBuilder() .setName('eval') - .setDescription('Evaluates something') + .setDescription('Make the bot less sentient by evaluating code') .addStringOption(option => option.setName('code').setDescription('The code to evaluate').setRequired(true)) .setDMPermission(true) .toJSON(), @@ -15,8 +15,7 @@ export default { ownerOnly: true, global: true, - // @ts-expect-error: Needed for science - async execute(context, interaction) { + async execute(_, interaction) { const code = interaction.options.getString('code', true) await interaction.reply({ diff --git a/bots/discord/src/commands/development/exception-test.ts b/bots/discord/src/commands/development/exception-test.ts index fb9440a..70dfbae 100644 --- a/bots/discord/src/commands/development/exception-test.ts +++ b/bots/discord/src/commands/development/exception-test.ts @@ -1,41 +1,26 @@ import { SlashCommandBuilder } from 'discord.js' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import type { Command } from '..' +import type { Command } from '../types' export default { data: new SlashCommandBuilder() .setName('exception-test') - .setDescription('throw up pls') + .setDescription('Makes the bot intentionally hate you by throwing an exception') .addStringOption(option => option .setName('type') .setDescription('The type of exception to throw') - .addChoices({ - name: 'process exception', - value: 'Process', - }) - .addChoices({ - name: 'generic error', - value: 'Generic', - }) - .addChoices({ - name: 'invalid argument', - value: 'InvalidArgument', - }) - .addChoices({ - name: 'invalid channel', - value: 'InvalidChannel', - }) - .addChoices({ - name: 'invalid user', - value: 'InvalidUser', - }) - .addChoices({ - name: 'invalid duration', - value: 'InvalidDuration', - }) - .setRequired(true), + .setRequired(true) + .addChoices( + Object.keys(CommandErrorType).map( + k => + ({ + name: k, + value: k, + }) as const, + ), + ), ) .setDMPermission(true) .toJSON(), diff --git a/bots/discord/src/commands/development/stop.ts b/bots/discord/src/commands/development/stop.ts index 1627d54..2d08763 100644 --- a/bots/discord/src/commands/development/stop.ts +++ b/bots/discord/src/commands/development/stop.ts @@ -1,9 +1,15 @@ import { SlashCommandBuilder } from 'discord.js' -import type { Command } from '..' +import type { Command } from '../types' export default { - data: new SlashCommandBuilder().setName('stop').setDescription('Stops the bot').setDMPermission(true).toJSON(), + data: new SlashCommandBuilder() + .setName('stop') + .setDescription( + "You don't want to run this unless the bot starts to go insane, and like, you really need to stop it.", + ) + .setDMPermission(true) + .toJSON(), ownerOnly: true, global: true, diff --git a/bots/discord/src/commands/fun/coinflip.ts b/bots/discord/src/commands/fun/coinflip.ts index 5b724f5..e6e7a95 100644 --- a/bots/discord/src/commands/fun/coinflip.ts +++ b/bots/discord/src/commands/fun/coinflip.ts @@ -2,7 +2,7 @@ import { applyCommonEmbedStyles } from '$/utils/discord/embeds' import { EmbedBuilder, SlashCommandBuilder } from 'discord.js' -import type { Command } from '..' +import type { Command } from '../types' export default { data: new SlashCommandBuilder().setName('coinflip').setDescription('Do a coinflip!').setDMPermission(true).toJSON(), diff --git a/bots/discord/src/commands/fun/reply.ts b/bots/discord/src/commands/fun/reply.ts index 73fe802..4a5c530 100644 --- a/bots/discord/src/commands/fun/reply.ts +++ b/bots/discord/src/commands/fun/reply.ts @@ -1,7 +1,7 @@ import { SlashCommandBuilder, type TextBasedChannel } from 'discord.js' import { config } from '$/context' -import type { Command } from '..' +import type { Command } from '../types' export default { data: new SlashCommandBuilder() diff --git a/bots/discord/src/commands/index.ts b/bots/discord/src/commands/index.ts index e782811..3bd4ce2 100644 --- a/bots/discord/src/commands/index.ts +++ b/bots/discord/src/commands/index.ts @@ -1,56 +1,16 @@ -import type { SlashCommandBuilder } from '@discordjs/builders' -import type { ChatInputCommandInteraction } from 'discord.js' +// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH -// Temporary system -export type Command = { - data: ReturnType - // The function has to return void or Promise - // because TS may complain about some code paths not returning a value - /** - * The function to execute when this command is triggered - * @param interaction The interaction that triggered this command - */ - execute: ( - context: typeof import('../context'), - interaction: ChatInputCommandInteraction, - info: Info, - ) => Promise | void - memberRequirements?: { - /** - * The mode to use when checking for requirements. - * - `all` means that the user needs meet all requirements specified. - * - `any` means that the user needs to meet any of the requirements specified. - * - * @default "all" - */ - mode?: 'all' | 'any' - /** - * The permissions required to use this command (in BitFields). - * - * - **0n** means that everyone can use this command. - * - **-1n** means that only bot owners can use this command. - * @default -1n - */ - permissions?: bigint - /** - * The roles required to use this command. - * By default, this is set to `[]`. - */ - roles?: string[] - } - /** - * Whether this command can only be used by bot owners. - * @default false - */ - ownerOnly?: boolean - /** - * Whether to register this command as a global slash command. - * This is set to `false` and commands will be registered in allowed guilds only by default. - * @default false - */ - global?: boolean -} - -export interface Info { - userIsOwner: boolean -} +import './index' +import './fun/reply' +import './fun/coinflip' +import './development/eval' +import './development/stop' +import './development/exception-test' +import './moderation/purge' +import './moderation/cure' +import './moderation/role-preset' +import './moderation/mute' +import './moderation/unmute' +import './moderation/unban' +import './moderation/slowmode' +import './moderation/ban' diff --git a/bots/discord/src/commands/moderation/ban.ts b/bots/discord/src/commands/moderation/ban.ts index 42d8f57..1b702b9 100644 --- a/bots/discord/src/commands/moderation/ban.ts +++ b/bots/discord/src/commands/moderation/ban.ts @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from 'discord.js' -import type { Command } from '..' +import type { Command } from '../types' import CommandError, { CommandErrorType } from '$/classes/CommandError' import { config } from '$/context' diff --git a/bots/discord/src/commands/moderation/cure.ts b/bots/discord/src/commands/moderation/cure.ts index 60d9a0b..c00270f 100644 --- a/bots/discord/src/commands/moderation/cure.ts +++ b/bots/discord/src/commands/moderation/cure.ts @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from 'discord.js' -import type { Command } from '..' +import type { Command } from '../types' import { config } from '$/context' import { createSuccessEmbed } from '$/utils/discord/embeds' diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index fa6ea26..af3b26f 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -2,7 +2,7 @@ import { SlashCommandBuilder } from 'discord.js' import CommandError, { CommandErrorType } from '$/classes/CommandError' import { applyRolePreset } from '$/utils/discord/rolePresets' -import type { Command } from '..' +import type { Command } from '../types' import { config } from '$/context' import { createModerationActionEmbed } from '$/utils/discord/embeds' diff --git a/bots/discord/src/commands/moderation/purge.ts b/bots/discord/src/commands/moderation/purge.ts index b1eb0e4..75eb398 100644 --- a/bots/discord/src/commands/moderation/purge.ts +++ b/bots/discord/src/commands/moderation/purge.ts @@ -4,7 +4,7 @@ import CommandError, { CommandErrorType } from '$/classes/CommandError' import { config } from '$/context' import { applyCommonEmbedStyles } from '$/utils/discord/embeds' -import type { Command } from '..' +import type { Command } from '../types' export default { data: new SlashCommandBuilder() diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts index f8eac4b..0d4cbff 100644 --- a/bots/discord/src/commands/moderation/role-preset.ts +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -4,7 +4,7 @@ import CommandError, { CommandErrorType } from '$/classes/CommandError' import { sendPresetReplyAndLogs } from '$/utils/discord/moderation' import { applyRolePreset, removeRolePreset } from '$/utils/discord/rolePresets' import { parseDuration } from '$/utils/duration' -import type { Command } from '..' +import type { Command } from '../types' export default { data: new SlashCommandBuilder() diff --git a/bots/discord/src/commands/moderation/slowmode.ts b/bots/discord/src/commands/moderation/slowmode.ts index 61b49da..8cc3815 100644 --- a/bots/discord/src/commands/moderation/slowmode.ts +++ b/bots/discord/src/commands/moderation/slowmode.ts @@ -5,7 +5,7 @@ import { SlashCommandBuilder } from 'discord.js' import CommandError, { CommandErrorType } from '$/classes/CommandError' import { config } from '$/context' -import type { Command } from '..' +import type { Command } from '../types' export default { data: new SlashCommandBuilder() diff --git a/bots/discord/src/commands/moderation/unban.ts b/bots/discord/src/commands/moderation/unban.ts index 9adbe89..5bcfa93 100644 --- a/bots/discord/src/commands/moderation/unban.ts +++ b/bots/discord/src/commands/moderation/unban.ts @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from 'discord.js' -import type { Command } from '..' +import type { Command } from '../types' import { config } from '$/context' import { createModerationActionEmbed } from '$/utils/discord/embeds' diff --git a/bots/discord/src/commands/moderation/unmute.ts b/bots/discord/src/commands/moderation/unmute.ts index 29860ea..2255a09 100644 --- a/bots/discord/src/commands/moderation/unmute.ts +++ b/bots/discord/src/commands/moderation/unmute.ts @@ -7,7 +7,7 @@ import { createModerationActionEmbed } from '$/utils/discord/embeds' import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { removeRolePreset } from '$/utils/discord/rolePresets' import { and, eq } from 'drizzle-orm' -import type { Command } from '..' +import type { Command } from '../types' export default { data: new SlashCommandBuilder() diff --git a/bots/discord/src/commands/types.ts b/bots/discord/src/commands/types.ts new file mode 100644 index 0000000..e782811 --- /dev/null +++ b/bots/discord/src/commands/types.ts @@ -0,0 +1,56 @@ +import type { SlashCommandBuilder } from '@discordjs/builders' +import type { ChatInputCommandInteraction } from 'discord.js' + +// Temporary system +export type Command = { + data: ReturnType + // The function has to return void or Promise + // because TS may complain about some code paths not returning a value + /** + * The function to execute when this command is triggered + * @param interaction The interaction that triggered this command + */ + execute: ( + context: typeof import('../context'), + interaction: ChatInputCommandInteraction, + info: Info, + ) => Promise | void + memberRequirements?: { + /** + * The mode to use when checking for requirements. + * - `all` means that the user needs meet all requirements specified. + * - `any` means that the user needs to meet any of the requirements specified. + * + * @default "all" + */ + mode?: 'all' | 'any' + /** + * The permissions required to use this command (in BitFields). + * + * - **0n** means that everyone can use this command. + * - **-1n** means that only bot owners can use this command. + * @default -1n + */ + permissions?: bigint + /** + * The roles required to use this command. + * By default, this is set to `[]`. + */ + roles?: string[] + } + /** + * Whether this command can only be used by bot owners. + * @default false + */ + ownerOnly?: boolean + /** + * Whether to register this command as a global slash command. + * This is set to `false` and commands will be registered in allowed guilds only by default. + * @default false + */ + global?: boolean +} + +export interface Info { + userIsOwner: boolean +} diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 9568561..2908e4f 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -4,13 +4,15 @@ import { createLogger } from '@revanced/bot-shared' import { ActivityType, Client as DiscordClient, Partials } from 'discord.js' import { drizzle } from 'drizzle-orm/bun-sqlite' -import config from '../config' +// Export config first, as commands require them +import config from '../config.js' +export { config } + +import * as commands from './commands' import * as schemas from './database/schemas' -import { loadCommands } from '$utils/discord/commands' -import { pathJoinCurrentDir } from '$utils/fs' +import type { Command } from './commands/types' -export { config } export const logger = createLogger({ level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, }) @@ -59,5 +61,5 @@ export const discord = { ], }, }), - commands: await loadCommands(pathJoinCurrentDir(import.meta.url, 'commands')), + commands: Object.fromEntries(Object.values(commands).map((cmd) => [cmd.data.name, cmd])) as Record, } as const diff --git a/bots/discord/src/events/api/index.ts b/bots/discord/src/events/api/index.ts new file mode 100644 index 0000000..80e2524 --- /dev/null +++ b/bots/discord/src/events/api/index.ts @@ -0,0 +1,5 @@ +// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH + +import './ready' +import './disconnect' +import './index' diff --git a/bots/discord/src/events/discord/index.ts b/bots/discord/src/events/discord/index.ts new file mode 100644 index 0000000..4f305b0 --- /dev/null +++ b/bots/discord/src/events/discord/index.ts @@ -0,0 +1,10 @@ +// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH + +import './ready' +import './cureRequired' +import './index' +import './messageCreate/messageScanRequired' +import './messageReactionAdd/correctResponse' +import './interactionCreate/chatCommand' +import './interactionCreate/correctResponse' +import './guildMemberAdd/applyRolePresets' diff --git a/bots/discord/src/events/register.ts b/bots/discord/src/events/register.ts new file mode 100644 index 0000000..fbab0e6 --- /dev/null +++ b/bots/discord/src/events/register.ts @@ -0,0 +1,2 @@ +import './discord' +import './api' diff --git a/bots/discord/src/index.ts b/bots/discord/src/index.ts index 8b80c9f..1588c24 100644 --- a/bots/discord/src/index.ts +++ b/bots/discord/src/index.ts @@ -1,7 +1,8 @@ import { api, discord, logger } from '$/context' -import { listAllFilesRecursive, pathJoinCurrentDir } from '$utils/fs' import { getMissingEnvironmentVariables } from '@revanced/bot-shared' +import './events/register' + // Check if token exists const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN', 'DATABASE_URL']) if (missingEnvs.length) { @@ -9,14 +10,5 @@ if (missingEnvs.length) { process.exit(1) } -for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'api'))) { - await import(event) -} - api.client.connect() - -for (const event of listAllFilesRecursive(pathJoinCurrentDir(import.meta.url, 'events', 'discord'))) { - await import(event) -} - discord.client.login() diff --git a/bots/discord/src/types.d.ts b/bots/discord/src/types.d.ts deleted file mode 100644 index 24ab764..0000000 --- a/bots/discord/src/types.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -type IfExtends = T extends U ? True : False -type IfTrue = IfExtends -type EmptyObject = Record -type ValuesOf = T[keyof T] -type MaybeArray = T | T[] diff --git a/bots/discord/src/utils/discord/commands.ts b/bots/discord/src/utils/discord/commands.ts index 5c4e454..188946f 100644 --- a/bots/discord/src/utils/discord/commands.ts +++ b/bots/discord/src/utils/discord/commands.ts @@ -1,4 +1,4 @@ -import type { Command } from '$commands' +import type { Command } from '$commands/types' import { listAllFilesRecursive } from '$utils/fs' export const loadCommands = async (dir: string) => { diff --git a/bots/discord/src/utils/fs.ts b/bots/discord/src/utils/fs.ts index 0fa0b7f..f3661a2 100644 --- a/bots/discord/src/utils/fs.ts +++ b/bots/discord/src/utils/fs.ts @@ -1,11 +1,22 @@ -import { readdirSync } from 'fs' -import { dirname, join } from 'path' -import { fileURLToPath } from 'bun' +import { readdirSync, writeFileSync } from 'fs' +import { join, relative } from 'path' export const listAllFilesRecursive = (dir: string): string[] => readdirSync(dir, { recursive: true, withFileTypes: true }) .filter(x => x.isFile()) .map(x => join(x.parentPath, x.name)) -export const pathJoinCurrentDir = (importMetaUrl: string, ...objects: [string, ...string[]]) => - join(dirname(fileURLToPath(importMetaUrl)), ...objects) +export const generateCommandsIndex = (dirPath: string) => generateIndexes(dirPath, x => !x.endsWith('types.ts')) + +export const generateEventsIndex = (dirPath: string) => generateIndexes(dirPath) + +const generateIndexes = async (dirPath: string, pathFilter?: (path: string) => boolean) => { + const files = listAllFilesRecursive(dirPath) + .filter(x => (x.endsWith('.ts') && !x.endsWith('index.ts') && pathFilter ? pathFilter(x) : true)) + .map(x => relative(dirPath, x)) + + writeFileSync( + join(dirPath, 'index.ts'), + `// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH\n\n${files.map(c => `import './${c.split('.').at(-2)}'`).join('\n')}`, + ) +} diff --git a/bots/discord/tsconfig.json b/bots/discord/tsconfig.json index 8176edc..890c538 100755 --- a/bots/discord/tsconfig.json +++ b/bots/discord/tsconfig.json @@ -18,8 +18,18 @@ "$commands": ["./src/commands/index.ts"], "$commands/*": ["./src/commands/*"] }, - "skipLibCheck": true + "skipLibCheck": true, + "plugins": [ + { + "transform": "typescript-transform-path-rewrite" + } + ] }, - "exclude": ["node_modules", "dist"], - "include": ["./**/*.ts", "./*.ts"] + "exclude": [ + "node_modules", + "dist", + "./config.schema.ts", + "./drizzle.config.ts" + ], + "include": ["./src/**/*.ts", "./scripts/**/*.ts"] } diff --git a/bun.lockb b/bun.lockb old mode 100755 new mode 100644 index 6ee688bd593127a4800e3e919ceebc9f6aeee04a..b4f32d2ffdae65493b8468c1d067190580eac1f2 GIT binary patch delta 24791 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 aT~ancP1ti0G{&CjbEY0}EOJ delta 24193 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%0D{QNAU!%anZsd5S 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 diff --git a/docs/0_development_environment.md b/docs/0_development_environment.md index 87af6c1..817d41a 100644 --- a/docs/0_development_environment.md +++ b/docs/0_development_environment.md @@ -17,17 +17,24 @@ To start developing, you'll need to set up the development environment first. 3. Install dependencies - ```sh - bun install - ``` + ```sh + bun install + ``` -4. Build packages/libraries +4. Install Git hooks for linting (optional, but recommended) ```sh - bun run build + bunx lefthook install ``` -5. Change your directory to a project's root +5. Build packages/libraries + + ```sh + bun run build:packages + ``` + +6. Change your directory to a project's root + ```sh # WebSocket API cd apis/websocket diff --git a/package.json b/package.json index 99d2a3c..db9b265 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,14 @@ "license": "GPL-3.0-or-later", "type": "module", "author": "Palm (https://palmdevs.me)", - "workspaces": ["apis/*", "bots/*", "packages/*"], + "workspaces": ["packages/*", "apis/*", "bots/*"], "scripts": { "build": "turbo run build", + "build:packages": "turbo run build --filter=\"./packages/*\"", "watch": "turbo run watch", - "flint": "biome check --apply .", + "flint": "biome check --write .", "flint:check": "biome check .", - "clint": "commitlint --edit", - "prepare": "lefthook install" + "clint": "commitlint --edit" }, "homepage": "https://github.com/revanced/revanced-helper#readme", "repository": { @@ -27,6 +27,7 @@ "Palm (https://palmdevs.me)", "ReVanced (https://revanced.app)" ], + "packageManager": "pnpm@9.4.0", "devDependencies": { "@biomejs/biome": "^1.8.2", "@commitlint/cli": "^19.3.0", @@ -38,7 +39,7 @@ "concurrently": "^8.2.2", "conventional-changelog-conventionalcommits": "^7.0.2", "lefthook": "^1.6.18", - "turbo": "^1.13.4", + "turbo": "2", "typescript": "^5.5.2" }, "trustedDependencies": ["@biomejs/biome", "esbuild", "lefthook"] diff --git a/packages/api/package.json b/packages/api/package.json index aa22783..884446b 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -31,6 +31,7 @@ "ws": "^8.17.1" }, "devDependencies": { + "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } } diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 6c0164e..1cd3a5f 100755 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -7,5 +7,6 @@ "module": "ESNext", "composite": true, "noEmit": false - } + }, + "include": ["src/**/*.ts"] } diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index 6c0164e..1cd3a5f 100755 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -7,5 +7,6 @@ "module": "ESNext", "composite": true, "noEmit": false - } + }, + "include": ["src/**/*.ts"] } diff --git a/tsconfig.json b/tsconfig.json index 9962c1d..3280e50 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,5 +21,7 @@ "allowSyntheticDefaultImports": true, "isolatedModules": true, "allowImportingTsExtensions": false - } + }, + "include": ["./**/*"], + "exclude": ["./packages/**/*"] } diff --git a/turbo.json b/turbo.json index 7cacc10..b31dd44 100755 --- a/turbo.json +++ b/turbo.json @@ -1,14 +1,14 @@ { "$schema": "https://turbo.build/schema.json", - "pipeline": { + "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**"], - "outputMode": "errors-only" + "outputLogs": "errors-only" }, "watch": { "dependsOn": ["^watch"], - "outputMode": "errors-only" + "outputLogs": "errors-only" } } } From 77fefb9bef286a22f40a4d76b79c64fcc5a2467f Mon Sep 17 00:00:00 2001 From: Palm Date: Mon, 8 Jul 2024 17:18:39 +0000 Subject: [PATCH 137/312] chore: fix more build issues BREAKING CHANGE: In `@revanced/discord-bot`, its environment variable `DATABASE_URL` has been renamed to `DATABASE_PATH` and the `file:` prefix is no longer needed --- .../websocket/docs/2_running_and_deploying.md | 5 ++--- bots/discord/.env.example | 2 +- bots/discord/docs/1_configuration.md | 6 +++--- bots/discord/drizzle.config.ts | 2 +- bots/discord/package.json | 2 +- bots/discord/src/commands/index.ts | 16 ++++++++-------- bots/discord/src/context.ts | 7 +++++-- bots/discord/src/events/api/index.ts | 2 +- bots/discord/src/events/discord/index.ts | 6 +++--- bots/discord/src/index.ts | 2 +- bun.lockb | Bin 119372 -> 119444 bytes package.json | 6 +++--- 12 files changed, 29 insertions(+), 27 deletions(-) mode change 100644 => 100755 bun.lockb diff --git a/apis/websocket/docs/2_running_and_deploying.md b/apis/websocket/docs/2_running_and_deploying.md index ae57227..08dafc9 100644 --- a/apis/websocket/docs/2_running_and_deploying.md +++ b/apis/websocket/docs/2_running_and_deploying.md @@ -25,8 +25,6 @@ The distribution files will be placed inside the `dist` directory. Inside will i - The default configuration for the API - Compiled source files of the API -You'll need to also copy the `node_modules` directory dereferenced if you want to run the distribution files somewhere else. - ## ✈️ Deploying To deploy the API, you'll need to: @@ -43,7 +41,8 @@ To deploy the API, you'll need to: 3. Replace the default configuration *(optional)* 4. Configure environment variables - As seen in [`.env.example`](../.env.example). You can also optionally use a `.env` file which **Bun will automatically load**. + As seen in [`.env.example`](../.env.example). You can also optionally use a `.env` file which **Bun will automatically load**. + It is recommended to set `NODE_ENV` to `production` when deploying production builds. 5. Finally, you can run the API using these commands diff --git a/bots/discord/.env.example b/bots/discord/.env.example index f1eefc5..8685374 100644 --- a/bots/discord/.env.example +++ b/bots/discord/.env.example @@ -1,2 +1,2 @@ DISCORD_TOKEN="YOUR-TOKEN-HERE" -DATABASE_URL=file:./db.sqlite3 \ No newline at end of file +DATABASE_PATH=./db.sqlite3 \ No newline at end of file diff --git a/bots/discord/docs/1_configuration.md b/bots/discord/docs/1_configuration.md index 09106d0..0a6a634 100644 --- a/bots/discord/docs/1_configuration.md +++ b/bots/discord/docs/1_configuration.md @@ -61,10 +61,10 @@ You can set environment variables in your shell or use a `.env` file which **Bun The Discord bot token. -#### `DATABASE_URL` +#### `DATABASE_PATH` -The database URL, since we're using SQLite, we'll be using the `file` protocol. -Example values are: `file:./revanced.db`, `file:./db.sqlite`, `file:./discord_bot.sqlite` +The database path. +Example values are: `./revanced.db`, `db.sqlite3`, `../some/path/discord_bot.sqlite` ## ⏭️ What's next diff --git a/bots/discord/drizzle.config.ts b/bots/discord/drizzle.config.ts index f50247d..3c1920e 100644 --- a/bots/discord/drizzle.config.ts +++ b/bots/discord/drizzle.config.ts @@ -4,6 +4,6 @@ export default defineConfig({ dialect: 'sqlite', schema: './src/database/schemas.ts', dbCredentials: { - url: process.env['DATABASE_URL'], + url: `file:./${process.env['DATABASE_PATH']}`, }, }) diff --git a/bots/discord/package.json b/bots/discord/package.json index 780b8a9..0fc0978 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -10,7 +10,7 @@ "start": "bun run scripts/generate-indexes.ts && bun run src/index.ts", "dev": "bun run scripts/generate-indexes.ts && bun --watch src/index.ts", "build:config": "bun build config.ts --outdir=dist", - "build": "bun prepare && bun build:config && bun build src/index.ts -e ./config.js --target=bun --outdir=dist/src && DATABASE_URL=dist/db.sqlite3 drizzle-kit push", + "build": "bun prepare && bun build:config && bun build src/index.ts -e ./config.js --target=bun --outdir=dist/src && DATABASE_PATH=dist/db.sqlite3 drizzle-kit push", "watch": "bun dev", "prepare": "bun run scripts/generate-indexes.ts" }, diff --git a/bots/discord/src/commands/index.ts b/bots/discord/src/commands/index.ts index 3bd4ce2..58723ee 100644 --- a/bots/discord/src/commands/index.ts +++ b/bots/discord/src/commands/index.ts @@ -1,16 +1,16 @@ // AUTO-GENERATED BY A SCRIPT, DON'T TOUCH import './index' -import './fun/reply' import './fun/coinflip' -import './development/eval' -import './development/stop' -import './development/exception-test' +import './fun/reply' +import './moderation/unban' import './moderation/purge' import './moderation/cure' -import './moderation/role-preset' -import './moderation/mute' import './moderation/unmute' -import './moderation/unban' -import './moderation/slowmode' +import './moderation/mute' import './moderation/ban' +import './moderation/role-preset' +import './moderation/slowmode' +import './development/stop' +import './development/exception-test' +import './development/eval' diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 2908e4f..a4806ee 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -29,7 +29,7 @@ export const api = { disconnectCount: 0, } -const db = new Database(process.env['DATABASE_URL']) +const db = new Database(process.env['DATABASE_PATH']) export const database = drizzle(db, { schema: schemas, @@ -61,5 +61,8 @@ export const discord = { ], }, }), - commands: Object.fromEntries(Object.values(commands).map((cmd) => [cmd.data.name, cmd])) as Record, + commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.data.name, cmd])) as Record< + string, + Command + >, } as const diff --git a/bots/discord/src/events/api/index.ts b/bots/discord/src/events/api/index.ts index 80e2524..0c774fe 100644 --- a/bots/discord/src/events/api/index.ts +++ b/bots/discord/src/events/api/index.ts @@ -1,5 +1,5 @@ // AUTO-GENERATED BY A SCRIPT, DON'T TOUCH import './ready' -import './disconnect' import './index' +import './disconnect' diff --git a/bots/discord/src/events/discord/index.ts b/bots/discord/src/events/discord/index.ts index 4f305b0..9e3e80f 100644 --- a/bots/discord/src/events/discord/index.ts +++ b/bots/discord/src/events/discord/index.ts @@ -4,7 +4,7 @@ import './ready' import './cureRequired' import './index' import './messageCreate/messageScanRequired' -import './messageReactionAdd/correctResponse' -import './interactionCreate/chatCommand' -import './interactionCreate/correctResponse' import './guildMemberAdd/applyRolePresets' +import './interactionCreate/correctResponse' +import './interactionCreate/chatCommand' +import './messageReactionAdd/correctResponse' diff --git a/bots/discord/src/index.ts b/bots/discord/src/index.ts index 1588c24..f7e77fd 100644 --- a/bots/discord/src/index.ts +++ b/bots/discord/src/index.ts @@ -4,7 +4,7 @@ import { getMissingEnvironmentVariables } from '@revanced/bot-shared' import './events/register' // Check if token exists -const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN', 'DATABASE_URL']) +const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN', 'DATABASE_PATH']) if (missingEnvs.length) { for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`) process.exit(1) diff --git a/bun.lockb b/bun.lockb old mode 100644 new mode 100755 index b4f32d2ffdae65493b8468c1d067190580eac1f2..64e6cbb7b833430c48103332280eb18bdbb74b0c GIT binary patch delta 4919 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 B+O7Zq delta 4846 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 Kn*QY?;{pIguev$_ diff --git a/package.json b/package.json index db9b265..566d1e8 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "author": "Palm (https://palmdevs.me)", "workspaces": ["packages/*", "apis/*", "bots/*"], "scripts": { - "build": "turbo run build", - "build:packages": "turbo run build --filter=\"./packages/*\"", + "build:all": "turbo run build", + "build:packages": "turbo build --filter=\"./packages/*\"", "watch": "turbo run watch", "flint": "biome check --write .", "flint:check": "biome check .", @@ -27,7 +27,7 @@ "Palm (https://palmdevs.me)", "ReVanced (https://revanced.app)" ], - "packageManager": "pnpm@9.4.0", + "packageManager": "bun@1.1.18", "devDependencies": { "@biomejs/biome": "^1.8.2", "@commitlint/cli": "^19.3.0", From fb8af008661bf37389e01cba19d64a8b4fc82139 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 9 Jul 2024 00:56:22 +0700 Subject: [PATCH 138/312] fix(bots/discord): remove auto-generated files --- bots/discord/.gitignore | 6 +++++- bots/discord/src/commands/index.ts | 16 ---------------- bots/discord/src/events/api/index.ts | 5 ----- bots/discord/src/events/discord/index.ts | 10 ---------- 4 files changed, 5 insertions(+), 32 deletions(-) delete mode 100644 bots/discord/src/commands/index.ts delete mode 100644 bots/discord/src/events/api/index.ts delete mode 100644 bots/discord/src/events/discord/index.ts diff --git a/bots/discord/.gitignore b/bots/discord/.gitignore index 7f0bd6c..1d6382a 100644 --- a/bots/discord/.gitignore +++ b/bots/discord/.gitignore @@ -179,4 +179,8 @@ config.ts # DB *.db -*.sqlite \ No newline at end of file +*.sqlite + +# Auto-generated files +src/commands/index.ts +src/events/*/index.ts \ No newline at end of file diff --git a/bots/discord/src/commands/index.ts b/bots/discord/src/commands/index.ts deleted file mode 100644 index 58723ee..0000000 --- a/bots/discord/src/commands/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH - -import './index' -import './fun/coinflip' -import './fun/reply' -import './moderation/unban' -import './moderation/purge' -import './moderation/cure' -import './moderation/unmute' -import './moderation/mute' -import './moderation/ban' -import './moderation/role-preset' -import './moderation/slowmode' -import './development/stop' -import './development/exception-test' -import './development/eval' diff --git a/bots/discord/src/events/api/index.ts b/bots/discord/src/events/api/index.ts deleted file mode 100644 index 0c774fe..0000000 --- a/bots/discord/src/events/api/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH - -import './ready' -import './index' -import './disconnect' diff --git a/bots/discord/src/events/discord/index.ts b/bots/discord/src/events/discord/index.ts deleted file mode 100644 index 9e3e80f..0000000 --- a/bots/discord/src/events/discord/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH - -import './ready' -import './cureRequired' -import './index' -import './messageCreate/messageScanRequired' -import './guildMemberAdd/applyRolePresets' -import './interactionCreate/correctResponse' -import './interactionCreate/chatCommand' -import './messageReactionAdd/correctResponse' From 09dc70632da0597fdb26677acee3f6fccbb2b9b5 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 9 Jul 2024 01:07:27 +0700 Subject: [PATCH 139/312] fix(bots/discord/scripts): unintentional escaping on windows --- bots/discord/src/utils/fs.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bots/discord/src/utils/fs.ts b/bots/discord/src/utils/fs.ts index f3661a2..0c91b2d 100644 --- a/bots/discord/src/utils/fs.ts +++ b/bots/discord/src/utils/fs.ts @@ -1,10 +1,11 @@ import { readdirSync, writeFileSync } from 'fs' -import { join, relative } from 'path' +import { join, sep as pathSep, relative } from 'path' +import { sep as posixPathSep } from 'path/posix' export const listAllFilesRecursive = (dir: string): string[] => readdirSync(dir, { recursive: true, withFileTypes: true }) .filter(x => x.isFile()) - .map(x => join(x.parentPath, x.name)) + .map(x => join(x.parentPath, x.name).replaceAll(pathSep, posixPathSep)) export const generateCommandsIndex = (dirPath: string) => generateIndexes(dirPath, x => !x.endsWith('types.ts')) @@ -13,7 +14,7 @@ export const generateEventsIndex = (dirPath: string) => generateIndexes(dirPath) const generateIndexes = async (dirPath: string, pathFilter?: (path: string) => boolean) => { const files = listAllFilesRecursive(dirPath) .filter(x => (x.endsWith('.ts') && !x.endsWith('index.ts') && pathFilter ? pathFilter(x) : true)) - .map(x => relative(dirPath, x)) + .map(x => relative(dirPath, x).replaceAll(pathSep, posixPathSep)) writeFileSync( join(dirPath, 'index.ts'), From 5aeade122b0bef345221cb7e005b18d26b4e0d10 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 13 Jul 2024 14:57:29 +0200 Subject: [PATCH 140/312] ci: Publish Docker images (#41) Co-authored-by: PalmDevs --- .github/workflows/release.yml | 79 ++++++++++++++++++ .multi-releaserc | 4 + .releaserc | 54 ++++++++++++ apis/websocket/.releaserc | 21 +++++ apis/websocket/Dockerfile | 16 ++++ apis/websocket/docker-compose.example.yml | 11 +++ bots/discord/.releaserc | 21 +++++ bots/discord/Dockerfile | 17 ++++ bots/discord/docker-compose.example.yml | 9 ++ bun.lockb | Bin 119444 -> 285680 bytes package.json | 12 ++- ...lilab%2Fmulti-semantic-release@1.1.3.patch | 14 ++++ 12 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml create mode 100644 .multi-releaserc create mode 100644 .releaserc create mode 100644 apis/websocket/.releaserc create mode 100644 apis/websocket/Dockerfile create mode 100644 apis/websocket/docker-compose.example.yml create mode 100644 bots/discord/.releaserc create mode 100644 bots/discord/Dockerfile create mode 100644 bots/discord/docker-compose.example.yml create mode 100644 patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8b047a3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,79 @@ +name: Release + +on: + workflow_dispatch: + push: + branches: + - main + - dev + +jobs: + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Make sure the release step uses its own credentials: + # https://github.com/cycjimmy/semantic-release-action#private-packages + persist-credentials: false + fetch-depth: 0 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }} + restore-keys: ${{ runner.os }}-bun- + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build packages + run: bun run build:packages + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: amd64, arm64 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and release + env: + DOCKER_REGISTRY_USER: ${{ github.actor }} + DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} + run: bunx multi-semantic-release + + - name: Set Portainer stack webhook URL based on branch + run: | + if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then + PORTAINER_WEBHOOK_URL=${{ secrets.PORTAINER_WEBHOOK_MAIN_URL }} + else + PORTAINER_WEBHOOK_URL=${{ secrets.PORTAINER_WEBHOOK_DEV_URL }} + fi + echo "PORTAINER_WEBHOOK_URL=$PORTAINER_WEBHOOK_URL" >> $GITHUB_ENV + + - name: Trigger Portainer stack update + uses: newarifrh/portainer-service-webhook@v1 + with: + webhook_url: ${{ env.PORTAINER_WEBHOOK_URL }} + + - name: Purge outdated images + uses: snok/container-retention-policy@v3.0.0 + with: + account: ${{ github.actor }} + token: ${{ secrets.REPOSITORY_PUSH_ACCESS }} + image-names: "revanced-bot-*" + keep-n-most-recent: 5 + cut-off: 3M diff --git a/.multi-releaserc b/.multi-releaserc new file mode 100644 index 0000000..5a6769e --- /dev/null +++ b/.multi-releaserc @@ -0,0 +1,4 @@ +{ + "ignorePrivate": false, + "ignorePackages": ["!packages/**"] +} \ No newline at end of file diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000..42d6f0c --- /dev/null +++ b/.releaserc @@ -0,0 +1,54 @@ +{ + "branches": [ + "main", + { + "name": "dev", + "prerelease": true + } + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "releaseRules": [ + { "type": "build", "scope": "Needs bump", "release": "patch"} + ] + } + ], + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + [ + "@semantic-release/git", + { + "assets": [ + "README.md", + "CHANGELOG.md", + "package.json" + ] + } + ], + [ + "@semantic-release/github", + { + "assets": [ + { + "path": "dist/*" + } + ], + "successComment": false + } + ], + [ + "@saithodev/semantic-release-backmerge", + { + "backmergeBranches": [ + { + "from": "main", + "to": "dev" + } + ], + "clearWorkspace": true + } + ] + ] +} \ No newline at end of file diff --git a/apis/websocket/.releaserc b/apis/websocket/.releaserc new file mode 100644 index 0000000..913e5d4 --- /dev/null +++ b/apis/websocket/.releaserc @@ -0,0 +1,21 @@ +{ + "plugins": [ + [ + "@codedependant/semantic-release-docker", + { + "dockerImage": "revanced-bot-websocket-api", + "dockerRegistry": "ghcr.io", + "dockerProject": "revanced", + "dockerContext": "../..", + "dockerPlatform": [ + "linux/amd64", + "linux/arm64" + ], + "dockerArgs": { + "GITHUB_ACTOR": null, + "GITHUB_TOKEN": null + } + } + ] + ] +} \ No newline at end of file diff --git a/apis/websocket/Dockerfile b/apis/websocket/Dockerfile new file mode 100644 index 0000000..21f5cd4 --- /dev/null +++ b/apis/websocket/Dockerfile @@ -0,0 +1,16 @@ +# This file should be triggered from the monorepo root +FROM oven/bun:latest AS base + +FROM base AS build + +WORKDIR /build +COPY . . +RUN cd apis/websocket && bun run build + +FROM base AS release + +WORKDIR /app +COPY --from=build /build/apis/websocket/dist /app +USER 1000:1000 + +ENTRYPOINT [ "bun", "run", "index.js" ] diff --git a/apis/websocket/docker-compose.example.yml b/apis/websocket/docker-compose.example.yml new file mode 100644 index 0000000..17cddce --- /dev/null +++ b/apis/websocket/docker-compose.example.yml @@ -0,0 +1,11 @@ +services: + websocket-api: + container_name: revanced-bot-websocket-api + image: ghcr.io/revanced/revanced-bot-websocket-api:latest + environment: + - WIT_AI_TOKEN= + volumes: + - /data/revanced-bot-websocket-api:/app + ports: + - 3000:3000 + restart: unless-stopped diff --git a/bots/discord/.releaserc b/bots/discord/.releaserc new file mode 100644 index 0000000..898cfd2 --- /dev/null +++ b/bots/discord/.releaserc @@ -0,0 +1,21 @@ +{ + "plugins": [ + [ + "@codedependant/semantic-release-docker", + { + "dockerImage": "revanced-bot-discord", + "dockerRegistry": "ghcr.io", + "dockerProject": "revanced", + "dockerContext": "../..", + "dockerPlatform": [ + "linux/amd64", + "linux/arm64" + ], + "dockerArgs": { + "GITHUB_ACTOR": null, + "GITHUB_TOKEN": null + } + } + ] + ] +} \ No newline at end of file diff --git a/bots/discord/Dockerfile b/bots/discord/Dockerfile new file mode 100644 index 0000000..9ae2603 --- /dev/null +++ b/bots/discord/Dockerfile @@ -0,0 +1,17 @@ +# This file should be triggered from the monorepo root +FROM oven/bun:latest AS base + +FROM base AS build + +WORKDIR /build +COPY . . +RUN cd bots/discord && bun run build + +FROM base AS release + +WORKDIR /app +COPY --from=build /build/bots/discord/dist /app + +USER 1000:1000 + +ENTRYPOINT [ "bun", "run", "src/index.js" ] diff --git a/bots/discord/docker-compose.example.yml b/bots/discord/docker-compose.example.yml new file mode 100644 index 0000000..c40f9d8 --- /dev/null +++ b/bots/discord/docker-compose.example.yml @@ -0,0 +1,9 @@ +services: + websocket-api: + container_name: revanced-bot-discord + image: ghcr.io/revanced/revanced-bot-discord:latest + volumes: + - /data/revanced-bot-discord:/app + ports: + - 3000:3000 + restart: unless-stopped \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 64e6cbb7b833430c48103332280eb18bdbb74b0c..5fa9dbab53c54d0c0129a42c206bec4034d4f547 100755 GIT binary patch literal 285680 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


      ZK~MEvT!5W-WtDCmx}lQyBcJ}dCkvl`Ro3N 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

      q#mSBiyKn@66=2?FCGSl2x#~t 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 z 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�)?YJbSTdg5v%bJruA`_Ffa9`C9T8!eBi&GK>sH~*RY_{{2^eJahhAI{1reOVBGKL1esK}V4vGmPgYCr(eRke8ay$R?d~ zx<$`$rK8M$9?T;_Je`1YNiDO0B?H!j{%;ld> zvp&2eX{E6N^8SF$$mZU8-g*832m6Of#&;MbPbeimTClcl+FYH9SI_EBzonRdR#IfD ziP)B~M9|$7pn4P3AKHR!uJ}$qkBo{A!_Unk z&%ZECNp!5)ueZVLP2j1fu+S$Fvt>SWy^%OIr}5zJR~g?|p1QuMDsB6v+ey_W|JAQV z*&Ub$*)I(lH`9qvIZs~B^bWrjcG>G^dEds+UymBNE=}Fbx5&nQsl`b( zV~Y&NV%w(JC{AWI$r=Akt6MAI&Nt>?SepVmV+Gdk+lFjzgpts#RnAgIBBc+6dy16~ z$nekN{uLlvbn>2}#%xB}-a3an2455AzTWe_xTqUR5XP(CvVG9{0q9P z1QZUS_QQ5$b9XLh)QGs5EBdE=@2ZQ-RE$(^+H;(;?O0~yNQP~{Xd;EjgVLXF&EN)*nw;=llvN{O~EcAhyQfNoXx2E&im4N z_4ITuNAEA;wD4JOHUvC>W1)V&>$Bnnaj~9V+|yrM9d~s)sPS^zIuS^F5@POdWOMzU zc7}B;9169|o@nawVo~M0>^n9a4IM01UpM_OJ%1@iqe17y4n~%F3SR?j4 z5r3H@Z)K*Rben3iRAEbN`LF(e-{b7}zHs_>qxRGZby>)LmXPtj{mAAXmV9+fSRnJg zV49NIbVcWvJCjU)FX+6JQakUL{0~8w_N5OmM5dJ8o?~}XEbx}Bm+(eAbLLg=Syya7 zPRY_?)Qhjv zABy-)obynR8F{_jQDk%ZJ7k`?eC3IM!@H*S@6$9nR=34FUxnz-yL<0l7^|NLr-jLL zwV>4}T`m4p-D;W9|Ly)=$%zFMJB$r|Yy(AmRwA#rKZb1X#9oKgsBf<8*Z!I@HDjK_ z(S=d2&qC#U88mKL2dMsve(rKW%KlcH=egzY%%i$jM{z%%W%=^(E#E@!Lt7qxh)RHr zKS09aII_7PSnD@v=bd$*y!eEG_N?Y~1HpAK9q+!-)i+Go`l|8Pb>RnRtl1B(eKVa| zE;F~^uGNC^&0h2MllLm5xIHv(3;K$b9!?;eTih5}>$$&lyIA^GyQ^7Sm>D*S?B71` z?rdRR%Y%P{&Of-eG+SvK=cLBYO*gN|Mk_U4Utb}x`IXW68)sd57aP3=okayp4=0h$ z-MG;5UG>Uie(Ud)nLmH=nVn7Q(xyqLT`zu^vN7e4g5JJQo1GZX39j$pt@m~8)JvJV z>bz$D1@%1*(}f&?0WnTLdg!zpBQZ%k0XZ_nVq>aq5bWuLk(a(mS0o~-9v^n0uD zUCDVIS2XU5Ka?`qIpc`nr?^MmpZnu>9zNRkA>+tt`Kh00fAWcfjN3!ZJ&kOxd1=q4 zS5sfF48HHMI_BDuemOQ_&Wyv=zt-(j<2?Oq+0OfiM7CVpsc<4Z+2%q-`L)+?c7=z> zx6G0?t*Lx;T*ELAJx9^eLaI)>!$uw$L*I+*3LK`@OA3Gm4CM6?g?AQE23p_XW<>K zSNrlCm15G@Wo!_L*gIpk!ULnZv%Dt$RjFn{z9;@XvblT8gRWm#o?KDTaXlq#rR^Q- z$06Hq3m&qWt!)uxJ#Ag+*VYBk)^*t|?r}ONXTZPUSop$^E;_SzbGW-L7Orv0MZQZ8 zxl9DjCxiGf8pH-!TT)t-l+VDx0NS&{$pD^S14+>xgWPhP5j=k1Fp-6UL3{w+LU+%P zS~wa4R0;u5J}OSlElVw8V2~1IVBj6iM^p;gQDcY+0Z{tOO)SbzO<`bAl^*`-59Hr+ zIR*w$-Ps^F{QXNz8W>eG8UmvsF#JLQRMtJ!XJFuBU}$)+4{56pKVJ?VAA#Z{t5~-( zF*k=H)s%sOZ?vsPQsjZcPs);kL7IV~!NQ7xL6C&-1L=Ka!N4Gkq!%Q%)EbgM)>}j3 z10+_Oo|BnYnN=)m!@$5pf*mAzW>oQ@4gpX)C`yJnQBOZPCsQx07{Vh=g2rrMn6T=h zr2-T#={fmHAXkTYjn*lkaG(@}!W33dPxBuwb0`hZQT?MKFd72*LjY9fOk^?8H40H zLsT~X@(ecDfZP(|3duk0xeN>vLo|P2^V_J{$O{2bdd|(vO$GT}x@=(c7RU{wcr*kC zdkBE?OipTANk)Et_E2sMfW}Hd>ytq=EX=`vYlF4iAwf)s;1B`X0UP5c)eewev5AnnQ%^rBGmnHZOB~J_6&@xb07@GpX`Kfp z(FO{uj5&~e1j>WBvN=ed<6KA|45W@0T(t_)UL5Sc0J+&{9Rq_H14F}TUw5#_*3fnzD6PHP04bM2Ey846l^Gqp z335wnaZ+h!P6~tY9*CRhVC|?CbP9pd_@z@ok6JSt0;3^7lMn!vVX8MEbF-OwDXA61 za0~@>o*zvDZ`9Dy5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c z4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C z(GVC7fzc2c4S~@R7zLvtFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsKz;~}UaLWV z*x}JR8mxE%XH;r51V%$(Gz3ONU^E0qLtr!nMnhmU1V%$({}_7Kp!7 zm>3v9ZUedZC&&Q|3=CONz5EOe48NHn{>)}#U;z0`fPsO*ml@*L0;m|sU;a?BQYJ`v z2r)1)m@+dkfUItUsuN~lU^oqBw?o|m^8YzdKr%2ebV9{M85kI}p>FAhx&w8v0E(9xP`7~Gs|hu47E}!6UM;BD9H?Gp1_p+|&~TXx6;okgVEE4rNpJI@VGH&b zRPSP_IyD9ch9?XR44^n&0u@sSrFUiq22lJhh3eH{U|>jrimigWMU#PnAss5V2C7#J zlun>xYoU6zL1`B%wh5XhbwFtsDz+7>P8XD}7#J8p>0uYtJUs>m22H5nc0#K6D+N=ql9ezRa;VCaCxIVfF& zlC~uS14B1d>;lvdD+UIJwa_@a2o0|Nsq3wW}W;SN;H37TF& z>FX|3%o!>MN;mhQVlL2d0i~P!P%&2q28Nl;3=E(&@&H=CxG^v=%!P_QgNCU)0|Uc+ zsMrgrUJnKah6PZumryZJ1_p*cW(EdO`g#Qw^I~9NC}Cz`0Hv?jP%&=?28L3o*c+&r z57d32H1ZZI2Fic7pulBdV0Z_0iys36gDWEg11Q~mfU5InU|{fss{05P3jmdWP_a)? zy@3o23`tP2FHm(s3=9mJP_eI2v0w%UhFqxFH>g+$0|P@LRO~xcER=zPp$sba11c8A zz`#%r75fPl3ujCe?!G085kI*GcYiK(&itiSQG;T!)$08`U@3{ zW?*2L16B7A>bDpM28InR3=E+33aX$$T-OsQW-^QxYl$O5bcO3=E)jC~I~N=wpEv1A4YhCR#-44||m z0~JeQU|`q_6_bUEr7|!u>||zO0HrTbodF7>GzJESMa&Egp!6jVRhJH`&p{a!l)s>2 z8KAP6iGcx>Miil9nG6gJ(NJ|tP_Zlq28LfCgF)pCR4f}*4lyw>fYOKxR4j*qfnf_& zuPRh57nH7`Vro#aJO%~^b*Pv+R4gCVj$mS70HqNPs8|8WAB+qPptuK>mmnt;GB7Y0 zffx)73|dfip!hL{ih=4<5VM$pfk6+%U|?X-fvPKEU|;}=gW^;dTGy8{FfeekK*}9` zsJb!+28Mp9UIS=dU(Ue5u$qy90Te#QP<0gy3=CDE2xnkmFoB9yGB7ZlgX#sf2|!Gc z8c_NG*>46_R}C%eKw{=lu^I*jhKY;}3?Tb0pklS4`WY%_2^FhjU|^UE6|;hh)kD)H z$S!NBSO=(J1?3x1Va33}z-3cvY-Fckg0H~{axW-qK-CVY=mfQcKy4;adjiy61GT$A z^*N|r1!_lu>R3=a2~@9w+CiXt4%E&8wQE56=M^&p!)s;+hPTWN43C%@7#=e-Fg#&q zV0g;Rz#z@az#z-Yz>viPX$OMZeYq?Q423KV450R2F$)7j2@3;51PcR0BntyW6bl1G zGz$Ym3=0E8EDHkzsQnku!oZLKia!0xCm5Wd^8>0F?=#G60n4L2WNk8w%8B0=08MbrGoU0ku&; z^@9!r0|Tgi0ctyd+6tiZ98|}H%0p250MtGN)#0Ez8`RzdweLV_{24O?!*gZ^h8N5X z3@@1(801+Q7!+6;7!+9<7?fBU7?fEV7*tpp7*tsq7}Qu97}QxA7&KTJ7(TEtFnnZT zVEDws!0?%cf#EAC>{u8Wez7nx{AOWb_{YM)@E_FHW@TXLXJKHNz{0>Vkp zU|7wIRfngH^1H)NRzGGluxX8f3aEXC|;WD&6 zdX<5J;Ti)2!*vD*h8qkF44Xk^6axdpRt5%!Z43+y+Zh-bb}%q7fa*U`xeqGSLG2Jw zy93nD0F~XKc7!tv1A_+(1A{Ol1A_=71A{0d1A`c-9mdGOpuxhx0BX~M+ORw<3=F(1 z3=DiM3=Bdb^(+hwA}kCHqAbw%EGX#F8xquJRAppf zPy_jkk%57gk%8ec0|Ubu1_p-X3=9k>7$B?6HZm|Uv_ac{oeT^NUC?$PsO|yPE1+^y zla+yCAqxY;A{GXQ#ViaAOIR2fma;G~EMs9{SkA)0u!4nwVI>O#!(j75X#8F5C*DG85tNN7#SEM85tO&7#SF%85tO285tPj7#SGi85tN7 z7#SE686oYwWJU&t6h;PybVde-3{d^Y$iR@r$iR@z$iR@p2x;TxF)}dZGcqs~FhbgQ zMT`s##f%ILC5#LVrJ#C>5z?Lmwcjck85pV<85pV=85n9985n9A85qnN85pcUW`o-B zj0_Cgj0_CAj0_B%j0_B1j0_AX85kH&F)%Q!XJBBM0cvkEFfh!5w&|uZFfdF2)tjKc zE2ypn^`}`G7<5<}7^br@Fw9_KV3^6mz_5{pfnhxh0|Tf{4QfM!+Tg!H?NMm^4%C)> z$iTqxfPsNwH>m9eYHKktFzjPsVAv1pS1~X!9Asc%0JZTzZ4^+u71ST_Wnp0OV_{&Z zXJ%k%U}j)2Vqst~W?^73Wno}2V_{%WVPRlUWno}YV_{%mXJKHdVrF1yVPIeYwbj{~ z7#KL27#O&j7#P|a85lYk85p`585nvP85lH~7#O0MAZ;!0?BW zfnh%r1A{0N0|TgU*viPju#Jg<0o0$|zyxWB6)`a|6f-d}=rS=d7&0+1m@_djSTiv& zI506VI5IIXxG*s=xHB;@crYOeO{f&^S#l69dBrMh1p7CI*H*j0_BWLHU4*f#E+Rq%YhCN{37g44`qD zW+n!PdL~Gl?I|Oqopzp)fx(24fnhbMK4D^D0QCVTF)}brVPs&K21>U~3=9oS3=A(A z85k~r@(HLN&BDMimyvVP;?ewI9*V`OUz< z@CQ^bGBGfK`njO?^Ey!fnh`P{!OY0O08;}RCwR}oz%UJz=2;mSeljpH+=BM^LH!>K z7D)dc)L#en#X(~=Ptp9s0tydk{|Q|VX3sTd28J2Tkp4HQzYXeNgZk6G%#c2GKQjYE zJ~IPDF*Bt94C*hJF*7jyh4vi;7#SFNk=*UV$N=sar!q1y)G;zJSb)kokXl9t1|3F- zK5T9U^*2F%$v$ZR5!81B_31!;;3gb#rNF|#0O|+6Wnf?cVUU}aFf%YLWrp+xmoqak ztYl_jSj7zK2ZH*2pne{xUk5T1glnOG;bLN7;9+85Sj@=4@D)_YGchp8GBGfK#`?UO z7#Qw@>J%mhh69WY49giA7#1-?>;%Q%a?n@>0|UciP`ikQfnh5P1H(2J28Qh{3=BJ1 z7#Mc4Ffi<5VPM$J!oaYHg@Iu&3j@PG76yj>EDQ_>SQr=%vM?|lVqsu7%)-EMgoT0O zC<_C_F%|}f<17peCs-I5PO?DyT%f)ysLu`ZE2zJ4Emt@3fhn20<|?685kxrLi$D^b)bIIB}N7Y zSbWT3WMG)Z$iOfiRHuQ;Cr|?lH1-T?Xc5sr;$vc90F~LG_8MqR+=q#Q!I_DH!Ip`E zL4=8cL6C`o0j362X5M9FU;x?Q2`wW|gUTdm*|`l=zcE0@n?ZdpP~QsF#{%`WKz%My z-wV_S+X}50KxTm2wR=JReFg>wP~3s~vP+=#A1LjC#^TyR<0+uAAqL3U0jR$W>PLgp zB50fjRPTV=8K5yG(AXcSzW^$WKz#>LKLIq>4(hvs`f#AW9H>tR>g$2}e4xG`sILd= z^MU$)pgtc+4kRYV02z~n@nL-rP#*;}HU{c{fX3HAeGt&tE@-?MR2G8zIH3LwsGkEW z7h(Mz&=@XqUkB930rh!6{ThplcEkR=*p!Paw91_F_jRS$& z^q{sqh!1MhgWC5XciA&AFo5`=wmzs02QmvZE(02e0k!2o^*g9N2V#S0P#X_aE`!EP zK>Y<+`wujp0UGB3_3=St3!pj-)J6b>IcO{k)K&n|pt=Q=2SD{4sJ#JdOMt=)G@b++ zI{}TUfZETXF&$8v1+_6j6fLnt_1<)CLFfLH$6G{h;v` zPO zDo;Uey+xogQU(Twd7$xA1_p+Ops_Lr28N{!3=C^PV=~b81;`GNIiU6i$Xrm{15~zw z`ahul-4_A}y>N9})PRF4A2T&RV^&vp*BT$MnL1Ypt2S;*8}Q%ctFeD zCQ!Q{+U5gID1!PVpgsr44O-BCK|Lb_1E_Bf>brp22B7*B)J6gIB@CeLe^5UF)E@x# z3qa$&puPgA&j9K>fXa4IIRWZRfX0kLeT#5VTa%H20o30B^*=y;q%2T>2sGaX>OVmH zLZChosE-8d>&ig;u%Pj7Q2h?-Q?W5JFbFa-Fo5cReo%jyk%57Uk%8eq0|Uc91_p)~ z3=9mQGUPe5O#*6vfWiRe{#OhP3@@Q=E|55=J_C(Kfa-5h9}=VneRvw}9LLG8dFCLFo=;FQ~l->W6~t2g!r_qo95bhz$xS z&=@qR-vH|GfW$!kS4&37_zdGEn1jp$#TBUD0?C2qa6o2&{07RCpgucDJt(X} z;S6fqg8T|fr?9*T>f?j@{Ghl6#XZPekUK$RKgjt7G&TSlBjADN7tojiD6fF>L=31u z1nRdiLi!}2v;rHG0HrSw2Dt~>Eg&@@KZ4u}avvxhL2d+%S%A_uC@w(b7@)KZauduw zpu7w6Hz*y0>;R2(fcyg*-vEtsfW|mLV;Z3H0yOpk;v>s}_^|O0Q2GFcJ7^pP-3*X8 z$UPwYVdEj7`VTY~lE%ou04h&F{sNVgpfCsd4KyYK(gO-pSlEEXL1G|3fx;8yZ;+os z;xP3fKZE=T@*5~0gJ>8Zq!+{ol^dWk12kU-;)B8yqy{wp12PvT4&sB-0!SX&4v?FW z)q~;<6#k$w7?4_!|6%f=u^CX=R8-#tzmoGBB)#%B^B#U|7k>z_13&UymdQ5(muzfoPB(7#l=`^ux>r z@j>Q;#-!2P4WKpys2&Hk!$Ea5XeJW%@tpuP*J?*W>N1NBQl z;RCW8=y( z=K+lYfy6;#ps^p&m=CBfMDBmU>;t(S*&di3pmYyX0~%ifI3_@C7<1@B>NB zN2olA2I&Q*R}dRCrU)_{l#W5>gV-Q*L3~hp2IWUkz6ABLVfhr6*Fo_LN&_H&5Yt8l z^_O6MB_<}wS`b*D3DlQBZm)y#Eoc=R$PQ4upO=Y&0i+)^cFF-A`v8rhg6dz;SO_TH z3qbqo{GhQwkmmpY|AX?55EBE#eo#FPH3KxJXT!w6V88?!-vo`Pg4BTQ0`-L@m>3vf zd@;~C9;lxSnm=GwkQ}IQ0@4SvQ;i9-_CS@1fdSM{2hEv-#6e@jdeCuQ9VP|_ zEhYvAZRpsp6?Ci@q|Stifx(!Gfx!rBKgdrYaZ8X}q2s}3ObiUBPlZ<5pq7Kik(d}5K&l%7HAKxTl}D}u(p zGng0{(wQLR-k^B_(AvgiCI$x3y2mai$oj$@CI*IlP&#K~V8{cdB_;+2&^pFKCdiyh z2WZ}biGiVwiGiV&iGiVoiGcw$Mh+S)Z-kC}gT~A2m>3vpnHU&qK;v+r@pC2yhDs&| zh6*MIhH@qbhB77w2GCl_66kybD6N3pu#ky?VF42Z!+a(N2GDvv&^kWQ`o7sr3=E*P ze>0gF7(nX+LF)xU>j**X3qk7+LF*AgW0w;_anHoS09xDF$Hc$@TK5QA582JczyMlv z2b$;D2#x2JObiU5JOP^H0Im50#s4xU1_sbtKhU~BkQk_b1g-OfsRfCx0F9e6LF!hJ zevlfFx$Bu27(nVkYCz_!1I?u}F))DE1cKrOqz)7>tC7?ps{zf;fYJqMjUH%z2DGLR zG&ciU1Gtlkfnf&|1H*PE$eaymtsrRK49GlKxdmEh2uiP@d0~)!pfv`dwFYOI7#Pk# z*B^k^A%NB+fY$PX*7$+uk3nk^K=y*xo`U8bCP2qFm8wCbiJ&zg(MD#mr51JyMrQDJ zAcm+5he2a?f}m*+{mAyb1MC6{Y@iW89tMU6(5Q(B149Growcv4Ctd4QU;~Y#@iQXW6sGr!OR?tafW(^dd84Z50KtPb8=P`9#&fima)(?g3LjyF)%cA=eh5Ewn*tS z2V!q(QE5E&yqBL;Mz za^UnS2i&Ki-l!@AL&L52^EbHG#%8Bp4VP_D*m9&NpdR0z}48&j36dD9pgn!1v^P)VfnF z795OmMtTN%rVI?TA#J{fQ*4@hy$u?_fb{4Yf@DA~3Q)-UcWf(4zcgt+2V}8% zpti0S14BdZPVa{=O0G@fV2m@@GXRBXPHI|7Mt*)a|D@lGa)0h=1)FB3XJ`SQ`x5}g z4|Cy_f-TJ;{}_S1YQRuZT9gDDQG9&mk-#6pkOU4!(D*TEDHNzp#KpkSFkiJnN4qsO z2^>GBAbUWi1t@;H)n?5YElMphXoCR{V(958et~Jvm5Hg@OJD?Elu40*S#m!h2 zAp=^qqR7C|u-sYeS}pIcN+w3oIv0@5pmj0g3=9oYLLs`$uTwTLL1NngGTV(Db4-kJ zhM+h#U@!-*Wno}wm~!IwuDE&Kl^_|AUkwlUTtk@f-Hn&-nAuF)E&kG0sxYK+k}IAtg1b6tu3d zubIK*q135k2)Cu?mFXsDGB6x0+%aoDpW#1{3k<=UQY%uEL1V|Yk&&_qUna{SOiRm5 zFD*(fW@vC%kh!&$vz!T%dJPyD=7QGlfO7B|;T?a^oNq+PtYQV%zzrH7@4YV;|F9h) zvjyre?ZiFrw@XSr0J%WV2;^!|TGK5qNm=Xul>N@zlDkZdamHY$F&u)1L6rJIv&IP_ zmzW@F)PRBEB2?zvYv#u;#b%OV(~LoR?g1+U11AGR!zxZ0el0z@^WZQrG}kjS%uOuH zPEBE8U=324>#~3`8>9yk3ooIjxvk-OIe)gpV}w0Fp#FKCdt`RR^T5+!)4;iei4DBg zzahv@_|J8>DqV!D3v`Q8Q%aLllMYTzoNval>cj4LqLtRyGbS1J=_4_37Un#tj8ZF1Nt0HUgE`m4+6ei4z8wPmG_Q z?khO}4FgjKhAB`zw&@pAYTC0@K&F8z21ABGc1U`YReGp=fKzEFG`$%zFhsLMLg!6N zROnrs11?bi7%(uTv4dx(8rI|+ZpnFshvBH%Cp=U}Y%fC_(H_82fQNOD0^Vp5@s ztphi|1t@gDRS81|7sNjoBUWAT{4)P36Js4H*i0B0k~0!>K(kBdA2%BKh{fCh>j9@K zEV&+%Nf-*U({+>c^V0U*I<2Y8zGO0357=#KnRzL?r3DOp&ocR>wgwi1Wx)QC;f9oE zo?W7SJWcx<5I)W0hJ=BI*Qs|#v!yD)GKQdZUC9k`b%tsDGJT!5Q^7J|(+V=vKvPi+ z#@^4qZEMzKN5ot+HzcfgOsu)@w4i(fLMFASD8EQIwc_r}XB~I+rp^GRcs+1QVZ_70 z04lFz3f$g!t+T%dmI0^uTRafAeaoBHB*L{!nTavZ0Fr-Z@Iqw50#ExT3T!!!@M(c= zPJVJ?N$PcF8}`=9_BMpfL#Q728sUXWaSxXx+*Y8QlbM@Y!tgaVpyzM#DJ`%Iz#&?o zTT%q_YGb&z__D3BXZW?;ik~PwdX^jGNud+SW=ZS#MQX5{LT7fPtYXH8DlEAUpk#)7w|aAMconsF^+pLSp-%koDJipGlBX z*$m`@93ch^D_6IUSV!;Gdx^xPG=T#bh2gga?yw`OPT2dG=Fkne<7LfV} zw>|TPAbBq4sLl(?T6I5g3IWHQmN2B`^V<6V!M&IVp!hM@GcwXMG`5Dy#AsMMEYq%C z1Jz^1z<|{rb5IF;OBj+DiaS02ZCR>Pg9x1`!VCE#}`tY5ZywFr-!oa{Q2B`t79JC)9>xOVcWkBVd7y|>St!%a4C-%9oV+lg0 zK@3u#vfJl$?NZp60?u>bSm+mngpS&2m7V(^ya0t8v`rz2(=^wt{b_eAbEi$!YJe{+p44B+z2j_Vx%CZY3Y0X`4KJh39JWP9z2zT zgqu~`s^ZD&aajl%EM>eYxckHKQ3{fJkDl}pnP&FD7VI`dJwro=e^L;)-A$ReIjN3C zADl+bKs^Z#X^0Ga`=j{Vo^J)9dW;zugrvc{*&3vDt*2?Pa0IpNpka;P;u2uAvo%z( zF*LzexK{;p z%ZPy?H8(#iGbc02;ml_5H3!^&f&F6wDnr_3AYmQ5RU%<;K`WEem_#D;5| zZ*?;i@q_Xl$TS0d?E_soNSsc-{F-^+!T)Pn7!CDILDpc)r_eCCAP0%l!28>{uk(EZ z`Nz;e4^$xJf|d|~Rc>I`-p*wqDzMa8M63=FD=RxedhEX@I@9dN3;Bgeo1 z>g`U8IBm5m&-Xr3$%myi2RvMNN z1qF^F1H)_uNGo9Jw~CZtzGI+L44Mv>DL`^oQ0KLEauHr!;FJi?!JDCaSih${dG)x{ z091xRYMGLZ)I3nb=F{gjFIU7L>}6v7qX3D2-1cCp>rM2)B_G!O1D08=1abAZ8M9Vp zEOg{VxBzRv(OA!ffgw*BlDAiX{=UfI>iZK={}?bZ(`rnn#{v&4vj;fR?Y)7S3h zYY;MLR3LdfYRZHy`wV6s0?UBYbxB51erbA!QDP&f+v;u0BbXTLpe@ysqQtV)qT*D3 znN<%}cllgoVl>b*(*w;C`lv!`jMHyfBOKNCn}PKh>zOeyRFtIVf!0i!+~~je?XsIH z6Js4HHySc9Tvmm|#cn!IoVPOq36CT0|Ti2cG}v_dV2Lzf2bY<28Pe7koZx&tskczr}&(SvCb6I z&-$kd3DMn`o+Yg)-qH>>4IBnMY7m)4#@GDjFa2o_mI0?X9HtpCFsNxj%D19A{aNQ9 zI4CeN))_$34rb_pN0^Kl7;wwrj)jeykZ}7FE6M)-U+XSt+A(Ed*sBTg>55|qp8RcC z6NpHOY+8`m&SHJeCaj&j5h`QIz#ygtadqdiH#O5=@N9#YZw3quky?;2IGLEf^-uZv zGO!FJezYNBUAXKnLz%iHsHTOckpJ3{lDtguY;Dg|`L|HF889$N=|J2TP?xjk&c@B4 zRvM_}GiG4G(lauHgd3Jo59o-Dst%+)SUPRq8&-t`M?~#xqyq^Z2|1@|rAE!IU>AV% zshtkQr`P=4mcQR_ksGD27 zK*mX`HdX8+LQg?rNrrBAYUQQk{r|28*?>wHkgE+C7!36x{Vc{+iH9@$O*ezv6egh7 zEpDG;F%2~4V8Fm|O&^kj9YpWXu-ea2i*Q?cMrJZ-mE)P*zVD0uKe<6=3>g?Qi&HB= zYbaOfgx?AldUYKtW5B?$(f|@aTrb0A?HBZbS^?0u3zl&)Xs=+aA;bk|ODrSBtb6uB z)2I;xLw;UrNoH;;L%7xI_+CLq(6|ez#xP)Dz~WP=Jy_BX$R0y{DG_&^+89An)oG5G zYF>V)&LDh>HSK`Y84}!xD?6wsqkp5iNZFzw+ z$-3aNTRlTijgeTC4$clt{O_)Y9l8e^Z3UGsh71gfW{_01S}|hLuh##d@hWKh0LwTZ zG?#2JgM`~_9tBD6CRHPF=zv{aYz`@rI69r#?=M~iYOO=Vpw1j(&vsqrrTpg)gL(zf zm}B6FlzfO$0+_@R3kC)`28M?AQ(D%C{xyNdAVKNPfPvwW1tbjWR&7x14mMqk7%}^A z0VyrHCEn=!bBCWrl(15k5SjM^H>Y2zXxN8nTVicffc=9d#e+&%T%)?+`qb1C5(eE{ zo~+s=QV6Ospn7nR_u?LHy$xFHi8PM^>X#US>%g?4{9Mq?1!Jn2&+^dU-H3EhX9-EY zvKQan{dZ=~21NW|wgSMlrXkK+6V!T2%?B;6W|;2tNjr0{Yu4m4`kc-natUYn?>i3DRQ#?o$?l zHVJ_CtsS`2yiE2rsIE8EGXgbyagU`L+e2Cb8!}}6?+L#IG7VZ(U`;#VCVok3ZUJa? z(rMGWNh%*s2_y0X?wp0Y1_YU=o03^{bbkW3q4q0KdV^#IY~x}^dWN9s%L-`dTnfMX z=>De(QxGmVZVxH>zDHm4^4M1*3eF|qw1Z`o4HP|w3=C?HkQU^ZQ2DR}`|BWe7P$T? za)h*9%tUUKTLvzd1BErD&DZ4!>C5mLUVO6ihzV$h18UlQM@Z@|Y*U|koZ-PyoC>eu*IimY!Z5 zw5x~3)u6VeF#|(MB4}R;1H%fdnQ5_37eR9cW*}!XFr???gSN0Rq=omslZy0n1%-}* z9;i8(mtO$dWWcbKtIk#HbRwvgW)9Ha z+My!`+dUzs8R(fYBo-xSfYRt$)XQc-Fd1B3awSIwWMX@PnYpjH5AJ~%Nsza*7`VSB4X{q?D{mx0^{ zs%p&`u;wgqIhvf9oS2-E%5X`3)y+_km7uX2Ly#V0hU84$%)GRG21jP@KUr_q@gdxn zS*)9!nNq~?>Ev?NqRm@iBR|mCE=VjcE@qohn>@n`6mI5_9%WvBN~&&pWx={QcFhw@ zGT(#T2Ce`Z3X1ZRb#wC5%~th^PYezN%}Ia~oB;zvoC~C^QvO>escrTMG@}M;-I_5l zq(f!onI6RjzIz86R|b`dCJYP(P??`rn&InC$AC-&#T;mS#|4rX_V~orFl-7t3C_Xb zK5a8p#)*+BC4Di+EO46wyb7cjD#Nq?spZq1FB!pQBDh4F4wYeP)N$pY&CfJ!k# zLxzP=8EMb;=BX?5LH2-Wc%d7-zz8(f32IXqGcat0%A_S7IeNEV8k7=2 zJ~d@v*bkKnz4=gENZz{&+;;}gVxM$@l~O7XdL!FRFA3bGKrR{d%i;A2ULu`a)FdCdM~eJrwUK{3l0NNf@k;!l@YV^ z*R5O01ZvrV{A0$zz~l<4OFqTV3HY-2)EjUO2rgatp)zrnn_}}@uRe$N8!Z_aWS}w~ zw?nF@?OzXSae>0Xn1Mml6;fJicTKv{DPPSGc7ZWyj>Q}*)4F7ytUz`QXw)5=7qE;< zS?YnuEe}EG-lI_G_>CACTwNjkEE}DsP2Z&S+d*Lf%ECqrAyAoGJ=b}f-e*9izX7OT zFknb_g^VA>W}i`gQF;3(xW8=*a$7M}rpiF4_X4v&Xyg=>LJSxfnq46+{KnIBo_*eb z1GLHj>eDGunQfZZp2ELuK>ck)P}CVREQQJxHop1tac*rg$ZdK?ptj34sLaK-+EP}# zz+nxpnSMiM_Sr12|CaR)H0Npt8Qte~gXp>0^kvUvG5+}=w}BFZA%i?r=5$l< z-R6~oIv^Q{9z!=s>2K|pe&xhQp+jI913g1C1{bJIz(U5LStVJZ*=0x&Foe57YFdv? zhccwR9U!g-=iqENNZsiA^DM`;hCop10&;*60|S=X7ic-!;0BqW6I}I|_YcQo(EI|( z9y10895Z)@3=GrU7#Ji$>uMH$IPcca4qDLxic>=dhV^cczKrzV>+<^=YCvl+KvR(h z3=CL$dLVm@aMetp)&`F9&47VH&jaG>4XL5CE|_1~4KBq%*_6S>1JZK1kuyhGS zufT|bVV(~}&yQc#8HXaKEM;PB_kpYn!Lp_VlzI&r7!LbD!a61L@S%D93s{*L*ZDwF zI+j@k3rIWhsSm`}hFl+CYq^QAFfm^Af#e%3WBAbVM69j`mlU|&hGp#vs1!5AxlhaV!=H=YQVsNbyN!MYI5djaksdzjOKyH1`Qb) z`~o29?aq$nIazW9_;xC6b;7%ASv;JZRGXrn38FTGUQ4CG%u`>{Jd~(5@=Kxl!FZ!7;w~P1`G^1 z%6QP~GwdtYOmWxf1`JqhGq8UcaF&)>LkCp2U~gX&2shm607orjz`&4NRHU1npOU)O z-&Q^KTO(|=)d;k91+spJ@j}LfP2%gD!Q&x@pi+z_7*h5+8CE4r__rv6Teo1J@&-f7 zcq@x6IeX^Kpw=yPoDX-sfV;)j5(2TOA!)m}7yGI@aM=r9e}Jo1V}QMk2d{SQ4S|#g zQ+Mt4*(SpYTJHj?HKBcss+`QEIqxiXGlsr`)J)*I1Xugb0DB(;90r*60&Fd4WnxZF zW^oC_r|BE6N!2KV#yX)b2i!7POaqmC#*o?!yQ^D6AwCsn73_1W+W=WF1|EY<%}dTt z$;?Y{>75$#pmpL-NG+phXv}aX6w;SDx!!J9N{EdDIJUw4Ii)a2YyBs$(pFK21s|9g zg~A|t4U5~LA&RR{Yruf3cZe_Mu$B~{gvjtW43a{m18)Vqc(qIooI*ea1p`|+#6Lx> z+0R}$O!xuyk1+#-KsclfVX})*vhtO%gO(u%3=FtS7u;z_HXIVxGd1Kc@0Q=92Xz5x ztP|QI#%y0}heK-5|0acg9;;u0+KHes2hcc0W?m**3k4dYxWgKEX&D{?34_$XtF2@$ z>p?v*P~X{rfgv#h5`#wKYt*U?Ye4-HP^vOuU?_}$^dy#~#MdtJ$qxqiKENT`1eLiX zU1s~Iv0y2<9S=@bIP5WGz`mLr+-ob&PtFESzwanMD<)MQ44Qp0(z5{da`+=5t`^{X zw{YWJK~S#^)Dt&lV33T2q(r+1e%juHmv>umm!#XKzp}9<2qRNfSPZ3!k`4(2orb>6ZR1% zP&*!X&cZ$NgC*^NV%wO30e9M|gpLxRjcph(FyI&qFkoQ7ofjrWL+XXtI4^G9Teq|;OU>@_K$rGr1ccCtZJQHjRw-_R|ix^pPwU1 ze&S|OdIJp>;p!ED(>2!G8C(Zr8C!?uQ!F!GApaOJFkqSKf|mZc{ga%LU!GUQz+iv$ zdZ7ce?jl54m7ATCSyaHlu>W`3uCjgeSA)|v*wrM}C7`}0?i$b#YivXPgT>X*Fu>g! z#6D&RN(flXc#s5xO#&o^2rm6-xBTcDK}6cY+It7r~8a+V;<)=VSW0qG3m@-@433iA&=BVgAr*U5;5aSLOfN3UFG`)wV&BTX zbvpm4U!Vm3)a7ko_YW@CjlC_G{HHmXu!a5HVx9M*~{2;w%W}ew6o4g z54?NrZW^RV>EIq2xpnPt(EJZ52OBUjyiS9R$9MKF0i*S2xz5+AwGLZbTzpDgWJ`(WlBoZbhC;X7-laD zXquP$Aqd>=1=q9_GaxmFvh3@UQ&Fe)f!1t;BLPo83s+mrkl|(qWbAuZa>(C*>z?{U zOao1D;I08l3&V%j4svCkvT&HhUDVn)Z%0Y zhG37IpIGWlK|9f)r6uP2>o`z)Gh$#!&P~xR&dAL5f8@`SRIUQrn*>VN1`G^7IgnT= z-fhwSdKU+1CmN_tVZgwEV;&F`q&P=Gajd~G!rS7)*|x;J$^dtW;%=dkQ)}XGzu|TP zj&Xl;9OWB0XW1hZ~OBawB}}#ZofUKt~`myr?L<((BVH1)kFb=V08S zgS-AA$3II8A)~q}J=?-E@5}?Ovjepc3>X-&wrap`BPWF{D1waL8pQ6D4ccc1vIo@e z1?^t|9VwBXTG2e`@_w<%Z=iW}=-3mEF%$y^hOFZJJkYJ6-iKFQwRlhls%4-#i?syO zw)}J5i^(>8Za;fmqJzn2)e8eleuka4PB39 z0J)C{`?)Jv#uK5TgE`_HX98JOnUYzg3mTa!K63Q->8bBQJA*(qx*=qyIH{6>q3=dQ zy=>lp(5eYTNcBwexIbuYuoM#3g+BXyQ$LD>;uPvr+-rh!L0d=|7#Px&+pfRmZv?Fu zgE)YpKsPNjrzEu~<59H0=2K0eQ4gq0X;F@DR(@t)Z=uqI1jYnba4rGIDee`ySjN0T zZZl+Hm|6x&@%kH84hyHRDg>`(1GgZTl|lO3()EYAqx-y@z%t<3zMW-|zVm{My9zGy z<@JGO!0Xb^mO;j^HD_+n)VE*+#VN?uh71gM${=Z{`Dg?GmiXJCv}2-Y4%!@xWq%g5 z=h{~R8IduxU!q#mEC?zOj3MoK&`q~RsSFISf{vZMk-7}D6B;ysYQn%U9ja&Do!xH~ zi*-PHpedv{C$Si`XXx!*P7%%2-gDq^1Lt7eJN&CEA#MNelqnnc+&T&BJA-N_BL;>p zsLXzy*|)4iCA`2re(>2QvnnCIoOx}#omc$j1FZ#xu0zR9ElN){Vqo~POlS_%q%_Ey zZSc+$9B0p%fLFAG5-&q}OWHF%|58(ESQ|1h_*Oy65U#loZ?&*LHU{Ty@c6-{DoD(o z4Rg=6xE22Ps zCL^rz14<#Febu#)zFE_p|1;8@@G{6(C``~6pgTmUNhK&QpvbhT0)Bxhxb)=wzfCI>3XLFJAS1A}oL zB>!+8-n#B~%oEU=7NFb+ni;Bx*fWVy-zV$COVAp0BgoJ~2Tpr98zA-^(>SLt7~Z@C z6my^x3k*nhH7GOSaGMbW18x`K)`L4lNj42{3c(RS1`H&n9XngliDbBZ3fjq!nciS} z27a#<)^oB9Kr_X->mMBDjuGVUC(xNAQ=#X-A@)rLH9>0h#ZAB8&n|bcWMKpqA%+$V z4CzgfJg1^~{ov{C>+2CRRZWl-AKUo!>fNN(vk)?UO_1^4o?dauO<`3#5i$#!AbG*F zkC)HxX0{YUW*gKrb+(v}TXXl{LCBnf>S5iMUT9JsHwhv00IFyD>q}`h%xkV8WN`05 z!g8txsFh~Kz>tzylB%1QS8O|}&*iVZIG zkxu@6(C&8d1UmzkRo0+Yh=vRdIc<DbC3R zZQ^j7e|Oa)@!g>HA<){{r2|r4i~Un_-M{`as1;zYX9OxkCUij30ne%n7pz$3fLdGz zdZ3mSgL)^#ZPjnm&vppCR)VZQFf?ai$cD-+K0Tvye%7T-CdN7o&|zf;3?-eA(3z+o z)vLDE))KT^1-t~@VrM6${^88GIrb`W)^6}FdLuJE6D)c__t1dWAK=!5WrrB3yaufr zhpt;^MV!wEmBHHT1SJCo9IZ~!N;1q-_@FHmTxrLI0Y^SHU|;}s7tl_i169uk*mE#= zgNXr_z01%R3YMK)P}8vN7`VY6IA9xKByMeIg~6oCQj~ z1`G_i>r>ph1V;`w!g|IhG~A;4ASGXD_?f`eH+A5%fgrtV+~+}J*>w&rUFKq*r}Cv$_Xu9u$`w02?H#-1XNOh%0x!U{15D0T}0Z! zJnJl9Li~kELPtDR>1dXlMod2q2F3n<1#hMf%7b?hwr_g`_v6QA23Shu+sj zj9}m{d&wEM#4)jhZ ztZDMF1?}vHmK6DU$)E#`7*1?(nmMB)5j1jY2-!~yI%@-TIKz=SH_xfpl>KI61l@`W z3LPwCLZDbM#5$6N*QX%2VHw{c=pWpAaKr-M@g3Zz$zmBXgR~&Ag*7CCNE%NB)l4|t z28kf7X$RcuB*h*(TS&^rJx-2$Y@NDe__%XD?s5lr+aGt&2zQw{a!yLYGQtEsp906} zti}usxKC%ray~5RBn|@xJg2~texj=}1MbuQsC(`gmbn~g8ISu!Fx)8-_gQX9#rfb} zG4etOEcFf@IjIDiMF7uDp~sIB=w=4+j77X13%NCD0CK@YrB-eolT-F$06c1P=*~>{M0I=ok241cpO1A?;Cdf4$Al zKkgbbF`9q|zZe)8aO__&VPGi9%&RQP%w_naI%$&F?)+HL*dTcN2V*Qv19ZN>sh*($ z!=hP`@kH@jPL;+y9YYP!DgK}$o&o)K7bWI6_n|WPzpjwyD?$7mDRXe$c5w1`G^X&fSNm1Ke}FSWfYWwra4P)ekLq^yfobY5L3U4m@j3 z0-Z<+8H`}qIv;Y{`I*1(Ocz?d0-aoL2uZyc=R-y#<{njBuGM+wDI$Je&WEf3XXyQQ zc)8PW(9H~nmf)iVaHkOT5((TZ0GB(s?}}kr09l=paQ1($f^rn-v`lE|;GW4>Tmb2p z>|V+*0{rM`9eqz4tuco(yNQBL1(Oi;?xk| zxyRd~de(Q$*K^>$W!0VrT6@qeWmD1BR_jAz@(4760Jb zEmqJP8K?|ydvKSqxb;k021z?-4nJD>fTtwK zJyL>u1Q54Rv5u{S*N5QNgS$k+(vyI6ow1Lpg2N3<{DAVQ5xz4qvAWtAbXFyfnJ(<7 z^n!AU0Rsb;F??ugiQ~Lz?B}6?d}>6-OqT)HnJ&<}8bbyKJTqOTxuE0h7~D1;WNn(Y zH4(aR%7B3(dKIM2r_Q*!T8A!EE6az4`DH6Hdd;TH1| zGCr#z_B{P*oc%)rJg7Ir&APdy_V+e%RC(%B~5XMF8h|^g0VH z18Rz4PuJja}!uBP>G98d5WX+vM0=s-RT{m?MA>*Fn~R z;$G>8`@Cqg4Upc)VdKWRT^JVR))J(@g~4N-IiDOa>j;IpO(@dy*55OabWu zt@vSJ$gV6-&8cKy;Pp{>QhMl)FhWmmWpQDS5d(useE-oOub+s5^nlJ=W?(2T%+Uj_ zg(-jd$9q>Za}h`e)ItKwWR|2FGc4>o=v<;Ztsf)<85b+aFD^+hN-b70`ndXrqmmCu z26WpL1B3lW$eNPfyt6gKPdsu)xBz{%2|~tWBP87P_}{i)d-7yD$TUMxXd47?gtU95 zL{$YFm4nZKWx&A`zY&syOEcG9c6W9AgfI;~g&<^dHbT4IB}vnJqaYpfLx~ zajFcfH$qxMuC5+EH6kn-5Ivv@b^k_4@30|0rk{QB1yzs?B&@GMWdwZVr+*BZXN_>d zOQ_7EA9)5WC!}%_GXFM0%6Qgvuk8EW+7CgdL1IB{6GX<7pJ7sf+QveV40tGjL2nbJ zjPKgNQ{eWU$E_e4h#q&SjHb(B2f?E&z@Y<4Avu{z#f3Qx4B>W*rMB>Mg3f6K?cg&s zV3@NB65BgHg&d@Qfp^*(>zRYj$y&Mz5_9_68&iGX*MZKA1D%m;#K5p|6C`xR%t}jj zTESlR2DM6P&! z+-2bz&<&rU7K$+g1N!(5_`Dj>%3F+aB(RJrXiYHgSilu?rVI_+AbBq1XH5f({Z@C- zs$_75!O*=8lCvx{!($E~kp-RC2^zvTVPKfC4H9mFLb{dw7qabG7{STFl7V5>Hi&TAWmxnUlgGpls`0&i)y6kD39f zA8gL>eJ3PuXUck+ZNBjvwCCFxe4IA}`!0w*e9O-)4V->Ai3wEyn1a%EI#kAQ-zNLA z`hTEy2sBmI?SiCuQ9jS8-5AVvn4$TjGnVso?X;A!m(ax(#$AjD?<& zLH2G)SWgpZa5Rox2U-^mI%?d=km1yBNX)hT-#YKNglHbPrUj>79B08BFffGeftZ%L z(`JPUTkbPZ=oo^mVaR~WJfBwbX`%hM-3XZ~sLa8q6ITd}yMIT>^gw0Ie9mlJIB822 zLT26`NR4jbG%J2P8$T05W+PN)(aO54cO0sh5Hd%gG94^yGU5fTDiAW)p)wqTcG^1_ zr<_5^yxIdvZztFTrn|k!J%o_?2bGcd^~*5mlvyf5MrbcY&mo3)?rJZY91t>UP?_7! zX>NwoZXHC(SV3j9gfxs9yB{w^$oN5JlEnUS^2>{SL&zjSWhPs{QO@d})q#*HgUX!v z)*xTABy=)DrfV-Gm&}tk*me13auh;l{$5Bf39`GOEh8(q93it6D#I)8@^ap~vf~Ju z(@>e!Zke$~_UdyGG7q6LISGsYUz@no5h3$^FC@iZTqq!WMB}ADLI(GIJ$mXDV4QOV zQXkws0!eT8?(Ltu+GwvO*fdK$LnG`d5iEl>C4yzJrbMs|)|3dA!I~1mGFVe0SO#lK z1j}GeiC`J5DG@A#H6?;&u%<+?4Aztgmcg15!7^A=B3K4%N(9SbO^IL`tSJ#JgEb|B zWw541ung9e2$sQ`62UT9QzBRfbM-T5T>_|$Qks{N3A$yw{!E%ZhY^Pz6Qh|PxYA_U zaT-#aZQuAbY`N+N(CwC>{x;}LS+qU|xQqw)F^-;wv<7D-Ej}`Rj|b?yXpkNQ1}r0h zpymOn{e(Hf1RY>vK=1#6W`>N-^(>4T(EC5&k{sOsxpEp(j-H5m_9W_D+&?BpP{jnY z=lN+!NwHPPr1#*{BVwRgE70B>28KUSnbkMbU*-Pj2Hl7WI^N%a0gnqXLo^N=q$S0= zxtSHIMMmYXBc`6q{}0`hZ^FPpO6bHvg9mpQ6rF{X%8z*6{XV+)2!TgREJ1gx;Pwx$ zRlG)qxJPJl`xJNDsX7PgFUQ3PV#X=>o(Ir6 zhjWnFPLA4f(NDYuvPTWP&Tj5G$la1Fjmy4Y@stCdF9uppVS=j#2^u!U9R`@|{o|nP z{n2~k#N-m(VL)A<9=O23pvJ(^V6XGRv504vGYccA(+J8X&n`e_A{(djJFu_H3qioMA)|YdfkB0Vq2c?9b7z9p8#)j&flxg$sRy1V_q|z;kSV*! zz@W~+(4e(w;f{T~ZeK&l%)Q9Kpuxbf7FZo{Ny_y~Mzv47!EsSD&A!7~=*?+do0}#)5qsa062M+os&S z{%Gk3&-y%zkrc4k?%FNRTiSgfCZ1YaGa92rjacWUnYEkTE zg~p&X?;l?oA*rLq!t@Md1>ZmP8<~V%ASLz!>vtdgt!8}7D*z9nF|c&Jl-#5>RYMUf z?{He7S~N+gY$?<{|Bh`%>6a$W|I-TTMXuO-`J_wDE}K#dI|W0yZw*m=3p$;;3^b~kqMMPLQ;=FzkeHmEn4Vg!pIDITP?QRh z(@)AT(M>GKgi97@Bo?KnpvptU5(_em^~+O}K*!RimY}MD3xmu`$}cI_PsuD!&M!hT z2r8hPlwabIn3tcEnUk2LpIe$!lBrvqnwywcl9{Yql$w*8Se)vRl$oEKnpLb1rX4`D zlPReMsd*`hc_sSjigi=+le1Hc9Fp^Mb2CeFGV@CGlXEgrxS%5v(sh&b^U6~5N;32F z5_24i6EjOP@>5dFFwIL!OwI;x*LFa+NjP5e9amhuQ1tpL~ z4N1ruB_##LR{Huysp*-;B}J8bc?G#y#d`Tg>G}u_`npIQT~j^KBD9k9DzG`FMM?Si z3<1f5^n++!&>WhXUP*dY37DIbSX7>wr)y?{!c8p7g^A>3=9N~!GI6Hl>G8y zxB?g-tb~xGbWv2porofea4^xPnVDc1WoANxIY^#_8-yfAvPnqpffz%QYoIX%4p6vr zkOfI_4^$hHgWw_vQ$T47J>}|U72^qhbk+L0nEBNRG&E`eN_cUgUYMSNZc=JVqMjjU zc7$Xe-Nd}aoXV=yB1{FTMMe2og!2k=<3M{@@fZy)xpedLOHzw<(^K6s;$9lGLKy%)G>$+|1;n{M^KneW6-YlwQGsq^ zUU6n>Zhls#Zc%D_YDH$TZdz$h4%m0#Ya(F@VE_!4t-sH95TA#<`BpQcuYld zlD@7!5>FSrAdi4kfq=NEiS3dNyS%WBB_E!0z{84Xh9LUoCGgM zhBPaR@Fi3fbs#T+7BR#0fddijHHfmj#N1RoenTjN8wAp!3(CwUAm8E93tfr;N|~AI zd5Jlxy5L&`rwEB`V7oWTsVue3zM4Nu2t`qT*Dr zyFj|&9E2ah0RnM-Qff&_YLPCu+QOIIaB0!k)yE~J3!0ZCl#Rd^>!wzKvLC2c)=jO@ z1>J56)|XkVn^;toSeaRzfhS;*R6)vCm>ym5L@$BlgA|{j5(E*u;QGK2obvHl4zAxz z3-opMp)BOImkeqVfDS0YV?a(mTopLg!Fk|}2TsHw6Ldi}Pzk;mLQ(~e_L2;!9^%rB zF2o@qtxzVy0eGAbT}TblmYJJb0**bfv+?RGDFP>H-IAh8a5^ERBP}z%v?#S09L``C z!gZh$ydV|Su*%F!htypKcW(lylIINLgwT z!R|*{Y7xQ|bI>w)0@fq9tHDheL^B)Q4k8p~Xw7u65s4{C4R>A8@^C${F|eYgI5W2( zCl%DE*G)?;##g1o)qpyDl?AC_^`H_BR3_t(6NtmIL1_ub)CFyG0H+4%fJR<^QEp;R zW>qTOy(x($iP%L;^D>k3i}G}H6Vo%3@uUS5b^5ycD12Se${cW5;4uMGqk*a;P;rE) z_dus6f%Q5hB_^fj=z|8Dbkm9wb5k?(Qc^2)OY(IK@{2P;Lq>Q^$50EZY|!SL&4U{a9z4_qZEZ^db!l}oQ$V9=X_=`-guMb8dBmz2WIb-9ksO4_7DPCJ zA{HJf;NlsaEb-`t`xD$!f%+LyMBvehJvr;^>SLDx_o2X14-Laq-MoTacm~PIOis-! z#@BSlr%hj1AD=Y1um?Ai2p4t%J#H6SNsCPT*HbU~*`gYyF( zok^hX1*j1VVSt?sZs(+B=H%!Wfo2`>=td|42RlrMF6itFu+1g;kmfHzlTa0c+6X8* z!3`L&iJ8T^d8N5YcA@Qx8t+gyIu&0vLQi6BH$hC_|iJuYpYgw^Sib_u}N7 zOkH?Q2;!6^Cgr3WgKty=4~W5=)ESipsYN-NdD+FFrXa*R{7pfK8iY#>LDL>lLI&T z;CANbr(}YfAG%42c}C>GR)PJWlbHnSB7#PPb25{_ojgJzi3k`_>kb}D zU^@w=C-gvuWGv9eRD!nVWa>h41ISi5M;Ejh9^8?20H>j1eF%jo1)(YgnFi|J73&(p zD^gga5|p*z*%6P4pqvU<2Qm;j8-r6AbhHI#uwE8qh|N?tqa-&6+`2C^(k&@UP1Q|I zNh|;zolanC0W>v`Sd?C@YY1MU0V?3}I1uk_1ZXVDl7IoIW5S>TT|%`f*i*Ru2dZE| z{TSRTz^zVjQH{rA5L*z2!AJW^&;vR`h>$(GRiJ{iO4kT<>^mr|67wo`K^Gim7N_PX zWu+#U=q46}MtMszQu9EgnTa_$c$|Qw3RFcQ^yq?)BL-&$J6i?NV7-BYje-eblaNdS zt*{Z#l!l#`Y3!|@Ip>d5aDq(JhSL!f!YknTu?L6Tn}6#<1qkr zXbNN{p^^tx2WX7|IGFL+fm|_zL#a4Z7g|pv+5o9}Wtm0!dAX^0C(Mvkfl6D19`JY& zA&0=HNkPqJ@Uf@5sl}jhHU}M9jDO4nZN^n!S07CT9OK~343)@DElJb`)q^=@_$oXs zsu6}Gn*nkt!CD=>YF=MgA54SidB9~lp4iC+C7j%1aOp}Y?lbc9vvrG0Qb276D3hr6 z3TWCoCqEt1y#$S-p$h1NmzjZ*dSX#}nQj_r=m<3DP+X8$o(GwD011{S7Nw*XL1xEw zlk;#0%cJM3mguFN}kNTjMSpc5>QryF~PQh%L_blfE>%9_7GB}gY6|$yrX9pPym8g zmmu;FA`VhY3vyCHYwz(m2(AWH#=z8rhcpR!rMMs`vjmi-APlhc2?ZP00s!O(Yy|^R zCZZNFpxOwf#39NI&>8|rz=Ea&;R|+%YG;ARMnHuUXt58tkDiyJ3*&=L0EY>5<^sH| zq`0&o721Kq6ER4tKmml%gYZ&XXVi=B9$x zgEJo3o#1tfMJ2i!MIeQE%t2KMPRe;@sc@a(;RLXWc+5a|KPdRnyae_vp&|h}a6w@O znHK_wHMo=p7xVbsficl&20E_^RDWd_>%#h)&^~%<3LcZNDF>G3qm0TJ}Uy$ zFaq5!pbMH@1M5KTEP_|GLTj($mHEjf z&0D3pb?pZTwPck=@QdA2QLc9P6f}kCzfRBW~WvX zWiLEcKoTsZKm_X~6ko_`3!)jG)QAdWl!OXuiXtamM2`Yb9OEj%5tEq3smZ#aHRfn7 zGCXEF;Lw7l5eFKdClp9X852}mBC;vigW!6SDulXoS~-= z8gN1I@wghS9<;~|qz_!i5Gp-0i&HB=Eov|g)XKkf_MmYBLh*v9ECr2hfG&4~mX@HNCFleL{QXME>O9eHl5KTfn_Q9^P0r@jCH#1o` zEe~7}6AB}wMd+Y12C*a^QDK3SCn&3crtTms&QtNOw1ZS#mDL(D9|TqSWFH zP;Cp_IgnbMp$i(~1jkB2B52={Zf<@`X%4=@D(G+)c&h-oVnbTe1YiG($1y1OfSO3i z_JT_(uy;Uv2#PaH@D_S-HK3%Mn3Drm4_=oI_7NUOz|%Ce)CH$+UC=g6u;qAkCZcZQ z07V6=2%;a8oRbP_O_wAlXM>gn<$uW%@324*gjxx9&a61j0 z6bp1gE#9L1+)TWal*r0JSp!KIQFSJ0GZ1Ke1x$mBC9v&yA_!L<2bz`whj~h7F?bK9 zJ}7Bc;4jrsV*nb+kdOqWMWij4ps}ai%#vbAHBdxc6_=V{4yxW#^UHN1XS9L3+j#tj z)j5zmTW~oCB1=@#Ml~I_7gQIL+%U{XlLv1F0mlt=5e{TPA|o*`B_}m0u?V~p0l~xL zNrWO$Hwdm@7qmDKJW8CGny;H!T$z_#oL^d$oT{6fSWudt1DaRM0^0)N;N5r!QKAc; zUxKqiI=hs zuM65QPpD&)3q9H#l+2(^aPt8ika%oFiA0ctA>&ErdZv1WwBkrc&@v5oI)W-dB&6j0 zTu{}j3mPKB<5Xm2VAB!0z-gK-Dd1uLsC&M5QCl3Mj827nCjva&^J0cEE`#FSRI5Hzl#C1hHtdv?vF* zQ(8B%v?K$vCKQhwOHy+SKqDw%8k9L86LqC|nT4gWeFu27qo@PTv>@w46#mIYl?9-6 zS&4Zm`MIE~34eP6n{rUBV=;xO?gI7|9kaP>6yykb=gaQ*#SI zOhka-(T64ez|9QE{&%EU1()~WvcaJMlsrJ=X`qd{c+5ss2u)BRJ>W(pl35_J%yd0a zgR>~LASW?76{NBlsi%h%U$^4tP|kpeVm2 zUpFTc@8bW=JkZ9(%)CtStTmxx3SlIuT>>{BTsji!Tw(PvD9BKQ0Gyw}A%`d232ek7 zl$0P&frKJ*oWR2pSr8negiI_-2BoQFa6o_qF(oy%KsOaU;R_j+2KOyNyUkPc@NUh* zq8e0{fQL^|%m5de;BpI3Kp{B;R6-*h1+GHDz5?}D!AU(QQxDP>$SbJOO$PJum<-Ax zy80lEhM?nU!4ApEOv)?B%`Zv^*Px)?=AZ>wph>M%hrEK^E3Gsy1;PR?FMzN>o6{kzlGMc9lGKV4$eK0K0i+@{2Ny!KxEWQ!-0*i&8HDXC8?Qt;HEriD_2@hW^xI5?S7bGU97GvG60NUK;kXf9Vm|P4#Bqc8$yw@-%71V10wQ=wy zPEfEyIu%$3YQV!S_y%%7<8y>cAiOSvW>^dt6IGPp3=CZRIzRyhuDif_5KpLJ4}4H7 z7bP~pwJP}37SNmrN>bGYB?p)>V0%G*Ox?8noD{svzHw;*75|`ZOh~4Hi#2e;gvTe? z9R@0bQ<3(?fl~p<#b6(SV-%DL5Uo2{a!tZp+v3y%a(zLrE>`ovRuYO3P(}xDAOOVy zsHXucGQmTx;F)Le!hhY=iUQEOxqN&L0yLHSy837$;7|oSCn+%pasm|CVmx7tst{x( zicUm54IUdvEzSkaHRiw*QHTRAb2S39Kw};Tc)|q~X`s>>8fPFTc&W@v=iC@Sx7fI3sgaYl|c?3aR5h>AxIHK0Hy>q zvH(6u!yyhFd2l{R0UmF{)q#d3iV~rQ!gqk;(TQ*aw5W&b1?{dO6lb8!4A}(%>c-|3 zL(hx>4}O7jC;@w6VFtAs#sx3M0S9hsVsS}gab{v3`1~0>rlBeXje(=+1Rse&$T#3E z4{+}w@pM6VlYrfW#|7Z9fE+~v3k(QP7qkYSP-vi+8=z%CXhjKlFCHOtzJpYb=U=^$clvcr0^u)}vfT!lc zgDj~Pso>EFLSX@SIkbHXDwIGDN8%!;5MYYaGx2RQ1}(e<9bg4ArYJud#02dwhYXD1 zNgPO~gX)aT5^y62vhxlajzy^@MU}8X&df_AwBH$2l@}m4q@h(fiWs;8CKOH}lTcgJ zP@_-9|oyKMc|XNK&23r3APG6l7Yu6aCL^-838#N zv=IhX6wx5dDlIMnAN~Mm=q45vVlfro?$RREx*irR^UW&~70(AjX%^CUn; z3OL$eEh$}a-xH5}K?*@9&qAAAASQS{AfX&lnV16_{sOHegs_Mzyg{~sT6Iv{Kuqxb zIH6F5>4SI&SvS~!-~R!RhgKU3e!bY z)S!j|a$tc21)Pq-?!)5-jDZu7G3bLSph-f|fgqp&E7r|P%uPy3%u7wrFUbU*Tmh?N z64O)jO5kov%PEDdZGr5&haX0ZZxss2o#@Izb8#gl1?a-yRUU*&VYp*pgFqn9WkQCz z-~&V8@kT-euW*yV-a}DB%QZs)51_F~|)LKKR~S(1cJ$r7lo(G}fb56MEYM9%0c}w(F3wFX zDj_1-BPjzJhr}hSO@puylpW!=gPR=SI4CXvEjLKh#oLWWC<3_wt^?Gwh4>3km}V6x zflk2$QDF6i%6D)qf}<85?Vvr@;4UwrGz@kIa$G|*1WNpao55hegOy`X2_Pvf1|bW9 z%^_4HAaXvamVy@ph~f}r5so~N2`fH8Q3qp#a}S|#NKOQw-vR0eLs+_opgD7J`39Zw z0}WY&2Gro0H90>gCp8&gyBb|7a?Xb81~;Jyxem<*h*?LNo4_>}IGsS17Uz`WTfT&( z3Sk&X52E_b%q=a-EiKAOgp{+mLL8wA6y^vXxE3Uo=aJ?GK#e8DECJX*;K0Wd+prQZ z4YXA&t(d6JQvqmM1*rK6q7k0OV+-MRWT2(U;IPMI3TROdd|eqRq)^t25z|q`y_q%hmQ!O_>wd7%kw~W* zl28_gC05A%6u9RFny3QXO0?e6JYC345lBCrqYEDogINOJPLN(%fG0tsD+Lv{`6;Qo z>6HcG%m)q$P}sxF!XE)JRmjE|f*N&Tb5bh`^7BCFk0s{lf)47>PfNq&G;E_0;D#?O zIppORln`=GK?&F?1ts7ERtSY~Vp393Y8j~d1T(;~0(LnbXJYXbs270gRiX+ejOjtp zC=zHzB=VFYczA`7U-Alabx~#>K>>j-2KFH#lQ8B%!37uE+$eZdlu%I(8nCEJECTJY zhB3i;lu%;;G>ZgE>!3L!P!a&UIJdL}yiN!IVoxMhpmGJF2W&B+$OQ!=@{}+%43UMv zkpVUblubYdJb_i0uq+3gb_OLk=nS+jXayQL5R)?VKzA%;7ng(1A1y7=1h^VK!>z~N+swLqs+8Q-Mq}?RM0Yp{5)OIJQ8Rs9c&6H zZOwu(AgxXC@CC@VprOVb{HubHl|g+4(*^Esfpb=7dLC#OIw&dO@gb5bP`D%XfU`EC zc0YWvhOR!030}|)4p+od4oHwA_@LeL2p)LVQc`J}ZgD|saxp?2w80a>D@!fHztj<3 zDQG?(tOwi+2fGMQI3RR`90%$OfC3LR5eZIgMCpWh6EqbIPL|-107nH;x>17x)Jz2} z;DU#XF6bgB;!P_l1)ZW^Qkn*K1)=5;NF8|LFoXdfIRJ;4LrG~`RQUO34sA$y!cszknN`UzpQNbX2 z7a>Se=?Sj6^mX;YG`K+ojx*3D@yVb~?Wv%ibbb+JMG(FtYH{cQxiG)D1dCa^@D2Z< zYC5^NIJFqxxy29#pvVBL1bY(fR6NlMDqlfoW`e3}P_qrG<_7C0lpNqo@IYhyMX9Nv z1#Y0ZK*Bpx;jINwi34jnfV;KeNCq{Zbdz&abc-`G@tuByq7K&Hg6RVrM<@tjia`Mf zwGUi-fZH^9g07$_KRq3^8XLkuuiE^e_nM zQSYF_0(SBv_>?_eOFd9`9lsf{77k=>A}CNG3xGj`nuLNRITJJ)m=DqfVG$Jph*iR% zgTV;dfwGViycrX;EEJUPKqm`>=DUb90lF>|ZUU4K9xNqvydK02pd~BGpv{iRT<}04 zxHW_)=|D6CPDj}=KL5FeUYZqatg_PB(`oWD6LP-wf2GAPu%(OJb3J-!22f8>=S0Cb~ z{GxPlG9i>VGK+PSGgCl=V^9`&?Go61AaTfQB0M%D6oK+aW(xF7B%-F(GC}7_r-Sd1 zfib}`4z?YSouH-Tpqd0sgPTo+bRd?6mKNoJ$^g(|cHnyuN{e#9WeA~~8@?Y2q#L$3 zi730%ib1KT2$U6JOwbwrwDXKCrl+&61>g@T?gpQz!K1%l9!N;LJ|g> zN2pkZnui=}2;-1N!Knls=Y(B{h!lib2r=-SHz9w57JVmy%1#IavXdWdJ!n~zE=UyA zx6uXd4aDQ1;+#bAxfNgkxSf&-u4O>RB8U;G6(t}Y z;9*umE=La|P{0-!9+U;Hfe96SSeym2wpb79MzH_EO;peht%AfN&}FIk4!A~E2I@qEPv3*+0tW`T z%qlERElt%0-&IkPSe%U~TF_L2y0fTy!G?mP8ILI_zAQ-u&5A*jH@JlW_8&g&C>I2P zlQd{ZDK!y%_#FzL$R=zNEU~6!LYPDikzg+(^nr?` z;-vhNlAP4M)MR`y25)D8nkk@$FKGN2H2ef9USI|ycB#WPq!%R?WEAGW`JgLQky|rR zKIDAmq*Tzddfmh{&|yrV-W0qU2$}Z-uLr^tr`VJuw;qv90k;6auE%2@hI>FA9CSB= zW0{aiaQ}nD9Mm%gUAPWf9|&3wP?BB+SCC(jnwMCR39gawb-u7`0G-)|Y=Ev2F>5H` zW}(CeD5s|umV(y)f*Zl$1~kE96P6aBMj;tusRx>IK=x~DUJ9YhiQ!IzsRcE~q1qtr zXrf&NO^Tp;2$CqlJ!Yb{BN8`gkO6)k1GqIn$Ph#(1EnB%jsq3MmU;wBDp-&rxBL-V z9NZBgYHAK1mQaHV5|R2B2m?`sL0vb{0bTg(Da>vYXd)o91b(+UxXj1}UAqap?HrW$ z2@eD3q^6aC3Ib3A5XuB!s$dANe_^tqgEPuA^K`*i$D<2Yn3=$ZAh*(^Duak2t4b?M zO-(9JK{E~}imb9cGcP5-9CZ6@E|M)U(F!wscRj!zg`^zWn@GY)UdB^eVHgPZCpII& z5+oXm0EHBY1|MZbsDTa{696Sl@E`}M=TF2HC!m8L zbkp)c2@S>swZB0D0%|%yZbmIC%>$iFil;Dy-YyDN2x^5uw15`Pfet1sNv{G8?&PHw zftCY-4yz_$AQsgiQ&ZDZ(aZqN#ert3@CQHiyso5D(9l~_X&$IQs0SWp0dc^sL_EHO zs{u79O7kF%MX(Wu;5~{^)60vY3{d|ck8wz&`BepY*bQ| z31$=7iA>E+%qz)E25nwTO)O5;PtOE(L_s&p7=n^4p;7=eBLKQq64d&F-7rba!eY>b z4`_rPOcT`v1$h!QR0}HMp~vDwy^AIa_Bf$n$A2_3F+KoIQbNWRNt>QxvUJbgx}Om`?w%q+>sPf0D)FGfrH;PuboaT8E{;FKgTa?!M7 zGtmefTi{9(yvm~>J6*RF@4|3&7weO85-c&1hNFQ5JueXy<|yZo!Xp5x0NiRK6sb7u z057%%&2HkA2ipKn=%9Hr(6Co#UOH%jEof&Yo?L@!DD-qy$bqF8zCw`&M>-)dL9XNh zMK$=69`N`+Aw9XsC$fU#2J9<5UclmE=o}$hFo4JE37Lj9K*0NpG2;LlAP`xw>0r-! z1^fF2gKtC1EY<}to&b#q;_(5pGRUwtToXVi?5&s1Z(J>;g2B1&%&EzQHVaA=AI00jSK9jM60VF(u$OA3jsiMt@L7XF$yc z$UrZOFxX&1X%{{kggmB8%n9A3jIx3jToG~zaigrD40MncRFXlKWFRstLJT~TL8_795nqI9U@o}z zMkuG@TM+>YC4vsYC@PUo;smW|A(TNOx0b_?s|2m3N4rR!m@{j@CpSSB6@a>m$Xw#i zZpuWu?hZ6Fip&RhrU(TD?mJ6B>tG2L>5x!Bcpfw{LP!tj^xkC9Mpp;}H1z>Wjrh|w z=s3rud{BoJ$^z}PAgryVC@~qlvX?I;M}1UhwK=P)dL>h$^I$iu1uEFQ8jh%=ADz;!D!2Kt)E1 zZfb5O=-yX6UPO#gfcMguq*p=iI0ao+T3V8(YXLtS6^|h(mVugGl?ADA%Rw_kgb%5L zG*ogj^FZsz^3pOvw{Yc^!3J_6O>%TmT|@ZcXbwpFL9W3&-H5Ih>f&OMd7#}ppust? zA)p~%xEpjK+F-XV;0X@s7!(y+)DSB{p@PpkgerZ|*}%FW666<9j~e6`BzZ`ZM-DBJ z;VAt4#1v#cs7Xqs z33NwdG5CgCBp#x(g~#bR`JkJK!P`l}L)LH}XvCOcRxALY4-DD&2xWq6WP)yn>~jTG z<`5PlV!+o?>VntG;BhvRD$r3=poR=Y4`ehLG<=IkH*$RCjEP+N>&=MkK zxD-~lURINUFe*mXcVU0bWcAt~Uq;K2$f@K7@8KSJy-jbP6Q?>J_hNz@=|-F6ay_P!S9+ z8j(GVNR*((uUVNTpcByXcon1qG*XdS0y-|=$PhAFfJXzKSOAwdkV~_m@dAo*Py?5{KC@2 z9LSJVaz<)yqAu))cu+?!vnW*;d^j~|=pOH#U8O~t(5rF43|$jF!V^#hrFoU0v*STU z50nWWZ6Op&SX>59PKBjt?gbA<5$gA1^E)V&gPLWxP@QogMc<;kQb|XwVxc&zn z{Q$F_P$VK70(AzcvjlMvDBMU3bco+TaR>G!!~k#-fR3I~AE;X&Ye(cp$VfXhYl4yxH1Fc64Ur9kbRj^+5ojkCIP*h08u$%JNli&DNX<(D z-2#n1_@bMVpPUWur{jg)jcOU(m=np*jTDm4C#HJ{Oec zf;t8u3cT_b?Ap}46y5wZ&Z6@=Gfqt0=+7Ap(aqgDcET!1_^)g$a^tC^%qHOazONYaC=v zA1JON;SO>hXc-nbEfDKnXvBjZ0(Ba)5b17%>P9#YAwarekVK5^I3y8rjYBvMVi-!e z<(B4@WWrCtg{`r{7n+HA`8k<6iAnm{wSt-vSj`0YmcelanzbuR&4KP=Ey*v)Pf0CF zO)k*|or9HE0_EhTmV+)wDh5y7LXU1Ojx#dQGXUS?VPptl*x7K|#gU4S8mFh`3`9;~Ff)GR_wwvH_H)dFa z+lmGGpy&WqMW8X+{DOSQRh)PX0!0%<5!3((2fUaQTtOt2<|!1F<|#l987$U^uG&il z$7)4tv0h1WN@j6#eo=}peDj3^R0LGUmuBXqq!tw;34!%C z7pE4Lr55F+7Q+@DCZW0^vAkHfI3*kE2Hm2RV$daDi4J+G`FSwokPQKwnwFDTl&TN9 ztt+*t7&2nykYAvinwOrLmzr9XnU}7gUjT9>{*%ZZP$CLaw5H~jfREkbV05G-<$&03-(k>er{?-a%w?IW`16IY7(@WSFB5fTd^qz z9b|;X6!3OgLM1JRdthOW>_(zSD!`$I5*whLpIDNJ8by$BBruE%Prcxw6KG1-HP$oN zBeV_@YjOkkb>PXe9DMB(;l;bqEQX$-Q4NDcxPcyYngdVLfVm8LbqeTkUvN^!qYvr~ z@VOwxg*l)C6m-LDNqQBuhRexJN(L7?x<$qMpn+$YO^CJ;nh3bb0~13u@6z*1F$KYG zYN#6!Et=fovSd`XU^8_wjDQG(48h|?Y$k!UfI7^Xd8kcp(A1nEp#zJs8JM0|ie_GV zUMW&&65&2n|AVp}tQ({Y3J}r^LxekuS3&JXkN~-cA$t>K7RjE3rW$a>fpai;iw~Gf znwy{oK^nve4v~g{YEf8%2E|)`VhVT@3AjItC$5k(1<3V~b|WZ@5H)-YYqx+-FwxaV z;(?BP0^M{_l3tZsoCIn;flk)REG|hca)1edG^G~jLMMOlxDkgAQ2!mS9lRq2yiN&4 zHK_E^1x?ErgT@?6pez3I*oCYNH0_XDq6^cd3)(jVwgNV(1zK{Pn^=HvAQoMzzOFvH z5K+|y4qt(qGN8TKU@wAeFmU+@ZT^C8I)(04!sBI3#h_jY;=+5V3AzS)rg}!;P5@-J z19)-*kGV*yK)r&}G|)9I;0X=T7!lZ=pn(=>a=>E{R0TM`A!*4#&(K282;4$|1<V zfJiLLMH8toGl7c~m*(XcgU4ZDX2Q!TxEe&(g9|~7&P+5mf(zxPmVma1A{!1d3Pn|7 zUP@7ZCU_?)TqjHnSu4^mO1L_RrQm`DO{}0G8Ocu&b;ZV(1{H91pawm5=b@Sh^((3% z%z>z)FejplLM+e8&(BN8a8glbadH`kXl`afv6)FKzT+m~-hh}6(*Y{hz||W}0JN|O zmJIMDA2j2j&Vri;<&$R^)aP)+pnOsdLkV0^Qy6&%GO324#2;v{IwBF8nUHE2atZ-e z5Re_L;6y;GQIPxr)(US6fO(`Egp|6WYc!#0om7J$=?k27;4uj1k!KJ%U&0Ln^T;y@ z94c^wz&uh7f~6=(a{`G+szI>S1ThGSN3KDMMWD5TFoP1oV;u0(oS1A3Nk^b^7M_ei z98%qbD8WH}ICybRsxgo>1abg8!ay8Sje+JfNGKyXq#6SW8;B`zHmQcdYe`T$09Ip? zY6v7;ATEKk$uk6!VjzYPpI)FfJiOhB)O>(k513k{Ype%7e#OofwBJ?1#t=^egc^us zjsd7=X$Wq~z$=-=%nDs_*2iNKTrtE?u=)(1fM5ck;IahWV2OW17|l3ng$6eb$|uh- zXoUth49X|fFjxTuF3Yf0aHMz>sq6rsq=3RF)n(AU26GrPmsF!584hL=5|31a@K$i7 zgcKxwfvYrl41#&2x(H$rI9VW5GO94T7a8NC|_)Bh?^S>VX)9#3R=r zNO1)-2wGr~5^Io@1FA(ZDmYS%!BfGJVhkjXpsqo1NDUom#)5=0fCRSu}J537EN3=_np6{vL&nF7UGsHm6ELiA)ntz)qyqQ>BnU@S%F7Hs5lbDiL1loeH4;GG3 z1#M!9C%hSmfKE`J1X+V>9(YqVcr1;mqylyk#Gx?rAUs`Ak^{#zK64N;f-B{M=3~G? zg~u4wPy{WdKnYe*3=>|M2{|MNlqevF%9w#JlqO^|QvAZUUV(-X356f#kwt_;DkU`; zbg>vH0m7Kzxkzw^bSTSAEy_hoi6x~)sk)GPY&^-XDkn1ueBvR50rC{VoefxQ0QJZ*?FFB*2@W7UF2L{rddPr0 zg$N!zCXsX&p|Kvp8-;KOI&^*odyIgW2oTEd;1yHgWj~-{cF@s+V2-Yl9_aRulJu(b zjKq@ibluV-d{cr5MW6_P>j3X?0Jk2?Q%X>7~mzUg!(t2dkjF=2o{0QEI{Ic@3JJ6?hyG5WGg(= z5p`5XQf>icXCZ_E8dQMv;tCQ=k~30^^&Mak8IC7z%IagXYK3i zN!L2hFlv=QGpd}TaZjYi_-^z$j!1}MC!jQ!O>t4O9<=n(D^ATVOD)n1O9iJ6I|UQ$ zmVlkZz`zKliKPaU`X5mLOGH7!18<-*FfgEpBYHR|K+S1@(pbYaxCFEq7dh$-k)jS9 zjQ$J^415d>4HXOw3_J`B4g64Ahk=2CpMjyFB&amF#MLEb2h^S;P@2o8)Y!;Q!33Tt zOi)c;G$&_;fU>PK|D@lG3LHzEGh9<#en9myL=z4Jd&$*5S_1#bdO+1CKxtHe=Hw@5 ygRYU%3jnA56qnQjI|U}>`HGX{nRu%{Uq7zENMpI_kJ(hB1b78(E`xx_92 delta 24251 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 Date: Sun, 14 Jul 2024 00:20:24 +0700 Subject: [PATCH 141/312] fix(apis/websocket): fix undefined error --- apis/websocket/src/classes/Client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index 93b44f7..291a621 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -55,7 +55,7 @@ export default class Client { send(packet: Omit, 's'>, sequence?: number) { this.#throwIfDisconnected('Cannot send packet to client that has already disconnected') this.#socket.send(serializePacket({ ...packet, s: sequence ?? this.currentSequence++ } as Packet), err => { - throw err + if (err) throw err }) } From 43bd0a021cd885a3d74a1f307ec2935e81d17458 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jul 2024 00:24:32 +0700 Subject: [PATCH 142/312] feat(apis/websocket): clear old client sessions and instances --- apis/websocket/src/context.ts | 63 ++++++++++++ apis/websocket/src/events/index.ts | 10 +- apis/websocket/src/index.ts | 96 ++++++------------- .../shared/src/constants/DisconnectReason.ts | 6 +- .../constants/HumanizedDisconnectReason.ts | 1 + 5 files changed, 97 insertions(+), 79 deletions(-) create mode 100644 apis/websocket/src/context.ts diff --git a/apis/websocket/src/context.ts b/apis/websocket/src/context.ts new file mode 100644 index 0000000..27b80a7 --- /dev/null +++ b/apis/websocket/src/context.ts @@ -0,0 +1,63 @@ +import { OEM, createWorker as createTesseractWorker } from 'tesseract.js' + +import { join as joinPath } from 'path' +import { createLogger } from '@revanced/bot-shared' +import { exists as pathExists } from 'fs/promises' +import { getConfig } from './utils/config' + +export const config = getConfig() + +export const logger = createLogger({ + level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, +}) + +export const wit = { + token: process.env['WIT_AI_TOKEN']!, + async fetch(route: string, options?: RequestInit) { + const res = await fetch(`https://api.wit.ai${route}`, { + headers: { + Authorization: `Bearer ${this.token}`, + 'Content-Type': 'application/json', + }, + ...options, + }) + + if (!res.ok) throw new Error(`Failed to fetch from Wit.ai: ${res.statusText} (${res.status})`) + + return await res.json() + }, + message(text: string) { + return this.fetch(`/message?q=${encodeURIComponent(text)}&n=8`) as Promise + }, + async train(text: string, label: string) { + await this.fetch('/utterances', { + body: JSON.stringify([ + { + text, + intent: label, + entities: [], + traits: [], + }, + ]), + method: 'POST', + }) + }, +} as const + +export interface WitMessageResponse { + text: string + intents: Array<{ + id: string + name: string + confidence: number + }> +} + +const TesseractWorkerPath = joinPath(import.meta.dir, 'worker', 'index.js') +const TesseractCompiledWorkerExists = await pathExists(TesseractWorkerPath) + +export const tesseract = await createTesseractWorker( + 'eng', + OEM.DEFAULT, + TesseractCompiledWorkerExists ? { workerPath: TesseractWorkerPath } : undefined, +) diff --git a/apis/websocket/src/events/index.ts b/apis/websocket/src/events/index.ts index 53392b1..4c5778d 100755 --- a/apis/websocket/src/events/index.ts +++ b/apis/websocket/src/events/index.ts @@ -2,6 +2,7 @@ import type { ClientOperation } from '@revanced/bot-shared' import type { Logger } from '@revanced/bot-shared' import type { Worker as TesseractWorker } from 'tesseract.js' import type { ClientPacketObject } from '../classes/Client' +import type { WitMessageResponse } from '../context' import type { Config } from '../utils/config' export { default as parseTextEventHandler } from './parseText' @@ -22,12 +23,3 @@ export type EventContext = { logger: Logger config: Config } - -export interface WitMessageResponse { - text: string - intents: Array<{ - id: string - name: string - confidence: number - }> -} diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index 571ad1a..e0795cc 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -1,32 +1,17 @@ -import { OEM, createWorker as createTesseractWorker } from 'tesseract.js' - -import { join as joinPath } from 'path' import { inspect as inspectObject } from 'util' -import { exists as pathExists } from 'fs/promises' import Client from './classes/Client' -import { - type EventContext, - type WitMessageResponse, - parseImageEventHandler, - parseTextEventHandler, - trainMessageEventHandler, -} from './events' +import { type EventContext, parseImageEventHandler, parseTextEventHandler, trainMessageEventHandler } from './events' -import { DisconnectReason, HumanizedDisconnectReason, createLogger } from '@revanced/bot-shared' -import { getConfig } from './utils/config' +import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared' import { createServer } from 'http' import { type WebSocket, WebSocketServer } from 'ws' +import { config, logger, tesseract, wit } from './context' // Load config, init logger, check environment -const config = getConfig() -const logger = createLogger({ - level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, -}) - if (!process.env['NODE_ENV']) logger.warn('NODE_ENV not set, defaulting to `development`') const environment = (process.env['NODE_ENV'] ?? 'development') as NodeEnvironment @@ -43,52 +28,16 @@ if (!process.env['WIT_AI_TOKEN']) { process.exit(1) } -// Workers and API clients +// Handle uncaught exceptions -const TesseractWorkerPath = joinPath(import.meta.dir, 'worker', 'index.js') -const TesseractCompiledWorkerExists = await pathExists(TesseractWorkerPath) -const tesseract = await createTesseractWorker( - 'eng', - OEM.DEFAULT, - TesseractCompiledWorkerExists ? { workerPath: TesseractWorkerPath } : undefined, -) - -const wit = { - token: process.env['WIT_AI_TOKEN']!, - async fetch(route: string, options?: RequestInit) { - const res = await fetch(`https://api.wit.ai${route}`, { - headers: { - Authorization: `Bearer ${this.token}`, - 'Content-Type': 'application/json', - }, - ...options, - }) - - if (!res.ok) throw new Error(`Failed to fetch from Wit.ai: ${res.statusText} (${res.status})`) - - return await res.json() - }, - message(text: string) { - return this.fetch(`/message?q=${encodeURIComponent(text)}&n=8`) as Promise - }, - async train(text: string, label: string) { - await this.fetch('/utterances', { - body: JSON.stringify([ - { - text, - intent: label, - entities: [], - traits: [], - }, - ]), - method: 'POST', - }) - }, -} as const +process.on('uncaughtException', e => logger.error('Uncaught exception:', e)) +process.on('unhandledRejection', e => logger.error('Unhandled rejection:', e)) // Server logic -const clientMap = new WeakMap() +const clientIds = new Set() +const clientToSocket = new WeakMap() +const socketToClient = new WeakMap() const eventContext: EventContext = { tesseract, logger, @@ -97,7 +46,6 @@ const eventContext: EventContext = { } const server = createServer() - const wss = new WebSocketServer({ // 16 KiB max payload // A Discord message can not be longer than 4000 characters @@ -113,17 +61,29 @@ wss.on('connection', async (socket, request) => { return logger.warn('Connection failed because client is missing remote address') } + const id = `${request.socket.remoteAddress}:${request.socket.remotePort}` + + if (clientIds.has(id)) { + logger.warn(`Client ${id} already connected, disconnecting old session`) + const oldClient = socketToClient.get(socket) + await oldClient?.disconnect(DisconnectReason.NewConnection) + } + const client = new Client({ socket, - id: `${request.socket.remoteAddress}:${request.socket.remotePort}`, + id, }) - clientMap.set(socket, client) + socketToClient.set(socket, client) + clientToSocket.set(client, socket) - logger.debug(`Client ${client.id}'s instance has been added`) - logger.info(`New client connected with ID: ${client.id}`) + logger.info(`New client connected with ID: ${id}`) client.on('disconnect', reason => { + clientIds.delete(client.id) + clientToSocket.delete(client) + socketToClient.delete(socket) + logger.info( `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]} (${reason})`, ) @@ -136,17 +96,16 @@ wss.on('connection', async (socket, request) => { if (['debug', 'trace'].includes(config.logLevel)) { logger.debug('Debug logs enabled, attaching debug events...') + client.on('message', d => logger.debug(`Message from client ${client.id}:`, d)) client.on('packet', ({ client, ...rawPacket }) => logger.debug(`Packet received from client ${client.id}: ${inspectObject(rawPacket)}`), ) - - client.on('message', d => logger.debug(`Message from client ${client.id}:`, d)) } } catch (e) { if (e instanceof Error) logger.error(e.stack ?? e.message) else logger.error(inspectObject(e)) - const client = clientMap.get(socket) + const client = socketToClient.get(socket) if (!client) { logger.error( @@ -164,7 +123,6 @@ wss.on('connection', async (socket, request) => { // Start the server server.listen(config.port, config.address) - logger.debug(`Starting with these configurations: ${inspectObject(config)}`) const addressInfo = wss.address() diff --git a/packages/shared/src/constants/DisconnectReason.ts b/packages/shared/src/constants/DisconnectReason.ts index 048f49b..2276c21 100755 --- a/packages/shared/src/constants/DisconnectReason.ts +++ b/packages/shared/src/constants/DisconnectReason.ts @@ -26,10 +26,14 @@ enum DisconnectReason { * The receiving end didn't have an open socket */ NoOpenSocket = 4003, + /** + * The client connected from another location + */ + NewConnection = 4004, /** * The client was not ready in time (**CLIENT-ONLY**) */ - TooSlow = 4002, + TooSlow = 4012, } export default DisconnectReason diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts index e3d18b6..881d03d 100755 --- a/packages/shared/src/constants/HumanizedDisconnectReason.ts +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -12,6 +12,7 @@ const HumanizedDisconnectReason = { [DisconnectReason.TooSlow]: 'the client was not ready in time', [DisconnectReason.PlannedDisconnect]: 'the client has disconnected on its own', [DisconnectReason.NoOpenSocket]: 'the receiving end did not have an open socket', + [DisconnectReason.NewConnection]: 'the client connected from another location', } as const satisfies Record export default HumanizedDisconnectReason From f3d51b7c6bf6923681df9d119b75d8c1b7ea31a0 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jul 2024 00:27:23 +0700 Subject: [PATCH 143/312] chore(bots/discord): automatically build default db --- bots/discord/drizzle.config.ts | 2 +- bots/discord/package.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bots/discord/drizzle.config.ts b/bots/discord/drizzle.config.ts index 3c1920e..d9a9a69 100644 --- a/bots/discord/drizzle.config.ts +++ b/bots/discord/drizzle.config.ts @@ -4,6 +4,6 @@ export default defineConfig({ dialect: 'sqlite', schema: './src/database/schemas.ts', dbCredentials: { - url: `file:./${process.env['DATABASE_PATH']}`, + url: process.env['DATABASE_PATH'] ? `file:./${process.env['DATABASE_PATH']}` : 'file:./db.sqlite3', }, }) diff --git a/bots/discord/package.json b/bots/discord/package.json index 0fc0978..d81dd2a 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -7,12 +7,12 @@ "main": "src/index.ts", "scripts": { "register": "bun run scripts/reload-slash-commands.ts", - "start": "bun run scripts/generate-indexes.ts && bun run src/index.ts", - "dev": "bun run scripts/generate-indexes.ts && bun --watch src/index.ts", + "start": "bun prepare && bun run src/index.ts", + "dev": "bun prepare && bun --watch src/index.ts", "build:config": "bun build config.ts --outdir=dist", - "build": "bun prepare && bun build:config && bun build src/index.ts -e ./config.js --target=bun --outdir=dist/src && DATABASE_PATH=dist/db.sqlite3 drizzle-kit push", + "build": "bun prepare && bun build:config && bun build src/index.ts -e ./config.js --target=bun --outdir=dist/src && drizzle-kit push", "watch": "bun dev", - "prepare": "bun run scripts/generate-indexes.ts" + "prepare": "bun run scripts/generate-indexes.ts && drizzle-kit push" }, "repository": { "type": "git", From 92b985ff5d0178555c3da57e6b90e8e97637b3f4 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jul 2024 00:27:46 +0700 Subject: [PATCH 144/312] chore(bots/discord): add `*.sqlite3` to gitignore --- bots/discord/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/bots/discord/.gitignore b/bots/discord/.gitignore index 1d6382a..b9c3d9f 100644 --- a/bots/discord/.gitignore +++ b/bots/discord/.gitignore @@ -180,6 +180,7 @@ config.ts # DB *.db *.sqlite +*.sqlite3 # Auto-generated files src/commands/index.ts From 177d4135267354371c1dad0e7298e82eabc92e08 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jul 2024 00:29:12 +0700 Subject: [PATCH 145/312] ci: remove `codeql` workflow --- .github/workflows/codeql.yml | 84 ------------------------------------ 1 file changed, 84 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index b5be0f2..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,84 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ "main", "*" ] - pull_request: - branches: [ "main", "*" ] - schedule: - - cron: '30 0 * * 1' - -jobs: - analyze: - name: Analyze - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners - # Consider using larger runners for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} - permissions: - # required for all workflows - security-events: write - - # only required for workflows in private repositories - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - language: [ 'javascript-typescript' ] - # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] - # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - queries: security-extended,security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" From a4e357c1ef11b2e5358d7fcee10c7b1c51848709 Mon Sep 17 00:00:00 2001 From: Palm Date: Sun, 14 Jul 2024 08:35:42 +0000 Subject: [PATCH 146/312] ci(release): fix zero packages found --- .multi-releaserc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.multi-releaserc b/.multi-releaserc index 5a6769e..2fa8d60 100644 --- a/.multi-releaserc +++ b/.multi-releaserc @@ -1,4 +1,4 @@ { "ignorePrivate": false, - "ignorePackages": ["!packages/**"] + "ignorePackages": ["packages/**"] } \ No newline at end of file From a20f8345b7b252b20ca45864db71216039a97cdd Mon Sep 17 00:00:00 2001 From: Palm Date: Sun, 14 Jul 2024 09:57:09 +0000 Subject: [PATCH 147/312] ci(release): trigger container build and portainer webhook for specific projects and branches --- .github/workflows/release.yml | 16 ++-------- .releaserc | 5 +-- apis/websocket/.releaserc | 21 ------------- apis/websocket/.releaserc.js | 31 +++++++++++++++++++ .../scripts/trigger-portainer-webhook.ts | 4 +++ bots/discord/.releaserc | 21 ------------- bots/discord/.releaserc.js | 31 +++++++++++++++++++ .../scripts/trigger-portainer-webhook.ts | 4 +++ package.json | 3 +- ...lilab%2Fmulti-semantic-release@1.1.3.patch | 8 +++-- 10 files changed, 81 insertions(+), 63 deletions(-) delete mode 100644 apis/websocket/.releaserc create mode 100644 apis/websocket/.releaserc.js create mode 100644 apis/websocket/scripts/trigger-portainer-webhook.ts delete mode 100644 bots/discord/.releaserc create mode 100644 bots/discord/.releaserc.js create mode 100644 bots/discord/scripts/trigger-portainer-webhook.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b047a3..91fe320 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,22 +53,10 @@ jobs: DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} + WEBSOCKET_API_PORTAINER_WEBHOOK_URL: ${{ secrets.WEBSOCKET_API_PORTAINER_WEBHOOK_URL }} + DISCORD_BOT_PORTAINER_WEBHOOK_URL: ${{ secrets.DISCORD_BOT_PORTAINER_WEBHOOK_URL }} run: bunx multi-semantic-release - - name: Set Portainer stack webhook URL based on branch - run: | - if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then - PORTAINER_WEBHOOK_URL=${{ secrets.PORTAINER_WEBHOOK_MAIN_URL }} - else - PORTAINER_WEBHOOK_URL=${{ secrets.PORTAINER_WEBHOOK_DEV_URL }} - fi - echo "PORTAINER_WEBHOOK_URL=$PORTAINER_WEBHOOK_URL" >> $GITHUB_ENV - - - name: Trigger Portainer stack update - uses: newarifrh/portainer-service-webhook@v1 - with: - webhook_url: ${{ env.PORTAINER_WEBHOOK_URL }} - - name: Purge outdated images uses: snok/container-retention-policy@v3.0.0 with: diff --git a/.releaserc b/.releaserc index 42d6f0c..2d80a81 100644 --- a/.releaserc +++ b/.releaserc @@ -11,7 +11,7 @@ "@semantic-release/commit-analyzer", { "releaseRules": [ - { "type": "build", "scope": "Needs bump", "release": "patch"} + { "type": "build", "scope": "Needs bump", "release": "patch" } ] } ], @@ -48,7 +48,8 @@ } ], "clearWorkspace": true - } + }, + true ] ] } \ No newline at end of file diff --git a/apis/websocket/.releaserc b/apis/websocket/.releaserc deleted file mode 100644 index 913e5d4..0000000 --- a/apis/websocket/.releaserc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "plugins": [ - [ - "@codedependant/semantic-release-docker", - { - "dockerImage": "revanced-bot-websocket-api", - "dockerRegistry": "ghcr.io", - "dockerProject": "revanced", - "dockerContext": "../..", - "dockerPlatform": [ - "linux/amd64", - "linux/arm64" - ], - "dockerArgs": { - "GITHUB_ACTOR": null, - "GITHUB_TOKEN": null - } - } - ] - ] -} \ No newline at end of file diff --git a/apis/websocket/.releaserc.js b/apis/websocket/.releaserc.js new file mode 100644 index 0000000..811889d --- /dev/null +++ b/apis/websocket/.releaserc.js @@ -0,0 +1,31 @@ +import { $ } from 'execa' + +const branch = (await $`git rev-parse --abbrev-ref HEAD`).stdout.trim() + +export default { + plugins: + branch === 'main' + ? [ + [ + '@codedependant/semantic-release-docker', + { + dockerImage: 'revanced-bot-websocket-api', + dockerRegistry: 'ghcr.io', + dockerProject: 'revanced', + dockerContext: '../..', + dockerPlatform: ['linux/amd64', 'linux/arm64'], + dockerArgs: { + GITHUB_ACTOR: null, + GITHUB_TOKEN: null, + }, + }, + ], + [ + '@semantic-release/exec', + { + publishCmd: 'bun run scripts/trigger-portainer-webhook.ts', + }, + ], + ] + : [], +} diff --git a/apis/websocket/scripts/trigger-portainer-webhook.ts b/apis/websocket/scripts/trigger-portainer-webhook.ts new file mode 100644 index 0000000..a8d89bb --- /dev/null +++ b/apis/websocket/scripts/trigger-portainer-webhook.ts @@ -0,0 +1,4 @@ +import { $ } from 'bun' + +const URLEnvironmentVariableName = 'WEBSOCKET_API_PORTAINER_WEBHOOK_URL' +await $`INPUT_WEBHOOK_URL=${process.env[URLEnvironmentVariableName]} bun run ../../node_modules/portainer-service-webhook/dist` diff --git a/bots/discord/.releaserc b/bots/discord/.releaserc deleted file mode 100644 index 898cfd2..0000000 --- a/bots/discord/.releaserc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "plugins": [ - [ - "@codedependant/semantic-release-docker", - { - "dockerImage": "revanced-bot-discord", - "dockerRegistry": "ghcr.io", - "dockerProject": "revanced", - "dockerContext": "../..", - "dockerPlatform": [ - "linux/amd64", - "linux/arm64" - ], - "dockerArgs": { - "GITHUB_ACTOR": null, - "GITHUB_TOKEN": null - } - } - ] - ] -} \ No newline at end of file diff --git a/bots/discord/.releaserc.js b/bots/discord/.releaserc.js new file mode 100644 index 0000000..06f1fec --- /dev/null +++ b/bots/discord/.releaserc.js @@ -0,0 +1,31 @@ +import { $ } from 'execa' + +const branch = (await $`git rev-parse --abbrev-ref HEAD`).stdout.trim() + +export default { + plugins: + branch === 'main' + ? [ + [ + '@codedependant/semantic-release-docker', + { + dockerImage: 'revanced-bot-discord', + dockerRegistry: 'ghcr.io', + dockerProject: 'revanced', + dockerContext: '../..', + dockerPlatform: ['linux/amd64', 'linux/arm64'], + dockerArgs: { + GITHUB_ACTOR: null, + GITHUB_TOKEN: null, + }, + }, + ], + [ + '@semantic-release/exec', + { + publishCmd: 'bun run scripts/trigger-portainer-webhook.ts', + }, + ], + ] + : [], +} diff --git a/bots/discord/scripts/trigger-portainer-webhook.ts b/bots/discord/scripts/trigger-portainer-webhook.ts new file mode 100644 index 0000000..c2f3855 --- /dev/null +++ b/bots/discord/scripts/trigger-portainer-webhook.ts @@ -0,0 +1,4 @@ +import { $ } from 'bun' + +const URLEnvironmentVariableName = 'DISCORD_BOT_PORTAINER_WEBHOOK_URL' +await $`INPUT_WEBHOOK_URL=${process.env[URLEnvironmentVariableName]} bun run ../../node_modules/portainer-service-webhook/dist` diff --git a/package.json b/package.json index fff9d77..903a0f1 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,9 @@ "@codedependant/semantic-release-docker": "^5.0.3", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", - "@revanced/bot-api": "workspace:*", - "@revanced/bot-shared": "workspace:*", "@saithodev/semantic-release-backmerge": "^4.0.1", "@semantic-release/changelog": "^6.0.3", + "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^10.1.0", "@tsconfig/strictest": "^2.0.5", diff --git a/patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch b/patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch index 24584e7..dd96e2a 100644 --- a/patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch +++ b/patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch @@ -1,14 +1,16 @@ diff --git a/lib/multi-semantic-release.js b/lib/multi-semantic-release.js -index 1247e618244dc35f6d60d1e484fbd1a84e504b2a..4b6f1bb5aae5219db2069412f56094af0e33a664 100644 +index 1247e618244dc35f6d60d1e484fbd1a84e504b2a..fc8dbf1c418351f2c25698e3fc3fab478693f125 100644 --- a/lib/multi-semantic-release.js +++ b/lib/multi-semantic-release.js -@@ -114,6 +114,9 @@ async function releasePackage(package_, createInlinePlugin, multiContext, flags) +@@ -114,6 +114,11 @@ async function releasePackage(package_, createInlinePlugin, multiContext, flags) options.ci = flags.ci === undefined ? options.ci : flags.ci; options.branches = flags.branches ? castArray(flags.branches) : options.branches; + // Patching so plugins from globalOptions are also inherited. ++ // If the third element in the array is set to true, the plugin will not be inherited. + options.plugins = [...(multiContext.globalOptions.plugins || []), ...(options.plugins || [])] ++ .filter(plugin => Array.isArray(plugin) ? !plugin[2] : true) + // This options are needed for plugins that do not rely on `pluginOptions` and extract them independently. options._pkgOptions = packageOptions; - + From f1a169fb6b90893285aeb041cdd5dae103bfcec8 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 20 Jul 2024 01:12:34 +0700 Subject: [PATCH 148/312] ci(release): release and trigger portainer webhooks in different steps --- .github/workflows/release.yml | 11 +++++++ .releaserc | 55 ----------------------------------- .releaserc.js | 52 +++++++++++++++++++++++++++++++++ apis/websocket/.releaserc.js | 35 ++++++++++------------ bots/discord/.releaserc.js | 23 +++++++-------- 5 files changed, 88 insertions(+), 88 deletions(-) delete mode 100644 .releaserc create mode 100644 .releaserc.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91fe320..8373482 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,17 @@ jobs: - name: Build and release env: + RELEASE_WORKFLOW_STEP: release + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} + run: bunx multi-semantic-release + + # We call multi-semantic-release twice to publish in a different step + # An environment variable determines which plugins in the config to run + - name: Trigger Portainer webhooks + if: github.ref == 'refs/heads/main' + env: + RELEASE_WORKFLOW_STEP: publish DOCKER_REGISTRY_USER: ${{ github.actor }} DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} GITHUB_ACTOR: ${{ github.actor }} diff --git a/.releaserc b/.releaserc deleted file mode 100644 index 2d80a81..0000000 --- a/.releaserc +++ /dev/null @@ -1,55 +0,0 @@ -{ - "branches": [ - "main", - { - "name": "dev", - "prerelease": true - } - ], - "plugins": [ - [ - "@semantic-release/commit-analyzer", - { - "releaseRules": [ - { "type": "build", "scope": "Needs bump", "release": "patch" } - ] - } - ], - "@semantic-release/release-notes-generator", - "@semantic-release/changelog", - [ - "@semantic-release/git", - { - "assets": [ - "README.md", - "CHANGELOG.md", - "package.json" - ] - } - ], - [ - "@semantic-release/github", - { - "assets": [ - { - "path": "dist/*" - } - ], - "successComment": false - } - ], - [ - "@saithodev/semantic-release-backmerge", - { - "backmergeBranches": [ - { - "from": "main", - "to": "dev" - } - ], - "clearWorkspace": true - }, - true - ] - ] -} \ No newline at end of file diff --git a/.releaserc.js b/.releaserc.js new file mode 100644 index 0000000..175fde7 --- /dev/null +++ b/.releaserc.js @@ -0,0 +1,52 @@ +export default { + branches: [ + 'main', + { + name: 'dev', + prerelease: true, + }, + ], + plugins: + process.env.RELEASE_WORKFLOW_STEP === 'release' + ? [ + [ + '@semantic-release/commit-analyzer', + { + releaseRules: [{ type: 'build', scope: 'Needs bump', release: 'patch' }], + }, + ], + '@semantic-release/release-notes-generator', + '@semantic-release/changelog', + [ + '@semantic-release/git', + { + assets: ['README.md', 'CHANGELOG.md', 'package.json'], + }, + ], + [ + '@semantic-release/github', + { + assets: [ + { + path: 'dist/*', + }, + ], + successComment: false, + }, + ], + [ + '@saithodev/semantic-release-backmerge', + { + backmergeBranches: [ + { + from: 'main', + to: 'dev', + }, + ], + clearWorkspace: true, + }, + true, + ], + ] + : [], +} diff --git a/apis/websocket/.releaserc.js b/apis/websocket/.releaserc.js index 811889d..eeed372 100644 --- a/apis/websocket/.releaserc.js +++ b/apis/websocket/.releaserc.js @@ -1,25 +1,7 @@ -import { $ } from 'execa' - -const branch = (await $`git rev-parse --abbrev-ref HEAD`).stdout.trim() - export default { plugins: - branch === 'main' + process.env.RELEASE_WORKFLOW_STEP === 'publish' ? [ - [ - '@codedependant/semantic-release-docker', - { - dockerImage: 'revanced-bot-websocket-api', - dockerRegistry: 'ghcr.io', - dockerProject: 'revanced', - dockerContext: '../..', - dockerPlatform: ['linux/amd64', 'linux/arm64'], - dockerArgs: { - GITHUB_ACTOR: null, - GITHUB_TOKEN: null, - }, - }, - ], [ '@semantic-release/exec', { @@ -27,5 +9,18 @@ export default { }, ], ] - : [], + : [ + '@codedependant/semantic-release-docker', + { + dockerImage: 'revanced-bot-websocket-api', + dockerRegistry: 'ghcr.io', + dockerProject: 'revanced', + dockerContext: '../..', + dockerPlatform: ['linux/amd64', 'linux/arm64'], + dockerArgs: { + GITHUB_ACTOR: null, + GITHUB_TOKEN: null, + }, + }, + ], } diff --git a/bots/discord/.releaserc.js b/bots/discord/.releaserc.js index 06f1fec..737d57c 100644 --- a/bots/discord/.releaserc.js +++ b/bots/discord/.releaserc.js @@ -1,11 +1,15 @@ -import { $ } from 'execa' - -const branch = (await $`git rev-parse --abbrev-ref HEAD`).stdout.trim() - export default { plugins: - branch === 'main' + process.env.RELEASE_WORKFLOW_STEP === 'publish' ? [ + [ + '@semantic-release/exec', + { + publishCmd: 'bun run scripts/trigger-portainer-webhook.ts', + }, + ], + ] + : [ [ '@codedependant/semantic-release-docker', { @@ -20,12 +24,5 @@ export default { }, }, ], - [ - '@semantic-release/exec', - { - publishCmd: 'bun run scripts/trigger-portainer-webhook.ts', - }, - ], - ] - : [], + ], } From 2f8658617923c07f6847cbf1fdfc5f5379d95b6c Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 20 Jul 2024 01:13:27 +0700 Subject: [PATCH 149/312] feat(bots/discord): add `api.disconnectRetryInterval` config --- bots/discord/config.schema.ts | 1 + bots/discord/config.ts | 1 + bots/discord/src/events/api/disconnect.ts | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 2563266..e631e1d 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -41,6 +41,7 @@ export type Config = { api: { url: string disconnectLimit?: number + disconnectRetryInterval?: number } } diff --git a/bots/discord/config.ts b/bots/discord/config.ts index 40727a4..c387561 100644 --- a/bots/discord/config.ts +++ b/bots/discord/config.ts @@ -68,5 +68,6 @@ export default { api: { url: 'ws://127.0.0.1:3000', disconnectLimit: 3, + disconnectRetryInterval: 10000, }, } satisfies Config as Config diff --git a/bots/discord/src/events/api/disconnect.ts b/bots/discord/src/events/api/disconnect.ts index 6b6c230..7af62eb 100644 --- a/bots/discord/src/events/api/disconnect.ts +++ b/bots/discord/src/events/api/disconnect.ts @@ -25,5 +25,5 @@ withContext(on, 'disconnect', ({ api, config, logger }, reason, msg) => { `Disconnected from bot API ${++api.disconnectCount} times (this time because: ${reason}, ${msg}), reconnecting again...`, ) - setTimeout(() => api.client.connect(), 10000) + setTimeout(() => api.client.connect(), config.api.disconnectRetryInterval) }) From e7ddbe8abd032132bcca8be5e77dcae044ad78b6 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 14 Jul 2024 00:28:52 +0700 Subject: [PATCH 150/312] chore: update dependencies --- bun.lockb | Bin 285680 -> 286528 bytes package.json | 21 +++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/bun.lockb b/bun.lockb index 5fa9dbab53c54d0c0129a42c206bec4034d4f547..e8f36332c15f7a5cefcd58dd9955c8bc307b3fa3 100755 GIT binary patch delta 52391 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 delta 51415 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 diff --git a/package.json b/package.json index 903a0f1..3180c96 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "packageManager": "bun@1.1.18", "devDependencies": { "@anolilab/multi-semantic-release": "^1.1.3", - "@biomejs/biome": "^1.8.2", + "@biomejs/biome": "^1.8.3", "@codedependant/semantic-release-docker": "^5.0.3", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", @@ -38,17 +38,22 @@ "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", - "@semantic-release/github": "^10.1.0", + "@semantic-release/github": "^10.1.1", "@tsconfig/strictest": "^2.0.5", - "@types/bun": "^1.1.5", - "concurrently": "^8.2.2", + "@types/bun": "^1.1.6", "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.6.18", + "lefthook": "^1.7.4", + "portainer-service-webhook": "https://github.com/newarifrh/portainer-service-webhook#v1", "semantic-release": "^24.0.0", - "turbo": "2", - "typescript": "^5.5.2" + "turbo": "^2.0.9", + "typescript": "^5.5.3" }, - "trustedDependencies": ["@biomejs/biome", "esbuild", "lefthook"], + "trustedDependencies": [ + "@biomejs/biome", + "@revanced/discord-bot", + "esbuild", + "lefthook" + ], "patchedDependencies": { "@anolilab/multi-semantic-release@1.1.3": "patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch" } From 57891428a26c171178eb9c62fa890bedce3b21b2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 20 Jul 2024 23:36:58 +0700 Subject: [PATCH 151/312] ci(release): use correct credentials --- .github/workflows/release.yml | 10 ++++------ apis/websocket/.releaserc.js | 4 ---- bots/discord/.releaserc.js | 4 ---- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8373482..9060cc8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,8 +50,9 @@ jobs: - name: Build and release env: RELEASE_WORKFLOW_STEP: release - GITHUB_ACTOR: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} + DOCKER_REGISTRY_USER: ${{ github.repository_owner }} + DOCKER_REGISTRY_PASSWORD: ${{ secrets.REPOSITORY_PUSH_ACCESS }} run: bunx multi-semantic-release # We call multi-semantic-release twice to publish in a different step @@ -60,9 +61,6 @@ jobs: if: github.ref == 'refs/heads/main' env: RELEASE_WORKFLOW_STEP: publish - DOCKER_REGISTRY_USER: ${{ github.actor }} - DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - GITHUB_ACTOR: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} WEBSOCKET_API_PORTAINER_WEBHOOK_URL: ${{ secrets.WEBSOCKET_API_PORTAINER_WEBHOOK_URL }} DISCORD_BOT_PORTAINER_WEBHOOK_URL: ${{ secrets.DISCORD_BOT_PORTAINER_WEBHOOK_URL }} @@ -71,8 +69,8 @@ jobs: - name: Purge outdated images uses: snok/container-retention-policy@v3.0.0 with: - account: ${{ github.actor }} - token: ${{ secrets.REPOSITORY_PUSH_ACCESS }} + account: ${{ github.repository_owner }} + token: ${{ secrets.DELETE_PACKAGES_TOKEN }} image-names: "revanced-bot-*" keep-n-most-recent: 5 cut-off: 3M diff --git a/apis/websocket/.releaserc.js b/apis/websocket/.releaserc.js index eeed372..d9ea2ce 100644 --- a/apis/websocket/.releaserc.js +++ b/apis/websocket/.releaserc.js @@ -17,10 +17,6 @@ export default { dockerProject: 'revanced', dockerContext: '../..', dockerPlatform: ['linux/amd64', 'linux/arm64'], - dockerArgs: { - GITHUB_ACTOR: null, - GITHUB_TOKEN: null, - }, }, ], } diff --git a/bots/discord/.releaserc.js b/bots/discord/.releaserc.js index 737d57c..2fc4eb1 100644 --- a/bots/discord/.releaserc.js +++ b/bots/discord/.releaserc.js @@ -18,10 +18,6 @@ export default { dockerProject: 'revanced', dockerContext: '../..', dockerPlatform: ['linux/amd64', 'linux/arm64'], - dockerArgs: { - GITHUB_ACTOR: null, - GITHUB_TOKEN: null, - }, }, ], ], From 73b2169a5a5602cae0250c516ecb4d714acbfa58 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 21 Jul 2024 01:08:32 +0700 Subject: [PATCH 152/312] ci(release): add docker tags, fix unnested plugins array, verbose logs for docker builds --- .github/workflows/release.yml | 3 ++- apis/websocket/.releaserc.js | 24 ++++++++++++++++-------- bots/discord/.releaserc.js | 6 ++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9060cc8..e9f61d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} DOCKER_REGISTRY_USER: ${{ github.repository_owner }} DOCKER_REGISTRY_PASSWORD: ${{ secrets.REPOSITORY_PUSH_ACCESS }} - run: bunx multi-semantic-release + DEBUG: semantic-release:* + run: bunx multi-semantic-release --debug # We call multi-semantic-release twice to publish in a different step # An environment variable determines which plugins in the config to run diff --git a/apis/websocket/.releaserc.js b/apis/websocket/.releaserc.js index d9ea2ce..2f2d58d 100644 --- a/apis/websocket/.releaserc.js +++ b/apis/websocket/.releaserc.js @@ -10,13 +10,21 @@ export default { ], ] : [ - '@codedependant/semantic-release-docker', - { - dockerImage: 'revanced-bot-websocket-api', - dockerRegistry: 'ghcr.io', - dockerProject: 'revanced', - dockerContext: '../..', - dockerPlatform: ['linux/amd64', 'linux/arm64'], - }, + [ + '@codedependant/semantic-release-docker', + { + dockerImage: 'revanced-bot-websocket-api', + dockerRegistry: 'ghcr.io', + dockerProject: 'revanced', + dockerContext: '../..', + dockerPlatform: ['linux/amd64', 'linux/arm64'], + dockerBuildQuiet: false, + dockerTags: [ + '{{#if prerelease.[0]}}dev{{else}}main{{/if}}', + '{{#unless prerelease.[0]}}latest{{/unless}}', + '{{version}}', + ], + }, + ], ], } diff --git a/bots/discord/.releaserc.js b/bots/discord/.releaserc.js index 2fc4eb1..a1ed2f8 100644 --- a/bots/discord/.releaserc.js +++ b/bots/discord/.releaserc.js @@ -18,6 +18,12 @@ export default { dockerProject: 'revanced', dockerContext: '../..', dockerPlatform: ['linux/amd64', 'linux/arm64'], + dockerBuildQuiet: false, + dockerTags: [ + '{{#if prerelease.[0]}}dev{{else}}main{{/if}}', + '{{#unless prerelease.[0]}}latest{{/unless}}', + '{{version}}', + ], }, ], ], From 67ba3bdb903c11f5154d4e688b2a82d99f45a967 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 21 Jul 2024 01:33:52 +0700 Subject: [PATCH 153/312] docs(bots/discord): document extra deployment step --- bots/discord/docs/3_running_and_deploying.md | 9 +++++++-- bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bots/discord/docs/3_running_and_deploying.md b/bots/discord/docs/3_running_and_deploying.md index e191359..44f87b8 100644 --- a/bots/discord/docs/3_running_and_deploying.md +++ b/bots/discord/docs/3_running_and_deploying.md @@ -23,7 +23,6 @@ bun run build The distribution files will be placed inside the `dist` directory. Inside will include: - The default configuration for the bot -- An empty database for the bot with schemas configured - Compiled source files of the bot ## ✈️ Deploying @@ -49,7 +48,13 @@ To deploy the bot, you'll need to: cp -R ./dist/* /usr/src/discord-bot ``` -5. Replace the default empty database with your own _(optional)_ +5. Copy the empty database (or use your own existing database) + + ```sh + # By default, the build script creates the database called "db.sqlite3" + # Unless you specify otherwise via the "DATABASE_PATH" environment variable + cp ./db.sqlite3 /usr/src/discord-bot + ``` 6. Configure environment variables As seen in [`.env.example`](../.env.example). You can also optionally use a `.env` file which **Bun will automatically load**. diff --git a/bots/discord/package.json b/bots/discord/package.json index d81dd2a..e20d6fb 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -10,7 +10,7 @@ "start": "bun prepare && bun run src/index.ts", "dev": "bun prepare && bun --watch src/index.ts", "build:config": "bun build config.ts --outdir=dist", - "build": "bun prepare && bun build:config && bun build src/index.ts -e ./config.js --target=bun --outdir=dist/src && drizzle-kit push", + "build": "bun prepare && bun build:config && bun build src/index.ts -e ./config.js --target=bun --outdir=dist/src", "watch": "bun dev", "prepare": "bun run scripts/generate-indexes.ts && drizzle-kit push" }, From af9cfa1384ece664e27977c28ee299e310a7431a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 21 Jul 2024 15:16:37 +0700 Subject: [PATCH 154/312] ci(release): explicitly build with bun --- apis/websocket/Dockerfile | 2 +- bots/discord/Dockerfile | 2 +- bots/discord/package.json | 2 +- bun.lockb | Bin 286528 -> 287664 bytes 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apis/websocket/Dockerfile b/apis/websocket/Dockerfile index 21f5cd4..68a3828 100644 --- a/apis/websocket/Dockerfile +++ b/apis/websocket/Dockerfile @@ -5,7 +5,7 @@ FROM base AS build WORKDIR /build COPY . . -RUN cd apis/websocket && bun run build +RUN cd apis/websocket && bun --bun run build FROM base AS release diff --git a/bots/discord/Dockerfile b/bots/discord/Dockerfile index 9ae2603..e3afbcd 100644 --- a/bots/discord/Dockerfile +++ b/bots/discord/Dockerfile @@ -5,7 +5,7 @@ FROM base AS build WORKDIR /build COPY . . -RUN cd bots/discord && bun run build +RUN cd bots/discord && bun --bun run build FROM base AS release diff --git a/bots/discord/package.json b/bots/discord/package.json index e20d6fb..076143e 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -32,7 +32,7 @@ "dependencies": { "@discordjs/builders": "^1.8.2", "@discordjs/rest": "^2.3.0", - "@libsql/client": "^0.6.2", + "@libsql/client": "^0.7.0", "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", diff --git a/bun.lockb b/bun.lockb index e8f36332c15f7a5cefcd58dd9955c8bc307b3fa3..c4dbd7a6fa341276584d1fe83143f3c745efeabd 100755 GIT binary patch delta 4090 zcmX@GPjJI=!3lbrAv-3$zk754l9v54SY8k7Qu$14F}21_ogUhKBr`5Opz7zSB(x1}O%HhTx413|tHh4eqxgbnk5j z1|bH9hLR!^GmslHi*-wjau~Q7%x^LKCV92giHSSK%Ru;94Kz+eJa@zBAX^Y~2$20sRd2FA&WR_2_dw-^|_zzP#h%$eeE zO^&*6$GQF%149f0Lj%)fMMra1?%NCuHejm)K`gN1g-+(2^KUaS_%JXuFi&1+XwLci zHUonJ*bOW@2DqaAIX(0MRZ`eOPEdP)snuB{m2C5FTu9 z+J2LdvE-A()kTN5=DZQw_3J}g)nk{dCmub~|G$(yjqBqveU(6dGb}s`g^rc+!ygK{?{5EB;)sSdM_iBcsrzr~hDL z;+_5%ByfDXClgcn^cBoZywle*F?lndocux%m} z0|UqfAlu@hQYld7Alp))VyRH8co`TN99bY9N}rwxG9nkMoS%V#;SV#!19?mg3?K&! zFfcI0GDEB=hKdP-Tm%)Xn7$EYL@QJZU&gc)2HF|3E$sKLO%P!1K_2sJ{Jfq|h4Dz<6*M352Nq3J@Kfq|h0 zDzyu0tPTSM!!HH~22jlGhZ>>Fz`!61jgbS>AA*cH2$j-jU|`^bN*#iV889#~$S^W6 zfMN?&Xn-=2Ap--05~#3eU|={p-S8}vuld$b=s4MIl7#L)srayoxcVJ*( zU}IrmP-0+UcnB49ghnbT5j>h+2r}X^RLU9ZM^NNGfr_~>FfgoSW?%qC4N8( zgl#`UrMwv!7z&~O{sa~Cfw~M7xu2n8z6=ZuHqco5GCdJw#5bsvKLZ1U2UO}iR16fn zK2WhA&{zs&U|>juiv60t5oE+~sPbS?AqSQE0~HHlU|=YKiv5L(g)%TOltRV+O=rBo zBy9U1DisdO4^SykNeE&_KtmtYC}U)X6n2pe3=Gp47#Kk5h-rEt$OsmwR5YkefJ(7K z3&0o#28PWn3=E*;#LmpXAO7%Eo60B+MV#4#{1m_Wr! z85kJ2SQr>UL2Wv{5oClJv}P)2U|?9q$iM*dza><81p@;^B`Eel85}AG;-7`O!g~5e zkP$XescHrWh6#)e3}p-q47O0Q8U_Z2Nl-CRO$X9h%fP@e1uAAgUGXxLu$2Q;c|8Lp zqUu1^E>x@m>JgBf6I86@8YC>IJf5C+nQ0oU5i0|O$#%{wOy!KKptiC(D+7ZCD+7Zi zD+7ZSD+7ZyD+7ZKD+7ZqD+7ZaD+7c5^rcss^o2B885p!!85p!#85nd}K{ej?M^~Bt zF)})Ae|Vj#o{1IIesTOf7(gv9P=ODs2tXCY2_^=H<4g<;XPFonK&`IxObiU5QlH@` zBurj3LMk6Fo9OMAzcR%YGn?xfY&XedekL@1LJqU1tht^60|TtD2kT-nF~%9`nd=!$ zzmvl(GhHW_S!#M+HM8IJj2dPW#)s4A)-d}r>E4|FriNL9$^7PYwpwN%8CXAW&dQ>S zm)*lv!C+Z8!&|0Ko47-y#}b zB(Y4-zs#gMEsjNkYX?;DKoUrknvlKZ>K`qEe`GnJU3Z1#=}~bkc8q_g&x~VP4gg$I B<Q6IfyX-agc*&n*o?Ep|+IT}83j+vT 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 K)pX8emgN8~zB^d} From 058a2a0dc453af7f81c90ba2d953a60cffd6a849 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 21 Jul 2024 16:16:15 +0700 Subject: [PATCH 155/312] chore: update dependencies --- bots/discord/package.json | 10 +++++----- bun.lockb | Bin 287664 -> 286936 bytes package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bots/discord/package.json b/bots/discord/package.json index 076143e..d3ee658 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -32,16 +32,16 @@ "dependencies": { "@discordjs/builders": "^1.8.2", "@discordjs/rest": "^2.3.0", - "@libsql/client": "^0.7.0", "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", - "decancer": "^3.2.2", + "decancer": "^3.2.3", "discord.js": "^14.15.3", - "drizzle-kit": "^0.22.7", - "drizzle-orm": "^0.31.2" + "drizzle-orm": "^0.31.4" }, "devDependencies": { - "discord-api-types": "^0.37.91" + "@libsql/client": "^0.7.0", + "discord-api-types": "^0.37.92", + "drizzle-kit": "^0.22.8" } } diff --git a/bun.lockb b/bun.lockb index c4dbd7a6fa341276584d1fe83143f3c745efeabd..836f0273bcc67ceafe4f31396bf855ec835c9f13 100755 GIT binary patch delta 10239 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 QKTl#&WSl;oGnr*M00$2Ip8x;= delta 10742 zcmca{P;kR>!3lbrAv-3$zk754l9v54W~9RFo-ZPGz4#CVBlh4XxO@ufkBXgpiK64`jgAhpF1_lOB28IT^jSLK;3=9qB#UL9P7}Pd0Fi0>kH0;~Jz`(=6(2$dv zR9u+Dz)-RkA|AVyfkB*sp}xU=D+7ZV14Dx$l$O~FF^FR;#KLb|AQrvc!oa}Gz|e4S z3j+f;149GzdI+7m18h*kogEO1G^>3;{tg$RIaBWL2Yyj5+WzpLFfyoAwjQvih+TjfuW(KST{GbBDJWbI43iS zfq~)9I*7RLSq27q28ITSvk-G&=5d{cB=XX{l+5Hz1_rTHkhD}%oB=T)B{exQFFCbH z_96p=G6O@y*9!~`8Vn2#=PocXs53A$thm6ypvJ(^PK>kF zU{GaXXxMw6fkB0Vpy1DPgtx;%g9(C+Fwn7Zo!wBo*i9fwGG<1H}Kq*CFmI zs>lLoBUt9-y8#i0WhPj7#WFB3NHZ`rJiGx(`&BuaNg$mKHzDDce-mPE41_P$;B=FL zK?*6CyWfU5sCV*1F>x;Qn~*G`dvi0Bc%0DW0-XQ>b{5at9ep;ZH@}@JF=xfslanv# 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 (https://palmdevs.me)", "ReVanced (https://revanced.app)" ], - "packageManager": "bun@1.1.18", + "packageManager": "bun@1.1.20", "devDependencies": { "@anolilab/multi-semantic-release": "^1.1.3", "@biomejs/biome": "^1.8.3", From 3ad0f7b19d5f1bb549cecf00c095c19059048fec Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 21 Jul 2024 16:26:58 +0700 Subject: [PATCH 156/312] ci(release): reinstall packages before building --- apis/websocket/Dockerfile | 1 + bots/discord/Dockerfile | 1 + bun.lockb | Bin 286936 -> 286936 bytes 3 files changed, 2 insertions(+) diff --git a/apis/websocket/Dockerfile b/apis/websocket/Dockerfile index 68a3828..af98045 100644 --- a/apis/websocket/Dockerfile +++ b/apis/websocket/Dockerfile @@ -5,6 +5,7 @@ FROM base AS build WORKDIR /build COPY . . +RUN bun install --frozen-lockfile RUN cd apis/websocket && bun --bun run build FROM base AS release diff --git a/bots/discord/Dockerfile b/bots/discord/Dockerfile index e3afbcd..96ca47b 100644 --- a/bots/discord/Dockerfile +++ b/bots/discord/Dockerfile @@ -5,6 +5,7 @@ FROM base AS build WORKDIR /build COPY . . +RUN bun install --frozen-lockfile RUN cd bots/discord && bun --bun run build FROM base AS release diff --git a/bun.lockb b/bun.lockb index 836f0273bcc67ceafe4f31396bf855ec835c9f13..aa9c4468eaab812ed77c96351e187e9eab17e1d2 100755 GIT binary patch delta 29 lcmca{Q1He;|%o7^$fPZ%Vd7_4FI=$3^4!z delta 29 hcmca{Q1He Date: Mon, 22 Jul 2024 22:40:56 +0700 Subject: [PATCH 157/312] ci(release): don't patch MSR anymore, add npm semantic release plugin --- apis/websocket/.releaserc.js | 6 ++-- bots/discord/.releaserc.js | 6 ++-- bun.lockb | Bin 286936 -> 285856 bytes package.json | 6 +--- ...lilab%2Fmulti-semantic-release@1.1.3.patch | 16 --------- .releaserc.js => semantic-release-config.js | 31 +++++++++++++++--- 6 files changed, 36 insertions(+), 29 deletions(-) delete mode 100644 patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch rename .releaserc.js => semantic-release-config.js (63%) diff --git a/apis/websocket/.releaserc.js b/apis/websocket/.releaserc.js index 2f2d58d..954c2cf 100644 --- a/apis/websocket/.releaserc.js +++ b/apis/websocket/.releaserc.js @@ -1,4 +1,6 @@ -export default { +import defineSubprojectReleaseConfig from '../../semantic-release-config.js' + +export default defineSubprojectReleaseConfig({ plugins: process.env.RELEASE_WORKFLOW_STEP === 'publish' ? [ @@ -27,4 +29,4 @@ export default { }, ], ], -} +}) diff --git a/bots/discord/.releaserc.js b/bots/discord/.releaserc.js index a1ed2f8..693e211 100644 --- a/bots/discord/.releaserc.js +++ b/bots/discord/.releaserc.js @@ -1,4 +1,6 @@ -export default { +import defineSubprojectReleaseConfig from '../../semantic-release-config.js' + +export default defineSubprojectReleaseConfig({ plugins: process.env.RELEASE_WORKFLOW_STEP === 'publish' ? [ @@ -27,4 +29,4 @@ export default { }, ], ], -} +}) diff --git a/bun.lockb b/bun.lockb index aa9c4468eaab812ed77c96351e187e9eab17e1d2..27e8610af7b2f6cc078b5cd1c4f41bd43d134fdc 100755 GIT binary patch delta 29148 zcmca{P;kLc!3lbr*O&Gct~8o2*z|u{wsU^@gMJ~Qn<|X1->2@XjSE=*Axt2Kg#iRE zP7Ie1<~_*-VnZ=M14Dy869WSm14BbeX;D%>0|NsOGXsM#14F|fCI$up28M>$ObiTy z3=9o-nHU)O7#JE(Gchm-F)%djVPaqa>6hPl@;f(I3M&JHI0HjNHS6Y6JaLSi&a4a! ziVO@5Y?BT7>NyuPGB9W{Ff`1ZypvC#wT+R1L3r|4zG%jj$$|XxjPjEU`OTdS`yuw0 zSu!w4GcYvloxs4r!@$tMIT2#d<_S=DW)$U@re`pG?`2@%W?*Q5*;LIs`4zt%qyA(~ zfqos&xN?<&|C%vSq6rNtjv;3P!KMk3vp}de2B5BP&zXYEX`0nIa5%+{?!7A&#ogV3=Con3``B#lR1U->m^q(Fz_-kH0)%6L}c(vh-WNTLOh|c65<@Lm0)cR zA6GCiNHQ=q+*$#N+CwWK@@rN=n1PPz+4Uljw zDb~%+tVk^?DbC4EVqjocw+{hWnEX*#zMf$pByfr|GN}ISJ632S`A>u{{z=7B>@c<;uQj3#HGjmcH_8o%AuQ&vWlu1y!21=(v>A*vv zFkq-}uss9`JWVJq3Z)sL^xK0F`I}JsD3sm^rRN=lL_#-|u7J|1P&)V^#G#H*S|3Ww zg6VpOhJE`WJ|oiylzE_@fuVsiA22j5ISKJ0ESVOrgQTX{7a$>5auJf8uU&*B<1ktiOCzC%r|QoaAY&GZ8n_!o0Zj( zfuVtG@~1+g8#8a`sv$Yt9`#3=B>T;L6a^oby)?1A`Bo=hn->kPbHAu-{^` z=2Cl3ralISOa_Jqp2>wK=A7Am3=Am@3=Qlc?{S{*V_-;v@C?m4UHTaq;=p=0I+=4W z>Stgu0rNiEnR8z2XJ9adm||eg$vy$%L)OWK6&4&5Alg_#Msl2-0P!H)^PCbB85rEa z-ZC^X=Zu@kz+l6`(7-sk(94{4$wZJRb$zTkZ%t%iuxDUs;0Bq(DLILOArj0hG%;r^ zn*4HwJ?9Rn80Ta|GjmSC$qWpZ;2{4v)q-U*14F>%wT9N5vnDezc!4eZXkgCyWHJMT zH&{`ji8-s)6b1%^$+{-itXWeS7)&OwHMM4)H-&-0Zt`0PYtB1UAP(S|405LQR0al6 zgmX?VG&1LmnF^6(2Z!P8sSFIR;E(}@(}SrH5AjUi=wi+(GYt|OB4BT1Ok-dOW?*RG znEcVsoOSm!1_n>CD_N#P0*MW*%4IslF7C;Oj^>=L(;>mdHo4H)obxnXCCJm`sSqu!;V3i=-*Un;Kum$rr=2&pdhB%A`?C6l$kR;DBxzGeG zh?Idjr}F{^hETA#3?0oGXH3@IYR~z10VJ4N zCT|QeXEj{Nz+eGRKA8(4S%PWuMpJXn?F$(g0>G&p#Bs5`gh@V-^z~Btm$G-%UXdscea0w*YvrPWzV9xn^3Cx3_ zq-?wt#w#>1=d4ENu^tBTCg&Pkb8;_3w%KbLq!ffCn90jvc7YPbm1VF514^!z%OUB2 z1st^5%OMFA5^HOhL$U@dD7IN&E(c{lPywd40;X!Cy*X#$3P_S;oV>BroU>phB@%5)}3oW1+1>4HRoRt3!Dlz8k%!Du7$Xk9aJoHHm`;FmlKpd8MjW>+;7kHZ|&r) z{dG*2)=hr3-;U8?vgQGM&g&Z(7(iKq0aTnY%1vH*z@9T}BSegG@<&~B&W#%x7+k=G z|3_VOrmq_(XC1U-^qIW!pgrfLP4FPuV8O8omPwZzCcivh&$xAR z<_UYwH+vu%lo1>xT6-ad3InJZVN95;dD5PB&R$UIuWMq>bYbu0RVV8>dG|qLm}zoh zv<1gL1_lFg>9sMF6NAZlQmDGIGiPL3?>^1XDx!WULOOcjko9jGrEG! z_c_790AWppv#!BdoYE&D9%crWx{NuKGcVb*ZaWF~%_VDA)>B~jUb1Elg0tqsS#RJh z%hO=rT(V|uISqE(C2Q8RaF*B^m{>fVwF1ui2xnQHg{fu2+rCLXZ?Y(7#$~P-mvHFy#Ohz7$zIKn{)oZ0P!mWxK63N2$2IvF4Oaile2Ex zu^L@sV6d6|*3O!9@+C-N&pf%X!<_T+B}h>WNf)}8A?*Qhdylbl^2%HGoSQF0R4`25 zIMtl>?`2TiMc2%l)%Oahm8k1(&DnnilI6jUVLE*T_v7L!-rx94QK#lR2)E_4hX%~|7a zfkFl3hb15uyj}6>79<>)Cl{t$aNLGuF$Pcrgf;IrC_Ao=v}Rg#d$LxF9p{7FklFuR=1YS~aNh&W!pE0o0Ea9CI|I171FN?OlMp^L z1Gp#vi#UNvFrSHmadPh?@yT-@ae#0pNSPZ00|SWmWME)mWB>;XnD&Nh1JNLPKL!Q{ z&?s2|0|NtS=qv;(k4%H)!=U0I8f0ELh(B5OvA7~gHVVXJ0GIDzTcV-1#(=~p$37O< z1lg4U6-@+*GJp$Auxija2FT20koe@ekHzajj1;I3(m@;saM8=a%D}*o1LcEgkg1?? zJdhBG2DzmW>XTBCAOiyfHX5X_0_yWBC|v_pU&Fu<1ZIK+KnB)9oeg#s0|NswG)P|~ z*c1i^5Y0Hb_X)&XpaCIJ{B}cSK{QBpFVvPks5poQMaU$m_++R!J{n}sRH!--4N8Zz z85qFnX%Wofi=g4U6l(A?s0L&j6h14V@~fcqYN$GFG|1w$P;>4iyK{APYcac_1MW4GO^nAPxfq1BeEha}dhMNrO@pNDW8<$iTx; zpB;hHM?sPd3=G(4kox1$lyVZP{uER_3OzaZnYbbk0|Uc#s4O;`aq`w@@|GY4H$emg z0|PP*vgdqxz`y{aLAoA6`5>B;fq~&Qlurx|3c@!~ z^&lFQ*FHk?(pRWB5?v3@y5FD@AR6R=A5cDs1{wGh%15R_>V8ATK{QC+A1EJ0bAwVW zBP6YGf+QIj7(g^gA2*cG%~%g9*La`;$TZ01f{YB{(n1U>PYMmvA`W$cBqPKgDUcuo z0|T5+VFZtaF=#MC3QirULR~1W2i1U$21)5NLJDXjs6{3qK?Vi}WSVjE-xuQbpi;mR zDvM0JFfcIGLdCJsAbpKcc@PZ>zh)>OM1zWrP-Yy-)=p z8e~vEln3=AL|#JtD|9(ZK93*s;^fN2o(9#s8(Xo`IT4aw(F`|4kUR536x zfM}3UUPJlFG)U+TRKq(c{Q)G%z`%e^g9@fUPz(P;>3>lAKU6)41~Zw!NxzRA4>5rifYCr>2 z12!6z-&>&a#L%Eb-2+vROoNKgiA<10Ka+_8Qqa$WI$#czo(H8DKn(!VAm$s1aoa z6-TB)23bSJk!g^)4OASN29-+A%#hN>gBemx`Z0s&q#3g7nZZ*>3}sN4l|vQOLJh)4 zgQBDns;>#E9z=tJz6Ht$(I93!GbB^@GlQqP7#2W7s(ukOB!#R5sbXMYK&C-LYoHda z1#uV{7?5d@12#ep+6+xQ+o0+|G$=&2L-`;Y#N5RUX*2DEgh)NZVP;5U>LS!3H$bWw z7#Kh_$bg#=g$(zg;vgF2!v|1{A3^CSP=`K+n)3##?<3SQBs2hpIai528+1_lOX8YILCB0%j`7DycPL-`;YWUc^|528UH5Mp6q zU{PRTU=U-0WFv8?0u*}k(NFUApfW@ODvL~m${sbSI59NHCz>pfOsoy1by*-u(})F< z6|AA=foPDkZCDuU!NUZ0P-oh+K$4O>R3RlaKLZ0p7}OlxbUmb4ii9cz(V&h(3JYXt z1=LCgHEz104(x%d!$yNL<7BAMr$W_Xqd}Q}9#nqbufduPusE1BM#ZQ40GcYiK zXiz#m1C>9=0%{I1Ffg2B0nfuTJb@~t8x4wRP_;;jg&-e2V*xKHVE7C50I_sEu?lIV zt{$t4L4^YoE2LCqV}-;iCo7~#;bMhkb{>!-(4sdyG$>^Fpz_Exs1V?11+8qTXJC+o zDxd}p%8LqMgBcjKpyuMGLFo^q0OV2~sL%AE8i}Pr>4)4gThM?iXw4qT3@}aRpzY|G zEdzMs4P32^j@g2f)99EjXlND`6mWWU%og9+EvQ%qjn#sJ3`B#(J@<4CCR`5 z8mk4R3=j>fElnkQpIFDpJ6(4@qc`Kl>6Pml!>3oQ zXXKrJ7bI|by5{let-mGw=#M&KAygED`WWd6v?qc+2{5ZXG7brY-fx-hM@OirC zZcupa28G9NMsLQi({F+VZh!=A_b_@hexKgC2NWKAK;Z!r_&MEjFDN|rg2H1jqc`L4 z=`TS7FF*pm`xw0$|4yH|4-_8zK;f~E(VOW%<8-h6jA7F~_JaasKchDzxoYQxL z1a^P~WDhfXGjdPQJPZnu!=L~G3GhyrJOT=kBcK2|!syM&Km8;~-~>oO_b8(`qu}() zqo4pe3JMUAfbew9W1s*z1`3d4jNXi*({F+VZh!=Ak288Ricjx64hoRtpa200NKUsr z0Sb^4pa40+=*=iS{Uu1?1xUd6B%?Q@?DUx@K>>0S6d)i0`RSggKndg&C_GLvdNV3c z{|OTK0TPHk&FIajJbmS9PJN z1aXy-cY5qKMsG&v=_{{+0^k}b0IoB7GrCTXybcP0>!1Jt3Alr{Y5FiSP5*bDQEYn7 z4MrbE&*?jFFosXxaf6X}y6jCxZ$|IwnKwZ}a1#^+AOYX$lD9xXa0?U!w-~({{imM< z37h~4=-y`ZW(=HOc^ec2w?RPw5(u8Ic?T2(cR)dKhtZocboxz@zzvXq?OjH1#_;K# zcR@+wE+|QW1R|$f-UB6xd!Qt7kI|bkdiqO{zzdLo?|nvZ#@OjI?=yxo#!deSqT;7} zK41)IOqjk9L?urD38Ipw2R>vBXH1^H@*yZ79)bem5u-O_>h#D*jNy!F(>H>s^y!?B z8N(SfrYC}^Oi-AFPv7yFk!QNh6Gk7#?CF_LKmqUs6aY^cy%}?-OFjh!z*A5FJZ1D| z%%6S|Byi#>BhPf5XN*3Kh0`mafdb$eC;*-@dNUSJ*L)5Nfajn9c+Tj}SUUYCNZs1YUpyd|xqoGuBR@`HC@| zv2OZD5LG|j^EG2QW5e`?AgXctPY~5KJ@5@n~S^h6NVIejOH>Y6V2o-v%UdwM2_>Y08JMD5FCxWQi(_ez9Iny1#GKMqGojwyp&71xaM9rV>`HeB0al!P3 zAZp?CpCD?{^uX_o;f#x?uLMy`rZfIv3};+AJrYDMo4yf5EuYT$lQEoe#q>lFwQ~AS z5VdN$;4j8-#?{j^e}N+S7bt>%F?utuoi6zs6v4ki5&WCcn{oa0lOTZ;AOYP!jNXhJ zr&s;~dG`;4Esy!#jA-M@_9j9aJQ1PR;#3E2K)^k&>Xz4ISqIOC4#4?)z< z>6ZUNvHKqsyZ;%z8Fx>A`JXYIanDporf|l+Q)e=TGwz%Ektv*U|8!4Arf|js(-(rM zgVTR9GKEk7!N>$nYKNz5}>m0y{tgvaC$rjHjn(vNDBF&tPTZoqiT1aCW*R8&mjn2{tC) z>9uT3;AR0x-~>oOmz}AcwULQ|fpdDJG*k8D2f8erpmm|3<6W+9H{@VyRzVe;Zpgu; z4-%Trq06*@ar5?-x=i&$j}R~Ga{lxy?0#~Cu! zF)?OLziZ62n(4#p?Oi5JznB>(OmDSdn#_6&bbiJ5Mro$=j39l}1uU635Q6F~AVJ2d z+coW((h3-@wjW&2I0& z43nV2H4Q2T+Sb(w6`KwflVM5hyJ{#ZG`$0E5`Z3=9lvAO_R{AWKXb7#KjCTR;iw zG*q)GbVm*-L4h0viW1OSA#EUEFfcG&ggVBYfq|hDDs~yF*Mfn80kjwkBzy%bRu9@K z6b}-Cu3rYZ!3q?2P%+TjWssOP19(e5SoAtnGpO&S!NR}*N@O>nVz!_h!2&)oh~XwQ zO6)+v4^43Qpz1(-55Iuc=YkU6eW;)V0|NsnA%PO!1E`oIR1B1$Kx5}1$AC5{fR~|A_my{Xb%Pkh9YJL22jESt&s*f2DECY7%B!@7Y!2gVqjn>0EZO=!w0B&ply*L zM}yMXN2r(&Xf_oj2ue_(#nd3pz6=Zu)*uE01H)&it3i7yof*NqM;X3CHTyF#Ft|h2 zeS?aD_F8&F#lA!J1~M=(BtXSL3$#H@&^E+$M(Do1Ur^1#3=9m}AXx?mhTl*z&}Pki zsMsH*46*|t5>x^* zfH$l&Fha|F(7sR5(O95RWQH!41?~Nu1yu)HL=MU&pnagArG%i=&BhE_IS4wC2V^-& zuMAXg90T|)3eX`R46;zMc+mQAP=W)cS~;j7Xe%jb@h&KZf)3OHxf-;sbO|%~7zzdj zs5;QrQqXE+P$B~@EC;CrZ8O~sI%Wry@1g2I%S=G4lR*g&w16C>E|q}+yhPd!6#ptv zg=wJ0y&x|!FfhPYn1gnxaxpQ0&dp$etuoJGU;tOwAm71OoP*9u`vEeUfq?oja8uF)`NJ3m3GzzC|mgaLf40O(jA24kogXtynB5j7}+OrT^PXhRIMdJE&M40|UbpkOt7cYi0{bg9;Supdtq(=>S#O0QE6Q&Jij$ z0aSD|Ffhz`Fg@=IQ&}cx3GRLt28M$ykoCaZSr{00urM&}WMN>CVP#;DV`X3ftzM2| zVPJ@6VPF6?2}HP-88bg@FOIoZ16)))NZ@gBJ?}gE#0bC>90=P{YlSg@M7J zg@GYpdg4_k;Y83%YzGzw21gbK1}7E<2GFrlE-VZTuAoDsSQr>UOS=tN7#KiH;-4`y zFg#~wU;s7YUNSQ&fN z0kvO1RSBql0%}r#N_RB|NQn+AaJ8m8K4uc;1l6CQYEX2#?=>cISx~M3EjzRpw-DoSr{0Ou`n=z7IOEp zFfjD7Fff1)#G1$gX@pH~>Hs#ZxM99FvaDKYybtd_G&`#w|3=9mL85kJ0FfcG|1r76qnzzvX`=I(7R6m32 zV^IAID!D-QD`!qH7;Sv)A11R0gGcqtJFfxGKRG>DLG9v?n3L~VM#LURR@DQ|y9J;0Y7y|#?nRWW12TV@&-&hzJzOyhe{9s{V_{qY+@Qa0k;WrBd!ygt% zo9YA$0|Tga@`#B6-1>OJ#J~XB(G1$W4BBUX2ec~_x)&R?Ya6r(@el(8!wv=p22gDU zs+~a1Fi<@Ys@Fl43aG*WwTK)+0Swv+1llqo4a({)3=FcMxMpEs;AUZ9;00~5U}0bY zZAJOS3|aaLDuY3FGU$W=(4sZao+%zi1_n?g1JtPa0&*Y|0|Thx0BR`w05zGI85lsv zlY*LKpyt;*Mg|5@`|mmvKLfZicZ&(K$<1oI;w>g=P8UW723JM~2KVW%x0pcVR1fnhfT1H&E$28O+$y$qnXJoK0dP|XEu4uM)f zpoXOl3j>2K3j+gaYg8RG1A`(91A`I^1A__+1A{6H1A{ON1A_<)1A{0F1H(6F28Ie| z1_n^I2CA}}7#J8pyV8Dx8kV587HAC=J7~m!39nHU)CKphKvCI$v4CI$vqCI$vKCI$vj9S=HFLlqQ_ptxmZV7Si6 zz;Kh1f#D7#1H%?Z$o|&@j0_B*z07+V85lsDr{^;=Fo4Sny zF))C}WC!melpb-!T1_n^O2-LnYXJ%jkwP`?27*O{*kePuYh?#*Q zoSA_knwfzio|ysM#ZCon{7YkIU`S_%bfrOE=PYLI24OQC)F?f{1nCWf4knNW?c`>J zbVz?eJE5$s3=E)6QHw!^BWOz*ZM(7(jdBK+S7(-+X3L|)7(iXH5@rSlanOE0(9r^* z^BiD?fp*-2dY&-86C(oys5_O+$iPs;$iQI62tJJqeBy!@BLf5I>Ry2wa}6j_SI@xk5*l})4SJvs1!%Q32!jjlFi0~o zFic=#VDMyOV7Lp~+6~$qzmJiDVJRa61E`G-vK-XVUc(xf8T;6SN5wM1wHs z>;urQe$b%{pz{hqK5AlQUAqo_bKJ2`X1W-4{>@gAQQ;9mwDV zYRoZ0Hamh!*K3Rn44}5iT+j#$BLf4d9J<2Dz;Kz7f#DJ(0|V&LoEy+m7-k;KEKqv} zG|~#QtCbOQ=mRI{9B`N*$N?}7pc5cKd{D6r8jb~>r@%Zt@;#HXAn1q%Q27OFKZAyr z7^nBXXA-Xm9mxPXrlEt8fdO>P1L)`nP&|N40W~5(EtP$sl*|Z8fe#rO7@jgRFg##n zU;v#f0dgLwWCJz9K<7(3vf5dq4JAU$9{69WUN z*$+zguwdZ@9Rm+K8lDMqPL}}aoCwen@l232DwLTZnH*$BE~t3}I{QeBiGcx>_C=W( z7(|#D7{r+%RUSwV)aL}9askpH#RO@hNHT#BIAZ{n6d-ZXVHZkF3=E(~fdUf)gB%m& zpbJn-LXU}o0n|_csRJEu0cx#)4!;2Lmx0DjKnz_b1_m8y^Fxb?fk6}MYf!=fB@&Pt zP+|blpcVuu$$}D=5h(N+7#KiuusmYI#K3@_M~s;uc?8s~F=Jw2_ymne`{{z8nWQBx zpp6?BP>YCxfdOQ+GpNxs-SIP%w2LFCkOU2?fSQ(!3=E)t6{sDwnu&n{H2z%7#J~VL zmjiSr2do4Dxe_$*0V+g2m>3wQFZ|4uUH>1{K7+N@K*|G{7#Khy2WoeLj`{!@13K!% zhlzo~iwV*~0hM5Zpz#-|T97(_s642k0O<#*0ht>PRR>Z7GAE3Qfguz$)WXET5XEE! zDGpCFGBCg_2Ps5W^dAZ`m>3v9%;^WdFli)#W@|tPh=6!UVAmGrZCQG zCP?5VR067b!ZpZY%Z%oR%phho92I3qm+JyQmTnbYfkFiA2lo<8#jld&`?DT9vlpM7Ry@+)B;B@PZy0~2&2v;6eu zKbVXe^`}e!WRjEyO(}rxb?8=`HUIp=7xP#cLB@f)@298x|74Oh1NF2(7eUf|4 z0qa?W0??@(psOOJJj29OnJ!O4$oyfNKJO=!gej;)4RUOE70Zk(ZpN|*6|id@mOE=* ztL5EQ$;4>N3O)d#A!YjOpG=ZW)vVjuelg{UGU`vak7btJ-owwV#Kb5+z0sFhcv=iI z+x8QJ%M?3Vs_j9SD2Zb5m{DAl-WUxF=czVC$l+<#1;o;BQ_+J+uT9src94d zWtN=&&xhG$x|TO{6GVaJ_G8}6av)1!vM>ooga)bU=X{w>pc;_GMW=iFF$XisZ=dhS z+{(!Gy?47>0J99pFsT2g#|1J=Fgi~!4`iOr^u2ewND%WZh%sOfPCpUEY{F2ES@vmG z2293)Vea(UIA%$xP17gDF`G!gngyPzYDhNOR$+BMoDHJbK+k{y;vSGHDW?Ckwu{6w z%P>mAZtu`vZg=2WYf>#pxgJP0L-BO)1ZGKT*o7N&kE$)#>OAulq`(raV)^v?1cc=- zsmxN-&m=HQF&&ya{Y3(^5!3y-(`6EwC752%oo<%MoW`g>eQqMNBqPLJsGAv6r@u~Q zHfAiHE}z5<3$p2LNz76*up2)lPfX;z+qz*j6JwmQo}r#G1H-ii)AuAXOEA4!F#TE* zvyaSm$ekSxDjTBr2km`!0pdhZ$h}=O-7c9~LK=2U$h^Lvm8T32e1oVk&@+Yx3Zvij z?qp_3Y1r){*KKX0#m?Ftf~hlLD4xDQ8R6Lpam-RsA4tP)5IJ-B(ZVOE*W6}ej5EhtR#io1i=nSj(ynBEUk2aTUYDaR?eVG1t&EJ4)1Rg>`^vzs69-+Wu`gxE7 z*kvf8{SHm*ydEhd6r@aN&R|YwESsL4!7RzxJiRr8c`M_@>DHOd#=@{g6jPZCuM})) zt`T6IKfN)N*_!Fg+Uff;nI)tPp?5*g>^VmMZW5i}!)BQo}>Zj-CV~ISlc^05jan|&?dCZcguzP2=3YqjCe0oHTiP2Ec z6q2-|SKO?=nf@yGN4Go^D1Cr462$E3EcwhPxZMLQMIef0ET9+TJmPit`{>>y1TJPQ z^~@O<4sDpeD4*Gg>G*=_*YcTtq+vJU%=k0s(V0hUIlxXf1eKK#zcWHiWSljT zaqaYO5Y;ffzmQpyaq9H_1@n|htxMSb0+t&~ZV7K+$J#QL1QRmEUgbdU4wIH*#px66!e|KD# z_NDqNLP6;ChafjpPyb!SEGYxK9>_2@R(;#M&vOy#PHmrVP|Q4wQDOS&V&-&4hUta1 z%*NBxN|-a4_U)T~p@dn&6n4!I-|{m{1E=3jVgfbROhJt`*hN2n`!?B^)&J83$DJ`K z5xGv6EoGKuoH*UQl-ZckXnHn;*ILSKYznzmhoK>Jr_Blzw%lhdj0Sp!7J5bo4Eo^v zb{HC-Pb>Mf(Ei(QgpA|#w;&UOr%$Y47GVP`PMxk;#w^KLG2Ob1SyH+idX-NH%bJXM zL8}Uc)_K$O%a|q2HbU

    1. 8<=-R(v0A%p_hZ9x*hei;UxGD}6s+&nmaUm3H6H0*w% z+s$cihSP2xM5th#{t@H|(dpdf%#zZu`-vu7zfsQWoz;O*XFJ`#oLSNodZSUpJZXbn zmtQ7FAr!#wJPNYApe-XSxEvu;t;gc-oZHK3_=0cSO7cZ=)viF zmCO>RH=*%#h~b^P+Dj$}gkG$%0M-kR1(E6fmCUTtu)Cp>#Qt#d%Zq$N=+~USzLHte z6n2@^iEj<^HA_M#BNSkb9k2yy)Bjd7OG?8osNxlOc{y)g*>QwcaO_Ajf+I#`dVCc# zD=2o-s+f(;U^gZ0^b~TC`jrSOwv6@6LD>g(N0Pqw##G<;b*c9cx_uV$7sgI(KXez1GnH0Ous z!HpkK(a8Y2>B)ZC%RfePd0)YK*ig@ef#KT4>GP_YC8TkvFk)cHn|=}Gi2CWTtC@Y7 zPF|R9Rl{t=^ykX-yc%W+Y4)p-D$7DMJm&BbS!b~04L~M~OkY^TEXk-geRmDBq%`d6 zr(8zPYwc5#7l1<5SkKUsfx&nT?_48Dqorr68Wk^qaNJzS6L(oo-#@t6CIxtcHm(&J5JpU|{H)?pVicEDgI2 zYr?ms3wFseH-Sq$Gd)WNhN;v0>zF0YV3$krth#W)ie(O{M_{05Xsl<<0K0xljoq%u zY5ONBCdN2ZJws58UA{5>ULCU$d0C~yUU7A-I^FbG6X zUlq&pkIMnHfC02}DH^PF`sFwlRjw6K!4qIb($i(*S-iQy%N#&DV_2r=Utv<6J|mt* KVERO9rU?L{R=EoR delta 29535 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 HVwwN|9Wm9s diff --git a/package.json b/package.json index c4c6c25..72440fb 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", - "@semantic-release/github": "^10.1.1", "@tsconfig/strictest": "^2.0.5", "@types/bun": "^1.1.6", "conventional-changelog-conventionalcommits": "^7.0.2", @@ -53,8 +52,5 @@ "@revanced/discord-bot", "esbuild", "lefthook" - ], - "patchedDependencies": { - "@anolilab/multi-semantic-release@1.1.3": "patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch" - } + ] } diff --git a/patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch b/patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch deleted file mode 100644 index dd96e2a..0000000 --- a/patches/@anolilab%2Fmulti-semantic-release@1.1.3.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff --git a/lib/multi-semantic-release.js b/lib/multi-semantic-release.js -index 1247e618244dc35f6d60d1e484fbd1a84e504b2a..fc8dbf1c418351f2c25698e3fc3fab478693f125 100644 ---- a/lib/multi-semantic-release.js -+++ b/lib/multi-semantic-release.js -@@ -114,6 +114,11 @@ async function releasePackage(package_, createInlinePlugin, multiContext, flags) - options.ci = flags.ci === undefined ? options.ci : flags.ci; - options.branches = flags.branches ? castArray(flags.branches) : options.branches; - -+ // Patching so plugins from globalOptions are also inherited. -+ // If the third element in the array is set to true, the plugin will not be inherited. -+ options.plugins = [...(multiContext.globalOptions.plugins || []), ...(options.plugins || [])] -+ .filter(plugin => Array.isArray(plugin) ? !plugin[2] : true) -+ - // This options are needed for plugins that do not rely on `pluginOptions` and extract them independently. - options._pkgOptions = packageOptions; - diff --git a/.releaserc.js b/semantic-release-config.js similarity index 63% rename from .releaserc.js rename to semantic-release-config.js index 175fde7..7812220 100644 --- a/.releaserc.js +++ b/semantic-release-config.js @@ -1,4 +1,9 @@ -export default { +// @ts-check + +/** + * @type {import('semantic-release').Options} + */ +const Options = { branches: [ 'main', { @@ -7,7 +12,7 @@ export default { }, ], plugins: - process.env.RELEASE_WORKFLOW_STEP === 'release' + process.env['RELEASE_WORKFLOW_STEP'] !== 'publish' ? [ [ '@semantic-release/commit-analyzer', @@ -17,10 +22,16 @@ export default { ], '@semantic-release/release-notes-generator', '@semantic-release/changelog', + [ + '@semantic-release/npm', + { + npmPublish: false, + } + ], [ '@semantic-release/git', { - assets: ['README.md', 'CHANGELOG.md', 'package.json'], + assets: ['CHANGELOG.md', 'package.json'], }, ], [ @@ -34,6 +45,7 @@ export default { successComment: false, }, ], + // This unfortunately has to run multiple times, even though it needs to run only once. [ '@saithodev/semantic-release-backmerge', { @@ -45,8 +57,19 @@ export default { ], clearWorkspace: true, }, - true, ], ] : [], } + +/** + * @param {import('semantic-release').Options} subprojectOptions + * @returns {import('semantic-release').Options} + */ +export default function defineSubprojectReleaseConfig(subprojectOptions) { + return { + ...Options, + ...subprojectOptions, + plugins: [...(subprojectOptions.plugins || []), ...(Options.plugins || [])], + } +} From 565260ef909b2f4ecbdb00e8375697cf68563006 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 22 Jul 2024 22:58:26 +0700 Subject: [PATCH 158/312] ci(release): patch semantic release npm plugin to not use npm CLI --- bun.lockb | Bin 285856 -> 286056 bytes package.json | 5 +- patches/@semantic-release%2Fnpm@12.0.1.patch | 62 +++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 patches/@semantic-release%2Fnpm@12.0.1.patch diff --git a/bun.lockb b/bun.lockb index 27e8610af7b2f6cc078b5cd1c4f41bd43d134fdc..afde74b2f96f60e2c3aec010c5a8b85c2bc0f186 100755 GIT binary patch delta 305 zcmZ3mQ}D$u!3lnhHyZt0{Ta9VGnIW~XN)t`Gte{Me&!ofN*m*q?S{3?<#${Q5=)XZ zQj7H+ic@nF^GY(4b&FDSQWJ|)RgK*83UVC`jr0uk4E4au7@(lszsS2dAkkSMiUllU zF#TX8i}Lgrkt_4H%#|2Z?FAxb)?OGdM}b6$XQKY(Oqrng43cynvSK$KX- zuuRXt%%nOkhDAWAz_G+R!!^aF04iA#GhHg0MNK%U85kIh+dPfGHl+NS Jeo=~P0stQMSHJ)O delta 152 zcmaE{OK`zX!3lnh0*(Hy{)}7wnaaMgGcka{_A}p@QrZ|-Y&WcBF25rQ7Gq#wDEBY& zE)Ga^ei6w67UG!xHj+h|nSp^}I)4<4B47h|9k2xKpAz}&B9iv&? dIaffrCqPQlIT>OgvI5f?V_3|mUzB2+002IWB@qAs diff --git a/package.json b/package.json index 72440fb..82e9a39 100644 --- a/package.json +++ b/package.json @@ -52,5 +52,8 @@ "@revanced/discord-bot", "esbuild", "lefthook" - ] + ], + "patchedDependencies": { + "@semantic-release/npm@12.0.1": "patches/@semantic-release%2Fnpm@12.0.1.patch" + } } diff --git a/patches/@semantic-release%2Fnpm@12.0.1.patch b/patches/@semantic-release%2Fnpm@12.0.1.patch new file mode 100644 index 0000000..5344d0a --- /dev/null +++ b/patches/@semantic-release%2Fnpm@12.0.1.patch @@ -0,0 +1,62 @@ +diff --git a/node_modules/@semantic-release/npm/.bun-tag-c9c8130945517add b/.bun-tag-c9c8130945517add +new file mode 100644 +index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +diff --git a/lib/prepare.js b/lib/prepare.js +index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360e031c7b5 100644 +--- a/lib/prepare.js ++++ b/lib/prepare.js +@@ -1,6 +1,7 @@ + import path from "path"; +-import { move } from "fs-extra"; ++import { rename, readFile, writeFile } from "fs/promises"; + import { execa } from "execa"; ++import detectIndent from 'detect-indent'; + + export default async function ( + npmrc, +@@ -11,19 +12,12 @@ export default async function ( + + logger.log("Write version %s to package.json in %s", version, basePath); + +- const versionResult = execa( +- "npm", +- ["version", version, "--userconfig", npmrc, "--no-git-tag-version", "--allow-same-version"], +- { +- cwd: basePath, +- env, +- preferLocal: true, +- } +- ); +- versionResult.stdout.pipe(stdout, { end: false }); +- versionResult.stderr.pipe(stderr, { end: false }); ++ const pkgJsonPath = path.join(basePath, 'package.json') ++ const pkgJsonRaw = (await readFile(pkgJsonPath)).toString() ++ const pkgJson = JSON.parse(pkgJsonRaw) ++ pkgJson.version = version + +- await versionResult; ++ await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, detectIndent(pkgJsonRaw).indent)) + + if (tarballDir) { + logger.log("Creating npm package version %s", version); +@@ -38,7 +32,7 @@ export default async function ( + // Only move the tarball if we need to + // Fixes: https://github.com/semantic-release/npm/issues/169 + if (tarballSource !== tarballDestination) { +- await move(tarballSource, tarballDestination); ++ await rename(tarballSource, tarballDestination); + } + } + } +diff --git a/package.json b/package.json +index e716bf6b35130c168ab7c4babc89a0346aacc9ad..e372de1ca5967509a6769db88ff967e1039b03ac 100644 +--- a/package.json ++++ b/package.json +@@ -21,6 +21,7 @@ + "dependencies": { + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", ++ "detect-indent": "^7.0.1", + "execa": "^9.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", From df3fee96fea11af6219efc3a621a7b91e8536216 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 22 Jul 2024 16:03:59 +0000 Subject: [PATCH 159/312] chore(release): 1.0.0-dev.1 [skip ci] # @revanced/bot-websocket-api 1.0.0-dev.1 (2024-07-22) ### Bug Fixes * **apis/websocket:** builds not working due to dynamic import requirement ([fc7be22](https://github.com/revanced/revanced-helper/commit/fc7be22c6c15974c7394790e93de2a23a6627153)) * **apis/websocket:** don't bundle `tesseract.js` ([51a6fb6](https://github.com/revanced/revanced-helper/commit/51a6fb65f0df3409eacffb297430840a0e326989)) * **apis/websocket:** fix forever stuck Promise ([168f40d](https://github.com/revanced/revanced-helper/commit/168f40def64ca213cd2b549f4bafed4c0e1e3695)) * **apis/websocket:** fix undefined error ([2f03800](https://github.com/revanced/revanced-helper/commit/2f03800c61c00e59e512567d273a195e605d6736)) * **apis/websocket:** improve logging and error handling ([b6cbe9d](https://github.com/revanced/revanced-helper/commit/b6cbe9d64c01ff11feab8351fb801bc1aee48325)) * remove error cb handling for `socket.send()` calls ([29544d4](https://github.com/revanced/revanced-helper/commit/29544d4e0127173465796b7e3c62161f4db59c8b)) * **types:** fix issues with typings ([669e24c](https://github.com/revanced/revanced-helper/commit/669e24ca8103ea051b4e61160dd0f978e36707ea)) ### chore * fix more build issues ([77fefb9](https://github.com/revanced/revanced-helper/commit/77fefb9bef286a22f40a4d76b79c64fcc5a2467f)) ### Features * **apis/websocket:** clear old client sessions and instances ([43bd0a0](https://github.com/revanced/revanced-helper/commit/43bd0a021cd885a3d74a1f307ec2935e81d17458)) * **packages/shared:** add logger factory ([17c6be7](https://github.com/revanced/revanced-helper/commit/17c6be7bee5b5c24fd4a5279e73374b0bb7a6229)) ### BREAKING CHANGES * In `@revanced/discord-bot`, its environment variable `DATABASE_URL` has been renamed to `DATABASE_PATH` and the `file:` prefix is no longer needed --- apis/websocket/CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ apis/websocket/package.json | 4 ++-- 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 apis/websocket/CHANGELOG.md diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md new file mode 100644 index 0000000..c11b5ed --- /dev/null +++ b/apis/websocket/CHANGELOG.md @@ -0,0 +1,30 @@ +# @revanced/bot-websocket-api 1.0.0-dev.1 (2024-07-22) + + +### Bug Fixes + +* **apis/websocket:** builds not working due to dynamic import requirement ([fc7be22](https://github.com/revanced/revanced-helper/commit/fc7be22c6c15974c7394790e93de2a23a6627153)) +* **apis/websocket:** don't bundle `tesseract.js` ([51a6fb6](https://github.com/revanced/revanced-helper/commit/51a6fb65f0df3409eacffb297430840a0e326989)) +* **apis/websocket:** fix forever stuck Promise ([168f40d](https://github.com/revanced/revanced-helper/commit/168f40def64ca213cd2b549f4bafed4c0e1e3695)) +* **apis/websocket:** fix undefined error ([2f03800](https://github.com/revanced/revanced-helper/commit/2f03800c61c00e59e512567d273a195e605d6736)) +* **apis/websocket:** improve logging and error handling ([b6cbe9d](https://github.com/revanced/revanced-helper/commit/b6cbe9d64c01ff11feab8351fb801bc1aee48325)) +* remove error cb handling for `socket.send()` calls ([29544d4](https://github.com/revanced/revanced-helper/commit/29544d4e0127173465796b7e3c62161f4db59c8b)) +* **types:** fix issues with typings ([669e24c](https://github.com/revanced/revanced-helper/commit/669e24ca8103ea051b4e61160dd0f978e36707ea)) + + +### chore + +* fix more build issues ([77fefb9](https://github.com/revanced/revanced-helper/commit/77fefb9bef286a22f40a4d76b79c64fcc5a2467f)) + + +### Features + +* **apis/websocket:** clear old client sessions and instances ([43bd0a0](https://github.com/revanced/revanced-helper/commit/43bd0a021cd885a3d74a1f307ec2935e81d17458)) +* **packages/shared:** add logger factory ([17c6be7](https://github.com/revanced/revanced-helper/commit/17c6be7bee5b5c24fd4a5279e73374b0bb7a6229)) + + +### BREAKING CHANGES + +* In `@revanced/discord-bot`, its environment variable + `DATABASE_URL` has been renamed to `DATABASE_PATH` + and the `file:` prefix is no longer needed diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 75718a1..029b382 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "0.1.0", + "version": "1.0.0-dev.1", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { @@ -37,4 +37,4 @@ "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } -} +} \ No newline at end of file From 55631b220f175406ee9dfe8d8c51efca8251fedf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 22 Jul 2024 16:04:34 +0000 Subject: [PATCH 160/312] chore(release): 1.0.0-dev.1 [skip ci] # @revanced/discord-bot 1.0.0-dev.1 (2024-07-22) ### Bug Fixes * **bots/discord/commands/mute:** use existing `parseDuration` util function' ([3e07429](https://github.com/revanced/revanced-helper/commit/3e07429664f7dbb6ce653083e0adb1a232737fde)) * **bots/discord/commands/unmute:** fix option description and embeds ([7fdf8c0](https://github.com/revanced/revanced-helper/commit/7fdf8c0dc722e21fe5a3ad6ef8d3a306ef85f532)) * **bots/discord/commands:** minor issues ([3b2596e](https://github.com/revanced/revanced-helper/commit/3b2596e748cf2cde1500ef2ded55f0faabc2c272)) * **bots/discord/commands:** refactor and add checks ([a2bf3ea](https://github.com/revanced/revanced-helper/commit/a2bf3eade99b46f9ffb55d45b8caf1bcf3d22a9b)) * **bots/discord/database:** fix schema for role presets ([4aa138a](https://github.com/revanced/revanced-helper/commit/4aa138a9af8db7093ef637470fcfdea1f5341236)) * **bots/discord/scripts:** unintentional escaping on windows ([09dc706](https://github.com/revanced/revanced-helper/commit/09dc70632da0597fdb26677acee3f6fccbb2b9b5)) * **bots/discord/utils/discord/embeds:** set thumbnail on moderation embeds ([b056291](https://github.com/revanced/revanced-helper/commit/b056291ad0f2e2eac5eec8aa71f15dbc769aa0f9)) * **bots/discord/utils/discord/rolePresets:** correct property access for presets ([4c6ad11](https://github.com/revanced/revanced-helper/commit/4c6ad11be30c1d6af97c4ae40fc62d05fa7bdd57)) * **bots/discord/utils/discord:** add `moderation` module related functions ([7e8270f](https://github.com/revanced/revanced-helper/commit/7e8270f7d260322e1950e058b221ab088bd595d0)) * **bots/discord/utils/duration:** fix empty string returning with duration is 0 ([83c314e](https://github.com/revanced/revanced-helper/commit/83c314ef5f721abc355272db0e4c182dcfe5d943)) * **bots/discord:** apply active role presets if members rejoin ([f50b26b](https://github.com/revanced/revanced-helper/commit/f50b26b82d66c88fd1dbb8c07d77c177c0e781df)) * **bots/discord:** check token before connecting to bot api ([f3e4408](https://github.com/revanced/revanced-helper/commit/f3e4408aa28fb6a9d21365af8c1bea3d07b481de)) * **bots/discord:** clear role presets after they expire ([faa81f4](https://github.com/revanced/revanced-helper/commit/faa81f4d887eaeae809651f5b68187d033a260f2)) * **bots/discord:** connect to discord API even if initial bot API connection fails ([6658b58](https://github.com/revanced/revanced-helper/commit/6658b582dbeba7e072a7a04c4efa255e7f634aef)) * **bots/discord:** do decancer after resetting nickname ([0303fe3](https://github.com/revanced/revanced-helper/commit/0303fe3e367c07e92f831365d5548ca5b03435b2)) * **bots/discord:** follow-up if reply is already sent when error ([f75060b](https://github.com/revanced/revanced-helper/commit/f75060bc9cda44902cf872def73c116a6df039d7)) * **bots/discord:** messed up file name for `unmute` command ([399dca7](https://github.com/revanced/revanced-helper/commit/399dca71538fe5c8831977694a97058254a17578)) * **bots/discord:** owners cannot bypass checks on some commands ([39cba97](https://github.com/revanced/revanced-helper/commit/39cba973418027ba6ed67e1ae5ab5c6458807562)) * **bots/discord:** remove auto-generated files ([fb8af00](https://github.com/revanced/revanced-helper/commit/fb8af008661bf37389e01cba19d64a8b4fc82139)) * **bots/discord:** remove usage of macros ([7f27c56](https://github.com/revanced/revanced-helper/commit/7f27c5607ceeeef56d67097e88f68caa1b8791b3)) * **bots/discord:** remove useless feature ([d830e48](https://github.com/revanced/revanced-helper/commit/d830e48bc2de7aa457eab3a5f96ae652a93178f9)) * **bots/discord:** use `APIEmbed` for response config ([35b9448](https://github.com/revanced/revanced-helper/commit/35b944800a3943c187d5b0e0d3e465ad7d2056fe)) * **bots/discord:** use env for initializing database ([af3759c](https://github.com/revanced/revanced-helper/commit/af3759caf428fada3b3f4a51852543d6fb280018)) * **bots/discord:** wrong command file path being imported ([fa0159c](https://github.com/revanced/revanced-helper/commit/fa0159c3a8dd4dad8778ccdb75b9e7c02ebbb64f)) * config file not being read ([474a8be](https://github.com/revanced/revanced-helper/commit/474a8be4af4eb2bae6e80a893439d846ad4f7503)) * **discord-bot:** also execute slash commands ([f0d45b2](https://github.com/revanced/revanced-helper/commit/f0d45b2c926ed753e2d21f2e06e24d7e6c43880a)) * **discord-bot:** check for role position ([d332043](https://github.com/revanced/revanced-helper/commit/d332043b1a4bb7ac9698a2fc912832e184130b4b)) * **discord-bot:** check if the member has the role ([9bff68c](https://github.com/revanced/revanced-helper/commit/9bff68c8c40c692764e4dec15a058e35059efbc9)) * **discord-bot:** not executing slash commands ([aa08087](https://github.com/revanced/revanced-helper/commit/aa0808768b90844c5fbd3e75d9f2d01c723b0151)) * **discord-bot:** only send lowercased text ([7803758](https://github.com/revanced/revanced-helper/commit/78037580dc92883f5ca21157e45268850cb5db90)) * dislike button not working properly ([85eba55](https://github.com/revanced/revanced-helper/commit/85eba554247738066af72a8efd0de215ec1164dc)) * fix deprecation ([4373ede](https://github.com/revanced/revanced-helper/commit/4373ede855333f209676551162a525238656e1f8)) * fix the fiter for the interaction collector ([a9ff003](https://github.com/revanced/revanced-helper/commit/a9ff00394a73f68a6793c2b35ff184675ee5a72c)) * ignore message if there's no content ([3cbebc2](https://github.com/revanced/revanced-helper/commit/3cbebc284277808495e64cf0fb47c555924ad9c5)) * move modules to `/bots` ([cd7156e](https://github.com/revanced/revanced-helper/commit/cd7156e792e65777ad1ab5a6f5d828b9ef6a9754)) * trainAI not using the bin location ([bd29943](https://github.com/revanced/revanced-helper/commit/bd2994388bc65f720120ef49edb6ba8163260309)) ### chore * fix more build issues ([77fefb9](https://github.com/revanced/revanced-helper/commit/77fefb9bef286a22f40a4d76b79c64fcc5a2467f)) ### Features * add wit.ai support ([1909e2c](https://github.com/revanced/revanced-helper/commit/1909e2c42148d635dcd045c738d88f65c8be16e3)) * **bots/discord/commands/reply:** send stacktrace when failed ([9f1ac37](https://github.com/revanced/revanced-helper/commit/9f1ac379276c11da65235577a9c6717e01cb02eb)) * **bots/discord/commands:** add `ban` and `unban` commands ([dc4863d](https://github.com/revanced/revanced-helper/commit/dc4863dc208b3fede4d4def323306ab58daffe04)) * **bots/discord/commands:** add `eval` command ([e64d1da](https://github.com/revanced/revanced-helper/commit/e64d1da00cc2ba718da5a4b0da141fe86a0e48d2)) * **bots/discord/commands:** add `mute` and `unmute` commands ([c0fa2fe](https://github.com/revanced/revanced-helper/commit/c0fa2fe1c36acdc7c52cde277aa7da867065f55e)) * **bots/discord/commands:** add `purge` and `role-preset` commands ([fb01ce5](https://github.com/revanced/revanced-helper/commit/fb01ce57400130c93751a11573eb444c0ba103eb)) * **bots/discord/commands:** allow process exception in `exception-test` ([ca47535](https://github.com/revanced/revanced-helper/commit/ca475356ad95fec86e8e8b5bf4bbf17b70add5fe)) * **bots/discord/utils/discord/embeds:** expose `applyCommonEmbedStyles` fn ([2d794ed](https://github.com/revanced/revanced-helper/commit/2d794ede7d7a208bd3616c45e8e6d2a2cd83e9ed)) * **bots/discord/utils/embeds:** make title parameter nullable ([ee885ca](https://github.com/revanced/revanced-helper/commit/ee885ca7585a55fdc31e137ae29dc13a37ce2fb2)) * **bots/discord/utils/fs:** use `recursive` option for listing files ([da21e1a](https://github.com/revanced/revanced-helper/commit/da21e1a6f76deaeb477203b04263bd170863825b)) * **bots/discord/utils:** add duration utility ([a9add9e](https://github.com/revanced/revanced-helper/commit/a9add9ea9affb42bdfcb17cf4b268feec5729854)) * **bots/discord/utils:** add functions for role presets ([fb32a04](https://github.com/revanced/revanced-helper/commit/fb32a04ad38be8d0836dc99259b6ef05a0825830)) * **bots/discord/utils:** allow loading commands from custom dir ([8b690b8](https://github.com/revanced/revanced-helper/commit/8b690b879bb5c6023c8fc863afbd9fd1d02719bb)) * **bots/discord:** add `api.disconnectRetryInterval` config ([2f86586](https://github.com/revanced/revanced-helper/commit/2f8658617923c07f6847cbf1fdfc5f5379d95b6c)) * **bots/discord:** add `moderation.roles` config to be used in `moderation` commands ([39d5b3a](https://github.com/revanced/revanced-helper/commit/39d5b3a479b4d856aabe12cc31177c24f88ae23e)) * **bots/discord:** add `ocrTriggers` resp config, embed footer scan mode ([744a56a](https://github.com/revanced/revanced-helper/commit/744a56a4fdc8844e37959a88bcf81ee39fe726ef)) * **bots/discord:** add a better way to manage databases ([a68d726](https://github.com/revanced/revanced-helper/commit/a68d72687584332587455962b0202a306288057d)) * **bots/discord:** add more fallbacks for decancering ([2e1e009](https://github.com/revanced/revanced-helper/commit/2e1e009b4272495798313bd3bd61f258875c62e1)) * **bots/discord:** add source ([f9d50a0](https://github.com/revanced/revanced-helper/commit/f9d50a0a6bef8beaa428a0a555bfa4f879f685f1)) * **bots/discord:** improve logs ([6abb740](https://github.com/revanced/revanced-helper/commit/6abb7409945c10bd3af451fb45ef4b4d4ebe9489)) * **bots/discord:** sanitize `BasicDatabase` inputs ([fd76e0a](https://github.com/revanced/revanced-helper/commit/fd76e0af72fe28b414ae3b5e8d3886e58561e57e)) * **bots/discord:** support nickname decancering ([1723e8c](https://github.com/revanced/revanced-helper/commit/1723e8cacf96e8c6bdee22cfd30e89524fdcef74)) * **bots/discord:** switch to `drizzle-orm` ([e204b7b](https://github.com/revanced/revanced-helper/commit/e204b7b7566fd7fa423baef32977a8575d44a9e0)) * **bots/discord:** update config ([197d2ac](https://github.com/revanced/revanced-helper/commit/197d2acea89c38e43858d52736508d449152e804)) * discord bot scanning messages ([d1bd3b2](https://github.com/revanced/revanced-helper/commit/d1bd3b2b7e4985a64e9b070ab006cc6f3508c46e)) * **discord-bot:** a way to train AI ([355a508](https://github.com/revanced/revanced-helper/commit/355a50803adc85b5579155b55ddbba4fa0449237)) * **discord-bot:** command handler and train cmd ([6aee8a4](https://github.com/revanced/revanced-helper/commit/6aee8a4c63eb108800fcb0a23ca61f200d8f1f2a)) * **discord-bot:** event handler ([0ad5ece](https://github.com/revanced/revanced-helper/commit/0ad5ece08593c0db111fa4a592b42c6e0348fd1c)) * GODEL AI ([0ba525c](https://github.com/revanced/revanced-helper/commit/0ba525c4a5802106d582c75f713728accf2f151a)) * initalize discord bot ([bb4a5a7](https://github.com/revanced/revanced-helper/commit/bb4a5a77eefbc7ac88536f73a111df1050b235e7)) * initialize helper client ([7f9ca77](https://github.com/revanced/revanced-helper/commit/7f9ca77e0331ec143160ee51ed7c3aa9e4e70b9c)) * message buttons for training ([6551ca9](https://github.com/revanced/revanced-helper/commit/6551ca9dadc2e3ddfe98875e80ed61f7d71a1651)) * platform specific responses ([18e57b0](https://github.com/revanced/revanced-helper/commit/18e57b0c320732a937bb60db11c5d6794ed11522)) * prettier and eslint ([1c27ccb](https://github.com/revanced/revanced-helper/commit/1c27ccb17c85f0f6982db45de426181d2c231d0e)) * refactor and new features ([#7](https://github.com/revanced/revanced-helper/issues/7)) ([8b9f45d](https://github.com/revanced/revanced-helper/commit/8b9f45dc22de29dc2ccb1cfab9a026db00457e25)) * run bots in one process ([d26d533](https://github.com/revanced/revanced-helper/commit/d26d53317440c64fb775cea609a87d29be6c8b40)) * training and replies changed ([715aa91](https://github.com/revanced/revanced-helper/commit/715aa918cf84213c9b19591a398d7532eb3f232a)) * **utils/discord/embeds:** allow adding extra fields for moderation embeds ([49ce9a7](https://github.com/revanced/revanced-helper/commit/49ce9a7ca3d8558b73a9b94dfe7a01d809db6fff)) ### BREAKING CHANGES * In `@revanced/discord-bot`, its environment variable `DATABASE_URL` has been renamed to `DATABASE_PATH` and the `file:` prefix is no longer needed --- bots/discord/CHANGELOG.md | 95 +++++++++++++++++++++++++++++++++++++++ bots/discord/package.json | 4 +- 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 bots/discord/CHANGELOG.md diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md new file mode 100644 index 0000000..e07314f --- /dev/null +++ b/bots/discord/CHANGELOG.md @@ -0,0 +1,95 @@ +# @revanced/discord-bot 1.0.0-dev.1 (2024-07-22) + + +### Bug Fixes + +* **bots/discord/commands/mute:** use existing `parseDuration` util function' ([3e07429](https://github.com/revanced/revanced-helper/commit/3e07429664f7dbb6ce653083e0adb1a232737fde)) +* **bots/discord/commands/unmute:** fix option description and embeds ([7fdf8c0](https://github.com/revanced/revanced-helper/commit/7fdf8c0dc722e21fe5a3ad6ef8d3a306ef85f532)) +* **bots/discord/commands:** minor issues ([3b2596e](https://github.com/revanced/revanced-helper/commit/3b2596e748cf2cde1500ef2ded55f0faabc2c272)) +* **bots/discord/commands:** refactor and add checks ([a2bf3ea](https://github.com/revanced/revanced-helper/commit/a2bf3eade99b46f9ffb55d45b8caf1bcf3d22a9b)) +* **bots/discord/database:** fix schema for role presets ([4aa138a](https://github.com/revanced/revanced-helper/commit/4aa138a9af8db7093ef637470fcfdea1f5341236)) +* **bots/discord/scripts:** unintentional escaping on windows ([09dc706](https://github.com/revanced/revanced-helper/commit/09dc70632da0597fdb26677acee3f6fccbb2b9b5)) +* **bots/discord/utils/discord/embeds:** set thumbnail on moderation embeds ([b056291](https://github.com/revanced/revanced-helper/commit/b056291ad0f2e2eac5eec8aa71f15dbc769aa0f9)) +* **bots/discord/utils/discord/rolePresets:** correct property access for presets ([4c6ad11](https://github.com/revanced/revanced-helper/commit/4c6ad11be30c1d6af97c4ae40fc62d05fa7bdd57)) +* **bots/discord/utils/discord:** add `moderation` module related functions ([7e8270f](https://github.com/revanced/revanced-helper/commit/7e8270f7d260322e1950e058b221ab088bd595d0)) +* **bots/discord/utils/duration:** fix empty string returning with duration is 0 ([83c314e](https://github.com/revanced/revanced-helper/commit/83c314ef5f721abc355272db0e4c182dcfe5d943)) +* **bots/discord:** apply active role presets if members rejoin ([f50b26b](https://github.com/revanced/revanced-helper/commit/f50b26b82d66c88fd1dbb8c07d77c177c0e781df)) +* **bots/discord:** check token before connecting to bot api ([f3e4408](https://github.com/revanced/revanced-helper/commit/f3e4408aa28fb6a9d21365af8c1bea3d07b481de)) +* **bots/discord:** clear role presets after they expire ([faa81f4](https://github.com/revanced/revanced-helper/commit/faa81f4d887eaeae809651f5b68187d033a260f2)) +* **bots/discord:** connect to discord API even if initial bot API connection fails ([6658b58](https://github.com/revanced/revanced-helper/commit/6658b582dbeba7e072a7a04c4efa255e7f634aef)) +* **bots/discord:** do decancer after resetting nickname ([0303fe3](https://github.com/revanced/revanced-helper/commit/0303fe3e367c07e92f831365d5548ca5b03435b2)) +* **bots/discord:** follow-up if reply is already sent when error ([f75060b](https://github.com/revanced/revanced-helper/commit/f75060bc9cda44902cf872def73c116a6df039d7)) +* **bots/discord:** messed up file name for `unmute` command ([399dca7](https://github.com/revanced/revanced-helper/commit/399dca71538fe5c8831977694a97058254a17578)) +* **bots/discord:** owners cannot bypass checks on some commands ([39cba97](https://github.com/revanced/revanced-helper/commit/39cba973418027ba6ed67e1ae5ab5c6458807562)) +* **bots/discord:** remove auto-generated files ([fb8af00](https://github.com/revanced/revanced-helper/commit/fb8af008661bf37389e01cba19d64a8b4fc82139)) +* **bots/discord:** remove usage of macros ([7f27c56](https://github.com/revanced/revanced-helper/commit/7f27c5607ceeeef56d67097e88f68caa1b8791b3)) +* **bots/discord:** remove useless feature ([d830e48](https://github.com/revanced/revanced-helper/commit/d830e48bc2de7aa457eab3a5f96ae652a93178f9)) +* **bots/discord:** use `APIEmbed` for response config ([35b9448](https://github.com/revanced/revanced-helper/commit/35b944800a3943c187d5b0e0d3e465ad7d2056fe)) +* **bots/discord:** use env for initializing database ([af3759c](https://github.com/revanced/revanced-helper/commit/af3759caf428fada3b3f4a51852543d6fb280018)) +* **bots/discord:** wrong command file path being imported ([fa0159c](https://github.com/revanced/revanced-helper/commit/fa0159c3a8dd4dad8778ccdb75b9e7c02ebbb64f)) +* config file not being read ([474a8be](https://github.com/revanced/revanced-helper/commit/474a8be4af4eb2bae6e80a893439d846ad4f7503)) +* **discord-bot:** also execute slash commands ([f0d45b2](https://github.com/revanced/revanced-helper/commit/f0d45b2c926ed753e2d21f2e06e24d7e6c43880a)) +* **discord-bot:** check for role position ([d332043](https://github.com/revanced/revanced-helper/commit/d332043b1a4bb7ac9698a2fc912832e184130b4b)) +* **discord-bot:** check if the member has the role ([9bff68c](https://github.com/revanced/revanced-helper/commit/9bff68c8c40c692764e4dec15a058e35059efbc9)) +* **discord-bot:** not executing slash commands ([aa08087](https://github.com/revanced/revanced-helper/commit/aa0808768b90844c5fbd3e75d9f2d01c723b0151)) +* **discord-bot:** only send lowercased text ([7803758](https://github.com/revanced/revanced-helper/commit/78037580dc92883f5ca21157e45268850cb5db90)) +* dislike button not working properly ([85eba55](https://github.com/revanced/revanced-helper/commit/85eba554247738066af72a8efd0de215ec1164dc)) +* fix deprecation ([4373ede](https://github.com/revanced/revanced-helper/commit/4373ede855333f209676551162a525238656e1f8)) +* fix the fiter for the interaction collector ([a9ff003](https://github.com/revanced/revanced-helper/commit/a9ff00394a73f68a6793c2b35ff184675ee5a72c)) +* ignore message if there's no content ([3cbebc2](https://github.com/revanced/revanced-helper/commit/3cbebc284277808495e64cf0fb47c555924ad9c5)) +* move modules to `/bots` ([cd7156e](https://github.com/revanced/revanced-helper/commit/cd7156e792e65777ad1ab5a6f5d828b9ef6a9754)) +* trainAI not using the bin location ([bd29943](https://github.com/revanced/revanced-helper/commit/bd2994388bc65f720120ef49edb6ba8163260309)) + + +### chore + +* fix more build issues ([77fefb9](https://github.com/revanced/revanced-helper/commit/77fefb9bef286a22f40a4d76b79c64fcc5a2467f)) + + +### Features + +* add wit.ai support ([1909e2c](https://github.com/revanced/revanced-helper/commit/1909e2c42148d635dcd045c738d88f65c8be16e3)) +* **bots/discord/commands/reply:** send stacktrace when failed ([9f1ac37](https://github.com/revanced/revanced-helper/commit/9f1ac379276c11da65235577a9c6717e01cb02eb)) +* **bots/discord/commands:** add `ban` and `unban` commands ([dc4863d](https://github.com/revanced/revanced-helper/commit/dc4863dc208b3fede4d4def323306ab58daffe04)) +* **bots/discord/commands:** add `eval` command ([e64d1da](https://github.com/revanced/revanced-helper/commit/e64d1da00cc2ba718da5a4b0da141fe86a0e48d2)) +* **bots/discord/commands:** add `mute` and `unmute` commands ([c0fa2fe](https://github.com/revanced/revanced-helper/commit/c0fa2fe1c36acdc7c52cde277aa7da867065f55e)) +* **bots/discord/commands:** add `purge` and `role-preset` commands ([fb01ce5](https://github.com/revanced/revanced-helper/commit/fb01ce57400130c93751a11573eb444c0ba103eb)) +* **bots/discord/commands:** allow process exception in `exception-test` ([ca47535](https://github.com/revanced/revanced-helper/commit/ca475356ad95fec86e8e8b5bf4bbf17b70add5fe)) +* **bots/discord/utils/discord/embeds:** expose `applyCommonEmbedStyles` fn ([2d794ed](https://github.com/revanced/revanced-helper/commit/2d794ede7d7a208bd3616c45e8e6d2a2cd83e9ed)) +* **bots/discord/utils/embeds:** make title parameter nullable ([ee885ca](https://github.com/revanced/revanced-helper/commit/ee885ca7585a55fdc31e137ae29dc13a37ce2fb2)) +* **bots/discord/utils/fs:** use `recursive` option for listing files ([da21e1a](https://github.com/revanced/revanced-helper/commit/da21e1a6f76deaeb477203b04263bd170863825b)) +* **bots/discord/utils:** add duration utility ([a9add9e](https://github.com/revanced/revanced-helper/commit/a9add9ea9affb42bdfcb17cf4b268feec5729854)) +* **bots/discord/utils:** add functions for role presets ([fb32a04](https://github.com/revanced/revanced-helper/commit/fb32a04ad38be8d0836dc99259b6ef05a0825830)) +* **bots/discord/utils:** allow loading commands from custom dir ([8b690b8](https://github.com/revanced/revanced-helper/commit/8b690b879bb5c6023c8fc863afbd9fd1d02719bb)) +* **bots/discord:** add `api.disconnectRetryInterval` config ([2f86586](https://github.com/revanced/revanced-helper/commit/2f8658617923c07f6847cbf1fdfc5f5379d95b6c)) +* **bots/discord:** add `moderation.roles` config to be used in `moderation` commands ([39d5b3a](https://github.com/revanced/revanced-helper/commit/39d5b3a479b4d856aabe12cc31177c24f88ae23e)) +* **bots/discord:** add `ocrTriggers` resp config, embed footer scan mode ([744a56a](https://github.com/revanced/revanced-helper/commit/744a56a4fdc8844e37959a88bcf81ee39fe726ef)) +* **bots/discord:** add a better way to manage databases ([a68d726](https://github.com/revanced/revanced-helper/commit/a68d72687584332587455962b0202a306288057d)) +* **bots/discord:** add more fallbacks for decancering ([2e1e009](https://github.com/revanced/revanced-helper/commit/2e1e009b4272495798313bd3bd61f258875c62e1)) +* **bots/discord:** add source ([f9d50a0](https://github.com/revanced/revanced-helper/commit/f9d50a0a6bef8beaa428a0a555bfa4f879f685f1)) +* **bots/discord:** improve logs ([6abb740](https://github.com/revanced/revanced-helper/commit/6abb7409945c10bd3af451fb45ef4b4d4ebe9489)) +* **bots/discord:** sanitize `BasicDatabase` inputs ([fd76e0a](https://github.com/revanced/revanced-helper/commit/fd76e0af72fe28b414ae3b5e8d3886e58561e57e)) +* **bots/discord:** support nickname decancering ([1723e8c](https://github.com/revanced/revanced-helper/commit/1723e8cacf96e8c6bdee22cfd30e89524fdcef74)) +* **bots/discord:** switch to `drizzle-orm` ([e204b7b](https://github.com/revanced/revanced-helper/commit/e204b7b7566fd7fa423baef32977a8575d44a9e0)) +* **bots/discord:** update config ([197d2ac](https://github.com/revanced/revanced-helper/commit/197d2acea89c38e43858d52736508d449152e804)) +* discord bot scanning messages ([d1bd3b2](https://github.com/revanced/revanced-helper/commit/d1bd3b2b7e4985a64e9b070ab006cc6f3508c46e)) +* **discord-bot:** a way to train AI ([355a508](https://github.com/revanced/revanced-helper/commit/355a50803adc85b5579155b55ddbba4fa0449237)) +* **discord-bot:** command handler and train cmd ([6aee8a4](https://github.com/revanced/revanced-helper/commit/6aee8a4c63eb108800fcb0a23ca61f200d8f1f2a)) +* **discord-bot:** event handler ([0ad5ece](https://github.com/revanced/revanced-helper/commit/0ad5ece08593c0db111fa4a592b42c6e0348fd1c)) +* GODEL AI ([0ba525c](https://github.com/revanced/revanced-helper/commit/0ba525c4a5802106d582c75f713728accf2f151a)) +* initalize discord bot ([bb4a5a7](https://github.com/revanced/revanced-helper/commit/bb4a5a77eefbc7ac88536f73a111df1050b235e7)) +* initialize helper client ([7f9ca77](https://github.com/revanced/revanced-helper/commit/7f9ca77e0331ec143160ee51ed7c3aa9e4e70b9c)) +* message buttons for training ([6551ca9](https://github.com/revanced/revanced-helper/commit/6551ca9dadc2e3ddfe98875e80ed61f7d71a1651)) +* platform specific responses ([18e57b0](https://github.com/revanced/revanced-helper/commit/18e57b0c320732a937bb60db11c5d6794ed11522)) +* prettier and eslint ([1c27ccb](https://github.com/revanced/revanced-helper/commit/1c27ccb17c85f0f6982db45de426181d2c231d0e)) +* refactor and new features ([#7](https://github.com/revanced/revanced-helper/issues/7)) ([8b9f45d](https://github.com/revanced/revanced-helper/commit/8b9f45dc22de29dc2ccb1cfab9a026db00457e25)) +* run bots in one process ([d26d533](https://github.com/revanced/revanced-helper/commit/d26d53317440c64fb775cea609a87d29be6c8b40)) +* training and replies changed ([715aa91](https://github.com/revanced/revanced-helper/commit/715aa918cf84213c9b19591a398d7532eb3f232a)) +* **utils/discord/embeds:** allow adding extra fields for moderation embeds ([49ce9a7](https://github.com/revanced/revanced-helper/commit/49ce9a7ca3d8558b73a9b94dfe7a01d809db6fff)) + + +### BREAKING CHANGES + +* In `@revanced/discord-bot`, its environment variable + `DATABASE_URL` has been renamed to `DATABASE_PATH` + and the `file:` prefix is no longer needed diff --git a/bots/discord/package.json b/bots/discord/package.json index d3ee658..87ba25b 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "0.1.0", + "version": "1.0.0-dev.1", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -44,4 +44,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file From 7dfbf6c92c49100954fa4aca471dce4ab9fd9565 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 23 Jul 2024 00:43:26 +0700 Subject: [PATCH 161/312] fix(apis/websocket): also include tesseract core files in build --- apis/websocket/scripts/build.ts | 8 +++++++- apis/websocket/src/context.ts | 9 ++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apis/websocket/scripts/build.ts b/apis/websocket/scripts/build.ts index 6a2d348..360fa97 100644 --- a/apis/websocket/scripts/build.ts +++ b/apis/websocket/scripts/build.ts @@ -1,9 +1,12 @@ import { createLogger } from '@revanced/bot-shared' -import { cp } from 'fs/promises' +import { cp, rm } from 'fs/promises' async function build(): Promise { const logger = createLogger() + logger.info('Cleaning previous build...') + await rm('./dist', { recursive: true }) + logger.info('Building Tesseract.js worker...') await Bun.build({ entrypoints: ['../../node_modules/tesseract.js/src/worker-script/node/index.js'], @@ -11,6 +14,9 @@ async function build(): Promise { outdir: './dist/worker', }) + logger.info('Copying Tesseract.js WASM...') + await cp('../../node_modules/tesseract.js-core', './dist/worker/core', { recursive: true }) + logger.info('Building WebSocket API...') await Bun.build({ entrypoints: ['./src/index.ts'], diff --git a/apis/websocket/src/context.ts b/apis/websocket/src/context.ts index 27b80a7..0f5301d 100644 --- a/apis/websocket/src/context.ts +++ b/apis/websocket/src/context.ts @@ -53,11 +53,14 @@ export interface WitMessageResponse { }> } -const TesseractWorkerPath = joinPath(import.meta.dir, 'worker', 'index.js') -const TesseractCompiledWorkerExists = await pathExists(TesseractWorkerPath) +const TesseractWorkerDirPath = joinPath(import.meta.dir, 'worker') +const TesseractWorkerPath = joinPath(TesseractWorkerDirPath, 'index.js') +const TesseractCorePath = joinPath(TesseractWorkerDirPath, 'core') export const tesseract = await createTesseractWorker( 'eng', OEM.DEFAULT, - TesseractCompiledWorkerExists ? { workerPath: TesseractWorkerPath } : undefined, + (await pathExists(TesseractWorkerDirPath)) + ? { workerPath: TesseractWorkerPath, corePath: TesseractCorePath } + : undefined, ) From f142c2b82e9076846095515775ab8ec8d81746e3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 22 Jul 2024 17:46:37 +0000 Subject: [PATCH 162/312] chore(release): 1.0.0-dev.2 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.1...@revanced/bot-websocket-api@1.0.0-dev.2) (2024-07-22) ### Bug Fixes * **apis/websocket:** also include tesseract core files in build ([7dfbf6c](https://github.com/revanced/revanced-helper/commit/7dfbf6c92c49100954fa4aca471dce4ab9fd9565)) --- apis/websocket/CHANGELOG.md | 7 +++++++ apis/websocket/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index c11b5ed..747d0b0 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/bot-websocket-api [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.1...@revanced/bot-websocket-api@1.0.0-dev.2) (2024-07-22) + + +### Bug Fixes + +* **apis/websocket:** also include tesseract core files in build ([7dfbf6c](https://github.com/revanced/revanced-helper/commit/7dfbf6c92c49100954fa4aca471dce4ab9fd9565)) + # @revanced/bot-websocket-api 1.0.0-dev.1 (2024-07-22) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 029b382..da32924 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.1", + "version": "1.0.0-dev.2", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { From 89d8ab1ee58278a9a96cdc31c679d0a0a0d865af Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 23 Jul 2024 20:53:56 +0700 Subject: [PATCH 163/312] fix(apis/websocket): build and runtime issues --- apis/websocket/package.json | 2 +- apis/websocket/scripts/build.ts | 44 ++++++++++++++++++--------------- apis/websocket/src/context.ts | 5 +--- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index da32924..681337e 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -37,4 +37,4 @@ "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } -} \ No newline at end of file +} diff --git a/apis/websocket/scripts/build.ts b/apis/websocket/scripts/build.ts index 360fa97..7085b24 100644 --- a/apis/websocket/scripts/build.ts +++ b/apis/websocket/scripts/build.ts @@ -1,29 +1,33 @@ import { createLogger } from '@revanced/bot-shared' import { cp, rm } from 'fs/promises' -async function build(): Promise { - const logger = createLogger() +const logger = createLogger() - logger.info('Cleaning previous build...') - await rm('./dist', { recursive: true }) +logger.info('Cleaning previous build...') +await rm('./dist', { recursive: true }) - logger.info('Building Tesseract.js worker...') - await Bun.build({ - entrypoints: ['../../node_modules/tesseract.js/src/worker-script/node/index.js'], - target: 'bun', - outdir: './dist/worker', - }) +logger.info('Building WebSocket API...') +await Bun.build({ + entrypoints: ['./src/index.ts'], + outdir: './dist', + target: 'bun', + minify: true, + sourcemap: 'external', +}) - logger.info('Copying Tesseract.js WASM...') - await cp('../../node_modules/tesseract.js-core', './dist/worker/core', { recursive: true }) +logger.info('Building Tesseract.js worker...') +await Bun.build({ + entrypoints: ['../../node_modules/tesseract.js/src/worker-script/node/index.js'], + target: 'bun', + outdir: './dist/worker', + minify: true, + sourcemap: 'external', +}) - logger.info('Building WebSocket API...') - await Bun.build({ - entrypoints: ['./src/index.ts'], - outdir: './dist', - target: 'bun', - }) -} +// Tesseract.js is really bad for minification +// It forcefully requires this core module to be present which contains the WASM files +logger.info('Copying Tesseract.js Core...') +await cp('../../node_modules/tesseract.js-core', './dist/node_modules/tesseract.js-core', { recursive: true }) -await build() +logger.info('Copying config...') await cp('config.json', 'dist/config.json') diff --git a/apis/websocket/src/context.ts b/apis/websocket/src/context.ts index 0f5301d..033f473 100644 --- a/apis/websocket/src/context.ts +++ b/apis/websocket/src/context.ts @@ -55,12 +55,9 @@ export interface WitMessageResponse { const TesseractWorkerDirPath = joinPath(import.meta.dir, 'worker') const TesseractWorkerPath = joinPath(TesseractWorkerDirPath, 'index.js') -const TesseractCorePath = joinPath(TesseractWorkerDirPath, 'core') export const tesseract = await createTesseractWorker( 'eng', OEM.DEFAULT, - (await pathExists(TesseractWorkerDirPath)) - ? { workerPath: TesseractWorkerPath, corePath: TesseractCorePath } - : undefined, + (await pathExists(TesseractWorkerDirPath)) ? { workerPath: TesseractWorkerPath } : undefined, ) From ffe0a3ddf5c31a01c362ccce67c7629d0dc3823c Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 23 Jul 2024 20:56:09 +0700 Subject: [PATCH 164/312] chore(bots/discord): change config to a js file --- bots/discord/{config.ts => config.js} | 7 +++++-- bots/discord/tsconfig.json | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) rename bots/discord/{config.ts => config.js} (96%) diff --git a/bots/discord/config.ts b/bots/discord/config.js similarity index 96% rename from bots/discord/config.ts rename to bots/discord/config.js index c387561..fb05c8e 100644 --- a/bots/discord/config.ts +++ b/bots/discord/config.js @@ -1,5 +1,8 @@ -import type { Config } from './config.schema' +// @ts-check +/** + * @type {import('./config.schema').Config} + */ export default { owners: ['USER_ID_HERE'], guilds: ['GUILD_ID_HERE'], @@ -70,4 +73,4 @@ export default { disconnectLimit: 3, disconnectRetryInterval: 10000, }, -} satisfies Config as Config +} diff --git a/bots/discord/tsconfig.json b/bots/discord/tsconfig.json index 890c538..22f6498 100755 --- a/bots/discord/tsconfig.json +++ b/bots/discord/tsconfig.json @@ -9,6 +9,7 @@ "composite": false, "exactOptionalPropertyTypes": false, "esModuleInterop": true, + "allowJs": true, "paths": { "$/*": ["./src/*"], "$constants": ["./src/constants"], From 48346851864c4d4b6276388644dd24ce16222b3e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 23 Jul 2024 20:58:05 +0700 Subject: [PATCH 165/312] feat(bots/discord): don't nest builds in src directory, autogen db when missing --- bots/discord/.gitignore | 1 + bots/discord/Dockerfile | 2 +- bots/discord/docs/3_running_and_deploying.md | 21 ++++---------- bots/discord/drizzle.config.ts | 1 + bots/discord/package.json | 7 ++--- bots/discord/scripts/build.ts | 24 ++++++++++++++++ bots/discord/src/context.ts | 30 +++++++++++++++++++- 7 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 bots/discord/scripts/build.ts diff --git a/bots/discord/.gitignore b/bots/discord/.gitignore index b9c3d9f..f742463 100644 --- a/bots/discord/.gitignore +++ b/bots/discord/.gitignore @@ -181,6 +181,7 @@ config.ts *.db *.sqlite *.sqlite3 +.drizzle # Auto-generated files src/commands/index.ts diff --git a/bots/discord/Dockerfile b/bots/discord/Dockerfile index 96ca47b..f5b2626 100644 --- a/bots/discord/Dockerfile +++ b/bots/discord/Dockerfile @@ -15,4 +15,4 @@ COPY --from=build /build/bots/discord/dist /app USER 1000:1000 -ENTRYPOINT [ "bun", "run", "src/index.js" ] +ENTRYPOINT [ "bun", "run", "index.js" ] diff --git a/bots/discord/docs/3_running_and_deploying.md b/bots/discord/docs/3_running_and_deploying.md index 44f87b8..6532ece 100644 --- a/bots/discord/docs/3_running_and_deploying.md +++ b/bots/discord/docs/3_running_and_deploying.md @@ -29,9 +29,8 @@ The distribution files will be placed inside the `dist` directory. Inside will i To deploy the bot, you'll need to: -1. Replace the `config.ts` file with your own configuration _(optional)_ -2. [Build the bot as seen in the previous step](#-building) -3. Run the `reload-slash-commands` script +1. [Build the bot as seen in the previous step](#-building) +2. Run the `reload-slash-commands` script This is to ensure all commands are registered, so they can be used. **It may take up to 2 hours until **global** commands are updated. This is a Discord limitation.** @@ -40,7 +39,7 @@ To deploy the bot, you'll need to: bun run scripts/reload-slash-commands.ts ``` -4. Copy contents of the `dist` directory +3. Copy contents of the `dist` directory ```sh # For instance, we'll copy them both to /usr/src/discord-bot @@ -48,22 +47,14 @@ To deploy the bot, you'll need to: cp -R ./dist/* /usr/src/discord-bot ``` -5. Copy the empty database (or use your own existing database) - - ```sh - # By default, the build script creates the database called "db.sqlite3" - # Unless you specify otherwise via the "DATABASE_PATH" environment variable - cp ./db.sqlite3 /usr/src/discord-bot - ``` - -6. Configure environment variables +4. Configure environment variables As seen in [`.env.example`](../.env.example). You can also optionally use a `.env` file which **Bun will automatically load**. -7. Finally, run the bot +5. Finally, run the bot ```sh cd /usr/src/discord-bot - bun run src/index.js + bun run index.js ``` ## ⏭️ What's next diff --git a/bots/discord/drizzle.config.ts b/bots/discord/drizzle.config.ts index d9a9a69..bebe08d 100644 --- a/bots/discord/drizzle.config.ts +++ b/bots/discord/drizzle.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'drizzle-kit' export default defineConfig({ dialect: 'sqlite', schema: './src/database/schemas.ts', + out: './.drizzle', dbCredentials: { url: process.env['DATABASE_PATH'] ? `file:./${process.env['DATABASE_PATH']}` : 'file:./db.sqlite3', }, diff --git a/bots/discord/package.json b/bots/discord/package.json index 87ba25b..42fc321 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -9,10 +9,9 @@ "register": "bun run scripts/reload-slash-commands.ts", "start": "bun prepare && bun run src/index.ts", "dev": "bun prepare && bun --watch src/index.ts", - "build:config": "bun build config.ts --outdir=dist", - "build": "bun prepare && bun build:config && bun build src/index.ts -e ./config.js --target=bun --outdir=dist/src", + "build": "bun prepare && bun run scripts/build.ts", "watch": "bun dev", - "prepare": "bun run scripts/generate-indexes.ts && drizzle-kit push" + "prepare": "bun run scripts/generate-indexes.ts && drizzle-kit generate --name=schema" }, "repository": { "type": "git", @@ -44,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} \ No newline at end of file +} diff --git a/bots/discord/scripts/build.ts b/bots/discord/scripts/build.ts new file mode 100644 index 0000000..58e81c0 --- /dev/null +++ b/bots/discord/scripts/build.ts @@ -0,0 +1,24 @@ +import { createLogger } from '@revanced/bot-shared' +import { cp, rm } from 'fs/promises' + +const logger = createLogger() + +logger.warn('Cleaning previous build...') +await rm('./dist', { recursive: true }) +await rm('./.drizzle', { recursive: true }) + +logger.info('Building bot...') +await Bun.build({ + entrypoints: ['./src/index.ts'], + outdir: './dist', + target: 'bun', + external: ['./config.js'], + minify: true, + sourcemap: 'external', +}) + +logger.info('Copying config...') +await cp('config.js', 'dist/config.js') + +logger.info('Copying database schema...') +await cp('.drizzle', 'dist/.drizzle', { recursive: true }) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index a4806ee..74256be 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -1,4 +1,6 @@ import { Database } from 'bun:sqlite' +import { existsSync, readFileSync, readdirSync } from 'fs' +import { join } from 'path' import { Client as APIClient } from '@revanced/bot-api' import { createLogger } from '@revanced/bot-shared' import { ActivityType, Client as DiscordClient, Partials } from 'discord.js' @@ -29,7 +31,33 @@ export const api = { disconnectCount: 0, } -const db = new Database(process.env['DATABASE_PATH']) +const DatabasePath = process.env['DATABASE_PATH'] +const DatabaseSchemaDir = join(import.meta.dir, '.drizzle') + +let dbSchemaFileName: string | undefined + +if (DatabasePath && !existsSync(DatabasePath)) { + logger.warn('Database file not found, trying to create from schema...') + + try { + const file = readdirSync(DatabaseSchemaDir, { withFileTypes: true }) + .filter(file => file.isFile() && file.name.endsWith('.sql')) + .sort() + .at(-1) + + if (!file) throw new Error('No schema file found') + + dbSchemaFileName = file.name + logger.debug(`Using schema file: ${dbSchemaFileName}`) + } catch (e) { + logger.fatal('Could not create database from schema, check if the schema file exists and is accessible') + logger.fatal(e) + process.exit(1) + } +} + +const db = new Database(DatabasePath) +if (dbSchemaFileName) db.run(readFileSync(join(DatabaseSchemaDir, dbSchemaFileName)).toString()) export const database = drizzle(db, { schema: schemas, From 94d0dcc32bc9b1685602d530db0268b65fbeca06 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 23 Jul 2024 21:00:18 +0700 Subject: [PATCH 166/312] fix(packages/api): misleading errors being thrown --- packages/api/src/classes/ClientWebSocket.ts | 35 ++++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index 2383868..2df482d 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -53,23 +53,34 @@ export class ClientWebSocketManager { } }, this.timeout) - this.#socket.on('open', () => { - this.disconnected = false - clearTimeout(timeout) - this.#listen() - rs() - }) - - this.#socket.on('error', err => { - clearTimeout(timeout) + const errorBeforeReadyHandler = (err: Error) => { + cleanup() throw err - }) + } - this.#socket.on('close', (code, reason) => { + const closeBeforeReadyHandler = (code: number, reason: Buffer) => { clearTimeout(timeout) this._handleDisconnect(code, reason.toString()) throw new Error('WebSocket connection closed before ready') - }) + } + + const readyHandler = () => { + this.disconnected = false + cleanup() + this.#listen() + rs() + } + + const cleanup = () => { + this.#socket.off('open', readyHandler) + this.#socket.off('close', closeBeforeReadyHandler) + this.#socket.off('error', errorBeforeReadyHandler) + clearTimeout(timeout) + } + + this.#socket.on('open', readyHandler) + this.#socket.on('error', errorBeforeReadyHandler) + this.#socket.on('close', closeBeforeReadyHandler) } catch (e) { rj(e) } From 12f5aaf70f1aee02215b674bb6402ece4280fc9e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 23 Jul 2024 21:00:53 +0700 Subject: [PATCH 167/312] chore: update dependencies --- bun.lockb | Bin 286056 -> 286456 bytes package.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bun.lockb b/bun.lockb index afde74b2f96f60e2c3aec010c5a8b85c2bc0f186..575d723348cbf830c2c372e147c365a2368f2d2f 100755 GIT binary patch delta 28989 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 YI*>gAU+EkD$mb!iVMJc8U02M0%AOHXW delta 28880 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 P!QY%1p!FFce>;)P!QY#1;H&y8yX~V z0wkb&8`6fp4GMzWpdbJV1W(tz0}6sWpdh#dX+whqZh!=A??T$pcR@+wE+|QW1R|$f z-UB6xd!Qt757LGP3A_La_}+)Kq3<(>GsaE-2%_Srdp=+cXH1yB5JV+T{|Ta!rUyP` z3C-tMGlnx}Oiu(+nV>KUpT6TUBhPf1CyYLz zM)MO;06YN&z!OGq#@y+WPeB3j6chkYA&q8`z=@}fJkxcaG5UZS&Cfsq@C+0H<TK zi>GTo2L-@$PyjrKG@3yIH$VcmFCdNP7oY%m0SW++K;?AHm!JT62?~IhkVZ2|-~~v) z_Z6hk{E9K0v2OZD5LG|j^EG2QW5e`?AgXctPY~5KJ@5@v!}lVQFEp{eq{^?HHAUcyy+i7)con5-x$Lg7ffFWq83j7 z38EHF5B$y;4r&2|s3p@Ge=vrFn!g}w+4PMdYWZ}|pN!#*E2bxcsFl-qf~Zx~1%EMy zgIc}6KoR^46v4k3y&2a|m;4Qi;NPGK{tapMf&@;01a$vETD^Zj-u(meE=XYWbj`mY z@BRgO_b;T?3lg{i60rRT8Fu@}7|yt3`a=-4bGqe!Q0)E(#qNJdllMPkIOCqFj!fZ< zd#BE13TNCm^&?X_`IwvM_~D=U`#tou13W1RiGt3G4s~$g(ndgT~odnZl=M zurl#ZKMN8#J6)2EDSWyF8x!yJS~e!|I2%ad1RE3Yc3pO+_q@{!w3uurKhR~FULeZE z!Fte?fq`$kqBxU2BS?_947wY7iWLKc;B-ZCCeR`c7DL!>FR(CZ7?pv60k%<^+Xk|w zfeEy&H9aRYtum{)aJ#HHlQq+H1yLp+kc~Yc!#Tm5^cWZzHf*<*VhUzt{p8QUz_UHk zkSU*Odw~|y7qAni|B+|nV9kkVVBiATpAOQ=m6xBAs+(R}@FHP)odS~@Yjq+612;%| zJ4jkKu_!6OD6_bPKLxTk0pt{}RLK4V{^^a1OxBEGN*`p61H>8wWhM^RZP`$3szK)P z!ggaO=Q1!zKpX~geE>*udXE|t2V?p6i6%_`%pd_y*k(MI=^r(jl96n1VFJldKcmBB z!wL?$>4myX#*;VbYJkKU%cke*GFdZj-o8?osh){ZZMvoblR7JSml;?g$jTESm0Yl` zW7pR99HWf^LE=8ZfELZ0||yRg2+w$AaXquSHMY7 zcrq}gT%Im+iph3+$SEd4F3!sk!5I&>=Urja=H+|<;WvDpo_B>w&9#7ufgzKLf#DDn z1A{vg1A_w-1A{pe1A`$G0|N&W0|P4)1H&Cg28P>=3=Fpz85qtnGBBKFWMDWBY8Epx zFn~Ia+otnAVp12~%E-Xb!N|bS$;iNPiGhJ(F9QR^&h4I$nDm+JcQ7z8Y++zv*u=oV zupX2s8NhRwpv{vE8$n5pfq`K&0|Uc01_p+$3=9n085kILGcYjhVqjp{!@$6BmVtrc zC<6n-5m1MRfq? zX`f+WU^oWhFc?D(I?ceqaEgI};UohC!wCikhT{wj4Cfda7%nm}Fq~&#V7R~l@epWp z-2?^(22hs(WKjzv14A<-1H%&r28M?W;8NSINl0P{GK+P|nD}P|C=_ zP{PQ-P|V1{pw9^2KF3hR$iPs@$iPs*$iNWJ$iR@#$iR@t$iR@z$iQI3$iR@r2wvyF zz|P3Pkjcov;LXUu-~|$6WMGg2IYfq$fq|8gfx(rLfx(55fx(uMfq{*YfkBXwfkA+g zfq|cqfq{pSfq{vUf#E*`1H(TC28I_5;He~r=L`%C&lngOo-#l}0px*K3=9k}85kH| zL&ZURLEkYjF#Kg;VED`c@g&ISpBNYzK7isM#Bpa}VED?w!0?5Ef#D|ucpnzScLwmz zHHL2t;H=8<2Py{=|G~fjmjBJb!0?NKfnhEK1H)tn@XS4EJqCEpfRT}bfdv{pZcxWK zFfcHHLK~FMKqDc>p!f#~fE>UH8XAFy2FRx%MWA2afq|D15)z zIHX(?%LvIapqvrI2+19OjL@6|ihmzQ1_ob7NR|O5G?0Tpu7^1Wl)XUF0g4Kc7$~Sg zjtqjPa8PuBhH*e?0^}%|gFsmrID(-9$dVuh z5sVBBkWEmuGu1C^Mdr~`QpR0=XP zGSoAGf)!MtfPx1k0Wt`bZ$Uu{@;S)YAaSS$29U2oz6ALWl%GK~j1STa;`2hwjxuOL z1`1J-8c^v6G8ZQ94T^tIVgV^YwgluTWDOw4fuf)mT9$)KI+%P7BLf2{cwvS%FfuSS zLW^h6IAa?l0|N+y67LE|28Jz+3=EqY85ov>YGqLN0M%lkyt$N-fnf?#zU>2*>kJGG zy^IVD-HZ$jU7&o=z`)QmUGXiGaD6``1H%$V28J1o3=Gp585pK9GB8YK1TQLJSj@=4 zFqe^mVHP6;!%UF-7#J94GcqvDVPs%fz{tQbkCB04J|hEI-9knNhDD4F49gf97*;bf zFl=IEU;w#uBO?RD21W)3&_Lc=sN5<>28NZ43=C@+8T`PC*E2FOz$8HuAOk=&NE3_= zqCsk427~w@3s9O4)BnC@5*OXh$iT3Zk%3_cBLl-8Mh1pmj0_CBr|Z6BlCK91ScBRC zCm0zRPBStvoMdERIK{}oa0W`>WMp6f6*#bvyvWGFZ~^M|^U#pH&d9)Ujgf(&9a?8x z1$hV54q#+pxD09>fr0|mK44^EKsN6pBZC*%Fi;DLlK~;w3bh1T4CDZq<#(YuXBh){ zPc8!s69WVDbiwyb%Joc43=FS8Ze{?l#9&}#0xyAMC<8Ul7$CJas1fy_k%0j;)Cp?9 zy#bXHj0_C-pvHpQJfOxFsG$Wi3#9%bs2XBqV0ggDz;GYxZ&0{_+E`B*85kZziaI5R zM~n;%AjL00Wf4>YB=#IsMlmulSWmzDo=LtQq~s}*S)kMi@**guf>JCjWrE!Dmyvv^VEE0*!0;C8o}W;4KNuMpzB4i~d<9hy&?NQ&RMtb;UqA)KCq@Q_ z&x{NV-;fl&hbjQkAkCm81!DhVWMBXp4oYeuhk)20k+SNrD=ApiSN3pxFZ^@ESbOhE&jo28Ld=W;G~t{r~^}Kgg1$ zpu@D77#NO$Y9}U0V-b{!4VV}hRG1hTgqau^IGGq2K>G-mfhq_t1_lNmm;fjxF)=WJ zTB6)ckmv-(8ZQ$A13wc310NHl1Q1|iU=U3uonHU%p zm>3x3m>3x3nHU)KnHU)Km>3vTq3Sf47#P%<7#P%`d{8KW%++OLV9;S=V9;h_V9;V> zV99NJ9`gQyVieFkAw)%b6G$ z%$OJ$Kxyd{3vbnHU&cm>3v9J_ecV%*4RpH2vo%CTSN(CP#KpB7{kQC;K{_m;K9VeFg@`zQ}XnJ z&rF>4(M${s5ljpWkx(rG(4Yf(-5Y9@FB1cU4-*4}7pN#;VqgejVqgelVqk!&1*!9g z%7cnFP|$nntsk7#Omc7#Q+Ed?rX!E(eqpL46QtZ#4>(QlL(s1WH~^ z3=Abq3=HK=3=E}A3=CyV3=CCF3=9=a3=EY_kRILyCI*InCI*H+CI*IHCI*HcCI*IX zCI*HsCWh(qUzsd9t3kodz`!tRdg526Xy3_93=BJ%7#KD)F)(arVqn48Gbl8j~3rwcHrGftfTUx2xh>6Yd6 zCPC&jY1pxP^UC(A;>^$bDYgPqpLXL#|+&LbvqU>O5FLk5Q5j?>qGO!An1RFv75>8SH`5iw>T zrVGy7lf;-6n51FnZYlpQlhihQ?xne)9G{e`Oj?rQQ z`2!rLS6!w*&}Wuly6-amjRdoV4D5_Ap8ZcPpYD9g2o8NiP>_6fnXVwoEMW>e6-?T5 zy?N@&{F5MO8t575nKHo64ST!cg>LWyBYkk#8|WE>#sqUEnI)xR2abi_e5frX?_I^j zSZAPT1oFG;^o5ell8gq^cS|xGGg?o7FUc%v>IOZxOz-8D>{Q`Nf5C16dkc25nV6lw zZrw^IP#Q6ZcsOpltrWAQH0%hpILl43`K?!KW)+GB8w3FO*`IWNe+@4WcGb z*EL|4oW4(r*@SW7^h5(@NyZJ+d8L^p8TU^=Xw57s13P1`%0Q?00<*s}6JwkyBsJc5 zogO32EWz}_b^1FUW-(?%2FB_A(#(>ku;T?Uw$+xh+8r-|+HJ_d06W-j(iJIZC9_!* z!RZYg!zR=3f-H5Nz8>U+@acjw%#w_m(+f?Q#bpf{V22EzZtA_;yi!mH6y$owpm6J* z9uHEvV7j3pvlxpZ1HHlS!B~9a?$32DzUh$3nbn+qCo#15l5qe~!q>xZfh%)~bZ~_FU zddBI#a?Fy9kc2wDL5^99k!$*FkdVUkt#ZuTj9Jqk$}uZSd-;GzNE+s?n)F&s^inG{ zF&Hs0gin{1XEql31wA|R$FJ&)LlIM!GBG~(nVurgtRO7!3mIxoi9CF09{&PXCPuF5 z)8&~Jg&ln%W*TyRe68gs!otL;JN>jg^I}Ga>G=xGTNzhP-)+n+Io(c?*#s0UAg@mE zQDol6s62h48aTPeDltnk3Qe~)VU}b%?lwI~hgo9!DkWwqNMaIaHedk9r3f216d<8C z-Ab2Pbh^4Sb1>50#*fELCPBMvdunRhcD)VTV=zagGg6Jy@=ld8;}(y+rLr2}sTym+-t4QvHC z`6f-zRb!Tvh8IhInQ-JT? z!i{qU1EG1&l!2jix}yfOG1K43>17(sMi5OR)AwpH%R|_b)4ypjZ)1X#gQ9W<3=FU{ zFJHf%J|+8m4ih-7g5wY3Y^Hb7)Bk8POGy8P9-jG3+5Fz(J?9rgy=w@nYxK34jb&y- zPtgonR<%y9Mq>#RW1I=3@OTn4y-tfcP3AoGI9iTQXZHJx7uiCMFkoP~Wj;-tc?+Y# z^sU;=jf^vGO4%eW4}L7AA9XKAaG^|r$u9k z-I7p8OXDsNk&8PKHgH7%Df5^O8EU8hHDH#Mh8>}4;M(F`@v$KjoaVqaFu2Ty*4xtm3s&Oht2wEYEE3{IesB2WTSkV#6z4(d$mZ8E)dE|CMO5MI7X!;TL9 z{omz#+RE2X;M4~$RJo@kmy*)3Ge@l*A3vY4VBT+V1cJk&W4gZ)vm_%V&qJx{bBvfX z;6;g?G4mW~86%B5(Zx)EZ_F$SD{{b<6sTN*6gtwdlT=?{{=ZjM<@yI`u?Q+TK(z-- z_`pgirWYyG5124ZNW%_7O)psgGJ5I(P`v_bH<*Ch4d8NLb2_gnvm~SC^oM55l8o-t z9YMVK>CqqxQqwZ#PVWctpmN9>MA*RL0F`l{E@;MV%=9*6x|`i4pE(z z9P;VST#UX z^7MN)%zn@ejHZrpdbKUHBq*+@*)khJ&1NxV_)|3fi7j&)W8!pgJ7!5JHNDP`S;`c4 zekSw!G?8-EDO`wZ2zJ8e{F!E7mt57@1IjRl;AomP{Vd3s_0yl*G0%n;-b`mBrq8oy zmS8$rI(>&d^A@I$Wz+Kzsk8g`T`*W8D8Or>}EnmSFl+JKfKfS%R@(y0$a3q_ipY zaMp#J9M9g@J}HYxZywX*otY&WBc`8rVV0DEobt-hkiUr2c)$N@P}u{@bcPHJP=lpm z=fh@+)=wzfCN~FMz8Zj>&_Del$fU~Yznz&SVGdzTovu&N;5(tr5{&)RH@YC{gK(kl zf|~}h1IESWTmuFMg#Tx|GD}Lsj!kWv^M6Jfb3`O4;EeT5^$Zvarh^rb)Uh+3Zsx|E z!MJkzd^cuErVmZiceydAG47qN=*}!@TG|3ReQ_FR|UUA7yVO2X3iVj1My!Gtk<+Hn)ErpPQ9g3^Y z7SnNS?*2On8Q8J8tlQEHO{(K2A!L}Re{^RSWd&IY=`Kpc4$G~6lYX{C=(Q3PBdG6Z zXwJYeVYx>Na42|?GVCU*`=Gz>56*y}*6JwpJo{^cJ z3B%0ky&lYxO!qprukc__U}gH=yPeCA`4S@|xN9uQ^u2fb4}WGO#^~wigTVdfgGtPi zjCRxG1DGWl6{c4PFdH*M+{Xy%BQqLKe;B}=4%HwHJB7Dk8vhHPoR(Ffh}AO&b*cAG zuMK4OW#pWGI*?hLYt}^Y*iplSkZD28=b*Y7%cg^^gV;0OESPy4s8o0t49+<0AWfuAs~6Fqzwy)`*I&cKTkB0&M9G)O!b|H||jE84}w1@|bQP z3eJ%C!yvt;jOnpqh>U?P0~$dZ-Tl)KhC&pAGnD@Huc6Go(y()yk7=A!7YuLS0d0L6 zGB6+v2CGNY4&gFJOuvt0J0n!Ia=LCfv!o1mZ-UxNi2PR_jwAm;YSN79>%*BPVda%H zdMPi!XlDyrtiZqkDT$%wGWJ4ydR+u_6Snb=^zE-AndKNkg*K>^oURnj?8oRoy)c^D zm~sB}r63BDP^GhHF@TTVclpAp;GT*lxI0@roi~PAQW|zNa=>w$Da$_1$^a!e zOK|wlo$eUJED14Ia{8SZW~u3YG0alZuVz6Cgk+O#6;|iN*&sSVZFaB=AWE73&)WVY zhFOMD8g>S={&Kqm&svjeLCV3E8AI`O{WxYxQ`oV|bC0Sm*Xlg;6r=z&eqg`=JC2#5 z_uJv+PQPzJWIzoas7)Hl%u>@g#4$@T9-4kS4qn+Y-Jd%xo>_wF_1x)F@yteyGpGB; zGy5_^?1OrUF?IU+cxGe9(&@|z%)X5N(*qNjjb&g*IZK|H$a%MQ!)kDCUD7YiyAqgv%&tQYV{TB{5WPQW@3RXKw}I*|*m2E`_g<&^U$&Hi$QbAuGyGdL z-6oM)0_Fn0>G_GwlBTd@p|9K8M2nrZI|Ng2zyLcq+LkN+!LwVe8VDJv|3Q%gGc8~` zcM`LtH05v_DRf=rm)kd%|ch7U;j|t9jw3vG`;~lkGi=r z(n06#Ymjv&df;9y#5!!Fg`klj0|o|Y!2?k@{Yw(F6x3PMHIkV#7#X)uOlCG`WR#qK zGKJY!8g}CJnpz|Oj^*mmdYjr&RwN}!QQ3s5s--E`#) z=0+LVq1kU+gUdVxZ%hU$FwrwK)iXZ6Zu)@?<`<@d>%nWB8g}!})(k)K$XSF@3p6dn zz`%fB(;;M}rr*tEmXuaq4_S04C8{des2qGogwf8{&;q2}aJpa?vm|5TbnPr=Nol+F zkinZzS$>lg+8-Z4800fOK8sn>G-^Eq_^y$?XSTX=%yf%JD9Bt7UQE+a7NWzddakt( zWQBpAg`NRJ<@B{6v%A+rPA_(K_2{V)Vab3f0Hw_N(;sFrOEPU)KmAV@vxLma^^o#q z(T_X>mJ?FB2s`erpKg%NEMfX>Jp=e^6;FPKNdamb3!z4V&OzS*Ipe%*|4xD1cOJKb zWI(g-3=HbiYqObs8SAE>&SsWmTsNINpIMTzd%9vCv!p3zt_KY~gY?0UWM5n$a=fg) z+lYzLL=WU-28R0Sfw|0*(&+IDP8r~Moi)8Nhgp(w?ey6>%#zZuv(Q)HOn;U8qg$Q{ zlt93_4r0*s7dgx(Q1?hr) zj6ZW8oq4nt)Y>)FGX#~)5RWoK?2?`ZJ%Ig0)Uzj1=i>f>OF2_e23$KmKaW{b8gjxq zLql@Zj*EWcEfc`LG6kj9sndJ&nI)(1$zwKQoG|@k9(q_ptzab5AnEF@kakQ)@w)%N z_*Z9&Kt^p085mlpAIxW#Wcs^x`kj1c32EhRkdyx#r}8_nugVKyVKjzxM;KhE^A|8n zn!%1~|9;}!nPByX4uk^OLGCfB2c9MOy;+Ws*}QFfTmiF$H0T2|d-r)RLcyu+ z(?trIXE7>FUth@V%Q#{B-$G_dMuzF)Ma;8hU}vaj%6ge?zVRD00s?8xGo0Hu{ag{V zgf#5jc)xv{?91x^X@aB77!)I}(;17IB^f79mo8>DW;B}aU(75i4Ler8<^R@sza>QT zz!3;a2n^uK7BSH1vh4J^AdULd9m|+S*evvn3>X}zUjzvSPk#+E1a<_xfzzz`?QHx^ zEQ|(vhG2CS)8$K;B~4*x#&@u+$%q%Usz4}!oypE2Xs5k{ampEl%*N@tCCrj$uw&>Y ze*H2GI%SrMP;e7^F#REhckXI0nH&%@Zx2r2Q^G7E4LjOiOGv|*vHS5tgbLB=??LW@ zorOQy`i*i{@2n1l0@!K!C%!ev*DML0jF1VOZd=MM$(T0%VL2=`YNi*0geFbzE@hUK zft|>old$OjwTU|&5nA^hntq^^*@ZLtB&2{|GM%@KSyCEuLOw%7=1!XxCTzLSz#1&| z42>ATK`zM%4sDU?`DM(k(y(Lp&3w*mTR3S;6+&_9^!_qtNmJOl{EJrBWxeB2y@XJJ zH5S13%$t6#XO=XD-8YaV_J@;SUgR4>0oGUm8*V#&Z8@`~H1rOGhI!HkyDq;>jzVY!$ATmy zI08hb^H(slO2aNfSnZY>TV$_32cdYybo~luNv3^=rh8Q|8!?@{FuksVS;7o<%D%q# z##G<;b)Y#613hz4I|p_S|Ld3CS|YK{y-c8)NU+T73)9b3FiS|oPVC?OcBaIf6<Hk5NK`oIff!+l$;oH&$yJVT05ZQ9q#p!;P%o1j>+XC#Dz5HV&m-iK{ z0z9S)yH9{u&&28BR~OJQI;hKJXuxph;`BL{%o5VDiv^0jcD~cOp$lq_8bX@R_0!K+ zG8;omU@=gq_vD4?a#hSm(y*%lGJe)Hu-I>PhYaw6E>~lpo?XQ(DGj?kAW%rRlK(=s z9Vi@)^$aZ;7}TcEuVR)oh21ug%gA}HeM<5IgaX)o1o~wUCU9O;T#JxNo&FYNHtYg} za^*z=_j_t`5DF$uSFC21L@D)*85^c|Rx=wjnoQqY&FstAFrB*wJo}|u!)$DdC164Q zO-lv_*rf$?Ru)yf>|WLejy+K8nqmF)-Wp~}8Q3icYV3AJPTN08fyahGod$->H>Mw| zVK!o%HT`Q1v#%=bnu8@;_qt0xH9`I{y?V0}!FZ znGp@q(lNa{nniW`w-^>Bt_x7H4`3bA(`{o}yty@?*9lt0uuRXt!lXKVM=Xm3R{>PC zA_iom%JhG+EL>a%pn^AIrVGWf*hvK3VPMdKtm+5Hi*cK$@z;iwKhqPnm?i)K&WahP diff --git a/package.json b/package.json index 82e9a39..7036be3 100644 --- a/package.json +++ b/package.json @@ -41,11 +41,11 @@ "@tsconfig/strictest": "^2.0.5", "@types/bun": "^1.1.6", "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.7.4", + "lefthook": "^1.7.5", "portainer-service-webhook": "https://github.com/newarifrh/portainer-service-webhook#v1", "semantic-release": "^24.0.0", "turbo": "^2.0.9", - "typescript": "^5.5.3" + "typescript": "^5.5.4" }, "trustedDependencies": [ "@biomejs/biome", From 5db076aee5477c9ef954114d3558c5d41352d0cc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 Jul 2024 14:05:57 +0000 Subject: [PATCH 168/312] chore(release): 1.0.0-dev.3 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.2...@revanced/bot-websocket-api@1.0.0-dev.3) (2024-07-23) ### Bug Fixes * **apis/websocket:** build and runtime issues ([89d8ab1](https://github.com/revanced/revanced-helper/commit/89d8ab1ee58278a9a96cdc31c679d0a0a0d865af)) --- apis/websocket/CHANGELOG.md | 7 +++++++ apis/websocket/package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index 747d0b0..708405c 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/bot-websocket-api [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.2...@revanced/bot-websocket-api@1.0.0-dev.3) (2024-07-23) + + +### Bug Fixes + +* **apis/websocket:** build and runtime issues ([89d8ab1](https://github.com/revanced/revanced-helper/commit/89d8ab1ee58278a9a96cdc31c679d0a0a0d865af)) + # @revanced/bot-websocket-api [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.1...@revanced/bot-websocket-api@1.0.0-dev.2) (2024-07-22) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 681337e..e7c79f5 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.2", + "version": "1.0.0-dev.3", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { @@ -37,4 +37,4 @@ "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } -} +} \ No newline at end of file From 6a87464b40e34903241bf93d838fd72300f792b3 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 23 Jul 2024 21:14:52 +0700 Subject: [PATCH 169/312] chore(bots/discord): fix build script oversight --- bots/discord/scripts/build.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bots/discord/scripts/build.ts b/bots/discord/scripts/build.ts index 58e81c0..d1c4598 100644 --- a/bots/discord/scripts/build.ts +++ b/bots/discord/scripts/build.ts @@ -1,11 +1,10 @@ import { createLogger } from '@revanced/bot-shared' -import { cp, rm } from 'fs/promises' +import { cp, rename, rm } from 'fs/promises' const logger = createLogger() logger.warn('Cleaning previous build...') await rm('./dist', { recursive: true }) -await rm('./.drizzle', { recursive: true }) logger.info('Building bot...') await Bun.build({ @@ -21,4 +20,4 @@ logger.info('Copying config...') await cp('config.js', 'dist/config.js') logger.info('Copying database schema...') -await cp('.drizzle', 'dist/.drizzle', { recursive: true }) +await rename('.drizzle', 'dist/.drizzle') From 3117af54974daa2e266100f0044f0b32299a84a2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 Jul 2024 14:16:41 +0000 Subject: [PATCH 170/312] chore(release): 1.0.0-dev.2 [skip ci] # @revanced/discord-bot [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.1...@revanced/discord-bot@1.0.0-dev.2) (2024-07-23) ### Features * **bots/discord:** don't nest builds in src directory, autogen db when missing ([4834685](https://github.com/revanced/revanced-helper/commit/48346851864c4d4b6276388644dd24ce16222b3e)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index e07314f..2d58651 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.1...@revanced/discord-bot@1.0.0-dev.2) (2024-07-23) + + +### Features + +* **bots/discord:** don't nest builds in src directory, autogen db when missing ([4834685](https://github.com/revanced/revanced-helper/commit/48346851864c4d4b6276388644dd24ce16222b3e)) + # @revanced/discord-bot 1.0.0-dev.1 (2024-07-22) diff --git a/bots/discord/package.json b/bots/discord/package.json index 42fc321..143c03e 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.1", + "version": "1.0.0-dev.2", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file From 38e00eb4e59c763bd74d27b9b9b482ea66e4dcf4 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 23 Jul 2024 22:38:23 +0700 Subject: [PATCH 171/312] fix(apis/websocket): hardcoded paths in tesseract worker builds --- apis/websocket/scripts/build.ts | 1 + bun.lockb | Bin 286456 -> 286808 bytes 2 files changed, 1 insertion(+) diff --git a/apis/websocket/scripts/build.ts b/apis/websocket/scripts/build.ts index 7085b24..36bfa14 100644 --- a/apis/websocket/scripts/build.ts +++ b/apis/websocket/scripts/build.ts @@ -18,6 +18,7 @@ await Bun.build({ logger.info('Building Tesseract.js worker...') await Bun.build({ entrypoints: ['../../node_modules/tesseract.js/src/worker-script/node/index.js'], + external: ['tesseract.js-core/*'], target: 'bun', outdir: './dist/worker', minify: true, diff --git a/bun.lockb b/bun.lockb index 575d723348cbf830c2c372e147c365a2368f2d2f..f72cc98b3cc16347d64b6faf4de723669f88e457 100755 GIT binary patch delta 653 zcmeydSMbIG!3lnxC*oNcK;ZJkaCs*7(;F*hh;gtnF)&CnFf?oy5T78%G5gHM8tf$s2T8Hmf}95t}^W2MZ(PX4yxRq0H^J{EWswI2jrK@&99F zV7N40@foB4bo&??oqL(0|ZBBz)fwg;YK65-;!3K5*~XnWojCT(8MM-YC?_vv|8nAEnPc*wLvgl`8U z1H+E*5ShBV>0RHL)R-9<7`Ctf#&C>1Y5a9_s?%50G9TdDQ4f*YAG5uvj`_o#u)O?~RE5mE;*!Li9KEb!Ft<3l zD6^oXSic~@xCBiKRS4`Q1_p+5|03_=fJA48SV#;iOurbzqCS0oG>al<22@OAx?miO z==6KhEc1Ab85kIvwl^EERQmU3`v1vH3R)?tWqL*o3=Cm2Sm*C7UpNJ3GK`LhgP2?q zH@zmBMP>T87#1bY15m*SaHH9$+s3kZb4x%|l14ns^!zJKs?&GGvPf_xKt(g+A!huG SW#Qu502MqDKV2w}oX*(mM$@GGsOzDiq+i(12>S^I#QOgWAKckL$x^^s!)bz!5 z%&JTb4AW24F&_}0Q3sJtiiU(gmrbdKor2-?1>cz@wr{UzK7B_Dteb&>q1?a7yEq`x zc>z@Ef#~U0@hmFSVp$ZpIAS2$PsB{uie=fvW5~e3Q2lY=_Ftzi`c3bNWl_*dNiEYu z7KND#qklk6kcb7Dsy00$jzxhhAr@k6LoCQx>FIOhSiHG+Km||4vP{pv%A`77BA!Kp jlL4B5IHnuMv8Yc^h-cy841kJdOmB>15uM%^&vFR><4C#o From 0d4898dae8b26f8466d3f6b8f62875866f581644 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 23 Jul 2024 22:41:15 +0700 Subject: [PATCH 172/312] fix(bots/discord): revert dist denesting, fixes config not found --- bots/discord/Dockerfile | 2 +- bots/discord/docs/3_running_and_deploying.md | 2 +- bots/discord/scripts/build.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bots/discord/Dockerfile b/bots/discord/Dockerfile index f5b2626..96ca47b 100644 --- a/bots/discord/Dockerfile +++ b/bots/discord/Dockerfile @@ -15,4 +15,4 @@ COPY --from=build /build/bots/discord/dist /app USER 1000:1000 -ENTRYPOINT [ "bun", "run", "index.js" ] +ENTRYPOINT [ "bun", "run", "src/index.js" ] diff --git a/bots/discord/docs/3_running_and_deploying.md b/bots/discord/docs/3_running_and_deploying.md index 6532ece..05a18e6 100644 --- a/bots/discord/docs/3_running_and_deploying.md +++ b/bots/discord/docs/3_running_and_deploying.md @@ -54,7 +54,7 @@ To deploy the bot, you'll need to: ```sh cd /usr/src/discord-bot - bun run index.js + bun run src/index.js ``` ## ⏭️ What's next diff --git a/bots/discord/scripts/build.ts b/bots/discord/scripts/build.ts index d1c4598..9fb15d6 100644 --- a/bots/discord/scripts/build.ts +++ b/bots/discord/scripts/build.ts @@ -9,7 +9,7 @@ await rm('./dist', { recursive: true }) logger.info('Building bot...') await Bun.build({ entrypoints: ['./src/index.ts'], - outdir: './dist', + outdir: './dist/src', target: 'bun', external: ['./config.js'], minify: true, From 164570d17612be74062cc615a1cf191ebd108f8b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 Jul 2024 15:42:10 +0000 Subject: [PATCH 173/312] chore(release): 1.0.0-dev.4 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.3...@revanced/bot-websocket-api@1.0.0-dev.4) (2024-07-23) ### Bug Fixes * **apis/websocket:** hardcoded paths in tesseract worker builds ([38e00eb](https://github.com/revanced/revanced-helper/commit/38e00eb4e59c763bd74d27b9b9b482ea66e4dcf4)) --- apis/websocket/CHANGELOG.md | 7 +++++++ apis/websocket/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index 708405c..8bd5402 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/bot-websocket-api [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.3...@revanced/bot-websocket-api@1.0.0-dev.4) (2024-07-23) + + +### Bug Fixes + +* **apis/websocket:** hardcoded paths in tesseract worker builds ([38e00eb](https://github.com/revanced/revanced-helper/commit/38e00eb4e59c763bd74d27b9b9b482ea66e4dcf4)) + # @revanced/bot-websocket-api [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.2...@revanced/bot-websocket-api@1.0.0-dev.3) (2024-07-23) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index e7c79f5..cd23980 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.3", + "version": "1.0.0-dev.4", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { From 2b601b1a1d175d6192426a2add3a07984286a616 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 Jul 2024 15:42:43 +0000 Subject: [PATCH 174/312] chore(release): 1.0.0-dev.3 [skip ci] # @revanced/discord-bot [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.2...@revanced/discord-bot@1.0.0-dev.3) (2024-07-23) ### Bug Fixes * **bots/discord:** revert dist denesting, fixes config not found ([0d4898d](https://github.com/revanced/revanced-helper/commit/0d4898dae8b26f8466d3f6b8f62875866f581644)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 2d58651..95bf2fc 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.2...@revanced/discord-bot@1.0.0-dev.3) (2024-07-23) + + +### Bug Fixes + +* **bots/discord:** revert dist denesting, fixes config not found ([0d4898d](https://github.com/revanced/revanced-helper/commit/0d4898dae8b26f8466d3f6b8f62875866f581644)) + # @revanced/discord-bot [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.1...@revanced/discord-bot@1.0.0-dev.2) (2024-07-23) diff --git a/bots/discord/package.json b/bots/discord/package.json index 143c03e..c072235 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.2", + "version": "1.0.0-dev.3", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 875bd209b252566414bf89349839cabc01697e1c Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 24 Jul 2024 01:12:30 +0700 Subject: [PATCH 175/312] fix(bots/discord): wrong database schema path --- bots/discord/src/context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 74256be..a855887 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -32,7 +32,7 @@ export const api = { } const DatabasePath = process.env['DATABASE_PATH'] -const DatabaseSchemaDir = join(import.meta.dir, '.drizzle') +const DatabaseSchemaDir = join(import.meta.dir, '..', '.drizzle') let dbSchemaFileName: string | undefined From 2a6f3c3013bb936db20a6644a7c3c1bd1e5c48a1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 24 Jul 2024 03:03:19 +0700 Subject: [PATCH 176/312] chore: update lockfile --- bun.lockb | Bin 286808 -> 286456 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index f72cc98b3cc16347d64b6faf4de723669f88e457..53d36672aa615eccc10131d5b9de4cd3f2c777af 100755 GIT binary patch delta 583 zcmca{K=8+2!3lnx8F4HOAaH47xI7crsf`sg#5PNaPY|0vaT%k-<`23KTP7dSW!YTv zs7Gvb!J`Rc+ZXUN8vK|(Wf`N$b{h_+7K`a~Vp(*yU$AE4VHp_mH4{lruw~xfvw_J= ziYwq06UgHXDOaWoo@R2`9&(yVkc;yQL~zE#?Ri(3w0SunLii2ersrK{Qrmvw5z`J4 zt_|NHqA|78`@S=&ZQt>oX-ymVidtr{ff;qo)3sw+q^2*fV^(EiV3>ZQj`@K2j5>&H zQZyvMxok=;>=X>AFZj+Rv3+|z^XWTMVBHK14CVes-o*il&I_PQ4@6J5if2)o7R#c* z#SsJ1ej;YNRxHaV9uo!zh6CT59*T$;@=ouGWl@-Z1ElN+RGCC9NUPfPh&UDnu7p^K z-iBC^Ug_y`<5;}8cR&SC#Ij7!zsjUKT_T=Ef|CK7EI6hc#<8eRPl#vX;0%C@WlV33 MV-cO+7SD1C06EO8bN~PV delta 630 zcmeydSMbIG!3lnxC*oNcK;ZJkaCs*7(;F*hh;gtnF)&CnFf?oy5T78%G5gHM8tf$s2T8Hmf}95t}^W2MZ(PX4yxRq0H^J{EWswI2jrK@&99F zV7N40@foB4bo&??oqL(0|ZBBz)fwg;YK65-;!3K5*~XnWojCT(8MM-YC?_vv|8nAEnPc*wLvgl`8U z1H+E*5ShBV>0RHL)R-9<7`Ctf#AQ1+Jj(X4I@AqSNn1v&@^mCWb|3I{y?V z0j-qOGCd;(28OU1tn+u4FPs815k^PEK}@WOn_d&mqB8wk42u%y0jS^uxRLDBZDU!y uxh0@UMkAhOdj1tA)#*E8StPg;prRS^5HtS8vT$*2fC`?7pDq-~@(cjQ__T=t From e82f2ab34b705dcb470c238bea0922239253761f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 Jul 2024 20:04:31 +0000 Subject: [PATCH 177/312] chore(release): 1.0.0-dev.4 [skip ci] # @revanced/discord-bot [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.3...@revanced/discord-bot@1.0.0-dev.4) (2024-07-23) ### Bug Fixes * **bots/discord:** wrong database schema path ([875bd20](https://github.com/revanced/revanced-helper/commit/875bd209b252566414bf89349839cabc01697e1c)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 95bf2fc..b27bfb7 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.3...@revanced/discord-bot@1.0.0-dev.4) (2024-07-23) + + +### Bug Fixes + +* **bots/discord:** wrong database schema path ([875bd20](https://github.com/revanced/revanced-helper/commit/875bd209b252566414bf89349839cabc01697e1c)) + # @revanced/discord-bot [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.2...@revanced/discord-bot@1.0.0-dev.3) (2024-07-23) diff --git a/bots/discord/package.json b/bots/discord/package.json index c072235..c6c35bc 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.3", + "version": "1.0.0-dev.4", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From e02c86a9c4725d24f8e3337d6a58fcc5c2892af7 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 24 Jul 2024 03:05:29 +0700 Subject: [PATCH 178/312] ci(release): add time limit for job --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e9f61d1..02591f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,7 @@ jobs: permissions: contents: read packages: write + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v4 From 96a654043402ad7ff388e80e513f884b3dfebe15 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 24 Jul 2024 03:25:22 +0700 Subject: [PATCH 179/312] build(Needs bump): revert building with bun explicitly Building with only Bun causes compatibility issues, like Drizzle Kit not being to generate any schema for the database of the Discord bot. --- apis/websocket/Dockerfile | 2 +- bots/discord/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apis/websocket/Dockerfile b/apis/websocket/Dockerfile index af98045..9a9db88 100644 --- a/apis/websocket/Dockerfile +++ b/apis/websocket/Dockerfile @@ -6,7 +6,7 @@ FROM base AS build WORKDIR /build COPY . . RUN bun install --frozen-lockfile -RUN cd apis/websocket && bun --bun run build +RUN cd apis/websocket && bun run build FROM base AS release diff --git a/bots/discord/Dockerfile b/bots/discord/Dockerfile index 96ca47b..50ec086 100644 --- a/bots/discord/Dockerfile +++ b/bots/discord/Dockerfile @@ -6,7 +6,7 @@ FROM base AS build WORKDIR /build COPY . . RUN bun install --frozen-lockfile -RUN cd bots/discord && bun --bun run build +RUN cd bots/discord && bun run build FROM base AS release From a8ceeb29aef45cf45b6eb65f4766ef10a585373d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 24 Jul 2024 03:29:32 +0700 Subject: [PATCH 180/312] chore: update lockfile --- bun.lockb | Bin 286456 -> 286456 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index 53d36672aa615eccc10131d5b9de4cd3f2c777af..2b7f937ca2ae29494d4a8684e364d5285508e7b2 100755 GIT binary patch delta 28 kcmeydSMbMP!G;#b7N!>FEi7wdrq{%<$ZS6m!(zY$0JeP!{{R30 delta 36 scmeydSMbMP!G;#b7N!>FEi7wdc#Ihs7@D>>8?RLQ_h$Q!7#0U602M_JQvd(} From 4bb965e9ffc0d78b8e4efd4744093ce4bf7ef068 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 Jul 2024 20:30:32 +0000 Subject: [PATCH 181/312] chore(release): 1.0.0-dev.5 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.4...@revanced/bot-websocket-api@1.0.0-dev.5) (2024-07-23) --- apis/websocket/CHANGELOG.md | 2 ++ apis/websocket/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index 8bd5402..99c2904 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,5 @@ +# @revanced/bot-websocket-api [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.4...@revanced/bot-websocket-api@1.0.0-dev.5) (2024-07-23) + # @revanced/bot-websocket-api [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.3...@revanced/bot-websocket-api@1.0.0-dev.4) (2024-07-23) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index cd23980..323de24 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.4", + "version": "1.0.0-dev.5", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { From 1bd973ea6c317e94a517fc6afc3754b5a894c591 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 Jul 2024 20:31:04 +0000 Subject: [PATCH 182/312] chore(release): 1.0.0-dev.5 [skip ci] # @revanced/discord-bot [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.4...@revanced/discord-bot@1.0.0-dev.5) (2024-07-23) --- bots/discord/CHANGELOG.md | 2 ++ bots/discord/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index b27bfb7..380ea43 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,5 @@ +# @revanced/discord-bot [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.4...@revanced/discord-bot@1.0.0-dev.5) (2024-07-23) + # @revanced/discord-bot [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.3...@revanced/discord-bot@1.0.0-dev.4) (2024-07-23) diff --git a/bots/discord/package.json b/bots/discord/package.json index c6c35bc..347f5d6 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.4", + "version": "1.0.0-dev.5", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From c503a86c53d35dd2a964e1f30354017d584e21c1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 24 Jul 2024 03:36:21 +0700 Subject: [PATCH 183/312] ci(release): also update bun lockfile to prevent install freezes --- patches/@semantic-release%2Fnpm@12.0.1.patch | 13 ++++++++++--- semantic-release-config.js | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/patches/@semantic-release%2Fnpm@12.0.1.patch b/patches/@semantic-release%2Fnpm@12.0.1.patch index 5344d0a..7176bff 100644 --- a/patches/@semantic-release%2Fnpm@12.0.1.patch +++ b/patches/@semantic-release%2Fnpm@12.0.1.patch @@ -1,8 +1,14 @@ +diff --git a/node_modules/@semantic-release/npm/.bun-tag-3853154e196b7721 b/.bun-tag-3853154e196b7721 +new file mode 100644 +index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +diff --git a/node_modules/@semantic-release/npm/.bun-tag-550461f23a8ec245 b/.bun-tag-550461f23a8ec245 +new file mode 100644 +index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/node_modules/@semantic-release/npm/.bun-tag-c9c8130945517add b/.bun-tag-c9c8130945517add new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/prepare.js b/lib/prepare.js -index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360e031c7b5 100644 +index 3e76bec44cf595a1b4141728336bed904d4d518d..4b25ca64879bbee2a600f2b23b738c86136ad9c6 100644 --- a/lib/prepare.js +++ b/lib/prepare.js @@ -1,6 +1,7 @@ @@ -14,7 +20,7 @@ index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360 export default async function ( npmrc, -@@ -11,19 +12,12 @@ export default async function ( +@@ -11,19 +12,13 @@ export default async function ( logger.log("Write version %s to package.json in %s", version, basePath); @@ -36,10 +42,11 @@ index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360 - await versionResult; + await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, detectIndent(pkgJsonRaw).indent)) ++ await execa("bun", ["install"]); if (tarballDir) { logger.log("Creating npm package version %s", version); -@@ -38,7 +32,7 @@ export default async function ( +@@ -38,7 +33,7 @@ export default async function ( // Only move the tarball if we need to // Fixes: https://github.com/semantic-release/npm/issues/169 if (tarballSource !== tarballDestination) { diff --git a/semantic-release-config.js b/semantic-release-config.js index 7812220..a0f2c5d 100644 --- a/semantic-release-config.js +++ b/semantic-release-config.js @@ -31,7 +31,7 @@ const Options = { [ '@semantic-release/git', { - assets: ['CHANGELOG.md', 'package.json'], + assets: ['CHANGELOG.md', 'package.json', '../../bun.lockb'], }, ], [ From 673aa189bef1009a3e32ba3b1291a5ee84f2def3 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 24 Jul 2024 03:57:22 +0700 Subject: [PATCH 184/312] fix(bots/discord): ci issues causing database to not be auto generated --- bots/discord/Dockerfile | 7 ++++++- bots/discord/package.json | 2 +- bun.lockb | Bin 286456 -> 286464 bytes 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bots/discord/Dockerfile b/bots/discord/Dockerfile index 50ec086..17eeeec 100644 --- a/bots/discord/Dockerfile +++ b/bots/discord/Dockerfile @@ -1,5 +1,10 @@ # This file should be triggered from the monorepo root -FROM oven/bun:latest AS base +FROM node:latest AS base + +# Install Bun +RUN apt-get update && apt-get install -y curl unzip +RUN curl -fsSL https://bun.sh/install.sh | bash +ENV PATH="/root/.bun/bin:$PATH" FROM base AS build diff --git a/bots/discord/package.json b/bots/discord/package.json index 347f5d6..fd88b94 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -11,7 +11,7 @@ "dev": "bun prepare && bun --watch src/index.ts", "build": "bun prepare && bun run scripts/build.ts", "watch": "bun dev", - "prepare": "bun run scripts/generate-indexes.ts && drizzle-kit generate --name=schema" + "prepare": "bun run scripts/generate-indexes.ts && bunx drizzle-kit generate --name=schema" }, "repository": { "type": "git", diff --git a/bun.lockb b/bun.lockb index 2b7f937ca2ae29494d4a8684e364d5285508e7b2..612e90a45daba46e8959f49bb543b3b77aa19241 100755 GIT binary patch delta 4995 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 Date: Tue, 23 Jul 2024 20:59:49 +0000 Subject: [PATCH 185/312] chore(release): 1.0.0-dev.6 [skip ci] # @revanced/discord-bot [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.5...@revanced/discord-bot@1.0.0-dev.6) (2024-07-23) ### Bug Fixes * **bots/discord:** ci issues causing database to not be auto generated ([673aa18](https://github.com/revanced/revanced-helper/commit/673aa189bef1009a3e32ba3b1291a5ee84f2def3)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 380ea43..cea18ff 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.5...@revanced/discord-bot@1.0.0-dev.6) (2024-07-23) + + +### Bug Fixes + +* **bots/discord:** ci issues causing database to not be auto generated ([673aa18](https://github.com/revanced/revanced-helper/commit/673aa189bef1009a3e32ba3b1291a5ee84f2def3)) + # @revanced/discord-bot [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.4...@revanced/discord-bot@1.0.0-dev.5) (2024-07-23) # @revanced/discord-bot [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.3...@revanced/discord-bot@1.0.0-dev.4) (2024-07-23) diff --git a/bots/discord/package.json b/bots/discord/package.json index fd88b94..32b0156 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.5", + "version": "1.0.0-dev.6", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 3559ed1cb59aaad47be5c75185387ee442d09874 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 00:01:40 +0700 Subject: [PATCH 186/312] ci(bots/discord): patch `drizzle-kit` to stop using node, decreases image size --- bots/discord/Dockerfile | 7 +------ bun.lockb | Bin 286464 -> 286528 bytes package.json | 3 ++- patches/drizzle-kit@0.22.8.patch | 23 +++++++++++++++++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 patches/drizzle-kit@0.22.8.patch diff --git a/bots/discord/Dockerfile b/bots/discord/Dockerfile index 17eeeec..50ec086 100644 --- a/bots/discord/Dockerfile +++ b/bots/discord/Dockerfile @@ -1,10 +1,5 @@ # This file should be triggered from the monorepo root -FROM node:latest AS base - -# Install Bun -RUN apt-get update && apt-get install -y curl unzip -RUN curl -fsSL https://bun.sh/install.sh | bash -ENV PATH="/root/.bun/bin:$PATH" +FROM oven/bun:latest AS base FROM base AS build diff --git a/bun.lockb b/bun.lockb index 612e90a45daba46e8959f49bb543b3b77aa19241..17ba0582d671233b9a10cb7ac61077719de2526c 100755 GIT binary patch delta 234 zcmZqJCwO3=-~>O$4UPV-{*0~uOk4e#73&z=w-?qif4gH@kXVwOky@;uQj}R$m6NKQ zomt{wpl4*HXQ2m{Vt|5j|03_=fJEmL(JWw*7tW5W zr#D8ks7;GyQQ+)=imjNg8OxH+{Q%1S5X&+>{|b}p^aHUh5}X=w5RDeoAI7ri3!dTL wFe^mqJ7WV>c*AtbIF@jU^LH2+6c`v9z%F25VECNkcz@URgMX$6N-<3U0C7S?od5s; delta 170 zcmX@GPq1O1-~>O$f=2&Vf5ujSrmgMS(K|D%dgIGnOTt`v8=CA(mx& o{uL(G=?`LABse+ZAUXx6Gsdy#b4Eb91=AhlSi+|#N-|9V0EDnAQ2+n{ diff --git a/package.json b/package.json index 7036be3..43ead66 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "lefthook" ], "patchedDependencies": { - "@semantic-release/npm@12.0.1": "patches/@semantic-release%2Fnpm@12.0.1.patch" + "@semantic-release/npm@12.0.1": "patches/@semantic-release%2Fnpm@12.0.1.patch", + "drizzle-kit@0.22.8": "patches/drizzle-kit@0.22.8.patch" } } diff --git a/patches/drizzle-kit@0.22.8.patch b/patches/drizzle-kit@0.22.8.patch new file mode 100644 index 0000000..17d81bb --- /dev/null +++ b/patches/drizzle-kit@0.22.8.patch @@ -0,0 +1,23 @@ +diff --git a/bin.cjs b/bin.cjs +index 142ed9c20f28dc1080bebfb52325fa308c6cb771..9d3bea0787f6c05df11567c6821bc85743286340 100644 +--- a/bin.cjs ++++ b/bin.cjs +# Fixes no schema changes issue +@@ -22053,7 +22053,7 @@ var init_sqliteImports = __esm({ + const { unregister } = await safeRegister(); + for (let i2 = 0; i2 < imports.length; i2++) { + const it = imports[i2]; +- const i0 = require(`${it}`); ++ const i0 = await import(`${it}`); + const prepared = prepareFromExports3(i0); + tables.push(...prepared.tables); + } +# Fixes process hanging issue +@@ -129572,6 +129572,7 @@ var generateCommand = new Command("generate").option("--dialect ", "Dat + } else { + assertUnreachable(dialect7); + } ++ process.exit(0); + }); + var migrateCommand = new Command("migrate").option( + "--config ", From b79a1c7575e94c3e62654c87775cac497be4a50a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 00:04:21 +0700 Subject: [PATCH 187/312] fix(bots/discord): only check for member permissions when specified while correcting responses --- .../src/events/discord/messageReactionAdd/correctResponse.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index 2e8bd2a..701ad2f 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -46,7 +46,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { const member = await reactionMessage.guild.members.fetch(user.id) const { permissions, roles } = allowedMembers - if (!(member.permissions.has(permissions ?? 0n) || roles?.some(role => member.roles.cache.has(role)))) + if (!((permissions ? member.permissions.has(permissions) : false) || roles?.some(role => member.roles.cache.has(role)))) return } else if (allowedUsers) { if (!allowedUsers.includes(user.id)) return From 1f5c5a92a639973b83a1204355538936e69a4454 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 00:05:08 +0700 Subject: [PATCH 188/312] fix(bots/discord): set timeout for eligible mutes to unmute faster --- bots/discord/src/commands/moderation/mute.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index af3b26f..eccaf12 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -1,7 +1,7 @@ import { SlashCommandBuilder } from 'discord.js' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import { applyRolePreset } from '$/utils/discord/rolePresets' +import { applyRolePreset, removeRolePreset } from '$/utils/discord/rolePresets' import type { Command } from '../types' import { config } from '$/context' @@ -60,6 +60,10 @@ export default { createModerationActionEmbed('Muted', user, interaction.user, reason, durationMs), ) + if (durationMs) setTimeout(() => { + removeRolePreset(member, 'mute') + }, durationMs) + logger.info( `Moderator ${interaction.user.tag} (${interaction.user.id}) muted ${user.tag} (${user.id}) until ${expires} because ${reason}`, ) From e86180fe29d048daa67d60d7ddcc3cdcd411a735 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 00:16:11 +0700 Subject: [PATCH 189/312] feat(bots/discord)!: allow message scan response to be message payloads --- bots/discord/config.js | 14 +++++++++----- bots/discord/config.schema.ts | 4 ++-- bots/discord/package.json | 2 +- bots/discord/src/commands/moderation/mute.ts | 7 ++++--- .../discord/messageCreate/messageScanRequired.ts | 6 ++++-- .../discord/messageReactionAdd/correctResponse.ts | 7 ++++++- bots/discord/src/utils/discord/embeds.ts | 7 +++++-- bots/discord/src/utils/discord/messageScan.ts | 4 +++- 8 files changed, 34 insertions(+), 17 deletions(-) diff --git a/bots/discord/config.js b/bots/discord/config.js index fb05c8e..16d79b4 100644 --- a/bots/discord/config.js +++ b/bots/discord/config.js @@ -55,12 +55,16 @@ export default { text: [/^regexp?$/, { label: 'label', threshold: 0.85 }], }, response: { - title: 'Embed title', - description: 'Embed description', - fields: [ + embeds: [ { - name: 'Field name', - value: 'Field value', + title: 'Embed title', + description: 'Embed description', + fields: [ + { + name: 'Field name', + value: 'Field value', + }, + ], }, ], }, diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index e631e1d..23b2fb9 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -1,4 +1,4 @@ -import type { APIEmbed } from 'discord.js' +import type { BaseMessageOptions } from 'discord.js' export type Config = { owners: string[] @@ -70,4 +70,4 @@ export type ConfigMessageScanResponseLabelConfig = { threshold: number } -export type ConfigMessageScanResponseMessage = APIEmbed +export type ConfigMessageScanResponseMessage = BaseMessageOptions diff --git a/bots/discord/package.json b/bots/discord/package.json index 32b0156..5ffcf21 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} \ No newline at end of file +} diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index eccaf12..fededd2 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -60,9 +60,10 @@ export default { createModerationActionEmbed('Muted', user, interaction.user, reason, durationMs), ) - if (durationMs) setTimeout(() => { - removeRolePreset(member, 'mute') - }, durationMs) + if (durationMs) + setTimeout(() => { + removeRolePreset(member, 'mute') + }, durationMs) logger.info( `Moderator ${interaction.user.tag} (${interaction.user.id}) muted ${user.tag} (${user.id}) until ${expires} because ${reason}`, diff --git a/bots/discord/src/events/discord/messageCreate/messageScanRequired.ts b/bots/discord/src/events/discord/messageCreate/messageScanRequired.ts index 1ba7ef6..45d9f79 100644 --- a/bots/discord/src/events/discord/messageCreate/messageScanRequired.ts +++ b/bots/discord/src/events/discord/messageCreate/messageScanRequired.ts @@ -27,7 +27,8 @@ withContext(on, 'messageCreate', async (context, msg) => { logger.debug('Response found') const reply = await msg.reply({ - embeds: [createMessageScanResponseEmbed(response, label ? 'nlp' : 'match')], + ...response, + embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, label ? 'nlp' : 'match')), }) if (label) @@ -64,7 +65,8 @@ withContext(on, 'messageCreate', async (context, msg) => { if (response) { logger.debug(`Response found for attachment: ${attachment.url}`) await msg.reply({ - embeds: [createMessageScanResponseEmbed(response, 'ocr')], + ...response, + embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, 'ocr')), }) break diff --git a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index 701ad2f..74a700d 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -46,7 +46,12 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { const member = await reactionMessage.guild.members.fetch(user.id) const { permissions, roles } = allowedMembers - if (!((permissions ? member.permissions.has(permissions) : false) || roles?.some(role => member.roles.cache.has(role)))) + if ( + !( + (permissions ? member.permissions.has(permissions) : false) || + roles?.some(role => member.roles.cache.has(role)) + ) + ) return } else if (allowedUsers) { if (!allowedUsers.includes(user.id)) return diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index 6248bef..cb584cd 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -24,16 +24,19 @@ export const createSuccessEmbed = (title: string | null, description?: string) = ) export const createMessageScanResponseEmbed = ( - response: ConfigMessageScanResponseMessage, + response: NonNullable[number], mode: 'ocr' | 'nlp' | 'match', ) => { + // biome-ignore lint/style/noParameterAssign: While this is confusing, it is fine for this purpose + if ('toJSON' in response) response = response.toJSON() + const embed = new EmbedBuilder().setTitle(response.title ?? null) if (response.description) embed.setDescription(response.description) if (response.fields) embed.addFields(response.fields) embed.setFooter({ - text: `ReVanced • Done via ${MessageScanHumanizedMode[mode]}`, + text: `ReVanced • Via ${MessageScanHumanizedMode[mode]}`, iconURL: ReVancedLogoURL, }) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index 41f6f18..fa86ce5 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -158,8 +158,10 @@ export const handleUserResponseCorrection = async ( correctedById: user.id, }) .where(eq(responses.replyId, response.replyId)) + await reply.edit({ - embeds: [createMessageScanResponseEmbed(correctLabelResponse.response, 'nlp')], + ...correctLabelResponse.response, + embeds: correctLabelResponse.response.embeds?.map(it => createMessageScanResponseEmbed(it, 'nlp')), }) } From d0acab1915fdaa5b84e2e35ee6e5f8c607fbf945 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 00:53:25 +0700 Subject: [PATCH 190/312] feat(bots/discord)!: add admin config --- bots/discord/config.js | 13 ++++++++++++- bots/discord/config.schema.ts | 5 ++++- bots/discord/src/commands/development/eval.ts | 2 +- .../commands/development/exception-test.ts | 2 +- bots/discord/src/commands/development/stop.ts | 2 +- bots/discord/src/commands/moderation/mute.ts | 4 ++-- .../src/commands/moderation/role-preset.ts | 4 ++-- bots/discord/src/commands/types.ts | 6 +++--- .../discord/interactionCreate/chatCommand.ts | 13 +++++++------ .../messageReactionAdd/correctResponse.ts | 3 ++- bots/discord/src/utils/discord/commands.ts | 19 ------------------- bots/discord/src/utils/discord/permissions.ts | 11 +++++++++++ 12 files changed, 46 insertions(+), 38 deletions(-) delete mode 100644 bots/discord/src/utils/discord/commands.ts create mode 100644 bots/discord/src/utils/discord/permissions.ts diff --git a/bots/discord/config.js b/bots/discord/config.js index 16d79b4..24d919d 100644 --- a/bots/discord/config.js +++ b/bots/discord/config.js @@ -4,7 +4,18 @@ * @type {import('./config.schema').Config} */ export default { - owners: ['USER_ID_HERE'], + /** + * ? ADMIN CONFIGURATION + * Bot administrators can run destructive commands like /stop, or /register. + * + * ! The match condition is `any`: If the user ID matches or the member has a specific role in the list, it considers that user as admin. + */ + admin: { + users: ['USER_ID_HERE'], + roles: { + GUILD_ID_HERE: ['ROLE_ID_HERE'], + }, + }, guilds: ['GUILD_ID_HERE'], moderation: { cure: { diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 23b2fb9..4ca9d3b 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -1,7 +1,10 @@ import type { BaseMessageOptions } from 'discord.js' export type Config = { - owners: string[] + admin?: { + users?: string[] + roles?: Record + } guilds: string[] moderation?: { roles: string[] diff --git a/bots/discord/src/commands/development/eval.ts b/bots/discord/src/commands/development/eval.ts index 223d66d..59a9570 100644 --- a/bots/discord/src/commands/development/eval.ts +++ b/bots/discord/src/commands/development/eval.ts @@ -12,7 +12,7 @@ export default { .setDMPermission(true) .toJSON(), - ownerOnly: true, + adminOnly: true, global: true, async execute(_, interaction) { diff --git a/bots/discord/src/commands/development/exception-test.ts b/bots/discord/src/commands/development/exception-test.ts index 70dfbae..cde89ae 100644 --- a/bots/discord/src/commands/development/exception-test.ts +++ b/bots/discord/src/commands/development/exception-test.ts @@ -25,7 +25,7 @@ export default { .setDMPermission(true) .toJSON(), - ownerOnly: true, + adminOnly: true, global: true, async execute(_, interaction) { diff --git a/bots/discord/src/commands/development/stop.ts b/bots/discord/src/commands/development/stop.ts index 2d08763..3e7bbf4 100644 --- a/bots/discord/src/commands/development/stop.ts +++ b/bots/discord/src/commands/development/stop.ts @@ -11,7 +11,7 @@ export default { .setDMPermission(true) .toJSON(), - ownerOnly: true, + adminOnly: true, global: true, async execute({ api, logger }, interaction) { diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index fededd2..aeba84d 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -24,7 +24,7 @@ export default { global: false, - async execute({ logger }, interaction, { userIsOwner }) { + async execute({ logger }, interaction, { isExecutorBotAdmin: isExecutorAdmin }) { const user = interaction.options.getUser('member', true) const reason = interaction.options.getString('reason') ?? 'No reason provided' const duration = interaction.options.getString('duration') @@ -48,7 +48,7 @@ export default { if (!member.manageable) throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') - if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !userIsOwner) + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !isExecutorAdmin) throw new CommandError( CommandErrorType.InvalidUser, 'You cannot mute a user with a role equal to or higher than yours.', diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts index 0d4cbff..75457f1 100644 --- a/bots/discord/src/commands/moderation/role-preset.ts +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -35,7 +35,7 @@ export default { global: false, - async execute({ logger }, interaction, { userIsOwner }) { + async execute({ logger }, interaction, { isExecutorBotAdmin: isExecutorAdmin }) { const action = interaction.options.getString('action', true) as 'apply' | 'remove' const user = interaction.options.getUser('member', true) const preset = interaction.options.getString('preset', true) @@ -61,7 +61,7 @@ export default { 'The duration must be at least 1 millisecond long.', ) - if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !userIsOwner) + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !isExecutorAdmin) throw new CommandError( CommandErrorType.InvalidUser, 'You cannot apply a role preset to a user with a role equal to or higher than yours.', diff --git a/bots/discord/src/commands/types.ts b/bots/discord/src/commands/types.ts index e782811..ceb232f 100644 --- a/bots/discord/src/commands/types.ts +++ b/bots/discord/src/commands/types.ts @@ -39,10 +39,10 @@ export type Command = { roles?: string[] } /** - * Whether this command can only be used by bot owners. + * Whether this command can only be used by bot admins. * @default false */ - ownerOnly?: boolean + adminOnly?: boolean /** * Whether to register this command as a global slash command. * This is set to `false` and commands will be registered in allowed guilds only by default. @@ -52,5 +52,5 @@ export type Command = { } export interface Info { - userIsOwner: boolean + isExecutorBotAdmin: boolean } diff --git a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts index 0972b16..b608dd8 100644 --- a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts @@ -1,4 +1,5 @@ import CommandError from '$/classes/CommandError' +import { isAdmin } from '$/utils/discord/permissions' import { createErrorEmbed, createStackTraceEmbed } from '$utils/discord/embeds' import { on, withContext } from '$utils/discord/events' @@ -11,14 +12,14 @@ withContext(on, 'interactionCreate', async (context, interaction) => { logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag}`) if (!command) return void logger.error(`Command ${interaction.commandName} not implemented but registered!!!`) - const isOwner = config.owners.includes(interaction.user.id) + const isExecutorBotAdmin = isAdmin(await interaction.guild?.members.fetch(interaction.user.id) || interaction.user, config.admin) /** - * Owner check + * Admin check */ - if (command.ownerOnly && !isOwner) + if (command.adminOnly && !isExecutorBotAdmin) return void (await interaction.reply({ - embeds: [createErrorEmbed('Massive skill issue', 'This command can only be used by the bot owners.')], + embeds: [createErrorEmbed('Massive skill issue', 'This command can only be used by the bot admins.')], ephemeral: true, })) @@ -39,7 +40,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => { */ if (interaction.inGuild()) { // Bot owners get bypass - if (command.memberRequirements && !isOwner) { + if (command.memberRequirements && !isExecutorBotAdmin) { const { permissions = 0n, roles = [], mode } = command.memberRequirements const member = await interaction.guild!.members.fetch(interaction.user.id) @@ -69,7 +70,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => { try { logger.debug(`Command ${interaction.commandName} being executed`) - await command.execute(context, interaction, { userIsOwner: isOwner }) + await command.execute(context, interaction, { isExecutorBotAdmin }) } catch (err) { logger.error(`Error while executing command ${interaction.commandName}:`, err) await interaction[interaction.replied ? 'followUp' : 'reply']({ diff --git a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index 74a700d..ccc6fe8 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -14,6 +14,7 @@ import type { ConfigMessageScanResponseLabelConfig } from '$/../config.schema' import { responses } from '$/database/schemas' import { handleUserResponseCorrection } from '$/utils/discord/messageScan' import { eq } from 'drizzle-orm' +import { isAdmin } from '$/utils/discord/permissions' const PossibleReactions = Object.values(Reactions) as string[] @@ -32,7 +33,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { if (reactionMessage.author.id !== reaction.client.user!.id) return if (!PossibleReactions.includes(reaction.emoji.name!)) return - if (!config.owners.includes(user.id)) { + if (!isAdmin(reactionMessage.member || reactionMessage.author, config.admin)) { // User is in guild, and config has member requirements if ( reactionMessage.inGuild() && diff --git a/bots/discord/src/utils/discord/commands.ts b/bots/discord/src/utils/discord/commands.ts deleted file mode 100644 index 188946f..0000000 --- a/bots/discord/src/utils/discord/commands.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Command } from '$commands/types' -import { listAllFilesRecursive } from '$utils/fs' - -export const loadCommands = async (dir: string) => { - const commandsMap: Record = {} - const files = listAllFilesRecursive(dir) - const commands = await Promise.all( - files.map(async file => { - const command = await import(file) - return command.default - }), - ) - - for (const command of commands) { - if (command) commandsMap[command.data.name] = command - } - - return commandsMap -} diff --git a/bots/discord/src/utils/discord/permissions.ts b/bots/discord/src/utils/discord/permissions.ts new file mode 100644 index 0000000..6bb2540 --- /dev/null +++ b/bots/discord/src/utils/discord/permissions.ts @@ -0,0 +1,11 @@ +import { GuildMember, type User } from 'discord.js' +import type { Config } from 'config.schema' + +export const isAdmin = (userOrMember: User | GuildMember, adminConfig: Config['admin']) => { + return adminConfig?.users?.includes(userOrMember.id) || (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember, adminConfig)) +} + +export const isMemberAdmin = (member: GuildMember, adminConfig: Config['admin']) => { + const roles = new Set(member.roles.cache.keys()) + return Boolean(adminConfig?.roles?.[member.guild.id]?.some(role => roles.has(role))) +} \ No newline at end of file From 27662ed91a79bfac7d3f091834e859a7b57366ce Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 01:08:23 +0700 Subject: [PATCH 191/312] feat(bots/discord): add `replyToReplied` option in response config --- bots/discord/config.schema.ts | 1 + ...{messageScanRequired.ts => scanMessage.ts} | 5 +- bots/discord/src/utils/discord/messageScan.ts | 52 +++++++++---------- 3 files changed, 28 insertions(+), 30 deletions(-) rename bots/discord/src/events/discord/messageCreate/{messageScanRequired.ts => scanMessage.ts} (91%) diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 4ca9d3b..e55c4bd 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -60,6 +60,7 @@ export type ConfigMessageScanResponse = { } filterOverride?: NonNullable['filter'] response: ConfigMessageScanResponseMessage | null + replyToReplied?: boolean } export type ConfigMessageScanResponseLabelConfig = { diff --git a/bots/discord/src/events/discord/messageCreate/messageScanRequired.ts b/bots/discord/src/events/discord/messageCreate/scanMessage.ts similarity index 91% rename from bots/discord/src/events/discord/messageCreate/messageScanRequired.ts rename to bots/discord/src/events/discord/messageCreate/scanMessage.ts index 45d9f79..508737b 100644 --- a/bots/discord/src/events/discord/messageCreate/messageScanRequired.ts +++ b/bots/discord/src/events/discord/messageCreate/scanMessage.ts @@ -21,12 +21,13 @@ withContext(on, 'messageCreate', async (context, msg) => { try { logger.debug(`Classifying message ${msg.id}`) - const { response, label } = await getResponseFromText(msg.content, filteredResponses, context) + const { response, label, replyToReplied } = await getResponseFromText(msg.content, filteredResponses, context) if (response) { logger.debug('Response found') - const reply = await msg.reply({ + const toReply = replyToReplied ? await msg.fetchReference() : msg + const reply = await toReply.reply({ ...response, embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, label ? 'nlp' : 'match')), }) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index fa86ce5..7672c0f 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -2,8 +2,7 @@ import { type Response, responses } from '$/database/schemas' import type { Config, ConfigMessageScanResponse, - ConfigMessageScanResponseLabelConfig, - ConfigMessageScanResponseMessage, + ConfigMessageScanResponseLabelConfig } from 'config.schema' import type { Message, PartialUser, User } from 'discord.js' import { eq } from 'drizzle-orm' @@ -15,9 +14,12 @@ export const getResponseFromText = async ( // Just to be safe that we will never use data from the context parameter { api, logger }: Omit, ocrMode = false, -) => { - let label: string | undefined - let response: ConfigMessageScanResponseMessage | undefined | null +): Promise => { + let responseConfig: Awaited> = { + triggers: {}, + response: null + } + const firstLabelIndexes: number[] = [] // Test if all regexes before a label trigger is matched @@ -25,29 +27,28 @@ export const getResponseFromText = async ( const trigger = responses[i]! // Filter override check is not neccessary here, we are already passing responses that match the filter - // from the messageCreate handler + // from the messageCreate handler, see line 17 of messageCreate handler const { - triggers: { text: textTriggers, image: imageTriggers }, - response: resp, + triggers: { text: textTriggers, image: imageTriggers } } = trigger - if (response) break + if (responseConfig) break if (ocrMode) { if (imageTriggers) for (const regex of imageTriggers) if (regex.test(content)) { logger.debug(`Message matched regex (OCR mode): ${regex.source}`) - response = resp + responseConfig = trigger break } } else for (let j = 0; j < textTriggers!.length; j++) { - const trigger = textTriggers![j]! + const regex = textTriggers![j]! - if (trigger instanceof RegExp) { - if (trigger.test(content)) { - logger.debug(`Message matched regex (before mode): ${trigger.source}`) - response = resp + if (regex instanceof RegExp) { + if (regex.test(content)) { + logger.debug(`Message matched regex (before mode): ${regex.source}`) + responseConfig = trigger break } } else { @@ -58,7 +59,7 @@ export const getResponseFromText = async ( } // If none of the regexes match, we can search for labels immediately - if (!response && !ocrMode) { + if (!responseConfig && !ocrMode) { logger.debug('No match from before regexes, doing NLP') const scan = await api.client.parseText(content) if (scan.labels.length) { @@ -76,24 +77,22 @@ export const getResponseFromText = async ( if (!labelConfig) { logger.warn(`No label config found for label ${matchedLabel.name}`) - return { response: null, label: undefined } + return responseConfig } if (matchedLabel.confidence >= triggerConfig!.threshold) { logger.debug('Label confidence is enough') - label = matchedLabel.name - response = labelConfig.response + responseConfig = labelConfig } } } - // If we still don't have a label, we can match all regexes after the initial label trigger - if (!response) { + // If we still don't have a response config, we can match all regexes after the initial label trigger + if (!responseConfig) { logger.debug('No match from NLP, doing after regexes') for (let i = 0; i < responses.length; i++) { const { - triggers: { text: textTriggers }, - response: resp, + triggers: { text: textTriggers } } = responses[i]! const firstLabelIndex = firstLabelIndexes[i] ?? -1 @@ -103,7 +102,7 @@ export const getResponseFromText = async ( if (trigger instanceof RegExp) { if (trigger.test(content)) { logger.debug(`Message matched regex (after mode): ${trigger.source}`) - response = resp + responseConfig = responses[i]! break } } @@ -111,10 +110,7 @@ export const getResponseFromText = async ( } } - return { - response, - label, - } + return responseConfig } export const shouldScanMessage = ( From bd906fbf54dc462a8afb2a4ac798a184d660cd45 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 01:24:29 +0700 Subject: [PATCH 192/312] fix(bots/discord)!: remove `guilds` config in favor of upcoming impl --- bots/discord/config.js | 7 -- bots/discord/config.schema.ts | 1 - bots/discord/scripts/reload-slash-commands.ts | 83 ++++++++++--------- 3 files changed, 43 insertions(+), 48 deletions(-) diff --git a/bots/discord/config.js b/bots/discord/config.js index 24d919d..9415baa 100644 --- a/bots/discord/config.js +++ b/bots/discord/config.js @@ -4,19 +4,12 @@ * @type {import('./config.schema').Config} */ export default { - /** - * ? ADMIN CONFIGURATION - * Bot administrators can run destructive commands like /stop, or /register. - * - * ! The match condition is `any`: If the user ID matches or the member has a specific role in the list, it considers that user as admin. - */ admin: { users: ['USER_ID_HERE'], roles: { GUILD_ID_HERE: ['ROLE_ID_HERE'], }, }, - guilds: ['GUILD_ID_HERE'], moderation: { cure: { defaultName: 'Server member', diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index e55c4bd..7498d48 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -5,7 +5,6 @@ export type Config = { users?: string[] roles?: Record } - guilds: string[] moderation?: { roles: string[] cure?: { diff --git a/bots/discord/scripts/reload-slash-commands.ts b/bots/discord/scripts/reload-slash-commands.ts index b24e367..ad1054f 100644 --- a/bots/discord/scripts/reload-slash-commands.ts +++ b/bots/discord/scripts/reload-slash-commands.ts @@ -1,50 +1,53 @@ -import { REST } from '@discordjs/rest' -import { getMissingEnvironmentVariables } from '@revanced/bot-shared' -import { Routes } from 'discord-api-types/v9' -import type { - RESTGetCurrentApplicationResult, - RESTPutAPIApplicationCommandsResult, - RESTPutAPIApplicationGuildCommandsResult, -} from 'discord.js' -import { config, discord, logger } from '../src/context' +console.log('Deprecated. New implementation to be done.') +process.exit(1) -// Check if token exists +// import { REST } from '@discordjs/rest' +// import { getMissingEnvironmentVariables } from '@revanced/bot-shared' +// import { Routes } from 'discord-api-types/v9' +// import type { +// RESTGetCurrentApplicationResult, +// RESTPutAPIApplicationCommandsResult, +// RESTPutAPIApplicationGuildCommandsResult, +// } from 'discord.js' +// import { config, discord, logger } from '../src/context' -const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN']) -if (missingEnvs.length) { - for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`) - process.exit(1) -} +// // Check if token exists -// Group commands by global and guild +// const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN']) +// if (missingEnvs.length) { +// for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`) +// process.exit(1) +// } -const { global: globalCommands = [], guild: guildCommands = [] } = Object.groupBy(Object.values(discord.commands), c => - c.global ? 'global' : 'guild', -) +// // Group commands by global and guild -// Set commands +// const { global: globalCommands = [], guild: guildCommands = [] } = Object.groupBy(Object.values(discord.commands), c => +// c.global ? 'global' : 'guild', +// ) -const rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!) +// // Set commands -try { - const app = (await rest.get(Routes.currentApplication())) as RESTGetCurrentApplicationResult - const data = (await rest.put(Routes.applicationCommands(app.id), { - body: globalCommands.map(({ data }) => { - if (!data.dm_permission) data.dm_permission = true - logger.warn(`Command ${data.name} has no dm_permission set, forcing to true as it is a global command`) - return data - }), - })) as RESTPutAPIApplicationCommandsResult +// const rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!) - logger.info(`Reloaded ${data.length} global commands`) +// try { +// const app = (await rest.get(Routes.currentApplication())) as RESTGetCurrentApplicationResult +// const data = (await rest.put(Routes.applicationCommands(app.id), { +// body: globalCommands.map(({ data }) => { +// if (!data.dm_permission) data.dm_permission = true +// logger.warn(`Command ${data.name} has no dm_permission set, forcing to true as it is a global command`) +// return data +// }), +// })) as RESTPutAPIApplicationCommandsResult - for (const guildId of config.guilds) { - const data = (await rest.put(Routes.applicationGuildCommands(app.id, guildId), { - body: guildCommands.map(x => x.data), - })) as RESTPutAPIApplicationGuildCommandsResult +// logger.info(`Reloaded ${data.length} global commands`) - logger.info(`Reloaded ${data.length} guild commands for guild ${guildId}`) - } -} catch (e) { - logger.fatal(e) -} +// for (const guildId of config.guilds) { +// const data = (await rest.put(Routes.applicationGuildCommands(app.id, guildId), { +// body: guildCommands.map(x => x.data), +// })) as RESTPutAPIApplicationGuildCommandsResult + +// logger.info(`Reloaded ${data.length} guild commands for guild ${guildId}`) +// } +// } catch (e) { +// logger.fatal(e) +// } From cbf91162e27dd4c1ecb976927ab708f1d882abca Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 01:25:31 +0700 Subject: [PATCH 193/312] fix(bot/discord): start remove preset timeout for `role-preset` command --- bots/discord/src/commands/moderation/role-preset.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts index 75457f1..495744b 100644 --- a/bots/discord/src/commands/moderation/role-preset.ts +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -79,6 +79,11 @@ export default { ) } + if (expires) + setTimeout(() => { + removeRolePreset(member, preset) + }, expires) + await sendPresetReplyAndLogs(action, interaction, user, preset, expires) }, } satisfies Command From 6685ffb855b4edddc503cb347313f64d6cb361a6 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 01:30:33 +0700 Subject: [PATCH 194/312] chore: update lockfile --- bun.lockb | Bin 286528 -> 285528 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index 17ba0582d671233b9a10cb7ac61077719de2526c..f4a647390e8f59931200e71207a3aae34a7f4365 100755 GIT binary patch delta 43434 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 CRtWL{ delta 43579 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¨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%0AqBO_D=$sXM)Q#S6o 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 JA4QlZ007PF_)P!+ From 300e5cff3bbce55db399af58b527b1e5f8427bdd Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 26 Jul 2024 01:36:52 +0700 Subject: [PATCH 195/312] ci(bots/discord): fix freezing after generating db schemas --- patches/drizzle-kit@0.22.8.patch | 2 -- 1 file changed, 2 deletions(-) diff --git a/patches/drizzle-kit@0.22.8.patch b/patches/drizzle-kit@0.22.8.patch index 17d81bb..5571348 100644 --- a/patches/drizzle-kit@0.22.8.patch +++ b/patches/drizzle-kit@0.22.8.patch @@ -2,7 +2,6 @@ diff --git a/bin.cjs b/bin.cjs index 142ed9c20f28dc1080bebfb52325fa308c6cb771..9d3bea0787f6c05df11567c6821bc85743286340 100644 --- a/bin.cjs +++ b/bin.cjs -# Fixes no schema changes issue @@ -22053,7 +22053,7 @@ var init_sqliteImports = __esm({ const { unregister } = await safeRegister(); for (let i2 = 0; i2 < imports.length; i2++) { @@ -12,7 +11,6 @@ index 142ed9c20f28dc1080bebfb52325fa308c6cb771..9d3bea0787f6c05df11567c6821bc857 const prepared = prepareFromExports3(i0); tables.push(...prepared.tables); } -# Fixes process hanging issue @@ -129572,6 +129572,7 @@ var generateCommand = new Command("generate").option("--dialect ", "Dat } else { assertUnreachable(dialect7); From e748a4da92f210629765cf90e8622dac60af27de Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 25 Jul 2024 18:38:09 +0000 Subject: [PATCH 196/312] chore(release): 1.0.0-dev.7 [skip ci] # @revanced/discord-bot [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.6...@revanced/discord-bot@1.0.0-dev.7) (2024-07-25) ### Bug Fixes * **bot/discord:** start remove preset timeout for `role-preset` command ([cbf9116](https://github.com/revanced/revanced-helper/commit/cbf91162e27dd4c1ecb976927ab708f1d882abca)) * **bots/discord:** only check for member permissions when specified while correcting responses ([b79a1c7](https://github.com/revanced/revanced-helper/commit/b79a1c7575e94c3e62654c87775cac497be4a50a)) * **bots/discord:** set timeout for eligible mutes to unmute faster ([1f5c5a9](https://github.com/revanced/revanced-helper/commit/1f5c5a92a639973b83a1204355538936e69a4454)) ### Features * **bots/discord:** add `replyToReplied` option in response config ([27662ed](https://github.com/revanced/revanced-helper/commit/27662ed91a79bfac7d3f091834e859a7b57366ce)) --- bots/discord/CHANGELOG.md | 14 ++++++++++++++ bots/discord/package.json | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index cea18ff..95269cd 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,17 @@ +# @revanced/discord-bot [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.6...@revanced/discord-bot@1.0.0-dev.7) (2024-07-25) + + +### Bug Fixes + +* **bot/discord:** start remove preset timeout for `role-preset` command ([cbf9116](https://github.com/revanced/revanced-helper/commit/cbf91162e27dd4c1ecb976927ab708f1d882abca)) +* **bots/discord:** only check for member permissions when specified while correcting responses ([b79a1c7](https://github.com/revanced/revanced-helper/commit/b79a1c7575e94c3e62654c87775cac497be4a50a)) +* **bots/discord:** set timeout for eligible mutes to unmute faster ([1f5c5a9](https://github.com/revanced/revanced-helper/commit/1f5c5a92a639973b83a1204355538936e69a4454)) + + +### Features + +* **bots/discord:** add `replyToReplied` option in response config ([27662ed](https://github.com/revanced/revanced-helper/commit/27662ed91a79bfac7d3f091834e859a7b57366ce)) + # @revanced/discord-bot [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.5...@revanced/discord-bot@1.0.0-dev.6) (2024-07-23) diff --git a/bots/discord/package.json b/bots/discord/package.json index 5ffcf21..7d31c8f 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.6", + "version": "1.0.0-dev.7", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file From cdb600195520dba33110c40841629259e317055e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 28 Jul 2024 20:10:47 +0700 Subject: [PATCH 197/312] feat(bots/discord): blacklist and whitelist for filters --- bots/discord/config.js | 28 ++++++++++++++++--- bots/discord/config.schema.ts | 16 +++++++---- bots/discord/src/context.ts | 16 +++-------- bots/discord/src/events/api/disconnect.ts | 2 +- .../discord/messageCreate/scanMessage.ts | 7 +++-- .../messageReactionAdd/correctResponse.ts | 2 +- bots/discord/src/utils/discord/messageScan.ts | 20 +++++-------- bots/discord/src/utils/discord/permissions.ts | 10 +++---- 8 files changed, 58 insertions(+), 43 deletions(-) diff --git a/bots/discord/config.js b/bots/discord/config.js index 9415baa..5b7eedd 100644 --- a/bots/discord/config.js +++ b/bots/discord/config.js @@ -37,11 +37,19 @@ export default { checkExpiredEvery: 3600, }, messageScan: { + scanBots: false, + scanOutsideGuilds: false, filter: { - channels: ['CHANNEL_ID_HERE'], - roles: ['ROLE_ID_HERE'], - users: ['USER_ID_HERE'], - whitelist: false, + whitelist: { + channels: ['CHANNEL_ID_HERE'], + roles: ['ROLE_ID_HERE'], + users: ['USER_ID_HERE'], + }, + blacklist: { + channels: ['CHANNEL_ID_HERE'], + roles: ['ROLE_ID_HERE'], + users: ['USER_ID_HERE'], + }, }, humanCorrections: { falsePositiveLabel: 'false_positive', @@ -55,6 +63,18 @@ export default { allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], responses: [ { + filterOverride: { + whitelist: { + channels: ['CHANNEL_ID_HERE'], + roles: ['ROLE_ID_HERE'], + users: ['USER_ID_HERE'], + }, + blacklist: { + channels: ['CHANNEL_ID_HERE'], + roles: ['ROLE_ID_HERE'], + users: ['USER_ID_HERE'], + }, + }, triggers: { text: [/^regexp?$/, { label: 'label', threshold: 0.85 }], }, diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 7498d48..fb05df1 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -20,12 +20,12 @@ export type Config = { guilds: Record> } messageScan?: { + scanBots?: boolean + scanOutsideGuilds?: boolean allowedAttachmentMimeTypes: string[] - filter: { - roles?: string[] - users?: string[] - channels?: string[] - whitelist: boolean + filter?: { + whitelist?: Filter + blacklist?: Filter } humanCorrections: { falsePositiveLabel: string @@ -73,4 +73,10 @@ export type ConfigMessageScanResponseLabelConfig = { threshold: number } +export type Filter = { + roles?: string[] + users?: string[] + channels?: string[] +} + export type ConfigMessageScanResponseMessage = BaseMessageOptions diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index a855887..915df97 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -3,7 +3,7 @@ import { existsSync, readFileSync, readdirSync } from 'fs' import { join } from 'path' import { Client as APIClient } from '@revanced/bot-api' import { createLogger } from '@revanced/bot-shared' -import { ActivityType, Client as DiscordClient, Partials } from 'discord.js' +import { Client as DiscordClient, Partials } from 'discord.js' import { drizzle } from 'drizzle-orm/bun-sqlite' // Export config first, as commands require them @@ -13,7 +13,7 @@ export { config } import * as commands from './commands' import * as schemas from './database/schemas' -import type { Command } from './commands/types' +import type Command from './classes/Command' export const logger = createLogger({ level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, @@ -27,7 +27,7 @@ export const api = { }, }, }), - isStopping: false, + intentionallyDisconnecting: false, disconnectCount: 0, } @@ -80,16 +80,8 @@ export const discord = { repliedUser: true, }, partials: [Partials.Message, Partials.Reaction], - presence: { - activities: [ - { - type: ActivityType.Watching, - name: 'cat videos', - }, - ], - }, }), - commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.data.name, cmd])) as Record< + commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.name, cmd])) as Record< string, Command >, diff --git a/bots/discord/src/events/api/disconnect.ts b/bots/discord/src/events/api/disconnect.ts index 7af62eb..75b9340 100644 --- a/bots/discord/src/events/api/disconnect.ts +++ b/bots/discord/src/events/api/disconnect.ts @@ -2,7 +2,7 @@ import { on, withContext } from '$utils/api/events' import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared' withContext(on, 'disconnect', ({ api, config, logger }, reason, msg) => { - if (reason === DisconnectReason.PlannedDisconnect && api.isStopping) return + if (reason === DisconnectReason.PlannedDisconnect && api.intentionallyDisconnecting) return const ws = api.client.ws if (!ws.disconnected) ws.disconnect() diff --git a/bots/discord/src/events/discord/messageCreate/scanMessage.ts b/bots/discord/src/events/discord/messageCreate/scanMessage.ts index 508737b..1009808 100644 --- a/bots/discord/src/events/discord/messageCreate/scanMessage.ts +++ b/bots/discord/src/events/discord/messageCreate/scanMessage.ts @@ -1,6 +1,6 @@ import { MessageScanLabeledResponseReactions } from '$/constants' import { responses } from '$/database/schemas' -import { getResponseFromText, shouldScanMessage } from '$/utils/discord/messageScan' +import { getResponseFromText, messageMatchesFilter } from '$/utils/discord/messageScan' import { createMessageScanResponseEmbed } from '$utils/discord/embeds' import { on, withContext } from '$utils/discord/events' @@ -13,8 +13,11 @@ withContext(on, 'messageCreate', async (context, msg) => { } = context if (!config || !config.responses) return + if (msg.author.bot && !config.scanBots) + if (!msg.inGuild() && !config.scanOutsideGuilds) return + if (msg.inGuild() && msg.member?.partial) await msg.member.fetch() - const filteredResponses = config.responses.filter(x => shouldScanMessage(msg, x.filterOverride ?? config.filter)) + const filteredResponses = config.responses.filter(x => messageMatchesFilter(msg, x.filterOverride ?? config.filter)) if (!filteredResponses.length) return if (msg.content.length) { diff --git a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index ccc6fe8..4aa8a30 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -33,7 +33,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { if (reactionMessage.author.id !== reaction.client.user!.id) return if (!PossibleReactions.includes(reaction.emoji.name!)) return - if (!isAdmin(reactionMessage.member || reactionMessage.author, config.admin)) { + if (!isAdmin(reactionMessage.member || reactionMessage.author)) { // User is in guild, and config has member requirements if ( reactionMessage.inGuild() && diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index 7672c0f..5352901 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -113,24 +113,18 @@ export const getResponseFromText = async ( return responseConfig } -export const shouldScanMessage = ( +export const messageMatchesFilter = ( message: Message, filter: NonNullable['filter'], -): message is Message => { - if (message.author.bot) return false - if (!message.guild) return false +) => { if (!filter) return true - const filters = [ - filter.users?.includes(message.author.id), - message.member?.roles.cache.some(x => filter.roles?.includes(x.id)), - filter.channels?.includes(message.channel.id), - ] + const memberRoles = new Set(message.member?.roles.cache.keys()) + const blFilter = filter.blacklist - if (filter.whitelist && filters.every(x => !x)) return false - if (!filter.whitelist && filters.some(x => x)) return false - - return true + // If matches blacklist, will return false + // Any other case, will return true + return !(blFilter && (blFilter.channels?.includes(message.channelId) || blFilter.roles?.some(role => memberRoles.has(role)) || blFilter.users?.includes(message.author.id))) } export const handleUserResponseCorrection = async ( diff --git a/bots/discord/src/utils/discord/permissions.ts b/bots/discord/src/utils/discord/permissions.ts index 6bb2540..576e4c7 100644 --- a/bots/discord/src/utils/discord/permissions.ts +++ b/bots/discord/src/utils/discord/permissions.ts @@ -1,11 +1,11 @@ import { GuildMember, type User } from 'discord.js' -import type { Config } from 'config.schema' +import config from '../../../config' -export const isAdmin = (userOrMember: User | GuildMember, adminConfig: Config['admin']) => { - return adminConfig?.users?.includes(userOrMember.id) || (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember, adminConfig)) +export const isAdmin = (userOrMember: User | GuildMember) => { + return config.admin?.users?.includes(userOrMember.id) || (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember)) } -export const isMemberAdmin = (member: GuildMember, adminConfig: Config['admin']) => { +export const isMemberAdmin = (member: GuildMember) => { const roles = new Set(member.roles.cache.keys()) - return Boolean(adminConfig?.roles?.[member.guild.id]?.some(role => roles.has(role))) + return Boolean(config?.admin?.roles?.[member.guild.id]?.some(role => roles.has(role))) } \ No newline at end of file From 38c06997b4d0f7bb3f1e62618a5e3f088c522e30 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 28 Jul 2024 21:14:07 +0700 Subject: [PATCH 198/312] fix(bots/discord): cross-device link build errors --- bots/discord/scripts/build.ts | 5 +++-- bots/discord/src/context.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bots/discord/scripts/build.ts b/bots/discord/scripts/build.ts index 9fb15d6..ed335c7 100644 --- a/bots/discord/scripts/build.ts +++ b/bots/discord/scripts/build.ts @@ -17,7 +17,8 @@ await Bun.build({ }) logger.info('Copying config...') -await cp('config.js', 'dist/config.js') +await cp('./config.js', './dist/config.js') logger.info('Copying database schema...') -await rename('.drizzle', 'dist/.drizzle') +await cp('./.drizzle', './dist/.drizzle', { recursive: true }) +await rm('./.drizzle', { recursive: true }) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 915df97..2644af6 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -13,7 +13,7 @@ export { config } import * as commands from './commands' import * as schemas from './database/schemas' -import type Command from './classes/Command' +import type { Command } from './commands/types' export const logger = createLogger({ level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, @@ -81,7 +81,7 @@ export const discord = { }, partials: [Partials.Message, Partials.Reaction], }), - commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.name, cmd])) as Record< + commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.data.name, cmd])) as Record< string, Command >, From 8168f79ac68f44366c91858cf849cf899684e31b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 28 Jul 2024 14:15:40 +0000 Subject: [PATCH 199/312] chore(release): 1.0.0-dev.8 [skip ci] # @revanced/discord-bot [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.7...@revanced/discord-bot@1.0.0-dev.8) (2024-07-28) ### Bug Fixes * **bots/discord:** cross-device link build errors ([38c0699](https://github.com/revanced/revanced-helper/commit/38c06997b4d0f7bb3f1e62618a5e3f088c522e30)) ### Features * **bots/discord:** blacklist and whitelist for filters ([cdb6001](https://github.com/revanced/revanced-helper/commit/cdb600195520dba33110c40841629259e317055e)) --- bots/discord/CHANGELOG.md | 12 ++++++++++++ bots/discord/package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 95269cd..cb80f71 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,15 @@ +# @revanced/discord-bot [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.7...@revanced/discord-bot@1.0.0-dev.8) (2024-07-28) + + +### Bug Fixes + +* **bots/discord:** cross-device link build errors ([38c0699](https://github.com/revanced/revanced-helper/commit/38c06997b4d0f7bb3f1e62618a5e3f088c522e30)) + + +### Features + +* **bots/discord:** blacklist and whitelist for filters ([cdb6001](https://github.com/revanced/revanced-helper/commit/cdb600195520dba33110c40841629259e317055e)) + # @revanced/discord-bot [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.6...@revanced/discord-bot@1.0.0-dev.7) (2024-07-25) diff --git a/bots/discord/package.json b/bots/discord/package.json index 7d31c8f..901d29c 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.7", + "version": "1.0.0-dev.8", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From a848a9c896262bca92f04fbaad695e8696706563 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 30 Jul 2024 21:02:42 +0700 Subject: [PATCH 200/312] feat(packages/api): add force disconnecting and `disconnected` getter in `APIClient` --- packages/api/src/classes/Client.ts | 8 ++++++-- packages/api/src/classes/ClientWebSocket.ts | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index b0ccb43..1560e60 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -163,13 +163,17 @@ export default class Client { /** * Disconnects the client from the API */ - disconnect() { - this.ws.disconnect() + disconnect(force?: boolean) { + this.ws.disconnect(force) } #throwIfNotReady() { if (!this.isReady()) throw new Error('Client is not ready') } + + get disconnected() { + return this.ws.disconnected + } } export class ClientWebSocketPacketAwaiter { diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index 2df482d..bcb9a5f 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -137,8 +137,8 @@ export class ClientWebSocketManager { /** * Disconnects from the WebSocket API */ - disconnect() { - this.#throwIfDisconnected('Cannot disconnect when already disconnected from the server') + disconnect(force?: boolean) { + if (!force) this.#throwIfDisconnected('Cannot disconnect when already disconnected from the server') this._handleDisconnect(DisconnectReason.PlannedDisconnect) } From 646ec8da87617e6c8f48a89e8054e2cba91da549 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 30 Jul 2024 21:05:12 +0700 Subject: [PATCH 201/312] feat(bots/discord): framework changes and new features - Migrated to a new command framework which looks better and works better - Fixed commands not being bundled correctly - Added message (prefix) commands with argument validation - Added a new CommandErrorType, for invalid arguments - `/eval` is now a bit safer - Corrected colors for the coinflip embed - `/stop` now works even when the bot is not connected to the API --- bots/discord/config.js | 1 + bots/discord/config.schema.ts | 1 + bots/discord/package.json | 2 +- bots/discord/scripts/build.ts | 2 +- bots/discord/scripts/reload-slash-commands.ts | 53 -- bots/discord/src/classes/Command.ts | 591 ++++++++++++++++++ bots/discord/src/classes/CommandError.ts | 2 + bots/discord/src/commands/admin/eval.ts | 34 + .../src/commands/admin/exception-test.ts | 21 + .../src/commands/admin/slash-commands.ts | 93 +++ bots/discord/src/commands/admin/stop.ts | 24 + bots/discord/src/commands/development/eval.ts | 32 - .../commands/development/exception-test.ts | 36 -- bots/discord/src/commands/development/stop.ts | 35 -- bots/discord/src/commands/fun/coinflip.ts | 33 +- bots/discord/src/commands/fun/reply.ts | 62 +- bots/discord/src/commands/moderation/ban.ts | 76 +-- bots/discord/src/commands/moderation/cure.ts | 35 +- bots/discord/src/commands/moderation/mute.ts | 75 +-- bots/discord/src/commands/moderation/purge.ts | 58 +- .../src/commands/moderation/role-preset.ts | 95 ++- .../src/commands/moderation/slowmode.ts | 59 +- bots/discord/src/commands/moderation/unban.ts | 42 +- .../discord/src/commands/moderation/unmute.ts | 35 +- bots/discord/src/commands/types.ts | 56 -- bots/discord/src/context.ts | 16 +- .../discord/interactionCreate/chatCommand.ts | 66 +- .../discord/messageCreate/messageCommand.ts | 53 ++ .../discord/messageCreate/scanMessage.ts | 8 +- .../messageReactionAdd/correctResponse.ts | 2 +- bots/discord/src/events/discord/ready.ts | 4 +- bots/discord/src/utils/discord/messageScan.ts | 24 +- bots/discord/src/utils/discord/moderation.ts | 14 +- bots/discord/src/utils/discord/permissions.ts | 7 +- bots/discord/src/utils/discord/rolePresets.ts | 4 +- bots/discord/src/utils/fs.ts | 18 +- 36 files changed, 1153 insertions(+), 616 deletions(-) delete mode 100644 bots/discord/scripts/reload-slash-commands.ts create mode 100644 bots/discord/src/classes/Command.ts create mode 100644 bots/discord/src/commands/admin/eval.ts create mode 100644 bots/discord/src/commands/admin/exception-test.ts create mode 100644 bots/discord/src/commands/admin/slash-commands.ts create mode 100644 bots/discord/src/commands/admin/stop.ts delete mode 100644 bots/discord/src/commands/development/eval.ts delete mode 100644 bots/discord/src/commands/development/exception-test.ts delete mode 100644 bots/discord/src/commands/development/stop.ts delete mode 100644 bots/discord/src/commands/types.ts create mode 100644 bots/discord/src/events/discord/messageCreate/messageCommand.ts diff --git a/bots/discord/config.js b/bots/discord/config.js index 5b7eedd..bddf96d 100644 --- a/bots/discord/config.js +++ b/bots/discord/config.js @@ -4,6 +4,7 @@ * @type {import('./config.schema').Config} */ export default { + prefix: '!', admin: { users: ['USER_ID_HERE'], roles: { diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index fb05df1..8738981 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -1,6 +1,7 @@ import type { BaseMessageOptions } from 'discord.js' export type Config = { + prefix?: string admin?: { users?: string[] roles?: Record diff --git a/bots/discord/package.json b/bots/discord/package.json index 901d29c..24bb0f1 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} \ No newline at end of file +} diff --git a/bots/discord/scripts/build.ts b/bots/discord/scripts/build.ts index ed335c7..47814d7 100644 --- a/bots/discord/scripts/build.ts +++ b/bots/discord/scripts/build.ts @@ -1,5 +1,5 @@ import { createLogger } from '@revanced/bot-shared' -import { cp, rename, rm } from 'fs/promises' +import { cp, rm } from 'fs/promises' const logger = createLogger() diff --git a/bots/discord/scripts/reload-slash-commands.ts b/bots/discord/scripts/reload-slash-commands.ts deleted file mode 100644 index ad1054f..0000000 --- a/bots/discord/scripts/reload-slash-commands.ts +++ /dev/null @@ -1,53 +0,0 @@ -console.log('Deprecated. New implementation to be done.') -process.exit(1) - -// import { REST } from '@discordjs/rest' -// import { getMissingEnvironmentVariables } from '@revanced/bot-shared' -// import { Routes } from 'discord-api-types/v9' -// import type { -// RESTGetCurrentApplicationResult, -// RESTPutAPIApplicationCommandsResult, -// RESTPutAPIApplicationGuildCommandsResult, -// } from 'discord.js' -// import { config, discord, logger } from '../src/context' - -// // Check if token exists - -// const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN']) -// if (missingEnvs.length) { -// for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`) -// process.exit(1) -// } - -// // Group commands by global and guild - -// const { global: globalCommands = [], guild: guildCommands = [] } = Object.groupBy(Object.values(discord.commands), c => -// c.global ? 'global' : 'guild', -// ) - -// // Set commands - -// const rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!) - -// try { -// const app = (await rest.get(Routes.currentApplication())) as RESTGetCurrentApplicationResult -// const data = (await rest.put(Routes.applicationCommands(app.id), { -// body: globalCommands.map(({ data }) => { -// if (!data.dm_permission) data.dm_permission = true -// logger.warn(`Command ${data.name} has no dm_permission set, forcing to true as it is a global command`) -// return data -// }), -// })) as RESTPutAPIApplicationCommandsResult - -// logger.info(`Reloaded ${data.length} global commands`) - -// for (const guildId of config.guilds) { -// const data = (await rest.put(Routes.applicationGuildCommands(app.id, guildId), { -// body: guildCommands.map(x => x.data), -// })) as RESTPutAPIApplicationGuildCommandsResult - -// logger.info(`Reloaded ${data.length} guild commands for guild ${guildId}`) -// } -// } catch (e) { -// logger.fatal(e) -// } diff --git a/bots/discord/src/classes/Command.ts b/bots/discord/src/classes/Command.ts new file mode 100644 index 0000000..7baf466 --- /dev/null +++ b/bots/discord/src/classes/Command.ts @@ -0,0 +1,591 @@ +import { ApplicationCommandOptionType } from 'discord.js' + +import { createErrorEmbed } from '$/utils/discord/embeds' +import { isAdmin } from '$/utils/discord/permissions' + +import { config } from '../context' +import CommandError, { CommandErrorType } from './CommandError' + +import type { Filter } from 'config.schema' +import type { + APIApplicationCommandChannelOption, + CacheType, + Channel, + ChatInputCommandInteraction, + CommandInteractionOption, + GuildMember, + Message, + RESTPostAPIChatInputApplicationCommandsJSONBody, + Role, + User, +} from 'discord.js' + +export default class Command< + Global extends boolean = false, + Options extends CommandOptionsOptions | undefined = undefined, + AllowMessageCommand extends boolean = false, +> { + name: string + description: string + requirements?: CommandRequirements + options?: Options + global?: Global + #execute: CommandExecuteFunction + + static OptionType = ApplicationCommandOptionType + + constructor({ + name, + description, + requirements, + options, + global, + execute, + }: CommandOptions) { + this.name = name + this.description = description + this.requirements = requirements + this.options = options + this.global = global + this.#execute = execute + } + + async onMessage( + context: typeof import('../context'), + msg: Message>, + args: CommandArguments, + ): Promise { + if (!this.global && !msg.inGuild()) + return await msg.reply({ + embeds: [createErrorEmbed('Cannot run this command', 'This command can only be used in a server.')], + }) + + const executor = this.global ? msg.author : await msg.guild?.members.fetch(msg.author.id)! + + if (!(await this.canExecute(executor, msg.channelId))) + return await msg.reply({ + embeds: [ + createErrorEmbed( + 'Cannot run this command', + 'You do not meet the requirements to run this command.', + ), + ], + }) + + const options = this.options + ? ((await this.#resolveMessageOptions(msg, this.options, args)) as CommandExecuteFunctionOptionsParameter< + NonNullable + >) + : undefined + + // @ts-expect-error: Type mismatch (again!) because TypeScript is not smart enough + return await this.#execute({ ...context, executor }, msg, options) + } + + async #resolveMessageOptions(msg: Message, options: CommandOptionsOptions, args: CommandArguments) { + const iterableOptions = Object.entries(options) + const _options = {} as unknown + + for (let i = 0; i < iterableOptions.length; i++) { + const [name, option] = iterableOptions[i]! + const { type, required, description } = option + const isSubcommandLikeOption = + type === ApplicationCommandOptionType.Subcommand || + type === ApplicationCommandOptionType.SubcommandGroup + + const arg = args[i] + + const expectedType = `${ApplicationCommandOptionType[type]}${required ? '' : '?'}` + const argExplainationString = `\n-# **${name}**: ${description}` + const choicesString = + 'choices' in option && option.choices + ? `\n\n-# **AVAILABLE CHOICES**\n${option.choices.map(({ value }) => `- ${value}`).join('\n')}` + : '' + + if (isSubcommandLikeOption && !arg) + throw new CommandError( + CommandErrorType.MissingArgument, + `Missing required subcommand.\n\n-# **AVAILABLE SUBCOMMANDS**\n${iterableOptions.map(([name, { description }]) => `- **${name}**: ${description}`).join('\n')}`, + ) + + if (required && !arg) + throw new CommandError( + CommandErrorType.MissingArgument, + `Missing required argument **${name}** with type **${expectedType}**.${argExplainationString}${choicesString}`, + ) + + if (typeof arg === 'object' && arg.type !== type) + throw new CommandError( + CommandErrorType.InvalidArgument, + `Invalid type for argument **${name}**.${argExplainationString}\n\nExpected type: **${expectedType}**\nGot type: **${ApplicationCommandOptionType[arg.type]}**${choicesString}`, + ) + + if ('choices' in option && option.choices && !option.choices.some(({ value }) => value === arg)) + throw new CommandError( + CommandErrorType.InvalidArgument, + `Invalid choice for argument **${name}**.\n${argExplainationString}\n\n${choicesString}\n`, + ) + + const argValue = typeof arg === 'string' ? arg : arg?.id + + if (argValue && arg) { + if (isSubcommandLikeOption) { + const [subcommandName, subcommandOption] = iterableOptions.find(([name]) => name === argValue)! + + // @ts-expect-error: Not smart enough, TypeScript :( + _options[subcommandName] = await this.#resolveMessageOptions( + msg, + (subcommandOption as CommandSubcommandLikeOption).options, + args.slice(i + 1), + ) + + break + } + + if ( + (type === ApplicationCommandOptionType.Channel || + type === ApplicationCommandOptionType.User || + type === ApplicationCommandOptionType.Role) && + Number.isNaN(Number(argValue)) + ) + throw new CommandError( + CommandErrorType.InvalidArgument, + `Malformed ID for argument **${name}**.${argExplainationString}`, + ) + + if ( + (type === ApplicationCommandOptionType.Number || type === ApplicationCommandOptionType.Integer) && + Number.isNaN(Number(argValue)) + ) { + throw new CommandError( + CommandErrorType.InvalidArgument, + `Invalid number for argument **${name}**.${argExplainationString}`, + ) + } + + if ( + type === ApplicationCommandOptionType.Boolean && + !['true', 'false', 'yes', 'no', 'y', 'n', 't', 'f'].includes(argValue) + ) + throw new CommandError( + CommandErrorType.InvalidArgument, + `Invalid boolean for argument **${name}**.${argExplainationString}`, + ) + + // @ts-expect-error: Not smart enough, TypeScript :( + _options[name] = + type === ApplicationCommandOptionType.Number || type === ApplicationCommandOptionType.Integer + ? Number(argValue) + : type === ApplicationCommandOptionType.Boolean + ? argValue[0] === 't' || argValue[0] === 'y' + : type === ApplicationCommandOptionType.Channel + ? await msg.client.channels.fetch(argValue) + : type === ApplicationCommandOptionType.User + ? await msg.client.users.fetch(argValue) + : type === ApplicationCommandOptionType.Role + ? await msg.guild?.roles.fetch(argValue) + : argValue + } + } + + return _options + } + + async onInteraction( + context: typeof import('../context'), + interaction: ChatInputCommandInteraction, + ): Promise { + const { logger } = context + + if (interaction.commandName !== this.name) { + logger.warn(`Command name mismatch, expected ${this.name}, but got ${interaction.commandName}!`) + return await interaction.reply({ + embeds: [ + createErrorEmbed( + 'Internal command name mismatch', + 'The interaction command name does not match the expected command name.', + ), + ], + }) + } + + if (!this.global && !interaction.inGuild()) { + logger.error(`Command ${this.name} cannot be run in DMs, but was registered as global`) + return await interaction.reply({ + embeds: [createErrorEmbed('Cannot run this command', 'This command can only be used in a server.')], + ephemeral: true, + }) + } + + const executor = this.global ? interaction.user : await interaction.guild?.members.fetch(interaction.user.id)! + + if (!(await this.canExecute(executor, interaction.channelId))) + return await interaction.reply({ + embeds: [ + createErrorEmbed( + 'Cannot run this command', + 'You do not meet the requirements to run this command.', + ), + ], + ephemeral: true, + }) + + const options = this.options + ? ((await this.#resolveInteractionOptions(interaction)) as CommandExecuteFunctionOptionsParameter< + NonNullable + >) + : undefined + + if (options === null) + return await interaction.reply({ + embeds: [ + createErrorEmbed( + 'Internal command option type mismatch', + 'The interaction command option type does not match the expected command option type.', + ), + ], + }) + + // @ts-expect-error: Type mismatch (again!) because TypeScript is not smart enough + return await this.#execute({ ...context, executor }, interaction, options) + } + + async #resolveInteractionOptions( + interaction: ChatInputCommandInteraction, + options: readonly CommandInteractionOption[] = interaction.options.data, + ) { + const _options = {} as unknown + + if (this.options) + for (const { name, type, value } of options) { + if (this.options[name]?.type !== type) return null + + if ( + type === ApplicationCommandOptionType.Subcommand || + type === ApplicationCommandOptionType.SubcommandGroup + ) { + const subOptions = Object.entries((this.options[name] as CommandSubcommandLikeOption).options) + + // @ts-expect-error: Not smart enough, TypeScript :( + _options[name] = await this.#resolveInteractionOptions(interaction, subOptions) + + break + } + + if (!value) continue + + // @ts-expect-error: Not smart enough, TypeScript :( + _options[name] = + type === ApplicationCommandOptionType.Channel + ? await interaction.client.channels.fetch(value as string) + : type === ApplicationCommandOptionType.User + ? await interaction.client.users.fetch(value as string) + : type === ApplicationCommandOptionType.Role + ? await interaction.guild?.roles.fetch(value as string) + : value + } + + return _options + } + + async canExecute(executor: User | GuildMember, channelId: string): Promise { + if (!this.requirements) return false + + const { + adminOnly, + channels, + roles, + permissions, + users, + mode = 'all', + defaultCondition = 'fail', + memberRequirementsForUsers = 'pass', + } = this.requirements + + const member = this.global ? null : (executor as GuildMember) + const bDefCond = defaultCondition !== 'fail' + const bMemReqForUsers = memberRequirementsForUsers !== 'fail' + + const conditions = [ + adminOnly ? isAdmin(executor) : bDefCond, + channels ? channels.includes(channelId) : bDefCond, + member ? (roles ? roles.some(role => member.roles.cache.has(role)) : bDefCond) : bMemReqForUsers, + member ? (permissions ? member.permissions.has(permissions) : bDefCond) : bMemReqForUsers, + users ? users.includes(executor.id) : bDefCond, + ] + + if (mode === 'all' && conditions.some(condition => !condition)) return false + if (mode === 'any' && conditions.every(condition => !condition)) return false + + return true + } + + get json(): RESTPostAPIChatInputApplicationCommandsJSONBody & { contexts: Array<0 | 1 | 2> } { + return { + name: this.name, + description: this.description, + options: this.options ? this.#transformOptions(this.options) : undefined, + // https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types + contexts: this.global ? [0] : [0, 1], + } + } + + #transformOptions(optionsObject: Record) { + const options: RESTPostAPIChatInputApplicationCommandsJSONBody['options'] = [] + + for (const [name, option] of Object.entries(optionsObject)) { + options.push({ + // biome-ignore lint/suspicious/noExplicitAny: Good enough work here + type: option.type as any, + name, + description: option.description, + required: option.required, + ...(option.type === ApplicationCommandOptionType.Subcommand || + option.type === ApplicationCommandOptionType.SubcommandGroup + ? { + options: this.#transformOptions((option as CommandSubcommandLikeOption).options), + } + : {}), + ...(option.type === ApplicationCommandOptionType.Channel ? { channel_types: option.types } : {}), + ...(option.type === ApplicationCommandOptionType.Integer || + option.type === ApplicationCommandOptionType.Number + ? { + min_value: option.min, + max_value: option.max, + choices: option.choices, + autocomplete: option.autocomplete, + } + : {}), + ...(option.type === ApplicationCommandOptionType.String + ? { + min_length: option.minLength, + max_length: option.maxLength, + choices: option.choices, + autocomplete: option.autocomplete, + } + : {}), + }) + } + + return options + } +} + +export class ModerationCommand< + Options extends CommandOptionsOptions, + AllowMessageCommand extends boolean = true, +> extends Command { + constructor(options: ExtendedCommandOptions) { + super({ + ...options, + requirements: { + ...options.requirements, + defaultCondition: 'pass', + roles: (config.moderation?.roles ?? []).concat(options.requirements?.roles ?? []), + }, + // @ts-expect-error: No thanks + allowMessageCommand: options.allowMessageCommand ?? true, + global: false, + }) + } +} + +export class AdminCommand extends Command< + true, + Options, + AllowMessageCommand +> { + constructor(options: ExtendedCommandOptions) { + super({ + ...options, + requirements: { + ...options.requirements, + adminOnly: true, + defaultCondition: 'pass', + }, + global: true, + }) + } +} + +/* TODO: + APIApplicationCommandAttachmentOption + APIApplicationCommandMentionableOption + APIApplicationCommandRoleOption +*/ + +export interface CommandOptions< + Global extends boolean, + Options extends CommandOptionsOptions | undefined, + AllowMessageCommand extends boolean, +> { + name: string + description: string + requirements?: CommandRequirements + options?: Options + execute: CommandExecuteFunction + global?: Global + allowMessageCommand?: AllowMessageCommand +} + +export type CommandArguments = Array + +export type CommandSpecialArgument = { + type: (typeof CommandSpecialArgumentType)[keyof typeof CommandSpecialArgumentType] + id: string +} + +export const CommandSpecialArgumentType = { + Channel: ApplicationCommandOptionType.Channel, + Role: ApplicationCommandOptionType.Role, + User: ApplicationCommandOptionType.User, +} + +type ExtendedCommandOptions< + Global extends boolean, + Options extends CommandOptionsOptions, + AllowMessageCommand extends boolean, +> = Omit, 'global'> & { + requirements?: Omit['requirements'], 'defaultCondition'> +} + +export type CommandOptionsOptions = Record + +type CommandExecuteFunction< + Global extends boolean, + Options extends CommandOptionsOptions | undefined, + AllowMessageCommand extends boolean, +> = ( + context: CommandContext, + trigger: If< + AllowMessageCommand, + Message> | ChatInputCommandInteraction>, + ChatInputCommandInteraction> + >, + options: Options extends CommandOptionsOptions ? CommandExecuteFunctionOptionsParameter : never, +) => Promise | unknown + +type If = T extends true ? U : V +type InvertBoolean = If + +type CommandExecuteFunctionOptionsParameter = { + [K in keyof Options]: Options[K]['type'] extends + | ApplicationCommandOptionType.Subcommand + | ApplicationCommandOptionType.SubcommandGroup + ? // @ts-expect-error: Shut up, it works + CommandExecuteFunctionOptionsParameter | undefined + : If< + Options[K]['required'], + CommandOptionValueMap[Options[K]['type']], + CommandOptionValueMap[Options[K]['type']] | undefined + > +} + +type CommandContext = typeof import('../context') & { + executor: CommandExecutor +} + +type CommandOptionValueMap = { + [ApplicationCommandOptionType.Boolean]: boolean + [ApplicationCommandOptionType.Channel]: Channel + [ApplicationCommandOptionType.Integer]: number + [ApplicationCommandOptionType.Number]: number + [ApplicationCommandOptionType.String]: string + [ApplicationCommandOptionType.User]: User + [ApplicationCommandOptionType.Role]: Role + [ApplicationCommandOptionType.Subcommand]: never + [ApplicationCommandOptionType.SubcommandGroup]: never +} + +type CommandOption = + | CommandBooleanOption + | CommandChannelOption + | CommandIntegerOption + | CommandNumberOption + | CommandStringOption + | CommandUserOption + | CommandRoleOption + | CommandSubcommandOption + | CommandSubcommandGroupOption + +type CommandExecutor = If + +type CommandOptionBase = { + type: Type + description: string + required?: boolean +} + +type CommandBooleanOption = CommandOptionBase + +type CommandChannelOption = CommandOptionBase & { + types: APIApplicationCommandChannelOption['channel_types'] +} + +interface CommandOptionChoice { + name: string + value: ValueType +} + +type CommandOptionWithAutocompleteOrChoicesWrapper< + Base extends CommandOptionBase, + ChoiceType extends CommandOptionChoice, +> = + | (Base & { + autocomplete: true + choices?: never + }) + | (Base & { + autocomplete?: false + choices?: ChoiceType[] | readonly ChoiceType[] + }) + +type CommandIntegerOption = CommandOptionWithAutocompleteOrChoicesWrapper< + CommandOptionBase, + CommandOptionChoice +> & { + min?: number + max?: number +} + +type CommandNumberOption = CommandOptionWithAutocompleteOrChoicesWrapper< + CommandOptionBase, + CommandOptionChoice +> & { + min?: number + max?: number +} + +type CommandStringOption = CommandOptionWithAutocompleteOrChoicesWrapper< + CommandOptionBase, + CommandOptionChoice +> & { + minLength?: number + maxLength?: number +} + +type CommandUserOption = CommandOptionBase + +type CommandRoleOption = CommandOptionBase + +type SubcommandLikeApplicationCommandOptionType = + | ApplicationCommandOptionType.Subcommand + | ApplicationCommandOptionType.SubcommandGroup + +interface CommandSubcommandLikeOption< + Type extends SubcommandLikeApplicationCommandOptionType = SubcommandLikeApplicationCommandOptionType, +> extends CommandOptionBase { + options: CommandOptionsOptions + required?: never +} + +type CommandSubcommandOption = CommandSubcommandLikeOption +type CommandSubcommandGroupOption = CommandSubcommandLikeOption + +export type CommandRequirements = Filter & { + mode?: 'all' | 'any' + adminOnly?: boolean + permissions?: bigint + defaultCondition?: 'fail' | 'pass' + memberRequirementsForUsers?: 'pass' | 'fail' +} diff --git a/bots/discord/src/classes/CommandError.ts b/bots/discord/src/classes/CommandError.ts index 48ee202..23574b0 100644 --- a/bots/discord/src/classes/CommandError.ts +++ b/bots/discord/src/classes/CommandError.ts @@ -17,6 +17,7 @@ export default class CommandError extends Error { export enum CommandErrorType { Generic, MissingArgument, + InvalidArgument, InvalidUser, InvalidChannel, InvalidDuration, @@ -25,6 +26,7 @@ export enum CommandErrorType { const ErrorTitleMap: Record = { [CommandErrorType.Generic]: 'An exception was thrown', [CommandErrorType.MissingArgument]: 'Missing argument', + [CommandErrorType.InvalidArgument]: 'Invalid argument', [CommandErrorType.InvalidUser]: 'Invalid user', [CommandErrorType.InvalidChannel]: 'Invalid channel', [CommandErrorType.InvalidDuration]: 'Invalid duration', diff --git a/bots/discord/src/commands/admin/eval.ts b/bots/discord/src/commands/admin/eval.ts new file mode 100644 index 0000000..8d80997 --- /dev/null +++ b/bots/discord/src/commands/admin/eval.ts @@ -0,0 +1,34 @@ +import { inspect } from 'util' +import { runInNewContext } from 'vm' +import { ApplicationCommandOptionType } from 'discord.js' + +import { AdminCommand } from '$/classes/Command' +import { createSuccessEmbed } from '$/utils/discord/embeds' + +export default new AdminCommand({ + name: 'eval', + description: 'Make the bot less sentient by evaluating code', + options: { + code: { + description: 'The code to evaluate', + type: ApplicationCommandOptionType.String, + required: true, + }, + ['show-hidden']: { + description: 'Show hidden properties', + type: ApplicationCommandOptionType.Boolean, + required: false, + }, + }, + async execute(context, trigger, { code, 'show-hidden': showHidden }) { + await trigger.reply({ + ephemeral: true, + embeds: [ + createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``).addFields({ + name: 'Result', + value: `\`\`\`js\n${inspect(runInNewContext(code, { client: trigger.client, context, trigger }), { depth: 1, showHidden, getters: true, numericSeparator: true, showProxy: true })}\`\`\``, + }), + ], + }) + }, +}) diff --git a/bots/discord/src/commands/admin/exception-test.ts b/bots/discord/src/commands/admin/exception-test.ts new file mode 100644 index 0000000..68ac9c8 --- /dev/null +++ b/bots/discord/src/commands/admin/exception-test.ts @@ -0,0 +1,21 @@ +import { ApplicationCommandOptionType } from 'discord.js' + +import { AdminCommand } from '$/classes/Command' +import CommandError, { CommandErrorType } from '$/classes/CommandError' + +export default new AdminCommand({ + name: 'exception-test', + description: 'Makes the bot intentionally hate you by throwing an exception', + options: { + type: { + description: 'The type of exception to throw', + type: ApplicationCommandOptionType.String, + required: true, + choices: Object.keys(CommandErrorType).map(k => ({ name: k, value: k })), + }, + }, + async execute(_, __, { type }) { + if (type === 'Process') throw new Error('Intentional process exception') + throw new CommandError(CommandErrorType[type as keyof typeof CommandErrorType], 'Intentional bot design') // ;) + }, +}) diff --git a/bots/discord/src/commands/admin/slash-commands.ts b/bots/discord/src/commands/admin/slash-commands.ts new file mode 100644 index 0000000..30d5d27 --- /dev/null +++ b/bots/discord/src/commands/admin/slash-commands.ts @@ -0,0 +1,93 @@ +import { ApplicationCommandOptionType, Routes } from 'discord.js' + +import { AdminCommand } from '$/classes/Command' +import CommandError, { CommandErrorType } from '$/classes/CommandError' + +import { createSuccessEmbed } from '$/utils/discord/embeds' + +const SubcommandOptions = { + where: { + description: 'Where to register the commands', + type: ApplicationCommandOptionType.String, + choices: [ + { name: 'globally', value: 'global' }, + { name: 'this server', value: 'server' }, + ], + required: true, + }, +} as const + +export default new AdminCommand({ + name: 'slash-commands', + description: 'Register or delete slash commands', + options: { + register: { + description: 'Register slash commands', + type: ApplicationCommandOptionType.Subcommand, + options: SubcommandOptions, + }, + delete: { + description: 'Delete slash commands', + type: ApplicationCommandOptionType.Subcommand, + options: SubcommandOptions, + }, + }, + allowMessageCommand: true, + async execute(context, trigger, { delete: deleteOption, register }) { + const action = register ? 'register' : 'delete' + const { where } = (deleteOption ?? register)! + + if (!trigger.inGuild()) + throw new CommandError(CommandErrorType.Generic, 'This command can only be used in a server.') + + const { global: globalCommands, guild: guildCommands } = Object.groupBy( + Object.values(context.discord.commands), + cmd => (cmd.global ? 'global' : 'guild'), + ) + + const { + client, + client: { rest }, + } = trigger + + let response: string | undefined + + switch (action) { + case 'register': + if (where === 'global') { + response = 'Registered global slash commands' + + await rest.put(Routes.applicationCommands(client.application.id), { + body: globalCommands?.map(c => c.json), + }) + } else { + response = 'Registered slash commands on this server' + + await rest.put(Routes.applicationGuildCommands(client.application.id, trigger.guildId), { + body: guildCommands?.map(c => c.json), + }) + } + + break + + case 'delete': + if (where === 'global') { + response = 'Deleted global slash commands' + + await rest.put(Routes.applicationCommands(client.application.id), { + body: [], + }) + } else { + response = 'Deleted slash commands on this server' + + await rest.put(Routes.applicationGuildCommands(client.application.id, trigger.guildId), { + body: [], + }) + } + + break + } + + await trigger.reply({ embeds: [createSuccessEmbed(response!)] }) + }, +}) diff --git a/bots/discord/src/commands/admin/stop.ts b/bots/discord/src/commands/admin/stop.ts new file mode 100644 index 0000000..e4dd6c7 --- /dev/null +++ b/bots/discord/src/commands/admin/stop.ts @@ -0,0 +1,24 @@ +import { AdminCommand } from '$/classes/Command' + +export default new AdminCommand({ + name: 'stop', + description: "You don't want to run this unless the bot starts to go insane, and like, you really need to stop it.", + async execute({ api, logger, executor }, trigger) { + api.intentionallyDisconnecting = true + + logger.fatal('Stopping bot...') + trigger.reply({ + content: 'Stopping... (I will go offline once done)', + ephemeral: true, + }) + + if (!api.client.disconnected) api.client.disconnect() + logger.warn('Disconnected from API') + + trigger.client.destroy() + logger.warn('Disconnected from Discord API') + + logger.info(`Bot stopped, requested by ${executor.id}`) + process.exit(0) + }, +}) diff --git a/bots/discord/src/commands/development/eval.ts b/bots/discord/src/commands/development/eval.ts deleted file mode 100644 index 59a9570..0000000 --- a/bots/discord/src/commands/development/eval.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { inspect } from 'util' -import { SlashCommandBuilder } from 'discord.js' - -import { createSuccessEmbed } from '$/utils/discord/embeds' -import type { Command } from '../types' - -export default { - data: new SlashCommandBuilder() - .setName('eval') - .setDescription('Make the bot less sentient by evaluating code') - .addStringOption(option => option.setName('code').setDescription('The code to evaluate').setRequired(true)) - .setDMPermission(true) - .toJSON(), - - adminOnly: true, - global: true, - - async execute(_, interaction) { - const code = interaction.options.getString('code', true) - - await interaction.reply({ - ephemeral: true, - embeds: [ - createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``).addFields({ - name: 'Result', - // biome-ignore lint/security/noGlobalEval: Deal with it - value: `\`\`\`js\n${inspect(eval(code), { depth: 1 })}\`\`\``, - }), - ], - }) - }, -} satisfies Command diff --git a/bots/discord/src/commands/development/exception-test.ts b/bots/discord/src/commands/development/exception-test.ts deleted file mode 100644 index cde89ae..0000000 --- a/bots/discord/src/commands/development/exception-test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js' - -import CommandError, { CommandErrorType } from '$/classes/CommandError' -import type { Command } from '../types' - -export default { - data: new SlashCommandBuilder() - .setName('exception-test') - .setDescription('Makes the bot intentionally hate you by throwing an exception') - .addStringOption(option => - option - .setName('type') - .setDescription('The type of exception to throw') - .setRequired(true) - .addChoices( - Object.keys(CommandErrorType).map( - k => - ({ - name: k, - value: k, - }) as const, - ), - ), - ) - .setDMPermission(true) - .toJSON(), - - adminOnly: true, - global: true, - - async execute(_, interaction) { - const type = interaction.options.getString('type', true) - if (type === 'Process') throw new Error('Intentional process exception') - throw new CommandError(CommandErrorType[type as keyof typeof CommandErrorType], 'Intentional bot design') // ;) - }, -} satisfies Command diff --git a/bots/discord/src/commands/development/stop.ts b/bots/discord/src/commands/development/stop.ts deleted file mode 100644 index 3e7bbf4..0000000 --- a/bots/discord/src/commands/development/stop.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js' - -import type { Command } from '../types' - -export default { - data: new SlashCommandBuilder() - .setName('stop') - .setDescription( - "You don't want to run this unless the bot starts to go insane, and like, you really need to stop it.", - ) - .setDMPermission(true) - .toJSON(), - - adminOnly: true, - global: true, - - async execute({ api, logger }, interaction) { - api.isStopping = true - - logger.fatal('Stopping bot...') - await interaction.reply({ - content: 'Stopping... (I will go offline once done)', - ephemeral: true, - }) - - api.client.disconnect() - logger.warn('Disconnected from API') - - await interaction.client.destroy() - logger.warn('Disconnected from Discord API') - - logger.info(`Bot stopped, requested by ${interaction.user.id}`) - process.exit(0) - }, -} satisfies Command diff --git a/bots/discord/src/commands/fun/coinflip.ts b/bots/discord/src/commands/fun/coinflip.ts index e6e7a95..df8ffcd 100644 --- a/bots/discord/src/commands/fun/coinflip.ts +++ b/bots/discord/src/commands/fun/coinflip.ts @@ -1,32 +1,37 @@ +import { EmbedBuilder } from 'discord.js' + +import Command from '$/classes/Command' import { applyCommonEmbedStyles } from '$/utils/discord/embeds' -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js' - -import type { Command } from '../types' - -export default { - data: new SlashCommandBuilder().setName('coinflip').setDescription('Do a coinflip!').setDMPermission(true).toJSON(), +export default new Command({ + name: 'coinflip', + description: 'Do a coinflip!', global: true, - - async execute(_, interaction) { + requirements: { + defaultCondition: 'pass', + }, + allowMessageCommand: true, + async execute(_, trigger) { const result = Math.random() < 0.5 ? ('heads' as const) : ('tails' as const) - const embed = applyCommonEmbedStyles(new EmbedBuilder().setTitle('Flipping... 🪙'), true, false, false) + const embed = applyCommonEmbedStyles(new EmbedBuilder().setTitle('Flipping... 🪙'), false, false, true) - await interaction.reply({ - embeds: [embed.toJSON()], - }) + const reply = await trigger + .reply({ + embeds: [embed.toJSON()], + }) + .then(it => it.fetch()) embed.setTitle(`The coin landed on... **${result.toUpperCase()}**! ${EmojiMap[result]}`) setTimeout( () => - interaction.editReply({ + reply.edit({ embeds: [embed.toJSON()], }), 1500, ) }, -} satisfies Command +}) const EmojiMap: Record<'heads' | 'tails', string> = { heads: '🤯', diff --git a/bots/discord/src/commands/fun/reply.ts b/bots/discord/src/commands/fun/reply.ts index 4a5c530..69a3def 100644 --- a/bots/discord/src/commands/fun/reply.ts +++ b/bots/discord/src/commands/fun/reply.ts @@ -1,44 +1,46 @@ -import { SlashCommandBuilder, type TextBasedChannel } from 'discord.js' +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { ApplicationCommandOptionType, Message } from 'discord.js' +import { ModerationCommand } from '../../classes/Command' -import { config } from '$/context' -import type { Command } from '../types' - -export default { - data: new SlashCommandBuilder() - .setName('reply') - .setDescription('Send a message as the bot') - .addStringOption(option => option.setName('message').setDescription('The message to send').setRequired(true)) - .addStringOption(option => - option - .setName('reference') - .setDescription('The message ID to reply to (use `latest` to reply to the latest message)') - .setRequired(false), - ) - .toJSON(), - - memberRequirements: { - roles: config.moderation?.roles ?? [], +export default new ModerationCommand({ + name: 'reply', + description: 'Send a message as the bot', + options: { + message: { + description: 'The message to send', + required: true, + type: ApplicationCommandOptionType.String, + }, + reference: { + description: 'The message ID to reply to (use `latest` to reply to the latest message)', + required: false, + type: ApplicationCommandOptionType.String, + }, }, + allowMessageCommand: false, + async execute({ logger, executor }, trigger, { reference: ref, message: msg }) { + if (trigger instanceof Message) return - global: false, - - async execute({ logger }, interaction) { - const msg = interaction.options.getString('message', true) - const ref = interaction.options.getString('reference') - - const channel = (await interaction.guild!.channels.fetch(interaction.channelId)) as TextBasedChannel - const refMsg = ref?.startsWith('latest') ? (await channel.messages.fetch({ limit: 1 })).at(0)?.id : ref + const channel = await trigger.guild!.channels.fetch(trigger.channelId) + if (!channel?.isTextBased()) + throw new CommandError( + CommandErrorType.InvalidArgument, + 'This command can only be used in or on text channels', + ) + const refMsg = ref?.startsWith('latest') + ? await channel.messages.fetch({ limit: 1 }).then(it => it.first()) + : ref await channel.send({ content: msg, reply: refMsg ? { messageReference: refMsg, failIfNotExists: true } : undefined, }) - logger.info(`User ${interaction.user.tag} made the bot say: ${msg}`) + logger.info(`User ${executor.user.tag} made the bot say: ${msg}`) - await interaction.reply({ + await trigger.reply({ content: 'OK!', ephemeral: true, }) }, -} satisfies Command +}) diff --git a/bots/discord/src/commands/moderation/ban.ts b/bots/discord/src/commands/moderation/ban.ts index 1b702b9..2f18f9b 100644 --- a/bots/discord/src/commands/moderation/ban.ts +++ b/bots/discord/src/commands/moderation/ban.ts @@ -1,58 +1,58 @@ -import { SlashCommandBuilder } from 'discord.js' - -import type { Command } from '../types' - +import { ModerationCommand } from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import { config } from '$/context' import { createModerationActionEmbed } from '$/utils/discord/embeds' import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { parseDuration } from '$/utils/duration' -export default { - data: new SlashCommandBuilder() - .setName('ban') - .setDescription('Ban a user') - .addUserOption(option => option.setName('user').setRequired(true).setDescription('The user to ban')) - .addStringOption(option => option.setName('reason').setDescription('The reason for banning the user')) - .addStringOption(option => - option.setName('dmd').setDescription('Duration to delete messages (must be from 0 to 7 days)'), - ) - .toJSON(), - - memberRequirements: { - roles: config.moderation?.roles ?? [], +export default new ModerationCommand({ + name: 'ban', + description: 'Ban a user', + options: { + user: { + description: 'The user to ban', + required: true, + type: ModerationCommand.OptionType.User, + }, + reason: { + description: 'The reason for banning the user', + required: false, + type: ModerationCommand.OptionType.String, + }, + dmd: { + description: 'Duration to delete messages (must be from 0 to 7 days)', + required: false, + type: ModerationCommand.OptionType.String, + }, }, + async execute({ logger, executor }, interaction, { user, reason, dmd }) { + const guild = await interaction.client.guilds.fetch(interaction.guildId) + const member = await guild.members.fetch(user).catch(() => {}) + const moderator = await guild.members.fetch(executor.user) - global: false, + if (member) { + if (!member.bannable) + throw new CommandError(CommandErrorType.Generic, 'This user cannot be banned by the bot.') - async execute({ logger }, interaction) { - const user = interaction.options.getUser('user', true) - const reason = interaction.options.getString('reason') ?? 'No reason provided' - const dmd = interaction.options.getString('dmd') - - const member = await interaction.guild!.members.fetch(user.id) - const moderator = await interaction.guild!.members.fetch(interaction.user.id) - - if (member.bannable) throw new CommandError(CommandErrorType.Generic, 'This user cannot be banned by the bot.') - - if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) - throw new CommandError( - CommandErrorType.InvalidUser, - 'You cannot ban a user with a role equal to or higher than yours.', - ) + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) + throw new CommandError( + CommandErrorType.InvalidUser, + 'You cannot ban a user with a role equal to or higher than yours.', + ) + } const dms = Math.floor(dmd ? parseDuration(dmd) : 0 / 1000) await interaction.guild!.members.ban(user, { - reason: `Banned by moderator ${interaction.user.tag} (${interaction.user.id}): ${reason}`, + reason: `Banned by moderator ${executor.user.tag} (${executor.id}): ${reason}`, deleteMessageSeconds: dms, }) await sendModerationReplyAndLogs( interaction, - createModerationActionEmbed('Banned', user, interaction.user, reason), + createModerationActionEmbed('Banned', user, executor.user, reason), ) + logger.info( - `${interaction.user.tag} (${interaction.user.id}) banned ${user.tag} (${user.id}) because ${reason}, deleting their messages sent in the previous ${dms}s`, + `${executor.user.tag} (${executor.id}) banned ${user.tag} (${user.id}) because ${reason}, deleting their messages sent in the previous ${dms}s`, ) }, -} satisfies Command +}) diff --git a/bots/discord/src/commands/moderation/cure.ts b/bots/discord/src/commands/moderation/cure.ts index c00270f..15b144e 100644 --- a/bots/discord/src/commands/moderation/cure.ts +++ b/bots/discord/src/commands/moderation/cure.ts @@ -1,31 +1,24 @@ -import { SlashCommandBuilder } from 'discord.js' - -import type { Command } from '../types' - -import { config } from '$/context' +import { ModerationCommand } from '$/classes/Command' import { createSuccessEmbed } from '$/utils/discord/embeds' import { cureNickname } from '$/utils/discord/moderation' -export default { - data: new SlashCommandBuilder() - .setName('cure') - .setDescription("Cure a member's nickname") - .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to cure')) - .toJSON(), - - memberRequirements: { - roles: config.moderation?.roles ?? [], +export default new ModerationCommand({ + name: 'cure', + description: "Cure a member's nickname", + options: { + member: { + description: 'The member to cure', + required: true, + type: ModerationCommand.OptionType.User, + }, }, - - global: false, - - async execute(_, interaction) { - const user = interaction.options.getUser('member', true) - const member = await interaction.guild!.members.fetch(user.id) + async execute(_, interaction, { member: user }) { + const guild = await interaction.client.guilds.fetch(interaction.guildId) + const member = await guild.members.fetch(user) await cureNickname(member) await interaction.reply({ embeds: [createSuccessEmbed(null, `Cured nickname for ${member.toString()}`)], ephemeral: true, }) }, -} satisfies Command +}) diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index aeba84d..2fa1313 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -1,44 +1,47 @@ -import { SlashCommandBuilder } from 'discord.js' - +import { ModerationCommand } from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import { applyRolePreset, removeRolePreset } from '$/utils/discord/rolePresets' -import type { Command } from '../types' - -import { config } from '$/context' import { createModerationActionEmbed } from '$/utils/discord/embeds' import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' +import { applyRolePreset, removeRolePreset } from '$/utils/discord/rolePresets' import { parseDuration } from '$/utils/duration' -export default { - data: new SlashCommandBuilder() - .setName('mute') - .setDescription('Mute a member') - .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to mute')) - .addStringOption(option => option.setName('reason').setDescription('The reason for muting the member')) - .addStringOption(option => option.setName('duration').setDescription('The duration of the mute')) - .toJSON(), - - memberRequirements: { - roles: config.moderation?.roles ?? [], +export default new ModerationCommand({ + name: 'mute', + description: 'Mute a member', + options: { + member: { + description: 'The member to mute', + required: true, + type: ModerationCommand.OptionType.User, + }, + reason: { + description: 'The reason for muting the member', + required: false, + type: ModerationCommand.OptionType.String, + }, + duration: { + description: 'The duration of the mute', + required: false, + type: ModerationCommand.OptionType.String, + }, }, + async execute( + { logger, executor }, + interaction, + { member: user, reason = 'No reason provided', duration: durationInput }, + ) { + const guild = await interaction.client.guilds.fetch(interaction.guildId) + const member = await guild.members.fetch(user.id) + const moderator = await guild.members.fetch(executor.id) + const duration = durationInput ? parseDuration(durationInput) : Infinity - global: false, - - async execute({ logger }, interaction, { isExecutorBotAdmin: isExecutorAdmin }) { - const user = interaction.options.getUser('member', true) - const reason = interaction.options.getString('reason') ?? 'No reason provided' - const duration = interaction.options.getString('duration') - const durationMs = duration ? parseDuration(duration) : null - - if (Number.isInteger(durationMs) && durationMs! < 1) + if (Number.isInteger(duration) && duration! < 1) throw new CommandError( CommandErrorType.InvalidDuration, 'The duration must be at least 1 millisecond long.', ) - const expires = durationMs ? Date.now() + durationMs : null - const moderator = await interaction.guild!.members.fetch(interaction.user.id) - const member = await interaction.guild!.members.fetch(user.id) + const expires = Math.max(duration, Date.now() + duration) if (!member) throw new CommandError( CommandErrorType.InvalidUser, @@ -48,25 +51,25 @@ export default { if (!member.manageable) throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') - if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !isExecutorAdmin) + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) throw new CommandError( CommandErrorType.InvalidUser, 'You cannot mute a user with a role equal to or higher than yours.', ) - await applyRolePreset(member, 'mute', durationMs ? Date.now() + durationMs : null) + await applyRolePreset(member, 'mute', expires) await sendModerationReplyAndLogs( interaction, - createModerationActionEmbed('Muted', user, interaction.user, reason, durationMs), + createModerationActionEmbed('Muted', user, executor.user, reason, duration), ) - if (durationMs) + if (duration) setTimeout(() => { removeRolePreset(member, 'mute') - }, durationMs) + }, duration) logger.info( - `Moderator ${interaction.user.tag} (${interaction.user.id}) muted ${user.tag} (${user.id}) until ${expires} because ${reason}`, + `Moderator ${executor.user.tag} (${executor.user.id}) muted ${user.tag} (${user.id}) until ${expires} because ${reason}`, ) }, -} satisfies Command +}) diff --git a/bots/discord/src/commands/moderation/purge.ts b/bots/discord/src/commands/moderation/purge.ts index 75eb398..8f59a31 100644 --- a/bots/discord/src/commands/moderation/purge.ts +++ b/bots/discord/src/commands/moderation/purge.ts @@ -1,37 +1,32 @@ -import { EmbedBuilder, GuildChannel, SlashCommandBuilder } from 'discord.js' +import { EmbedBuilder, GuildChannel } from 'discord.js' +import { ModerationCommand } from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import { config } from '$/context' import { applyCommonEmbedStyles } from '$/utils/discord/embeds' -import type { Command } from '../types' - -export default { - data: new SlashCommandBuilder() - .setName('purge') - .setDescription('Purge messages from a channel') - .addIntegerOption(option => - option.setName('amount').setDescription('The amount of messages to remove').setMaxValue(100).setMinValue(1), - ) - .addUserOption(option => - option.setName('user').setDescription('The user to remove messages from (needs `until`)'), - ) - .addStringOption(option => - option.setName('until').setDescription('The message ID to remove messages until (overrides `amount`)'), - ) - .toJSON(), - - memberRequirements: { - roles: config.moderation?.roles ?? [], +export default new ModerationCommand({ + name: 'purge', + description: 'Purge messages from a channel', + options: { + amount: { + description: 'The amount of messages to remove', + required: false, + type: ModerationCommand.OptionType.Integer, + min: 1, + max: 100, + }, + user: { + description: 'The user to remove messages from (needs `until`)', + required: false, + type: ModerationCommand.OptionType.User, + }, + until: { + description: 'The message ID to remove messages until (overrides `amount`)', + required: false, + type: ModerationCommand.OptionType.String, + }, }, - - global: false, - - async execute({ logger }, interaction) { - const amount = interaction.options.getInteger('amount') - const user = interaction.options.getUser('user') - const until = interaction.options.getString('until') - + async execute({ logger, executor }, interaction, { amount, user, until }) { if (!amount && !until) throw new CommandError(CommandErrorType.MissingArgument, 'Either `amount` or `until` must be provided.') @@ -59,8 +54,9 @@ export default { await channel.bulkDelete(messages, true) logger.info( - `Moderator ${interaction.user.tag} (${interaction.user.id}) purged ${messages.size} messages in #${channel.name} (${channel.id})`, + `Moderator ${executor.user.tag} (${executor.id}) purged ${messages.size} messages in #${channel.name} (${channel.id})`, ) + await reply.edit({ embeds: [ embed.setTitle('Purged messages').setDescription(null).addFields({ @@ -70,4 +66,4 @@ export default { ], }) }, -} satisfies Command +}) diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts index 495744b..aa33817 100644 --- a/bots/discord/src/commands/moderation/role-preset.ts +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -1,49 +1,48 @@ -import { SlashCommandBuilder } from 'discord.js' - +import { ModerationCommand } from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' import { sendPresetReplyAndLogs } from '$/utils/discord/moderation' import { applyRolePreset, removeRolePreset } from '$/utils/discord/rolePresets' import { parseDuration } from '$/utils/duration' -import type { Command } from '../types' -export default { - data: new SlashCommandBuilder() - .setName('role-preset') - .setDescription('Manage role presets for a member') - .addStringOption(option => - option - .setName('action') - .setRequired(true) - .setDescription('The action to perform') - .addChoices([ - { name: 'apply', value: 'apply' }, - { name: 'remove', value: 'remove' }, - ]), - ) - .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to manage')) - .addStringOption(option => - option.setName('preset').setRequired(true).setDescription('The preset to apply or remove'), - ) - .addStringOption(option => - option.setName('duration').setDescription('The duration to apply the preset for (only for apply action)'), - ) - .toJSON(), - - memberRequirements: { - roles: ['955220417969262612', '973886585294704640'], +const SubcommandOptions = { + member: { + description: 'The member to manage', + required: true, + type: ModerationCommand.OptionType.User, }, + preset: { + description: 'The preset to apply or remove', + required: true, + type: ModerationCommand.OptionType.String, + }, + duration: { + description: 'The duration to apply the preset for (only for apply action)', + required: false, + type: ModerationCommand.OptionType.String, + }, +} as const - global: false, +export default new ModerationCommand({ + name: 'role-preset', + description: 'Manage role presets for a member', + options: { + apply: { + description: 'Apply a role preset to a member', + type: ModerationCommand.OptionType.Subcommand, + options: SubcommandOptions, + }, + remove: { + description: 'Remove a role preset from a member', + type: ModerationCommand.OptionType.Subcommand, + options: SubcommandOptions, + }, + }, + async execute({ logger, executor }, trigger, { apply, remove }) { + let expires: number | undefined + const { member: user, duration: durationInput, preset } = (apply ?? remove)! + const moderator = await trigger.guild!.members.fetch(executor.user.id) + const member = await trigger.guild!.members.fetch(user.id) - async execute({ logger }, interaction, { isExecutorBotAdmin: isExecutorAdmin }) { - const action = interaction.options.getString('action', true) as 'apply' | 'remove' - const user = interaction.options.getUser('member', true) - const preset = interaction.options.getString('preset', true) - const duration = interaction.options.getString('duration') - - let expires: number | null | undefined = undefined - const moderator = await interaction.guild!.members.fetch(interaction.user.id) - const member = await interaction.guild!.members.fetch(user.id) if (!member) throw new CommandError( CommandErrorType.InvalidUser, @@ -53,29 +52,29 @@ export default { if (!member.manageable) throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') - if (action === 'apply') { - const durationMs = duration ? parseDuration(duration) : null - if (Number.isInteger(durationMs) && durationMs! < 1) + if (apply) { + const duration = durationInput ? parseDuration(durationInput) : Infinity + if (Number.isInteger(duration) && duration! < 1) throw new CommandError( CommandErrorType.InvalidDuration, 'The duration must be at least 1 millisecond long.', ) - if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !isExecutorAdmin) + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) throw new CommandError( CommandErrorType.InvalidUser, 'You cannot apply a role preset to a user with a role equal to or higher than yours.', ) - expires = durationMs ? Date.now() + durationMs : null + expires = Math.max(duration, Date.now() + duration) await applyRolePreset(member, preset, expires) logger.info( - `Moderator ${interaction.user.tag} (${interaction.user.id}) applied role preset ${preset} to ${user.id} until ${expires}`, + `Moderator ${executor.user.tag} (${executor.user.id}) applied role preset ${preset} to ${user.id} until ${expires}`, ) - } else if (action === 'remove') { + } else if (remove) { await removeRolePreset(member, preset) logger.info( - `Moderator ${interaction.user.tag} (${interaction.user.id}) removed role preset ${preset} from ${user.id}`, + `Moderator ${executor.user.tag} (${executor.user.id}) removed role preset ${preset} from ${user.id}`, ) } @@ -84,6 +83,6 @@ export default { removeRolePreset(member, preset) }, expires) - await sendPresetReplyAndLogs(action, interaction, user, preset, expires) + await sendPresetReplyAndLogs(apply ? 'apply' : 'remove', trigger, executor, user, preset, expires) }, -} satisfies Command +}) diff --git a/bots/discord/src/commands/moderation/slowmode.ts b/bots/discord/src/commands/moderation/slowmode.ts index 8cc3815..6fe055f 100644 --- a/bots/discord/src/commands/moderation/slowmode.ts +++ b/bots/discord/src/commands/moderation/slowmode.ts @@ -1,39 +1,31 @@ import { createSuccessEmbed } from '$/utils/discord/embeds' import { durationToString, parseDuration } from '$/utils/duration' -import { SlashCommandBuilder } from 'discord.js' - +import { ModerationCommand } from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import { config } from '$/context' -import type { Command } from '../types' +import { ChannelType } from 'discord.js' -export default { - data: new SlashCommandBuilder() - .setName('slowmode') - .setDescription('Set a slowmode for the current channel') - .addStringOption(option => option.setName('duration').setDescription('The duration to set').setRequired(true)) - .addStringOption(option => - option - .setName('channel') - .setDescription('The channel to set the slowmode on (defaults to current channel)') - .setRequired(false), - ) - .toJSON(), - - memberRequirements: { - roles: config.moderation?.roles ?? [], +export default new ModerationCommand({ + name: 'slowmode', + description: 'Set a slowmode for a channel', + options: { + duration: { + description: 'The duration to set', + required: true, + type: ModerationCommand.OptionType.String, + }, + channel: { + description: 'The channel to set the slowmode on (defaults to current channel)', + required: false, + type: ModerationCommand.OptionType.Channel, + types: [ChannelType.GuildText], + }, }, + async execute({ logger, executor }, interaction, { duration: durationInput, channel: channelInput }) { + const channel = channelInput ?? (await interaction.guild!.channels.fetch(interaction.channelId)) + const duration = parseDuration(durationInput) - global: false, - - async execute({ logger }, interaction) { - const durationStr = interaction.options.getString('duration', true) - const id = interaction.options.getChannel('channel')?.id ?? interaction.channelId - - const duration = parseDuration(durationStr) - const channel = await interaction.guild!.channels.fetch(id) - - if (!channel?.isTextBased()) + if (!channel?.isTextBased() || channel.isDMBased()) throw new CommandError( CommandErrorType.InvalidChannel, 'The supplied channel is not a text channel or does not exist.', @@ -46,10 +38,7 @@ export default { 'Duration out of range, must be between 0s and 6h.', ) - logger.info(`Setting slowmode to ${duration}ms on ${channel.id}`) - - await channel.setRateLimitPerUser(duration / 1000, `Set by ${interaction.user.tag} (${interaction.user.id})`) - + await channel.setRateLimitPerUser(duration / 1000, `Set by ${executor.user.tag} (${executor.id})`) await interaction.reply({ embeds: [ createSuccessEmbed( @@ -59,7 +48,7 @@ export default { }) logger.info( - `${interaction.user.tag} (${interaction.user.id}) set the slowmode on ${channel.name} (${channel.id}) to ${duration}ms`, + `${executor.user.tag} (${executor.id}) set the slowmode on ${channel.name} (${channel.id}) to ${duration}ms`, ) }, -} satisfies Command +}) diff --git a/bots/discord/src/commands/moderation/unban.ts b/bots/discord/src/commands/moderation/unban.ts index 5bcfa93..c0c86f7 100644 --- a/bots/discord/src/commands/moderation/unban.ts +++ b/bots/discord/src/commands/moderation/unban.ts @@ -1,33 +1,21 @@ -import { SlashCommandBuilder } from 'discord.js' - -import type { Command } from '../types' - -import { config } from '$/context' +import { ModerationCommand } from '$/classes/Command' import { createModerationActionEmbed } from '$/utils/discord/embeds' import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' -export default { - data: new SlashCommandBuilder() - .setName('unban') - .setDescription('Unban a user') - .addUserOption(option => option.setName('user').setRequired(true).setDescription('The user to unban')) - .toJSON(), - - memberRequirements: { - roles: config.moderation?.roles ?? [], +export default new ModerationCommand({ + name: 'unban', + description: 'Unban a user', + options: { + user: { + description: 'The user to unban', + required: true, + type: ModerationCommand.OptionType.User, + }, }, + async execute({ logger, executor }, interaction, { user }) { + await interaction.guild!.members.unban(user, `Unbanned by moderator ${executor.user.tag} (${executor.id})`) - global: false, - - async execute({ logger }, interaction) { - const user = interaction.options.getUser('user', true) - - await interaction.guild!.members.unban( - user, - `Unbanned by moderator ${interaction.user.tag} (${interaction.user.id})`, - ) - - await sendModerationReplyAndLogs(interaction, createModerationActionEmbed('Unbanned', user, interaction.user)) - logger.info(`${interaction.user.tag} (${interaction.user.id}) unbanned ${user.tag} (${user.id})`) + await sendModerationReplyAndLogs(interaction, createModerationActionEmbed('Unbanned', user, executor.user)) + logger.info(`${executor.user.tag} (${executor.id}) unbanned ${user.tag} (${user.id})`) }, -} satisfies Command +}) diff --git a/bots/discord/src/commands/moderation/unmute.ts b/bots/discord/src/commands/moderation/unmute.ts index 2255a09..fe164b6 100644 --- a/bots/discord/src/commands/moderation/unmute.ts +++ b/bots/discord/src/commands/moderation/unmute.ts @@ -1,29 +1,22 @@ -import { SlashCommandBuilder } from 'discord.js' - +import { ModerationCommand } from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import { config } from '$/context' import { appliedPresets } from '$/database/schemas' import { createModerationActionEmbed } from '$/utils/discord/embeds' import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { removeRolePreset } from '$/utils/discord/rolePresets' import { and, eq } from 'drizzle-orm' -import type { Command } from '../types' -export default { - data: new SlashCommandBuilder() - .setName('unmute') - .setDescription('Unmute a member') - .addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to unmute')) - .toJSON(), - - memberRequirements: { - roles: config.moderation?.roles ?? [], +export default new ModerationCommand({ + name: 'unmute', + description: 'Unmute a member', + options: { + member: { + description: 'The member to unmute', + required: true, + type: ModerationCommand.OptionType.User, + }, }, - - global: false, - - async execute({ logger, database }, interaction) { - const user = interaction.options.getUser('member', true) + async execute({ logger, database, executor }, interaction, { member: user }) { const member = await interaction.guild!.members.fetch(user.id) if (!member) throw new CommandError( @@ -39,8 +32,8 @@ export default { throw new CommandError(CommandErrorType.Generic, 'This user is not muted.') await removeRolePreset(member, 'mute') - await sendModerationReplyAndLogs(interaction, createModerationActionEmbed('Unmuted', user, interaction.user)) + await sendModerationReplyAndLogs(interaction, createModerationActionEmbed('Unmuted', user, executor.user)) - logger.info(`Moderator ${interaction.user.tag} (${interaction.user.id}) unmuted ${user.tag} (${user.id})`) + logger.info(`Moderator ${executor.user.tag} (${executor.id}) unmuted ${user.tag} (${user.id})`) }, -} satisfies Command +}) diff --git a/bots/discord/src/commands/types.ts b/bots/discord/src/commands/types.ts deleted file mode 100644 index ceb232f..0000000 --- a/bots/discord/src/commands/types.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { SlashCommandBuilder } from '@discordjs/builders' -import type { ChatInputCommandInteraction } from 'discord.js' - -// Temporary system -export type Command = { - data: ReturnType - // The function has to return void or Promise - // because TS may complain about some code paths not returning a value - /** - * The function to execute when this command is triggered - * @param interaction The interaction that triggered this command - */ - execute: ( - context: typeof import('../context'), - interaction: ChatInputCommandInteraction, - info: Info, - ) => Promise | void - memberRequirements?: { - /** - * The mode to use when checking for requirements. - * - `all` means that the user needs meet all requirements specified. - * - `any` means that the user needs to meet any of the requirements specified. - * - * @default "all" - */ - mode?: 'all' | 'any' - /** - * The permissions required to use this command (in BitFields). - * - * - **0n** means that everyone can use this command. - * - **-1n** means that only bot owners can use this command. - * @default -1n - */ - permissions?: bigint - /** - * The roles required to use this command. - * By default, this is set to `[]`. - */ - roles?: string[] - } - /** - * Whether this command can only be used by bot admins. - * @default false - */ - adminOnly?: boolean - /** - * Whether to register this command as a global slash command. - * This is set to `false` and commands will be registered in allowed guilds only by default. - * @default false - */ - global?: boolean -} - -export interface Info { - isExecutorBotAdmin: boolean -} diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 2644af6..a52e71f 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -6,19 +6,19 @@ import { createLogger } from '@revanced/bot-shared' import { Client as DiscordClient, Partials } from 'discord.js' import { drizzle } from 'drizzle-orm/bun-sqlite' -// Export config first, as commands require them +// Export some things first, as commands require them import config from '../config.js' export { config } -import * as commands from './commands' -import * as schemas from './database/schemas' - -import type { Command } from './commands/types' - export const logger = createLogger({ level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, }) +import * as commands from './commands' +import * as schemas from './database/schemas' + +import type { default as Command, CommandOptionsOptions } from './classes/Command' + export const api = { client: new APIClient({ api: { @@ -81,8 +81,8 @@ export const discord = { }, partials: [Partials.Message, Partials.Reaction], }), - commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.data.name, cmd])) as Record< + commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.name, cmd])) as Record< string, - Command + Command >, } as const diff --git a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts index b608dd8..28c7f56 100644 --- a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts @@ -1,78 +1,22 @@ import CommandError from '$/classes/CommandError' -import { isAdmin } from '$/utils/discord/permissions' -import { createErrorEmbed, createStackTraceEmbed } from '$utils/discord/embeds' +import { createStackTraceEmbed } from '$utils/discord/embeds' import { on, withContext } from '$utils/discord/events' withContext(on, 'interactionCreate', async (context, interaction) => { if (!interaction.isChatInputCommand()) return - const { logger, discord, config } = context + const { logger, discord } = context const command = discord.commands[interaction.commandName] logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag}`) if (!command) return void logger.error(`Command ${interaction.commandName} not implemented but registered!!!`) - const isExecutorBotAdmin = isAdmin(await interaction.guild?.members.fetch(interaction.user.id) || interaction.user, config.admin) - - /** - * Admin check - */ - if (command.adminOnly && !isExecutorBotAdmin) - return void (await interaction.reply({ - embeds: [createErrorEmbed('Massive skill issue', 'This command can only be used by the bot admins.')], - ephemeral: true, - })) - - /** - * Sanity check - */ - if (!command.global && !interaction.inGuild()) { - logger.error(`Command ${interaction.commandName} cannot be run in DMs, but was registered as global`) - await interaction.reply({ - embeds: [createErrorEmbed('Cannot run that here', 'This command can only be used in a server.')], - ephemeral: true, - }) - return - } - - /** - * Permission checks - */ - if (interaction.inGuild()) { - // Bot owners get bypass - if (command.memberRequirements && !isExecutorBotAdmin) { - const { permissions = 0n, roles = [], mode } = command.memberRequirements - - const member = await interaction.guild!.members.fetch(interaction.user.id) - - const [missingPermissions, missingRoles] = [ - // This command is an owner-only command (the user is not an owner, checked above) - permissions <= 0n || - // or the user doesn't have the required permissions - (permissions > 0n && !interaction.memberPermissions.has(permissions)), - - // If not: - !roles.some(x => member.roles.cache.has(x)), - ] - - if ((mode === 'any' && missingPermissions && missingRoles) || missingPermissions || missingRoles) - return void interaction.reply({ - embeds: [ - createErrorEmbed( - 'Missing roles or permissions', - "You don't have the required roles or permissions to use this command.", - ), - ], - ephemeral: true, - }) - } - } - try { logger.debug(`Command ${interaction.commandName} being executed`) - await command.execute(context, interaction, { isExecutorBotAdmin }) + await command.onInteraction(context, interaction) } catch (err) { - logger.error(`Error while executing command ${interaction.commandName}:`, err) + if (!(err instanceof CommandError)) + logger.error(`Error while executing command ${interaction.commandName}:`, err) await interaction[interaction.replied ? 'followUp' : 'reply']({ embeds: [err instanceof CommandError ? err.toEmbed() : createStackTraceEmbed(err)], ephemeral: true, diff --git a/bots/discord/src/events/discord/messageCreate/messageCommand.ts b/bots/discord/src/events/discord/messageCreate/messageCommand.ts new file mode 100644 index 0000000..2b2ecd4 --- /dev/null +++ b/bots/discord/src/events/discord/messageCreate/messageCommand.ts @@ -0,0 +1,53 @@ +import { type CommandArguments, CommandSpecialArgumentType } from '$/classes/Command' +import CommandError from '$/classes/CommandError' +import { createStackTraceEmbed } from '$utils/discord/embeds' +import { on, withContext } from '$utils/discord/events' + +withContext(on, 'messageCreate', async (context, msg) => { + const { logger, discord, config } = context + + if (msg.author.bot) return + + const regex = new RegExp(`^(?:${config.prefix}|${msg.client.user.toString()}\\s*)([a-zA-Z-_]+)(?:\\s+)?(.+)?`) + const matches = msg.content.match(regex) + + if (!matches) return + const [, commandName, argsString] = matches + if (!commandName) return + + const command = discord.commands[commandName] + logger.debug(`Command ${commandName} being invoked by ${msg.author.id}`) + if (!command) return void logger.error(`Command ${commandName} not implemented`) + + const argsRegex: RegExp = /[^\s"]+|"([^"]*)"/g + const args: CommandArguments = [] + let match: RegExpExecArray | null + + // biome-ignore lint/suspicious/noAssignInExpressions: nuh uh + while ((match = argsRegex.exec(argsString ?? '')) !== null) { + const arg = match[1] ? match[1] : match[0] + const mentionMatch = arg.match(/<(@(?:!|&)?|#)(.+?)>/) + + if (mentionMatch) { + const [, prefix, id] = mentionMatch + + if (!id || !prefix) { + args.push('') + continue + } + + args.push({ + type: CommandSpecialArgumentType[prefix[1] === '&' ? 'Role' : prefix[0] === '#' ? 'Channel' : 'User'], + id, + }) + } else args.push(arg) + } + + try { + logger.debug(`Command ${commandName} being executed`) + await command.onMessage(context, msg, args) + } catch (err) { + if (!(err instanceof CommandError)) logger.error(`Error while executing command ${commandName}:`, err) + await msg.reply({ embeds: [err instanceof CommandError ? err.toEmbed() : createStackTraceEmbed(err)] }) + } +}) diff --git a/bots/discord/src/events/discord/messageCreate/scanMessage.ts b/bots/discord/src/events/discord/messageCreate/scanMessage.ts index 1009808..848253b 100644 --- a/bots/discord/src/events/discord/messageCreate/scanMessage.ts +++ b/bots/discord/src/events/discord/messageCreate/scanMessage.ts @@ -13,7 +13,7 @@ withContext(on, 'messageCreate', async (context, msg) => { } = context if (!config || !config.responses) return - if (msg.author.bot && !config.scanBots) + if (msg.author.bot && !config.scanBots) return if (!msg.inGuild() && !config.scanOutsideGuilds) return if (msg.inGuild() && msg.member?.partial) await msg.member.fetch() @@ -24,7 +24,11 @@ withContext(on, 'messageCreate', async (context, msg) => { try { logger.debug(`Classifying message ${msg.id}`) - const { response, label, replyToReplied } = await getResponseFromText(msg.content, filteredResponses, context) + const { response, label, replyToReplied } = await getResponseFromText( + msg.content, + filteredResponses, + context, + ) if (response) { logger.debug('Response found') diff --git a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index 4aa8a30..4e73609 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -13,8 +13,8 @@ import { import type { ConfigMessageScanResponseLabelConfig } from '$/../config.schema' import { responses } from '$/database/schemas' import { handleUserResponseCorrection } from '$/utils/discord/messageScan' -import { eq } from 'drizzle-orm' import { isAdmin } from '$/utils/discord/permissions' +import { eq } from 'drizzle-orm' const PossibleReactions = Object.values(Reactions) as string[] diff --git a/bots/discord/src/events/discord/ready.ts b/bots/discord/src/events/discord/ready.ts index 73e8742..e40af8a 100644 --- a/bots/discord/src/events/discord/ready.ts +++ b/bots/discord/src/events/discord/ready.ts @@ -7,9 +7,7 @@ import { on, withContext } from 'src/utils/discord/events' export default withContext(on, 'ready', ({ config, logger }, client) => { logger.info(`Connected to Discord API, logged in as ${client.user.displayName} (@${client.user.tag})!`) - logger.info( - `Bot is in ${client.guilds.cache.size} guilds, if this is not expected, please run the /leave-unknowns command`, - ) + logger.info(`Bot is in ${client.guilds.cache.size} guilds`) if (config.rolePresets) { removeExpiredPresets(client) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index 5352901..8c285bd 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -1,9 +1,5 @@ import { type Response, responses } from '$/database/schemas' -import type { - Config, - ConfigMessageScanResponse, - ConfigMessageScanResponseLabelConfig -} from 'config.schema' +import type { Config, ConfigMessageScanResponse, ConfigMessageScanResponseLabelConfig } from 'config.schema' import type { Message, PartialUser, User } from 'discord.js' import { eq } from 'drizzle-orm' import { createMessageScanResponseEmbed } from './embeds' @@ -17,7 +13,7 @@ export const getResponseFromText = async ( ): Promise => { let responseConfig: Awaited> = { triggers: {}, - response: null + response: null, } const firstLabelIndexes: number[] = [] @@ -29,7 +25,7 @@ export const getResponseFromText = async ( // Filter override check is not neccessary here, we are already passing responses that match the filter // from the messageCreate handler, see line 17 of messageCreate handler const { - triggers: { text: textTriggers, image: imageTriggers } + triggers: { text: textTriggers, image: imageTriggers }, } = trigger if (responseConfig) break @@ -92,7 +88,7 @@ export const getResponseFromText = async ( logger.debug('No match from NLP, doing after regexes') for (let i = 0; i < responses.length; i++) { const { - triggers: { text: textTriggers } + triggers: { text: textTriggers }, } = responses[i]! const firstLabelIndex = firstLabelIndexes[i] ?? -1 @@ -113,10 +109,7 @@ export const getResponseFromText = async ( return responseConfig } -export const messageMatchesFilter = ( - message: Message, - filter: NonNullable['filter'], -) => { +export const messageMatchesFilter = (message: Message, filter: NonNullable['filter']) => { if (!filter) return true const memberRoles = new Set(message.member?.roles.cache.keys()) @@ -124,7 +117,12 @@ export const messageMatchesFilter = ( // If matches blacklist, will return false // Any other case, will return true - return !(blFilter && (blFilter.channels?.includes(message.channelId) || blFilter.roles?.some(role => memberRoles.has(role)) || blFilter.users?.includes(message.author.id))) + return !( + blFilter && + (blFilter.channels?.includes(message.channelId) || + blFilter.roles?.some(role => memberRoles.has(role)) || + blFilter.users?.includes(message.author.id)) + ) } export const handleUserResponseCorrection = async ( diff --git a/bots/discord/src/utils/discord/moderation.ts b/bots/discord/src/utils/discord/moderation.ts index 0250676..38d3953 100644 --- a/bots/discord/src/utils/discord/moderation.ts +++ b/bots/discord/src/utils/discord/moderation.ts @@ -1,6 +1,6 @@ import { config, logger } from '$/context' import decancer from 'decancer' -import type { ChatInputCommandInteraction, EmbedBuilder, Guild, GuildMember, User } from 'discord.js' +import type { ChatInputCommandInteraction, EmbedBuilder, Guild, GuildMember, Message, User } from 'discord.js' import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from './embeds' const PresetLogAction = { @@ -10,19 +10,23 @@ const PresetLogAction = { export const sendPresetReplyAndLogs = ( action: keyof typeof PresetLogAction, - interaction: ChatInputCommandInteraction, + interaction: ChatInputCommandInteraction | Message, + executor: GuildMember, user: User, preset: string, expires?: number | null, ) => sendModerationReplyAndLogs( interaction, - createModerationActionEmbed(PresetLogAction[action], user, interaction.user, undefined, expires, [ + createModerationActionEmbed(PresetLogAction[action], user, executor.user, undefined, expires, [ [{ name: 'Preset', value: preset, inline: true }], ]), ) -export const sendModerationReplyAndLogs = async (interaction: ChatInputCommandInteraction, embed: EmbedBuilder) => { +export const sendModerationReplyAndLogs = async ( + interaction: ChatInputCommandInteraction | Message, + embed: EmbedBuilder, +) => { const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) const logChannel = await getLogChannel(interaction.guild!) await logChannel?.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) @@ -46,7 +50,7 @@ export const getLogChannel = async (guild: Guild) => { } export const cureNickname = async (member: GuildMember) => { - if (!member.manageable) throw new Error('Member is not manageable') + if (!member.manageable) return const name = member.displayName let cured = decancer(name) .toString() diff --git a/bots/discord/src/utils/discord/permissions.ts b/bots/discord/src/utils/discord/permissions.ts index 576e4c7..1e97854 100644 --- a/bots/discord/src/utils/discord/permissions.ts +++ b/bots/discord/src/utils/discord/permissions.ts @@ -2,10 +2,13 @@ import { GuildMember, type User } from 'discord.js' import config from '../../../config' export const isAdmin = (userOrMember: User | GuildMember) => { - return config.admin?.users?.includes(userOrMember.id) || (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember)) + return ( + config.admin?.users?.includes(userOrMember.id) || + (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember)) + ) } export const isMemberAdmin = (member: GuildMember) => { const roles = new Set(member.roles.cache.keys()) return Boolean(config?.admin?.roles?.[member.guild.id]?.some(role => roles.has(role))) -} \ No newline at end of file +} diff --git a/bots/discord/src/utils/discord/rolePresets.ts b/bots/discord/src/utils/discord/rolePresets.ts index 925d436..69c2fa5 100644 --- a/bots/discord/src/utils/discord/rolePresets.ts +++ b/bots/discord/src/utils/discord/rolePresets.ts @@ -6,9 +6,9 @@ import { and, eq } from 'drizzle-orm' // TODO: Fix this type type PresetKey = string -export const applyRolePreset = async (member: GuildMember, presetName: PresetKey, untilMs: number | null) => { +export const applyRolePreset = async (member: GuildMember, presetName: PresetKey, untilMs: number) => { const afterInsert = await applyRolesUsingPreset(presetName, member, true) - const until = untilMs ? Math.ceil(untilMs / 1000) : null + const until = untilMs === Infinity ? null : Math.ceil(untilMs / 1000) await database .insert(appliedPresets) diff --git a/bots/discord/src/utils/fs.ts b/bots/discord/src/utils/fs.ts index 0c91b2d..b42f5fc 100644 --- a/bots/discord/src/utils/fs.ts +++ b/bots/discord/src/utils/fs.ts @@ -7,17 +7,27 @@ export const listAllFilesRecursive = (dir: string): string[] => .filter(x => x.isFile()) .map(x => join(x.parentPath, x.name).replaceAll(pathSep, posixPathSep)) -export const generateCommandsIndex = (dirPath: string) => generateIndexes(dirPath, x => !x.endsWith('types.ts')) +export const generateCommandsIndex = (dirPath: string) => + generateIndexes(dirPath, (x, i) => `export { default as C${i} } from './${x}'`) export const generateEventsIndex = (dirPath: string) => generateIndexes(dirPath) -const generateIndexes = async (dirPath: string, pathFilter?: (path: string) => boolean) => { +const generateIndexes = async ( + dirPath: string, + customMap?: (path: string, index: number) => string, + pathFilter?: (path: string) => boolean, +) => { const files = listAllFilesRecursive(dirPath) - .filter(x => (x.endsWith('.ts') && !x.endsWith('index.ts') && pathFilter ? pathFilter(x) : true)) + .filter(x => x.endsWith('.ts') && !x.endsWith('index.ts') && (pathFilter ? pathFilter(x) : true)) .map(x => relative(dirPath, x).replaceAll(pathSep, posixPathSep)) writeFileSync( join(dirPath, 'index.ts'), - `// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH\n\n${files.map(c => `import './${c.split('.').at(-2)}'`).join('\n')}`, + `// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH\n\n${files + .map((c, i) => { + const path = c.split('.').at(-2)! + return customMap ? customMap(path, i) : `import './${path}'` + }) + .join('\n')}`, ) } From 2efedc47dfa23826663c94f982c268629b24d739 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 30 Jul 2024 14:17:08 +0000 Subject: [PATCH 202/312] chore(release): 1.0.0-dev.9 [skip ci] # @revanced/discord-bot [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.8...@revanced/discord-bot@1.0.0-dev.9) (2024-07-30) ### Features * **bots/discord:** framework changes and new features ([646ec8d](https://github.com/revanced/revanced-helper/commit/646ec8da87617e6c8f48a89e8054e2cba91da549)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index cb80f71..3ec9cce 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.8...@revanced/discord-bot@1.0.0-dev.9) (2024-07-30) + + +### Features + +* **bots/discord:** framework changes and new features ([646ec8d](https://github.com/revanced/revanced-helper/commit/646ec8da87617e6c8f48a89e8054e2cba91da549)) + # @revanced/discord-bot [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.7...@revanced/discord-bot@1.0.0-dev.8) (2024-07-28) diff --git a/bots/discord/package.json b/bots/discord/package.json index 24bb0f1..fc7e61b 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.8", + "version": "1.0.0-dev.9", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file From d31616ebcba6f1dcd8bde183bcb8d1adb1501b61 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 30 Jul 2024 21:30:35 +0700 Subject: [PATCH 203/312] fix(bots/discord): hanging process when disconnecting from API too many times --- apis/websocket/package.json | 2 +- biome.json | 3 +++ bots/discord/docs/4_commands_and_events.md | 2 -- bots/discord/src/events/api/disconnect.ts | 2 +- packages/api/src/classes/ClientWebSocket.ts | 17 +++++------------ semantic-release-config.js | 4 ++-- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 323de24..4a92de7 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -37,4 +37,4 @@ "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } -} \ No newline at end of file +} diff --git a/biome.json b/biome.json index 21f22fe..7b175a7 100644 --- a/biome.json +++ b/biome.json @@ -21,6 +21,9 @@ }, "useNodejsImportProtocol": { "level": "off" + }, + "useNumberNamespace": { + "level": "off" } } } diff --git a/bots/discord/docs/4_commands_and_events.md b/bots/discord/docs/4_commands_and_events.md index e1bb033..c1d58b2 100644 --- a/bots/discord/docs/4_commands_and_events.md +++ b/bots/discord/docs/4_commands_and_events.md @@ -35,8 +35,6 @@ export default { data: new SlashCommandBuilder() .setName("my-command") .setDescription("My cool command") - // Allowing this command to be used in DMs - .setDMPermission(true) // DO NOT forget this line! .toJSON(), diff --git a/bots/discord/src/events/api/disconnect.ts b/bots/discord/src/events/api/disconnect.ts index 75b9340..178980d 100644 --- a/bots/discord/src/events/api/disconnect.ts +++ b/bots/discord/src/events/api/disconnect.ts @@ -16,7 +16,7 @@ withContext(on, 'disconnect', ({ api, config, logger }, reason, msg) => { ) if (api.disconnectCount >= (config.api.disconnectLimit ?? 3)) { - console.error('Disconnected from bot API too many times') + logger.fatal('Disconnected from bot API too many times') // We don't want the process hanging process.exit(1) } diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index bcb9a5f..0632da0 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -49,19 +49,13 @@ export class ClientWebSocketManager { const timeout = setTimeout(() => { if (!this.ready) { this.#socket?.close(DisconnectReason.TooSlow) - throw new Error('WebSocket connection was not readied in time') + this._handleDisconnect(DisconnectReason.TooSlow, 'WebSocket connection was not readied in time') } }, this.timeout) - const errorBeforeReadyHandler = (err: Error) => { - cleanup() - throw err - } - const closeBeforeReadyHandler = (code: number, reason: Buffer) => { - clearTimeout(timeout) this._handleDisconnect(code, reason.toString()) - throw new Error('WebSocket connection closed before ready') + cleanup() } const readyHandler = () => { @@ -71,15 +65,14 @@ export class ClientWebSocketManager { rs() } + const socket = this.#socket const cleanup = () => { - this.#socket.off('open', readyHandler) - this.#socket.off('close', closeBeforeReadyHandler) - this.#socket.off('error', errorBeforeReadyHandler) + socket.off('open', readyHandler) + socket.off('close', closeBeforeReadyHandler) clearTimeout(timeout) } this.#socket.on('open', readyHandler) - this.#socket.on('error', errorBeforeReadyHandler) this.#socket.on('close', closeBeforeReadyHandler) } catch (e) { rj(e) diff --git a/semantic-release-config.js b/semantic-release-config.js index a0f2c5d..a834cdc 100644 --- a/semantic-release-config.js +++ b/semantic-release-config.js @@ -25,8 +25,8 @@ const Options = { [ '@semantic-release/npm', { - npmPublish: false, - } + npmPublish: false, + }, ], [ '@semantic-release/git', From 9b9bb1e1e655f76136cd256499d48d918e365675 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 30 Jul 2024 14:32:03 +0000 Subject: [PATCH 204/312] chore(release): 1.0.0-dev.6 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.5...@revanced/bot-websocket-api@1.0.0-dev.6) (2024-07-30) ### Bug Fixes * **bots/discord:** hanging process when disconnecting from API too many times ([d31616e](https://github.com/revanced/revanced-helper/commit/d31616ebcba6f1dcd8bde183bcb8d1adb1501b61)) --- apis/websocket/CHANGELOG.md | 7 +++++++ apis/websocket/package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index 99c2904..7e935cb 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/bot-websocket-api [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.5...@revanced/bot-websocket-api@1.0.0-dev.6) (2024-07-30) + + +### Bug Fixes + +* **bots/discord:** hanging process when disconnecting from API too many times ([d31616e](https://github.com/revanced/revanced-helper/commit/d31616ebcba6f1dcd8bde183bcb8d1adb1501b61)) + # @revanced/bot-websocket-api [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.4...@revanced/bot-websocket-api@1.0.0-dev.5) (2024-07-23) # @revanced/bot-websocket-api [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.3...@revanced/bot-websocket-api@1.0.0-dev.4) (2024-07-23) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 4a92de7..535f2be 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.5", + "version": "1.0.0-dev.6", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { @@ -37,4 +37,4 @@ "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } -} +} \ No newline at end of file From 3188f8dbedd4cb08dd2fac0abc4b209c48a0fc3b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 30 Jul 2024 14:32:44 +0000 Subject: [PATCH 205/312] chore(release): 1.0.0-dev.10 [skip ci] # @revanced/discord-bot [1.0.0-dev.10](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.9...@revanced/discord-bot@1.0.0-dev.10) (2024-07-30) ### Bug Fixes * **bots/discord:** hanging process when disconnecting from API too many times ([d31616e](https://github.com/revanced/revanced-helper/commit/d31616ebcba6f1dcd8bde183bcb8d1adb1501b61)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 3ec9cce..5aff52c 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.10](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.9...@revanced/discord-bot@1.0.0-dev.10) (2024-07-30) + + +### Bug Fixes + +* **bots/discord:** hanging process when disconnecting from API too many times ([d31616e](https://github.com/revanced/revanced-helper/commit/d31616ebcba6f1dcd8bde183bcb8d1adb1501b61)) + # @revanced/discord-bot [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.8...@revanced/discord-bot@1.0.0-dev.9) (2024-07-30) diff --git a/bots/discord/package.json b/bots/discord/package.json index fc7e61b..b6f1947 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.9", + "version": "1.0.0-dev.10", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From d234d79310caed9c43e14a905f9ef46a110e071d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 00:21:38 +0700 Subject: [PATCH 206/312] fix(bots/discord): reset counter when reconnected to api, redo message scan filter logic --- bots/discord/package.json | 2 +- bots/discord/src/events/api/ready.ts | 6 ++++- bots/discord/src/utils/discord/messageScan.ts | 25 +++++++++++++------ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/bots/discord/package.json b/bots/discord/package.json index b6f1947..eaedcf5 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} \ No newline at end of file +} diff --git a/bots/discord/src/events/api/ready.ts b/bots/discord/src/events/api/ready.ts index 94e55df..99fafac 100644 --- a/bots/discord/src/events/api/ready.ts +++ b/bots/discord/src/events/api/ready.ts @@ -1,3 +1,7 @@ import { on, withContext } from '$utils/api/events' -withContext(on, 'ready', ({ logger }) => void logger.info('Connected to the bot API')) +withContext(on, 'ready', ({ api, logger }) => { + // Reset disconnect count, so it doesn't meet the threshold for an accidental disconnect + api.disconnectCount = 0 + logger.info('Connected to the bot API') +}) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index 8c285bd..e9ca7d9 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -113,15 +113,24 @@ export const messageMatchesFilter = (message: Message, filter: NonNullable memberRoles.has(role)) || - blFilter.users?.includes(message.author.id)) + // If matches only blacklist, will return false + // If matches whitelist but also matches blacklist, will return false + // If matches only whitelist, will return true + // If matches neither, will return true + return ( + (whitelist + ? whitelist.channels?.includes(message.channelId) || + whitelist.roles?.some(role => memberRoles.has(role)) || + whitelist.users?.includes(message.author.id) + : true) && + !( + blacklist && + (blacklist.channels?.includes(message.channelId) || + blacklist.roles?.some(role => memberRoles.has(role)) || + blacklist.users?.includes(message.author.id)) + ) ) } From 95a122a22520fccf522c3986c0fb4c7ce745e5e1 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 30 Jul 2024 17:23:25 +0000 Subject: [PATCH 207/312] chore(release): 1.0.0-dev.11 [skip ci] # @revanced/discord-bot [1.0.0-dev.11](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.10...@revanced/discord-bot@1.0.0-dev.11) (2024-07-30) ### Bug Fixes * **bots/discord:** reset counter when reconnected to api, redo message scan filter logic ([d234d79](https://github.com/revanced/revanced-helper/commit/d234d79310caed9c43e14a905f9ef46a110e071d)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 5aff52c..a101b0e 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.11](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.10...@revanced/discord-bot@1.0.0-dev.11) (2024-07-30) + + +### Bug Fixes + +* **bots/discord:** reset counter when reconnected to api, redo message scan filter logic ([d234d79](https://github.com/revanced/revanced-helper/commit/d234d79310caed9c43e14a905f9ef46a110e071d)) + # @revanced/discord-bot [1.0.0-dev.10](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.9...@revanced/discord-bot@1.0.0-dev.10) (2024-07-30) diff --git a/bots/discord/package.json b/bots/discord/package.json index eaedcf5..a1188da 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.10", + "version": "1.0.0-dev.11", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file From a60c60c0f994a4c256b7d0582e99a1731209cf49 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 01:40:30 +0700 Subject: [PATCH 208/312] fix(bots/discord): deployment runtime errors due to minification --- bots/discord/scripts/build.ts | 6 +++++- bun.lockb | Bin 285528 -> 285584 bytes package.json | 3 ++- patches/decancer@3.2.3.patch | 13 +++++++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 patches/decancer@3.2.3.patch diff --git a/bots/discord/scripts/build.ts b/bots/discord/scripts/build.ts index 47814d7..eb52745 100644 --- a/bots/discord/scripts/build.ts +++ b/bots/discord/scripts/build.ts @@ -12,7 +12,11 @@ await Bun.build({ outdir: './dist/src', target: 'bun', external: ['./config.js'], - minify: true, + minify: { + syntax: true, + whitespace: true, + identifiers: false, + }, sourcemap: 'external', }) diff --git a/bun.lockb b/bun.lockb index f4a647390e8f59931200e71207a3aae34a7f4365..598bb479d70d2cc79365dbe8fb731a3a419a3af4 100755 GIT binary patch delta 287 zcmcbyO>n|?!3lm$4AG4NtpSX!0Zdy1m>*Oz?%r-#&760~tRS%@IU}`LKP5FeF)ul_ z$iZ08NY7XgEX)7}<^Dz9#Q}-VKf+nSA`%hPrJ`7rr#}d1QQ%C7fCwr~PmE-dnXVea zGJX1+2o{;?H4!WVnklJedWHrJ49jh$2k-JWTux#v3M#>Im($* g8c=k^{SE_z3I4YQG{s%05N1sk^lez delta 203 zcmbQRUGT;>!3lm$C!!hyS_2qc1DLi3Fh8hboWI?$nmO-|6ay5L`xki^2P8T_2xoza zL`>I;WKo;`J)A|6(<1^R$T3|pf@L$0DFXvTx$L@9lmG8Brf-N~ap1IwgeZuZt{KVV z&ba`}JpfWKGkt0#i#O*Fs2Ioemys;?oDNYC9Rbq~qgc$jXF$0pqFAQO9cPl6K0S)X UljA? Date: Tue, 30 Jul 2024 18:42:20 +0000 Subject: [PATCH 209/312] chore(release): 1.0.0-dev.12 [skip ci] # @revanced/discord-bot [1.0.0-dev.12](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.11...@revanced/discord-bot@1.0.0-dev.12) (2024-07-30) ### Bug Fixes * **bots/discord:** deployment runtime errors due to minification ([a60c60c](https://github.com/revanced/revanced-helper/commit/a60c60c0f994a4c256b7d0582e99a1731209cf49)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index a101b0e..9a3d3f4 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.12](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.11...@revanced/discord-bot@1.0.0-dev.12) (2024-07-30) + + +### Bug Fixes + +* **bots/discord:** deployment runtime errors due to minification ([a60c60c](https://github.com/revanced/revanced-helper/commit/a60c60c0f994a4c256b7d0582e99a1731209cf49)) + # @revanced/discord-bot [1.0.0-dev.11](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.10...@revanced/discord-bot@1.0.0-dev.11) (2024-07-30) diff --git a/bots/discord/package.json b/bots/discord/package.json index a1188da..8ddca8b 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.11", + "version": "1.0.0-dev.12", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From ab62e55e76005f5999d7413d1158e54053f28d1f Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 02:09:11 +0700 Subject: [PATCH 210/312] fix(bots/discord): broken regex when prefix set to special characters --- .../events/discord/messageCreate/messageCommand.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/events/discord/messageCreate/messageCommand.ts b/bots/discord/src/events/discord/messageCreate/messageCommand.ts index 2b2ecd4..f1b680a 100644 --- a/bots/discord/src/events/discord/messageCreate/messageCommand.ts +++ b/bots/discord/src/events/discord/messageCreate/messageCommand.ts @@ -8,7 +8,9 @@ withContext(on, 'messageCreate', async (context, msg) => { if (msg.author.bot) return - const regex = new RegExp(`^(?:${config.prefix}|${msg.client.user.toString()}\\s*)([a-zA-Z-_]+)(?:\\s+)?(.+)?`) + const regex = new RegExp( + `^(?:${config.prefix ? `${escapeRegexSpecials(config.prefix)}|` : ''}${msg.client.user.toString()}\\s*)([a-zA-Z-_]+)(?:\\s+)?(.+)?`, + ) const matches = msg.content.match(regex) if (!matches) return @@ -51,3 +53,12 @@ withContext(on, 'messageCreate', async (context, msg) => { await msg.reply({ embeds: [err instanceof CommandError ? err.toEmbed() : createStackTraceEmbed(err)] }) } }) + +const escapeRegexSpecials = (str: string): string => { + let escapedStr = '' + for (const char of str) { + if (['.', '+', '*', '?', '$', '(', ')', '[', ']', '{', '}', '|', '\\'].includes(char)) escapedStr += `\\${char}` + else escapedStr += char + } + return escapedStr +} From c9b788dc51b1099bdacdd9346cba7d457d18c48f Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 02:12:19 +0700 Subject: [PATCH 211/312] build(Needs bump): do not minify builds --- apis/websocket/scripts/build.ts | 2 -- bots/discord/scripts/build.ts | 5 ----- 2 files changed, 7 deletions(-) diff --git a/apis/websocket/scripts/build.ts b/apis/websocket/scripts/build.ts index 36bfa14..effc23d 100644 --- a/apis/websocket/scripts/build.ts +++ b/apis/websocket/scripts/build.ts @@ -11,7 +11,6 @@ await Bun.build({ entrypoints: ['./src/index.ts'], outdir: './dist', target: 'bun', - minify: true, sourcemap: 'external', }) @@ -21,7 +20,6 @@ await Bun.build({ external: ['tesseract.js-core/*'], target: 'bun', outdir: './dist/worker', - minify: true, sourcemap: 'external', }) diff --git a/bots/discord/scripts/build.ts b/bots/discord/scripts/build.ts index eb52745..13a9f87 100644 --- a/bots/discord/scripts/build.ts +++ b/bots/discord/scripts/build.ts @@ -12,11 +12,6 @@ await Bun.build({ outdir: './dist/src', target: 'bun', external: ['./config.js'], - minify: { - syntax: true, - whitespace: true, - identifiers: false, - }, sourcemap: 'external', }) From 887ee85e4190b69700586d639a5106999dcb4b7a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 30 Jul 2024 19:13:45 +0000 Subject: [PATCH 212/312] chore(release): 1.0.0-dev.7 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.6...@revanced/bot-websocket-api@1.0.0-dev.7) (2024-07-30) --- apis/websocket/CHANGELOG.md | 2 ++ apis/websocket/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index 7e935cb..12808f8 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,5 @@ +# @revanced/bot-websocket-api [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.6...@revanced/bot-websocket-api@1.0.0-dev.7) (2024-07-30) + # @revanced/bot-websocket-api [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.5...@revanced/bot-websocket-api@1.0.0-dev.6) (2024-07-30) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 535f2be..9eef52e 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.6", + "version": "1.0.0-dev.7", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { From b832311f7ea0d4bbe24c0f4c42774a6ef0d2b7de Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 30 Jul 2024 19:14:27 +0000 Subject: [PATCH 213/312] chore(release): 1.0.0-dev.13 [skip ci] # @revanced/discord-bot [1.0.0-dev.13](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.12...@revanced/discord-bot@1.0.0-dev.13) (2024-07-30) ### Bug Fixes * **bots/discord:** broken regex when prefix set to special characters ([ab62e55](https://github.com/revanced/revanced-helper/commit/ab62e55e76005f5999d7413d1158e54053f28d1f)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 9a3d3f4..966506a 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.13](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.12...@revanced/discord-bot@1.0.0-dev.13) (2024-07-30) + + +### Bug Fixes + +* **bots/discord:** broken regex when prefix set to special characters ([ab62e55](https://github.com/revanced/revanced-helper/commit/ab62e55e76005f5999d7413d1158e54053f28d1f)) + # @revanced/discord-bot [1.0.0-dev.12](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.11...@revanced/discord-bot@1.0.0-dev.12) (2024-07-30) diff --git a/bots/discord/package.json b/bots/discord/package.json index 8ddca8b..3c49da7 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.12", + "version": "1.0.0-dev.13", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From e29e9c3dd114541b38bdbd1bcd98e18ffba83241 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 19:27:12 +0700 Subject: [PATCH 214/312] fix(packages/api): properly await failed packets --- packages/api/src/classes/Client.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index 1560e60..333651a 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -52,17 +52,20 @@ export default class Client { // But if we add anything similar, this will cause another race condition // To fix this, we can try adding a instanced function that would return the currentSequence // and it would be updated every time a "heartbeat ack" packet is received - return Promise.race([ + const packet = await Promise.race([ this.#awaiter.await(ServerOperation.ParsedText, this.ws.currentSequence), - this.#awaiter.await(ServerOperation.ParseTextFailed, this.ws.timeout + 5000), + this.#awaiter.await(ServerOperation.ParseTextFailed, this.ws.currentSequence, this.ws.timeout + 5000), ]) .then(pkt => { if (pkt.op === ServerOperation.ParsedText) return pkt.d - throw new Error('Failed to parse text, the API encountered an error') + return null }) .catch(() => { throw new Error('Failed to parse text, the API did not respond in time') }) + + if (!packet) throw new Error('Failed to parse text, the API encountered an error') + return packet } /** @@ -82,17 +85,20 @@ export default class Client { // See line 50 - return Promise.race([ + const packet = await Promise.race([ this.#awaiter.await(ServerOperation.ParsedImage, this.ws.currentSequence), - this.#awaiter.await(ServerOperation.ParseImageFailed, this.ws.timeout + 5000), + this.#awaiter.await(ServerOperation.ParseImageFailed, this.ws.currentSequence, this.ws.timeout + 5000), ]) .then(pkt => { if (pkt.op === ServerOperation.ParsedImage) return pkt.d - throw new Error('Failed to parse image, the API encountered an error') + return null }) .catch(() => { throw new Error('Failed to parse image, the API did not respond in time') }) + + if (!packet) throw new Error('Failed to parse image, the API encountered an error') + return packet } async trainMessage(text: string, label: string) { @@ -107,17 +113,20 @@ export default class Client { }) // See line 50 - return Promise.race([ + const packet = await Promise.race([ this.#awaiter.await(ServerOperation.TrainedMessage, this.ws.currentSequence), - this.#awaiter.await(ServerOperation.TrainMessageFailed, this.ws.timeout + 5000), + this.#awaiter.await(ServerOperation.TrainMessageFailed, this.ws.currentSequence, this.ws.timeout + 5000), ]) .then(pkt => { if (pkt.op === ServerOperation.TrainedMessage) return pkt.d - throw new Error('Failed to train message, the API encountered an error') + return null }) .catch(() => { throw new Error('Failed to train message, the API did not respond in time') }) + + if (!packet) throw new Error('Failed to train message, the API encountered an error') + return packet } /** From 711f57f4a12f400f1f0066bf57977a38b9635401 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 19:27:36 +0700 Subject: [PATCH 215/312] fix(packages/api): null errors being thrown --- packages/api/src/classes/ClientWebSocket.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index 0632da0..620d068 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -123,7 +123,7 @@ export class ClientWebSocketManager { this.currentSequence++ this.#socket.send(serializePacket(packet), err => { - throw err + if (err) throw err }) } From 98ec37b5d18cade85270ab83b0ed0abe41244dd9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 19:28:47 +0700 Subject: [PATCH 216/312] fix(bots/discord): always true check causing no messages to be scanned --- bots/discord/src/utils/discord/messageScan.ts | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index e9ca7d9..e1bb281 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -10,9 +10,12 @@ export const getResponseFromText = async ( // Just to be safe that we will never use data from the context parameter { api, logger }: Omit, ocrMode = false, -): Promise => { - let responseConfig: Awaited> = { - triggers: {}, +): Promise< + Omit & { label?: string; triggers?: ConfigMessageScanResponse['triggers'] } +> => { + type ResponseConfig = Awaited> + let responseConfig: Omit & { triggers?: ResponseConfig['triggers'] } = { + triggers: undefined, response: null, } @@ -27,7 +30,6 @@ export const getResponseFromText = async ( const { triggers: { text: textTriggers, image: imageTriggers }, } = trigger - if (responseConfig) break if (ocrMode) { if (imageTriggers) @@ -55,7 +57,7 @@ export const getResponseFromText = async ( } // If none of the regexes match, we can search for labels immediately - if (!responseConfig && !ocrMode) { + if (!responseConfig.triggers && !ocrMode) { logger.debug('No match from before regexes, doing NLP') const scan = await api.client.parseText(content) if (scan.labels.length) { @@ -84,7 +86,7 @@ export const getResponseFromText = async ( } // If we still don't have a response config, we can match all regexes after the initial label trigger - if (!responseConfig) { + if (!responseConfig.triggers) { logger.debug('No match from NLP, doing after regexes') for (let i = 0; i < responses.length; i++) { const { @@ -119,19 +121,17 @@ export const messageMatchesFilter = (message: Message, filter: NonNullable memberRoles.has(role)) || - whitelist.users?.includes(message.author.id) - : true) && - !( - blacklist && - (blacklist.channels?.includes(message.channelId) || - blacklist.roles?.some(role => memberRoles.has(role)) || - blacklist.users?.includes(message.author.id)) - ) - ) + return whitelist + ? (whitelist.channels?.includes(message.channelId) ?? true) || + (whitelist.roles?.some(role => memberRoles.has(role)) ?? true) || + (whitelist.users?.includes(message.author.id) ?? true) + : true && + !( + blacklist && + (blacklist.channels?.includes(message.channelId) || + blacklist.roles?.some(role => memberRoles.has(role)) || + blacklist.users?.includes(message.author.id)) + ) } export const handleUserResponseCorrection = async ( From 1a4ec1ece80becd9156582cc490f6681cb2a1f39 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 19:29:27 +0700 Subject: [PATCH 217/312] feat(bots/discord): add more options for curing, fix default regex --- bots/discord/config.schema.ts | 2 ++ bots/discord/src/utils/discord/moderation.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 8738981..2df4e54 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -9,6 +9,8 @@ export type Config = { moderation?: { roles: string[] cure?: { + minimumNameLength?: number + removeCharactersRegex?: RegExp defaultName: string } log?: { diff --git a/bots/discord/src/utils/discord/moderation.ts b/bots/discord/src/utils/discord/moderation.ts index 38d3953..062e6b1 100644 --- a/bots/discord/src/utils/discord/moderation.ts +++ b/bots/discord/src/utils/discord/moderation.ts @@ -54,9 +54,9 @@ export const cureNickname = async (member: GuildMember) => { const name = member.displayName let cured = decancer(name) .toString() - .replace(/[^a-zA-Z0-9]/g, '') + .replace(new RegExp(config.moderation?.cure?.removeCharactersRegex ?? '[^a-zA-Z0-9 \\-_]', 'g'), '') - if (cured.length < 3 || !/^[a-zA-Z]/.test(cured)) + if (cured.length < (config?.moderation?.cure?.minimumNameLength ?? 3)) cured = member.user.username.length >= 3 ? member.user.username From 620f9339f0737b79d72c66d90ffa42ea3f987710 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 19:30:30 +0700 Subject: [PATCH 218/312] feat(bots/discord): allow admins to bypass permission checks --- bots/discord/src/classes/Command.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/classes/Command.ts b/bots/discord/src/classes/Command.ts index 7baf466..f02a573 100644 --- a/bots/discord/src/classes/Command.ts +++ b/bots/discord/src/classes/Command.ts @@ -291,6 +291,9 @@ export default class Command< async canExecute(executor: User | GuildMember, channelId: string): Promise { if (!this.requirements) return false + const isExecutorAdmin = isAdmin(executor) + if (isExecutorAdmin) return true + const { adminOnly, channels, @@ -307,7 +310,7 @@ export default class Command< const bMemReqForUsers = memberRequirementsForUsers !== 'fail' const conditions = [ - adminOnly ? isAdmin(executor) : bDefCond, + adminOnly ? isExecutorAdmin : bDefCond, channels ? channels.includes(channelId) : bDefCond, member ? (roles ? roles.some(role => member.roles.cache.has(role)) : bDefCond) : bMemReqForUsers, member ? (permissions ? member.permissions.has(permissions) : bDefCond) : bMemReqForUsers, From bc437a5ec7ce1d339094d608e2a61ac5f460c163 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 19:33:40 +0700 Subject: [PATCH 219/312] fix: other small issues --- apis/websocket/package.json | 2 +- bots/discord/package.json | 2 +- .../src/events/discord/interactionCreate/chatCommand.ts | 3 ++- .../src/events/discord/messageCreate/messageCommand.ts | 2 +- bots/discord/src/events/discord/messageCreate/scanMessage.ts | 2 +- bots/discord/src/index.ts | 5 +++++ 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 9eef52e..43d73d7 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -37,4 +37,4 @@ "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } -} \ No newline at end of file +} diff --git a/bots/discord/package.json b/bots/discord/package.json index 3c49da7..f577fd4 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} \ No newline at end of file +} diff --git a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts index 28c7f56..b3d1c95 100644 --- a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts @@ -9,7 +9,8 @@ withContext(on, 'interactionCreate', async (context, interaction) => { const command = discord.commands[interaction.commandName] logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag}`) - if (!command) return void logger.error(`Command ${interaction.commandName} not implemented but registered!!!`) + if (!command) + return void logger.error(`Interaction command ${interaction.commandName} not implemented but registered!!!`) try { logger.debug(`Command ${interaction.commandName} being executed`) diff --git a/bots/discord/src/events/discord/messageCreate/messageCommand.ts b/bots/discord/src/events/discord/messageCreate/messageCommand.ts index f1b680a..3b9440b 100644 --- a/bots/discord/src/events/discord/messageCreate/messageCommand.ts +++ b/bots/discord/src/events/discord/messageCreate/messageCommand.ts @@ -19,7 +19,7 @@ withContext(on, 'messageCreate', async (context, msg) => { const command = discord.commands[commandName] logger.debug(`Command ${commandName} being invoked by ${msg.author.id}`) - if (!command) return void logger.error(`Command ${commandName} not implemented`) + if (!command) return void logger.debug(`Message command ${commandName} not implemented`) const argsRegex: RegExp = /[^\s"]+|"([^"]*)"/g const args: CommandArguments = [] diff --git a/bots/discord/src/events/discord/messageCreate/scanMessage.ts b/bots/discord/src/events/discord/messageCreate/scanMessage.ts index 848253b..4fb83e0 100644 --- a/bots/discord/src/events/discord/messageCreate/scanMessage.ts +++ b/bots/discord/src/events/discord/messageCreate/scanMessage.ts @@ -33,7 +33,7 @@ withContext(on, 'messageCreate', async (context, msg) => { if (response) { logger.debug('Response found') - const toReply = replyToReplied ? await msg.fetchReference() : msg + const toReply = replyToReplied ? (msg.reference?.messageId ? await msg.fetchReference() : msg) : msg const reply = await toReply.reply({ ...response, embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, label ? 'nlp' : 'match')), diff --git a/bots/discord/src/index.ts b/bots/discord/src/index.ts index f7e77fd..2aaf35c 100644 --- a/bots/discord/src/index.ts +++ b/bots/discord/src/index.ts @@ -10,5 +10,10 @@ if (missingEnvs.length) { process.exit(1) } +// Handle uncaught exceptions + +process.on('uncaughtException', error => console.error('Uncaught exception:', error)) +process.on('unhandledRejection', reason => console.error('Unhandled rejection:', reason)) + api.client.connect() discord.client.login() From b726c40fd423fcd1b28664c9b931ac8c12fa2d85 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 31 Jul 2024 12:35:31 +0000 Subject: [PATCH 220/312] chore(release): 1.0.0-dev.8 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.7...@revanced/bot-websocket-api@1.0.0-dev.8) (2024-07-31) ### Bug Fixes * other small issues ([bc437a5](https://github.com/revanced/revanced-helper/commit/bc437a5ec7ce1d339094d608e2a61ac5f460c163)) --- apis/websocket/CHANGELOG.md | 7 +++++++ apis/websocket/package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index 12808f8..f195cc8 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/bot-websocket-api [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.7...@revanced/bot-websocket-api@1.0.0-dev.8) (2024-07-31) + + +### Bug Fixes + +* other small issues ([bc437a5](https://github.com/revanced/revanced-helper/commit/bc437a5ec7ce1d339094d608e2a61ac5f460c163)) + # @revanced/bot-websocket-api [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.6...@revanced/bot-websocket-api@1.0.0-dev.7) (2024-07-30) # @revanced/bot-websocket-api [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.5...@revanced/bot-websocket-api@1.0.0-dev.6) (2024-07-30) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 43d73d7..e982fed 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.7", + "version": "1.0.0-dev.8", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { @@ -37,4 +37,4 @@ "@types/ws": "^8.5.10", "typed-emitter": "^2.1.0" } -} +} \ No newline at end of file From 561426028c1a1e6772a3cc2f2ac5e810eecffbcf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 31 Jul 2024 12:36:12 +0000 Subject: [PATCH 221/312] chore(release): 1.0.0-dev.14 [skip ci] # @revanced/discord-bot [1.0.0-dev.14](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.13...@revanced/discord-bot@1.0.0-dev.14) (2024-07-31) ### Bug Fixes * **bots/discord:** always true check causing no messages to be scanned ([98ec37b](https://github.com/revanced/revanced-helper/commit/98ec37b5d18cade85270ab83b0ed0abe41244dd9)) * other small issues ([bc437a5](https://github.com/revanced/revanced-helper/commit/bc437a5ec7ce1d339094d608e2a61ac5f460c163)) ### Features * **bots/discord:** add more options for curing, fix default regex ([1a4ec1e](https://github.com/revanced/revanced-helper/commit/1a4ec1ece80becd9156582cc490f6681cb2a1f39)) * **bots/discord:** allow admins to bypass permission checks ([620f933](https://github.com/revanced/revanced-helper/commit/620f9339f0737b79d72c66d90ffa42ea3f987710)) --- bots/discord/CHANGELOG.md | 14 ++++++++++++++ bots/discord/package.json | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 966506a..1f0231a 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,17 @@ +# @revanced/discord-bot [1.0.0-dev.14](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.13...@revanced/discord-bot@1.0.0-dev.14) (2024-07-31) + + +### Bug Fixes + +* **bots/discord:** always true check causing no messages to be scanned ([98ec37b](https://github.com/revanced/revanced-helper/commit/98ec37b5d18cade85270ab83b0ed0abe41244dd9)) +* other small issues ([bc437a5](https://github.com/revanced/revanced-helper/commit/bc437a5ec7ce1d339094d608e2a61ac5f460c163)) + + +### Features + +* **bots/discord:** add more options for curing, fix default regex ([1a4ec1e](https://github.com/revanced/revanced-helper/commit/1a4ec1ece80becd9156582cc490f6681cb2a1f39)) +* **bots/discord:** allow admins to bypass permission checks ([620f933](https://github.com/revanced/revanced-helper/commit/620f9339f0737b79d72c66d90ffa42ea3f987710)) + # @revanced/discord-bot [1.0.0-dev.13](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.12...@revanced/discord-bot@1.0.0-dev.13) (2024-07-30) diff --git a/bots/discord/package.json b/bots/discord/package.json index f577fd4..2e12c33 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.13", + "version": "1.0.0-dev.14", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file From 763ef253f9d4ff70a8b79969a7f4f41cba7f3c59 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 31 Jul 2024 21:24:55 +0700 Subject: [PATCH 222/312] fix(bots/discord): import `config` from context --- bots/discord/src/utils/discord/permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/utils/discord/permissions.ts b/bots/discord/src/utils/discord/permissions.ts index 1e97854..4c6d6cd 100644 --- a/bots/discord/src/utils/discord/permissions.ts +++ b/bots/discord/src/utils/discord/permissions.ts @@ -1,5 +1,5 @@ import { GuildMember, type User } from 'discord.js' -import config from '../../../config' +import { config } from '../../context' export const isAdmin = (userOrMember: User | GuildMember) => { return ( From bf661556e131bf0ef24e47f658fbcd701960e312 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 1 Aug 2024 02:28:54 +0700 Subject: [PATCH 223/312] feat(bots/discord): add sticky messages --- bots/discord/config.schema.ts | 7 ++ bots/discord/package.json | 2 +- bots/discord/src/context.ts | 17 ++++- .../discord/{cureRequired.ts => cure.ts} | 0 .../{scanMessage.ts => messageScan.ts} | 0 .../messageCreate/stickyMessageReset.ts | 30 +++++++++ bots/discord/src/events/discord/ready.ts | 60 +++++++++++++++++- bots/discord/src/utils/discord/embeds.ts | 31 ++++----- bun.lockb | Bin 285584 -> 285584 bytes 9 files changed, 123 insertions(+), 24 deletions(-) rename bots/discord/src/events/discord/{cureRequired.ts => cure.ts} (100%) rename bots/discord/src/events/discord/messageCreate/{scanMessage.ts => messageScan.ts} (100%) create mode 100644 bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 2df4e54..b55c529 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -6,6 +6,7 @@ export type Config = { users?: string[] roles?: Record } + stickyMessages?: Record> moderation?: { roles: string[] cure?: { @@ -50,6 +51,12 @@ export type Config = { } } +export type StickyMessageConfig = { + timeout: number + forceSendTimeout?: number + message: BaseMessageOptions +} + export type RolePresetConfig = { give: string[] take: string[] diff --git a/bots/discord/package.json b/bots/discord/package.json index 2e12c33..9c6425b 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} \ No newline at end of file +} diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index a52e71f..62ede58 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -3,7 +3,7 @@ import { existsSync, readFileSync, readdirSync } from 'fs' import { join } from 'path' import { Client as APIClient } from '@revanced/bot-api' import { createLogger } from '@revanced/bot-shared' -import { Client as DiscordClient, Partials } from 'discord.js' +import { Client as DiscordClient, type Message, Partials } from 'discord.js' import { drizzle } from 'drizzle-orm/bun-sqlite' // Export some things first, as commands require them @@ -85,4 +85,19 @@ export const discord = { string, Command >, + stickyMessages: {} as Record< + string, + Record< + string, + { + forceSendTimerActive?: boolean + timeoutMs: number + forceSendMs?: number + send: (forced?: boolean) => Promise + currentMessage?: Message + interval?: NodeJS.Timeout + forceSendInterval?: NodeJS.Timeout + } + > + >, } as const diff --git a/bots/discord/src/events/discord/cureRequired.ts b/bots/discord/src/events/discord/cure.ts similarity index 100% rename from bots/discord/src/events/discord/cureRequired.ts rename to bots/discord/src/events/discord/cure.ts diff --git a/bots/discord/src/events/discord/messageCreate/scanMessage.ts b/bots/discord/src/events/discord/messageCreate/messageScan.ts similarity index 100% rename from bots/discord/src/events/discord/messageCreate/scanMessage.ts rename to bots/discord/src/events/discord/messageCreate/messageScan.ts diff --git a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts new file mode 100644 index 0000000..0183c14 --- /dev/null +++ b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts @@ -0,0 +1,30 @@ +import { on, withContext } from '$utils/discord/events' + +withContext(on, 'messageCreate', async ({ discord, logger }, msg) => { + if (!msg.inGuild()) return + if (msg.author.id === msg.client.user.id) return + + const store = discord.stickyMessages[msg.guildId]?.[msg.channelId] + if (!store) return + + if (!store.interval) store.interval = setTimeout(store.send, store.timeoutMs) as NodeJS.Timeout + else { + store.interval.refresh() + + if (!store.forceSendTimerActive && store.forceSendMs) { + logger.debug(`Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer`) + + store.forceSendTimerActive = true + + if (!store.forceSendInterval) + store.forceSendInterval = setTimeout( + () => + store.send(true).then(() => { + store.forceSendTimerActive = false + }), + store.forceSendMs, + ) as NodeJS.Timeout + else store.forceSendInterval.refresh() + } + } +}) diff --git a/bots/discord/src/events/discord/ready.ts b/bots/discord/src/events/discord/ready.ts index e40af8a..3e21af1 100644 --- a/bots/discord/src/events/discord/ready.ts +++ b/bots/discord/src/events/discord/ready.ts @@ -1,14 +1,68 @@ import { database, logger } from '$/context' import { appliedPresets } from '$/database/schemas' +import { applyCommonEmbedStyles } from '$/utils/discord/embeds' +import { on, withContext } from '$/utils/discord/events' import { removeRolePreset } from '$/utils/discord/rolePresets' -import type { Client } from 'discord.js' import { lt } from 'drizzle-orm' -import { on, withContext } from 'src/utils/discord/events' -export default withContext(on, 'ready', ({ config, logger }, client) => { +import type { Client } from 'discord.js' + +export default withContext(on, 'ready', async ({ config, discord, logger }, client) => { logger.info(`Connected to Discord API, logged in as ${client.user.displayName} (@${client.user.tag})!`) logger.info(`Bot is in ${client.guilds.cache.size} guilds`) + if (config.stickyMessages) + for (const [guildId, channels] of Object.entries(config.stickyMessages)) { + const guild = await client.guilds.fetch(guildId) + discord.stickyMessages[guildId] = {} + + for (const [channelId, { message, timeout, forceSendTimeout }] of Object.entries(channels)) { + const channel = await guild.channels.fetch(channelId) + if (!channel?.isTextBased()) return + + const send = async (forced = false) => { + try { + const msg = await channel.send({ + ...message, + embeds: message.embeds?.map(it => applyCommonEmbedStyles(it, true, true, true)), + }) + + const store = discord.stickyMessages[guildId]![channelId] + if (!store) return + + await store.currentMessage?.delete().catch() + store.currentMessage = msg + + if (!forced) { + clearTimeout(store.forceSendInterval) + logger.debug( + `Timeout ended for sticky message in channel ${channelId} in guild ${guildId}, channel is inactive`, + ) + } else { + clearTimeout(store.interval) + logger.debug( + `Forced send timeout for sticky message in channel ${channelId} in guild ${guildId} ended, channel is too active`, + ) + } + + logger.debug(`Sent sticky message to channel ${channelId} in guild ${guildId}`) + } catch (e) { + logger.error( + `Error while sending sticky message to channel ${channelId} in guild ${guildId}:`, + e, + ) + } + } + + discord.stickyMessages[guildId]![channelId] = { + forceSendMs: forceSendTimeout, + timeoutMs: timeout, + send, + forceSendTimerActive: false, + } + } + } + if (config.rolePresets) { removeExpiredPresets(client) setTimeout(() => removeExpiredPresets(client), config.rolePresets.checkExpiredEvery) diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index cb584cd..a486bbb 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -1,5 +1,5 @@ import { DefaultEmbedColor, MessageScanHumanizedMode, ReVancedLogoURL } from '$/constants' -import { EmbedBuilder, type EmbedField, type User } from 'discord.js' +import { type APIEmbed, EmbedBuilder, type EmbedField, type JSONEncodable, type User } from 'discord.js' import type { ConfigMessageScanResponseMessage } from '../../../config.schema' export const createErrorEmbed = (title: string | null, description?: string) => @@ -26,23 +26,12 @@ export const createSuccessEmbed = (title: string | null, description?: string) = export const createMessageScanResponseEmbed = ( response: NonNullable[number], mode: 'ocr' | 'nlp' | 'match', -) => { - // biome-ignore lint/style/noParameterAssign: While this is confusing, it is fine for this purpose - if ('toJSON' in response) response = response.toJSON() - - const embed = new EmbedBuilder().setTitle(response.title ?? null) - - if (response.description) embed.setDescription(response.description) - if (response.fields) embed.addFields(response.fields) - - embed.setFooter({ +) => + applyCommonEmbedStyles(response, true, true, true).setFooter({ text: `ReVanced • Via ${MessageScanHumanizedMode[mode]}`, iconURL: ReVancedLogoURL, }) - return applyCommonEmbedStyles(embed, true, true, true) -} - export const createModerationActionEmbed = ( action: string, user: User, @@ -77,19 +66,23 @@ export const applyReferenceToModerationActionEmbed = (embed: EmbedBuilder, refer } export const applyCommonEmbedStyles = ( - embed: EmbedBuilder, + embed: EmbedBuilder | JSONEncodable | APIEmbed, setThumbnail = false, setFooter = false, setColor = false, ) => { + // biome-ignore lint/style/noParameterAssign: While this is confusing, it is fine for this purpose + if ('toJSON' in embed) embed = embed.toJSON() + const builder = new EmbedBuilder(embed) + if (setFooter) - embed.setFooter({ + builder.setFooter({ text: 'ReVanced', iconURL: ReVancedLogoURL, }) - if (setColor) embed.setColor(DefaultEmbedColor) - if (setThumbnail) embed.setThumbnail(ReVancedLogoURL) + if (setColor) builder.setColor(DefaultEmbedColor) + if (setThumbnail) builder.setThumbnail(ReVancedLogoURL) - return embed + return builder } diff --git a/bun.lockb b/bun.lockb index 598bb479d70d2cc79365dbe8fb731a3a419a3af4..d84d3156234124d78cae17263125752a889c3248 100755 GIT binary patch delta 67 zcmbQRU2wv7!G;#b7N!>FEi6|ecq|wg82GX;t$x3xpm_R&2o_at69$Gb1w%#M8)k*u Y1tM8qFml|OBWwS-piy@EM-iq804fL=1^@s6 delta 67 zcmbQRU2wv7!G;#b7N!>FEi6|ec+40W80M|oml$ZUVdnG)5iF|Q1`G_#ZKdREj=65! ZE)dD`f|0{*>XHoG-*;-Je-vSw0025&8C(DW From 9fe6b4ca700fdca93cf0dec3b1d604e7f182c9c1 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 31 Jul 2024 19:31:21 +0000 Subject: [PATCH 224/312] chore(release): 1.0.0-dev.15 [skip ci] # @revanced/discord-bot [1.0.0-dev.15](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.14...@revanced/discord-bot@1.0.0-dev.15) (2024-07-31) ### Bug Fixes * **bots/discord:** import `config` from context ([763ef25](https://github.com/revanced/revanced-helper/commit/763ef253f9d4ff70a8b79969a7f4f41cba7f3c59)) ### Features * **bots/discord:** add sticky messages ([bf66155](https://github.com/revanced/revanced-helper/commit/bf661556e131bf0ef24e47f658fbcd701960e312)) --- bots/discord/CHANGELOG.md | 12 ++++++++++++ bots/discord/package.json | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 1f0231a..4d66664 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,15 @@ +# @revanced/discord-bot [1.0.0-dev.15](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.14...@revanced/discord-bot@1.0.0-dev.15) (2024-07-31) + + +### Bug Fixes + +* **bots/discord:** import `config` from context ([763ef25](https://github.com/revanced/revanced-helper/commit/763ef253f9d4ff70a8b79969a7f4f41cba7f3c59)) + + +### Features + +* **bots/discord:** add sticky messages ([bf66155](https://github.com/revanced/revanced-helper/commit/bf661556e131bf0ef24e47f658fbcd701960e312)) + # @revanced/discord-bot [1.0.0-dev.14](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.13...@revanced/discord-bot@1.0.0-dev.14) (2024-07-31) diff --git a/bots/discord/package.json b/bots/discord/package.json index 9c6425b..83ca7d1 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.14", + "version": "1.0.0-dev.15", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.92", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file From 8fe78e424e3774895ab730107fccc6d2b095c5f7 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 2 Aug 2024 18:28:13 +0700 Subject: [PATCH 225/312] fix(bots/discord)!: rename config `replyToReplied` to `respondToReply` --- bots/discord/config.schema.ts | 2 +- bots/discord/src/events/discord/messageCreate/messageScan.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index b55c529..91c0889 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -69,7 +69,7 @@ export type ConfigMessageScanResponse = { } filterOverride?: NonNullable['filter'] response: ConfigMessageScanResponseMessage | null - replyToReplied?: boolean + respondToReply?: boolean } export type ConfigMessageScanResponseLabelConfig = { diff --git a/bots/discord/src/events/discord/messageCreate/messageScan.ts b/bots/discord/src/events/discord/messageCreate/messageScan.ts index 4fb83e0..6f0ce73 100644 --- a/bots/discord/src/events/discord/messageCreate/messageScan.ts +++ b/bots/discord/src/events/discord/messageCreate/messageScan.ts @@ -24,7 +24,7 @@ withContext(on, 'messageCreate', async (context, msg) => { try { logger.debug(`Classifying message ${msg.id}`) - const { response, label, replyToReplied } = await getResponseFromText( + const { response, label, respondToReply } = await getResponseFromText( msg.content, filteredResponses, context, @@ -33,7 +33,7 @@ withContext(on, 'messageCreate', async (context, msg) => { if (response) { logger.debug('Response found') - const toReply = replyToReplied ? (msg.reference?.messageId ? await msg.fetchReference() : msg) : msg + const toReply = respondToReply ? (msg.reference?.messageId ? await msg.fetchReference() : msg) : msg const reply = await toReply.reply({ ...response, embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, label ? 'nlp' : 'match')), From 412e00317d1eaca23e9c1375e16f94a5f2fa8d86 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 2 Aug 2024 18:36:17 +0700 Subject: [PATCH 226/312] fix(bots/discord): remove redundant footer for response embeds --- bots/discord/src/constants.ts | 6 ------ .../discord/messageCreate/messageScan.ts | 4 ++-- bots/discord/src/utils/discord/embeds.ts | 9 ++------- bots/discord/src/utils/discord/messageScan.ts | 19 +++++++++++-------- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/bots/discord/src/constants.ts b/bots/discord/src/constants.ts index 7ab26d6..8e25126 100644 --- a/bots/discord/src/constants.ts +++ b/bots/discord/src/constants.ts @@ -4,12 +4,6 @@ export const MessageScanLabeledResponseReactions = { delete: '❌', } as const -export const MessageScanHumanizedMode = { - ocr: 'image recognition', - nlp: 'text analysis', - match: 'pattern matching', -} as const - export const DefaultEmbedColor = '#4E98F0' export const ReVancedLogoURL = 'https://media.discordapp.net/attachments/1095487869923119144/1115436493050224660/revanced-logo.png' diff --git a/bots/discord/src/events/discord/messageCreate/messageScan.ts b/bots/discord/src/events/discord/messageCreate/messageScan.ts index 6f0ce73..f56e7c3 100644 --- a/bots/discord/src/events/discord/messageCreate/messageScan.ts +++ b/bots/discord/src/events/discord/messageCreate/messageScan.ts @@ -36,7 +36,7 @@ withContext(on, 'messageCreate', async (context, msg) => { const toReply = respondToReply ? (msg.reference?.messageId ? await msg.fetchReference() : msg) : msg const reply = await toReply.reply({ ...response, - embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, label ? 'nlp' : 'match')), + embeds: response.embeds?.map(createMessageScanResponseEmbed), }) if (label) @@ -74,7 +74,7 @@ withContext(on, 'messageCreate', async (context, msg) => { logger.debug(`Response found for attachment: ${attachment.url}`) await msg.reply({ ...response, - embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, 'ocr')), + embeds: response.embeds?.map(createMessageScanResponseEmbed), }) break diff --git a/bots/discord/src/utils/discord/embeds.ts b/bots/discord/src/utils/discord/embeds.ts index a486bbb..453a3a8 100644 --- a/bots/discord/src/utils/discord/embeds.ts +++ b/bots/discord/src/utils/discord/embeds.ts @@ -1,4 +1,4 @@ -import { DefaultEmbedColor, MessageScanHumanizedMode, ReVancedLogoURL } from '$/constants' +import { DefaultEmbedColor, ReVancedLogoURL } from '$/constants' import { type APIEmbed, EmbedBuilder, type EmbedField, type JSONEncodable, type User } from 'discord.js' import type { ConfigMessageScanResponseMessage } from '../../../config.schema' @@ -25,12 +25,7 @@ export const createSuccessEmbed = (title: string | null, description?: string) = export const createMessageScanResponseEmbed = ( response: NonNullable[number], - mode: 'ocr' | 'nlp' | 'match', -) => - applyCommonEmbedStyles(response, true, true, true).setFooter({ - text: `ReVanced • Via ${MessageScanHumanizedMode[mode]}`, - iconURL: ReVancedLogoURL, - }) +) => applyCommonEmbedStyles(response, true, true, true) export const createModerationActionEmbed = ( action: string, diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index e1bb281..46c1274 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -64,23 +64,26 @@ export const getResponseFromText = async ( const matchedLabel = scan.labels[0]! logger.debug(`Message matched label with confidence: ${matchedLabel.name}, ${matchedLabel.confidence}`) - let triggerConfig: ConfigMessageScanResponseLabelConfig | undefined - const labelConfig = responses.find(x => { + let trigger: ConfigMessageScanResponseLabelConfig | undefined + const response = responses.find(x => { const config = x.triggers.text!.find( (x): x is ConfigMessageScanResponseLabelConfig => 'label' in x && x.label === matchedLabel.name, ) - if (config) triggerConfig = config + if (config) trigger = config return config }) - if (!labelConfig) { - logger.warn(`No label config found for label ${matchedLabel.name}`) + if (!response) { + logger.warn(`No response config found for label ${matchedLabel.name}`) + // This returns the default value set in line 17, which means no response matched return responseConfig } - if (matchedLabel.confidence >= triggerConfig!.threshold) { + responseConfig.label = trigger!.label + + if (matchedLabel.confidence >= trigger!.threshold) { logger.debug('Label confidence is enough') - responseConfig = labelConfig + responseConfig = response } } } @@ -158,7 +161,7 @@ export const handleUserResponseCorrection = async ( await reply.edit({ ...correctLabelResponse.response, - embeds: correctLabelResponse.response.embeds?.map(it => createMessageScanResponseEmbed(it, 'nlp')), + embeds: correctLabelResponse.response.embeds?.map(createMessageScanResponseEmbed), }) } From f5939e25288fea2022fdeec9085ecb9ffada6111 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 2 Aug 2024 18:42:53 +0700 Subject: [PATCH 227/312] fix(bots/discord): remove bad text channel checks --- bots/discord/src/commands/moderation/purge.ts | 4 ++-- bots/discord/src/commands/moderation/slowmode.ts | 2 +- bots/discord/src/utils/discord/moderation.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bots/discord/src/commands/moderation/purge.ts b/bots/discord/src/commands/moderation/purge.ts index 8f59a31..a956b69 100644 --- a/bots/discord/src/commands/moderation/purge.ts +++ b/bots/discord/src/commands/moderation/purge.ts @@ -1,4 +1,4 @@ -import { EmbedBuilder, GuildChannel } from 'discord.js' +import { EmbedBuilder } from 'discord.js' import { ModerationCommand } from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' @@ -31,7 +31,7 @@ export default new ModerationCommand({ throw new CommandError(CommandErrorType.MissingArgument, 'Either `amount` or `until` must be provided.') const channel = interaction.channel! - if (!(channel.isTextBased() && channel instanceof GuildChannel)) + if (!channel.isTextBased()) throw new CommandError(CommandErrorType.InvalidChannel, 'The supplied channel is not a text channel.') const embed = applyCommonEmbedStyles( diff --git a/bots/discord/src/commands/moderation/slowmode.ts b/bots/discord/src/commands/moderation/slowmode.ts index 6fe055f..5e55002 100644 --- a/bots/discord/src/commands/moderation/slowmode.ts +++ b/bots/discord/src/commands/moderation/slowmode.ts @@ -28,7 +28,7 @@ export default new ModerationCommand({ if (!channel?.isTextBased() || channel.isDMBased()) throw new CommandError( CommandErrorType.InvalidChannel, - 'The supplied channel is not a text channel or does not exist.', + 'The supplied channel is not a text channel.', ) if (Number.isNaN(duration)) throw new CommandError(CommandErrorType.InvalidDuration, 'Invalid duration.') diff --git a/bots/discord/src/utils/discord/moderation.ts b/bots/discord/src/utils/discord/moderation.ts index 062e6b1..9f8cb00 100644 --- a/bots/discord/src/utils/discord/moderation.ts +++ b/bots/discord/src/utils/discord/moderation.ts @@ -38,7 +38,7 @@ export const getLogChannel = async (guild: Guild) => { try { const channel = await guild.channels.fetch(logConfig.thread ?? logConfig.channel) - if (!channel || !channel.isTextBased()) + if (!channel?.isTextBased()) return void logger.warn('The moderation log channel does not exist, skipping logging') return channel From c36684091dddf67880505dc459e4334a8a5492f4 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 2 Aug 2024 18:45:02 +0700 Subject: [PATCH 228/312] fix(bots/discord): open database as read-write --- bots/discord/src/context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 62ede58..622ff6d 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -56,7 +56,7 @@ if (DatabasePath && !existsSync(DatabasePath)) { } } -const db = new Database(DatabasePath) +const db = new Database(DatabasePath, { readwrite: true, create: true }) if (dbSchemaFileName) db.run(readFileSync(join(DatabaseSchemaDir, dbSchemaFileName)).toString()) export const database = drizzle(db, { From 6875b32fd0c6ce3034da9dc6c704d425afb26f2e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 2 Aug 2024 18:53:43 +0700 Subject: [PATCH 229/312] feat(bots/discord/commands): add `reload` command --- bots/discord/.gitignore | 3 --- bots/discord/src/commands/admin/reload.ts | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 bots/discord/src/commands/admin/reload.ts diff --git a/bots/discord/.gitignore b/bots/discord/.gitignore index f742463..130f357 100644 --- a/bots/discord/.gitignore +++ b/bots/discord/.gitignore @@ -174,9 +174,6 @@ dist # Finder (MacOS) folder config .DS_Store -# Config -config.ts - # DB *.db *.sqlite diff --git a/bots/discord/src/commands/admin/reload.ts b/bots/discord/src/commands/admin/reload.ts new file mode 100644 index 0000000..1a5c920 --- /dev/null +++ b/bots/discord/src/commands/admin/reload.ts @@ -0,0 +1,17 @@ +import { AdminCommand } from '$/classes/Command' +import { join, dirname } from 'path' + +import type { Config } from 'config.schema' + +export default new AdminCommand({ + name: 'reload', + description: 'Reload configuration', + async execute(context, trigger) { + context.config = ((await import(join(dirname(Bun.main), '..', 'config.js'))) as { default: Config }).default + + await trigger.reply({ + content: 'Reloaded configuration', + ephemeral: true, + }) + }, +}) From b9d08fff644f1ef63333b406316df0c4429f2b92 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 2 Aug 2024 19:04:45 +0700 Subject: [PATCH 230/312] feat(bots/discord)!: add more attachment scan options --- bots/discord/config.schema.ts | 6 +++++- .../discord/messageCreate/messageScan.ts | 18 ++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 91c0889..cb118d4 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -26,7 +26,11 @@ export type Config = { messageScan?: { scanBots?: boolean scanOutsideGuilds?: boolean - allowedAttachmentMimeTypes: string[] + attachments?: { + scanAttachments?: boolean + allowedMimeTypes?: string[] + maxTextFileSize?: number + } filter?: { whitelist?: Filter blacklist?: Filter diff --git a/bots/discord/src/events/discord/messageCreate/messageScan.ts b/bots/discord/src/events/discord/messageCreate/messageScan.ts index f56e7c3..ef5d3c4 100644 --- a/bots/discord/src/events/discord/messageCreate/messageScan.ts +++ b/bots/discord/src/events/discord/messageCreate/messageScan.ts @@ -39,7 +39,7 @@ withContext(on, 'messageCreate', async (context, msg) => { embeds: response.embeds?.map(createMessageScanResponseEmbed), }) - if (label) + if (label) { db.insert(responses).values({ replyId: reply.id, channelId: reply.channel.id, @@ -49,7 +49,6 @@ withContext(on, 'messageCreate', async (context, msg) => { content: msg.content, }) - if (label) { for (const reaction of Object.values(MessageScanLabeledResponseReactions)) { await reply.react(reaction) } @@ -60,11 +59,22 @@ withContext(on, 'messageCreate', async (context, msg) => { } } - if (msg.attachments.size > 0) { + if (msg.attachments.size > 0 && config.attachments?.scanAttachments) { logger.debug(`Classifying message attachments for ${msg.id}`) for (const attachment of msg.attachments.values()) { - if (attachment.contentType && !config.allowedAttachmentMimeTypes.includes(attachment.contentType)) continue + if ( + config.attachments.allowedMimeTypes && + !config.attachments.allowedMimeTypes.includes(attachment.contentType!) + ) { + logger.debug(`Disallowed MIME type for attachment: ${attachment.url}, ${attachment.contentType}`) + continue + } + + if (attachment.contentType?.startsWith('text/') && attachment.size > (config.attachments.maxTextFileSize ?? 512 * 1000)) { + logger.debug(`Attachment ${attachment.url} is too large be to scanned, size is ${attachment.size}`) + continue + } try { const { text: content } = await api.client.parseImage(attachment.url) From 55065186354ca79bb8374940f6a4dd393cf98c98 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 2 Aug 2024 12:31:21 +0000 Subject: [PATCH 231/312] chore(release): 1.0.0-dev.16 [skip ci] # @revanced/discord-bot [1.0.0-dev.16](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.15...@revanced/discord-bot@1.0.0-dev.16) (2024-08-02) ### Bug Fixes * **bots/discord:** open database as read-write ([c366840](https://github.com/revanced/revanced-helper/commit/c36684091dddf67880505dc459e4334a8a5492f4)) * **bots/discord:** remove bad text channel checks ([f5939e2](https://github.com/revanced/revanced-helper/commit/f5939e25288fea2022fdeec9085ecb9ffada6111)) * **bots/discord:** remove redundant footer for response embeds ([412e003](https://github.com/revanced/revanced-helper/commit/412e00317d1eaca23e9c1375e16f94a5f2fa8d86)) ### Features * **bots/discord/commands:** add `reload` command ([6875b32](https://github.com/revanced/revanced-helper/commit/6875b32fd0c6ce3034da9dc6c704d425afb26f2e)) --- bots/discord/CHANGELOG.md | 14 ++++++++++++++ bots/discord/package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 4d66664..bbdc629 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,17 @@ +# @revanced/discord-bot [1.0.0-dev.16](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.15...@revanced/discord-bot@1.0.0-dev.16) (2024-08-02) + + +### Bug Fixes + +* **bots/discord:** open database as read-write ([c366840](https://github.com/revanced/revanced-helper/commit/c36684091dddf67880505dc459e4334a8a5492f4)) +* **bots/discord:** remove bad text channel checks ([f5939e2](https://github.com/revanced/revanced-helper/commit/f5939e25288fea2022fdeec9085ecb9ffada6111)) +* **bots/discord:** remove redundant footer for response embeds ([412e003](https://github.com/revanced/revanced-helper/commit/412e00317d1eaca23e9c1375e16f94a5f2fa8d86)) + + +### Features + +* **bots/discord/commands:** add `reload` command ([6875b32](https://github.com/revanced/revanced-helper/commit/6875b32fd0c6ce3034da9dc6c704d425afb26f2e)) + # @revanced/discord-bot [1.0.0-dev.15](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.14...@revanced/discord-bot@1.0.0-dev.15) (2024-07-31) diff --git a/bots/discord/package.json b/bots/discord/package.json index 83ca7d1..b3996f3 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.15", + "version": "1.0.0-dev.16", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 5925d902095acef5f6396ca03583a9cbb0862498 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 3 Aug 2024 01:30:44 +0700 Subject: [PATCH 232/312] fix(bots/discord/commands/eval): evaluate in current context --- bots/discord/src/commands/admin/eval.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bots/discord/src/commands/admin/eval.ts b/bots/discord/src/commands/admin/eval.ts index 8d80997..93fce79 100644 --- a/bots/discord/src/commands/admin/eval.ts +++ b/bots/discord/src/commands/admin/eval.ts @@ -1,5 +1,5 @@ import { inspect } from 'util' -import { runInNewContext } from 'vm' +import { runInThisContext } from 'vm' import { ApplicationCommandOptionType } from 'discord.js' import { AdminCommand } from '$/classes/Command' @@ -20,13 +20,13 @@ export default new AdminCommand({ required: false, }, }, - async execute(context, trigger, { code, 'show-hidden': showHidden }) { + async execute(_, trigger, { code, 'show-hidden': showHidden }) { await trigger.reply({ ephemeral: true, embeds: [ createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``).addFields({ name: 'Result', - value: `\`\`\`js\n${inspect(runInNewContext(code, { client: trigger.client, context, trigger }), { depth: 1, showHidden, getters: true, numericSeparator: true, showProxy: true })}\`\`\``, + value: `\`\`\`js\n${inspect(runInThisContext(code), { depth: 1, showHidden, getters: true, numericSeparator: true, showProxy: true })}\`\`\``, }), ], }) From a7688fa9b91919a87f74071b502cd0a87cd1c1fa Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 3 Aug 2024 01:31:32 +0700 Subject: [PATCH 233/312] fix(bots/discord): send right response for after regexes --- bots/discord/src/utils/discord/messageScan.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index 46c1274..0cd8376 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -97,8 +97,8 @@ export const getResponseFromText = async ( } = responses[i]! const firstLabelIndex = firstLabelIndexes[i] ?? -1 - for (let i = firstLabelIndex + 1; i < textTriggers!.length; i++) { - const trigger = textTriggers![i]! + for (let j = firstLabelIndex + 1; j < textTriggers!.length; j++) { + const trigger = textTriggers![j]! if (trigger instanceof RegExp) { if (trigger.test(content)) { From bc9951c9b5e007c3e1b3076aa0966ccf29bb18bc Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 3 Aug 2024 01:36:19 +0700 Subject: [PATCH 234/312] feat(bots/discord): update example config file --- bots/discord/config.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/bots/discord/config.js b/bots/discord/config.js index bddf96d..66b1b14 100644 --- a/bots/discord/config.js +++ b/bots/discord/config.js @@ -11,8 +11,21 @@ export default { GUILD_ID_HERE: ['ROLE_ID_HERE'], }, }, + stickyMessages: { + GUILD_ID_HERE: { + CHANNEL_ID_HERE: { + message: { + content: 'This is a sticky message!', + }, + timeout: 60000, + forceSendTimeout: 300000, + } + } + }, moderation: { cure: { + minimumNameLength: 3, + removeCharactersRegex: /[^a-zA-Z0-9 \-_]/g, defaultName: 'Server member', }, roles: ['ROLE_ID_HERE'], @@ -61,7 +74,11 @@ export default { }, }, }, - allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], + attachments: { + scanAttachments: true, + allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp', 'text/plain'], + maxTextFileSize: 512000 + }, responses: [ { filterOverride: { From 2d8688bd4c4cc0ff2be96836c7e0db989a545acf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 2 Aug 2024 18:40:39 +0000 Subject: [PATCH 235/312] chore(release): 1.0.0-dev.17 [skip ci] # @revanced/discord-bot [1.0.0-dev.17](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.16...@revanced/discord-bot@1.0.0-dev.17) (2024-08-02) ### Bug Fixes * **bots/discord/commands/eval:** evaluate in current context ([5925d90](https://github.com/revanced/revanced-helper/commit/5925d902095acef5f6396ca03583a9cbb0862498)) * **bots/discord:** send right response for after regexes ([a7688fa](https://github.com/revanced/revanced-helper/commit/a7688fa9b91919a87f74071b502cd0a87cd1c1fa)) ### Features * **bots/discord:** update example config file ([bc9951c](https://github.com/revanced/revanced-helper/commit/bc9951c9b5e007c3e1b3076aa0966ccf29bb18bc)) --- bots/discord/CHANGELOG.md | 13 +++++++++++++ bots/discord/package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index bbdc629..2582050 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,16 @@ +# @revanced/discord-bot [1.0.0-dev.17](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.16...@revanced/discord-bot@1.0.0-dev.17) (2024-08-02) + + +### Bug Fixes + +* **bots/discord/commands/eval:** evaluate in current context ([5925d90](https://github.com/revanced/revanced-helper/commit/5925d902095acef5f6396ca03583a9cbb0862498)) +* **bots/discord:** send right response for after regexes ([a7688fa](https://github.com/revanced/revanced-helper/commit/a7688fa9b91919a87f74071b502cd0a87cd1c1fa)) + + +### Features + +* **bots/discord:** update example config file ([bc9951c](https://github.com/revanced/revanced-helper/commit/bc9951c9b5e007c3e1b3076aa0966ccf29bb18bc)) + # @revanced/discord-bot [1.0.0-dev.16](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.15...@revanced/discord-bot@1.0.0-dev.16) (2024-08-02) diff --git a/bots/discord/package.json b/bots/discord/package.json index b3996f3..7542988 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.16", + "version": "1.0.0-dev.17", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 6d463df586dee5dd8fe8d6cff1c5316f7809b32a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 3 Aug 2024 22:22:42 +0700 Subject: [PATCH 236/312] fix(bots/discord): set the `label` property correctly for message scans --- bots/discord/src/utils/discord/messageScan.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index 0cd8376..5c4822d 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -79,11 +79,9 @@ export const getResponseFromText = async ( return responseConfig } - responseConfig.label = trigger!.label - if (matchedLabel.confidence >= trigger!.threshold) { logger.debug('Label confidence is enough') - responseConfig = response + responseConfig = { ...responseConfig, ...response, label: trigger!.label } } } } From 4e889d49915a1600bf30643b855df5861626e9ac Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 3 Aug 2024 15:24:18 +0000 Subject: [PATCH 237/312] chore(release): 1.0.0-dev.18 [skip ci] # @revanced/discord-bot [1.0.0-dev.18](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.17...@revanced/discord-bot@1.0.0-dev.18) (2024-08-03) ### Bug Fixes * **bots/discord:** set the `label` property correctly for message scans ([6d463df](https://github.com/revanced/revanced-helper/commit/6d463df586dee5dd8fe8d6cff1c5316f7809b32a)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 2582050..b107846 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.18](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.17...@revanced/discord-bot@1.0.0-dev.18) (2024-08-03) + + +### Bug Fixes + +* **bots/discord:** set the `label` property correctly for message scans ([6d463df](https://github.com/revanced/revanced-helper/commit/6d463df586dee5dd8fe8d6cff1c5316f7809b32a)) + # @revanced/discord-bot [1.0.0-dev.17](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.16...@revanced/discord-bot@1.0.0-dev.17) (2024-08-02) diff --git a/bots/discord/package.json b/bots/discord/package.json index 7542988..aa9fc66 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.17", + "version": "1.0.0-dev.18", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 49c29bebfbe348ae4e2cc1b3a83bfa41eb26ccd1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 4 Aug 2024 00:29:11 +0700 Subject: [PATCH 238/312] fix(bots/discord): correct whitelist logic --- bots/discord/src/utils/discord/messageScan.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index 5c4822d..c66409d 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -87,7 +87,7 @@ export const getResponseFromText = async ( } // If we still don't have a response config, we can match all regexes after the initial label trigger - if (!responseConfig.triggers) { + if (!responseConfig.triggers && ocrMode) { logger.debug('No match from NLP, doing after regexes') for (let i = 0; i < responses.length; i++) { const { @@ -122,17 +122,19 @@ export const messageMatchesFilter = (message: Message, filter: NonNullable memberRoles.has(role)) ?? true) || - (whitelist.users?.includes(message.author.id) ?? true) - : true && - !( - blacklist && - (blacklist.channels?.includes(message.channelId) || - blacklist.roles?.some(role => memberRoles.has(role)) || - blacklist.users?.includes(message.author.id)) - ) + return ( + (whitelist + ? whitelist.channels?.includes(message.channelId) || + whitelist.roles?.some(role => memberRoles.has(role)) || + whitelist.users?.includes(message.author.id) + : true) && + !( + blacklist && + (blacklist.channels?.includes(message.channelId) || + blacklist.roles?.some(role => memberRoles.has(role)) || + blacklist.users?.includes(message.author.id)) + ) + ) } export const handleUserResponseCorrection = async ( From 2c2f6b76d45b7f483fda2863f8ad349df8ae71eb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 3 Aug 2024 17:31:01 +0000 Subject: [PATCH 239/312] chore(release): 1.0.0-dev.19 [skip ci] # @revanced/discord-bot [1.0.0-dev.19](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.18...@revanced/discord-bot@1.0.0-dev.19) (2024-08-03) ### Bug Fixes * **bots/discord:** correct whitelist logic ([49c29be](https://github.com/revanced/revanced-helper/commit/49c29bebfbe348ae4e2cc1b3a83bfa41eb26ccd1)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index b107846..b6ddc97 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.19](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.18...@revanced/discord-bot@1.0.0-dev.19) (2024-08-03) + + +### Bug Fixes + +* **bots/discord:** correct whitelist logic ([49c29be](https://github.com/revanced/revanced-helper/commit/49c29bebfbe348ae4e2cc1b3a83bfa41eb26ccd1)) + # @revanced/discord-bot [1.0.0-dev.18](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.17...@revanced/discord-bot@1.0.0-dev.18) (2024-08-03) diff --git a/bots/discord/package.json b/bots/discord/package.json index aa9fc66..5adcc9a 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.18", + "version": "1.0.0-dev.19", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 65add4dfeed2fa067c2c8e2377f7d01d505ade54 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 4 Aug 2024 02:46:01 +0700 Subject: [PATCH 240/312] feat(apis/websocket): return `true` for data on a `TrainedMessage` packet --- apis/websocket/src/events/trainMessage.ts | 2 +- packages/shared/src/schemas/Packet.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apis/websocket/src/events/trainMessage.ts b/apis/websocket/src/events/trainMessage.ts index 66698ed..1e70982 100644 --- a/apis/websocket/src/events/trainMessage.ts +++ b/apis/websocket/src/events/trainMessage.ts @@ -18,7 +18,7 @@ const trainMessageEventHandler: EventHandler = asy client.send( { op: ServerOperation.TrainedMessage, - d: null, + d: true, }, nextSeq, ) diff --git a/packages/shared/src/schemas/Packet.ts b/packages/shared/src/schemas/Packet.ts index 2e91901..c6447b9 100755 --- a/packages/shared/src/schemas/Packet.ts +++ b/packages/shared/src/schemas/Packet.ts @@ -1,9 +1,9 @@ import { - url, type AnySchema, type NullSchema, type ObjectSchema, type Output, + type BooleanSchema, array, enum_, null_, @@ -11,6 +11,8 @@ import { parse, special, string, + boolean, + url, // merge } from 'valibot' import DisconnectReason from '../constants/DisconnectReason' @@ -26,8 +28,7 @@ export const PacketSchema = special(input => { 'op' in input && typeof input.op === 'number' && input.op in Operation && - 'd' in input && - typeof input.d === 'object' + 'd' in input ) { if (input.op in ServerOperation && !('s' in input && typeof input.s === 'number')) return false @@ -62,7 +63,7 @@ export const PacketDataSchemas = { [ServerOperation.Disconnect]: object({ reason: enum_(DisconnectReason), }), - [ServerOperation.TrainedMessage]: null_(), + [ServerOperation.TrainedMessage]: boolean(), [ServerOperation.TrainMessageFailed]: null_(), [ClientOperation.ParseText]: object({ @@ -78,7 +79,7 @@ export const PacketDataSchemas = { } as const satisfies Record< Operation, // biome-ignore lint/suspicious/noExplicitAny: This is a schema, it's not possible to type it - ObjectSchema | AnySchema | NullSchema + ObjectSchema | AnySchema | NullSchema | BooleanSchema > export type Packet = TOp extends ServerOperation From d90ad5c955a16a4d4359f287dca0aa6f4b1fdec7 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 4 Aug 2024 02:47:00 +0700 Subject: [PATCH 241/312] fix(packages/api): handle close event as a disconnect --- packages/api/src/classes/ClientWebSocket.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index 620d068..fd6e9d0 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -77,9 +77,13 @@ export class ClientWebSocketManager { } catch (e) { rj(e) } - }).finally(() => { - this.connecting = false }) + .then(() => { + this.#socket.on('close', (code, reason) => this._handleDisconnect(code, reason.toString())) + }) + .finally(() => { + this.connecting = false + }) } /** From 4da6175cf58b1fa6144bdc71ec806766d32c1025 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 4 Aug 2024 02:51:49 +0700 Subject: [PATCH 242/312] fix(bots/discord): await when putting entries into db --- .../discord/messageCreate/messageScan.ts | 2 +- .../messageReactionAdd/correctResponse.ts | 23 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/bots/discord/src/events/discord/messageCreate/messageScan.ts b/bots/discord/src/events/discord/messageCreate/messageScan.ts index ef5d3c4..452a470 100644 --- a/bots/discord/src/events/discord/messageCreate/messageScan.ts +++ b/bots/discord/src/events/discord/messageCreate/messageScan.ts @@ -40,7 +40,7 @@ withContext(on, 'messageCreate', async (context, msg) => { }) if (label) { - db.insert(responses).values({ + await db.insert(responses).values({ replyId: reply.id, channelId: reply.channel.id, guildId: reply.guild!.id, diff --git a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index 4e73609..75d8ea5 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -20,7 +20,8 @@ const PossibleReactions = Object.values(Reactions) as string[] withContext(on, 'messageReactionAdd', async (context, rct, user) => { if (user.bot) return - + await rct.users.remove(user.id) + const { database: db, logger, config } = context const { messageScan: msConfig } = config @@ -35,10 +36,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { if (!isAdmin(reactionMessage.member || reactionMessage.author)) { // User is in guild, and config has member requirements - if ( - reactionMessage.inGuild() && - (msConfig.humanCorrections.allow?.members || msConfig.humanCorrections.allow?.users) - ) { + if (reactionMessage.inGuild() && msConfig.humanCorrections.allow) { const { allow: { users: allowedUsers, members: allowedMembers }, } = msConfig.humanCorrections @@ -54,20 +52,19 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { ) ) return - } else if (allowedUsers) { - if (!allowedUsers.includes(user.id)) return - } else { - return void logger.warn( - 'No member or user requirements set for human corrections, all requests will be ignored', - ) - } - } + } else if (!allowedUsers?.includes(user.id)) return + } else + return void logger.warn( + 'No member or user requirements set for human corrections, all requests will be ignored', + ) } // Sanity check const response = await db.query.responses.findFirst({ where: eq(responses.replyId, rct.message.id) }) if (!response || response.correctedById) return + logger.debug(`User ${user.id} is trying to correct the response ${rct.message.id}`) + const handleCorrection = (label: string) => handleUserResponseCorrection(context, response, reactionMessage, label, user) From 9f3295cc0fd8bbf803f9b03ab929e0ea026acab2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 3 Aug 2024 19:53:18 +0000 Subject: [PATCH 243/312] chore(release): 1.0.0-dev.9 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.8...@revanced/bot-websocket-api@1.0.0-dev.9) (2024-08-03) ### Features * **apis/websocket:** return `true` for data on a `TrainedMessage` packet ([65add4d](https://github.com/revanced/revanced-helper/commit/65add4dfeed2fa067c2c8e2377f7d01d505ade54)) --- apis/websocket/CHANGELOG.md | 7 +++++++ apis/websocket/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index f195cc8..311a7fa 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/bot-websocket-api [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.8...@revanced/bot-websocket-api@1.0.0-dev.9) (2024-08-03) + + +### Features + +* **apis/websocket:** return `true` for data on a `TrainedMessage` packet ([65add4d](https://github.com/revanced/revanced-helper/commit/65add4dfeed2fa067c2c8e2377f7d01d505ade54)) + # @revanced/bot-websocket-api [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.7...@revanced/bot-websocket-api@1.0.0-dev.8) (2024-07-31) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index e982fed..9ccad99 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.8", + "version": "1.0.0-dev.9", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { From 98dea81eeb6de7f77caa0e1cb784a4a4a596961d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 3 Aug 2024 19:53:58 +0000 Subject: [PATCH 244/312] chore(release): 1.0.0-dev.20 [skip ci] # @revanced/discord-bot [1.0.0-dev.20](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.19...@revanced/discord-bot@1.0.0-dev.20) (2024-08-03) ### Bug Fixes * **bots/discord:** await when putting entries into db ([4da6175](https://github.com/revanced/revanced-helper/commit/4da6175cf58b1fa6144bdc71ec806766d32c1025)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index b6ddc97..76f1f89 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.20](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.19...@revanced/discord-bot@1.0.0-dev.20) (2024-08-03) + + +### Bug Fixes + +* **bots/discord:** await when putting entries into db ([4da6175](https://github.com/revanced/revanced-helper/commit/4da6175cf58b1fa6144bdc71ec806766d32c1025)) + # @revanced/discord-bot [1.0.0-dev.19](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.18...@revanced/discord-bot@1.0.0-dev.19) (2024-08-03) diff --git a/bots/discord/package.json b/bots/discord/package.json index 5adcc9a..3456b04 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.19", + "version": "1.0.0-dev.20", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From de8bef6520d53a1299f0478458320a7eb75c5e1d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 5 Aug 2024 00:40:36 +0700 Subject: [PATCH 245/312] fix(bots/discord): correct sticky messages logic --- bots/discord/src/context.ts | 17 +++++++--- .../messageCreate/stickyMessageReset.ts | 29 ++++++++++------ bots/discord/src/events/discord/ready.ts | 34 +++++++++++++------ 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 622ff6d..6623773 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -90,13 +90,20 @@ export const discord = { Record< string, { - forceSendTimerActive?: boolean - timeoutMs: number - forceSendMs?: number + /** + * Chat is active, so force send timer is also active + */ + forceTimerActive: boolean + /** + * There was a message sent, so the timer is active + */ + timerActive: boolean + timerMs: number + forceTimerMs?: number send: (forced?: boolean) => Promise currentMessage?: Message - interval?: NodeJS.Timeout - forceSendInterval?: NodeJS.Timeout + timer?: NodeJS.Timeout + forceTimer?: NodeJS.Timeout } > >, diff --git a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts index 0183c14..e0104ac 100644 --- a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts +++ b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts @@ -7,24 +7,31 @@ withContext(on, 'messageCreate', async ({ discord, logger }, msg) => { const store = discord.stickyMessages[msg.guildId]?.[msg.channelId] if (!store) return - if (!store.interval) store.interval = setTimeout(store.send, store.timeoutMs) as NodeJS.Timeout + // If there isn't a timer, start it up + store.timerActive = true + if (!store.timer) store.timer = setTimeout(store.send, store.timerMs) as NodeJS.Timeout else { - store.interval.refresh() + // If there is a timer, but it isn't active, restart it + if (!store.timerActive) store.timer.refresh() + // If there is a timer and it is active, but the force timer isn't active... + else if (!store.forceTimerActive && store.forceTimerMs) { + logger.debug(`Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer and clearing existing timer`) - if (!store.forceSendTimerActive && store.forceSendMs) { - logger.debug(`Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer`) + // Clear the timer + clearTimeout(store.timer) + store.timerActive = false + store.forceTimerActive = true - store.forceSendTimerActive = true - - if (!store.forceSendInterval) - store.forceSendInterval = setTimeout( + // (Re)start the force timer + if (!store.forceTimer) + store.forceTimer = setTimeout( () => store.send(true).then(() => { - store.forceSendTimerActive = false + store.forceTimerActive = false }), - store.forceSendMs, + store.forceTimerMs, ) as NodeJS.Timeout - else store.forceSendInterval.refresh() + else store.forceTimer.refresh() } } }) diff --git a/bots/discord/src/events/discord/ready.ts b/bots/discord/src/events/discord/ready.ts index 3e21af1..f4f6dd9 100644 --- a/bots/discord/src/events/discord/ready.ts +++ b/bots/discord/src/events/discord/ready.ts @@ -14,11 +14,16 @@ export default withContext(on, 'ready', async ({ config, discord, logger }, clie if (config.stickyMessages) for (const [guildId, channels] of Object.entries(config.stickyMessages)) { const guild = await client.guilds.fetch(guildId) + // In case of configuration refresh, this will not be nullable + const oldStore = discord.stickyMessages[guildId] discord.stickyMessages[guildId] = {} for (const [channelId, { message, timeout, forceSendTimeout }] of Object.entries(channels)) { const channel = await guild.channels.fetch(channelId) - if (!channel?.isTextBased()) return + if (!channel?.isTextBased()) + return void logger.warn( + `Channel ${channelId} in guild ${guildId} is not a text channel, sticky messages will not be sent`, + ) const send = async (forced = false) => { try { @@ -33,17 +38,19 @@ export default withContext(on, 'ready', async ({ config, discord, logger }, clie await store.currentMessage?.delete().catch() store.currentMessage = msg - if (!forced) { - clearTimeout(store.forceSendInterval) + // Clear any remaining timers + clearTimeout(store.timer) + clearTimeout(store.forceTimer) + store.forceTimerActive = store.timerActive = false + + if (!forced) logger.debug( `Timeout ended for sticky message in channel ${channelId} in guild ${guildId}, channel is inactive`, ) - } else { - clearTimeout(store.interval) + else logger.debug( `Forced send timeout for sticky message in channel ${channelId} in guild ${guildId} ended, channel is too active`, ) - } logger.debug(`Sent sticky message to channel ${channelId} in guild ${guildId}`) } catch (e) { @@ -53,13 +60,20 @@ export default withContext(on, 'ready', async ({ config, discord, logger }, clie ) } } - + + // Set up the store discord.stickyMessages[guildId]![channelId] = { - forceSendMs: forceSendTimeout, - timeoutMs: timeout, + forceTimerActive: false, + timerActive: false, + forceTimerMs: forceSendTimeout, + timerMs: timeout, send, - forceSendTimerActive: false, + // If the store exists before the configuration refresh, take its current message + currentMessage: oldStore?.[channelId]?.currentMessage } + + // Send a new sticky message immediately, as well as deleting the old/outdated message, if it exists + await send() } } From c567ef25c6f67561fdb270ace3ca64ccf48d71c8 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 5 Aug 2024 00:43:05 +0700 Subject: [PATCH 246/312] feat(packages/api): allow setting new options for `ClientWebsocketManager` --- packages/api/src/classes/ClientWebSocket.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index fd6e9d0..87b75d2 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -17,7 +17,7 @@ import { type RawData, WebSocket } from 'ws' * This is the only relevant class for the time being. But in the future, there may be more classes to handle different protocols of the API. */ export class ClientWebSocketManager { - readonly url: string + url: string timeout: number connecting = false @@ -33,6 +33,21 @@ export class ClientWebSocketManager { this.timeout = options.timeout ?? 10000 } + /** + * Sets the URL to connect to + * + * **Requires a reconnect to take effect** + */ + async setOptions({ url, timeout }: Partial, autoReconnect = true) { + if (url) this.url = url + this.timeout = timeout ?? this.timeout + + if (autoReconnect) { + this.disconnect(true) + await this.connect() + } + } + /** * Connects to the WebSocket API * @returns A promise that resolves when the client is ready From a976dd2accc4b74914651245acde0979c30c92f5 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 5 Aug 2024 00:41:40 +0700 Subject: [PATCH 247/312] fix(bots/discord): some configuration values not applying after running `/reload` --- bots/discord/src/commands/admin/reload.ts | 26 +++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/bots/discord/src/commands/admin/reload.ts b/bots/discord/src/commands/admin/reload.ts index 1a5c920..c8ca185 100644 --- a/bots/discord/src/commands/admin/reload.ts +++ b/bots/discord/src/commands/admin/reload.ts @@ -7,11 +7,29 @@ export default new AdminCommand({ name: 'reload', description: 'Reload configuration', async execute(context, trigger) { + const { api, logger, discord } = context context.config = ((await import(join(dirname(Bun.main), '..', 'config.js'))) as { default: Config }).default - await trigger.reply({ - content: 'Reloaded configuration', - ephemeral: true, - }) + if ('deferReply' in trigger) await trigger.deferReply({ ephemeral: true }) + + logger.info('Reinitializing API client to reload configuration...') + await api.client.ws.setOptions( + { + url: context.config.api.url, + }, + false, + ) + api.intentionallyDisconnecting = true + api.client.disconnect(true) + api.disconnectCount = 0 + api.intentionallyDisconnecting = false + await api.client.connect() + + logger.info('Reinitializing Discord client to reload configuration...') + await discord.client.destroy() + await discord.client.login() + + // @ts-expect-error: TypeScript dum + await trigger[('deferReply' in trigger ? 'editReply' : 'reply')]({ content: 'Reloaded configuration' }) }, }) From eaa25f2eb58a9e2d25bb98633ad668485e099714 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 5 Aug 2024 00:42:20 +0700 Subject: [PATCH 248/312] fix(bots/discord): make `/eval` work --- bots/discord/src/commands/admin/eval.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/commands/admin/eval.ts b/bots/discord/src/commands/admin/eval.ts index 93fce79..2e716c8 100644 --- a/bots/discord/src/commands/admin/eval.ts +++ b/bots/discord/src/commands/admin/eval.ts @@ -1,5 +1,4 @@ import { inspect } from 'util' -import { runInThisContext } from 'vm' import { ApplicationCommandOptionType } from 'discord.js' import { AdminCommand } from '$/classes/Command' @@ -26,7 +25,14 @@ export default new AdminCommand({ embeds: [ createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``).addFields({ name: 'Result', - value: `\`\`\`js\n${inspect(runInThisContext(code), { depth: 1, showHidden, getters: true, numericSeparator: true, showProxy: true })}\`\`\``, + // biome-ignore lint/security/noGlobalEval: This is fine as it's an admin command + value: `\`\`\`js\n${inspect(eval(code), { + depth: 1, + showHidden, + getters: true, + numericSeparator: true, + showProxy: true, + })}\`\`\``, }), ], }) From 9897f244e01008e78a1c108f86253bb6293ad33a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 4 Aug 2024 17:45:52 +0000 Subject: [PATCH 249/312] chore(release): 1.0.0-dev.21 [skip ci] # @revanced/discord-bot [1.0.0-dev.21](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.20...@revanced/discord-bot@1.0.0-dev.21) (2024-08-04) ### Bug Fixes * **bots/discord:** correct sticky messages logic ([de8bef6](https://github.com/revanced/revanced-helper/commit/de8bef6520d53a1299f0478458320a7eb75c5e1d)) * **bots/discord:** make `/eval` work ([eaa25f2](https://github.com/revanced/revanced-helper/commit/eaa25f2eb58a9e2d25bb98633ad668485e099714)) * **bots/discord:** some configuration values not applying after running `/reload` ([a976dd2](https://github.com/revanced/revanced-helper/commit/a976dd2accc4b74914651245acde0979c30c92f5)) --- bots/discord/CHANGELOG.md | 9 +++++++++ bots/discord/package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 76f1f89..d317e7e 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,12 @@ +# @revanced/discord-bot [1.0.0-dev.21](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.20...@revanced/discord-bot@1.0.0-dev.21) (2024-08-04) + + +### Bug Fixes + +* **bots/discord:** correct sticky messages logic ([de8bef6](https://github.com/revanced/revanced-helper/commit/de8bef6520d53a1299f0478458320a7eb75c5e1d)) +* **bots/discord:** make `/eval` work ([eaa25f2](https://github.com/revanced/revanced-helper/commit/eaa25f2eb58a9e2d25bb98633ad668485e099714)) +* **bots/discord:** some configuration values not applying after running `/reload` ([a976dd2](https://github.com/revanced/revanced-helper/commit/a976dd2accc4b74914651245acde0979c30c92f5)) + # @revanced/discord-bot [1.0.0-dev.20](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.19...@revanced/discord-bot@1.0.0-dev.20) (2024-08-03) diff --git a/bots/discord/package.json b/bots/discord/package.json index 3456b04..8dff5e8 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.20", + "version": "1.0.0-dev.21", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 6c8dce059366a6ef85f5b8b1794c056515b9f5b6 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 10 Aug 2024 21:44:09 +0700 Subject: [PATCH 250/312] fix(bots/discord): parse larger units of durations, fix wrong timestamp in mod embed --- bots/discord/src/commands/moderation/mute.ts | 2 +- .../src/commands/moderation/role-preset.ts | 2 +- bots/discord/src/utils/discord/moderation.ts | 6 +++--- bots/discord/src/utils/discord/rolePresets.ts | 4 ++-- bots/discord/src/utils/duration.ts | 15 +++++++++------ 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index 2fa1313..2b4472f 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -60,7 +60,7 @@ export default new ModerationCommand({ await applyRolePreset(member, 'mute', expires) await sendModerationReplyAndLogs( interaction, - createModerationActionEmbed('Muted', user, executor.user, reason, duration), + createModerationActionEmbed('Muted', user, executor.user, reason, Math.ceil(expires / 1000)), ) if (duration) diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts index aa33817..c65b1bb 100644 --- a/bots/discord/src/commands/moderation/role-preset.ts +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -83,6 +83,6 @@ export default new ModerationCommand({ removeRolePreset(member, preset) }, expires) - await sendPresetReplyAndLogs(apply ? 'apply' : 'remove', trigger, executor, user, preset, expires) + await sendPresetReplyAndLogs(apply ? 'apply' : 'remove', trigger, executor, user, preset, expires ? Math.ceil(expires / 1000) : undefined) }, }) diff --git a/bots/discord/src/utils/discord/moderation.ts b/bots/discord/src/utils/discord/moderation.ts index 9f8cb00..dbb0a9c 100644 --- a/bots/discord/src/utils/discord/moderation.ts +++ b/bots/discord/src/utils/discord/moderation.ts @@ -1,6 +1,6 @@ import { config, logger } from '$/context' import decancer from 'decancer' -import type { ChatInputCommandInteraction, EmbedBuilder, Guild, GuildMember, Message, User } from 'discord.js' +import type { CommandInteraction, EmbedBuilder, Guild, GuildMember, Message, User } from 'discord.js' import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from './embeds' const PresetLogAction = { @@ -10,7 +10,7 @@ const PresetLogAction = { export const sendPresetReplyAndLogs = ( action: keyof typeof PresetLogAction, - interaction: ChatInputCommandInteraction | Message, + interaction: CommandInteraction | Message, executor: GuildMember, user: User, preset: string, @@ -24,7 +24,7 @@ export const sendPresetReplyAndLogs = ( ) export const sendModerationReplyAndLogs = async ( - interaction: ChatInputCommandInteraction | Message, + interaction: CommandInteraction | Message, embed: EmbedBuilder, ) => { const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) diff --git a/bots/discord/src/utils/discord/rolePresets.ts b/bots/discord/src/utils/discord/rolePresets.ts index 69c2fa5..42f1704 100644 --- a/bots/discord/src/utils/discord/rolePresets.ts +++ b/bots/discord/src/utils/discord/rolePresets.ts @@ -6,9 +6,9 @@ import { and, eq } from 'drizzle-orm' // TODO: Fix this type type PresetKey = string -export const applyRolePreset = async (member: GuildMember, presetName: PresetKey, untilMs: number) => { +export const applyRolePreset = async (member: GuildMember, presetName: PresetKey, expires: number) => { const afterInsert = await applyRolesUsingPreset(presetName, member, true) - const until = untilMs === Infinity ? null : Math.ceil(untilMs / 1000) + const until = expires === Infinity ? null : Math.ceil(expires / 1000) await database .insert(appliedPresets) diff --git a/bots/discord/src/utils/duration.ts b/bots/discord/src/utils/duration.ts index 4b5205c..64e672c 100644 --- a/bots/discord/src/utils/duration.ts +++ b/bots/discord/src/utils/duration.ts @@ -1,13 +1,16 @@ export const parseDuration = (duration: string) => { if (!duration.length) return Number.NaN - const matches = duration.match(/(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?/)! + const matches = duration.match(/(?:(\d+y)?(\d+M)?(\d+w)?(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)/)! - const [, days, hours, minutes, seconds] = matches.map(Number) + const [, years, months, weeks, days, hours, minutes, seconds] = matches.map(Number) return ( - (days || 0) * 24 * 60 * 60 * 1000 + - (hours || 0) * 60 * 60 * 1000 + - (minutes || 0) * 60 * 1000 + - (seconds || 0) * 1000 + (years || 0) * 290304e5 + + (months || 0) * 24192e5 + + (weeks || 0) * 6048e5 + + (days || 0) * 864e5 + + (hours || 0) * 36e5 + + (minutes || 0) * 6e4 + + (seconds || 0) * 1e3 ) } From 80aeb1902063140a2e78cfaed9424e5101ab03f1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 10 Aug 2024 22:28:55 +0700 Subject: [PATCH 251/312] feat(bots/discord): add code to actually scan text files correctly --- .../discord/messageCreate/messageScan.ts | 30 +++++++++++++------ bots/discord/src/utils/discord/messageScan.ts | 8 ++--- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/bots/discord/src/events/discord/messageCreate/messageScan.ts b/bots/discord/src/events/discord/messageCreate/messageScan.ts index 452a470..c9bea0d 100644 --- a/bots/discord/src/events/discord/messageCreate/messageScan.ts +++ b/bots/discord/src/events/discord/messageCreate/messageScan.ts @@ -59,26 +59,38 @@ withContext(on, 'messageCreate', async (context, msg) => { } } - if (msg.attachments.size > 0 && config.attachments?.scanAttachments) { + if (msg.attachments.size && config.attachments?.scanAttachments) { logger.debug(`Classifying message attachments for ${msg.id}`) for (const attachment of msg.attachments.values()) { + const mimeType = attachment.contentType?.split(';')?.[0] + if (!mimeType) return void logger.warn(`No MIME type for attachment: ${attachment.url}`) + if ( config.attachments.allowedMimeTypes && - !config.attachments.allowedMimeTypes.includes(attachment.contentType!) + !config.attachments.allowedMimeTypes.includes(mimeType) ) { - logger.debug(`Disallowed MIME type for attachment: ${attachment.url}, ${attachment.contentType}`) + logger.debug(`Disallowed MIME type for attachment: ${attachment.url}, ${mimeType}`) continue } - if (attachment.contentType?.startsWith('text/') && attachment.size > (config.attachments.maxTextFileSize ?? 512 * 1000)) { - logger.debug(`Attachment ${attachment.url} is too large be to scanned, size is ${attachment.size}`) + const isTextFile = mimeType.startsWith('text/') + + if (isTextFile && attachment.size > (config.attachments.maxTextFileSize ?? 512 * 1000)) { + logger.debug(`Attachment ${attachment.url} is too large be to scanned, size is ${attachment.size}`) continue } try { - const { text: content } = await api.client.parseImage(attachment.url) - const { response } = await getResponseFromText(content, filteredResponses, context, true) + let response: Awaited>['response'] | undefined + + if (isTextFile) { + const content = await (await fetch(attachment.url)).text() + response = await getResponseFromText(content, filteredResponses, context, { skipApiRequest: true }).then(it => it.response) + } else { + const { text: content } = await api.client.parseImage(attachment.url) + response = await getResponseFromText(content, filteredResponses, context, { onlyImageTriggers: true }).then(it => it.response) + } if (response) { logger.debug(`Response found for attachment: ${attachment.url}`) @@ -89,8 +101,8 @@ withContext(on, 'messageCreate', async (context, msg) => { break } - } catch { - logger.error(`Failed to parse image: ${attachment.url}`) + } catch (e) { + logger.error(`Failed to parse attachment: ${attachment.url}`, e) } } } diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index c66409d..8d6699a 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -9,7 +9,7 @@ export const getResponseFromText = async ( responses: ConfigMessageScanResponse[], // Just to be safe that we will never use data from the context parameter { api, logger }: Omit, - ocrMode = false, + flags: { onlyImageTriggers?: boolean; skipApiRequest?: boolean } = {} ): Promise< Omit & { label?: string; triggers?: ConfigMessageScanResponse['triggers'] } > => { @@ -31,7 +31,7 @@ export const getResponseFromText = async ( triggers: { text: textTriggers, image: imageTriggers }, } = trigger - if (ocrMode) { + if (flags.onlyImageTriggers) { if (imageTriggers) for (const regex of imageTriggers) if (regex.test(content)) { @@ -57,7 +57,7 @@ export const getResponseFromText = async ( } // If none of the regexes match, we can search for labels immediately - if (!responseConfig.triggers && !ocrMode) { + if (!responseConfig.triggers && !flags.onlyImageTriggers && !flags.skipApiRequest) { logger.debug('No match from before regexes, doing NLP') const scan = await api.client.parseText(content) if (scan.labels.length) { @@ -87,7 +87,7 @@ export const getResponseFromText = async ( } // If we still don't have a response config, we can match all regexes after the initial label trigger - if (!responseConfig.triggers && ocrMode) { + if (!responseConfig.triggers && flags.onlyImageTriggers) { logger.debug('No match from NLP, doing after regexes') for (let i = 0; i < responses.length; i++) { const { From dd21a5abad560f3d00b8c58912786d4b6bd520e9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 10 Aug 2024 22:29:36 +0700 Subject: [PATCH 252/312] fix(bots/discord): provide discord token for `reload` command --- bots/discord/src/commands/admin/reload.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/commands/admin/reload.ts b/bots/discord/src/commands/admin/reload.ts index c8ca185..ada207c 100644 --- a/bots/discord/src/commands/admin/reload.ts +++ b/bots/discord/src/commands/admin/reload.ts @@ -27,7 +27,9 @@ export default new AdminCommand({ logger.info('Reinitializing Discord client to reload configuration...') await discord.client.destroy() - await discord.client.login() + // discord.client.token only gets set whenever a new Client is intialized + // so that's why we need to provide the token here :/ + await discord.client.login(process.env['DISCORD_TOKEN']) // @ts-expect-error: TypeScript dum await trigger[('deferReply' in trigger ? 'editReply' : 'reply')]({ content: 'Reloaded configuration' }) From 82fac783ea50afaaa455ff325f7c6e22d7360e39 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 10 Aug 2024 15:31:57 +0000 Subject: [PATCH 253/312] chore(release): 1.0.0-dev.22 [skip ci] # @revanced/discord-bot [1.0.0-dev.22](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.21...@revanced/discord-bot@1.0.0-dev.22) (2024-08-10) ### Bug Fixes * **bots/discord:** parse larger units of durations, fix wrong timestamp in mod embed ([6c8dce0](https://github.com/revanced/revanced-helper/commit/6c8dce059366a6ef85f5b8b1794c056515b9f5b6)) * **bots/discord:** provide discord token for `reload` command ([dd21a5a](https://github.com/revanced/revanced-helper/commit/dd21a5abad560f3d00b8c58912786d4b6bd520e9)) ### Features * **bots/discord:** add code to actually scan text files correctly ([80aeb19](https://github.com/revanced/revanced-helper/commit/80aeb1902063140a2e78cfaed9424e5101ab03f1)) --- bots/discord/CHANGELOG.md | 13 +++++++++++++ bots/discord/package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index d317e7e..2dd09d4 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,16 @@ +# @revanced/discord-bot [1.0.0-dev.22](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.21...@revanced/discord-bot@1.0.0-dev.22) (2024-08-10) + + +### Bug Fixes + +* **bots/discord:** parse larger units of durations, fix wrong timestamp in mod embed ([6c8dce0](https://github.com/revanced/revanced-helper/commit/6c8dce059366a6ef85f5b8b1794c056515b9f5b6)) +* **bots/discord:** provide discord token for `reload` command ([dd21a5a](https://github.com/revanced/revanced-helper/commit/dd21a5abad560f3d00b8c58912786d4b6bd520e9)) + + +### Features + +* **bots/discord:** add code to actually scan text files correctly ([80aeb19](https://github.com/revanced/revanced-helper/commit/80aeb1902063140a2e78cfaed9424e5101ab03f1)) + # @revanced/discord-bot [1.0.0-dev.21](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.20...@revanced/discord-bot@1.0.0-dev.21) (2024-08-04) diff --git a/bots/discord/package.json b/bots/discord/package.json index 8dff5e8..e525e7a 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.21", + "version": "1.0.0-dev.22", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 97f2795df4ede4d12a08193dba453c1bc765a4c2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 13 Aug 2024 21:15:34 +0700 Subject: [PATCH 254/312] feat(bots/discord): update to newer command definition framework --- bots/discord/package.json | 3 +- bots/discord/src/classes/Command.ts | 393 ++++++++++++------ bots/discord/src/classes/CommandError.ts | 33 +- .../src/commands/admin/slash-commands.ts | 2 +- bots/discord/src/commands/fun/coinflip.ts | 2 +- bots/discord/src/commands/moderation/ban.ts | 2 +- bots/discord/src/commands/moderation/mute.ts | 6 +- bots/discord/src/commands/moderation/purge.ts | 2 +- .../src/commands/moderation/role-preset.ts | 6 +- .../src/commands/moderation/slowmode.ts | 6 +- .../discord/src/commands/moderation/unmute.ts | 2 +- bots/discord/src/context.ts | 4 +- .../discord/interactionCreate/chatCommand.ts | 11 +- .../interactionCreate/contextMenuCommand.ts | 26 ++ .../discord/messageCreate/messageCommand.ts | 4 +- 15 files changed, 340 insertions(+), 162 deletions(-) create mode 100644 bots/discord/src/events/discord/interactionCreate/contextMenuCommand.ts diff --git a/bots/discord/package.json b/bots/discord/package.json index e525e7a..0378f09 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -6,12 +6,11 @@ "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { - "register": "bun run scripts/reload-slash-commands.ts", "start": "bun prepare && bun run src/index.ts", "dev": "bun prepare && bun --watch src/index.ts", "build": "bun prepare && bun run scripts/build.ts", "watch": "bun dev", - "prepare": "bun run scripts/generate-indexes.ts && bunx drizzle-kit generate --name=schema" + "prepare": "bun run scripts/generate-indexes.ts && bunx --bun drizzle-kit generate --name=schema" }, "repository": { "type": "git", diff --git a/bots/discord/src/classes/Command.ts b/bots/discord/src/classes/Command.ts index f02a573..0331d0f 100644 --- a/bots/discord/src/classes/Command.ts +++ b/bots/discord/src/classes/Command.ts @@ -1,76 +1,145 @@ -import { ApplicationCommandOptionType } from 'discord.js' +import { ApplicationCommandOptionType, ApplicationCommandType } from 'discord.js' +import { isAdmin } from '../utils/discord/permissions' -import { createErrorEmbed } from '$/utils/discord/embeds' -import { isAdmin } from '$/utils/discord/permissions' - -import { config } from '../context' import CommandError, { CommandErrorType } from './CommandError' -import type { Filter } from 'config.schema' import type { APIApplicationCommandChannelOption, CacheType, Channel, ChatInputCommandInteraction, + CommandInteraction, CommandInteractionOption, GuildMember, Message, + MessageContextMenuCommandInteraction, + RESTPostAPIApplicationCommandsJSONBody, RESTPostAPIChatInputApplicationCommandsJSONBody, Role, User, + UserContextMenuCommandInteraction, + UserResolvable, } from 'discord.js' +import { config } from '../context' + +export enum CommandType { + ChatGlobal = 1, + ChatGuild, + ContextMenuUser, + ContextMenuMessage, + ContextMenuGuildMessage, + ContextMenuGuildMember, +} export default class Command< - Global extends boolean = false, - Options extends CommandOptionsOptions | undefined = undefined, - AllowMessageCommand extends boolean = false, + const Type extends CommandType = CommandType.ChatGuild, + const Options extends If, undefined, CommandOptionsOptions | undefined> = undefined, + const AllowMessageCommand extends If, false, boolean> = false, > { name: string description: string requirements?: CommandRequirements options?: Options - global?: Global - #execute: CommandExecuteFunction + type: Type + allowMessageCommand: AllowMessageCommand + #execute: CommandExecuteFunction static OptionType = ApplicationCommandOptionType + static Type = CommandType constructor({ name, description, requirements, options, - global, + type, + allowMessageCommand, execute, - }: CommandOptions) { + }: CommandOptions) { this.name = name - this.description = description + this.description = description! this.requirements = requirements this.options = options - this.global = global + // @ts-expect-error: Default is `CommandType.GuildOnly`, it makes sense + this.type = type ?? CommandType.ChatGuild + // @ts-expect-error: Default is `false`, it makes sense + this.allowMessageCommand = allowMessageCommand ?? false this.#execute = execute } + isGuildSpecific(): this is Command< + CommandType.ChatGuild | CommandType.ContextMenuGuildMember | CommandType.ContextMenuGuildMessage, + Options, + AllowMessageCommand + > { + return [ + CommandType.ChatGuild, + CommandType.ContextMenuGuildMessage, + CommandType.ContextMenuGuildMember, + ].includes(this.type) + } + + isContextMenuSpecific(): this is Command< + | CommandType.ContextMenuGuildMessage + | CommandType.ContextMenuGuildMember + | CommandType.ContextMenuUser + | CommandType.ContextMenuMessage, + undefined, + false + > { + return [ + CommandType.ContextMenuMessage, + CommandType.ContextMenuUser, + CommandType.ContextMenuGuildMessage, + CommandType.ContextMenuGuildMember, + ].includes(this.type) + } + + isGuildContextMenuSpecific(): this is Command< + CommandType.ContextMenuGuildMessage | CommandType.ContextMenuGuildMember, + undefined, + false + > { + return [CommandType.ContextMenuGuildMessage, CommandType.ContextMenuGuildMember].includes(this.type) + } + + async onContextMenuInteraction( + context: typeof import('../context'), + interaction: If< + Extends, + MessageContextMenuCommandInteraction>, + UserContextMenuCommandInteraction> + >, + ): Promise { + if (!this.isGuildSpecific() && !interaction.inGuild()) + throw new CommandError(CommandErrorType.InteractionNotInGuild) + + const executor = await this.#fetchInteractionExecutor(interaction) + const target = + this.type === CommandType.ContextMenuGuildMember + ? this.isGuildSpecific() + ? fetchMember(interaction as CommandInteraction<'raw' | 'cached'>, interaction.targetId) + : interaction.client.users.fetch(interaction.targetId) + : interaction.channel?.messages.fetch(interaction.targetId) + + if (!target) throw new CommandError(CommandErrorType.FetchManagerNotFound) + + // @ts-expect-error: Type mismatch (again!) because TypeScript is not smart enough + return await this.#execute({ ...context, executor, target }, interaction, undefined) + } + async onMessage( context: typeof import('../context'), - msg: Message>, + msg: Message>, args: CommandArguments, ): Promise { - if (!this.global && !msg.inGuild()) - return await msg.reply({ - embeds: [createErrorEmbed('Cannot run this command', 'This command can only be used in a server.')], - }) + if (!this.allowMessageCommand) return + if (!this.isGuildSpecific() && !msg.guildId) throw new CommandError(CommandErrorType.InteractionNotInGuild) - const executor = this.global ? msg.author : await msg.guild?.members.fetch(msg.author.id)! - - if (!(await this.canExecute(executor, msg.channelId))) - return await msg.reply({ - embeds: [ - createErrorEmbed( - 'Cannot run this command', - 'You do not meet the requirements to run this command.', - ), - ], - }) + const executor = this.isGuildSpecific() + ? await msg.guild?.members.fetch(msg.author)! + : await msg.client.users.fetch(msg.author) + if (!(await this.canExecute(executor))) throw new CommandError(CommandErrorType.RequirementsNotMet) const options = this.options ? ((await this.#resolveMessageOptions(msg, this.options, args)) as CommandExecuteFunctionOptionsParameter< @@ -120,14 +189,18 @@ export default class Command< `Invalid type for argument **${name}**.${argExplainationString}\n\nExpected type: **${expectedType}**\nGot type: **${ApplicationCommandOptionType[arg.type]}**${choicesString}`, ) - if ('choices' in option && option.choices && !option.choices.some(({ value }) => value === arg)) + const argValue = typeof arg === 'string' ? arg : arg?.id + + if ( + 'choices' in option && + option.choices && + !option.choices.some(({ value }) => value === (typeof value === 'number' ? Number(argValue) : argValue)) + ) throw new CommandError( CommandErrorType.InvalidArgument, - `Invalid choice for argument **${name}**.\n${argExplainationString}\n\n${choicesString}\n`, + `Invalid choice for argument **${name}**.\n${argExplainationString}${choicesString}\n`, ) - const argValue = typeof arg === 'string' ? arg : arg?.id - if (argValue && arg) { if (isSubcommandLikeOption) { const [subcommandName, subcommandOption] = iterableOptions.find(([name]) => name === argValue)! @@ -142,6 +215,16 @@ export default class Command< break } + if ( + type === ApplicationCommandOptionType.String && + ((typeof option.minLength === 'number' && argValue.length < option.minLength) || + (typeof option.maxLength === 'number' && argValue.length > option.maxLength)) + ) + throw new CommandError( + CommandErrorType.InvalidArgument, + `Invalid string length for argument **${name}**.\nLengths allowed: ${option.minLength ?? '(any)'} - ${option.maxLength ?? '(any)'}.${argExplainationString}`, + ) + if ( (type === ApplicationCommandOptionType.Channel || type === ApplicationCommandOptionType.User || @@ -153,14 +236,21 @@ export default class Command< `Malformed ID for argument **${name}**.${argExplainationString}`, ) - if ( - (type === ApplicationCommandOptionType.Number || type === ApplicationCommandOptionType.Integer) && - Number.isNaN(Number(argValue)) - ) { - throw new CommandError( - CommandErrorType.InvalidArgument, - `Invalid number for argument **${name}**.${argExplainationString}`, + if (type === ApplicationCommandOptionType.Number || type === ApplicationCommandOptionType.Integer) { + if (Number.isNaN(Number(argValue))) + throw new CommandError( + CommandErrorType.InvalidArgument, + `Invalid number for argument **${name}**.${argExplainationString}`, + ) + + if ( + (typeof option.min === 'number' && Number(argValue) < option.min) || + (typeof option.max === 'number' && Number(argValue) > option.max) ) + throw new CommandError( + CommandErrorType.InvalidArgument, + `Number out of range for argument **${name}**.\nRange allowed: ${option.min ?? '(any)'} - ${option.max ?? '(any)'}.${argExplainationString}`, + ) } if ( @@ -177,7 +267,7 @@ export default class Command< type === ApplicationCommandOptionType.Number || type === ApplicationCommandOptionType.Integer ? Number(argValue) : type === ApplicationCommandOptionType.Boolean - ? argValue[0] === 't' || argValue[0] === 'y' + ? ['t', 'y', 'yes', 'true'].some(value => value === argValue.toLowerCase()) : type === ApplicationCommandOptionType.Channel ? await msg.client.channels.fetch(argValue) : type === ApplicationCommandOptionType.User @@ -191,44 +281,27 @@ export default class Command< return _options } + #fetchInteractionExecutor(interaction: CommandInteraction) { + return this.isGuildSpecific() + ? fetchMember(interaction as CommandInteraction<'raw' | 'cached'>) + : fetchUser(interaction) + } + async onInteraction( context: typeof import('../context'), interaction: ChatInputCommandInteraction, ): Promise { - const { logger } = context + if (interaction.commandName !== this.name) + throw new CommandError( + CommandErrorType.InteractionDataMismatch, + 'The interaction command name does not match the expected command name.', + ) - if (interaction.commandName !== this.name) { - logger.warn(`Command name mismatch, expected ${this.name}, but got ${interaction.commandName}!`) - return await interaction.reply({ - embeds: [ - createErrorEmbed( - 'Internal command name mismatch', - 'The interaction command name does not match the expected command name.', - ), - ], - }) - } + if (!this.isGuildSpecific() && !interaction.inGuild()) + throw new CommandError(CommandErrorType.InteractionNotInGuild) - if (!this.global && !interaction.inGuild()) { - logger.error(`Command ${this.name} cannot be run in DMs, but was registered as global`) - return await interaction.reply({ - embeds: [createErrorEmbed('Cannot run this command', 'This command can only be used in a server.')], - ephemeral: true, - }) - } - - const executor = this.global ? interaction.user : await interaction.guild?.members.fetch(interaction.user.id)! - - if (!(await this.canExecute(executor, interaction.channelId))) - return await interaction.reply({ - embeds: [ - createErrorEmbed( - 'Cannot run this command', - 'You do not meet the requirements to run this command.', - ), - ], - ephemeral: true, - }) + const executor = await this.#fetchInteractionExecutor(interaction) + if (!(await this.canExecute(executor))) throw new CommandError(CommandErrorType.RequirementsNotMet) const options = this.options ? ((await this.#resolveInteractionOptions(interaction)) as CommandExecuteFunctionOptionsParameter< @@ -237,14 +310,10 @@ export default class Command< : undefined if (options === null) - return await interaction.reply({ - embeds: [ - createErrorEmbed( - 'Internal command option type mismatch', - 'The interaction command option type does not match the expected command option type.', - ), - ], - }) + throw new CommandError( + CommandErrorType.InteractionDataMismatch, + 'The registered interaction command option type does not match the expected command option type.', + ) // @ts-expect-error: Type mismatch (again!) because TypeScript is not smart enough return await this.#execute({ ...context, executor }, interaction, options) @@ -288,7 +357,7 @@ export default class Command< return _options } - async canExecute(executor: User | GuildMember, channelId: string): Promise { + async canExecute(executor: User | GuildMember): Promise { if (!this.requirements) return false const isExecutorAdmin = isAdmin(executor) @@ -296,7 +365,6 @@ export default class Command< const { adminOnly, - channels, roles, permissions, users, @@ -305,16 +373,23 @@ export default class Command< memberRequirementsForUsers = 'pass', } = this.requirements - const member = this.global ? null : (executor as GuildMember) - const bDefCond = defaultCondition !== 'fail' - const bMemReqForUsers = memberRequirementsForUsers !== 'fail' + const member = this.isGuildSpecific() ? null : (executor as GuildMember) + const boolDefaultCondition = defaultCondition !== 'fail' + const boolMemberRequirementsForUsers = memberRequirementsForUsers !== 'fail' const conditions = [ - adminOnly ? isExecutorAdmin : bDefCond, - channels ? channels.includes(channelId) : bDefCond, - member ? (roles ? roles.some(role => member.roles.cache.has(role)) : bDefCond) : bMemReqForUsers, - member ? (permissions ? member.permissions.has(permissions) : bDefCond) : bMemReqForUsers, - users ? users.includes(executor.id) : bDefCond, + adminOnly ? isExecutorAdmin : boolDefaultCondition, + users ? users.includes(executor.id) : boolDefaultCondition, + member + ? roles + ? roles.some(role => member.roles.cache.has(role)) + : boolDefaultCondition + : boolMemberRequirementsForUsers, + member + ? permissions + ? member.permissions.has(permissions) + : boolDefaultCondition + : boolMemberRequirementsForUsers, ] if (mode === 'all' && conditions.some(condition => !condition)) return false @@ -323,14 +398,27 @@ export default class Command< return true } - get json(): RESTPostAPIChatInputApplicationCommandsJSONBody & { contexts: Array<0 | 1 | 2> } { - return { + get json(): RESTPostAPIApplicationCommandsJSONBody { + // @ts-expect-error: I hate union types in TypeScript + const base: RESTPostAPIApplicationCommandsJSONBody = { name: this.name, + type: + this.type === CommandType.ContextMenuGuildMessage || this.type === CommandType.ContextMenuMessage + ? ApplicationCommandType.Message + : this.type === CommandType.ContextMenuGuildMember || this.type === CommandType.ContextMenuUser + ? ApplicationCommandType.User + : ApplicationCommandType.ChatInput, + } + + if (this.isContextMenuSpecific()) return base + + return { + ...base, description: this.description, options: this.options ? this.#transformOptions(this.options) : undefined, // https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types - contexts: this.global ? [0] : [0, 1], - } + contexts: this.isGuildSpecific() ? [0] : [0, 1, 2], + } as RESTPostAPIChatInputApplicationCommandsJSONBody & { contexts: Array<0 | 1 | 2> } } #transformOptions(optionsObject: Record) { @@ -377,8 +465,8 @@ export default class Command< export class ModerationCommand< Options extends CommandOptionsOptions, AllowMessageCommand extends boolean = true, -> extends Command { - constructor(options: ExtendedCommandOptions) { +> extends Command { + constructor(options: ExtendedCommandOptions) { super({ ...options, requirements: { @@ -388,17 +476,16 @@ export class ModerationCommand< }, // @ts-expect-error: No thanks allowMessageCommand: options.allowMessageCommand ?? true, - global: false, + type: CommandType.ChatGuild, }) } } -export class AdminCommand extends Command< - true, - Options, - AllowMessageCommand -> { - constructor(options: ExtendedCommandOptions) { +export class AdminCommand< + Options extends CommandOptionsOptions, + AllowMessageCommand extends boolean = true, +> extends Command { + constructor(options: ExtendedCommandOptions) { super({ ...options, requirements: { @@ -406,38 +493,52 @@ export class AdminCommand, + source: UserResolvable = interaction.user, + manager = interaction.guild?.members, +) => { + const _manager = manager ?? (await interaction.client.guilds.fetch(interaction.guildId).then(it => it.members)) + if (!_manager) throw new CommandError(CommandErrorType.FetchManagerNotFound, 'Cannot fetch member.') + return await _manager.fetch(source) +} + +const fetchUser = (interaction: CommandInteraction, source: UserResolvable = interaction.user) => { + return interaction.client.users.fetch(source) +} + /* TODO: APIApplicationCommandAttachmentOption APIApplicationCommandMentionableOption APIApplicationCommandRoleOption */ -export interface CommandOptions< - Global extends boolean, +export type CommandOptions< + Type extends CommandType, Options extends CommandOptionsOptions | undefined, AllowMessageCommand extends boolean, -> { +> = { name: string - description: string requirements?: CommandRequirements options?: Options - execute: CommandExecuteFunction - global?: Global + execute: CommandExecuteFunction + type?: Type allowMessageCommand?: AllowMessageCommand -} +} & If, { description?: never }, { description: string }> export type CommandArguments = Array - export type CommandSpecialArgument = { type: (typeof CommandSpecialArgumentType)[keyof typeof CommandSpecialArgumentType] id: string } +//! If things ever get minified, this will most likely break property access via string names export const CommandSpecialArgumentType = { Channel: ApplicationCommandOptionType.Channel, Role: ApplicationCommandOptionType.Role, @@ -445,31 +546,56 @@ export const CommandSpecialArgumentType = { } type ExtendedCommandOptions< - Global extends boolean, + Type extends CommandType, Options extends CommandOptionsOptions, AllowMessageCommand extends boolean, -> = Omit, 'global'> & { - requirements?: Omit['requirements'], 'defaultCondition'> +> = Omit, 'type'> & { + requirements?: Omit['requirements'], 'defaultCondition'> } export type CommandOptionsOptions = Record +type ToCacheType = If, 'raw' | 'cached', CacheType> + type CommandExecuteFunction< - Global extends boolean, + Type extends CommandType, Options extends CommandOptionsOptions | undefined, AllowMessageCommand extends boolean, > = ( - context: CommandContext, + context: CommandContext, trigger: If< AllowMessageCommand, - Message> | ChatInputCommandInteraction>, - ChatInputCommandInteraction> + Message> | CommandTypeToInteractionMap>[Type], + CommandTypeToInteractionMap>[Type] >, options: Options extends CommandOptionsOptions ? CommandExecuteFunctionOptionsParameter : never, ) => Promise | unknown +type CommandTypeToInteractionMap = { + [CommandType.ChatGlobal]: ChatInputCommandInteraction + [CommandType.ChatGuild]: ChatInputCommandInteraction + [CommandType.ContextMenuUser]: UserContextMenuCommandInteraction + [CommandType.ContextMenuMessage]: MessageContextMenuCommandInteraction + [CommandType.ContextMenuGuildMessage]: MessageContextMenuCommandInteraction + [CommandType.ContextMenuGuildMember]: MessageContextMenuCommandInteraction +} + +type IsContextMenu = Extends< + Type, + | CommandType.ContextMenuGuildMessage + | CommandType.ContextMenuGuildMember + | CommandType.ContextMenuMessage + | CommandType.ContextMenuUser +> + +type IsGuildSpecific = Extends< + Type, + CommandType.ChatGuild | CommandType.ContextMenuGuildMember | CommandType.ContextMenuGuildMessage +> + +type Extends = T extends U ? true : false type If = T extends true ? U : V -type InvertBoolean = If +// type InvertBoolean = If type CommandExecuteFunctionOptionsParameter = { [K in keyof Options]: Options[K]['type'] extends @@ -484,8 +610,13 @@ type CommandExecuteFunctionOptionsParameter } -type CommandContext = typeof import('../context') & { - executor: CommandExecutor +type CommandContext = typeof import('../context') & { + executor: CommandExecutor + target: If< + Extends, + GuildMember, + If, Message, never> + > } type CommandOptionValueMap = { @@ -511,7 +642,7 @@ type CommandOption = | CommandSubcommandOption | CommandSubcommandGroupOption -type CommandExecutor = If +type CommandExecutor = If, GuildMember, User> type CommandOptionBase = { type: Type @@ -585,10 +716,12 @@ interface CommandSubcommandLikeOption< type CommandSubcommandOption = CommandSubcommandLikeOption type CommandSubcommandGroupOption = CommandSubcommandLikeOption -export type CommandRequirements = Filter & { - mode?: 'all' | 'any' - adminOnly?: boolean +export type CommandRequirements = { + users?: string[] + roles?: string[] permissions?: bigint + adminOnly?: boolean defaultCondition?: 'fail' | 'pass' - memberRequirementsForUsers?: 'pass' | 'fail' + memberRequirementsForUsers?: 'fail' | 'pass' + mode?: 'all' | 'any' } diff --git a/bots/discord/src/classes/CommandError.ts b/bots/discord/src/classes/CommandError.ts index 23574b0..94e9733 100644 --- a/bots/discord/src/classes/CommandError.ts +++ b/bots/discord/src/classes/CommandError.ts @@ -1,9 +1,9 @@ -import { createErrorEmbed } from '$/utils/discord/embeds' +import { createErrorEmbed } from '../utils/discord/embeds' export default class CommandError extends Error { type: CommandErrorType - constructor(type: CommandErrorType, message?: string) { + constructor(type: CommandErrorType, message: string = ErrorMessageMap[type]) { super(message) this.name = 'CommandError' this.type = type @@ -15,19 +15,34 @@ export default class CommandError extends Error { } export enum CommandErrorType { - Generic, + Generic = 1, + InteractionNotInGuild, + InteractionDataMismatch, + FetchManagerNotFound, + FetchNotFound, + RequirementsNotMet = 100, MissingArgument, InvalidArgument, - InvalidUser, - InvalidChannel, - InvalidDuration, } const ErrorTitleMap: Record = { [CommandErrorType.Generic]: 'An exception was thrown', + [CommandErrorType.InteractionNotInGuild]: 'This command can only be used in servers', + [CommandErrorType.InteractionDataMismatch]: 'Command data mismatch', + [CommandErrorType.FetchManagerNotFound]: 'Cannot fetch data (manager not found)', + [CommandErrorType.FetchNotFound]: 'Cannot fetch data (source not found)', + [CommandErrorType.RequirementsNotMet]: 'Command requirements not met', [CommandErrorType.MissingArgument]: 'Missing argument', [CommandErrorType.InvalidArgument]: 'Invalid argument', - [CommandErrorType.InvalidUser]: 'Invalid user', - [CommandErrorType.InvalidChannel]: 'Invalid channel', - [CommandErrorType.InvalidDuration]: 'Invalid duration', +} + +const ErrorMessageMap: Record = { + [CommandErrorType.Generic]: 'An generic exception was thrown.', + [CommandErrorType.InteractionNotInGuild]: 'This command can only be used in servers.', + [CommandErrorType.InteractionDataMismatch]: 'Interaction command data does not match the expected command data.', + [CommandErrorType.FetchManagerNotFound]: 'Cannot fetch required data.', + [CommandErrorType.FetchNotFound]: 'Cannot fetch target.', + [CommandErrorType.RequirementsNotMet]: 'You do not meet the requirements to use this command.', + [CommandErrorType.MissingArgument]: 'You are missing a required argument.', + [CommandErrorType.InvalidArgument]: 'You provided an invalid argument.', } diff --git a/bots/discord/src/commands/admin/slash-commands.ts b/bots/discord/src/commands/admin/slash-commands.ts index 30d5d27..9970ac4 100644 --- a/bots/discord/src/commands/admin/slash-commands.ts +++ b/bots/discord/src/commands/admin/slash-commands.ts @@ -42,7 +42,7 @@ export default new AdminCommand({ const { global: globalCommands, guild: guildCommands } = Object.groupBy( Object.values(context.discord.commands), - cmd => (cmd.global ? 'global' : 'guild'), + cmd => (cmd.isGuildSpecific() ? 'guild' : 'global'), ) const { diff --git a/bots/discord/src/commands/fun/coinflip.ts b/bots/discord/src/commands/fun/coinflip.ts index df8ffcd..ec0b341 100644 --- a/bots/discord/src/commands/fun/coinflip.ts +++ b/bots/discord/src/commands/fun/coinflip.ts @@ -6,7 +6,7 @@ import { applyCommonEmbedStyles } from '$/utils/discord/embeds' export default new Command({ name: 'coinflip', description: 'Do a coinflip!', - global: true, + type: Command.Type.ChatGlobal, requirements: { defaultCondition: 'pass', }, diff --git a/bots/discord/src/commands/moderation/ban.ts b/bots/discord/src/commands/moderation/ban.ts index 2f18f9b..895ea9d 100644 --- a/bots/discord/src/commands/moderation/ban.ts +++ b/bots/discord/src/commands/moderation/ban.ts @@ -35,7 +35,7 @@ export default new ModerationCommand({ if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) throw new CommandError( - CommandErrorType.InvalidUser, + CommandErrorType.InvalidArgument, 'You cannot ban a user with a role equal to or higher than yours.', ) } diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index 2b4472f..c7549a6 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -37,14 +37,14 @@ export default new ModerationCommand({ if (Number.isInteger(duration) && duration! < 1) throw new CommandError( - CommandErrorType.InvalidDuration, + CommandErrorType.InvalidArgument, 'The duration must be at least 1 millisecond long.', ) const expires = Math.max(duration, Date.now() + duration) if (!member) throw new CommandError( - CommandErrorType.InvalidUser, + CommandErrorType.InvalidArgument, 'The provided member is not in the server or does not exist.', ) @@ -53,7 +53,7 @@ export default new ModerationCommand({ if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) throw new CommandError( - CommandErrorType.InvalidUser, + CommandErrorType.InvalidArgument, 'You cannot mute a user with a role equal to or higher than yours.', ) diff --git a/bots/discord/src/commands/moderation/purge.ts b/bots/discord/src/commands/moderation/purge.ts index a956b69..dc218d0 100644 --- a/bots/discord/src/commands/moderation/purge.ts +++ b/bots/discord/src/commands/moderation/purge.ts @@ -32,7 +32,7 @@ export default new ModerationCommand({ const channel = interaction.channel! if (!channel.isTextBased()) - throw new CommandError(CommandErrorType.InvalidChannel, 'The supplied channel is not a text channel.') + throw new CommandError(CommandErrorType.InvalidArgument, 'The supplied channel is not a text channel.') const embed = applyCommonEmbedStyles( new EmbedBuilder({ diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts index c65b1bb..9313fef 100644 --- a/bots/discord/src/commands/moderation/role-preset.ts +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -45,7 +45,7 @@ export default new ModerationCommand({ if (!member) throw new CommandError( - CommandErrorType.InvalidUser, + CommandErrorType.InvalidArgument, 'The provided member is not in the server or does not exist.', ) @@ -56,13 +56,13 @@ export default new ModerationCommand({ const duration = durationInput ? parseDuration(durationInput) : Infinity if (Number.isInteger(duration) && duration! < 1) throw new CommandError( - CommandErrorType.InvalidDuration, + CommandErrorType.InvalidArgument, 'The duration must be at least 1 millisecond long.', ) if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) throw new CommandError( - CommandErrorType.InvalidUser, + CommandErrorType.InvalidArgument, 'You cannot apply a role preset to a user with a role equal to or higher than yours.', ) diff --git a/bots/discord/src/commands/moderation/slowmode.ts b/bots/discord/src/commands/moderation/slowmode.ts index 5e55002..67facf7 100644 --- a/bots/discord/src/commands/moderation/slowmode.ts +++ b/bots/discord/src/commands/moderation/slowmode.ts @@ -27,14 +27,14 @@ export default new ModerationCommand({ if (!channel?.isTextBased() || channel.isDMBased()) throw new CommandError( - CommandErrorType.InvalidChannel, + CommandErrorType.InvalidArgument, 'The supplied channel is not a text channel.', ) - if (Number.isNaN(duration)) throw new CommandError(CommandErrorType.InvalidDuration, 'Invalid duration.') + if (Number.isNaN(duration)) throw new CommandError(CommandErrorType.InvalidArgument, 'Invalid duration.') if (duration < 0 || duration > 36e4) throw new CommandError( - CommandErrorType.InvalidDuration, + CommandErrorType.InvalidArgument, 'Duration out of range, must be between 0s and 6h.', ) diff --git a/bots/discord/src/commands/moderation/unmute.ts b/bots/discord/src/commands/moderation/unmute.ts index fe164b6..d45db30 100644 --- a/bots/discord/src/commands/moderation/unmute.ts +++ b/bots/discord/src/commands/moderation/unmute.ts @@ -20,7 +20,7 @@ export default new ModerationCommand({ const member = await interaction.guild!.members.fetch(user.id) if (!member) throw new CommandError( - CommandErrorType.InvalidUser, + CommandErrorType.InvalidArgument, 'The provided member is not in the server or does not exist.', ) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 6623773..97229ba 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -17,7 +17,7 @@ export const logger = createLogger({ import * as commands from './commands' import * as schemas from './database/schemas' -import type { default as Command, CommandOptionsOptions } from './classes/Command' +import type { default as Command, CommandOptionsOptions, CommandType } from './classes/Command' export const api = { client: new APIClient({ @@ -83,7 +83,7 @@ export const discord = { }), commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.name, cmd])) as Record< string, - Command + Command >, stickyMessages: {} as Record< string, diff --git a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts index b3d1c95..8807d72 100644 --- a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts @@ -8,19 +8,24 @@ withContext(on, 'interactionCreate', async (context, interaction) => { const { logger, discord } = context const command = discord.commands[interaction.commandName] - logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag}`) + logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag} via chat`) if (!command) - return void logger.error(`Interaction command ${interaction.commandName} not implemented but registered!!!`) + return void logger.error(`Chat command ${interaction.commandName} not implemented but registered!!!`) try { - logger.debug(`Command ${interaction.commandName} being executed`) + logger.debug(`Command ${interaction.commandName} being executed via chat`) await command.onInteraction(context, interaction) } catch (err) { if (!(err instanceof CommandError)) logger.error(`Error while executing command ${interaction.commandName}:`, err) + await interaction[interaction.replied ? 'followUp' : 'reply']({ embeds: [err instanceof CommandError ? err.toEmbed() : createStackTraceEmbed(err)], ephemeral: true, }) + + // 100 and up are user errors + if (err instanceof CommandError && err.type < 100) + logger.error(`Command ${interaction.commandName} internally failed with error:`, err) } }) diff --git a/bots/discord/src/events/discord/interactionCreate/contextMenuCommand.ts b/bots/discord/src/events/discord/interactionCreate/contextMenuCommand.ts new file mode 100644 index 0000000..e98aa56 --- /dev/null +++ b/bots/discord/src/events/discord/interactionCreate/contextMenuCommand.ts @@ -0,0 +1,26 @@ +import CommandError from '$/classes/CommandError' +import { createStackTraceEmbed } from '$utils/discord/embeds' +import { on, withContext } from '$utils/discord/events' + +withContext(on, 'interactionCreate', async (context, interaction) => { + if (!interaction.isContextMenuCommand()) return + + const { logger, discord } = context + const command = discord.commands[interaction.commandName] + + logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag} via context menu`) + if (!command) + return void logger.error(`Context menu command ${interaction.commandName} not implemented but registered!!!`) + + try { + logger.debug(`Command ${interaction.commandName} being executed via context menu`) + await command.onContextMenuInteraction(context, interaction) + } catch (err) { + if (!(err instanceof CommandError)) + logger.error(`Error while executing command ${interaction.commandName}:`, err) + await interaction[interaction.replied ? 'followUp' : 'reply']({ + embeds: [err instanceof CommandError ? err.toEmbed() : createStackTraceEmbed(err)], + ephemeral: true, + }) + } +}) diff --git a/bots/discord/src/events/discord/messageCreate/messageCommand.ts b/bots/discord/src/events/discord/messageCreate/messageCommand.ts index 3b9440b..328522e 100644 --- a/bots/discord/src/events/discord/messageCreate/messageCommand.ts +++ b/bots/discord/src/events/discord/messageCreate/messageCommand.ts @@ -18,7 +18,7 @@ withContext(on, 'messageCreate', async (context, msg) => { if (!commandName) return const command = discord.commands[commandName] - logger.debug(`Command ${commandName} being invoked by ${msg.author.id}`) + logger.debug(`Command ${commandName} being invoked by ${msg.author.id} via message`) if (!command) return void logger.debug(`Message command ${commandName} not implemented`) const argsRegex: RegExp = /[^\s"]+|"([^"]*)"/g @@ -46,7 +46,7 @@ withContext(on, 'messageCreate', async (context, msg) => { } try { - logger.debug(`Command ${commandName} being executed`) + logger.debug(`Command ${commandName} being executed via message`) await command.onMessage(context, msg, args) } catch (err) { if (!(err instanceof CommandError)) logger.error(`Error while executing command ${commandName}:`, err) From ee90ef247b4bf2b3c0698606b947116f2dc1b868 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 13 Aug 2024 21:16:05 +0700 Subject: [PATCH 255/312] feat(bots/discord): add `train` commands --- .../src/commands/support/train/chat.ts | 76 +++++++++++++++++++ .../commands/support/train/context-menu.ts | 50 ++++++++++++ .../interactionCreate/correctResponse.ts | 18 ++--- .../discord/interactionCreate/trainMessage.ts | 52 +++++++++++++ 4 files changed, 187 insertions(+), 9 deletions(-) create mode 100644 bots/discord/src/commands/support/train/chat.ts create mode 100644 bots/discord/src/commands/support/train/context-menu.ts create mode 100644 bots/discord/src/events/discord/interactionCreate/trainMessage.ts diff --git a/bots/discord/src/commands/support/train/chat.ts b/bots/discord/src/commands/support/train/chat.ts new file mode 100644 index 0000000..572d8c5 --- /dev/null +++ b/bots/discord/src/commands/support/train/chat.ts @@ -0,0 +1,76 @@ +import Command from '$/classes/Command' +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { config } from '../../../context' +import type { FetchMessageOptions, MessageResolvable } from 'discord.js' +import type { ConfigMessageScanResponseLabelConfig } from 'config.schema' +import { createSuccessEmbed } from '$/utils/discord/embeds' + +const msRcConfig = config.messageScan?.humanCorrections?.allow + +export default new Command({ + name: 'train', + description: 'Train a specific message or text to a specific label', + type: Command.Type.ChatGuild, + requirements: { + users: msRcConfig?.users, + roles: msRcConfig?.members?.roles, + permissions: msRcConfig?.members?.permissions, + mode: 'any', + memberRequirementsForUsers: 'fail', + defaultCondition: 'fail', + }, + options: { + message: { + description: 'The message to train (use `latest` to train the latest message)', + type: Command.OptionType.String, + required: true, + }, + label: { + description: 'The label to train the message as', + type: Command.OptionType.String, + required: true, + }, + }, + allowMessageCommand: true, + async execute(context, trigger, { label, message: ref }) { + const { logger, config } = context + const { messageScan: msConfig } = config + + // If there's no config, we can't do anything + if (!msConfig?.humanCorrections) throw new CommandError(CommandErrorType.Generic, 'Response correction is off.') + const labels = msConfig.responses?.flatMap(r => + r.triggers.text!.filter((t): t is ConfigMessageScanResponseLabelConfig => 'label' in t).map(t => t.label), + ) + + const channel = await trigger.guild!.channels.fetch(trigger.channelId) + if (!channel?.isTextBased()) + throw new CommandError( + CommandErrorType.InvalidArgument, + 'This command can only be used in or on text channels', + ) + + if (!labels.includes(label)) + throw new CommandError( + CommandErrorType.InvalidArgument, + `The provided label is invalid.\nValid labels are:${labels.map(l => `\n- \`${l}\``).join('')}`, + ) + + const refMsg = await channel.messages.fetch( + (ref.startsWith('latest') ? { limit: 1 } : ref) as MessageResolvable | FetchMessageOptions, + ) + if (!refMsg) throw new CommandError(CommandErrorType.InvalidArgument, 'The provided message does not exist.') + + logger.debug(`User ${context.executor.id} is training message ${refMsg?.id} as ${label}`) + + await context.api.client.trainMessage(refMsg.content, label) + await trigger.reply({ + embeds: [ + createSuccessEmbed( + 'Message trained', + `The provided message has been trained as \`${label}\`. Thank you for your contribution!`, + ), + ], + ephemeral: true, + }) + }, +}) diff --git a/bots/discord/src/commands/support/train/context-menu.ts b/bots/discord/src/commands/support/train/context-menu.ts new file mode 100644 index 0000000..50dab3f --- /dev/null +++ b/bots/discord/src/commands/support/train/context-menu.ts @@ -0,0 +1,50 @@ +import Command from '$/classes/Command' +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { config } from '../../../context' +import { type APIStringSelectComponent, ComponentType } from 'discord.js' +import type { ConfigMessageScanResponseLabelConfig } from 'config.schema' + +const msRcConfig = config.messageScan?.humanCorrections?.allow + +export default new Command({ + name: 'Train Message', + type: Command.Type.ContextMenuGuildMessage, + requirements: { + users: msRcConfig?.users, + roles: msRcConfig?.members?.roles, + permissions: msRcConfig?.members?.permissions, + mode: 'any', + memberRequirementsForUsers: 'fail', + defaultCondition: 'fail', + }, + async execute(context, trigger) { + const { logger, config } = context + const { messageScan: msConfig } = config + + // If there's no config, we can't do anything + if (!msConfig?.humanCorrections) throw new CommandError(CommandErrorType.Generic, 'Response correction is off.') + + logger.debug(`User ${context.executor.id} is training message ${trigger.targetId}`) + + const labels = msConfig.responses.flatMap(r => + r.triggers.text!.filter((t): t is ConfigMessageScanResponseLabelConfig => 'label' in t).map(t => t.label), + ) + + await trigger.reply({ + content: 'Select a label to train this message as:', + components: [ + { + components: [ + { + custom_id: `tr_${trigger.targetMessage.channelId}_${trigger.targetId}`, + options: labels.map(label => ({ label, value: label })), + type: ComponentType.StringSelect, + } satisfies APIStringSelectComponent, + ], + type: ComponentType.ActionRow, + }, + ], + ephemeral: true, + }) + }, +}) diff --git a/bots/discord/src/events/discord/interactionCreate/correctResponse.ts b/bots/discord/src/events/discord/interactionCreate/correctResponse.ts index 81b67a8..5792a23 100644 --- a/bots/discord/src/events/discord/interactionCreate/correctResponse.ts +++ b/bots/discord/src/events/discord/interactionCreate/correctResponse.ts @@ -18,10 +18,10 @@ withContext(on, 'interactionCreate', async (context, interaction) => { if (!interaction.isStringSelectMenu() && !interaction.isButton()) return if (!interaction.customId.startsWith('cr_')) return - const [, key, action] = interaction.customId.split('_') as ['cr', string, 'select' | 'cancel' | 'delete'] - if (!key || !action) return + const [, msgId, action] = interaction.customId.split('_') as ['cr', string, 'select' | 'cancel' | 'delete'] + if (!msgId || !action) return - const response = await db.query.responses.findFirst({ where: eq(responses.replyId, key) }) + const response = await db.query.responses.findFirst({ where: eq(responses.replyId, msgId) }) // If the message isn't saved in my DB (unrelated message) if (!response) return void (await interaction.reply({ @@ -32,11 +32,11 @@ withContext(on, 'interactionCreate', async (context, interaction) => { try { // We're gonna pretend reactionChannel is a text-based channel, but it can be many more // But `messages` should always exist as a property - const reactionGuild = await interaction.client.guilds.fetch(response.guildId) - const reactionChannel = (await reactionGuild.channels.fetch(response.channelId)) as TextBasedChannel | null - const reactionMessage = await reactionChannel?.messages.fetch(key) + const guild = await interaction.client.guilds.fetch(response.guildId) + const channel = (await guild.channels.fetch(response.channelId)) as TextBasedChannel | null + const msg = await channel?.messages.fetch(msgId) - if (!reactionMessage) { + if (!msg) { await interaction.deferUpdate() await interaction.message.edit({ content: null, @@ -53,9 +53,9 @@ withContext(on, 'interactionCreate', async (context, interaction) => { } const editMessage = (content: string, description?: string) => - editInteractionMessage(interaction, reactionMessage.url, content, description) + editInteractionMessage(interaction, msg.url, content, description) const handleCorrection = (label: string) => - handleUserResponseCorrection(context, response, reactionMessage, label, interaction.user) + handleUserResponseCorrection(context, response, msg, label, interaction.user) if (response.correctedById) return await editMessage( diff --git a/bots/discord/src/events/discord/interactionCreate/trainMessage.ts b/bots/discord/src/events/discord/interactionCreate/trainMessage.ts new file mode 100644 index 0000000..917ba10 --- /dev/null +++ b/bots/discord/src/events/discord/interactionCreate/trainMessage.ts @@ -0,0 +1,52 @@ +import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds' +import { on, withContext } from '$utils/discord/events' + +import type { TextBasedChannel } from 'discord.js' + +withContext(on, 'interactionCreate', async (context, interaction) => { + const { + logger, + config: { messageScan: msConfig }, + } = context + + if (!msConfig?.humanCorrections) return + if (!interaction.isStringSelectMenu()) return + if (!interaction.customId.startsWith('tr_')) return + + const [, channelId, msgId] = interaction.customId.split('_') as ['tr', string, string] + if (!channelId || !msgId) return + + try { + const channel = (await interaction.client.channels.fetch(channelId)) as TextBasedChannel | null + const msg = await channel?.messages.fetch(msgId) + + if (!msg) + return void (await interaction.reply({ + embeds: [ + createErrorEmbed( + 'Message not found', + 'Thank you for your contribution! Unfortunately, the message could not be found.', + ), + ], + ephemeral: true, + })) + + const selectedLabel = interaction.values[0]! + await context.api.client.trainMessage(msg.content, selectedLabel) + await interaction.reply({ + embeds: [ + createSuccessEmbed( + 'Message being trained', + `Thank you for your contribution! The selected message is being trained as \`${selectedLabel}\`. 🎉`, + ), + ], + ephemeral: true, + }) + } catch (e) { + logger.error('Failed to handle train message interaction:', e) + await interaction.reply({ + embeds: [createStackTraceEmbed(e)], + ephemeral: true, + }) + } +}) From 6dc7f0211e08d4b96d2a997dfc545f602052e378 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 13 Aug 2024 21:19:18 +0700 Subject: [PATCH 256/312] chore: update lockfile --- bun.lockb | Bin 285584 -> 285592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index d84d3156234124d78cae17263125752a889c3248..991adf7bad9ad683c08087bed31fbe94183c3ebd 100755 GIT binary patch delta 4972 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 d>6zl{cNiEHki@4P Date: Tue, 13 Aug 2024 14:20:22 +0000 Subject: [PATCH 257/312] chore(release): 1.0.0-dev.23 [skip ci] # @revanced/discord-bot [1.0.0-dev.23](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.22...@revanced/discord-bot@1.0.0-dev.23) (2024-08-13) ### Features * **bots/discord:** add `train` commands ([ee90ef2](https://github.com/revanced/revanced-helper/commit/ee90ef247b4bf2b3c0698606b947116f2dc1b868)) * **bots/discord:** update to newer command definition framework ([97f2795](https://github.com/revanced/revanced-helper/commit/97f2795df4ede4d12a08193dba453c1bc765a4c2)) --- bots/discord/CHANGELOG.md | 8 ++++++++ bots/discord/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 2dd09d4..c46fbc2 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,11 @@ +# @revanced/discord-bot [1.0.0-dev.23](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.22...@revanced/discord-bot@1.0.0-dev.23) (2024-08-13) + + +### Features + +* **bots/discord:** add `train` commands ([ee90ef2](https://github.com/revanced/revanced-helper/commit/ee90ef247b4bf2b3c0698606b947116f2dc1b868)) +* **bots/discord:** update to newer command definition framework ([97f2795](https://github.com/revanced/revanced-helper/commit/97f2795df4ede4d12a08193dba453c1bc765a4c2)) + # @revanced/discord-bot [1.0.0-dev.22](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.21...@revanced/discord-bot@1.0.0-dev.22) (2024-08-10) diff --git a/bots/discord/package.json b/bots/discord/package.json index 0378f09..bf2e856 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.22", + "version": "1.0.0-dev.23", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 031fd26b2619ecafeff3964e50accacb87de6108 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 14 Aug 2024 11:53:03 +0700 Subject: [PATCH 258/312] fix(bots/discord): do not remove unrelated reactions --- .../src/events/discord/messageReactionAdd/correctResponse.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index 75d8ea5..04b765c 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -20,11 +20,10 @@ const PossibleReactions = Object.values(Reactions) as string[] withContext(on, 'messageReactionAdd', async (context, rct, user) => { if (user.bot) return - await rct.users.remove(user.id) const { database: db, logger, config } = context const { messageScan: msConfig } = config - + // If there's no config, we can't do anything if (!msConfig?.humanCorrections) return @@ -33,6 +32,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { if (reactionMessage.author.id !== reaction.client.user!.id) return if (!PossibleReactions.includes(reaction.emoji.name!)) return + await rct.users.remove(user.id) if (!isAdmin(reactionMessage.member || reactionMessage.author)) { // User is in guild, and config has member requirements From 51f877f32157c45891bbf21f07c02719dc87ad56 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 14 Aug 2024 04:54:54 +0000 Subject: [PATCH 259/312] chore(release): 1.0.0-dev.24 [skip ci] # @revanced/discord-bot [1.0.0-dev.24](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.23...@revanced/discord-bot@1.0.0-dev.24) (2024-08-14) ### Bug Fixes * **bots/discord:** do not remove unrelated reactions ([031fd26](https://github.com/revanced/revanced-helper/commit/031fd26b2619ecafeff3964e50accacb87de6108)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index c46fbc2..4f59fa0 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.24](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.23...@revanced/discord-bot@1.0.0-dev.24) (2024-08-14) + + +### Bug Fixes + +* **bots/discord:** do not remove unrelated reactions ([031fd26](https://github.com/revanced/revanced-helper/commit/031fd26b2619ecafeff3964e50accacb87de6108)) + # @revanced/discord-bot [1.0.0-dev.23](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.22...@revanced/discord-bot@1.0.0-dev.23) (2024-08-13) diff --git a/bots/discord/package.json b/bots/discord/package.json index bf2e856..2e7fb45 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.23", + "version": "1.0.0-dev.24", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 99f65f07f5f8830c6e8ea4ae171e986af4d3f1f6 Mon Sep 17 00:00:00 2001 From: Palm Date: Thu, 15 Aug 2024 12:10:04 +0700 Subject: [PATCH 260/312] fix(bots/discord): allow access to `context` in `/eval` and await result --- bots/discord/src/commands/admin/eval.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/commands/admin/eval.ts b/bots/discord/src/commands/admin/eval.ts index 2e716c8..fc67146 100644 --- a/bots/discord/src/commands/admin/eval.ts +++ b/bots/discord/src/commands/admin/eval.ts @@ -19,14 +19,17 @@ export default new AdminCommand({ required: false, }, }, - async execute(_, trigger, { code, 'show-hidden': showHidden }) { + async execute(context, trigger, { code, 'show-hidden': showHidden }) { + // So it doesn't show up as unused, and we can use it in `code` + context + await trigger.reply({ ephemeral: true, embeds: [ createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``).addFields({ name: 'Result', // biome-ignore lint/security/noGlobalEval: This is fine as it's an admin command - value: `\`\`\`js\n${inspect(eval(code), { + value: `\`\`\`js\n${inspect(await eval(code), { depth: 1, showHidden, getters: true, From 845dd5d914354c187f44c2b56c6e5111afd97d05 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 15 Aug 2024 05:11:31 +0000 Subject: [PATCH 261/312] chore(release): 1.0.0-dev.25 [skip ci] # @revanced/discord-bot [1.0.0-dev.25](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.24...@revanced/discord-bot@1.0.0-dev.25) (2024-08-15) ### Bug Fixes * **bots/discord:** allow access to `context` in `/eval` and await result ([99f65f0](https://github.com/revanced/revanced-helper/commit/99f65f07f5f8830c6e8ea4ae171e986af4d3f1f6)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 4f59fa0..3402bdf 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.25](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.24...@revanced/discord-bot@1.0.0-dev.25) (2024-08-15) + + +### Bug Fixes + +* **bots/discord:** allow access to `context` in `/eval` and await result ([99f65f0](https://github.com/revanced/revanced-helper/commit/99f65f07f5f8830c6e8ea4ae171e986af4d3f1f6)) + # @revanced/discord-bot [1.0.0-dev.24](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.23...@revanced/discord-bot@1.0.0-dev.24) (2024-08-14) diff --git a/bots/discord/package.json b/bots/discord/package.json index 2e7fb45..701b2dc 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.24", + "version": "1.0.0-dev.25", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 96065ff17584ff99a56ca5008327863ca5a7852b Mon Sep 17 00:00:00 2001 From: Palm Date: Thu, 15 Aug 2024 22:33:12 +0700 Subject: [PATCH 262/312] fix(bots/discord): correct timer active condition for sticky messages --- .../src/events/discord/messageCreate/stickyMessageReset.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts index e0104ac..2b18fd9 100644 --- a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts +++ b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts @@ -7,12 +7,13 @@ withContext(on, 'messageCreate', async ({ discord, logger }, msg) => { const store = discord.stickyMessages[msg.guildId]?.[msg.channelId] if (!store) return + const timerPreviouslyActive = store.timerActive // If there isn't a timer, start it up store.timerActive = true if (!store.timer) store.timer = setTimeout(store.send, store.timerMs) as NodeJS.Timeout else { // If there is a timer, but it isn't active, restart it - if (!store.timerActive) store.timer.refresh() + if (!timerPreviouslyActive) store.timer.refresh() // If there is a timer and it is active, but the force timer isn't active... else if (!store.forceTimerActive && store.forceTimerMs) { logger.debug(`Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer and clearing existing timer`) From e3dcbab5087c9c7100c00415b51e6261f6c82afc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 15 Aug 2024 15:34:29 +0000 Subject: [PATCH 263/312] chore(release): 1.0.0-dev.26 [skip ci] # @revanced/discord-bot [1.0.0-dev.26](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.25...@revanced/discord-bot@1.0.0-dev.26) (2024-08-15) ### Bug Fixes * **bots/discord:** correct timer active condition for sticky messages ([96065ff](https://github.com/revanced/revanced-helper/commit/96065ff17584ff99a56ca5008327863ca5a7852b)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 3402bdf..42393f8 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.26](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.25...@revanced/discord-bot@1.0.0-dev.26) (2024-08-15) + + +### Bug Fixes + +* **bots/discord:** correct timer active condition for sticky messages ([96065ff](https://github.com/revanced/revanced-helper/commit/96065ff17584ff99a56ca5008327863ca5a7852b)) + # @revanced/discord-bot [1.0.0-dev.25](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.24...@revanced/discord-bot@1.0.0-dev.25) (2024-08-15) diff --git a/bots/discord/package.json b/bots/discord/package.json index 701b2dc..e3e75dd 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.25", + "version": "1.0.0-dev.26", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 94c4fedc06e20051e4123508e3134b97eb84782a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 23 Aug 2024 17:36:32 +0700 Subject: [PATCH 264/312] fix(bots/discord): replace duration parser with a library --- bots/discord/package.json | 3 ++- bots/discord/src/utils/duration.ts | 16 ++-------------- bun.lockb | Bin 285592 -> 285960 bytes 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/bots/discord/package.json b/bots/discord/package.json index e3e75dd..a6ccb27 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -35,7 +35,8 @@ "chalk": "^5.3.0", "decancer": "^3.2.3", "discord.js": "^14.15.3", - "drizzle-orm": "^0.31.4" + "drizzle-orm": "^0.31.4", + "parse-duration": "^1.1.0" }, "devDependencies": { "@libsql/client": "^0.7.0", diff --git a/bots/discord/src/utils/duration.ts b/bots/discord/src/utils/duration.ts index 64e672c..4f4e129 100644 --- a/bots/discord/src/utils/duration.ts +++ b/bots/discord/src/utils/duration.ts @@ -1,18 +1,6 @@ -export const parseDuration = (duration: string) => { - if (!duration.length) return Number.NaN - const matches = duration.match(/(?:(\d+y)?(\d+M)?(\d+w)?(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)/)! +import parse from 'parse-duration' - const [, years, months, weeks, days, hours, minutes, seconds] = matches.map(Number) - return ( - (years || 0) * 290304e5 + - (months || 0) * 24192e5 + - (weeks || 0) * 6048e5 + - (days || 0) * 864e5 + - (hours || 0) * 36e5 + - (minutes || 0) * 6e4 + - (seconds || 0) * 1e3 - ) -} +export const parseDuration = (duration: string) => parse(duration, 'ms') ?? Number.NaN export const durationToString = (duration: number) => { if (duration === 0) return '0s' diff --git a/bun.lockb b/bun.lockb index 991adf7bad9ad683c08087bed31fbe94183c3ebd..c8ffb651f93a0c3471f63571186eb5f34aa6c0db 100755 GIT binary patch delta 11591 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 N6djpA{bLl%asah`^HKl+ delta 11387 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 From 522ad28fd83565e9ca411dbce86c8447574288fd Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 23 Aug 2024 18:05:48 +0700 Subject: [PATCH 265/312] fix(bots/discord): give only removed roles for role presets --- bots/discord/package.json | 4 +- bots/discord/src/database/schemas.ts | 1 + .../guildMemberAdd/applyRolePresets.ts | 2 +- bots/discord/src/utils/discord/rolePresets.ts | 57 ++++++++++++------ bun.lockb | Bin 285960 -> 287544 bytes package.json | 8 +-- 6 files changed, 47 insertions(+), 25 deletions(-) diff --git a/bots/discord/package.json b/bots/discord/package.json index a6ccb27..d804352 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -33,14 +33,14 @@ "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", - "decancer": "^3.2.3", + "decancer": "^3.2.4", "discord.js": "^14.15.3", "drizzle-orm": "^0.31.4", "parse-duration": "^1.1.0" }, "devDependencies": { "@libsql/client": "^0.7.0", - "discord-api-types": "^0.37.92", + "discord-api-types": "^0.37.97", "drizzle-kit": "^0.22.8" } } \ No newline at end of file diff --git a/bots/discord/src/database/schemas.ts b/bots/discord/src/database/schemas.ts index e889383..0acee06 100644 --- a/bots/discord/src/database/schemas.ts +++ b/bots/discord/src/database/schemas.ts @@ -16,6 +16,7 @@ export const appliedPresets = sqliteTable( { memberId: text('member').notNull(), guildId: text('guild').notNull(), + removedRoles: text('roles', { mode: 'json' }).notNull().$type().default([]), preset: text('preset').notNull(), until: integer('until'), }, diff --git a/bots/discord/src/events/discord/guildMemberAdd/applyRolePresets.ts b/bots/discord/src/events/discord/guildMemberAdd/applyRolePresets.ts index 90f176f..686350d 100644 --- a/bots/discord/src/events/discord/guildMemberAdd/applyRolePresets.ts +++ b/bots/discord/src/events/discord/guildMemberAdd/applyRolePresets.ts @@ -12,5 +12,5 @@ withContext(on, 'guildMemberAdd', async ({ database }, member) => { ), }) - for (const { preset } of applieds) await applyRolesUsingPreset(preset, member, true) + for (const { preset } of applieds) await applyRolesUsingPreset(preset, member) }) diff --git a/bots/discord/src/utils/discord/rolePresets.ts b/bots/discord/src/utils/discord/rolePresets.ts index 42f1704..4b1ab79 100644 --- a/bots/discord/src/utils/discord/rolePresets.ts +++ b/bots/discord/src/utils/discord/rolePresets.ts @@ -7,7 +7,7 @@ import { and, eq } from 'drizzle-orm' type PresetKey = string export const applyRolePreset = async (member: GuildMember, presetName: PresetKey, expires: number) => { - const afterInsert = await applyRolesUsingPreset(presetName, member, true) + const { removed, callback } = await applyRolesUsingPreset(presetName, member) const until = expires === Infinity ? null : Math.ceil(expires / 1000) await database @@ -16,39 +16,60 @@ export const applyRolePreset = async (member: GuildMember, presetName: PresetKey memberId: member.id, guildId: member.guild.id, preset: presetName, + removedRoles: removed, until, }) .onConflictDoUpdate({ target: [appliedPresets.memberId, appliedPresets.preset, appliedPresets.guildId], set: { until }, }) - .then(afterInsert) + .then(callback) } export const removeRolePreset = async (member: GuildMember, presetName: PresetKey) => { - const afterDelete = await applyRolesUsingPreset(presetName, member, false) + const where = and( + eq(appliedPresets.memberId, member.id), + eq(appliedPresets.preset, presetName), + eq(appliedPresets.guildId, member.guild.id), + ) - await database - .delete(appliedPresets) - .where( - and( - eq(appliedPresets.memberId, member.id), - eq(appliedPresets.preset, presetName), - eq(appliedPresets.guildId, member.guild.id), - ), - ) - .execute() - .then(afterDelete) + const data = await database.query.appliedPresets.findFirst({ where }) + if (!data) return false + + const { callback } = await applyRolesUsingPreset(presetName, member, data.removedRoles) + await database.delete(appliedPresets).where(where).execute().then(callback) + + return true } -export const applyRolesUsingPreset = async (presetName: string, member: GuildMember, applying: boolean) => { +export const applyRolesUsingPreset = async ( + presetName: string, + member: GuildMember, + removePresetGiveRoles?: string[], +) => { const preset = config.rolePresets?.guilds[member.guild.id]?.[presetName] if (!preset) throw new Error(`The preset "${presetName}" does not exist for this server`) const roles = new Set(member.roles.cache.keys()) + const removed: string[] = [] - for (const role of preset.give) roles[applying ? 'add' : 'delete'](role) - for (const role of preset.take) roles[applying ? 'delete' : 'add'](role) + // If removePresetGiveRoles is not provided, we're applying a preset + if (!removePresetGiveRoles) { + for (const role of preset.give) roles.add(role) + for (const role of preset.take) { + if (roles.has(role)) { + roles.delete(role) + removed.push(role) + } + } + } else { + const guildRoles = await member.guild.roles.fetch() + for (const role of preset.give) roles.delete(role) + for (const role of removePresetGiveRoles) if (guildRoles.has(role)) roles.add(role) + } - return () => member.roles.set(Array.from(roles)) + return { + removed, + callback: () => member.roles.set(Array.from(roles)), + } } diff --git a/bun.lockb b/bun.lockb index c8ffb651f93a0c3471f63571186eb5f34aa6c0db..d0db354f717bad6e44068535a78f3ff522b250f9 100755 GIT binary patch delta 54329 zcmeC!CAj05-~>HQt-yU|!K$}(FO@AST6$3W=CPxCp1f7F4W2aCET2B%Li~y(76uTw zG%;MBiR;wF3I$DGW(Edf28M>;ObiS{3=9pgm>3uY85kOFF)=U*FfcTnW@2FAXJBa9 zJMo~n+Xbz`)JG&>+IWz`(`8&>%2bkWpOn z04D0e%0+GnotBxGqFY+P@Ea=bGI=ATdVMlK1A_#Voo*# z1H(^#h%Fh3#ky%ZiRl<_;ey!K%LXyEI5oE{wTOYC5$eMtsLz9iAg(M*O-#`($WCVx zhWG{M+Ihm@kg9L!g)kc2MIhn1NQ8kwiGiWPRs>?vOc97dZ-f{a#26SFzKAj~$S^Q8 z)QB-Kh%zuVd=i7`dn^VC@&YkPkgGx+A}#^Ze_n`zL6(7`!Bq(2!2;c))RfZtO`2Qm-^7i1tlNrY;cAP2E9L>l72%EY4dVg?2VSfWbK z$t=xeU|_JHJd;^IQcMvNAtf29d7zZ>PXWT`RD|d@mWPB3ETUnwhcrZ-O&OxznGNDh z6R7ttP8MX5XB3}o$Re+}UKJ9?`DzfCB|_;iHHcoP$%QQ9^=4`i6ZKUg{^`_!m{?qp zlUZWKz#ydxi8&rsh$+0#fMGBiM4%m8yJ%)dB#zhHKpdEv2TIut41a7Od1ICh!~$4h)?@>TilSst5y-&MVh1rNH#4t3GdHui zq|y%J;=F=funJg+e0PL|%sod221y2nh7*pEVx`9!Vqlpwgzw=3kza2Q36ZZZkPz{5 zg*Y@lCqD^fFrynp{YO_w;(X>>53x8mGcU6svAFoIE5v|qcZh;IcSy*<3VV1JzzOl` zX=p)O;t8<`R#m_XVpzGi*%1;V=!tNCy(1*Z3ySiSb#wC5r+GshB<};UxG1%x2oy!B zMMb*F`6;RVJ`jifa{!mf4ZRK!pE^52Dm++J^f*F-xY-XPU*!k!SS3_E9jd-Q6v_aF zKvhm=Qe|RJPG)fl1Bed_5#|7h&r8C! zw1-&v%O7H8y#vH=hR`tF6g|0$ORnB41`_rPju5Z%L+R7dASo!y&#g)1DE%NF5;8lWsrpDx1H;J_2wyN2 zl8UXN`j*5)JUSsB66fvlpm3>YXlPD@Xh_aY(Jju%%&m%t@Lkd&E>A8lPAyJmV2F>0 z6t#1psTfup7eWIzIa4z%hR;y`ODKIGN?(Q2r=fI8W?C93YPRM;qHq~B3NnjzlQUC_7&hcW%+CZhOwv;; z5{r^EK-tvHih)5F)C$zK0tE#_Lt3$JYDGy=qG&$E#aYGqd1;wBsoAWMkZ78GkVn0~ zwg4jgrVtWNcMBnD_+%j@V7C=Q0(xm7#5thyEUA)#p*Sb87*wY%FNWBbnVy$ll*+&` z56aI?ElN){VqjQR0!aXw#kwG^46&sU=g%quCxrTjw8Y|)LIwtgqSVr2PPhD&b17OBy7rAqxLgK=>~!AP!kp0dcWk0VD|;7C>qSFIENyMFxh3T1!Yu%1zBL z2Sr>Gl<#T@Q8%p)qW^ClB&5poi&Dyq5(_?q#6c z$uH3@D9SI$EY{6W%3@%6)&MbRh9x9uFE>Ker4{Ap8Zj_1z*+-rOpvPSU=zgL($dTn zkOMiIAt6(mmy*c}YGz!W9LOi`>DCGnHfRL}3`2tql+Om`e`aK6 zqkwq*y}1xx9aQ(7c@X}Ed61CZJP(rOvocG-&9WKuAXy`A0mQa2C=F{Wq|Af#(Gs)D zKnauK_CiRE9bX7ZYM`<~x3nm8$3n0x>l+&8L82rN>VVygA>K1s0?{D51Y!X95=h$g zT?&!6T?%o4E|ivq(!5Z571W$5iy>({W;R6saRvqkHBcW6+-0a|V1Tu}VR@{eG`9fc z^lK|1d2A;Gq?8d@1qq3-Dt0Vw_Z5I9uo8=fD6gy&@_y&p=ihSJlabTgFBhtknd z+T#!;UQD615|rkL(ti&^9Qqnc--Ob~!E`-CL&koH&&c!vWgY;fT*`dF(BO9p;zL-< z^@gVKsTUz3D|ZQ!!W%9@Qg#`XuX&k)p&m4JC2<)dk#Y%QQ1L}b6~%c897hcwFGBKW z$3;k5sksPA6FHel#f3Qx3^vyw@~YP$anFAZ68Ar^Lg*(~A?94T3UScBs}OrOUS+5U zkIyZ;3Q0t<43I+M-VKNapw15{S=Qc!#Bt6|h(jZxeEXY_rVY%8F1H~*?Ya%Iprpve z3{(hZ7VDN4C$nvC6QsFfe#x%G}}S0Cj)~O149ElSPS1|PGft{NG=8jCy*N_2ZmTo?liV% zd^q`}u|1>UWKI)%M)%2{Cia}Y+zbpZ3=9n{Abp(gxgn-7gL#@f3=9TvM@8~L8~{}h7tdX$%e!ECaxi8TisAH*}$9j`GO zfFh66A1VU2g`<~`fx&`-p#dR(l#hYI3~aWbu{p_&!Ni?kw@q#=VxFD z0PC1&V9t1aa;JqoBm3l&7WSOp0ua-|c5yZfK-|JSSiC&<9y01k6dto##%BnEC!;4sEa=CrowoGt`0o^$d=Lvs#(VMu^a z7o5aoF!{h?P7Yy6m~nv80_S>R1_mz%h6aYofkx(xKPP+I*t3RQS{GcdS;Bmbg>Ipf61C++MxFG9uG!I~8&d)nJGMo;dvx98X<0Z9h%K<4}< z0r43m;EW_8K4YGIG2CKur-MD?&dDbo>={2#=5(~@G?aokm>ra&Ijf~0X0d{ue?ki4 zd~m|#WRixsiX9vvQ>7UgOc@v&ctGyqJSEM*;0N)BnK`F~48#=n$rtU+Ib&oXu7L!? z6d8zpOp`BKnsa=Xf!F{~4jdk`3=DCgBn3+{oXcb(`k5vRnwYcRlVxDAncQn?%_udw z)5V@KWb#QDd(L@s5K|c^gF@`XWKUOn&Nz7n22cqk0CEK=6kY8(FUdm^B>Uuxw&tuX z3ZQ5^YiP}xrNF>o0*;zMV+#%_hZPhgjF%>Jy4!OqDngPNU3%14@Y>CUg4Ov(~CHFt~sMmh&Kp1JS&P zg5B<{4#}-dpajA(SsfA%h@^K?9pVZmPyll>YC!zPI9brqoYPT*fx!cA?j#LJ)H6(; zXl%}LMgtN7aFaMdqrsq@0TnsP$*u|U5>!B!gK3)P5qAkAzQ2D{0=2G*Qvy0Cb<7;eF#3vmiK(l|HiLY%<~GLrL+E+n8Jxk5z` zVmTy9r05~b&C!GCV*v#@=QXHGmdS#PEI9NbK8K{82z^K<02gSCYbJMw*mM5Yhd6;9 z90N%P5LYvSGA+jxC=c!{j$;OpEDjgplr)6c07+HRh7gA_gVHwVWJ5@(K)mqS5KA%TFfz1kS!BMz|Fw;MwO z0;2b$F)W5a1-!Zm#AOh-#G62T%?XNQ&P^r|Q&__5(T<}lw*ylKH<0dXSOQJi@ekf4Mp+-JeSkjcQ%zz6pBVoOL1M*{4bH|;zOy)|lW7=&q*(=44lg*ZaK@XgH1%1p}O>DuXaHKVBfh_}r4=AFU z_SjDLO10zsZ40T<7$*mAwqUViV9=TDn_$g3-40?j(`3O43r4%iUI})bvi6WLU;q`W zOy%~IyVC49MVuHIlE78TL{oFlGA9OxD6qAG*%lm74!Cw=(sG{Mm2Ste#2MmGM5_7X z45??CL8+WG(FI}xGdM7JyFg-#6P(LgTp{tpG&!)z0>l9qfJ|##CwpbuafrJ?%t4eS z*=~>$4BQ4`+Uz!&E6a|P#~l)c?BD{@#T{Y+^W;E#bEetulTT&Yao&Xra)4db91LrF*NMeD=$#_F}kgV+I4e=NxH7@jq_=p8$AL~tTP;>CCu{EcT z4d)$GJTK61dFZ6u}wDz+ee!=cSl)`UFDa zhGBA`l{x2YbWnJ~scdTyBu#?b08FgGle;4AIQ@fR8YUW; zvrY~MXUG(5PR;gvt=j{-PJHV-yNih`M>WvMBH1Qe1 zKJ5*KD1o%&PKH9vWCwdnI1CagkaQ3o22ly|)bub&kU}EtVi*I1IRit35Xdc@>fw;= z0Vy0R!XYUQl70?CdEn}s^M5!b`oO7>RW|}$9hz8kW=BB6A5yNZjeu10j3CoF9zuDD zj4K++z+lM0(7-i$V!An}TO=ffLhP%Egt-M&%zs+uGcmA)Ea+ju5etbFaPh^N0_8!hTnFW`fpQV+k5~p!OU=}p z(;yCF0XQdcWXD0G08t{XjDvU<62O1sAWmhUd{Nh&^G!U22iD7}lK}BB#2aY|kW2%S z+mrz5!ZJ<{w6p6X$r_u% zz~DZaH{Y6bNd_c&ae_KDoG&sUNsj~UGnGuFB8oFA6XFht&2ykU&dGv$=A8F3A>qvq ziWN?|EC`PS%!|unV6X>gBv9LEZWbiWA(`cN7Q{YCWg?yp;eq3cBRCu4en{XQ(B

      AP|v&H@*poZoUG5dco)9C~?>asiPE67nFy#4%Yg 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�WA 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 Date: Fri, 23 Aug 2024 11:07:18 +0000 Subject: [PATCH 266/312] chore(release): 1.0.0-dev.27 [skip ci] # @revanced/discord-bot [1.0.0-dev.27](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.26...@revanced/discord-bot@1.0.0-dev.27) (2024-08-23) ### Bug Fixes * **bots/discord:** give only removed roles for role presets ([522ad28](https://github.com/revanced/revanced-helper/commit/522ad28fd83565e9ca411dbce86c8447574288fd)) * **bots/discord:** replace duration parser with a library ([94c4fed](https://github.com/revanced/revanced-helper/commit/94c4fedc06e20051e4123508e3134b97eb84782a)) --- bots/discord/CHANGELOG.md | 8 ++++++++ bots/discord/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 42393f8..2b1b842 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,11 @@ +# @revanced/discord-bot [1.0.0-dev.27](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.26...@revanced/discord-bot@1.0.0-dev.27) (2024-08-23) + + +### Bug Fixes + +* **bots/discord:** give only removed roles for role presets ([522ad28](https://github.com/revanced/revanced-helper/commit/522ad28fd83565e9ca411dbce86c8447574288fd)) +* **bots/discord:** replace duration parser with a library ([94c4fed](https://github.com/revanced/revanced-helper/commit/94c4fedc06e20051e4123508e3134b97eb84782a)) + # @revanced/discord-bot [1.0.0-dev.26](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.25...@revanced/discord-bot@1.0.0-dev.26) (2024-08-15) diff --git a/bots/discord/package.json b/bots/discord/package.json index d804352..8903894 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.26", + "version": "1.0.0-dev.27", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From dd8872c027c7e7e1a00f38d659b4d6e79274238c Mon Sep 17 00:00:00 2001 From: Palm Date: Thu, 5 Sep 2024 17:33:06 +0700 Subject: [PATCH 267/312] fix(bots/discord): correct permission check logic Members were being previously treated as users and some requirements are passing by default when they must not. --- bots/discord/src/classes/Command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/classes/Command.ts b/bots/discord/src/classes/Command.ts index 0331d0f..95cc0be 100644 --- a/bots/discord/src/classes/Command.ts +++ b/bots/discord/src/classes/Command.ts @@ -373,7 +373,7 @@ export default class Command< memberRequirementsForUsers = 'pass', } = this.requirements - const member = this.isGuildSpecific() ? null : (executor as GuildMember) + const member = this.isGuildSpecific() ? (executor as GuildMember) : null const boolDefaultCondition = defaultCondition !== 'fail' const boolMemberRequirementsForUsers = memberRequirementsForUsers !== 'fail' From 7a379a2caebaebc9a73f7cf295bde3c9b54cb525 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 5 Sep 2024 10:34:11 +0000 Subject: [PATCH 268/312] chore(release): 1.0.0-dev.27 [skip ci] # @revanced/discord-bot [1.0.0-dev.27](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.26...@revanced/discord-bot@1.0.0-dev.27) (2024-09-05) ### Bug Fixes * **bots/discord:** correct permission check logic ([dd8872c](https://github.com/revanced/revanced-helper/commit/dd8872c027c7e7e1a00f38d659b4d6e79274238c)) * **bots/discord:** give only removed roles for role presets ([522ad28](https://github.com/revanced/revanced-helper/commit/522ad28fd83565e9ca411dbce86c8447574288fd)) * **bots/discord:** replace duration parser with a library ([94c4fed](https://github.com/revanced/revanced-helper/commit/94c4fedc06e20051e4123508e3134b97eb84782a)) --- bots/discord/CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 2b1b842..0289186 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,12 @@ +# @revanced/discord-bot [1.0.0-dev.27](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.26...@revanced/discord-bot@1.0.0-dev.27) (2024-09-05) + + +### Bug Fixes + +* **bots/discord:** correct permission check logic ([dd8872c](https://github.com/revanced/revanced-helper/commit/dd8872c027c7e7e1a00f38d659b4d6e79274238c)) +* **bots/discord:** give only removed roles for role presets ([522ad28](https://github.com/revanced/revanced-helper/commit/522ad28fd83565e9ca411dbce86c8447574288fd)) +* **bots/discord:** replace duration parser with a library ([94c4fed](https://github.com/revanced/revanced-helper/commit/94c4fedc06e20051e4123508e3134b97eb84782a)) + # @revanced/discord-bot [1.0.0-dev.27](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.26...@revanced/discord-bot@1.0.0-dev.27) (2024-08-23) From 8c0dd67d03d5a1747993da08a5bf82a39de43789 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 20 Sep 2024 13:49:38 +0700 Subject: [PATCH 269/312] fix(bots/discord): filter out text triggers correctly from image-only scans --- .../discord/messageCreate/messageScan.ts | 13 +-- bots/discord/src/utils/discord/messageScan.ts | 94 ++++++++++--------- bots/discord/src/utils/discord/moderation.ts | 5 +- 3 files changed, 57 insertions(+), 55 deletions(-) diff --git a/bots/discord/src/events/discord/messageCreate/messageScan.ts b/bots/discord/src/events/discord/messageCreate/messageScan.ts index c9bea0d..27a199c 100644 --- a/bots/discord/src/events/discord/messageCreate/messageScan.ts +++ b/bots/discord/src/events/discord/messageCreate/messageScan.ts @@ -66,10 +66,7 @@ withContext(on, 'messageCreate', async (context, msg) => { const mimeType = attachment.contentType?.split(';')?.[0] if (!mimeType) return void logger.warn(`No MIME type for attachment: ${attachment.url}`) - if ( - config.attachments.allowedMimeTypes && - !config.attachments.allowedMimeTypes.includes(mimeType) - ) { + if (config.attachments.allowedMimeTypes && !config.attachments.allowedMimeTypes.includes(mimeType)) { logger.debug(`Disallowed MIME type for attachment: ${attachment.url}, ${mimeType}`) continue } @@ -86,10 +83,14 @@ withContext(on, 'messageCreate', async (context, msg) => { if (isTextFile) { const content = await (await fetch(attachment.url)).text() - response = await getResponseFromText(content, filteredResponses, context, { skipApiRequest: true }).then(it => it.response) + response = await getResponseFromText(content, filteredResponses, context, { + textRegexesOnly: true, + }).then(it => it.response) } else { const { text: content } = await api.client.parseImage(attachment.url) - response = await getResponseFromText(content, filteredResponses, context, { onlyImageTriggers: true }).then(it => it.response) + response = await getResponseFromText(content, filteredResponses, context, { + imageTriggersOnly: true, + }).then(it => it.response) } if (response) { diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index 8d6699a..be14ba6 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -9,7 +9,7 @@ export const getResponseFromText = async ( responses: ConfigMessageScanResponse[], // Just to be safe that we will never use data from the context parameter { api, logger }: Omit, - flags: { onlyImageTriggers?: boolean; skipApiRequest?: boolean } = {} + flags: { imageTriggersOnly?: boolean; textRegexesOnly?: boolean } = {}, ): Promise< Omit & { label?: string; triggers?: ConfigMessageScanResponse['triggers'] } > => { @@ -31,7 +31,7 @@ export const getResponseFromText = async ( triggers: { text: textTriggers, image: imageTriggers }, } = trigger - if (flags.onlyImageTriggers) { + if (flags.imageTriggersOnly) { if (imageTriggers) for (const regex of imageTriggers) if (regex.test(content)) { @@ -39,7 +39,7 @@ export const getResponseFromText = async ( responseConfig = trigger break } - } else + } else { for (let j = 0; j < textTriggers!.length; j++) { const regex = textTriggers![j]! @@ -54,55 +54,59 @@ export const getResponseFromText = async ( break } } - } - // If none of the regexes match, we can search for labels immediately - if (!responseConfig.triggers && !flags.onlyImageTriggers && !flags.skipApiRequest) { - logger.debug('No match from before regexes, doing NLP') - const scan = await api.client.parseText(content) - if (scan.labels.length) { - const matchedLabel = scan.labels[0]! - logger.debug(`Message matched label with confidence: ${matchedLabel.name}, ${matchedLabel.confidence}`) + // If none of the regexes match, we can search for labels immediately + if (!responseConfig.triggers && !flags.textRegexesOnly) { + logger.debug('No match from before regexes, doing NLP') + const scan = await api.client.parseText(content) + if (scan.labels.length) { + const matchedLabel = scan.labels[0]! + logger.debug( + `Message matched label with confidence: ${matchedLabel.name}, ${matchedLabel.confidence}`, + ) - let trigger: ConfigMessageScanResponseLabelConfig | undefined - const response = responses.find(x => { - const config = x.triggers.text!.find( - (x): x is ConfigMessageScanResponseLabelConfig => 'label' in x && x.label === matchedLabel.name, - ) - if (config) trigger = config - return config - }) + let trigger: ConfigMessageScanResponseLabelConfig | undefined + const response = responses.find(x => { + const config = x.triggers.text!.find( + (x): x is ConfigMessageScanResponseLabelConfig => + 'label' in x && x.label === matchedLabel.name, + ) + if (config) trigger = config + return config + }) - if (!response) { - logger.warn(`No response config found for label ${matchedLabel.name}`) - // This returns the default value set in line 17, which means no response matched - return responseConfig + if (!response) { + logger.warn(`No response config found for label ${matchedLabel.name}`) + // This returns the default value set in line 17, which means no response matched + return responseConfig + } + + if (matchedLabel.confidence >= trigger!.threshold) { + logger.debug('Label confidence is enough') + responseConfig = { ...responseConfig, ...response, label: trigger!.label } + } + } } - if (matchedLabel.confidence >= trigger!.threshold) { - logger.debug('Label confidence is enough') - responseConfig = { ...responseConfig, ...response, label: trigger!.label } - } - } - } + // If we still don't have a response config, we can match all regexes after the initial label trigger + if (!responseConfig.triggers && !flags.imageTriggersOnly) { + logger.debug('No match from NLP, doing after regexes') + for (let i = 0; i < responses.length; i++) { + const { + triggers: { text: textTriggers }, + } = responses[i]! + const firstLabelIndex = firstLabelIndexes[i] ?? -1 - // If we still don't have a response config, we can match all regexes after the initial label trigger - if (!responseConfig.triggers && flags.onlyImageTriggers) { - logger.debug('No match from NLP, doing after regexes') - for (let i = 0; i < responses.length; i++) { - const { - triggers: { text: textTriggers }, - } = responses[i]! - const firstLabelIndex = firstLabelIndexes[i] ?? -1 + for (let j = firstLabelIndex + 1; j < textTriggers!.length; j++) { + const trigger = textTriggers![j]! - for (let j = firstLabelIndex + 1; j < textTriggers!.length; j++) { - const trigger = textTriggers![j]! - - if (trigger instanceof RegExp) { - if (trigger.test(content)) { - logger.debug(`Message matched regex (after mode): ${trigger.source}`) - responseConfig = responses[i]! - break + if (trigger instanceof RegExp) { + if (trigger.test(content)) { + logger.debug(`Message matched regex (after mode): ${trigger.source}`) + responseConfig = responses[i]! + break + } + } } } } diff --git a/bots/discord/src/utils/discord/moderation.ts b/bots/discord/src/utils/discord/moderation.ts index dbb0a9c..f8a4ad0 100644 --- a/bots/discord/src/utils/discord/moderation.ts +++ b/bots/discord/src/utils/discord/moderation.ts @@ -23,10 +23,7 @@ export const sendPresetReplyAndLogs = ( ]), ) -export const sendModerationReplyAndLogs = async ( - interaction: CommandInteraction | Message, - embed: EmbedBuilder, -) => { +export const sendModerationReplyAndLogs = async (interaction: CommandInteraction | Message, embed: EmbedBuilder) => { const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) const logChannel = await getLogChannel(interaction.guild!) await logChannel?.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) From 4abac0c890c0548e14cb56723cae919353a8e726 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 20 Sep 2024 13:51:56 +0700 Subject: [PATCH 270/312] fix(bots/discord): don't refresh timer if force timer is active for sticky messages --- .../messageCreate/stickyMessageReset.ts | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts index 2b18fd9..59cdcd6 100644 --- a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts +++ b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts @@ -12,18 +12,34 @@ withContext(on, 'messageCreate', async ({ discord, logger }, msg) => { store.timerActive = true if (!store.timer) store.timer = setTimeout(store.send, store.timerMs) as NodeJS.Timeout else { - // If there is a timer, but it isn't active, restart it - if (!timerPreviouslyActive) store.timer.refresh() - // If there is a timer and it is active, but the force timer isn't active... - else if (!store.forceTimerActive && store.forceTimerMs) { - logger.debug(`Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer and clearing existing timer`) + /* + If: + - (negate carried) There's a timer + - The timer is not active + - The force timer is not active + Then: + - Restart the timer + */ + if (!timerPreviouslyActive && !store.forceTimerActive) store.timer.refresh() + /* + If: + - Any of: + - (negate carried) The timer is active + - (negate carried) The force timer is active + - The force timer is not active + Then: + - Start the force timer and clear the existing timer + */ else if (!store.forceTimerActive && store.forceTimerMs) { + logger.debug( + `Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer and clearing existing timer`, + ) // Clear the timer clearTimeout(store.timer) store.timerActive = false - store.forceTimerActive = true // (Re)start the force timer + store.forceTimerActive = true if (!store.forceTimer) store.forceTimer = setTimeout( () => From f035994f9e7963b29f90908e58b66c2ab354a96a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 20 Sep 2024 06:53:32 +0000 Subject: [PATCH 271/312] chore(release): 1.0.0-dev.28 [skip ci] # @revanced/discord-bot [1.0.0-dev.28](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.27...@revanced/discord-bot@1.0.0-dev.28) (2024-09-20) ### Bug Fixes * **bots/discord:** don't refresh timer if force timer is active for sticky messages ([4abac0c](https://github.com/revanced/revanced-helper/commit/4abac0c890c0548e14cb56723cae919353a8e726)) * **bots/discord:** filter out text triggers correctly from image-only scans ([8c0dd67](https://github.com/revanced/revanced-helper/commit/8c0dd67d03d5a1747993da08a5bf82a39de43789)) --- bots/discord/CHANGELOG.md | 8 ++++++++ bots/discord/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 0289186..1f06149 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,11 @@ +# @revanced/discord-bot [1.0.0-dev.28](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.27...@revanced/discord-bot@1.0.0-dev.28) (2024-09-20) + + +### Bug Fixes + +* **bots/discord:** don't refresh timer if force timer is active for sticky messages ([4abac0c](https://github.com/revanced/revanced-helper/commit/4abac0c890c0548e14cb56723cae919353a8e726)) +* **bots/discord:** filter out text triggers correctly from image-only scans ([8c0dd67](https://github.com/revanced/revanced-helper/commit/8c0dd67d03d5a1747993da08a5bf82a39de43789)) + # @revanced/discord-bot [1.0.0-dev.27](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.26...@revanced/discord-bot@1.0.0-dev.27) (2024-09-05) diff --git a/bots/discord/package.json b/bots/discord/package.json index 8903894..bca5966 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.27", + "version": "1.0.0-dev.28", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 3261294822b0a9faec094536ed5be2d3e1d5e17b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 22 Sep 2024 04:01:09 +0700 Subject: [PATCH 272/312] fix(bots/discord): fix get response logic --- bots/discord/src/utils/discord/messageScan.ts | 125 +++++++++++------- 1 file changed, 75 insertions(+), 50 deletions(-) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index be14ba6..43026b7 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -1,6 +1,7 @@ import { type Response, responses } from '$/database/schemas' import type { Config, ConfigMessageScanResponse, ConfigMessageScanResponseLabelConfig } from 'config.schema' -import type { Message, PartialUser, User } from 'discord.js' +import { ButtonStyle, ComponentType } from 'discord.js' +import type { APIActionRowComponent, APIButtonComponent, Message, PartialUser, User } from 'discord.js' import { eq } from 'drizzle-orm' import { createMessageScanResponseEmbed } from './embeds' @@ -54,59 +55,56 @@ export const getResponseFromText = async ( break } } + } + } - // If none of the regexes match, we can search for labels immediately - if (!responseConfig.triggers && !flags.textRegexesOnly) { - logger.debug('No match from before regexes, doing NLP') - const scan = await api.client.parseText(content) - if (scan.labels.length) { - const matchedLabel = scan.labels[0]! - logger.debug( - `Message matched label with confidence: ${matchedLabel.name}, ${matchedLabel.confidence}`, - ) + // If none of the regexes match, we can search for labels immediately + if (!responseConfig.triggers && !flags.textRegexesOnly) { + logger.debug('No match from before regexes, doing NLP') + const scan = await api.client.parseText(content) + if (scan.labels.length) { + const matchedLabel = scan.labels[0]! + logger.debug(`Message matched label with confidence: ${matchedLabel.name}, ${matchedLabel.confidence}`) - let trigger: ConfigMessageScanResponseLabelConfig | undefined - const response = responses.find(x => { - const config = x.triggers.text!.find( - (x): x is ConfigMessageScanResponseLabelConfig => - 'label' in x && x.label === matchedLabel.name, - ) - if (config) trigger = config - return config - }) + let trigger: ConfigMessageScanResponseLabelConfig | undefined + const response = responses.find(x => { + const config = x.triggers.text!.find( + (x): x is ConfigMessageScanResponseLabelConfig => 'label' in x && x.label === matchedLabel.name, + ) + if (config) trigger = config + return config + }) - if (!response) { - logger.warn(`No response config found for label ${matchedLabel.name}`) - // This returns the default value set in line 17, which means no response matched - return responseConfig - } - - if (matchedLabel.confidence >= trigger!.threshold) { - logger.debug('Label confidence is enough') - responseConfig = { ...responseConfig, ...response, label: trigger!.label } - } - } + if (!response) { + logger.warn(`No response config found for label ${matchedLabel.name}`) + // This returns the default value set in line 17, which means no response matched + return responseConfig } - // If we still don't have a response config, we can match all regexes after the initial label trigger - if (!responseConfig.triggers && !flags.imageTriggersOnly) { - logger.debug('No match from NLP, doing after regexes') - for (let i = 0; i < responses.length; i++) { - const { - triggers: { text: textTriggers }, - } = responses[i]! - const firstLabelIndex = firstLabelIndexes[i] ?? -1 + if (matchedLabel.confidence >= trigger!.threshold) { + logger.debug('Label confidence is enough') + responseConfig = { ...responseConfig, ...response, label: trigger!.label } + } + } + } - for (let j = firstLabelIndex + 1; j < textTriggers!.length; j++) { - const trigger = textTriggers![j]! + // If we still don't have a response config, we can match all regexes after the initial label trigger + if (!responseConfig.triggers && !flags.imageTriggersOnly) { + logger.debug('No match from NLP, doing after regexes') + for (let i = 0; i < responses.length; i++) { + const { + triggers: { text: textTriggers }, + } = responses[i]! + const firstLabelIndex = firstLabelIndexes[i] ?? -1 - if (trigger instanceof RegExp) { - if (trigger.test(content)) { - logger.debug(`Message matched regex (after mode): ${trigger.source}`) - responseConfig = responses[i]! - break - } - } + for (let j = firstLabelIndex + 1; j < textTriggers!.length; j++) { + const trigger = textTriggers![j]! + + if (trigger instanceof RegExp) { + if (trigger.test(content)) { + logger.debug(`Message matched regex (after mode): ${trigger.source}`) + responseConfig = responses[i]! + break } } } @@ -163,14 +161,41 @@ export const handleUserResponseCorrection = async ( }) .where(eq(responses.replyId, response.replyId)) - await reply.edit({ + return void (await reply.edit({ ...correctLabelResponse.response, embeds: correctLabelResponse.response.embeds?.map(createMessageScanResponseEmbed), - }) + components: [], + })) } await api.client.trainMessage(response.content, label) logger.debug(`User ${user.id} trained message ${response.replyId} as ${label} (positive)`) - await reply.reactions.removeAll() + await reply.edit({ + components: [], + }) } + +export const createMessageScanResponseComponents = (reply: Message) => [ + { + type: ComponentType.ActionRow, + components: [ + { + type: ComponentType.Button, + style: ButtonStyle.Secondary, + emoji: { + id: '👍', + }, + custom_id: `train:${reply.id}`, + }, + { + type: ComponentType.Button, + style: ButtonStyle.Secondary, + emoji: { + id: '🔧', + }, + custom_id: `edit:${reply.id}`, + }, + ], + } as APIActionRowComponent, +] From d3c56222bef851e836f7b35517584bd84ffe6ab7 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 22 Sep 2024 04:02:20 +0700 Subject: [PATCH 273/312] chore: update dependencies --- bun.lockb | Bin 287544 -> 288360 bytes package.json | 16 ++++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bun.lockb b/bun.lockb index d0db354f717bad6e44068535a78f3ff522b250f9..59ad7e3be738636f9579713fb195aa0f18e2fd71 100755 GIT binary patch delta 53234 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 delta 53138 zcmaEHMsUY5!3lbrT7mn_f>m$nUMgEuwDh3#&0|OPJb9~T8$4;OSw4Njh4>XoEDRuU zX=1oM6W6JU6$+ZX%nS^|3=9pwnHU&^7#JE}F)=U*GB7mUVq#zrU|?uC&BVaK&%n^I zcj7^D%TH_&$;WIE!Ru@c3_Ku3P+F6NfkB*sp+Sy=fq|QWp+SU$fq{#Gp+R7>AfveC z0Zs-6P6mbsW-f@V4;KRiF9Sov?8%Od@`3f-3=Dh>3=Pk?A*u>8(<(u3y~7PLV=4~= zg9HOZLuye`evxi!g)J|{l#ARDIxRCVMYpto;Wt#=W%5Qw_4;If1_luZhKA&f#GGsf z28N&f5L+@5i*?g-64NogR2n4g9W-psVSxP$*HMq z5SK->K`h)L3DJ;Ll$e}a%)p>71rg7cf@s_?1(ElGT9BJql%1Nwz)&g;Nhs(cm@N&_ z|4Ig;PgjK@MVJh&053m5D{^#S9D#utb%d zlUbU{z`$TXc_y=bq?jTkLP|1H^FS%%p8|x>sR+?+EDs46SVY5U4{3-vn=(YbGaJO2 zCQ$EPoGi#9&Y6*)pRHS5k|I9Yl109Ly(%QE^VJ|OOoY;5Y7jG=)F6o}vpBJ+D6x_u zGcO~xD6^#4Obud}zAD70of;6kiVJcwON; z5{rscb#seX=|IdWNG!?F%}%Xc2$i1-7MEsd=+J?LD$Ipb^dXtAc5)%Bc%-EPBz$-b zA*x`;Bq6LWJI5{uHo{#j-MF$I=ITg)J?5;li;daenCe#Q!M%|mmD{55lkJ+LBT zFDnCsCIdr#gFLk8o5c!=u9DQ;0#H%ZVFl56-3k)wurdHfgVgD!WEMGFK|Pg zZ6FTJ%mXE728KU2kPI=)24X&}1Z%PZ1#&$@Ls2rQ;A3EDv4a?to0*rHn^{~^X$Ns} zUO_Hc0W3tmJ3>O{o+AT;Bm+ak2}ej-(&G#=kfEW>8N&B)fyl47hlI#i7f6VBxk4P8 zo|B)%P|v`?z~~0i@X-~L?w+|qECxBGAhEdkt}DcVZg+@$ojW9CU?n`f+~9+h~#`nNDvnk{F?nB@>PBiXIDbS)1m4^CwFql*E0t|9G9AxoS%}Jm+lk@ zar}%xNc8=N%HM(7x-bah#|9txqe zij(pf7#M;W&IUsSo}ud z2+>`b2vOaV1mPEFrWcpw7o~b8LrfJ-hPag#%2!T?Bz!LiNK}-Rrs-xCGccS?f$#-W zAvw<)s&7d=#8(sI!BJY@&>jy7gyuAehUDB7-QtYQ+^Tp8-z6Q=&`B;XPAyJmV2F>0 zlzelbDG^pl7D59tIa4 zl1MA9Y{{cuUt0i?eNzYtl)HtH^mwum z9EuIw3L&Aov=Cx9sCY@LWMC-HNi5D_U|?8Y46!XUJukl~m4RU%l%Jbgl%8tDz_6+W z65E-@x*)9#v89l(m{kIf_4@~0>Xb;0ddH(3W$sS3LxptumDm}d$BSwC^9fK)LKH4O>Sy_IVhHrpnO+Lh`MQY z5dDAaAR$$rUzAc_lvwZ?Bwi0{twSX`>mV*yw`5?DV_;~Q)c^^Bl>8Fif};F_%wpaA zq$~!8XAKa8W>`Xk_HrXcU0P9ot`P$R1FR-yV}jHa2b&<~mX>CwfE>ut3<;Uiyp+sT zP!)f*84|=LrFnU&ISdRrsc9vk#*|wtM4>?|D5w}3WT1RDDF0InME*_-ME+C@B>!)N ziZ5<~gkay~gM8wOww(}XcXdLXeZCXo>_(XQe;a}Y{k=qVGHl3@uBmPKJr8Catp)+&az zJYj7vNMnEUHUXh}hgpy`Jz*B4EPl%X@q6EFNE-hz8^TY4HrsE{hNN*=Kg@*>;wM;t ztUxy>KRK}^wLmu~GdHt@;oe+`d>z!hJM$p?3-iEXUEi>I9wZZFWtM>3O*7^}GE3S5 zhy%i)G_1BynFr}PC1#a@k}AXPg^)-*z7UesK&6FlX;J2mg%F1~%!5Qy9@GK57ehR3 zumq&uo1sB=3B&;IC6Kh~yA-0pb}7UGx=>mcO7lYLRZxSbEQX}*n8}Jl;`OjbFD#1{ zl;#$I+<9#UB#Z52fD|$Ut03O~x)S2uJ1Zes@yJSubJsxWsVgC6as5h&EtybyCIbV5 z0s}*X-&%-y4iLJ&!FVmiqNJSsB2ZUYaV^AK8`nV+a|;6`0@tjEXk54+5+T#qL*(CW zgvdv(hXlh-DF4_-NHCSIhtMgTLE*>Huze#WoJ)#zb2BUIQ;SN9b25_{7#KR%LnIDv zVPKGFU}#vg1!6GFzDi4a8tzXt=)}qHx!C1_o6IhK6a| z85mR;7#a$pe9g%Zh1Ho(?VZdeB3Hj^AH?>e3Q!AzfguYTEEW49;s1U=IK4JlAAqEa z8&L5(2Oz;~eGn3isl`d9nK>y88HXYIf(}FC%?3)VL1_Uf{reC&RO%a^AA*GEWhlKL zO0R~})1h=Tl+K6J(NNms5F}nqp|lc|=7-XM4?-OJ8cN@U(#OGcJwrpreu&S=^Z{ia z0M&Ao`GBFp?-ay`u$1c!P2p27LPA#V5+sE;T!N(RGALj3G6O?BX!J%_H4Y$P!Ap=TXq$ah+-Kag~GiX5DThuGLt~bvi2q5*D-ItA+>>L@&xsQ z%^Vs6j0Stxc-YH?Tg<;`w%KX_xleKSx+^)dZpppp|2%z~yxsW)Yi@~|9{lEa@##?g|1Oq)G4@32fxsWB1kVq#!0W?*Pw0$BRi7-N$04Y|%)sD}RVJUAfx#E6 z%pPV225+n~>?{lno>*l9SQr@GvC2$mVPJ5@Cd2s|YMR94g(l{VGbTqG*mE+oGcb68 zR8LkkFlY3geAB?5b22*vgChe&gD6<+%H&8xdrk)q28J-upyy;ob92sV91IM03=9n% zARgnD$&yC)oKl<&3^oi54Xl$Djm$a2I2jl$!0HqA%sKlw85oQp>W$1fPjWIaSTQg( zu!D8;O^!6S=ZxfHU~q!C!^oU*>ExTn_KXiFOPbg-3QmqRv1fFjJk!LUvzMEJ!G(dL zfd!{*ZUGBB7;o@-*w!Nv#i(saXHj0T{nONkBXW2{R)}h^N3j#*)c59qbu*PL_1EXZ$=l($SvNPzvH;c2JV$td@e9 z21%+Xq#(`*Cr3^uX^5-XCqIm~;E-luFlAt9-~l;^^OQ6LgC9JFNytD9VV|sMZ_XJb z191){45r9HtYez2Xl2gvSq5SOBqa#wad3FZGBCt3Ff<^%vrHDEpJ{TUi8<>%Sq281 z$#+ey8KoxQbg^d)nJnpQ&pA&HVk+ZgMN4zW50huQ+H=OqLrNk6kSiD`PL_1D=e#5j zNsH{0746JfSrkCACTnEPnWezMU;>Vrg~k>fP!8+lg<%$xBi-#e6%`?gi*fQp7jw>R zMM&~xo~-C-&bbgSmuPIxd0!C{+AN^ZWK^6y)5D%KNeN;p3n=MuPJ!}RL79W|m=Yw3 zLPA(j8DLVe&&`bB;3_kl=@!)WgZH3CR^u0X+_mAWeu9;Ub*VG$C=$HhH11 z1&1ai+}S{xn?qX*;%JEC3BH^hS`h14LBYp)N(*8ZD@ZXbpEd)72Pj2xhG|0_%rg0* zfjQ?iZHV{SKzcYHXhXaXPpO;+I*@?m1V`ix9f*}opor&G(}hJBsKhPNg~+i?et5uw zLl@!{PLQ#jZ*(Cc2FV;MdJqRd(ng9NvfLa!h&~oj*mGWkssyJ8PBDFm?;)uuLLU+^ z;6jaY&1A_?d(Pkb5GSyM<08ob;&diZ=H-|I<-xtiam)ac)8QhVl7P8F5QjmW5^n(KZ}b5VIj6^x6ty8rZ$8*47LRR+D2*tQkuu zOUB!CZm@><6r%QpH6&VCLFt0gW%A8_$h(YMlV>K|b1ZR&_zsd- z1oSvKzc@oGS!R$QI1^nUCV;C~&fPAM_~8U+a28icj6mEK;mW|^GC4NfW^!bzJ%_j( z#1u#gav_J4!wph=L7E<$CrhT;bMm-D0*@VBu)DZJ3}>Fa(B7PJ_T-ys_MCU2VjN({ zG%X%AI6(= zvUovwY?B|ZwBYc9gg7{UIj49*nknFR0_Q6)NIr+i$#_F}kZkMc4e<~p%`NnXc!dRI z9qUbRP}@(|#F|sb2a=Y+VZ@j>Su)$6bD6F$ztd znQPCP=npXm+`8mk>ksKW2!cY9RWJaQxIk5+X8Nsx1E5G3(| zD|;r^;K@=^cAWmfFbxk4%vmP~gYw(mcxz715J<>yfa*rZ;K?@&?Kx+MKuQ*HsB_*9 zfj9x2I++wh!R^}EP)OSyoS`^-Lm^5ajkS}Z5F^>a{t*s?Lo$JEI7g#7|aAySe%Nin5e2gm)U33Nf_Rr{^1|g7EFh`Lv5wZ9N1`BhLlXIqD44%NO*NxvNSJ^V zGiP2jB%&a>@lZ4*Dj>li7z0TgT%hd0nGpkt19p(dIcCH_Y(V(uObjgbCVE(K#6ltk zTuO1KKzR@=*Fkw~;GFX#77}QTlOImDV2NX3Fqk~I#)dr(;!8-$11iy0#zA}v4qT?c zag(EJ>^R@V!`*9S&Z(0C@h8OfX$g=V!aO-~zXeAEq@}54q(X@^Dih)ku+5BfCQG*3bKc8@1T;G+IymLBAUqB*FD{FL!5*A#K<=KK z1qp0Owz-`Ju?JFJh-X81;0WRf&W5-f5o+yFd2ly~^Kdo;gDu!xQ16>z^2~O7PFv8J z7Gzk$%AB(j%3}ky);W*mK(Yg*=J=KaN$*VH){#RlB>dRGF0RUj_!rXb-jxe+6}Y(M z{FVy|cW|oa(944q0*F+fkOv7JaI24FQ69uxg#4X6NEYP=XHms`NJv5QSz8$iSFi{)m9q6+_KAUzR|U2rIaKqFxG# z43^1?1r{8okhlPMS6CO9GBDUro||aR^tE*ItqFFV8f6f*;1ZlMZgS*Ad(QP`5Kl9L z1N>hZ#G8zh6AjH-UCKcQfV!8>e7O$S?)CzsWhH7E+<{f*o-ZDhG)!^*Tt47BV2XZt~6P_MAfXkW!6# zvSPeBhi^S3kl<;Xv32sy8TOn9>mk9#G+9y4oauM{WT}~UoMsIS3@MO|X>QK3tN{`y z(5$_flcND*7VBiiAPe?JNJ@doUdZGG34n$d;WlwjZGcw+S*X&IX-6E;EFT^G=(*ake_sxu(gtX4`RUHbXKC2RKbeHbXKTxEsW{ zVDij4_MA_eAw>>2`Ee??K-|dy$|QoJEs&JJ$iTqBjxvHA)ylvS2ERS;!=)v#4XYMC4#8zu^#mLR8-~ReJq|&ic1SA})6Fy6u{j{%1gAhdWH=mCi)9BS zW1z_xa&mMqFw}rYhe2foM<=AjV4R$|-dxbF6OyDrnm||(Htx)1K>sbR@YzUo&ZUIkY@Pt36RXp11b?X ze@uY*7Sfz^n+VazIyo`If_);yv+#i61_l#QSDo|hREPsOz)cjM zX^_YVcRM(Jr$OY{CnqLaa7=?tm_UN*;xveVctHJiPTuK|f>#7w9!5@wq*)G73COx; zIs=0z*o|MOL&6ABPMXeuSj7!)|CY^wgccjP<*;W4Ol6{(Ip?1lkhq6bfOaz>35jF! z!#s1wn#qyt?KwBhgd~41u>DVGGB89lFf<5*JIH~vAf6Bg+p}pF14Al&8bEP2B%=s} zJwI96X&G!E)1~>7 zZ*8&TWL^M?0%1@>;w)PL=`=tz>_hO3%vsqMGBD^(zPr6U&e-W?v4e;UVcIU^(0>V{^`l%VFsSG;Hy0IV2T8Mg}!jK++{7 z#2dMhERj&btCLUT^tRggRn;kB)T!~%qOdlj_8 z0ISql4Xq`>ylNEQsnw8RhUgPt1IhmoUJ^2o_3#=7hPcVHR@SU#Ye8i`sH40K!~z$i zph?MpYa#xEv}hgHLHrCJ+GZ+RH(Ba{9n`&HxJq~P1`hC>X04h-Axc}%##%lnR6;`hQu`EONvOWgTyftsC&b>Z*tUWJ4VgPGf&%d_V0(}2Byi1 zuI5a?_fM8OW5?lr0FsfQSzeEWbMgU*GH_pm>D__Jx6ascx*dd+Q;eX7J5&F`$x&zR zSnCfnF!)c7J!{Q+?l1#`FPJ5L1jfpMv$n!nOh*|Qyus>xkAf->kltx<)_pij;}}e~ z5YE~KXE7g#$@;)qQ;&nw!g(7OFau<;@(GX-h?NCrZ9Kui;0kuYH#p1sBuuOU#^OAB z5)v%TpjwboY;xpfd)9@?8(d4tbgDv&(komX>iu#(_rsjwr15k19tLd zYu0i&>j0d^c@`!c1ZT~Lv!1|Ny60f(%FcoOHutIx3z!jm*_xH*JV*${@`ke}odZTp1`9(;X#{ll_uDl2d4F+(%q;Ux%2T33kFHMfR zWj9&k7SH4*6HPfTLuwG_$%)go`ylu}Jdj+D3VRGU` zbJpcoKrNqGGi%oGS3nKUSa)kq%d3#=3U&=s;nm5v?%1)0Tw`DeoP76=HS5xApojsn zzFY%E0*K{!9meXr4vGYj*v;#p-~+LgZh*qz?j38+^c#?77XzpSVp?!xvef-L&Q~`e z^)RGeGT|m96Ec7+r6V^XH6|lCEQD@B+zwXB6mx6xtOs_SYi=?{-U)+XND-4qpvn^QefHJjglr@vjoyoW2?Kr#cK!O}RD9d#0 z&gNT>9x;Ps1+%UgmZZh{B~1_oprWXEl&IEZFuU|_fhwevnmeDd0V;^3fq0OBz)Fo0-~u18Ql zh~{KqV0aDX6GMZ7@C{Twh-PJAVED+uzyR8p`4uXTMAtKbG<<_ffM}2den9yk8YKS{ z%15R_>V8ATK{QC+A1EJ0bAx)xjNqlF3@VJ^1(ys)j1Y&IF*1PdHD~mOFf2g285kI_ z(I6>HMg|5@_}MT*;?52v$iTpWOfyc7eJNfK@~1OY78~utz`)QBmB&Vd^mjw$K{Uvl zy-+@g1~L1f&hH0t7#J8pG#dj0!&FA_<|Z%$oW!O>ojVh%5JZCvoDJoJXpqK*Q12~) z8oU%L528WdUIFD}qe14ag6dn%SPu!$HBb#88kEU4L-`;Yl%n=P9kvfDj*SL|#35*e z9fMkM9I75fgB*4W%7@eS=mLxk3>*v$440r5UV+MkXb|%%)PkE(3vNN_+aO5>1_lrf zV%}i{uSaBf0pc()fN2o(B~<+@Mg|5@Qm=mx4a!eY3%`I=F)%QIXi!jngYuDSkkEIi z`kzqxH%O3yfdQEYl?UuhkPzm8(wrbe85kHqG#jV@=7&lMKqauzAhQLb>V%jeNm>M| z9+_sG?E3~%M2JIWK{P1E$U^xbnu~#fK^oG1_^0_2nGfQ5DiKYhEVlJP}&$O zk4!5wFffEMfffWp7~squ233fS7Gz*xNQZhS6J+pY*|*~LAm!7-=?WEv!N0;>Kbls*L#WME*xM}uB>tPHK4KnZ}Q~`(vS^Np=v(Hd*5Dj8}g^GUz zaTpjFKs1Q?11kO#!~yNN$3lb5{RI)9ZqLTZst5{_zf6!3T^0}rR3#$OjFZp4hg2S1 z%#i%Z!wgBX{7_ZMG$_dlL)D2ggNLLU~)DBvb=34H7y7A{ZDLKr|>^&O-Sh8svbBP;)Lr zlkPRBJctIVzYdGx>re%^m?4GJJ!ptLW`?xWK0+Px18M+>1{v@Zs_q|D97KbB2x?t| zXeJg&-eF;ZxfH&I!(|qkSAu6Ac_f3=E@v9MJJHqkSA`0~*w~0i_HO4a$?FeVoxg4szcHlr}*8 z(LN5SMjGwoFfcHT_Hjo0IFN=Ta^D72P=NTLYy#@rfcPL9#0T|lKztAlYUqvjaYp+% z;FdW9!)PCe!ahzc>+~c6#_Z{PPcnK>mk?y+t-tJ)T%yeU_f$^LUygGgcV-`+wP;^a zfcoJB58h^M`xmYD^yKoC3`3<28AW%@?mk`fcmBI1UFAnX?Ej*SZ_hi|;|3n9WnpMw zfOG;u85e|EVwb3%?6h3ankCF*AvT~F{<05JH zTJ)N2oH}>v>qS0~f2CijUCp}Q@~N!)JF)Z`eeKV6xVQg2y?ue)N0Gyzb~I=(9n=j4 zjbebD2Er_p*$>Y$E|_xt{kGkgZ{MHE#+QCI`{VTUr8$9jS`HPRIVE_ZxaG@TUrCSa zL89WF%67}ys6mxvf@ViobQ#t*{WQG2~RHK{~ zb23*NE|G0uD76gJ-O1M*lBM|mryuXP4!xz1m;G$@413Aa@LJ=^FQM<6Yr&!SkN+Pd zWH=1uG!SMHXKT47)GW*SS9)Q?d&yNB&xg%BaZAMh<&w>owjP!*tZWx7X{vvHT=M1N zd-)YEtntq-ul(aBpPw7m-&4K+TIXly?PrAyu^p%#3 z;nP=GGV)I6wPN&UtehTc#TY(4!itf1`d*Mg^>j{a#_;JJ){MN{~G0=5o}-i+PTI~^Fq zr*}9o@=kvX66l?7>Bty9-NKQPclumMMsLRc=`TS7FF*pmPK@4+6Q|E~Vho=?!-1+7;o$`e4=+Y<#+B1= zf&^}W1Z=$-y%|?e@AL+Rhc_rZKmu#0Tl#>)!v_=|K8)Us>!-g23A_La`1&$>Gj5zd z(-#yTzM${`32dJ3=?4lAKTvr1F?us@o&FOf@B<_e>(A)TxPAIce^7Y%gTf<#(VKDS z^vD2Ecm#mL10=9}I%gm#JOV-C5yH{<^4nL(iN2m*x%NZ{af z$zV`;1cSmOn9-Z@@br@)ffFDB-4I4^#-r0KLqOpX0tye1!13vtp`h>x1%*c_qc`Kp z={G?FH$VcmVT|64r>A#@fx;sU6doXfv(qiZLE#Y&3XgC`Z^rY}UxEZ)fCPLa7`+)U zPM;Y83Xcd-cz^^hPxp)jg-0YPJR%vr8Lv+N2@?1L5{Qjr^k%$1ePt9VJfcA15zXk$ zcyoGWG$=fxLE!-sxILXS1{5AKpzw%c^k%#}eJ4m@2S`9RmeHH>{`AaPPlwNszz^kbrJHqc`Kz>6P)I@Q4S62T0)gbj<`%cqD+rBZ1MI z@#XZJAb}eo0oz1IZ^qZtI}<_Skq8P8kigsNmPw%SNCJgN5~DZc`{^%10xv)UzR8T< zj31}ZOa_HVGAKMi0-vXQrhvjD1r#1BjNXi2r~d>A`~V5WrZRdnexJTF6%-z+pzugz z^k)1yJu(dx9%-QP015n_&Y2Dhk91IYq%(Rm{++%PB(MV{Ae+JH&GesPdR7Kw*z}AH zP=I7GdNVRkm&^nONG2#iG8w%YnWvuw37h~4=w>l`GqO&v%mM{S7AQbK0_@W@vq1rp z4GNHKMsG&W={G?FH$VcmIgH+n+|xUAKmn2i3J{P0?{v#tP=Mru0wkBwn~{I|OOU_| zkbrL>e^ zAjP2Y00}5gmn;E=M+qoAN*KKvm8YKs37h~4=$0~iGpbInECq!}DJVQZ0_xK>%Ru2# z1`3ZdMsG&V={G?FH$Vcm<&55p+S5DBLE%vj3J;Kg?sUrvP;MVK)-ifB+E34{ z1BFK&C_F#{j?*RULE%vk3Xgh5Z${_oCqV)yKmxiAjNXi{(<>W5;n4sJ50HTSbj?Ok zcr=2-qmj{@(R2DukiZR)fNc|_H>3CT&L&WJG=ahcB;Y&UvKbT}&5V50=QT5WG5Sw` z2@-hG%*Z?4w}sK0F>w0K7EpM!fWiYL5Io(p6%-z=pzvsA^kxj5{u3nd10)dJ#t160 zSGIw|qYV@u?Tp@xk<%mFLE+I33J;J#^mNV+Pyx z^vq6Bcyxlo10;|*U9t-l9$ldD=wkF{OrCxcBya*Gpxe#p&6qm9vKtg0-JtLQ38YWg z>;Z*G4=6l(7`+)Yr{4q#+yDvK_A+`iW>4?z1%*d1C_F#{xzjECK;h8`N*{fU-i-Ov zUxEZ)fCPN|8NC?`r_byMg-1UqJU{})(>*7E!eas`JSH%DGnP*O2@?1L5{R7$X@E=w zg~votcuaycKqi60V-hGlKmyg%IVXd{V=^c_CPNw^Ab}kq0of^#2FMgpcuWC>2S}iC zy5v+)cuWO_$5coI1SD``DkI-?y=jbIpa#e^PKp-Et-K@R$v0fPe&kfCOUaKpG%(K;bb56drRS4UoB@@R$n<50Jp@>74UG;V};s z9`hg#5RkwQkbvxbNCRX(C_Lta!UH6*aJu9IP+0wkcj5Yhly2nvsd zpzr_*ET68q2oxTRK;f|n(f|Po+yDvKE`~Hf7K6fLF(^Dh0&AyRE&+wd5>R+7fiyrs z0xv)UzDpqukfosTSPBXckih2Yp34}+r+X{|g~u{T0|X@SV;LjQ^qA$0KA;B3a!`0I z2ZhH9NCRXAC_Gkx!UH6*dphSzPoDmd?@!Bz z$J%|j2+I6fl-q!+gz*}=wFr2Wo!R=Z5p z(4F(&^^YaDTJ;K5u}vGDdcq6>dM~H!nse!q+~Obm4!&%e81kF*z@dL!!iP)~mi+8E z6I<=5JPo>b33U7o!vSQ2tIE!Aeec8AYNk>tv)3U|ZR?2_lSK=;I%V#+epc#J+2}a$ zMuxjv$WE2dE13DZe*8bgc}LETf5p>|#i8=-4_``ydnPQPGYc6GBAaWp&aKJju#sZY z{HHRr=PvKAw|>vdY`XZdy8h>XB4!6y=asWGx$#Wex59e@8>7J7i?+Ew4fb7&WcN>e zW%ILkm%|jW!3-Rrbt9mKQlQ`kVV20J{(N~S*#lM=iCbIpzSC2Q*NZ4PSbO&Nx@|l^ z^o+KzS$1~y`|Bar_fEGI8<)QtxE( z+^+F2Sz=jq=lP!^^YC|<9{n}g#+a~Z!BbmpUD1M7tNvy-&HNpbz58E={^_;yQA?aP zHaNK}&sk|C&$1Y_S`O5O6aX!kL^iiaIXTRKZ++*&phJP~4~4BgdM&q2&pqvZ{;bLy z2GJ|d>}i^}OIyA!oa@cfqH1za)BLXI%DU7m+PA#LSl0S)-MJp>V35H_kquV#{-`2; znm1s@jO(U+mKLF{oEsH%%h@mQ=kh)Jt?kyb?^lEK#TTD1n`fTKl~BLhw8q-0nd9XC zO|^@Gp0|hS#vlN!*95vadDAeVy#wu{)z|p@_|7(4&{8TdG zc&hUCf?b2*5#8*((@%~j{n=aL{@^cze!Q_~t7HAO$&;=ztYcx@0XCNfG;hrSTFVA9 zlOX_hB0%rT6^1AV>Dv+Jd7e-K_oB zD*3*7SF`Y!*2?hp;pNYzA4I-i4mKFl4L&iwaV=xG?@8nU*)fBw*F5|}rkFrkv9n>0 zn(NBv@61#6j&q-uw23@-sARFKrp?Q@>_LNvk(g7HT>OVmYB>he z&#q;Z@Ja9QTNW-<{!McAww$#ezFaWstkBUj?XSuXVpdnFzW-d9b)kp%mZldwGLye< zXE-QsePhPz$)3-THXN$qzBVsk3(1kEr+2Oc6^83Th2c6zZ^pCJE!Tqz!}XxTa6P2= z0TOtzo{?|5-v&l6Q14>{s4(0BDhxL;dNW?0?zs_E7;Xd=h8rQh50JnQkU;DvNbh44 zs4(0FDhxM6dLNrXh2dsUVF(hqJ)Lt4s4(0DDh#(kdLJNx9UuYOt&rZwR#4@z6;wHZ z1RhS8+y<%~wt*^#ZIIpvNZ=P3x6%UVsFAcSCv~yFuZx8x$TOfzQ)D z_khA<4=6nLKzbh_fgd1&*u9Y6$6io)>;;9#K1lClA1FNbfx-hM@OwJveo%Pq2ZhIe zNbdtAumdC@djQn?V4R+H0Mz?901A);klx2ZP=Fi+1;{~2?*k-o0wkb&2-5pF1PYKt zpa40<=*LL$qPmT2&1N6-CFV%{P~dwr&CLDVlj^d2J>m-Wo5Qp6 zuRh8L;mY@0LZ_l{jTANS7s)|hu` zN)<=^$tlf^S?d(c&onL5_1!u1Yk%k&&uzW$T@sI&WcB=9yg6S|e|3TRobNS#mk+Zi zASF)D={FBEhEKn7n31=B!_*(@SCtmVNXo4{8F4w>|R03=tj8 z`wpC$xa~^5!{(I5y!xD%P59Tn{&G<)*j4Frk%bVFvD~1k1E{ZA*56Ckc_?{i!L5na z=}yo7ot6FAd-L`5)X!C$Kg`~)c>TM~lZmS?cNa-!+=_a2XA7smw9=?257gY-7QQ&U z-zc4D+pp=hM;IlhyB=i(4QNa}3Q8+S8F_s)R$OYmx9;j*4$ZB`E*RARbCLPe3>%Rk-m7-RVK z7e^U+r!PGQ?%sU_3CuXg$U8msIHNbC@brbpK`HJyBky$96O7)zqR3uU?sSS=D4EDq zdit4N7poDvZk#~CPNpLTb^AsrIo@C_pIiK}N`90emMd2sP z4`L=iJ^S1Jr^K{`%@X??_HI?Wt6QG^YJzW9O4lwo{x3(iy?ya)9VK6ea+0%jN#LFoMPnlxncYI!KcNG`fAUJn`OO{U8^uF zs9+MqhKk|%IPm|`E%zggT%VemmsW(XIk2niqSJrzTVLKBOnUnI+aj5she-Cw zPnSFcDpXE`vdkGqZ$`!GCqV)dXBc^>>z)PoSS!ymhEG3nhLN{^)@|9Py;&zS7_5Kv zX*>ODRGjC&v9<8(?kyd6Ok!f9+)wkwWrc1&uJy3NCu*Y6nHcNCQ#5XT`CZy4BX>kD zkFy-fVpZe_I=1J`Y&*`?jGHu>CNGJ3YVKZm(Ll+6OVs@ER{|0&Gd%v;`qva~Zk61w z&Dq#Lq4deDX1Nw#i3JX=C&S8LD|_AAGClV!qeQ*mDX;vij;~HXnJ(<4vBt2)>G``A zxyeyS#ZTruKX6C%F6;NLFQolG?qYu2yER>JWzR&3o4gxN91vRjb$hJkr(5<&&I7F{ zhUO3kmVG{9i=PGr&tYv-{u{VIXzg3p#J8qj=D2ow9ng(pe(E^u^Y8K#k6a{|cocHa z@#=8)(h^nio5-={$VDc@4>uK7rcakW$0$)>eBHQyWytSatlwBevoq5)7dN=fe*5T@ z;Ks<=?wgYj&39e8Az{WoX*7TBtkh?am#om)T)9~I&h5s3w;u)C9nO}#@?^W@@>v(Oj=%Y| zkZFUDhRjdb`W)uTxqj1oFEUC@Uwetso6&gs&r6^Z<`QTC{xW#LWaVYX@aaE50=!qi z116DIKt;o4M&9~=Petz>t=q34bLRbet<85Wb2&0!R|ggO1h<_v(tOa66wFlM-=oDX z*lv_`joqx2H!1qZ(aG$uL_Hi7G|sJ;eGcxtuz>CdW3WUH@9!E{Y99K&=en`6y59c> zOXTmXM^>h{*EB9GnREC)hxjYL3N`8H-~QMCSa-v0(L~GGMOQ3+`uG$6xbuGJe*EJm z&rWbqF>rtys#eGb2UHzXtvkPFG1r-oD}+vIXy_-$OqRNRCe?1%zkIC_DS{PT&%`bLl zUpi>t1FW}YgY01Shwr{-zh-@X|Fqw{;NYlBQ8DkH)NWII_ARabR8jqj1stc-rik2W z-Dk1z%66qc%h!KjYTLPcc4bvcTt&+^51zz{P#1$j!WP-!+UI3``Z_=QpM822w$#Wi zt7?(D*YhdGU3bh2H>-N5@-t2CTc!8-&bQ|aIRzOzzj*4!KT=oGlxfMU*>G>sZT{^b zbL$x(O?EqEg9D%Lc(DJ#)}4Lgo2Kh%@%+Agso|H2g1M2P`-uflZ&WZxnU>!FmUQJ{ ztXoBFUzE+F;yI6BXdY0%y?Jud<$M1>FoO(cU;uY$?UBv>^QrRN2h(LGa+)jRW-gI` zaA)zG`lcy+QiO`j;;ubn>wda^eT?h7jVp4s4lv}J`u`B&$=$lo;Z16@$(;*ocB(1R z%`@OM;ec$g^1*FR*)7+ucX+G(>3r)RcjBnKz4x`e^v|WH)+@JPSYtQgGrz}*Tg^+@ zg6h}A+Kc`y?MN4NWotPsAY>MHWAR~ru#5lkPhntSaGZYfI%7Da({#lfjNy#V(eefmw1fW}Qmp6NEX7=0K$r+3~0wS#Vg8l|_uLw=UG zLB-T9P%(9z(VNkC`b&_2#cfbAb%)WL(SQ2PJD@fLNZ>C>AaJ_pT~I6a4yaLjm(iOs zc=}I}fX7`>j=l#T@>_Wil%qicy!XLFev$VoX+_GRN3DLHA){adNW2( z-w6`ncmS%VAA*PcG9QAfX^_BKkU;!&$w!Rgj0w{VK~&=OlOQT-y5eKTaK_~6l^`l* z`b7|xI$iS#V>n~l^hOYsKK&+$%9w8Wlrfw!b9yI;%9{QVL}gF6e8w2gm@|DMh{~P* z@)@Yn^$g_o=it%3na@F92MPQI2^500O`q``n~O^goWWjNy!HrcVS>Yp1{b1B%o?ph*1-9+I2+7Zj-=fxjSujnh5V z{U=Dk;~ywJ{|67rt^5y))PIb;Q+b)Z8MjZ3WD1|Y;y)wr)V)lg2|-Rqrf|kx(-T3| z?&&*0)Sl^rOiba7d#7iDsD0B9f~ft|C7GGR84pY^1W^a4p9E2drYo{Ag)<(8q^%Ro zOuW-u#=4`eEJQLz*~^O z>FJj2OyScz*qC^y&t+!<^}%0)1T5H@c&GbvFo9+OXL2xwPk#Xt_zM!axZRVJiJy`2 z&i08COva4c7w9l?FijTFW8o}@UMN|+eIYMXJriTcbVUKC=bX3X!1MAAkGBg7GTmnZ zEA=n{FRO0|+Ab)@)X&Iz!jyr5Z#tti(|wTi^Z+g<4u}QWOpJ53|CM6$X5@wGx#z{e zz_UHkmgzb(Sh0Zu69?@1js3?At8{PnG3q)hCzF~qdJo|GcW9t9nD$>2EpkUIhnLU)|>>9O$(~ z5ce))V!Shbq6CvZ$Rbv-Mbi)JGZ{~QpsN8AVyvCMSD(q6amn_d`b^5uI0^^Lh)=IH zVp0Y%S-}_Jfb~sZpu=#OZ))7G_{z0BxiKg+MRVVi5+&h9;0$A9VYlC<6oN z;x3R7!vv_97z6lz9I)6Vs9tf%?LOeG`;(#SBp4VNK$}593a5fLG=fGnB|!>7E&*j5 zsAee!28K=0Km+Zd1gVo|U|?7X6`KK7C&R$N0J`o8WZ+Dwm@H_QFi3!bfngR@Opbwp z!5pf0Hq6x}#6TJt z7#Q||Iu{^IbQl;IB%p$Op$c^w7#OZFfp2(W0PQOUJ@Mo|0*F)%O`LIpwld_l}$1_p*w5CgPp zA8KF-0|P@PRO}B_ER=zPp$;kr+7b+6hJh}{12GsF82&-kg)=};bYx&)_zx9}0I37T zF(@U2_Hu&sM1qcfU;v+h#J~vMf*HlYzyP`^4V2=Tq5DFkK_weh9cT|SC_ZBt7#Lo% zfDe&jU}FZ?+YGS`3=AO4L3(ANdO?XC6wTJ4_?LwW#zPYqDAmHYA18n=Sz`g8HO2ti zf(&vX=;r1l%-|!h7(mw`fy6-fq=B}7f)W{McQi;0bSv;}X7E8-3`$TtK({j9V20!# zWw2O1_}nS*rd5!OK>M0OnnAaK@`AhsDqO${!RJgd@G*gp31d)$ih(ZXjDf0Chl+u& z-(!S|X+XuY7#JATn81hjfVV`0>;T;=x4Ryy5VS8EB$xxLB%os2P|cw0iw&S+uzk~@ zJ8(hACxFrfXg>i+Z$1M91E~KG3T{29UQo)n0!cD3Fz7?YK$n18L&fS1pn{-Fa6u=T zfZ_?Z0ReP_+-guu1X_-R^pr4w&#eO8E5l$6)msWGzF8O;K+y%-hz(K)x>XQ#LI}t_ zQ)qhtbZh5sMg|5@s98Yt)-zNvFn}>gp(R9+0d#-#b&x3x3=CFKu_~y=AV*n4#i|(? z7(h#jL6+G-#cCKB80IoEFo48tp<=ZR3=H$3Vs=omItB)Y1&p9#4s>feR1lOMLFoV_ z=>Qc2t?&jFEFd}1u5pmNCV&cZ1_p*152xR{#-ui#kHqa(v7D)4nlZAmH zlbL}bhnayPm6?Gdk(q(Po0);Z4|I7KGo+;hGSGaw=?x}zM#JfeH<+Rs-KOuo!E{;_ zbleT7Rk4AAf#Ec0n=}Ih!};kmZ!*b?f;Q@IVqjp{%)r2~g@J)#D+2?=w&@pdGHKU? z(yuBr12}zx(jzGSfwudC+8v-~259fG3=0E;EDHmJA`1h95(@)^G7AHP3Javk!4GQT zu`n=z&hY>(x~O4>9L{i>i2+>ifezpK#K6Gt8B`vE%1clc0xDWSr*VK5Kx5ysvtlI1%b{40&VTR1G@eldc+avFeK3VOotd47KVq!1?*P2h67#Kjc zu?r&ugDWEg18B#AJ0k;w2O|T6CnE!c7b63MA0q>UKO+M};Pjn$m_X~xSno2)Gv-fM zy~`A>3Yr3OVuTc2ptFdK85tN%85tNr+=1~UT#s5AkU6iuKS0(9~KsPzvzTY`y!0W@I=DqANoGB8YHWMG)g$iM(veF8eY zC7OwW0aTuXIy0coOB$%y165a`mLlk!WKhdZj){Q*bg~HO1fv5?3=9XE7#Kj!%e_nt z44^JYITHheITHhe1rq~!z}=dOfdMpl3mOk~W@2FQV`5I<;seBLf4dPzD|31S(uX zg(;})JUKn}K9hVsr~v>v?*r7Z0M$&OdKOeqgHAdC9isp`THzEk14A>YT4iKln8nDz z0P0fZGBGgZGchoLx>BGu9a&5a44|`(_JZP&iGcw$y$x!lf=+A#oh{SO#J~XRPPH&W zO3-(V3=G#nr#68Wnt%?MTFJn`0BU)F>hC#>3=H!aK}8+|1L!~+P?zf?BLf3yA1|mb zmttXH0Il}`9X0fsk%0kpb`j{9p|7A+#sca4WivA{fckczzMKy;`2JW1P@56dwBEqN zz_62rfnh$&^tuO3T#V@6PY2cSWv5V z3Nr%(sHz0DU_dP(m|391YF>g$2+-{fAlLgaLRz4?j0_BIj0_A8jNmgd!MpWM7#SEq zCzxS4Zo1?nCi(gW%-G`tbhrfQ+zQaCY#^fc0%l|1gpom- z0kkR*)CvIA`XGrD4?)Ec1H+$h;M(35ltDq6^Dq+wg9j4>gCn%C0hM4}(2RPIk%8eZ zBLf3y&g%jr1H*Yn1_qE{L1hRiLvNez`h-c{auFk>onHnjZx|RD_A)RqfDRK}$H2e< zYUwOtU|^WQz`)Q4>V|_(dt-pKY)Yp`zF@N6{@@AI6GmB3kF%A5fdOOw#qBmJ+BH1S)et!XOM%)(h>N_JF$Y3=9n23=9nY3=9m5L9HmLy|bZB zeNY!*255kUfq?-u4Kx|l17l!dm>IR*v>P`h#!)DnI4cn00e1u7V1 zL5U93(r08~098mzj0_C<(8@}gk%8eIbZ7(AG5|S3l97QS3RL@mYCi@B22cwN)WpjM zRR9bO44{flb9&)RCgJ)NMg|7ZnwCUH1_scoCJjc&Fj_n#14A4m14Aq*qcJit#4s{2 zL^ColL^48#U?M;*7f?L}>IX71Fo5bzP-6sCPH{3aFmN+MN?%ap1azVzsIdns_&`T0 z{s(nnK^Ym;nO=14te;&H$>VKt=uw(1;son1+FYK@c=f@D$V;g&trDl6=O% z!0;TZ092*DWPo%wL8Bj_fPT-wzyNA1-v!kxP)CBg8=wFN1wN>C4~jI9rJ(u;Bo1ny zg4)&~d5{^PHtQEqI~&yb0Qm^iI0H$7iYJf;5E~Q85G1I zpRs_ZZy6aFK>9)YKy7Fy(BUS~mpt)p0Mg|4}Mg|69 zMg|7ZayF1SOddvqIsu?`b{7=?AOk^;PzN2J2(=KDra>+QrF$tx1_sdTXc>$HHQ3V=B1IdA^rYoRxo}e{2C{c%jns5vZ z3^2=sp}9y0G)@=5$iM)K3s8d}6#t-j26X^HAp=U)AP0f64#+Z4f&yhBP)-Nc#h|Ez z)rp{{A*itfavUi1K)D>$j0JfD#0S{{3K5VVQ0D`bi0eV|0P0MDQYXv;P+kRD0`eIs zHG|qAj-WOrv}Xl6vJ~VnP_G5ldja)aKqF_M-V3Nl1L7mgf%u?ajW@KY28A-Ht#b<; z|Db{d+Wi4ZfLsT1Oc1F1!^i+`{D5*u2qOanXxI%@I=V29g7*1BC*J2Ju0@26+&qM+r1e2a*730!e~`1r!P(--Gfrh=%b&@*qBF2p%-^ zkqjCzfR=n913~IQ4nr0Pm5LxakohoqkU5}q0*)$B{s*}Zq!1L$ph3h;XekZT0E$1D zp`fN=9<+!q0R<-%gA(slMh0*bX92Y11*^J1SrF7AYedSn)zGS}zKW57p@NZt0krH7 zRBwR>A{!VX1IVBO$XZ4Qh8ji&2GCNRRz?Pf7Dfh!W<~~vCPv73AgH(3#RxgB7Sv`1 zjkb0$GB9*9GBAKTa-idBK@B;OI#34(bPO%1XE=iqJakhJI*%6IfdRGNKx00jUK2gd4aK;j@V5Dn4;V}odr8c-_`6dE8thz9NZhBO!u?FCSawtyiU ze2O#!$mgJQZPzeDdP^WSC|!WgwguJ3pe7}#B@5~yf_jSU7#SEgGBPlLTDTh+85p)O zGBALStp$xKftp~T1xBD{T%hhAsH?Y&k%3|N^oJjq)aya}TtM1Er_r8eWMBaGLeDTV zFq~s#U;xpeRx79+gT>lqP(y?ST3}B~%I(MMXU^}#-1sYQWHMBsB>_CUrf)1_) zwbVeJJ!JDPgL;vmrZ%Y61@bdY64bH*i6e`F900TYJ}8+%TY8{LM^KlY6>^I`xCslI zQv;0_f?Cy}p-GU#Kq-NRiGkq_C=)Y)4*_Rj22IR?M(IJLc%Wt#sJsKk1bAST3DRZ; z^>^PgGBAMp?jT2i3FJg8p}>feJL4hmIJSN9ns1H)5B1_n?B zg8KX*wVY&&Lc?8jpt;gMv1r3P62K$+q7#Kj~`JmBh&`buXmjN27gf-+r85=Zr4zdK)Apo5} z4C)wx&K(As14`qd#h;*c#h}ICpdllWnq{D35R|S!N24yA&1|=p221OpaC+_(n-*|OwfQCXhA7xwW=Ev z1A{9Q0|O`&iW#yP7+jbb7(kluJM zKk^I=4T1Y+$SG`X-O0fiXK1Nsq-Qie@E6l8CNqZVZ+|fvF_|(%jtQ)nZ)Z&^-LHTK>p%mU}(6>Z*%_81J<)Fj35<; zCJ@K*GcYuCtIe8!e&LIG2pQ0Hh5+bV;Fm!wU2CRCAY?$p{elb(4N{(A;;BrRCn02h zGchm-F)%bF*fUs5pTD=9h0zvf5ic_XgD}XVDwY{n+>B)rn!H#U7!(;88kRe2U908Y zRmsF?!wOmLl|9|@50fNg)AU>sW=Ydotl$+A4O33s-W4~myON19&Jbj}0mEKa@Em%B z`p4>u1KYmLU}9VkHR0Lx-6G7ApwlL@nf0bKi!ujGgI3b;GB7k~oDtsf_ssc5CdN23 zJy19@@J=rjWtLQm?araroRSJ&eI?MWipobVPjwbg_+wL zo|p4yJ3MA$j5CLr5Ix;dj9HQ~b$YZIvoT}Q^nMVpar*ikW<54zJtGE&-syM6n0=>9 z{AcoGJUxA_9kV3k_34fOnIst>PoMptNmBX~8>p+!(D3AL;PK42vT;nH$TZY5WMB}P z8pteZD#rmn6S?6{N>u1wn*%OPjByqayEQq$Ydad&D+`Di)J7U|{g!0QZ=06WV7i!t2yKquEjwVDk(?9=|hvF(b1v zJ(c8lpp* zNf;6)F-LV?NY<+Rfn9G1vY>Z*A`7!5mKbC54> zr>_U8bDw^ah1r;K=JdZH9#nAAbVF8VNoWF`K24Qbbb7ryGb`hl>GiD4lF|iYkhPgr z4%&~5bwjujv0XErk&PLYp3Z{wf118lg;|mjDhTC?PxselmYnX##w=yJK@#G+(1bVt ze@#gI298-{kn7Z?AOT?e`M}0g0;U<@WDZV>xzp#eF-uDCmx5^3()alDBUMf%{*H zce*VHBE3(TUdX|0%xFA)DF?GJqy6;H9L$Zdya3L6!t9_lXT(4-10rQ0F%|;`25=rS zX8bpOEf=#SBj@yZ5G6Lfl8afJGhPuAJrGIk`3#&?eolYL#VpChrab)*7qb!L#p(Lo z%*Kp7(`&hzB^jlrPY3bzr=R9#_GMf@U6O~{n9*#yFAuXXqto=+Jj{~PVQP>pqRk4wX8H@BZQu+G_WqvfZ+V#|rO#-C7s)jgF1yQ6rY;FeYLJqhA!)iIAG0K5 z?eveC%wp36`Iu!Hd8g;|F-w|u=s*(J(rNSFuqq@tB2wQ}9f(UMAn2Sl8g{@ryt;FmSVJ=ew&}!myu_>rU0`sqr~)B5VdgnL;+?= zM&9YGK@`L{EXE8B5Q`Y+Pqz=i1Pv| zjT!GuXG~<4oNkiHEX4>edPJrR3Ngz|zcT?ZJ8iJ;T6D)q@#jHs76F%&+|#{HGOPV#ZLBi|JkEFZt!WH9sIB^i^Z3wnd2GgFw^7_?VLm|0TV1X`bd zd(HgVrPxdonsAI67-mer2+|C$cO{|q>2x^}W+~~B*JxmbwuYWfUmeG+GrdrpS&~0g!wM2vm%^_;y8mgy z6hw4hx0)u&EMbbJs)804W;T#wPMS^AeI=PCrDxedT4#KQ7oY4r zVghQoK((%)-YCf|$@Ir&`W#7c5qDmaS(34G`b$Y>U&fa0=2FaxOw!BjAt_DyZ<(aF z*&|T92GlAuV_;Z6J<)<$jKvhRvZYssS(53X{q!?N%n~xE?IHD(6C+bf`eKe*;D($b zsJVO9e)=65W(k@5P!%kVT26a9Sz5vQ+(6IJh~cIEbOBjr3DeI|6>m4Z&<$Q-qz^4b zOc)scLS@pDjvT#PFMS0JAd7}l}zSPH<&RncsW4g>r?!k zfG>Mby#WWhG02DE({<&TC8blK3Oa6wR8QN#J`n0iV+Mx8>4|d8l8p7!t3m3pw3saQ zOh5%5xPdFi0&bcHIxbcI-^gaVhBcKq|GhkRfeWN|Iq}gr_vs_wUOxu~BCwa1Ww!En01J+q{=vm>OlvCn3C{kN=d z#bE2e#arlfMg?X`#`NjZ3e1wym5z{7+}bVu%889ahd>bks+Y_ddZzn>)XksnXuvGO z2Ce`$Pv5A@3=WT)>2npBC7Dh*PBUf}Wiex505=UKneI7Gf1|)GA@ki4Qo2g-y)M76 zp@tc3EI3EJGnuZS$ZW(2PPvkdWz!oSAWh01X9fld28MU`xd;Oc)rRxk3_Xt3XUnP7C84hyy`wgO9F|D3lZu$_Y{CzXH{5z`(#by;})f zK7Y()mSo&NeZLa3Hs>cd$lf1tvr&{46i#*1d6k(ZrMulB2Cb<4mv6j4+K~m+fHwpM z!s+RbAO(6J5CuPeRc9QEn6i|KQQQO4O%a(csKP865bOadS5hJmADYL%fR%~S&I3}A z9N>g_G4%2qm8yJ!NO1cbWHdvm2P9h3QX4qHH4u&x#{sL%#zY2o)Ft3kC_F%^7;wNV4%jJ0RzM1>4@gDFXOc7zg3ux zp^b4^EJ6DU(<@b(%^AVz#+VTtM3PMZ9Hyx;8%gu~Kz+YL^7F#ENgu&60Ctr8^od%` zlGD@Fm@^oir=M42mSlt^0mk0xfsV}L)1}mzWuW#=4^U?|0VTUOb!H>8DnCfDI2l$Y zOZc}af>S-XP15WKNi*jLqo zods_4L-MJ_^bieZDMse$`5Mf|FbD0@V4lbHJ79X0CbJQv)AU|VW=Y{0fsm5xC$G|0 zQHKQ|KpUraYBG097Y9LXkPf^R@Z!}nHLwj}kJn6()nb;EZVQ4$A(LH%l9jK79W;R( zFfc$u24cA+VXUQ{^^D~ z%%FHo)nPUUsh_38Yy_gu=`b5fAB0v4dwk+*7&e8SL}VOrnJ+e7P?cF0;yhz%j&Mlq zKT|fpw|LL_#ZW&QGBBX}YWf9TWGFEalG3^1kW3r0tZJQHjm8p0t+H);upaYl zMw#jF^_YDbpG~*bXO@(?W)4x#(do>7fAJzn4-1?c9-2=t(r4bnxM{ktF|#BixQvs8 zBvVQ0xh9bKnH6yHQ`EU>klGxaRJ^A58!#KoVC~2o>6tMwK$6H7>1|Ml>hp6X$xqz; z3flWM0kvuOD=|w>KW5162TeT81`H7UrSCa1Fi0{mG)!6e;k;Wvdp0$ z!ZbdffkB#qq2c#`m+xsSUps+Q38++Lz+F5e2cpRI{pQT9j8)ScLE#F?QjCx)iLrh9 zd1Gc{=?U?W;;3lT`Db00w!fh6H)LRdRJW`~44^!zpvWx2xMX^u33$-SUzb^QdV>iw z3#8r;mlL|F_O7(%B{G-no{E@#Rt!w9a~#HI(DGMk%jkB5}F zNxe;`m(C?}KwV?Nz;G@eQiG~lK2V)HqiQWUIfJt)xXv=3{=t-4igCwuaI@<{{PY7h z%o5Y>%$Vmu3PedpNU8^Qh6|_jn=?x?LUS+JZQ?8j4B&!LLfQ~oRlmOcf3K>_^$*~( z931UnM?;e*G-Sa=sU)NA^!w(_lG48uA!(r}OILjItoiq$ZiLsF&~j6{B?+P~yn)j$nNFrm-(tyZ#01GRV$6mNg3}p6wnHju783>r zY_5W-pFYcqSq2)U(=S*tOEET2e{IDqDUG9Pga{Lt>6X^a#*F#XbFG;hVf7WHDI)a6UM$ zVe-?`u(ajH1)6oxsTkOkJFw z@5C%=s!{-{zL?jiiIl5O0hOPi-iiSOgJA)rRXBg9+1DjkHTHlq8>k;-z~DE1uM@MR zbaDZtF(v4-I!xxar8N^{oF%w7zyR;xaXT}o!zwkV=fTtaoS7x0YYQNCZK2OT-_(!d z@4&?tI14#XKj_Rnn{ny%SQlnV#%x0%KW(*8>3#YGf zVU}QeQ#f59j#+{+Y5IMTzIoHVbD1Tl3%N2&F)p93>&k45XMnS7dbKO4&sPGe;r8py zzGWRM;RW)no{^q01H-E6>s^_R8NuV>V$*-RGRw-umO|=WuDK6ywXi=n24^*Jx%;Se zx{({R4`bK#dN*cC)5T>FpSH7W&huToX*t+bLp@6dhWBNV7LK*NM_kn;FHrm05HvT! zz_5J!SvO`$#u?KKJ()$PE5tIhFfNqSg>J7y&n2+NcuJ)W{O8pHQ?- z4%D^=4YnIGFgQ=Y2(ngp`fCt{>>$bM@`NlXiEXHcUz;Li> zdYuom1S7}v=|0TqOjnzy`7%q?_q9O!7mJ&Izn@+1V9CO0sAsMRnhRUp0!bq(iq{XG z-oCybA+xOolAB^1pI*J2w0ago=2Q!$3hn6?m)sOqwG$z8rv;LbJo|Y0>~3aDL1jQa zvrkat)Y)P>Zq40)2P$R4z`)iD(G+vEUGvH7Q(qA>GSkodGK2ed22eGu+tLe7s^cah z)VQ@moH_mVrL-F6HP;X_kg$@r?Suq(^_%pw9YU{_m>3N~gT>|y42{zR{g@>gyQXLR zF-uCH?}TJQ&U~9=uL5W729GKmndzA@bWNX+UqSZtiGj?LjL)XO^?6#%XN%z>am zQc$N7I?Dzb1C;}ff$BhN?tMX-hnfCeo&c@kjTsmq!=j9=Kpn-S;1_tnKAZ%KZ5jrCY zZU{<3Cl;~ID1r(d0|tg!)8#|K6D6RTQ0XRC$N<9L>CNByCe2DfR32W_b3y9%PHzuo zHbyvqE@=1^GKvawH=f*_H2ooHDy(+;{0wGEM%U?uVc-GW?l5L!Mo0=`gydmHrRkr; zn9~^{8l;y@gcR)s)A(QTk%*)31rs?}amKDR)nTD8$+W1-ELE zC*|P&fP_EM4p*NJa`AMJNai`Pkf@#hKayEe8cV$m9j%5$3wo`~=r_GF3K9HR>TYm) z0u4!>k7724l+42Hpd!qGL1nsZG_#~BmYN4T$NHxNQhf?k6mDGRDi3OWnnO}E&-9Nm z;Nda`ndt{(nMK&ZX&hW&Luxl{6_Al0I0T)iUyf!rmR6Vw$yM$RAJ#N^*yez%TX1_} z#&pFPuv@{kjpX$F7|>u^C8X%!JiK+??U*N^NlIu6M0Ej3kF+yR#bAe{)Wdib>rS_i z18O09J4Ph;b0qDpZ+JFS&9)ng((J_#DUIYdQA6DV3x!)=?R|Z#5a+KGM~c; zY14qFbxa`BNRo^W)B97HMVSrsjHk~|WR_-}FnxO>voRyIP>z`msf%2`@NZpxazA)n z#}Yg-o;O`SiCNUrNY99&ZZ;&*1su1Tvh33=&{UftWGuK3Ds%tq3VBXG3D5)|R0hdf zM*(IDM#Je3f|w=bL6LHMHl#L9HrZBTbv~R8l=UI)Su{5c+#B4O(CYd=y z`Zsj)KP>RHU!uU4X%Jfeu*Ja78F6lMuC+$re7JV>>f_tWx+Z&r{rxX}%6 z&D@zcT_=^9$r|YLN8zU!^>NI9)rX35X8x%83Oy7{kEX8zu;q)_U z%n~xU7eZP)(bC7aRha$*4d8(0T#XnQrYxS8&MX2>Evpto+PRJQUZ?tBwv>Sc9>|ru zp)&LOepa3`H~?BVU*IqyPoE}lcYXmvX3a`S zKC{>E9lANpppwx&2Rv>ITDt`+ zyumB4e3|^#PXCv~?7|td7Si4}EI|F2 zj`h=-@|YWBrmu&@@!QtmGEc!9lR+v>^bAe)j2Et-J}-}1f@#hA={xe6UohHEUzpDf z8u!|r&nzk9wgFNO>G8j9zxL$GbP+~7TSH?|6(6`^`iFdG3A5M@kd9Vq=DN%7u5O=1 z7(s^>fzF^n_Yp!SYr}NA0%i%biVYAyfBdSms*PpgPmt-LLw*<-+BQJ?*n7`xb>o=n z7LCw5b;I~L~3SBjtI!lCZH8V+c!)<05bjb21upQ5FgXezW9PF z$aK&KYX*h~P#FQ=_~{>m=2;_5|GHs1Um>%EG}lH*0%J}0%D&I7{Sc%Vv@Vx{L20^U zA+sc-)%55>W=W>tjnm5tnSEqdZG?ov_Ki=&maA?6Erf#RhK}{q?-epjFm2yBU9OZ_ zLVCwSh|eTXOys=Vx?wdFqp_YLI3wsz*Dhj~ltGVfCPo82V{<(VV}{c8(-VrACCrX( zghcm=sAo^2&c*!$8DR=G;^sz(m$wR;^d5YAL=4okg2=qvIDK6avjkJ*`spW%n0;W* zffdSTxNS|@3@I2N@w)qcbng)YcZ@9c%o!NoZJb_L%xnZRWBRFLW)r4)Tc(vDyo<*- z$2Ly)D`A$9!E|#Rv^TY8%k-R5W(lT)Tc*z`VfJBcoc^$$S&Y@tfB`K~@wlE?(N~og9-yf!}k;C&IGGBbRcAQZJ%ya#w;Oy ze>?b`rv|M>3wP|>b^97Z1>5xeGG<9Ly&d3ln;Q0ol`YVFp?VRaAZ!N%gEnYY>vzXx zXsWy*S)ZNBjvG*4lyX9_BoD)vubQ^71Db7McGl=j=V$-b=qpC&kc z7=sF*_xqpO z)BUTNCCzN03Rb&i#unMD&p{{%g39D1Ec$1(KpgHI=}5Egg;j!=O$V!(R&r{~r% zOG+z2<7d&zx~z8`s+SO2!4U(Rj5e%d7MXs&hM83+8Wu3@0n^=HFV?66>wSJ`dQUC0 zg!J!2kV1E!w85^+FO#DXdIhHMuVt1rQ-cOOuei(0dF#rKBNSkbDzM=}ho|$^F-tII z9G%^QQ(85lA!a8AEl$1Dl6gi&s~U_G-WQ_7|3 zI`zyFGG&(_jR*T>FaH?H<$=}?f$A(128M=9(-Z2MB}{RsFk)bkxD08$6nX7@r*lJh zH6pWXPG4KkYz!&X!B+pNXEtJ5c6GWz1E>_f3aL;nG{a*KACU#E?*T1WGGSoYcNJ11 z1PbX^@?Xfd1BIrso}ncJ!-cEU>l&CPOrKnZL`W_p=e71J$qNuFeqM#-9sRNg6F9Fa zu0_c3Pe0wjEGeye4N{nuD=!kb-&2!=P+&9te*-gUoDHdXG-6t_WqL*l zW=W>Vo6~cem?dO#ZbAZ1joq%uY5ONBaH|~DUu3AgIekGBvk{}q^vg}mzN%ffA>pw^ z>t1)Mr=}OU`~`K283Gz1CqZ6lWS;&ziA8F9d^2+;WAyZ!&CK%Znf8!s&f&B|wP=z~ z8Tb%O1_p+tc*voKTsEZ^b_#~m1Aa0|Y%gzNu4bD4MumlY`rkHY0~P~4LzC$S?ab?# z4fG7BZ@j~-IQ@PHGtYG14rW;tA=c?Z9n4B-LUTHp6;OnfQFyG=wK|z)Aa;0mGRHF- zY+u>QTq?gkWhL{Sct(@$j+`tqjMEu!Fe^-#<6+^P-p0kEgT&S0X5pCrn2SXLRSa24 zBR7i%lI&}47T)Rixmo1lV!Ch^|MV&z79}{BbNX!_7Ov^%d01qS#6);mv>;r45f<_3 zrMxUvjAqk6^0GKF8Jcf501?8 z_!&*6A5~y^G`(p7v(ohQvMj9I&ndFhGcuZN_fuxcWSst4g{6bhaC?m^ixK1Wq)W_7 z+b^iGtYMtqB+a5cT|kG0d-^I37Jjfrdo)-|z!n*6vQ#4}xTDD;io})IVi5)@+it4G z@*QF(#5rd)Sh%LA=&%Sf8ck<3U{Qh?zgUMwcsk>BW~J>nbXc}AvRmjG>y@NeO;^-s z5u47b&%!%>ksgcK^b2|{;?wk5*rxmIv#?HIt;eD>{el4t>-5hcNppRc5=M*Zjf^Zx z)6eO%7{S^P}DwRkC=~tVsk;0P0-;fB>FFidsg~gkD2UPGxBFpsK*O*kN zzer(`;9^LED3wS8>9^xffbv%)u}tSV%Oo>BKb1w%@Yfv%1|0_QrRZRry`7EjZJnMe fu6CCJbW#*(+cQ{v%2Cdg(tx5PZqpB@vMdJxl&j4j diff --git a/package.json b/package.json index 9febce6..8e2288d 100644 --- a/package.json +++ b/package.json @@ -30,22 +30,22 @@ "packageManager": "bun@1.1.20", "devDependencies": { "@anolilab/multi-semantic-release": "^1.1.3", - "@biomejs/biome": "^1.8.3", + "@biomejs/biome": "^1.9.2", "@codedependant/semantic-release-docker": "^5.0.3", - "@commitlint/cli": "^19.4.0", - "@commitlint/config-conventional": "^19.2.2", + "@commitlint/cli": "^19.5.0", + "@commitlint/config-conventional": "^19.5.0", "@saithodev/semantic-release-backmerge": "^4.0.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@tsconfig/strictest": "^2.0.5", - "@types/bun": "^1.1.6", + "@types/bun": "^1.1.10", "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.7.14", + "lefthook": "^1.7.15", "portainer-service-webhook": "https://github.com/newarifrh/portainer-service-webhook#v1", - "semantic-release": "^24.1.0", - "turbo": "^2.0.14", - "typescript": "^5.5.4" + "semantic-release": "^24.1.1", + "turbo": "^2.1.2", + "typescript": "^5.6.2" }, "trustedDependencies": [ "@biomejs/biome", From e0e40237fa6d6e2150f38b71c13956536ac00683 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 21 Sep 2024 21:03:36 +0000 Subject: [PATCH 274/312] chore(release): 1.0.0-dev.29 [skip ci] # @revanced/discord-bot [1.0.0-dev.29](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.28...@revanced/discord-bot@1.0.0-dev.29) (2024-09-21) ### Bug Fixes * **bots/discord:** fix get response logic ([3261294](https://github.com/revanced/revanced-helper/commit/3261294822b0a9faec094536ed5be2d3e1d5e17b)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 1f06149..c922a42 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.29](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.28...@revanced/discord-bot@1.0.0-dev.29) (2024-09-21) + + +### Bug Fixes + +* **bots/discord:** fix get response logic ([3261294](https://github.com/revanced/revanced-helper/commit/3261294822b0a9faec094536ed5be2d3e1d5e17b)) + # @revanced/discord-bot [1.0.0-dev.28](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.27...@revanced/discord-bot@1.0.0-dev.28) (2024-09-20) diff --git a/bots/discord/package.json b/bots/discord/package.json index bca5966..20d553f 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.28", + "version": "1.0.0-dev.29", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 03467411882b8598e2c06f389a09ef2e201bb43f Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 25 Sep 2024 06:30:51 +0700 Subject: [PATCH 275/312] feat(bots/discord): improve admin commands - The reload command now properly reloads configuration changes by skipping the configuration cache - The eval command now sends a file if the output is too long - The eval command now restricts access to the bot token by removing it and sandboxing the input code execution --- bots/discord/src/commands/admin/eval.ts | 83 +++++++++++++++---- .../src/commands/admin/exception-test.ts | 5 +- bots/discord/src/commands/admin/reload.ts | 11 ++- 3 files changed, 79 insertions(+), 20 deletions(-) diff --git a/bots/discord/src/commands/admin/eval.ts b/bots/discord/src/commands/admin/eval.ts index fc67146..9a13bcc 100644 --- a/bots/discord/src/commands/admin/eval.ts +++ b/bots/discord/src/commands/admin/eval.ts @@ -1,8 +1,12 @@ +import { unlinkSync, writeFileSync } from 'fs' +import { join } from 'path' import { inspect } from 'util' +import { runInNewContext } from 'vm' import { ApplicationCommandOptionType } from 'discord.js' import { AdminCommand } from '$/classes/Command' import { createSuccessEmbed } from '$/utils/discord/embeds' +import { parseDuration } from '$/utils/duration' export default new AdminCommand({ name: 'eval', @@ -18,26 +22,73 @@ export default new AdminCommand({ type: ApplicationCommandOptionType.Boolean, required: false, }, + ['inspect-depth']: { + description: 'How many times to recurse while formatting the object (default: 1)', + type: ApplicationCommandOptionType.Integer, + required: false, + }, + timeout: { + description: 'Timeout for the evaluation (default: 10s)', + type: ApplicationCommandOptionType.String, + required: false, + }, }, - async execute(context, trigger, { code, 'show-hidden': showHidden }) { - // So it doesn't show up as unused, and we can use it in `code` - context + async execute(context, trigger, { code, 'show-hidden': showHidden, timeout, ['inspect-depth']: inspectDepth }) { + const currentToken = context.discord.client.token + const currentEnvToken = process.env['DISCORD_TOKEN'] + context.discord.client.token = null + process.env['DISCORD_TOKEN'] = undefined + + // This allows developers to access and modify the context object to apply changes + // to the bot while the bot is running, minus malicious actors getting the token to perform malicious actions + const output = await runInNewContext( + code, + { + ...globalThis, + context, + }, + { + timeout: parseDuration(timeout ?? '10s'), + filename: 'eval', + displayErrors: true, + }, + ) + + context.discord.client.token = currentToken + process.env['DISCORD_TOKEN'] = currentEnvToken + + const inspectedOutput = inspect(output, { + depth: inspectDepth ?? 1, + showHidden, + getters: showHidden, + numericSeparator: true, + showProxy: showHidden, + }) + + const embed = createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``) + const files: string[] = [] + const filepath = join(Bun.main, '..', `output-eval-${Date.now()}.js`) + + if (inspectedOutput.length > 1000) { + writeFileSync(filepath, inspectedOutput) + files.push(filepath) + + embed.addFields({ + name: 'Result', + value: '```js\n// (output too long, file uploaded)```', + }) + } else + embed.addFields({ + name: 'Result', + value: `\`\`\`js\n${inspectedOutput}\`\`\``, + }) await trigger.reply({ ephemeral: true, - embeds: [ - createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``).addFields({ - name: 'Result', - // biome-ignore lint/security/noGlobalEval: This is fine as it's an admin command - value: `\`\`\`js\n${inspect(await eval(code), { - depth: 1, - showHidden, - getters: true, - numericSeparator: true, - showProxy: true, - })}\`\`\``, - }), - ], + embeds: [embed], + files, }) + + if (files.length) unlinkSync(filepath) }, }) diff --git a/bots/discord/src/commands/admin/exception-test.ts b/bots/discord/src/commands/admin/exception-test.ts index 68ac9c8..a9017ca 100644 --- a/bots/discord/src/commands/admin/exception-test.ts +++ b/bots/discord/src/commands/admin/exception-test.ts @@ -11,7 +11,10 @@ export default new AdminCommand({ description: 'The type of exception to throw', type: ApplicationCommandOptionType.String, required: true, - choices: Object.keys(CommandErrorType).map(k => ({ name: k, value: k })), + choices: [ + { name: 'Process', value: 'Process' }, + ...Object.keys(CommandErrorType).map(k => ({ name: k, value: k })), + ], }, }, async execute(_, __, { type }) { diff --git a/bots/discord/src/commands/admin/reload.ts b/bots/discord/src/commands/admin/reload.ts index ada207c..6fcc71d 100644 --- a/bots/discord/src/commands/admin/reload.ts +++ b/bots/discord/src/commands/admin/reload.ts @@ -1,5 +1,5 @@ +import { dirname, join } from 'path' import { AdminCommand } from '$/classes/Command' -import { join, dirname } from 'path' import type { Config } from 'config.schema' @@ -8,7 +8,12 @@ export default new AdminCommand({ description: 'Reload configuration', async execute(context, trigger) { const { api, logger, discord } = context - context.config = ((await import(join(dirname(Bun.main), '..', 'config.js'))) as { default: Config }).default + logger.info(`Reload triggered by ${context.executor.tag} (${context.executor.id})`) + + // Apparently the query strings only work with non-Windows "URLs", otherwise it'd just infinitely hang + const path = `${Bun.pathToFileURL(join(dirname(Bun.main), '..', 'config.js')).toString()}?cache=${Date.now()}` + logger.debug(`Reloading configuration from: ${path}`) + context.config = ((await import(path)) as { default: Config }).default if ('deferReply' in trigger) await trigger.deferReply({ ephemeral: true }) @@ -32,6 +37,6 @@ export default new AdminCommand({ await discord.client.login(process.env['DISCORD_TOKEN']) // @ts-expect-error: TypeScript dum - await trigger[('deferReply' in trigger ? 'editReply' : 'reply')]({ content: 'Reloaded configuration' }) + await trigger['deferReply' in trigger ? 'editReply' : 'reply']({ content: 'Reloaded configuration' }) }, }) From 2ef66fbc87b97090fc52c12e4f58cee78c716191 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 25 Sep 2024 06:40:31 +0700 Subject: [PATCH 276/312] chore(bots/discord): add more debug logs on messageScan --- .../src/events/discord/messageCreate/messageScan.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bots/discord/src/events/discord/messageCreate/messageScan.ts b/bots/discord/src/events/discord/messageCreate/messageScan.ts index 27a199c..e60d864 100644 --- a/bots/discord/src/events/discord/messageCreate/messageScan.ts +++ b/bots/discord/src/events/discord/messageCreate/messageScan.ts @@ -1,4 +1,3 @@ -import { MessageScanLabeledResponseReactions } from '$/constants' import { responses } from '$/database/schemas' import { getResponseFromText, messageMatchesFilter } from '$/utils/discord/messageScan' import { createMessageScanResponseEmbed } from '$utils/discord/embeds' @@ -22,7 +21,7 @@ withContext(on, 'messageCreate', async (context, msg) => { if (msg.content.length) { try { - logger.debug(`Classifying message ${msg.id}`) + logger.debug(`Classifying message ${msg.id}, possible responses is ${filteredResponses.length}`) const { response, label, respondToReply } = await getResponseFromText( msg.content, @@ -48,10 +47,6 @@ withContext(on, 'messageCreate', async (context, msg) => { label, content: msg.content, }) - - for (const reaction of Object.values(MessageScanLabeledResponseReactions)) { - await reply.react(reaction) - } } } } catch (e) { @@ -60,7 +55,7 @@ withContext(on, 'messageCreate', async (context, msg) => { } if (msg.attachments.size && config.attachments?.scanAttachments) { - logger.debug(`Classifying message attachments for ${msg.id}`) + logger.debug(`Classifying message attachments for ${msg.id}, possible responses is ${filteredResponses.length}`) for (const attachment of msg.attachments.values()) { const mimeType = attachment.contentType?.split(';')?.[0] From 59dd80352953dac512ba3f38430f80a3904f14da Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 25 Sep 2024 06:41:17 +0700 Subject: [PATCH 277/312] chore(bots/discord): temporarily fix timer statuses being both active --- .../discord/messageCreate/stickyMessageReset.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts index 59cdcd6..d674f00 100644 --- a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts +++ b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts @@ -7,6 +7,18 @@ withContext(on, 'messageCreate', async ({ discord, logger }, msg) => { const store = discord.stickyMessages[msg.guildId]?.[msg.channelId] if (!store) return + // TODO: Fix this by fixing the logic below + if (store.timerActive && store.forceTimerActive) { + logger.error( + `Both timers are active in sticky message store: ${msg.guildId}.${msg.channelId}, this should not happen!`, + ) + logger.info('Clearing the timer and the restarting the force timer...') + clearTimeout(store.timer) + store.timerActive = false + // If the force timer is active, it implies the force timer exists + store.forceTimer!.refresh() + } + const timerPreviouslyActive = store.timerActive // If there isn't a timer, start it up store.timerActive = true From 37e64a2eb825ab7131890076f73916829f3c85f0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 24 Sep 2024 23:48:53 +0000 Subject: [PATCH 278/312] chore(release): 1.0.0-dev.30 [skip ci] # @revanced/discord-bot [1.0.0-dev.30](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.29...@revanced/discord-bot@1.0.0-dev.30) (2024-09-24) ### Features * **bots/discord:** improve admin commands ([0346741](https://github.com/revanced/revanced-helper/commit/03467411882b8598e2c06f389a09ef2e201bb43f)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index c922a42..dbd3d22 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.30](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.29...@revanced/discord-bot@1.0.0-dev.30) (2024-09-24) + + +### Features + +* **bots/discord:** improve admin commands ([0346741](https://github.com/revanced/revanced-helper/commit/03467411882b8598e2c06f389a09ef2e201bb43f)) + # @revanced/discord-bot [1.0.0-dev.29](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.28...@revanced/discord-bot@1.0.0-dev.29) (2024-09-21) diff --git a/bots/discord/package.json b/bots/discord/package.json index 20d553f..4540976 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.29", + "version": "1.0.0-dev.30", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 5b4965dcc7285676b2b3b6756c249bd56eaf8485 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 25 Sep 2024 12:43:42 +0700 Subject: [PATCH 279/312] fix(bots/discord): persist changes in context for eval command --- bots/discord/src/commands/admin/eval.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/commands/admin/eval.ts b/bots/discord/src/commands/admin/eval.ts index 9a13bcc..3aab45d 100644 --- a/bots/discord/src/commands/admin/eval.ts +++ b/bots/discord/src/commands/admin/eval.ts @@ -1,7 +1,7 @@ import { unlinkSync, writeFileSync } from 'fs' import { join } from 'path' import { inspect } from 'util' -import { runInNewContext } from 'vm' +import { runInContext } from 'vm' import { ApplicationCommandOptionType } from 'discord.js' import { AdminCommand } from '$/classes/Command' @@ -41,7 +41,7 @@ export default new AdminCommand({ // This allows developers to access and modify the context object to apply changes // to the bot while the bot is running, minus malicious actors getting the token to perform malicious actions - const output = await runInNewContext( + const output = await runInContext( code, { ...globalThis, From 8aefcdb2e8012bbe79f551977b07e09966cf0352 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 25 Sep 2024 05:46:18 +0000 Subject: [PATCH 280/312] chore(release): 1.0.0-dev.31 [skip ci] # @revanced/discord-bot [1.0.0-dev.31](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.30...@revanced/discord-bot@1.0.0-dev.31) (2024-09-25) ### Bug Fixes * **bots/discord:** persist changes in context for eval command ([5b4965d](https://github.com/revanced/revanced-helper/commit/5b4965dcc7285676b2b3b6756c249bd56eaf8485)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index dbd3d22..640a8e0 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.31](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.30...@revanced/discord-bot@1.0.0-dev.31) (2024-09-25) + + +### Bug Fixes + +* **bots/discord:** persist changes in context for eval command ([5b4965d](https://github.com/revanced/revanced-helper/commit/5b4965dcc7285676b2b3b6756c249bd56eaf8485)) + # @revanced/discord-bot [1.0.0-dev.30](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.29...@revanced/discord-bot@1.0.0-dev.30) (2024-09-24) diff --git a/bots/discord/package.json b/bots/discord/package.json index 4540976..cfbb0e5 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.30", + "version": "1.0.0-dev.31", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 062735f6d552890404d6192244c51a11b0709580 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 25 Sep 2024 12:49:08 +0700 Subject: [PATCH 281/312] fix(bots/discord): contextify object before sandboxing --- bots/discord/src/commands/admin/eval.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bots/discord/src/commands/admin/eval.ts b/bots/discord/src/commands/admin/eval.ts index 3aab45d..5e2b282 100644 --- a/bots/discord/src/commands/admin/eval.ts +++ b/bots/discord/src/commands/admin/eval.ts @@ -1,7 +1,7 @@ import { unlinkSync, writeFileSync } from 'fs' import { join } from 'path' import { inspect } from 'util' -import { runInContext } from 'vm' +import { createContext, runInContext } from 'vm' import { ApplicationCommandOptionType } from 'discord.js' import { AdminCommand } from '$/classes/Command' @@ -43,10 +43,10 @@ export default new AdminCommand({ // to the bot while the bot is running, minus malicious actors getting the token to perform malicious actions const output = await runInContext( code, - { + createContext({ ...globalThis, context, - }, + }), { timeout: parseDuration(timeout ?? '10s'), filename: 'eval', From f6d2e2513025ac52a58e4b5285663c46ce05c6c7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 25 Sep 2024 05:50:49 +0000 Subject: [PATCH 282/312] chore(release): 1.0.0-dev.32 [skip ci] # @revanced/discord-bot [1.0.0-dev.32](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.31...@revanced/discord-bot@1.0.0-dev.32) (2024-09-25) ### Bug Fixes * **bots/discord:** contextify object before sandboxing ([062735f](https://github.com/revanced/revanced-helper/commit/062735f6d552890404d6192244c51a11b0709580)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 640a8e0..56b8c4b 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.32](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.31...@revanced/discord-bot@1.0.0-dev.32) (2024-09-25) + + +### Bug Fixes + +* **bots/discord:** contextify object before sandboxing ([062735f](https://github.com/revanced/revanced-helper/commit/062735f6d552890404d6192244c51a11b0709580)) + # @revanced/discord-bot [1.0.0-dev.31](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.30...@revanced/discord-bot@1.0.0-dev.31) (2024-09-25) diff --git a/bots/discord/package.json b/bots/discord/package.json index cfbb0e5..7603e84 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.31", + "version": "1.0.0-dev.32", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From b5f40975386677ffff343c42f8ffac21f847a0b7 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 25 Sep 2024 13:00:53 +0700 Subject: [PATCH 283/312] feat(bots/discord): add trigger to context for eval --- bots/discord/src/commands/admin/eval.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/bots/discord/src/commands/admin/eval.ts b/bots/discord/src/commands/admin/eval.ts index 5e2b282..9205a63 100644 --- a/bots/discord/src/commands/admin/eval.ts +++ b/bots/discord/src/commands/admin/eval.ts @@ -46,6 +46,7 @@ export default new AdminCommand({ createContext({ ...globalThis, context, + trigger, }), { timeout: parseDuration(timeout ?? '10s'), From 6e181c0e7ff9195b4285f05357844f3b1be39a48 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 25 Sep 2024 06:02:32 +0000 Subject: [PATCH 284/312] chore(release): 1.0.0-dev.33 [skip ci] # @revanced/discord-bot [1.0.0-dev.33](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.32...@revanced/discord-bot@1.0.0-dev.33) (2024-09-25) ### Features * **bots/discord:** add trigger to context for eval ([b5f4097](https://github.com/revanced/revanced-helper/commit/b5f40975386677ffff343c42f8ffac21f847a0b7)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 56b8c4b..bf5d276 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.33](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.32...@revanced/discord-bot@1.0.0-dev.33) (2024-09-25) + + +### Features + +* **bots/discord:** add trigger to context for eval ([b5f4097](https://github.com/revanced/revanced-helper/commit/b5f40975386677ffff343c42f8ffac21f847a0b7)) + # @revanced/discord-bot [1.0.0-dev.32](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.31...@revanced/discord-bot@1.0.0-dev.32) (2024-09-25) diff --git a/bots/discord/package.json b/bots/discord/package.json index 7603e84..5f8b14c 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.32", + "version": "1.0.0-dev.33", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 27d3b392092141a1e3b4b0298131ff7817458dc1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 17 Oct 2024 21:37:24 +0700 Subject: [PATCH 285/312] feat(bots/discord): add default durations for moderation commands --- bots/discord/src/commands/moderation/ban.ts | 9 +++++---- bots/discord/src/commands/moderation/mute.ts | 10 +++++----- .../src/commands/moderation/role-preset.ts | 15 +++++++++++---- bots/discord/src/commands/moderation/slowmode.ts | 9 +++------ bots/discord/src/utils/duration.ts | 11 ++++++++++- 5 files changed, 34 insertions(+), 20 deletions(-) diff --git a/bots/discord/src/commands/moderation/ban.ts b/bots/discord/src/commands/moderation/ban.ts index 895ea9d..d584cd7 100644 --- a/bots/discord/src/commands/moderation/ban.ts +++ b/bots/discord/src/commands/moderation/ban.ts @@ -18,13 +18,14 @@ export default new ModerationCommand({ required: false, type: ModerationCommand.OptionType.String, }, - dmd: { - description: 'Duration to delete messages (must be from 0 to 7 days)', + dmt: { + description: + 'Time duration to delete messages (default time unit is days, must be from 0s to 7d, default value is 0s)', required: false, type: ModerationCommand.OptionType.String, }, }, - async execute({ logger, executor }, interaction, { user, reason, dmd }) { + async execute({ logger, executor }, interaction, { user, reason, dmt }) { const guild = await interaction.client.guilds.fetch(interaction.guildId) const member = await guild.members.fetch(user).catch(() => {}) const moderator = await guild.members.fetch(executor.user) @@ -40,7 +41,7 @@ export default new ModerationCommand({ ) } - const dms = Math.floor(dmd ? parseDuration(dmd) : 0 / 1000) + const dms = Math.floor((dmt ? parseDuration(dmt, 'd') : 0) / 1000) await interaction.guild!.members.ban(user, { reason: `Banned by moderator ${executor.user.tag} (${executor.id}): ${reason}`, deleteMessageSeconds: dms, diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index c7549a6..1e34236 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -14,13 +14,13 @@ export default new ModerationCommand({ required: true, type: ModerationCommand.OptionType.User, }, - reason: { - description: 'The reason for muting the member', + duration: { + description: 'The duration of the mute (default time unit is minutes)', required: false, type: ModerationCommand.OptionType.String, }, - duration: { - description: 'The duration of the mute', + reason: { + description: 'The reason for muting the member', required: false, type: ModerationCommand.OptionType.String, }, @@ -33,7 +33,7 @@ export default new ModerationCommand({ const guild = await interaction.client.guilds.fetch(interaction.guildId) const member = await guild.members.fetch(user.id) const moderator = await guild.members.fetch(executor.id) - const duration = durationInput ? parseDuration(durationInput) : Infinity + const duration = durationInput ? parseDuration(durationInput, 'm') : Infinity if (Number.isInteger(duration) && duration! < 1) throw new CommandError( diff --git a/bots/discord/src/commands/moderation/role-preset.ts b/bots/discord/src/commands/moderation/role-preset.ts index 9313fef..9e9f689 100644 --- a/bots/discord/src/commands/moderation/role-preset.ts +++ b/bots/discord/src/commands/moderation/role-preset.ts @@ -11,12 +11,12 @@ const SubcommandOptions = { type: ModerationCommand.OptionType.User, }, preset: { - description: 'The preset to apply or remove', + description: 'The preset to manage', required: true, type: ModerationCommand.OptionType.String, }, duration: { - description: 'The duration to apply the preset for (only for apply action)', + description: 'The duration to apply the preset for (only for apply action, default time unit is minutes)', required: false, type: ModerationCommand.OptionType.String, }, @@ -53,7 +53,7 @@ export default new ModerationCommand({ throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') if (apply) { - const duration = durationInput ? parseDuration(durationInput) : Infinity + const duration = durationInput ? parseDuration(durationInput, 'm') : Infinity if (Number.isInteger(duration) && duration! < 1) throw new CommandError( CommandErrorType.InvalidArgument, @@ -83,6 +83,13 @@ export default new ModerationCommand({ removeRolePreset(member, preset) }, expires) - await sendPresetReplyAndLogs(apply ? 'apply' : 'remove', trigger, executor, user, preset, expires ? Math.ceil(expires / 1000) : undefined) + await sendPresetReplyAndLogs( + apply ? 'apply' : 'remove', + trigger, + executor, + user, + preset, + expires ? Math.ceil(expires / 1000) : undefined, + ) }, }) diff --git a/bots/discord/src/commands/moderation/slowmode.ts b/bots/discord/src/commands/moderation/slowmode.ts index 67facf7..b6a43a9 100644 --- a/bots/discord/src/commands/moderation/slowmode.ts +++ b/bots/discord/src/commands/moderation/slowmode.ts @@ -10,7 +10,7 @@ export default new ModerationCommand({ description: 'Set a slowmode for a channel', options: { duration: { - description: 'The duration to set', + description: 'The duration to set (default time unit is seconds)', required: true, type: ModerationCommand.OptionType.String, }, @@ -23,13 +23,10 @@ export default new ModerationCommand({ }, async execute({ logger, executor }, interaction, { duration: durationInput, channel: channelInput }) { const channel = channelInput ?? (await interaction.guild!.channels.fetch(interaction.channelId)) - const duration = parseDuration(durationInput) + const duration = parseDuration(durationInput, 's') if (!channel?.isTextBased() || channel.isDMBased()) - throw new CommandError( - CommandErrorType.InvalidArgument, - 'The supplied channel is not a text channel.', - ) + throw new CommandError(CommandErrorType.InvalidArgument, 'The supplied channel is not a text channel.') if (Number.isNaN(duration)) throw new CommandError(CommandErrorType.InvalidArgument, 'Invalid duration.') if (duration < 0 || duration > 36e4) diff --git a/bots/discord/src/utils/duration.ts b/bots/discord/src/utils/duration.ts index 4f4e129..ede2d76 100644 --- a/bots/discord/src/utils/duration.ts +++ b/bots/discord/src/utils/duration.ts @@ -1,6 +1,15 @@ import parse from 'parse-duration' -export const parseDuration = (duration: string) => parse(duration, 'ms') ?? Number.NaN +const defaultUnitValue = parse['']! + +export const parseDuration = (duration: string, defaultUnit?: parse.Units) => { + if (defaultUnit) parse[''] = parse[defaultUnit]! + return ( + // biome-ignore lint/suspicious/noAssignInExpressions: Expression is ignored + // biome-ignore lint/style/noCommaOperator: The last expression (parse call) is returned, it is not confusing + (parse[''] = defaultUnitValue), parse(duration, 'ms') ?? Number.NaN + ) +} export const durationToString = (duration: number) => { if (duration === 0) return '0s' From 8ff6086028132cc4b49ee60846e8d6ef909f5a89 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 17 Oct 2024 21:38:12 +0700 Subject: [PATCH 286/312] feat(bots/discord): cure on every event --- bots/discord/src/events/discord/cure.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bots/discord/src/events/discord/cure.ts b/bots/discord/src/events/discord/cure.ts index b8e930b..a916558 100644 --- a/bots/discord/src/events/discord/cure.ts +++ b/bots/discord/src/events/discord/cure.ts @@ -1,9 +1,9 @@ import { on } from '$/utils/discord/events' import { cureNickname } from '$/utils/discord/moderation' -on('guildMemberUpdate', async (oldMember, newMember) => { +on('guildMemberUpdate', (_, newMember) => { if (newMember.user.bot) return - if (oldMember.displayName !== newMember.displayName) await cureNickname(newMember) + cureNickname(newMember) }) on('guildMemberAdd', member => { @@ -11,7 +11,7 @@ on('guildMemberAdd', member => { cureNickname(member) }) -on('messageCreate', async msg => { +on('messageCreate', msg => { if (msg.author.bot || !msg.member) return - await cureNickname(msg.member) + cureNickname(msg.member) }) From 3ed5bd11acc3b4fbd57b0d632c68eb9f77365b8a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 17 Oct 2024 21:40:41 +0700 Subject: [PATCH 287/312] fix(bots/discord): attempt to fix stuck sticky message timeouts --- .../messageCreate/stickyMessageReset.ts | 40 +++---------------- bots/discord/src/events/discord/ready.ts | 4 +- 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts index d674f00..e55122b 100644 --- a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts +++ b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts @@ -7,41 +7,8 @@ withContext(on, 'messageCreate', async ({ discord, logger }, msg) => { const store = discord.stickyMessages[msg.guildId]?.[msg.channelId] if (!store) return - // TODO: Fix this by fixing the logic below - if (store.timerActive && store.forceTimerActive) { - logger.error( - `Both timers are active in sticky message store: ${msg.guildId}.${msg.channelId}, this should not happen!`, - ) - logger.info('Clearing the timer and the restarting the force timer...') - clearTimeout(store.timer) - store.timerActive = false - // If the force timer is active, it implies the force timer exists - store.forceTimer!.refresh() - } - - const timerPreviouslyActive = store.timerActive - // If there isn't a timer, start it up - store.timerActive = true - if (!store.timer) store.timer = setTimeout(store.send, store.timerMs) as NodeJS.Timeout - else { - /* - If: - - (negate carried) There's a timer - - The timer is not active - - The force timer is not active - Then: - - Restart the timer - */ - if (!timerPreviouslyActive && !store.forceTimerActive) store.timer.refresh() - /* - If: - - Any of: - - (negate carried) The timer is active - - (negate carried) The force timer is active - - The force timer is not active - Then: - - Start the force timer and clear the existing timer - */ else if (!store.forceTimerActive && store.forceTimerMs) { + if (store.timerActive) { + if (!store.forceTimerActive && store.forceTimerMs) { logger.debug( `Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer and clearing existing timer`, ) @@ -62,5 +29,8 @@ withContext(on, 'messageCreate', async ({ discord, logger }, msg) => { ) as NodeJS.Timeout else store.forceTimer.refresh() } + } else if (!store.forceTimerActive) { + store.timerActive = true + if (!store.timer) store.timer = setTimeout(store.send, store.timerMs) as NodeJS.Timeout } }) diff --git a/bots/discord/src/events/discord/ready.ts b/bots/discord/src/events/discord/ready.ts index f4f6dd9..a719d4b 100644 --- a/bots/discord/src/events/discord/ready.ts +++ b/bots/discord/src/events/discord/ready.ts @@ -60,7 +60,7 @@ export default withContext(on, 'ready', async ({ config, discord, logger }, clie ) } } - + // Set up the store discord.stickyMessages[guildId]![channelId] = { forceTimerActive: false, @@ -69,7 +69,7 @@ export default withContext(on, 'ready', async ({ config, discord, logger }, clie timerMs: timeout, send, // If the store exists before the configuration refresh, take its current message - currentMessage: oldStore?.[channelId]?.currentMessage + currentMessage: oldStore?.[channelId]?.currentMessage, } // Send a new sticky message immediately, as well as deleting the old/outdated message, if it exists From 488d37e65bb697d5f08a1ea23453374b58de228a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 17 Oct 2024 21:43:12 +0700 Subject: [PATCH 288/312] fix(packages/shareed): add missing imports --- packages/api/src/classes/ClientWebSocket.ts | 2 +- packages/shared/src/schemas/Packet.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index 87b75d2..9495322 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -35,7 +35,7 @@ export class ClientWebSocketManager { /** * Sets the URL to connect to - * + * * **Requires a reconnect to take effect** */ async setOptions({ url, timeout }: Partial, autoReconnect = true) { diff --git a/packages/shared/src/schemas/Packet.ts b/packages/shared/src/schemas/Packet.ts index c6447b9..757cfef 100755 --- a/packages/shared/src/schemas/Packet.ts +++ b/packages/shared/src/schemas/Packet.ts @@ -1,18 +1,18 @@ import { + url, type AnySchema, + type BooleanSchema, type NullSchema, type ObjectSchema, type Output, - type BooleanSchema, array, + boolean, enum_, null_, object, parse, special, string, - boolean, - url, // merge } from 'valibot' import DisconnectReason from '../constants/DisconnectReason' From 11582d50345cae9fb645a65ca4e621596de6a408 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 17 Oct 2024 23:20:02 +0700 Subject: [PATCH 289/312] fix(bots/discord): fix reload not working --- bots/discord/src/commands/admin/reload.ts | 11 ++----- bots/discord/src/context.ts | 40 +++++++++++++++++++---- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/bots/discord/src/commands/admin/reload.ts b/bots/discord/src/commands/admin/reload.ts index 6fcc71d..d7e66e9 100644 --- a/bots/discord/src/commands/admin/reload.ts +++ b/bots/discord/src/commands/admin/reload.ts @@ -1,8 +1,5 @@ -import { dirname, join } from 'path' import { AdminCommand } from '$/classes/Command' -import type { Config } from 'config.schema' - export default new AdminCommand({ name: 'reload', description: 'Reload configuration', @@ -10,10 +7,8 @@ export default new AdminCommand({ const { api, logger, discord } = context logger.info(`Reload triggered by ${context.executor.tag} (${context.executor.id})`) - // Apparently the query strings only work with non-Windows "URLs", otherwise it'd just infinitely hang - const path = `${Bun.pathToFileURL(join(dirname(Bun.main), '..', 'config.js')).toString()}?cache=${Date.now()}` - logger.debug(`Reloading configuration from: ${path}`) - context.config = ((await import(path)) as { default: Config }).default + logger.debug('Invalidating previous config...') + context.config.invalidate() if ('deferReply' in trigger) await trigger.deferReply({ ephemeral: true }) @@ -28,7 +23,7 @@ export default new AdminCommand({ api.client.disconnect(true) api.disconnectCount = 0 api.intentionallyDisconnecting = false - await api.client.connect() + api.client.connect() logger.info('Reinitializing Discord client to reload configuration...') await discord.client.destroy() diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 97229ba..30bed63 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -1,23 +1,49 @@ import { Database } from 'bun:sqlite' import { existsSync, readFileSync, readdirSync } from 'fs' -import { join } from 'path' +import { dirname, join } from 'path' import { Client as APIClient } from '@revanced/bot-api' import { createLogger } from '@revanced/bot-shared' import { Client as DiscordClient, type Message, Partials } from 'discord.js' import { drizzle } from 'drizzle-orm/bun-sqlite' +import type { default as Command, CommandOptionsOptions, CommandType } from './classes/Command' +import * as schemas from './database/schemas' // Export some things first, as commands require them -import config from '../config.js' -export { config } +import _firstConfig from '../config.js' + +let currentConfig = _firstConfig + +// Other parts of the code will access properties of this proxy, they don't care what the target looks like +export const config = new Proxy( + { + INSPECTION_WARNING: 'Run `context.__getConfig()` to inspect the latest config.', + } as unknown as typeof currentConfig, + { + get(_, p, receiver) { + if (p === 'invalidate') + return async () => { + const path = join(dirname(Bun.main), '..', 'config.js') + Loader.registry.delete(path) + currentConfig = (await import(path)).default + logger.debug('New config set') + } + + return Reflect.get(currentConfig, p, receiver) + }, + set(_, p, newValue, receiver) { + return Reflect.set(currentConfig, p, newValue, receiver) + }, + }, +) as typeof _firstConfig & { invalidate(): void } + +export const __getConfig = () => currentConfig export const logger = createLogger({ level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, }) -import * as commands from './commands' -import * as schemas from './database/schemas' - -import type { default as Command, CommandOptionsOptions, CommandType } from './classes/Command' +// Importing later because config needs to be exported before +const commands = await import('./commands') export const api = { client: new APIClient({ From 7b5d4fa1a2a1e0d136c2dabd2010345328db0a3d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 17 Oct 2024 23:55:17 +0700 Subject: [PATCH 290/312] chore: update dependencies --- apis/websocket/package.json | 10 +++++----- bots/discord/package.json | 10 +++++----- bun.lockb | Bin 288360 -> 291848 bytes packages/api/package.json | 4 ++-- packages/shared/package.json | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 9ccad99..358830a 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -28,13 +28,13 @@ "homepage": "https://github.com/revanced/revanced-helper#readme", "dependencies": { "@revanced/bot-shared": "workspace:*", - "@sapphire/async-queue": "^1.5.2", + "@sapphire/async-queue": "^1.5.3", "chalk": "^5.3.0", - "tesseract.js": "^5.1.0", - "ws": "^8.17.1" + "tesseract.js": "^5.1.1", + "ws": "^8.18.0" }, "devDependencies": { - "@types/ws": "^8.5.10", + "@types/ws": "^8.5.12", "typed-emitter": "^2.1.0" } -} \ No newline at end of file +} diff --git a/bots/discord/package.json b/bots/discord/package.json index 5f8b14c..62e1db7 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -28,19 +28,19 @@ }, "homepage": "https://github.com/revanced/revanced-helper#readme", "dependencies": { - "@discordjs/builders": "^1.8.2", - "@discordjs/rest": "^2.3.0", + "@discordjs/builders": "^1.9.0", + "@discordjs/rest": "^2.4.0", "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", "chalk": "^5.3.0", "decancer": "^3.2.4", - "discord.js": "^14.15.3", + "discord.js": "^14.16.3", "drizzle-orm": "^0.31.4", "parse-duration": "^1.1.0" }, "devDependencies": { "@libsql/client": "^0.7.0", - "discord-api-types": "^0.37.97", + "discord-api-types": "^0.37.102", "drizzle-kit": "^0.22.8" } -} \ No newline at end of file +} diff --git a/bun.lockb b/bun.lockb index 59ad7e3be738636f9579713fb195aa0f18e2fd71..cf870a61106c5d93164117902ca22a926da1e79f 100755 GIT binary patch delta 27674 zcmaEHMzG_B-~>I*1OFGQ@~$*qnCP^5*Q;fACThRiPb(`-4X^j=QZ3l=Z&gJx3j+w; zoft09^zzEaiUYic)vOE*5)2FtY)lLck_-$D?#&DgybKHtrKOoEAhmtX3=AS5eqKsu zawY>q^yEZ7@%l5Z5YcU|3=Con3=NB*{2nO30?JQ<@&j5K7=#!Y8f>8Anym~B!VC-z zBCQMzq6`cTiy0Xhv=|r~W->A`a4|469O-0W;ACKE5bR=L;AUWG2LJ%b^C@=Jbu z#^sYW1?F2GpT)o+$iUDbKAV9-iGiWvEdxYPNoHPUYDH?Y=o|(H0S1PK^>ZNNDTzht z#S9D#vnDeN>MPEj2XPrIRMqVH5PtuBhzp7)2MUVUhtG$&D{&#jtUxH8nU@I)Tn6R& z3=A?13=N4{WnlibMG*fSS_BE-w8Y{P-O{4W&5Iyzu%8c!t*^5o=51R7@q_kKh_C?iN6*GNIKcD0ito~21sbn-T;yRv%yYl@#Y>CNVHD!1VWQ0Lj-gGze{DV322E zX!yGoVgbzH_gf(jDTQW>b(3f7DMw>Lmo6dlMg{6?$#kl zDporTNj8U};>QnzBc?&^2qgSdi<3$-b5aQM zOGhCQu@6eGg3{BVbQ6@$gVIq@+6_t@AB9Ay0+i;3(!Y*C#9thNIP@BnJ_4re85%Z$ z84L}}QaM{}M^$H}4?p}dp?vg8zG?Q@!k|uI8lZp#-7#K8eLgWQ+ zLgN1S4R9PcJcZI1Za~c0a|2@Esv8XT;7VZb4Tyo0Za@-IECZx8IdvOiK~+v>5-53Q z-hsqr#2tu3U7>vKI}8j`3=9o09~#_)__XXE#3Lm|CT5`0G_zQ@v?zyxLE#R>9$4g~dwavhPW1xKh-nsfSY4#jO9Cqw1p0zvrY)(%$(AvPU`1Fj*`B|4TH$Tww zV4l3f%3yMYp1@=cD}%{1p!5wWEujw)kATuMp!5xWfypbJ4JS()@K0W$Z@4+qz&v}h zfF1|unI;AXcTfR5`J%4=j0v)j`G!K4@iN2*fI5*v7yRidCkq4VRwl zIQ8hXxj?11)?F%lZkqhk^ zuTP%2(4JMWn}NY)@?8UK&d_cK21ig5pR8zP&NQui@+=cO)^ptq3}%z>dRlX`^e`|O zF)%c+f+CBxDR+b#kJfIn$P2kd>TodKnm8z#0_I%{dMF7#N%w z7#i3nCpwyQ7WFYO_`rGl`WP6};TEa(Gcb5DFf@owUf6EVS=Gh7H)|?^JAog=i2Dx(bG)NNQocz$hobw7)jvX9?!qXWTK#ct=kZ2JB%RQJ5NwyqdM;gyyVDJPxvStP(h}giY_RN6T#SMlXBGoP zGy_9}@MJ|xbIu#HAYO#{4A0|(eiW^*Bh8$@O8Tu7p32YYk#T!?eQsf6<*l*bMV8czLr5c?njl0FZj z4`RcLc@TY!pzOx^Y#y>(md%IAv4H*eWIiOYSSBm#nRBWvfTV4ReQgUM(Zo7AvBsS7 z?BttU>^c7}fJ6_(wS@~A7(5`UU(cL#CxT~W&dIolfk6)}w{WTj$0A6f2hrBDh=D;L zT)ID;V9xn+5hP=UluD=0i{t ze+}n>afv#zaXVDJaCRM#*tc!OC5YZw>;!K{O87#MuPEWWicb+Kz<>h`T=V2GP6Yh}$^ zxDHh6$2wYbZUwQxaja-$&iQj4#L4X763%)(#52kJTZZGe=~ zj3AFPF>RbIb` zhnpd$Fi%!IWX`L)1rnp68lQ9WMP2R56D&D7wm=dKBvTyP0?DqBO!0RMBvr9ZRtv)2hJ^9A!aavN<1FJZ43-ipi-KFfno9k7oEuy z@;O((%U&% z>Zl!4^3KV#j@ogq*a`6*^JK*+bI!H9A+;HVC$isa)G>)2m>hM|j??!5 zq^e{DIiIup0HpY002NqF2Mr|ozj z9E2Fc!N9=4I$1FroM@Ot4o#kQ+K#j85G3OOv(7pT_U2`4 z*5`1R$vK!`MOM$PSM@Z7F^1ybxXOipw+ z=bU;45=0E(s#4@CL=KW{YOhX?x@E_D0wg$D*4~;^^BSZYW1gHi&77n18l+A^q`Je` zAdMbyGna|~`s7)+?KnNJLzFR0PMm1YI{7-Nffj3K&H4B`q+n&5yzq_%%MAtwi^+3u z+A!XjeCv)KtHVuj`F+Qlb>dA>bltsU&HCUbC~80~-CHnL)h$refW(g80tG6F#djMN zFn8}*a|Yjrv<(?R#UWGA?a5O2>o~97hBOYqt#;0uJCMxA0InD}+=0~EjNs7tcL(Bf zuu3M6yOU=a|}qFksrG7S>XgNh^QdPvW=l$n8nm4ShwikX1{ROvJ@g9k(z z<}!na7#LPS9kvoGzaDB3J{mM8zZt4;3sgObW@2Dq*aqcqt7m3l0I_#6GcbUP=Y!1P zF%gDK(4e})3~}IXkRk>K24or}bPq%@Fff2YU|?v2nuCu9IlL39uCty6Jod*h6)FItr5P9)HbBMqKwY{QDvyl@4NV<^`s^4~ z9X=XVGMtC1tA{c0(I5>spc?SeAaxI+>OeFotvrT$92UHwH zi!m@T$U*sdX;A*dtAJR8$W#ZiAH}60A1SbcN46Qvp+2A?4Yr@A7J>X=!wM;|99SV` zs53MwU0CZOaq9+B#lXOTj|PQ|2UHz04JueXS)nZisC+Q7bUhx0pnM$#H8=&Tju;x` z&@^bG%z)~{MRUR>HgEiSc(e_~z<_A`KpJ~_%%g1}P?g2Nz%bedVqh3;1L12UjkbYC z+d!aVbF>XaLYrx{4FpOu3=G&BP4sO8O^*~{EOBTv7rK@azguHRWIjjKjq`7J9bS_= zXNOtdWUU)()z&R6>}{46dT%ag?{}-Tlk?<3?dJ>p0*(F8Zc<;=(R=sj*VWT`1sNsk zr}i54C$`^QJL}Fx!}O|S)vt~RhPMi*P2VY?S3U3fZ-17z^Ul6)Saz!7-6iKk*SHQi z8w$>9da1Q@^UU-*n;Q-MjG*2I3qu0~q{#{iA_fMQ?VK))PX?|@d+omDf0}2A(&}k< zI$b%uiw=1vgz%eYbX{W0tNM{BD!EkacuQx_@rb0vC%x;=w=J=elzcz+?e8O@)Axd$ z*PrO8c4&tV^IBQX=sT~}E~_^Fjo30LHnpeo_5IkiaKjfOOo3_FuigBTUpgiFUqHwq zla(L0@BFm1{S325(FPL>sPhCsL*_8IfjULH->>LD-+AcY!7X;pO~qyt7BA&<*0}j3 zxpjkbSk)o^8l_t=Ot!`KO7fd6o>TaE>tZ9TBKZg0pFNg)M;$24{MhI)Jy(cPqTc(; z`od`$S?iLu_AOyv-unJef|F?a$B1g%n4Qx)UwpTjy-%k5;WNA6y8RhjUMMmBk-j98 zvvYxk-gjZ)WDyD5Sg`XLI6!?sP!fW#F9zb0;Y5`LHo>>7rdeEA&}bHLu{6ymH>@?`s)tr<|#8 zmt3fqKDo8lvPtzJ*m?i>|1mGgLaZWrEuhQX^Z#pCX zQpZfk;~Nh@lj+wpkj%ZU{_m(ypad`1d+mc8#8oc@p6Ky;G=2N6?PrA<`zJfH1aetb$1X3X9qD{t+ujQ{8Om=jaGU?K-nBN^-y+ev{$1dq zbt}&A|Nc=}Ty)ak>2sGeicgPO&gjFqbNb5VjN#KGRxt8T=Uu_*&A5Ad{wdxA>sM`U zz9Lw8s6YPh@-v0vzEu|03qoTh!I1#TVF!^7c39?EsNpxm)_Ln=KW{%zmnU9<^*#H{ zOkO`XEOE6Kt`D`bC^;m$V56(w^Xp873p;;EO)^kbNf7C%Q8}afhGz<6J;cQd3=9m1 zkPY7CZYO(rt@}LrPY*i$8jmww?U`Ry^Lus7og4gX1584iBK7)=x4X?u)2x@i?84=5 zrFS=bhpmU6=22Tlb~}$}zEB5)%sq^3?&mjM2a8;eF(oVM?tB<@O1gf_j1@<(m8I_A z#F{fdSwr{5x}xLfI9~L9zC6i4Kx(efsRue*6CFRQ&p+Bb{aoz)=b!~nj0_AMpaIq+ z$Od0DF4EusND15 zZ~wEs%OjPi{>Uz=jN+5o7nq?)DOfvOR?O(Y>?wUM^J3RLSb4u>=3@SczwGZD;=txIaDYmmW60)4 zChxwS&0F88d!{o-lj-Bj{{ET++tLc>S$z2|eDJ^%zjwB|3^yNj@N=D9VJ5ERuk@{p zO-$a?;r|13d*=;Lw01Ir4Q2tYe`Yw2Y;YH6BmV?_+vhQl(vB{fQO~t}L5p9Br^sy1 z9&>|c9zpH1vQMYo>*Mmcw{6|tpn8={c6R6E)Xuy;z1ubO*2$0GbTIk&VAACUl{1WrpRn=y zd%aqe^y_fQ%D1L}b%em?vVe!APr}S~0TsmJZc>SE52v4zN>5Hc+_2J|S8!_=6mbJ&)=IQ>A*HMKM<{AuU(vuhb8ruVL8 z^!7asGZ)iG^2GY0&IH%~j|HzkE{)-R$8wrw zd2qY>$vL~WuKHTG?u+R|-${#Ef;4p7r`N7yl<=96QEjqgjb*h8tKT+$p-aDieweY- zDWQyEg3jH=4L83(|GaMUk81+5R?B)H81LH3es50MkELy2{+%eeebeIqA~TRz85tl2 z=-KI=>lnkQTdZf~o&I(mqc`LE>6Ysm!>7Lh3Cvy3=*@U>`b&_&j17#u(|tEEdNW?0 zK63+O_;inrjJ(tTf&{Kk_uR-BKK%zsVC_amZ^rA>e}V*7Y+~e{9=nOroAKuKm774N z;AT)MxS7$L@%Hq{&7e|n3#b$X3EZ8|xdl`Tf&_B6FnTlIpS}|$kg=7Kce?CWMsLQ4 z(=)d+hEJE+#>hMUEJ)z-bjfXu;nPom1ZuZ2dNV$qei9^5v7M23y6$#HZ^q}-E4MR- zPuJMN$T$7oc1AD8m(w+OFosXRv4fF!dhZTKZ^qZtZ-N9mb~5r#x82F;&G>eD=T1;~ z>;i=cNZ|c+%Uz)G013?9#puoWar#S;z>M9X@Yv1h&G>ox%-x{y*aHd=kiggJo_j#y z0TNidhtZqy`}Ch6ffai};jx#|oAKxLm3u+qu@4j;`xw0$e@~Cx2MUk{K;dx^6doV}=IN3LLE!-ss6EK&&B!|a zBuJp*5GXtjF?utyPp>=#3Xj8#ywmT31URQ_9tMR6NTByHqc^qU}ojw7J(IKt@7 z$UD9B2q-*`g2DqNz(3vcC@4HY0&|ZtdNT@6e+d$paSRk5#~8gCg{RLv1`3bkpzr_* zh)(xB4hj#Dz}n-C-i+eYe}V*7oB)N#2}W;5$>}RkfWqS>C_GLwdNWE-k30zqk5i!V z013!W=R5@p50F6aDMoKb`RO}B0vV@4;c=SLn^AFk=4nuPoB@RgNI-eIkF$*4jOx=X&w|3^94I_M0-Dn`&w;`NB+z?~(VJ0w`c05P$9YhA zoM-fA)ScdW9uyuIK;Z!r(4TI30Tdn}fw>nLy%`OszXS=)xCjc5i;Uik#?xnB1ck>X zPPQsc7HZKm?+cMa2PQPE;=MXqyJ=6PX@2=^_F^No8WsjZxa>|0=S-Wbx_fDL{ z&GEqQ_}R~~-}%c!GIu6-sphif$Z#uyn+A~5+7{W|5SdS{t5&UM{+`3B!nPxH^<1U9 z9)Ev|uIqnQA2GA)3?Ij(=TEv45-nfI@E-20X_dV+v+cC!Mvdze=Y;#2bC@jo2`*J3 z^@|;{!9FvWTjcIDdD!zlb7`@mj+w{aK>O)Thglh3*{FNY`*Y7)VE+3FTR+zKn^t{4 zt{3A|Wpd+1bB*VfppLKJ`xmf7n+6aU+anviXC^~e!na>X^EmBbiajhZ4Ty#{dkca?0{_U@!T!p z?RM)r=iXkdcWrjak9zO9hNVG|B{ypeU(LU{VD2|n^ESh8AAXw7y5%kZV$RIFcQWcv zpXd*~YRO^dsm1REYv+R+(vHXmJI*?Hb-;CziRw=wElbj*6sE;-$hPa-UxLu$Y3XAgZC<|P!Mvt zAYAU5aC8To{Z_H{OTB#;t(a3W&ws7&N8aDuMfq1=+%H#EHZi{3(4l|*UGn+b7jM@u zllk^#K~$hZIM`fBtIiqO+-dxu(zMO$Wv{SpRu|b*I_X@0iq+3!O~11fB{VpE_}O_F z^w0k)+Pw1q^B?ane^CB(;_9{RR*gq*KIcd*Y|rOuXa^e%X@|NX8(he2blYq>o2&P- z%#E>^b|pUkJabOT=4sU$o*!my-T7#{xOU|xj%ydP{eupq)yQ}M*U{%?{}Oxs%{uL) z!EV9l55qbGQy3T+T&JJB&KN$u;sz*x-(d7+be~>%1C+mSg7P;=z;n9hO;G*@3H07% z^k(#)eiJ0naf^{>y3H*{A4cElowq>w`!*Q#zY1nut(1FkQ#846AO+h;)lOwK3nc;z3bG( zZ5C}U`@gx%f0^Q((JpBjv*%}iU z&#^d^E@(O5RkNBs`R&#hKfauIO=Yt&mtfg@_U-Cr9-MP)XYUo!A1Xh57-thKXke2$;+Dao)e=FO{j4>(WFol>?Zl;>LZTn zzV{g=>SOmVT4U8)w7%~{to`KAQGE0MZTBx-{%lVE+$83IJ5p#yBfHLZk>$!+R{RN5y;lp%1TDDs&#!I$KepcP z^clthK9YYk+1t6@d=%;>HwJz9q;YNnt6|o$x~LPre`Ov&*3n@Qs!W;w_dcV<^wj%| z-oCNO2J5M;+ibqq_uMOS-#0qii^XTmT)t`FUSSs=v6>ele=`~M@BQ)7(+JtK`sD`I zMa~H)S4W2yd1-3pW*_TZVCrt$Hht{_Mv3X74;a0DyYZ{h_6X+u8Di^C zr4%3Cel#I}Ez|q?&9a}5@@!vuZGqDQ?*mh_%aynKh+m2OeroSyF4Y;Cd)+jwjO`vx zkA27}G5zQRMsLQ%>5TUo!x@vNZv;`v(_h|Z3};N4zVI$%IAiMcmG>CK8PldGK41)I zOrI|JkTIMwWBNf5l{sDV5o0)G*7QOUl|B6=h{~C+_?R)AF?V_;h{~IO5k%!r*L=bl z&R8(L5kwVEzX_s>rW-zG3}-B!-uV=iS6(otPIrCA=so?%3r61QbDuGKGnP+(`3#hx zUV`$~b4G8*%IPzogKFegph_GhP(A%2NHfQ4M&9XbUod(z)=vNV0@Olz&B*I>@AAb- ze_ggYygV0Sq^)GLB%3XTv0rfMz1i<*o3kJykeqjCky6&-FGc6 z;$W$>R)@376~l>0X{CO8;7i7E#)j!DK~&>(##fBtj7`%cK~(efjUcLJI_GP~aK_f@ zi6E+N`c4qlK3(t)V>n~S^h^-dIsG7r>Y6V3mNA^MdwL;=>Y08LMDsiK>75{I>hy;oYT9(mkBs4r)2B}a zQ8T8$1W_}mJAPseXPh;CCWx9n{UeB)Gu`tuV>sj7=?g*Byy-td)comzUl_v~7ffFX zq83hP{K^>4xM+GLh*~^-BZyiuo%0)GIOEdki6Cm(^qnAT`Esi+>5U+2)AXAl zYV&l%-;CjmTc&q{sIAi<{suME{xS0UJb$@!J6GGMy5DPdcKDz3z7hcC_Y3(yvRg%qLNr%wbG*(?4t z@=pKyhtZpH=XA%vjNwxwn0Tiz{R=|&wq^JjC-dq1X25@{{&I{rw9IL z3}-wreI-?l$ix)Rcw%}c zh&nm_Ac#6OU6Pq8obmMZLT0A$=^boLywk5TGkG(fovz5j6h7U8or!1qo2QIEe&><% z{&dsSKV0AKYaT9j`F`cbs|qdC`UgLMw!ciDRCV)}nRlFkY49=`k+-=g+-HAd$a&m% zMty-;(fkLp7rNxVcZJ6!GEKk7&LlqFl$8n8v20{z3ZFiMgNfHiv-WnPs%1wy-_@e1 zpN#!^`ZN1_SFK*&*mC2Nq@2sMwVc~6HM%BwS-LR4n^7|%W^IL{ydbYyX41P42c~cP z`g12z_+FlF$i@^t-Gh^fcluN|CU3^8(;u=ig)?58ZpqFR&Uk(LL=bgj`b!XXbGjo3 zQ#j+T=`%sp?dcyu)Sc~~oJ@z6CO^;>nJl2kGF?E2iGyiT^K>~Qrl{>DI!rChOxlyS z|I%lwV`7{)z0!!uniag=d-_3rCgbS~beK*|-(t+9zLFu-$PdQ#>QO+C)Z> zN#S5}6F*1?*y#04Tn1MmQUB%MbndfEJL`Ku%}vmLXK>E}vSP0nDi#M70}WP9f{KA^ z4Uh?-S-o1QSRzy%7Xt%B6I3jT39^oan}LBLo&`Mo$B@DVSu4rIz`#(;43-aL$YO#l z2jpd7U;wRc1R0*s1X)4|+OtpyU58i;RR@|-je{;ZY=Vk`M!i5wMnS!(c96x>XP#rq zuD6CR^_U2CpEv^p!(piVCPBp{7#J8fLB%FR-6zSw!0--a76SvrOsG031_p-9P_bE1 zG0?I!PpID6AZtMz)JNDa28Jn6 zu_aIgL1UZKp<>IRdKDQM7-m7mmP7R_F)%R9hKj9W0!@+BGcYJKFfh!43a*DLRAFFX z0IjP91=<#d%TcKiV3=9n7P_b=LF?9w821%&ccBq&J0|SFObP?(fsG~I* z7#OTTu?Y&XT~I+S1_lN-M)1yDhCNU*Z3YGg9q3@%KBxnA7#J9Ip<;)jmgq7tFz7+W zjzGoq7#J7~p<+j&=IJvqF#KWwZ!>17KMqxBz`(%p5VTvBfq~%!)DqAp**1{t7#JAN zLNyyPFfepN#Xuz_$ZTT<1_oPbI=TQg&jhrG6DoEQDrU;Sz;KBLybqe;5=5*XwEYvb zi4kn%Wr!eXLkehPDOl_ZRLla@(E*DvFo5zW$YM(d28IY02GC&^4A-FQtU!)|rladn zF>6q$vVaedV7LJlvteLhcnn%e3QA-*p@O#1paCVaTTn4O1_p+c%nS^m1a%vl`t3m> z0u_4*4H*Xp28Q!cvByxojtmS87ocKKpkhu83=I32!KY|2JgtWcIx{dZEMbNu#%EA5 z(17exsMvF;m@5MVLjg1tUO>g%ppFJ5vX@XXcc>UBJ-vd8d4QIxfr5>Jf#EgO(e<7T z3=DpZ;L{Qq-a-|6F)%QIhMqz8yn~8)GcYiOK{daJiuo`wFl0l;K0w8MK@Nn9eT0hn zF)%O`LB&2nE%s+%U?^h*g+BuW!xyN+0MKG_sKT#Mu|QCn2NnAU6$@ej?N|aikAdMk z)WBd+Spik|6RIwRfq|g~D)tL177C7c@Irrv-%OB67ltqf28OAitir&+@CRyOIH+WU z*un4*syPBws6f^IhpLMNjc>DpwYhES`aZ;Vo251}c`o zz`*c|nE|wmnL!pRmI%sr%;2qm402F0(D5C7Obn3lmxl@_gU)ne0&g^7P=Jbojs3{VI_^{Rq)Du8?p zI^M+)DyRlkn8m=rAi)IQ$ikow6$71ivYHXR36wztDwe~*z+eqBgn@xU6DpR=z`$Sw z71M%><$;P|sF*fXEFa|m)u5CODq^96pzH<86`=Uig^Cq2FfeGbfOnKI=t0GbKxqML zpgyz$1|1W!hY`GEo52XGu7rVsp#l^H3=9m$P_a@528J6Ty`cCvfeL~SwEP=x?0>_G-IGceRKGcc4hGcXi0 zGcd$5GcY7DGcY7FGcbS}L!iAmpnW=C%nS_P%nS@Z%nS^^%nS^E%nS_v%nS?x%nS^H z%nS@c%nS_9%;1V3=A(=7#Lo%FfhDgVPJU8!ocu`g@NHM3j@PD7Dzj88w+I5K|2crL&x;in@stU zpuG?2EDQ{)%nS^m90hm%0j*0{WMyDbVr5`ZW@TVdVP#-YWo2McV`X4a zXJuf}U}a#?WMyE`Vr5{^W@TW|VP#;@Wo2N{V`X5_XJue8U}a!1WMyD5Vr7`Ff162= z4ZO6Ub^5y7OmU96%nacDBA`twzAOw3ek=?O&MXWJwk!+`_ACqx<}3^hpglM$Ea1(p z;JO=B*))RMZ_Eq~ZrgqDFm*FZfmU9pvobJ%)|xxAGB7x?GBB`$)}6CX|8$qh%Q1(A zfgzWLfdRBNY6B=yvM?|Nu`n=%urM%$vM?~XvM?~Xu`n>WvoJ7durM&JVuq9%pu(q# zfq}tedhI=?--a%%3=FQU3=E*%2S1n?7(f;3XD0Bzdj?S52C5uEo1Q>BszAG}?o3~K zpQ)cwXS(GBCh2-m9|hDW0X14dT@FweLk84rht`M(Sr`~ju`n=fXJKI2#lpaFo&i!H zfZ7SkEDQ`OEDQ{3EDQ`8EDQ{pEDQ`;ATeeJhID2IhD>G#hAdEEGDDjD&CCo8EzArI zoy-gjz03>@6PX#p!R`L3%nS^mw*Cxe28Nl;kTyQ3O%H0qr(iJ*n;58RAI8kU04h<< znWt+#WRhaFXJug6Gd=Mpld$Yn76yjvEDQ`k7#J9Cf_B!iK$@AL%~Pt=Qy((P+p>Z3 zAv2_5c$OK`@B=mU_A)~ncl((c80IrGFf3+Lg^RN zBOf!_Gipzt`IyO^+u;qQ?0>+>JpJ}HCRNvJCI*HQCI*IcObiS@ObiUJObiU5DLFGH z1_mA`1_n+h28IWW3=H=f85lsL^p_YJ7%nn0Fo2c-oML2P*w4touw%OG6DEDe?b9`1 zFd1)O@q|f%XS&0CCI?wiFBvqBv73>BVHYC^#%4D&#d%E-X53R(qUXJlZw#>l|1n1O-e4kH5tvUyOm+!!`6Ffi5t2@0ka zj0_Af7#SFzGcquM`en};85lrn-!L*TfJWm$;vlitj0_BKLG6Af`Be?|s|k5GI6GD6h-VPs(V&B(y;laYbp zD|Ig3Kk(~y#=bjEI{31CI$vECI$v^CI$v2CI$u#CI$vMs2RDS z=JR^caa^E=G$R9p6zEh)CI$vsCP?xG$;m)9f*hg11Syo|nIOFd&`dN)T$u^dc2;9z zU{GaZU{C?cGchn2Gchn|K`ql|Vqnl=VqnmQ@^#CI$vjLIKI$hUN`RCd|BH!34=0R!j^G)=ZE{1VtohCKEJG z>NLIfE0egW7ZU@6Cldn$$iE=tJ(w66+^4Vo$|UXK267QJ^R$7IFsMpkVqgFbjDQAA zK>gnkCI*HmCI$vrDFbq81``7VXa>@ciGjhFiGg9d;5Vl1`u`D33=E*rwFIb^Flfku z0su7i1u`m_iGd*q)Nx>9UVv8I4+X_c3=AM<5fcMLArk{b0TTlQXjCr`l$Mzo7;=~x7_yldz@vYeObiTFpuhqJ zP8kyeLn#vjLnRXfLj@B9gCZjXLpc)zLmd+XLk+a3PG@3Z01XF%90bY~pdmHTP+J2N z14A&*ZKJ`)4OG*J08 zm1+8>?@U&zphyN82pR?h4UcVRVqn6|~A zq_aT7RmYeZ7(ioL$le8oEhuO}LsB4ZcbFI$K!Y`)K^)NF4rs&(G?D}wVFC?3frg|& zLsg(rBTyWGMukA5MFLEak-G(b)9rpTsZG!L$uyJEcskQBrdN!H)8GDLGGa0^oUZtr z$ymyWp`^4Z3DjA8eC3hAAHk3W4#qemJwrX?>3P4IB&CcYo&cQ$dXwMg{GkV|XIU5_ z@(X`6NirHw-~F3OQW}&8L8CUH!&Rj`!^BgWE>D7~d;gnBQVyhmhZ%CXUw0MDj4N)& zvQPyK3=GpPM3_Ao4YxOnFb6Ph-~X3suiy3#C+4k;Y$ke!dIs*(Ykim{8T+PB_hC+F zjGq4AhgmWcevWL@@AtFI9V}TG4fV|Rj0_nVG@&QWswiGRczXN#dV~z@EZW${r&sSL zt)7LDft^;{(!d zB4id#Kkv&dY5~#?J2{tiTY8~Mb=)L`BG}ow(_ddot6^Sq4IwjqUl_BL8SHG_>Nn|U zJA_^>xrW#ySf374KwV8w_Z|90&{=>|;d^>v#b2TgD z@#+03%#zdRBr;1eiO-(CBazvNNw8}=$YSZov%zD;4K82!x2`_9zYb)orJkvt0mIkn z@=45+rmWDDb_0&vOj-76Rt7==?4;iNUsuR;@=5GM$UrS@OJX)*oHc!S60;=K4UFrz zzfWQ|XOw}R6bE?r66qM?FyO9ZH$Z;r!UW9_LYX6HoWGRnCZc9eiuO*$Wjkf=u}PT&t?|Q1u>y-&-IW|!x8&X< zWIjxvn9VGz0J0KxdU9yLL(@91N6H9AGV7)v%Vthv(pxuOFNaxz$$H&%j~wPLjGoi~ z<}kZ5hEI3SWwvHqGrc~SS<)1C(5q5__?N%?nBp`T;|%o7L16_u3Kn*1sRm=n2Jps; zhP>%#bD1TjVaFg>Y`b&y#(kv-B~Z?_1ZnM=&X~t6!e}yGERQ)y8g{Jm+t%PRPr)0L zLF!EO49)b6mrkFX$1KUXVfyww=9kir8zIK;=AEq>e&Ug{2qS289yBs|Wcu!WW=UzU zjo@=p8ua+zwqJYlWV#5WovopxpnZtaI4t)Qr8U{Hg~c=9t$3Q*fv2$C@XS;}CyX?jc{vjk)K z^wL6R?dcCnm|1g-85m$ELc@-hWnwhbGqlvRU|@iqExmo?)3D{L8@M3_KFEbV;A5&8 z8eqrLf(r(4wt<~D4Li6NlzH{QS#I-m!6If!CXHp&b&8lJ>e15-6QhBivALe1fdP7A z0UH5MEU;6xPeeU?5_K-_9}}aoo+&tk!H&J&3Ozp;o(|;5(%Shw%5vauU!r>E3a|YO{-;a3R{XV+)2!Tr% za2b!?Rnr6fnPr%b48cXQ0jRlxJ96Gn|6j~3X^Pu*xSe%sx^D@yB;(!bfu+o1EQSmW zf2TK=FpGiOPq1UeVdoJ;Bi3yCrV?f|Y0P>)4qDImOut*mEXlZgx@;-4q%;nzEsPn! zc1kjWEtF&u+BSVo3A4oX2Sv zwm|QN>P3VK*m3LK-yN5weW|{Rkg?c3eILm5IM5003=9ntt(zIQUYeGNP|>=3`lm8x zT}F%P3gyf>OpOPo_mwmIFmZHEzf;aE!IXG#`nPiC22jfY)bN=uRLLx)5@!I)?B)!x zW63jRz05Y>`0d2R7zfH*1`G_zho-NoV3v@99mVdqZOiQt1eH1Q ztwFwKN$6yR%(J7@WvZDaWPU>qux|*myPz#2E4Un?Lh#u1fNEw5GuV;$tKBkVi|p0s zAXLB(#m`At^#9t#osI~Z;A7M0RWrMA{Wt@uM=j4ze*>}&c1V5ZPMZ}bY`M?CIxO`J zjTnv`nXXX7EMW#a*Z$zsi7SM~-M=GLV2wMl-d{(i=YjO{LStyr%DSv~9IBTPda=eG zSg-NX>3eFJC1hZy*PmbynC|u>_YgvF)Y0i5K&IzG!}1WrJ9o90Ob!SYSmO?C`n03d z?P{4N%vM45CW-ywa@Nq0IM(qwY6Xe>u-KLQ)14FuP4DWMxeIA z*DKTSfoy}>0z2V;!ndUhcF8g~A@X{_)#*C*%n~x7Q~g1Q`r9vi`Nv2u?<-h^p`Hl? zL(bLd3H8hpW;j$BF)+X`04Va>`A+AC?rLxa53UwPuT5W9&uk=vqoOfkV2Hmm{ZBo! z5!0j_(+wJ!CCp%F`des*#~eN)>kRgj0mvPzZa|t}fkL{K{1>wAK%s7|XK2a5u;<3~ zI*?x2MFF{toY&f?BriayfL$e^U-n=E=QYK(2$|nErk`nGmXHyIUOrH+yhz}FPfZR& zg~rWkjm#1lUeI8K)RjgIkaE~a;xF`SkL0Kw7yZOrCV(2dpytf z1>Hw5ou!G{$P7#D8tIwoSu!xdZaA2;vZ&%^_p&x{^=|-bVY%L!p3}rEAp^S;L5f#l_q8%RoJZvOSJBFmwIY?fl~-LmOu1CMus-@Gf)4W z#3D64zL{BbdRsH|A@K+O5GlO^(CjS(1D8#yg`I-o^njmC65GpLm}{9Bji-NYW42;6 zo^H_2ykUA1Ba6bc4rUz~gKc_H2eS%-H>ZPH3C81{{vEOOHZG$msx1KhXjiNo1r17PrluOo#j6xlab+eM@|-LMnfSfUjejkizK zW07G5g@)qvbNVbMaF)FowAq7W{hA&c@5zR~tp6BaKfMziS`%vk16|ES5LG~FnS#drEW zbC%W2mU;%$dn{N&r#qczRs{)hO&73a5tx3NgGF(=qa{lwNUb)Lp~3WpZ8DYHH-fA zgVrpr+bwKZEI6jW=VK9>t{lLku)V;A<*gvdaR*;8D{ik3W4Xl6Xt+Hnj>V7>0GHSYSYgpu_$rffC|nioc=3`<&m1PF$2T?d;5#N&f5J5+`&X5 zP833N$csXV0U9YR3S0(75W#>VkO9)ub5mHnxjUeOGm2QibCA%!Lv8kUHoCWUdZxJ01IUF+P+^9kGue@U PxeIfR9!x)!%CZaqW{;cP delta 26249 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 Date: Thu, 17 Oct 2024 16:56:34 +0000 Subject: [PATCH 291/312] chore(release): 1.0.0-dev.34 [skip ci] # @revanced/discord-bot [1.0.0-dev.34](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.33...@revanced/discord-bot@1.0.0-dev.34) (2024-10-17) ### Bug Fixes * **bots/discord:** attempt to fix stuck sticky message timeouts ([3ed5bd1](https://github.com/revanced/revanced-helper/commit/3ed5bd11acc3b4fbd57b0d632c68eb9f77365b8a)) * **bots/discord:** fix reload not working ([11582d5](https://github.com/revanced/revanced-helper/commit/11582d50345cae9fb645a65ca4e621596de6a408)) ### Features * **bots/discord:** add default durations for moderation commands ([27d3b39](https://github.com/revanced/revanced-helper/commit/27d3b392092141a1e3b4b0298131ff7817458dc1)) * **bots/discord:** cure on every event ([8ff6086](https://github.com/revanced/revanced-helper/commit/8ff6086028132cc4b49ee60846e8d6ef909f5a89)) --- bots/discord/CHANGELOG.md | 14 ++++++++++++++ bots/discord/package.json | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index bf5d276..d9d20c7 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,17 @@ +# @revanced/discord-bot [1.0.0-dev.34](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.33...@revanced/discord-bot@1.0.0-dev.34) (2024-10-17) + + +### Bug Fixes + +* **bots/discord:** attempt to fix stuck sticky message timeouts ([3ed5bd1](https://github.com/revanced/revanced-helper/commit/3ed5bd11acc3b4fbd57b0d632c68eb9f77365b8a)) +* **bots/discord:** fix reload not working ([11582d5](https://github.com/revanced/revanced-helper/commit/11582d50345cae9fb645a65ca4e621596de6a408)) + + +### Features + +* **bots/discord:** add default durations for moderation commands ([27d3b39](https://github.com/revanced/revanced-helper/commit/27d3b392092141a1e3b4b0298131ff7817458dc1)) +* **bots/discord:** cure on every event ([8ff6086](https://github.com/revanced/revanced-helper/commit/8ff6086028132cc4b49ee60846e8d6ef909f5a89)) + # @revanced/discord-bot [1.0.0-dev.33](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.32...@revanced/discord-bot@1.0.0-dev.33) (2024-09-25) diff --git a/bots/discord/package.json b/bots/discord/package.json index 62e1db7..51ea658 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.33", + "version": "1.0.0-dev.34", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.102", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file From 79fea8b2867ed873d83854333b6fb82817e30c3b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 18 Oct 2024 00:01:16 +0700 Subject: [PATCH 292/312] fix: update patches for dependencies --- bun.lockb | Bin 291848 -> 291856 bytes package.json | 14 +++++++------- ...ancer@3.2.3.patch => decancer@3.2.4.patch} | 0 3 files changed, 7 insertions(+), 7 deletions(-) rename patches/{decancer@3.2.3.patch => decancer@3.2.4.patch} (100%) diff --git a/bun.lockb b/bun.lockb index cf870a61106c5d93164117902ca22a926da1e79f..9637065e7518eb76f5ce187b6a0354c634b11bb6 100755 GIT binary patch delta 210 zcmeBpAvocM-~>O$hDQHZf5ujSrmgtQyVUee4gHr=3?nPq!lGqV;CqwV&c z{mgj}3{3P25=)XZ^72zs6*BXROA>Q(^sprG8p$h$Zo(Yd041uQb7VEU>e7RBiW z1uP1j51@hzrq3*3`OkTw5TfS8^o4~i?wl4y5N^cukA*DhoD-nj1=ACYSnN4(K)D~L duPkCQ7c}_5@*^ehshveJL{r1|k3}rvi~u6DKlcCt delta 201 zcmbR6La^h7-~>O$ibnrdf5ujSrmgV= zqOo2 Date: Fri, 18 Oct 2024 01:32:42 +0700 Subject: [PATCH 293/312] fix(bots/discord): fix freeze on prod builds --- bots/discord/src/config.ts | 28 ++++++++++++++++++++++++++ bots/discord/src/context.ts | 39 +++++++------------------------------ 2 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 bots/discord/src/config.ts diff --git a/bots/discord/src/config.ts b/bots/discord/src/config.ts new file mode 100644 index 0000000..d308eef --- /dev/null +++ b/bots/discord/src/config.ts @@ -0,0 +1,28 @@ +import { dirname, join } from 'path' +import _firstConfig from '../config.js' + +let currentConfig = _firstConfig + +// Other parts of the code will access properties of this proxy, they don't care what the target looks like +export const config = new Proxy( + { + INSPECTION_WARNING: 'Run `context.__getConfig()` to inspect the latest config.', + } as unknown as typeof currentConfig, + { + get(_, p, receiver) { + if (p === 'invalidate') + return async () => { + const path = join(dirname(Bun.main), '..', 'config.js') + Loader.registry.delete(path) + currentConfig = (await import(path)).default + } + + return Reflect.get(currentConfig, p, receiver) + }, + set(_, p, newValue, receiver) { + return Reflect.set(currentConfig, p, newValue, receiver) + }, + }, +) as typeof _firstConfig & { invalidate(): void } + +export const __getConfig = () => currentConfig diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index 30bed63..e444ce2 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -1,49 +1,24 @@ import { Database } from 'bun:sqlite' import { existsSync, readFileSync, readdirSync } from 'fs' -import { dirname, join } from 'path' +import { join } from 'path' import { Client as APIClient } from '@revanced/bot-api' import { createLogger } from '@revanced/bot-shared' import { Client as DiscordClient, type Message, Partials } from 'discord.js' import { drizzle } from 'drizzle-orm/bun-sqlite' -import type { default as Command, CommandOptionsOptions, CommandType } from './classes/Command' + import * as schemas from './database/schemas' -// Export some things first, as commands require them -import _firstConfig from '../config.js' +import type { default as Command, CommandOptionsOptions, CommandType } from './classes/Command' -let currentConfig = _firstConfig - -// Other parts of the code will access properties of this proxy, they don't care what the target looks like -export const config = new Proxy( - { - INSPECTION_WARNING: 'Run `context.__getConfig()` to inspect the latest config.', - } as unknown as typeof currentConfig, - { - get(_, p, receiver) { - if (p === 'invalidate') - return async () => { - const path = join(dirname(Bun.main), '..', 'config.js') - Loader.registry.delete(path) - currentConfig = (await import(path)).default - logger.debug('New config set') - } - - return Reflect.get(currentConfig, p, receiver) - }, - set(_, p, newValue, receiver) { - return Reflect.set(currentConfig, p, newValue, receiver) - }, - }, -) as typeof _firstConfig & { invalidate(): void } - -export const __getConfig = () => currentConfig +import { __getConfig, config } from './config' +export { config, __getConfig } export const logger = createLogger({ level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel, }) -// Importing later because config needs to be exported before -const commands = await import('./commands') +// Export a few things before we initialize commands +import * as commands from './commands' export const api = { client: new APIClient({ From 14d301eeb485c1009ecfbd5b803f14eaca18a306 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 17 Oct 2024 18:34:04 +0000 Subject: [PATCH 294/312] chore(release): 1.0.0-dev.35 [skip ci] # @revanced/discord-bot [1.0.0-dev.35](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.34...@revanced/discord-bot@1.0.0-dev.35) (2024-10-17) ### Bug Fixes * **bots/discord:** fix freeze on prod builds ([8efb549](https://github.com/revanced/revanced-helper/commit/8efb549453a04fab1ac6414a7f7f8bf702df3c93)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index d9d20c7..4d4f0c6 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.35](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.34...@revanced/discord-bot@1.0.0-dev.35) (2024-10-17) + + +### Bug Fixes + +* **bots/discord:** fix freeze on prod builds ([8efb549](https://github.com/revanced/revanced-helper/commit/8efb549453a04fab1ac6414a7f7f8bf702df3c93)) + # @revanced/discord-bot [1.0.0-dev.34](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.33...@revanced/discord-bot@1.0.0-dev.34) (2024-10-17) diff --git a/bots/discord/package.json b/bots/discord/package.json index 51ea658..0718c78 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.34", + "version": "1.0.0-dev.35", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 22d3eea88d532792c1237d1a1ab18bc02e57816a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 30 Oct 2024 18:50:22 +0700 Subject: [PATCH 295/312] fix(bots/discord): decrease length of an option in `ban` command --- bots/discord/src/commands/moderation/ban.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/commands/moderation/ban.ts b/bots/discord/src/commands/moderation/ban.ts index d584cd7..c664519 100644 --- a/bots/discord/src/commands/moderation/ban.ts +++ b/bots/discord/src/commands/moderation/ban.ts @@ -20,7 +20,7 @@ export default new ModerationCommand({ }, dmt: { description: - 'Time duration to delete messages (default time unit is days, must be from 0s to 7d, default value is 0s)', + 'Time duration to delete messages (default time unit is days, must be from 0s to 7d, default is 0s)', required: false, type: ModerationCommand.OptionType.String, }, From c2009ca6d42e4387bc5f375d76ecf72991b7fe32 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 30 Oct 2024 18:56:54 +0700 Subject: [PATCH 296/312] feat(bots/discord): add more month aliases to duration parser --- bots/discord/src/utils/duration.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bots/discord/src/utils/duration.ts b/bots/discord/src/utils/duration.ts index ede2d76..c629c47 100644 --- a/bots/discord/src/utils/duration.ts +++ b/bots/discord/src/utils/duration.ts @@ -1,5 +1,7 @@ import parse from 'parse-duration' +parse['mo'] = parse['M'] = parse['month']! + const defaultUnitValue = parse['']! export const parseDuration = (duration: string, defaultUnit?: parse.Units) => { From 8e3946a66602838715787090008c7bfaf72b67e9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 4 Mar 2025 02:09:59 +0700 Subject: [PATCH 297/312] fix(bots/discord): add GuildMember partial --- bots/discord/src/context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index e444ce2..c63468b 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -80,7 +80,7 @@ export const discord = { parse: ['users'], repliedUser: true, }, - partials: [Partials.Message, Partials.Reaction], + partials: [Partials.Message, Partials.Reaction, Partials.GuildMember], }), commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.name, cmd])) as Record< string, From 14c98e87df1ec4fd762bbc48ca4c06470cb110a2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 4 Mar 2025 02:15:19 +0700 Subject: [PATCH 298/312] fix(bots/discord): delete expired appliedPresets entries after unapplying --- bots/discord/src/events/discord/ready.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bots/discord/src/events/discord/ready.ts b/bots/discord/src/events/discord/ready.ts index a719d4b..6d54ab8 100644 --- a/bots/discord/src/events/discord/ready.ts +++ b/bots/discord/src/events/discord/ready.ts @@ -3,7 +3,7 @@ import { appliedPresets } from '$/database/schemas' import { applyCommonEmbedStyles } from '$/utils/discord/embeds' import { on, withContext } from '$/utils/discord/events' import { removeRolePreset } from '$/utils/discord/rolePresets' -import { lt } from 'drizzle-orm' +import { and, eq, lt } from 'drizzle-orm' import type { Client } from 'discord.js' @@ -92,11 +92,15 @@ const removeExpiredPresets = async (client: Client) => { for (const expired of expireds) try { + logger.debug(`Removing role preset for ${expired.memberId} in ${expired.guildId}`) + const guild = await client.guilds.fetch(expired.guildId) const member = await guild.members.fetch(expired.memberId) - logger.debug(`Removing role preset for ${expired.memberId} in ${expired.guildId}`) await removeRolePreset(member, expired.preset) + await database + .delete(appliedPresets) + .where(and(eq(appliedPresets.guildId, expired.guildId), eq(appliedPresets.memberId, expired.memberId))) } catch (e) { logger.error(`Error while removing role preset for ${expired.memberId} in ${expired.guildId}: ${e}`) } From 5d1af3c31c3379b6a13684dfb07583737908c8aa Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 4 Mar 2025 02:17:59 +0700 Subject: [PATCH 299/312] fix(bots/discord/utils/duration): make second the default unit --- bots/discord/src/utils/duration.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/bots/discord/src/utils/duration.ts b/bots/discord/src/utils/duration.ts index c629c47..8e3074a 100644 --- a/bots/discord/src/utils/duration.ts +++ b/bots/discord/src/utils/duration.ts @@ -1,5 +1,6 @@ import parse from 'parse-duration' +parse[''] = parse['s'] parse['mo'] = parse['M'] = parse['month']! const defaultUnitValue = parse['']! From f6119946f89d0512b85e7e8b74e3ecfbeec1ea1d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 4 Mar 2025 02:19:06 +0700 Subject: [PATCH 300/312] chore: update deps --- bun.lock | 1892 ++++++++++++++++++++++++++++++++++++++++++++++++++ bun.lockb | Bin 291856 -> 0 bytes package.json | 20 +- 3 files changed, 1902 insertions(+), 10 deletions(-) create mode 100644 bun.lock delete mode 100755 bun.lockb diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..9acd2b6 --- /dev/null +++ b/bun.lock @@ -0,0 +1,1892 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "revanced-helper", + "devDependencies": { + "@anolilab/multi-semantic-release": "^1.1.10", + "@biomejs/biome": "^1.9.4", + "@codedependant/semantic-release-docker": "^5.1.0", + "@commitlint/cli": "^19.7.1", + "@commitlint/config-conventional": "^19.7.1", + "@saithodev/semantic-release-backmerge": "^4.0.1", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/exec": "^6.0.3", + "@semantic-release/git": "^10.0.1", + "@tsconfig/strictest": "^2.0.5", + "@types/bun": "^1.2.4", + "conventional-changelog-conventionalcommits": "^7.0.2", + "lefthook": "^1.11.2", + "portainer-service-webhook": "https://github.com/newarifrh/portainer-service-webhook#v1", + "semantic-release": "^24.2.3", + "turbo": "^2.4.4", + "typescript": "^5.8.2", + }, + }, + "apis/websocket": { + "name": "@revanced/bot-websocket-api", + "version": "1.0.0-dev.9", + "dependencies": { + "@revanced/bot-shared": "workspace:*", + "@sapphire/async-queue": "^1.5.3", + "chalk": "^5.3.0", + "tesseract.js": "^5.1.1", + "ws": "^8.18.0", + }, + "devDependencies": { + "@types/ws": "^8.5.12", + "typed-emitter": "^2.1.0", + }, + }, + "bots/discord": { + "name": "@revanced/discord-bot", + "version": "1.0.0-dev.35", + "dependencies": { + "@discordjs/builders": "^1.9.0", + "@discordjs/rest": "^2.4.0", + "@revanced/bot-api": "workspace:*", + "@revanced/bot-shared": "workspace:*", + "chalk": "^5.3.0", + "decancer": "^3.2.4", + "discord.js": "^14.16.3", + "drizzle-orm": "^0.31.4", + "parse-duration": "^1.1.0", + }, + "devDependencies": { + "@libsql/client": "^0.7.0", + "discord-api-types": "^0.37.102", + "drizzle-kit": "^0.22.8", + }, + }, + "packages/api": { + "name": "@revanced/bot-api", + "version": "0.1.0", + "dependencies": { + "@revanced/bot-shared": "workspace:*", + "ws": "^8.18.0", + }, + "devDependencies": { + "@types/ws": "^8.5.12", + "typed-emitter": "^2.1.0", + }, + }, + "packages/shared": { + "name": "@revanced/bot-shared", + "version": "0.1.0", + "dependencies": { + "bson": "^6.8.0", + "chalk": "^5.3.0", + "tracer": "^1.3.0", + "valibot": "^0.30.0", + }, + }, + }, + "trustedDependencies": [ + "@revanced/discord-bot", + "esbuild", + "@biomejs/biome", + "lefthook", + ], + "patchedDependencies": { + "@semantic-release/npm@12.0.1": "patches/@semantic-release%2Fnpm@12.0.1.patch", + "drizzle-kit@0.22.8": "patches/drizzle-kit@0.22.8.patch", + "decancer@3.2.4": "patches/decancer@3.2.4.patch", + }, + "packages": { + "@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="], + + "@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="], + + "@actions/http-client": ["@actions/http-client@2.2.1", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw=="], + + "@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], + + "@anolilab/multi-semantic-release": ["@anolilab/multi-semantic-release@1.1.10", "", { "dependencies": { "@semrel-extra/topo": "^1.14.1", "blork": "^9.3.0", "cosmiconfig": "^9.0.0", "debug": "^4.4.0", "detect-indent": "^7.0.1", "detect-newline": "^4.0.1", "execa": "^9.5.2", "git-log-parser": "^1.2.1", "lodash-es": "^4.17.21", "resolve-from": "^5.0.0", "semver": "^7.7.1", "signale": "^1.4.0", "stream-buffers": "^3.0.3", "yargs": "^17.7.2" }, "peerDependencies": { "semantic-release": "^20.0 || ^21.0 || >=22.0.3" }, "os": [ "linux", "win32", "darwin", ], "bin": { "multi-semantic-release": "bin/cli.js" } }, "sha512-00eUBYDMEBWzZygcMYi7ek0gJJD2hKldSucDY4VVEbyd3K1tJU3N8Tmx6EZ2HYu7tzrlMLQGnHgbyrJA6TyZ0w=="], + + "@babel/code-frame": ["@babel/code-frame@7.24.7", "", { "dependencies": { "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" } }, "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.24.7", "", {}, "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w=="], + + "@babel/highlight": ["@babel/highlight@7.24.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw=="], + + "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], + + "@codedependant/semantic-release-docker": ["@codedependant/semantic-release-docker@5.1.0", "", { "dependencies": { "@actions/core": "^1.11.1", "@semantic-release/error": "^3.0.0", "debug": "^4.1.1", "execa": "^4.0.2", "handlebars": "^4.7.7", "semver": "^7.3.2" } }, "sha512-Ok37Hrj3y2AeZA4nBHzXNPR+twZHbAnGY2vXV3V3MC9xP676PnP67eBpzP5zLodxBXqRFKixo8QiQhQJ8nnXbQ=="], + + "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], + + "@commitlint/cli": ["@commitlint/cli@19.7.1", "", { "dependencies": { "@commitlint/format": "^19.5.0", "@commitlint/lint": "^19.7.1", "@commitlint/load": "^19.6.1", "@commitlint/read": "^19.5.0", "@commitlint/types": "^19.5.0", "tinyexec": "^0.3.0", "yargs": "^17.0.0" }, "bin": { "commitlint": "cli.js" } }, "sha512-iObGjR1tE/PfDtDTEfd+tnRkB3/HJzpQqRTyofS2MPPkDn1mp3DBC8SoPDayokfAy+xKhF8+bwRCJO25Nea0YQ=="], + + "@commitlint/config-conventional": ["@commitlint/config-conventional@19.7.1", "", { "dependencies": { "@commitlint/types": "^19.5.0", "conventional-changelog-conventionalcommits": "^7.0.2" } }, "sha512-fsEIF8zgiI/FIWSnykdQNj/0JE4av08MudLTyYHm4FlLWemKoQvPNUYU2M/3tktWcCEyq7aOkDDgtjrmgWFbvg=="], + + "@commitlint/config-validator": ["@commitlint/config-validator@19.5.0", "", { "dependencies": { "@commitlint/types": "^19.5.0", "ajv": "^8.11.0" } }, "sha512-CHtj92H5rdhKt17RmgALhfQt95VayrUo2tSqY9g2w+laAXyk7K/Ef6uPm9tn5qSIwSmrLjKaXK9eiNuxmQrDBw=="], + + "@commitlint/ensure": ["@commitlint/ensure@19.5.0", "", { "dependencies": { "@commitlint/types": "^19.5.0", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1", "lodash.startcase": "^4.4.0", "lodash.upperfirst": "^4.3.1" } }, "sha512-Kv0pYZeMrdg48bHFEU5KKcccRfKmISSm9MvgIgkpI6m+ohFTB55qZlBW6eYqh/XDfRuIO0x4zSmvBjmOwWTwkg=="], + + "@commitlint/execute-rule": ["@commitlint/execute-rule@19.5.0", "", {}, "sha512-aqyGgytXhl2ejlk+/rfgtwpPexYyri4t8/n4ku6rRJoRhGZpLFMqrZ+YaubeGysCP6oz4mMA34YSTaSOKEeNrg=="], + + "@commitlint/format": ["@commitlint/format@19.5.0", "", { "dependencies": { "@commitlint/types": "^19.5.0", "chalk": "^5.3.0" } }, "sha512-yNy088miE52stCI3dhG/vvxFo9e4jFkU1Mj3xECfzp/bIS/JUay4491huAlVcffOoMK1cd296q0W92NlER6r3A=="], + + "@commitlint/is-ignored": ["@commitlint/is-ignored@19.7.1", "", { "dependencies": { "@commitlint/types": "^19.5.0", "semver": "^7.6.0" } }, "sha512-3IaOc6HVg2hAoGleRK3r9vL9zZ3XY0rf1RsUf6jdQLuaD46ZHnXBiOPTyQ004C4IvYjSWqJwlh0/u2P73aIE3g=="], + + "@commitlint/lint": ["@commitlint/lint@19.7.1", "", { "dependencies": { "@commitlint/is-ignored": "^19.7.1", "@commitlint/parse": "^19.5.0", "@commitlint/rules": "^19.6.0", "@commitlint/types": "^19.5.0" } }, "sha512-LhcPfVjcOcOZA7LEuBBeO00o3MeZa+tWrX9Xyl1r9PMd5FWsEoZI9IgnGqTKZ0lZt5pO3ZlstgnRyY1CJJc9Xg=="], + + "@commitlint/load": ["@commitlint/load@19.6.1", "", { "dependencies": { "@commitlint/config-validator": "^19.5.0", "@commitlint/execute-rule": "^19.5.0", "@commitlint/resolve-extends": "^19.5.0", "@commitlint/types": "^19.5.0", "chalk": "^5.3.0", "cosmiconfig": "^9.0.0", "cosmiconfig-typescript-loader": "^6.1.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "lodash.uniq": "^4.5.0" } }, "sha512-kE4mRKWWNju2QpsCWt428XBvUH55OET2N4QKQ0bF85qS/XbsRGG1MiTByDNlEVpEPceMkDr46LNH95DtRwcsfA=="], + + "@commitlint/message": ["@commitlint/message@19.5.0", "", {}, "sha512-R7AM4YnbxN1Joj1tMfCyBryOC5aNJBdxadTZkuqtWi3Xj0kMdutq16XQwuoGbIzL2Pk62TALV1fZDCv36+JhTQ=="], + + "@commitlint/parse": ["@commitlint/parse@19.5.0", "", { "dependencies": { "@commitlint/types": "^19.5.0", "conventional-changelog-angular": "^7.0.0", "conventional-commits-parser": "^5.0.0" } }, "sha512-cZ/IxfAlfWYhAQV0TwcbdR1Oc0/r0Ik1GEessDJ3Lbuma/MRO8FRQX76eurcXtmhJC//rj52ZSZuXUg0oIX0Fw=="], + + "@commitlint/read": ["@commitlint/read@19.5.0", "", { "dependencies": { "@commitlint/top-level": "^19.5.0", "@commitlint/types": "^19.5.0", "git-raw-commits": "^4.0.0", "minimist": "^1.2.8", "tinyexec": "^0.3.0" } }, "sha512-TjS3HLPsLsxFPQj6jou8/CZFAmOP2y+6V4PGYt3ihbQKTY1Jnv0QG28WRKl/d1ha6zLODPZqsxLEov52dhR9BQ=="], + + "@commitlint/resolve-extends": ["@commitlint/resolve-extends@19.5.0", "", { "dependencies": { "@commitlint/config-validator": "^19.5.0", "@commitlint/types": "^19.5.0", "global-directory": "^4.0.1", "import-meta-resolve": "^4.0.0", "lodash.mergewith": "^4.6.2", "resolve-from": "^5.0.0" } }, "sha512-CU/GscZhCUsJwcKTJS9Ndh3AKGZTNFIOoQB2n8CmFnizE0VnEuJoum+COW+C1lNABEeqk6ssfc1Kkalm4bDklA=="], + + "@commitlint/rules": ["@commitlint/rules@19.6.0", "", { "dependencies": { "@commitlint/ensure": "^19.5.0", "@commitlint/message": "^19.5.0", "@commitlint/to-lines": "^19.5.0", "@commitlint/types": "^19.5.0" } }, "sha512-1f2reW7lbrI0X0ozZMesS/WZxgPa4/wi56vFuJENBmed6mWq5KsheN/nxqnl/C23ioxpPO/PL6tXpiiFy5Bhjw=="], + + "@commitlint/to-lines": ["@commitlint/to-lines@19.5.0", "", {}, "sha512-R772oj3NHPkodOSRZ9bBVNq224DOxQtNef5Pl8l2M8ZnkkzQfeSTr4uxawV2Sd3ui05dUVzvLNnzenDBO1KBeQ=="], + + "@commitlint/top-level": ["@commitlint/top-level@19.5.0", "", { "dependencies": { "find-up": "^7.0.0" } }, "sha512-IP1YLmGAk0yWrImPRRc578I3dDUI5A2UBJx9FbSOjxe9sTlzFiwVJ+zeMLgAtHMtGZsC8LUnzmW1qRemkFU4ng=="], + + "@commitlint/types": ["@commitlint/types@19.5.0", "", { "dependencies": { "@types/conventional-commits-parser": "^5.0.0", "chalk": "^5.3.0" } }, "sha512-DSHae2obMSMkAtTBSOulg5X7/z+rGLxcXQIkg3OmWvY6wifojge5uVMydfhUvs7yQj+V7jNmRZ2Xzl8GJyqRgg=="], + + "@discordjs/builders": ["@discordjs/builders@1.9.0", "", { "dependencies": { "@discordjs/formatters": "^0.5.0", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "0.37.97", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg=="], + + "@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], + + "@discordjs/formatters": ["@discordjs/formatters@0.5.0", "", { "dependencies": { "discord-api-types": "0.37.97" } }, "sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g=="], + + "@discordjs/rest": ["@discordjs/rest@2.4.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "0.37.97", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.19.8" } }, "sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw=="], + + "@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="], + + "@discordjs/ws": ["@discordjs/ws@1.1.1", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.3.0", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "0.37.83", "tslib": "^2.6.2", "ws": "^8.16.0" } }, "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], + + "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + + "@libsql/client": ["@libsql/client@0.7.0", "", { "dependencies": { "@libsql/core": "^0.7.0", "@libsql/hrana-client": "^0.6.0", "js-base64": "^3.7.5", "libsql": "^0.3.10", "promise-limit": "^2.7.0" } }, "sha512-1aLDtWzsErr68GZ640TVyOLkL/+lB2YK8cYvJIfiI7Mt+DEPB22Jkoz2QfBDg5PVGX/efeRHog2j/oVbUhxO8Q=="], + + "@libsql/core": ["@libsql/core@0.7.0", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-hCYfXa0S4t9CJtxZIWacboylrcx94ZQO0dEngH4f0f/LHg6ymHSZiubbojAwD7tNy94ahICoGPjEi6aEIyhlcQ=="], + + "@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.3.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rmOqsLcDI65zzxlUOoEiPJLhqmbFsZF6p4UJQ2kMqB+Kc0Rt5/A1OAdOZ/Wo8fQfJWjR1IbkbpEINFioyKf+nQ=="], + + "@libsql/darwin-x64": ["@libsql/darwin-x64@0.3.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-q9O55B646zU+644SMmOQL3FIfpmEvdWpRpzubwFc2trsa+zoBlSkHuzU9v/C+UNoPHQVRMP7KQctJ455I/h/xw=="], + + "@libsql/hrana-client": ["@libsql/hrana-client@0.6.2", "", { "dependencies": { "@libsql/isomorphic-fetch": "^0.2.1", "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw=="], + + "@libsql/isomorphic-fetch": ["@libsql/isomorphic-fetch@0.2.1", "", {}, "sha512-Sv07QP1Aw8A5OOrmKgRUBKe2fFhF2hpGJhtHe3d1aRnTESZCGkn//0zDycMKTGamVWb3oLYRroOsCV8Ukes9GA=="], + + "@libsql/isomorphic-ws": ["@libsql/isomorphic-ws@0.1.5", "", { "dependencies": { "@types/ws": "^8.5.4", "ws": "^8.13.0" } }, "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg=="], + + "@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.3.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-mgeAUU1oqqh57k7I3cQyU6Trpdsdt607eFyEmH5QO7dv303ti+LjUvh1pp21QWV6WX7wZyjeJV1/VzEImB+jRg=="], + + "@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.3.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-VEZtxghyK6zwGzU9PHohvNxthruSxBEnRrX7BSL5jQ62tN4n2JNepJ6SdzXp70pdzTfwroOj/eMwiPt94gkVRg=="], + + "@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.3.19", "", { "os": "linux", "cpu": "x64" }, "sha512-2t/J7LD5w2f63wGihEO+0GxfTyYIyLGEvTFEsMO16XI5o7IS9vcSHrxsvAJs4w2Pf907uDjmc7fUfMg6L82BrQ=="], + + "@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.3.19", "", { "os": "linux", "cpu": "x64" }, "sha512-BLsXyJaL8gZD8+3W2LU08lDEd9MIgGds0yPy5iNPp8tfhXx3pV/Fge2GErN0FC+nzt4DYQtjL+A9GUMglQefXQ=="], + + "@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.3.19", "", { "os": "win32", "cpu": "x64" }, "sha512-ay1X9AobE4BpzG0XPw1gplyLZPGHIgJOovvW23gUrukRegiUP62uzhpRbKNogLlUOynyXeq//prHgPXiebUfWg=="], + + "@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@npmcli/agent": ["@npmcli/agent@2.2.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og=="], + + "@npmcli/arborist": ["@npmcli/arborist@7.5.4", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^3.1.1", "@npmcli/installed-package-contents": "^2.1.0", "@npmcli/map-workspaces": "^3.0.2", "@npmcli/metavuln-calculator": "^7.1.1", "@npmcli/name-from-folder": "^2.0.0", "@npmcli/node-gyp": "^3.0.0", "@npmcli/package-json": "^5.1.0", "@npmcli/query": "^3.1.0", "@npmcli/redact": "^2.0.0", "@npmcli/run-script": "^8.1.0", "bin-links": "^4.0.4", "cacache": "^18.0.3", "common-ancestor-path": "^1.0.1", "hosted-git-info": "^7.0.2", "json-parse-even-better-errors": "^3.0.2", "json-stringify-nice": "^1.1.4", "lru-cache": "^10.2.2", "minimatch": "^9.0.4", "nopt": "^7.2.1", "npm-install-checks": "^6.2.0", "npm-package-arg": "^11.0.2", "npm-pick-manifest": "^9.0.1", "npm-registry-fetch": "^17.0.1", "pacote": "^18.0.6", "parse-conflict-json": "^3.0.0", "proc-log": "^4.2.0", "proggy": "^2.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", "ssri": "^10.0.6", "treeverse": "^3.0.0", "walk-up-path": "^3.0.1" }, "bin": { "arborist": "bin/index.js" } }, "sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g=="], + + "@npmcli/config": ["@npmcli/config@8.3.4", "", { "dependencies": { "@npmcli/map-workspaces": "^3.0.2", "@npmcli/package-json": "^5.1.1", "ci-info": "^4.0.0", "ini": "^4.1.2", "nopt": "^7.2.1", "proc-log": "^4.2.0", "semver": "^7.3.5", "walk-up-path": "^3.0.1" } }, "sha512-01rtHedemDNhUXdicU7s+QYz/3JyV5Naj84cvdXGH4mgCdL+agmSYaLF4LUG4vMCLzhBO8YtS0gPpH1FGvbgAw=="], + + "@npmcli/fs": ["@npmcli/fs@3.1.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg=="], + + "@npmcli/git": ["@npmcli/git@5.0.8", "", { "dependencies": { "@npmcli/promise-spawn": "^7.0.0", "ini": "^4.1.3", "lru-cache": "^10.0.1", "npm-pick-manifest": "^9.0.0", "proc-log": "^4.0.0", "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^4.0.0" } }, "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ=="], + + "@npmcli/installed-package-contents": ["@npmcli/installed-package-contents@2.1.0", "", { "dependencies": { "npm-bundled": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" }, "bin": { "installed-package-contents": "bin/index.js" } }, "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w=="], + + "@npmcli/map-workspaces": ["@npmcli/map-workspaces@3.0.6", "", { "dependencies": { "@npmcli/name-from-folder": "^2.0.0", "glob": "^10.2.2", "minimatch": "^9.0.0", "read-package-json-fast": "^3.0.0" } }, "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA=="], + + "@npmcli/metavuln-calculator": ["@npmcli/metavuln-calculator@7.1.1", "", { "dependencies": { "cacache": "^18.0.0", "json-parse-even-better-errors": "^3.0.0", "pacote": "^18.0.0", "proc-log": "^4.1.0", "semver": "^7.3.5" } }, "sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g=="], + + "@npmcli/name-from-folder": ["@npmcli/name-from-folder@2.0.0", "", {}, "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg=="], + + "@npmcli/node-gyp": ["@npmcli/node-gyp@3.0.0", "", {}, "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA=="], + + "@npmcli/package-json": ["@npmcli/package-json@5.2.0", "", { "dependencies": { "@npmcli/git": "^5.0.0", "glob": "^10.2.2", "hosted-git-info": "^7.0.0", "json-parse-even-better-errors": "^3.0.0", "normalize-package-data": "^6.0.0", "proc-log": "^4.0.0", "semver": "^7.5.3" } }, "sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ=="], + + "@npmcli/promise-spawn": ["@npmcli/promise-spawn@7.0.2", "", { "dependencies": { "which": "^4.0.0" } }, "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ=="], + + "@npmcli/query": ["@npmcli/query@3.1.0", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" } }, "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ=="], + + "@npmcli/redact": ["@npmcli/redact@2.0.1", "", {}, "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw=="], + + "@npmcli/run-script": ["@npmcli/run-script@8.1.0", "", { "dependencies": { "@npmcli/node-gyp": "^3.0.0", "@npmcli/package-json": "^5.0.0", "@npmcli/promise-spawn": "^7.0.0", "node-gyp": "^10.0.0", "proc-log": "^4.0.0", "which": "^4.0.0" } }, "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg=="], + + "@octokit/auth-token": ["@octokit/auth-token@5.1.1", "", {}, "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA=="], + + "@octokit/core": ["@octokit/core@6.1.2", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.0.0", "@octokit/request": "^9.0.0", "@octokit/request-error": "^6.0.1", "@octokit/types": "^13.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg=="], + + "@octokit/endpoint": ["@octokit/endpoint@10.1.1", "", { "dependencies": { "@octokit/types": "^13.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q=="], + + "@octokit/graphql": ["@octokit/graphql@8.1.1", "", { "dependencies": { "@octokit/request": "^9.0.0", "@octokit/types": "^13.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg=="], + + "@octokit/openapi-types": ["@octokit/openapi-types@22.2.0", "", {}, "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="], + + "@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@11.3.3", "", { "dependencies": { "@octokit/types": "^13.5.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA=="], + + "@octokit/plugin-retry": ["@octokit/plugin-retry@7.1.1", "", { "dependencies": { "@octokit/request-error": "^6.0.0", "@octokit/types": "^13.0.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw=="], + + "@octokit/plugin-throttling": ["@octokit/plugin-throttling@9.3.1", "", { "dependencies": { "@octokit/types": "^13.0.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": "^6.0.0" } }, "sha512-Qd91H4liUBhwLB2h6jZ99bsxoQdhgPk6TdwnClPyTBSDAdviGPceViEgUwj+pcQDmB/rfAXAXK7MTochpHM3yQ=="], + + "@octokit/request": ["@octokit/request@9.1.3", "", { "dependencies": { "@octokit/endpoint": "^10.0.0", "@octokit/request-error": "^6.0.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^7.0.2" } }, "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA=="], + + "@octokit/request-error": ["@octokit/request-error@6.1.4", "", { "dependencies": { "@octokit/types": "^13.0.0" } }, "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg=="], + + "@octokit/types": ["@octokit/types@13.5.0", "", { "dependencies": { "@octokit/openapi-types": "^22.2.0" } }, "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@pnpm/config.env-replace": ["@pnpm/config.env-replace@1.1.0", "", {}, "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w=="], + + "@pnpm/network.ca-file": ["@pnpm/network.ca-file@1.0.2", "", { "dependencies": { "graceful-fs": "4.2.10" } }, "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA=="], + + "@pnpm/npm-conf": ["@pnpm/npm-conf@2.2.2", "", { "dependencies": { "@pnpm/config.env-replace": "^1.1.0", "@pnpm/network.ca-file": "^1.0.1", "config-chain": "^1.1.11" } }, "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA=="], + + "@revanced/bot-api": ["@revanced/bot-api@workspace:packages/api"], + + "@revanced/bot-shared": ["@revanced/bot-shared@workspace:packages/shared"], + + "@revanced/bot-websocket-api": ["@revanced/bot-websocket-api@workspace:apis/websocket"], + + "@revanced/discord-bot": ["@revanced/discord-bot@workspace:bots/discord"], + + "@saithodev/semantic-release-backmerge": ["@saithodev/semantic-release-backmerge@4.0.1", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.1.0", "debug": "^4.3.4", "execa": "^5.1.1", "lodash": "^4.17.21", "semantic-release": "^22.0.7" } }, "sha512-WDsU28YrXSLx0xny7FgFlEk8DCKGcj6OOhA+4Q9k3te1jJD1GZuqY8sbIkVQaw9cqJ7CT+fCZUN6QDad8JW4Dg=="], + + "@sapphire/async-queue": ["@sapphire/async-queue@1.5.3", "", {}, "sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w=="], + + "@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="], + + "@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="], + + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@semantic-release/changelog": ["@semantic-release/changelog@6.0.3", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.0.0", "fs-extra": "^11.0.0", "lodash": "^4.17.4" }, "peerDependencies": { "semantic-release": ">=18.0.0" } }, "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag=="], + + "@semantic-release/commit-analyzer": ["@semantic-release/commit-analyzer@13.0.0", "", { "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", "import-from-esm": "^1.0.3", "lodash-es": "^4.17.21", "micromatch": "^4.0.2" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "sha512-KtXWczvTAB1ZFZ6B4O+w8HkfYm/OgQb1dUGNFZtDgQ0csggrmkq8sTxhd+lwGF8kMb59/RnG9o4Tn7M/I8dQ9Q=="], + + "@semantic-release/error": ["@semantic-release/error@3.0.0", "", {}, "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw=="], + + "@semantic-release/exec": ["@semantic-release/exec@6.0.3", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.0.0", "debug": "^4.0.0", "execa": "^5.0.0", "lodash": "^4.17.4", "parse-json": "^5.0.0" }, "peerDependencies": { "semantic-release": ">=18.0.0" } }, "sha512-bxAq8vLOw76aV89vxxICecEa8jfaWwYITw6X74zzlO0mc/Bgieqx9kBRz9z96pHectiTAtsCwsQcUyLYWnp3VQ=="], + + "@semantic-release/git": ["@semantic-release/git@10.0.1", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.0.0", "debug": "^4.0.0", "dir-glob": "^3.0.0", "execa": "^5.0.0", "lodash": "^4.17.4", "micromatch": "^4.0.0", "p-reduce": "^2.0.0" }, "peerDependencies": { "semantic-release": ">=18.0.0" } }, "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w=="], + + "@semantic-release/github": ["@semantic-release/github@11.0.0", "", { "dependencies": { "@octokit/core": "^6.0.0", "@octokit/plugin-paginate-rest": "^11.0.0", "@octokit/plugin-retry": "^7.0.0", "@octokit/plugin-throttling": "^9.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", "dir-glob": "^3.0.1", "globby": "^14.0.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "issue-parser": "^7.0.0", "lodash-es": "^4.17.21", "mime": "^4.0.0", "p-filter": "^4.0.0", "url-join": "^5.0.0" }, "peerDependencies": { "semantic-release": ">=24.1.0" } }, "sha512-Uon6G6gJD8U1JNvPm7X0j46yxNRJ8Ui6SgK4Zw5Ktu8RgjEft3BGn+l/RX1TTzhhO3/uUcKuqM+/9/ETFxWS/Q=="], + + "@semantic-release/npm": ["@semantic-release/npm@12.0.1", "", { "dependencies": { "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "execa": "^9.0.0", "fs-extra": "^11.0.0", "lodash-es": "^4.17.21", "nerf-dart": "^1.0.0", "normalize-url": "^8.0.0", "npm": "^10.5.0", "rc": "^1.2.8", "read-pkg": "^9.0.0", "registry-auth-token": "^5.0.0", "semver": "^7.1.2", "tempy": "^3.0.0" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw=="], + + "@semantic-release/release-notes-generator": ["@semantic-release/release-notes-generator@14.0.1", "", { "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", "get-stream": "^7.0.0", "import-from-esm": "^1.0.3", "into-stream": "^7.0.0", "lodash-es": "^4.17.21", "read-package-up": "^11.0.0" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "sha512-K0w+5220TM4HZTthE5dDpIuFrnkN1NfTGPidJFm04ULT1DEZ9WG89VNXN7F0c+6nMEpWgqmPvb7vY7JkB2jyyA=="], + + "@semrel-extra/topo": ["@semrel-extra/topo@1.14.1", "", { "dependencies": { "fast-glob": "^3.3.2", "js-yaml": "^4.1.0", "toposource": "^1.2.0" } }, "sha512-V7hlOQoBXgqLSa4ai9S0LGOO7cKTqRu5dh0T83xfE+VqZQmDkuRm956ooJ2/M8y62kWIxS2VEfePnEoB74x6fg=="], + + "@sigstore/bundle": ["@sigstore/bundle@2.3.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.3.2" } }, "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA=="], + + "@sigstore/core": ["@sigstore/core@1.1.0", "", {}, "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg=="], + + "@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.3.2", "", {}, "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw=="], + + "@sigstore/sign": ["@sigstore/sign@2.3.2", "", { "dependencies": { "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", "@sigstore/protobuf-specs": "^0.3.2", "make-fetch-happen": "^13.0.1", "proc-log": "^4.2.0", "promise-retry": "^2.0.1" } }, "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA=="], + + "@sigstore/tuf": ["@sigstore/tuf@2.3.4", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.3.2", "tuf-js": "^2.2.1" } }, "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw=="], + + "@sigstore/verify": ["@sigstore/verify@1.2.1", "", { "dependencies": { "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.1.0", "@sigstore/protobuf-specs": "^0.3.2" } }, "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g=="], + + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@tsconfig/strictest": ["@tsconfig/strictest@2.0.5", "", {}, "sha512-ec4tjL2Rr0pkZ5hww65c+EEPYwxOi4Ryv+0MtjeaSQRJyq322Q27eOQiFbuNgw2hpL4hB1/W/HBGk3VKS43osg=="], + + "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], + + "@tufjs/models": ["@tufjs/models@2.0.1", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^9.0.4" } }, "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg=="], + + "@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="], + + "@types/conventional-commits-parser": ["@types/conventional-commits-parser@5.0.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ=="], + + "@types/node": ["@types/node@20.14.12", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ=="], + + "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], + + "@types/semver": ["@types/semver@7.5.8", "", {}, "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="], + + "@types/ws": ["@types/ws@8.5.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ=="], + + "@vierofernando/decancer-android-arm-eabi": ["@vierofernando/decancer-android-arm-eabi@3.2.4", "", { "os": "android", "cpu": "arm" }, "sha512-eZ4IWFzQqbP1eO7UNOtJFDahZHSqR74kV0yt0QbuAZQv3X7A0gXvoQNc1EnPQCkb5JrOhwjt59IPI8Z7eZlBJw=="], + + "@vierofernando/decancer-android-arm64": ["@vierofernando/decancer-android-arm64@3.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-+q6e/wqPJANnn0AzdEYDaxC2XfZmdqKachUVUkyj2SKgUmPgZoU04R3jPumgxx+Vg9IgplxOuNdFlEfmoYW83w=="], + + "@vierofernando/decancer-darwin-arm64": ["@vierofernando/decancer-darwin-arm64@3.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-O/peTIPTUPR/lK3f/8EWSUAEwZjTB5xb1PnBaaOpmmAzHH9fh1HNGYx9KqX+8l5MG3mB2fwyu7US0yN1iAjNdw=="], + + "@vierofernando/decancer-linux-arm-gnueabihf": ["@vierofernando/decancer-linux-arm-gnueabihf@3.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-qnShXa/mFmfBO54HkfGUCL5B6o31C3XUdaPO934PffIF6wPCN6cj0KInnkGcJHYZLn83GNryDB9T8SK7UgnJOA=="], + + "@vierofernando/decancer-linux-arm64-gnu": ["@vierofernando/decancer-linux-arm64-gnu@3.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-xAKBSMRUQHzJzaysFUs0S0ziYF6cbhc12ELzn/MnvCkfJ2yNuWuazxYzzhfPdxKeMxV/ooNe18elX/0Z7Xl4Og=="], + + "@vierofernando/decancer-linux-arm64-musl": ["@vierofernando/decancer-linux-arm64-musl@3.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-eZHBK7HeTQI5f0KjnDSlASl+rjGVqwyoXeVxoeYGQxEi3XFyeOfmi2oaCqTjOr2wfXhZ+cCRz+1WOr0K+dAaaA=="], + + "@vierofernando/decancer-linux-x64-gnu": ["@vierofernando/decancer-linux-x64-gnu@3.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-33Ynp93KPcjRIG9Ug5Ywl36cLPfj07r9r1CQFmor86hL8FdikQDIWVDEj9oTE4Edp9oQTilUBgz6JecC8v0iCw=="], + + "@vierofernando/decancer-linux-x64-musl": ["@vierofernando/decancer-linux-x64-musl@3.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Ef6qT9ht0XrSQInXjHfsARTovYNBkiIr0sUWfz9bbi0wKgRn+2/YwdgmRLaubwMWp2bMdijN49myfjAVmPZpqw=="], + + "@vierofernando/decancer-win32-arm64-msvc": ["@vierofernando/decancer-win32-arm64-msvc@3.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-/uLoF2jqbdWvTCaCTx/bibZg85AVgRn3J5m/3i4Wu7Rzgarlbm7nZIvGqIv2Kn5bqbaX+VPvSCZHTCY3posWpg=="], + + "@vierofernando/decancer-win32-ia32-msvc": ["@vierofernando/decancer-win32-ia32-msvc@3.2.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-FAIyRWfpW1ioh4rwse61bp0YN7jxutJuF3zNFsxdOgc7tuxYq7ML0NGZxI5wSVtER0HkoktrFPmMOKKg3aWGEg=="], + + "@vierofernando/decancer-win32-x64-msvc": ["@vierofernando/decancer-win32-x64-msvc@3.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-9C1k77zT6fXmsDqkJHPJIbJkkw8bgDB4XgvGrLjLJBqg5Dp8GEBSXB2BsnRyep8cQG15jORx/dA4FpaI8UXUgQ=="], + + "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="], + + "JSONStream": ["JSONStream@1.3.5", "", { "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" }, "bin": { "JSONStream": "./bin.js" } }, "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ=="], + + "abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="], + + "agent-base": ["agent-base@7.1.1", "", { "dependencies": { "debug": "^4.3.4" } }, "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA=="], + + "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], + + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "aproba": ["aproba@2.0.0", "", {}, "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="], + + "archy": ["archy@1.0.0", "", {}, "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "argv-formatter": ["argv-formatter@1.0.0", "", {}, "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw=="], + + "array-ify": ["array-ify@1.0.0", "", {}, "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "before-after-hook": ["before-after-hook@3.0.2", "", {}, "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="], + + "bin-links": ["bin-links@4.0.4", "", { "dependencies": { "cmd-shim": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", "read-cmd-shim": "^4.0.0", "write-file-atomic": "^5.0.0" } }, "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA=="], + + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + + "blork": ["blork@9.3.0", "", {}, "sha512-9naBrHS2bwCQeGqGR9ptcoll6utsox9jtk1E0SwOAFa4RCV/IQHoBJARdi8AhHQTPPoWkjixMrzHvQKAV5Fx2A=="], + + "bmp-js": ["bmp-js@0.1.0", "", {}, "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw=="], + + "bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="], + + "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "bson": ["bson@6.8.0", "", {}, "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="], + + "cacache": ["cacache@18.0.4", "", { "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^4.0.0", "ssri": "^10.0.0", "tar": "^6.1.11", "unique-filename": "^3.0.0" } }, "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + + "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], + + "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + + "ci-info": ["ci-info@4.0.0", "", {}, "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg=="], + + "cidr-regex": ["cidr-regex@4.1.1", "", { "dependencies": { "ip-regex": "^5.0.0" } }, "sha512-ekKcVp+iRB9zlKFXyx7io7nINgb0oRjgRdXNEodp1OuxRui8FXr/CA40Tz1voWUp9DPPrMyQKy01vJhDo4N1lw=="], + + "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], + + "cli-columns": ["cli-columns@4.0.0", "", { "dependencies": { "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ=="], + + "cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="], + + "cli-table3": ["cli-table3@0.6.5", "", { "dependencies": { "string-width": "^4.2.0" }, "optionalDependencies": { "@colors/colors": "1.5.0" } }, "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "cmd-shim": ["cmd-shim@6.0.3", "", {}, "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA=="], + + "color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], + + "color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], + + "colors": ["colors@1.4.0", "", {}, "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="], + + "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + + "compare-func": ["compare-func@2.0.0", "", { "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA=="], + + "config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="], + + "conventional-changelog-angular": ["conventional-changelog-angular@8.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA=="], + + "conventional-changelog-conventionalcommits": ["conventional-changelog-conventionalcommits@7.0.2", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w=="], + + "conventional-changelog-writer": ["conventional-changelog-writer@8.0.0", "", { "dependencies": { "@types/semver": "^7.5.5", "conventional-commits-filter": "^5.0.0", "handlebars": "^4.7.7", "meow": "^13.0.0", "semver": "^7.5.2" }, "bin": { "conventional-changelog-writer": "dist/cli/index.js" } }, "sha512-TQcoYGRatlAnT2qEWDON/XSfnVG38JzA7E0wcGScu7RElQBkg9WWgZd1peCWFcWDh1xfb2CfsrcvOn1bbSzztA=="], + + "conventional-commits-filter": ["conventional-commits-filter@5.0.0", "", {}, "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q=="], + + "conventional-commits-parser": ["conventional-commits-parser@6.0.0", "", { "dependencies": { "meow": "^13.0.0" }, "bin": { "conventional-commits-parser": "dist/cli/index.js" } }, "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA=="], + + "convert-hrtime": ["convert-hrtime@5.0.0", "", {}, "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], + + "cosmiconfig-typescript-loader": ["cosmiconfig-typescript-loader@6.1.0", "", { "dependencies": { "jiti": "^2.4.1" }, "peerDependencies": { "@types/node": "*", "cosmiconfig": ">=9", "typescript": ">=5" } }, "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g=="], + + "cross-spawn": ["cross-spawn@7.0.3", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="], + + "crypto-random-string": ["crypto-random-string@4.0.0", "", { "dependencies": { "type-fest": "^1.0.1" } }, "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "dargs": ["dargs@8.1.0", "", {}, "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "dateformat": ["dateformat@4.5.1", "", {}, "sha512-OD0TZ+B7yP7ZgpJf5K2DIbj3FZvFvxgFUuaqA/V5zTjAtAAXZ1E8bktHxmAGs4x5b7PflqA9LeQ84Og7wYtF7Q=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "decancer": ["decancer@3.2.4", "", { "optionalDependencies": { "@vierofernando/decancer-android-arm-eabi": "3.2.4", "@vierofernando/decancer-android-arm64": "3.2.4", "@vierofernando/decancer-darwin-arm64": "3.2.4", "@vierofernando/decancer-darwin-x64": "3.2.4", "@vierofernando/decancer-linux-arm-gnueabihf": "3.2.4", "@vierofernando/decancer-linux-arm64-gnu": "3.2.4", "@vierofernando/decancer-linux-arm64-musl": "3.2.4", "@vierofernando/decancer-linux-x64-gnu": "3.2.4", "@vierofernando/decancer-linux-x64-musl": "3.2.4", "@vierofernando/decancer-win32-arm64-msvc": "3.2.4", "@vierofernando/decancer-win32-ia32-msvc": "3.2.4", "@vierofernando/decancer-win32-x64-msvc": "3.2.4" } }, "sha512-96iSooOgFfb8VUWQShcmBf2R6H3yVmYJDCywiSLQUtRL5rHnpn2TYQq55LXUQG/746U94ebww0cE34/BzibcIA=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="], + + "detect-indent": ["detect-indent@7.0.1", "", {}, "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g=="], + + "detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], + + "detect-newline": ["detect-newline@4.0.1", "", {}, "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog=="], + + "diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "discord-api-types": ["discord-api-types@0.37.102", "", {}, "sha512-5+m5twqG8n77rLhKuh2c/971UWszEL/c3KbdvVLUBTPXuS8PbYC/7W7NYhwP02qowjj6CHoKYZbD0ppOUCsT6g=="], + + "discord.js": ["discord.js@14.16.3", "", { "dependencies": { "@discordjs/builders": "^1.9.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.5.0", "@discordjs/rest": "^2.4.0", "@discordjs/util": "^1.1.1", "@discordjs/ws": "1.1.1", "@sapphire/snowflake": "3.5.3", "discord-api-types": "0.37.100", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "tslib": "^2.6.3", "undici": "6.19.8" } }, "sha512-EPCWE9OkA9DnFFNrO7Kl1WHHDYFXu3CNVFJg63bfU7hVtjZGyhShwZtSBImINQRWxWP2tgo2XI+QhdXx28r0aA=="], + + "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], + + "drizzle-kit": ["drizzle-kit@0.22.8", "", { "dependencies": { "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-VjI4wsJjk3hSqHSa3TwBf+uvH6M6pRHyxyoVbt935GUzP9tUR/BRZ+MhEJNgryqbzN2Za1KP0eJMTgKEPsalYQ=="], + + "drizzle-orm": ["drizzle-orm@0.31.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.1.1", "@libsql/client": "*", "@neondatabase/serverless": ">=0.1", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=13.2.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-VGD9SH9aStF2z4QOTnVlVX/WghV/EnuEzTmsH3fSVp2E4fFgc8jl3viQrS/XUJx1ekW4rVVLJMH42SfGQdjX3Q=="], + + "duplexer2": ["duplexer2@0.1.4", "", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "emojilib": ["emojilib@2.4.0", "", {}, "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="], + + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="], + + "env-ci": ["env-ci@11.0.0", "", { "dependencies": { "execa": "^8.0.0", "java-properties": "^1.0.2" } }, "sha512-apikxMgkipkgTvMdRT9MNqWx5VLOci79F4VBd7Op/7OPjjoanjdAvn6fglMCCEf/1bAh8eOiuEVCUs4V3qP3nQ=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + + "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], + + "esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + + "escalade": ["escalade@3.1.2", "", {}, "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA=="], + + "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "execa": ["execa@9.5.2", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.0.0" } }, "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q=="], + + "exponential-backoff": ["exponential-backoff@3.1.1", "", {}, "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.2", "", { "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" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="], + + "fast-uri": ["fast-uri@3.0.1", "", {}, "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw=="], + + "fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="], + + "fastq": ["fastq@1.17.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w=="], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@2.1.0", "", { "dependencies": { "locate-path": "^2.0.0" } }, "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ=="], + + "find-up-simple": ["find-up-simple@1.0.0", "", {}, "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw=="], + + "find-versions": ["find-versions@6.0.0", "", { "dependencies": { "semver-regex": "^4.0.5", "super-regex": "^1.0.0" } }, "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA=="], + + "foreground-child": ["foreground-child@3.2.1", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "from2": ["from2@2.3.0", "", { "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" } }, "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g=="], + + "fs-extra": ["fs-extra@11.2.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw=="], + + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + + "function-timeout": ["function-timeout@1.0.2", "", {}, "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "get-tsconfig": ["get-tsconfig@4.7.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA=="], + + "git-log-parser": ["git-log-parser@1.2.1", "", { "dependencies": { "argv-formatter": "~1.0.0", "spawn-error-forwarder": "~1.0.0", "split2": "~1.0.0", "stream-combiner2": "~1.1.1", "through2": "~2.0.0", "traverse": "0.6.8" } }, "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ=="], + + "git-raw-commits": ["git-raw-commits@4.0.0", "", { "dependencies": { "dargs": "^8.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "git-raw-commits": "cli.mjs" } }, "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ=="], + + "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "global-directory": ["global-directory@4.0.1", "", { "dependencies": { "ini": "4.1.1" } }, "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q=="], + + "globby": ["globby@14.0.2", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", "ignore": "^5.2.4", "path-type": "^5.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.1.0" } }, "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "hook-std": ["hook-std@3.0.0", "", {}, "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw=="], + + "hosted-git-info": ["hosted-git-info@8.0.0", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-4nw3vOVR+vHUOT8+U4giwe2tcGv+R3pwwRidUe67DoMBTjhrfr6rZYJVVwdkBE+Um050SG+X9tf0Jo4fOpn01w=="], + + "http-cache-semantics": ["http-cache-semantics@4.1.1", "", {}, "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.5", "", { "dependencies": { "agent-base": "^7.0.2", "debug": "4" } }, "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw=="], + + "human-signals": ["human-signals@8.0.0", "", {}, "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "idb-keyval": ["idb-keyval@6.2.1", "", {}, "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg=="], + + "ignore": ["ignore@5.3.1", "", {}, "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw=="], + + "ignore-walk": ["ignore-walk@6.0.5", "", { "dependencies": { "minimatch": "^9.0.0" } }, "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A=="], + + "import-fresh": ["import-fresh@3.3.0", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw=="], + + "import-from-esm": ["import-from-esm@2.0.0", "", { "dependencies": { "debug": "^4.3.4", "import-meta-resolve": "^4.0.0" } }, "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g=="], + + "import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "index-to-position": ["index-to-position@0.1.2", "", {}, "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="], + + "init-package-json": ["init-package-json@6.0.3", "", { "dependencies": { "@npmcli/package-json": "^5.0.0", "npm-package-arg": "^11.0.0", "promzard": "^1.0.0", "read": "^3.0.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^5.0.0" } }, "sha512-Zfeb5ol+H+eqJWHTaGca9BovufyGeIfr4zaaBorPmJBMrJ+KBnN+kQx2ZtXdsotUTgldHmHQV44xvUWOUA7E2w=="], + + "into-stream": ["into-stream@7.0.0", "", { "dependencies": { "from2": "^2.3.0", "p-is-promise": "^3.0.0" } }, "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw=="], + + "ip-address": ["ip-address@9.0.5", "", { "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" } }, "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g=="], + + "ip-regex": ["ip-regex@5.0.0", "", {}, "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw=="], + + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "is-cidr": ["is-cidr@5.1.0", "", { "dependencies": { "cidr-regex": "^4.1.1" } }, "sha512-OkVS+Ht2ssF27d48gZdB+ho1yND1VbkJRKKS6Pc1/Cw7uqkd9IOJg8/bTwBDQL6tfBhSdguPRnlGiE8pU/X5NQ=="], + + "is-electron": ["is-electron@2.2.2", "", {}, "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-text-path": ["is-text-path@2.0.0", "", { "dependencies": { "text-extensions": "^2.0.0" } }, "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw=="], + + "is-unicode-supported": ["is-unicode-supported@2.0.0", "", {}, "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q=="], + + "is-url": ["is-url@1.2.4", "", {}, "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "issue-parser": ["issue-parser@7.0.1", "", { "dependencies": { "lodash.capitalize": "^4.2.1", "lodash.escaperegexp": "^4.1.2", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.uniqby": "^4.7.0" } }, "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "java-properties": ["java-properties@1.0.2", "", {}, "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ=="], + + "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + + "js-base64": ["js-base64@3.7.7", "", {}, "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="], + + "json-parse-better-errors": ["json-parse-better-errors@1.0.2", "", {}, "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="], + + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="], + + "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + + "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], + + "just-diff-apply": ["just-diff-apply@5.5.0", "", {}, "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="], + + "lefthook": ["lefthook@1.11.2", "", { "optionalDependencies": { "lefthook-darwin-arm64": "1.11.2", "lefthook-darwin-x64": "1.11.2", "lefthook-freebsd-arm64": "1.11.2", "lefthook-freebsd-x64": "1.11.2", "lefthook-linux-arm64": "1.11.2", "lefthook-linux-x64": "1.11.2", "lefthook-openbsd-arm64": "1.11.2", "lefthook-openbsd-x64": "1.11.2", "lefthook-windows-arm64": "1.11.2", "lefthook-windows-x64": "1.11.2" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-/5royc/WbL2KTfFJ54wEdvxUZOBXwc54v/fW2Bz4LMOkAA3LWIxnoUiybSiauu+nhdTG98qERxH1YHwF2wZlAA=="], + + "lefthook-darwin-arm64": ["lefthook-darwin-arm64@1.11.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8DpvrybtWdt6UmfZk+hA8daYXr6zkpJVogZ8M49BQx6ISSKUaC03xzO1m4MrAsoKok77ka4JAidYhOa2gCu15A=="], + + "lefthook-darwin-x64": ["lefthook-darwin-x64@1.11.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-DrL1SOT8lJksjudRu6fTZTp3M0EbpCP2RQ22MDT71clS8BMrFL8x3h9Ziw+uNH76j9zA241tW5zMxWMSv+foAA=="], + + "lefthook-freebsd-arm64": ["lefthook-freebsd-arm64@1.11.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-AliG4Wi8BNC27hCSnuFBeUXh/eA3fppnUbQQPISy/G94yfwRkzyml9MZzvb7HKmUpw1LT0sq9RQ6FQPxBZ2DYA=="], + + "lefthook-freebsd-x64": ["lefthook-freebsd-x64@1.11.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V6cgRCoi5+jcq6XBIdRYraeEOK1UhBrtL/XZlNypAIkhPoBtfTP9u2wSprGMDzZvJCRriLXZxV/d0v94laKXzA=="], + + "lefthook-linux-arm64": ["lefthook-linux-arm64@1.11.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-VKcK7sjIK8UpXX/qK6Fxa0Lnwr4gzRtlXDS17jzxThcyFk8iGBpQ+9ZnPLv2yAaEIzmGhJUG9sDgOb9IQ5kpBQ=="], + + "lefthook-linux-x64": ["lefthook-linux-x64@1.11.2", "", { "os": "linux", "cpu": "x64" }, "sha512-aGa2Krph14YwSW7KF0PrlCBK9P7V/Z4oFklonmz3r2Fjm8EdhA750y7OQvA9KerXRleIb5SaUH/cz1azG/izeQ=="], + + "lefthook-openbsd-arm64": ["lefthook-openbsd-arm64@1.11.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-f7owNQ9Ki6Y07KBgdXdH28EYO0eBdZuGTpIggMeHNhYFVDavxuINP2BjmbXtzpUu8K5BX6exGx0umtWhRhXbvQ=="], + + "lefthook-openbsd-x64": ["lefthook-openbsd-x64@1.11.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HKv6PV64vOjqPrlxAqo07N9+Z34jdPDBfeExqi0ldR7vACFaBJFIdhWCLLP+3uQUrNKc8GXlikqplZn8MgRSQw=="], + + "lefthook-windows-arm64": ["lefthook-windows-arm64@1.11.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-042jCKZ/H+lS6XYoMIf2FWMP2hxXqfAT52UW6lYObIOvQ5xu/epUXFjtmXRyYxCv57No3JYYMg1Yr06xdzTKkQ=="], + + "lefthook-windows-x64": ["lefthook-windows-x64@1.11.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1Map6Ck2AyfY6ptN9T19N41HFKFqRTzmILtGaRGJABEzHiE4+gSWcq5YT1R6cCtkVlewD3Lx+J/80D/Kb/cVtw=="], + + "libnpmaccess": ["libnpmaccess@8.0.6", "", { "dependencies": { "npm-package-arg": "^11.0.2", "npm-registry-fetch": "^17.0.1" } }, "sha512-uM8DHDEfYG6G5gVivVl+yQd4pH3uRclHC59lzIbSvy7b5FEwR+mU49Zq1jEyRtRFv7+M99mUW9S0wL/4laT4lw=="], + + "libnpmdiff": ["libnpmdiff@6.1.4", "", { "dependencies": { "@npmcli/arborist": "^7.5.4", "@npmcli/installed-package-contents": "^2.1.0", "binary-extensions": "^2.3.0", "diff": "^5.1.0", "minimatch": "^9.0.4", "npm-package-arg": "^11.0.2", "pacote": "^18.0.6", "tar": "^6.2.1" } }, "sha512-KCNoCY8kjQ16/EE4VoW7AYqecsb9frNIh/cPwWQSk1s2grzZMQH+Scp7Yo7Fk6SWTkyVDLZEYfUT/vKONYrfmg=="], + + "libnpmexec": ["libnpmexec@8.1.3", "", { "dependencies": { "@npmcli/arborist": "^7.5.4", "@npmcli/run-script": "^8.1.0", "ci-info": "^4.0.0", "npm-package-arg": "^11.0.2", "pacote": "^18.0.6", "proc-log": "^4.2.0", "read": "^3.0.1", "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", "walk-up-path": "^3.0.1" } }, "sha512-DOI1G8R1mEVOGkXdQ73KXUTm3NuPjcfb7UazqVVjpl/xtosuK5p5a68TO6Nt6UWjOOiizTudAw8hI7psw5/t6w=="], + + "libnpmfund": ["libnpmfund@5.0.12", "", { "dependencies": { "@npmcli/arborist": "^7.5.4" } }, "sha512-lox1UHcv8/r/TE+T9B+aOylU3c13tK2IuwwUwUm+YMw+C/iq14dqskHqhGPTqa75ZJbiVOW7PMWO92Wn5HG49Q=="], + + "libnpmhook": ["libnpmhook@10.0.5", "", { "dependencies": { "aproba": "^2.0.0", "npm-registry-fetch": "^17.0.1" } }, "sha512-XulT+N/s3o9oFlIq6pGRv3OG2qR1NVRbVQOKLchycDwyf16RZA3oXbeEgs2H3oE7hRZPUMBZqsalQXMMPal3cQ=="], + + "libnpmorg": ["libnpmorg@6.0.6", "", { "dependencies": { "aproba": "^2.0.0", "npm-registry-fetch": "^17.0.1" } }, "sha512-4MVxsAS4H2z7su/sU0GsrirfBm4ssfqPRSDvoZ8qmRw58kEWJ0qE0cQ2VilRlFgCWKzKPhfoPeyNPyxBTnOusA=="], + + "libnpmpack": ["libnpmpack@7.0.4", "", { "dependencies": { "@npmcli/arborist": "^7.5.4", "@npmcli/run-script": "^8.1.0", "npm-package-arg": "^11.0.2", "pacote": "^18.0.6" } }, "sha512-oKZA0afbueiC88lskXzAEr3DCN9BTMbUySjUce6qhBV9CjYF2R/x347KhgHu75+p9W2Rd57ZvKz81c5a2+9h6Q=="], + + "libnpmpublish": ["libnpmpublish@9.0.9", "", { "dependencies": { "ci-info": "^4.0.0", "normalize-package-data": "^6.0.1", "npm-package-arg": "^11.0.2", "npm-registry-fetch": "^17.0.1", "proc-log": "^4.2.0", "semver": "^7.3.7", "sigstore": "^2.2.0", "ssri": "^10.0.6" } }, "sha512-26zzwoBNAvX9AWOPiqqF6FG4HrSCPsHFkQm7nT+xU1ggAujL/eae81RnCv4CJ2In9q9fh10B88sYSzKCUh/Ghg=="], + + "libnpmsearch": ["libnpmsearch@7.0.6", "", { "dependencies": { "npm-registry-fetch": "^17.0.1" } }, "sha512-PmiER4bgiIqN9OjBtgPn2/PxwU+OdJWtLBFM+vewOrn4VmaNAHSUKDt/wxOOkZSDLyMICVUBp61Ji1+XxhSrKw=="], + + "libnpmteam": ["libnpmteam@6.0.5", "", { "dependencies": { "aproba": "^2.0.0", "npm-registry-fetch": "^17.0.1" } }, "sha512-iJW4Cq42GMqMwZEV+Mx8ZLj0Np5kGXQ9P/BAekHjIpYC1v3/vJqbmfJkzkwFvGxEhUotmx+xpLChZCDJ7c3rxA=="], + + "libnpmversion": ["libnpmversion@6.0.3", "", { "dependencies": { "@npmcli/git": "^5.0.7", "@npmcli/run-script": "^8.1.0", "json-parse-even-better-errors": "^3.0.2", "proc-log": "^4.2.0", "semver": "^7.3.7" } }, "sha512-Kjk1anQ9sPn7E/qF1jXumItvr2OA1914tYWkSTXH9G2rYoY+Ol1+KNrWfGeje2aBvFfKlt4VeKdCfM3yxMXNBw=="], + + "libsql": ["libsql@0.3.19", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2", "libsql": "^0.3.15" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.3.19", "@libsql/darwin-x64": "0.3.19", "@libsql/linux-arm64-gnu": "0.3.19", "@libsql/linux-arm64-musl": "0.3.19", "@libsql/linux-x64-gnu": "0.3.19", "@libsql/linux-x64-musl": "0.3.19", "@libsql/win32-x64-msvc": "0.3.19" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-json-file": ["load-json-file@4.0.0", "", { "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", "pify": "^3.0.0", "strip-bom": "^3.0.0" } }, "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw=="], + + "locate-path": ["locate-path@2.0.0", "", { "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" } }, "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "lodash.capitalize": ["lodash.capitalize@4.2.1", "", {}, "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw=="], + + "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + + "lodash.kebabcase": ["lodash.kebabcase@4.1.1", "", {}, "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lodash.mergewith": ["lodash.mergewith@4.6.2", "", {}, "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="], + + "lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="], + + "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="], + + "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], + + "lodash.uniqby": ["lodash.uniqby@4.7.0", "", {}, "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww=="], + + "lodash.upperfirst": ["lodash.upperfirst@4.3.1", "", {}, "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "magic-bytes.js": ["magic-bytes.js@1.10.0", "", {}, "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ=="], + + "make-fetch-happen": ["make-fetch-happen@13.0.1", "", { "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", "http-cache-semantics": "^4.1.1", "is-lambda": "^1.0.1", "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "proc-log": "^4.2.0", "promise-retry": "^2.0.1", "ssri": "^10.0.0" } }, "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA=="], + + "marked": ["marked@12.0.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q=="], + + "marked-terminal": ["marked-terminal@7.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "chalk": "^5.3.0", "cli-highlight": "^2.1.11", "cli-table3": "^0.6.5", "node-emoji": "^2.1.3", "supports-hyperlinks": "^3.0.0" }, "peerDependencies": { "marked": ">=1 <14" } }, "sha512-+pvwa14KZL74MVXjYdPR3nSInhGhNvPce/3mqLVZT2oUvt654sL1XImFuLZ1pkA866IYZ3ikDTOFUIC7XzpZZg=="], + + "meow": ["meow@12.1.1", "", {}, "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.7", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q=="], + + "mime": ["mime@4.0.4", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@3.0.5", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg=="], + + "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + + "minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + + "nerf-dart": ["nerf-dart@1.0.0", "", {}, "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-emoji": ["node-emoji@2.1.3", "", { "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", "emojilib": "^2.4.0", "skin-tone": "^2.0.0" } }, "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "node-gyp": ["node-gyp@10.2.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^10.3.10", "graceful-fs": "^4.2.6", "make-fetch-happen": "^13.0.0", "nopt": "^7.0.0", "proc-log": "^4.1.0", "semver": "^7.3.5", "tar": "^6.2.1", "which": "^4.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw=="], + + "nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], + + "normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="], + + "normalize-url": ["normalize-url@8.0.1", "", {}, "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w=="], + + "npm": ["npm@10.8.2", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^7.5.4", "@npmcli/config": "^8.3.4", "@npmcli/fs": "^3.1.1", "@npmcli/map-workspaces": "^3.0.6", "@npmcli/package-json": "^5.2.0", "@npmcli/promise-spawn": "^7.0.2", "@npmcli/redact": "^2.0.1", "@npmcli/run-script": "^8.1.0", "@sigstore/tuf": "^2.3.4", "abbrev": "^2.0.0", "archy": "~1.0.0", "cacache": "^18.0.3", "chalk": "^5.3.0", "ci-info": "^4.0.0", "cli-columns": "^4.0.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", "glob": "^10.4.2", "graceful-fs": "^4.2.11", "hosted-git-info": "^7.0.2", "ini": "^4.1.3", "init-package-json": "^6.0.3", "is-cidr": "^5.1.0", "json-parse-even-better-errors": "^3.0.2", "libnpmaccess": "^8.0.6", "libnpmdiff": "^6.1.4", "libnpmexec": "^8.1.3", "libnpmfund": "^5.0.12", "libnpmhook": "^10.0.5", "libnpmorg": "^6.0.6", "libnpmpack": "^7.0.4", "libnpmpublish": "^9.0.9", "libnpmsearch": "^7.0.6", "libnpmteam": "^6.0.5", "libnpmversion": "^6.0.3", "make-fetch-happen": "^13.0.1", "minimatch": "^9.0.5", "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", "node-gyp": "^10.1.0", "nopt": "^7.2.1", "normalize-package-data": "^6.0.2", "npm-audit-report": "^5.0.0", "npm-install-checks": "^6.3.0", "npm-package-arg": "^11.0.2", "npm-pick-manifest": "^9.1.0", "npm-profile": "^10.0.0", "npm-registry-fetch": "^17.1.0", "npm-user-validate": "^2.0.1", "p-map": "^4.0.0", "pacote": "^18.0.6", "parse-conflict-json": "^3.0.1", "proc-log": "^4.2.0", "qrcode-terminal": "^0.12.0", "read": "^3.0.1", "semver": "^7.6.2", "spdx-expression-parse": "^4.0.0", "ssri": "^10.0.6", "supports-color": "^9.4.0", "tar": "^6.2.1", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^3.0.0", "validate-npm-package-name": "^5.0.1", "which": "^4.0.0", "write-file-atomic": "^5.0.1" }, "bin": { "npm": "bin/npm-cli.js", "npx": "bin/npx-cli.js" } }, "sha512-x/AIjFIKRllrhcb48dqUNAAZl0ig9+qMuN91RpZo3Cb2+zuibfh+KISl6+kVVyktDz230JKc208UkQwwMqyB+w=="], + + "npm-audit-report": ["npm-audit-report@5.0.0", "", {}, "sha512-EkXrzat7zERmUhHaoren1YhTxFwsOu5jypE84k6632SXTHcQE1z8V51GC6GVZt8LxkC+tbBcKMUBZAgk8SUSbw=="], + + "npm-bundled": ["npm-bundled@3.0.1", "", { "dependencies": { "npm-normalize-package-bin": "^3.0.0" } }, "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ=="], + + "npm-install-checks": ["npm-install-checks@6.3.0", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw=="], + + "npm-normalize-package-bin": ["npm-normalize-package-bin@3.0.1", "", {}, "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ=="], + + "npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="], + + "npm-packlist": ["npm-packlist@8.0.2", "", { "dependencies": { "ignore-walk": "^6.0.4" } }, "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA=="], + + "npm-pick-manifest": ["npm-pick-manifest@9.1.0", "", { "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", "npm-package-arg": "^11.0.0", "semver": "^7.3.5" } }, "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA=="], + + "npm-profile": ["npm-profile@10.0.0", "", { "dependencies": { "npm-registry-fetch": "^17.0.1", "proc-log": "^4.0.0" } }, "sha512-DXnge3nHYnEnPxmVd/kPmgcXKXwVUqFihGnU+EJUiu5mIOs3awq6zEm0rRp3kSQNhFsoqdLu8L1TIfRyeBOCog=="], + + "npm-registry-fetch": ["npm-registry-fetch@17.1.0", "", { "dependencies": { "@npmcli/redact": "^2.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^13.0.0", "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", "minizlib": "^2.1.2", "npm-package-arg": "^11.0.0", "proc-log": "^4.0.0" } }, "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA=="], + + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "npm-user-validate": ["npm-user-validate@2.0.1", "", {}, "sha512-d17PKaF2h8LSGFl5j4b1gHOJt1fgH7YUcCm1kNSJvaLWWKXlBsuUvx0bBEkr0qhsVA9XP5LtRZ83hdlhm2QkgA=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "opencollective-postinstall": ["opencollective-postinstall@2.0.3", "", { "bin": { "opencollective-postinstall": "index.js" } }, "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q=="], + + "p-each-series": ["p-each-series@3.0.0", "", {}, "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw=="], + + "p-filter": ["p-filter@4.1.0", "", { "dependencies": { "p-map": "^7.0.1" } }, "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw=="], + + "p-is-promise": ["p-is-promise@3.0.0", "", {}, "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ=="], + + "p-limit": ["p-limit@1.3.0", "", { "dependencies": { "p-try": "^1.0.0" } }, "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q=="], + + "p-locate": ["p-locate@2.0.0", "", { "dependencies": { "p-limit": "^1.1.0" } }, "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg=="], + + "p-map": ["p-map@7.0.2", "", {}, "sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q=="], + + "p-reduce": ["p-reduce@2.1.0", "", {}, "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw=="], + + "p-try": ["p-try@1.0.0", "", {}, "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.0", "", {}, "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw=="], + + "pacote": ["pacote@18.0.6", "", { "dependencies": { "@npmcli/git": "^5.0.0", "@npmcli/installed-package-contents": "^2.0.1", "@npmcli/package-json": "^5.1.0", "@npmcli/promise-spawn": "^7.0.0", "@npmcli/run-script": "^8.0.0", "cacache": "^18.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^11.0.0", "npm-packlist": "^8.0.0", "npm-pick-manifest": "^9.0.0", "npm-registry-fetch": "^17.0.0", "proc-log": "^4.0.0", "promise-retry": "^2.0.1", "sigstore": "^2.2.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, "bin": { "pacote": "bin/index.js" } }, "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-conflict-json": ["parse-conflict-json@3.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^3.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw=="], + + "parse-duration": ["parse-duration@1.1.0", "", {}, "sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ=="], + + "parse-json": ["parse-json@5.2.0", "", { "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" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="], + + "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="], + + "path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "picocolors": ["picocolors@1.0.1", "", {}, "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], + + "pkg-conf": ["pkg-conf@2.1.0", "", { "dependencies": { "find-up": "^2.0.0", "load-json-file": "^4.0.0" } }, "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g=="], + + "portainer-service-webhook": ["portainer-service-webhook@github:newarifrh/portainer-service-webhook#b6ec54b", { "dependencies": { "@actions/core": "^1.10.1" } }, "newarifrh-portainer-service-webhook-b6ec54b"], + + "postcss-selector-parser": ["postcss-selector-parser@6.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg=="], + + "pretty-ms": ["pretty-ms@9.1.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw=="], + + "proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "proggy": ["proggy@2.0.0", "", {}, "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A=="], + + "promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="], + + "promise-call-limit": ["promise-call-limit@3.0.1", "", {}, "sha512-utl+0x8gIDasV5X+PI5qWEPqH6fJS0pFtQ/4gZ95xfEFb/89dmh+/b895TbFDBLiafBvxD/PGTKfvxl4kH/pQg=="], + + "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], + + "promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + + "promzard": ["promzard@1.0.2", "", { "dependencies": { "read": "^3.0.1" } }, "sha512-2FPputGL+mP3jJ3UZg/Dl9YOkovB7DX0oOr+ck5QbZ5MtORtds8k/BZdn+02peDLI8/YWbmzx34k5fA+fHvCVQ=="], + + "proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="], + + "pump": ["pump@3.0.0", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww=="], + + "qrcode-terminal": ["qrcode-terminal@0.12.0", "", { "bin": { "qrcode-terminal": "./bin/qrcode-terminal.js" } }, "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "read": ["read@3.0.1", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw=="], + + "read-cmd-shim": ["read-cmd-shim@4.0.0", "", {}, "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q=="], + + "read-package-json-fast": ["read-package-json-fast@3.0.2", "", { "dependencies": { "json-parse-even-better-errors": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" } }, "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw=="], + + "read-package-up": ["read-package-up@11.0.0", "", { "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", "type-fest": "^4.6.0" } }, "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ=="], + + "read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="], + + "read-pkg-up": ["read-pkg-up@11.0.0", "", { "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", "type-fest": "^4.6.0" } }, "sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q=="], + + "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="], + + "registry-auth-token": ["registry-auth-token@5.0.2", "", { "dependencies": { "@pnpm/npm-conf": "^2.1.0" } }, "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + + "reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], + + "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semantic-release": ["semantic-release@24.2.3", "", { "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", "@semantic-release/github": "^11.0.0", "@semantic-release/npm": "^12.0.0", "@semantic-release/release-notes-generator": "^14.0.0-beta.1", "aggregate-error": "^5.0.0", "cosmiconfig": "^9.0.0", "debug": "^4.0.0", "env-ci": "^11.0.0", "execa": "^9.0.0", "figures": "^6.0.0", "find-versions": "^6.0.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", "hook-std": "^3.0.0", "hosted-git-info": "^8.0.0", "import-from-esm": "^2.0.0", "lodash-es": "^4.17.21", "marked": "^12.0.0", "marked-terminal": "^7.0.0", "micromatch": "^4.0.2", "p-each-series": "^3.0.0", "p-reduce": "^3.0.0", "read-package-up": "^11.0.0", "resolve-from": "^5.0.0", "semver": "^7.3.2", "semver-diff": "^4.0.0", "signale": "^1.2.1", "yargs": "^17.5.1" }, "bin": { "semantic-release": "bin/semantic-release.js" } }, "sha512-KRhQG9cUazPavJiJEFIJ3XAMjgfd0fcK3B+T26qOl8L0UG5aZUjeRfREO0KM5InGtYwxqiiytkJrbcYoLDEv0A=="], + + "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "semver-diff": ["semver-diff@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA=="], + + "semver-regex": ["semver-regex@4.0.5", "", {}, "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "signale": ["signale@1.4.0", "", { "dependencies": { "chalk": "^2.3.2", "figures": "^2.0.0", "pkg-conf": "^2.1.0" } }, "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w=="], + + "sigstore": ["sigstore@2.3.1", "", { "dependencies": { "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", "@sigstore/protobuf-specs": "^0.3.2", "@sigstore/sign": "^2.3.2", "@sigstore/tuf": "^2.3.4", "@sigstore/verify": "^1.2.1" } }, "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ=="], + + "skin-tone": ["skin-tone@2.0.0", "", { "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" } }, "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA=="], + + "slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.3", "", { "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" } }, "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.4", "", { "dependencies": { "agent-base": "^7.1.1", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "spawn-error-forwarder": ["spawn-error-forwarder@1.0.0", "", {}, "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g=="], + + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.18", "", {}, "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ=="], + + "split2": ["split2@1.0.0", "", { "dependencies": { "through2": "~2.0.0" } }, "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg=="], + + "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + + "ssri": ["ssri@10.0.6", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ=="], + + "stream-buffers": ["stream-buffers@3.0.3", "", {}, "sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw=="], + + "stream-combiner2": ["stream-combiner2@1.1.1", "", { "dependencies": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" } }, "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], + + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "super-regex": ["super-regex@1.0.0", "", { "dependencies": { "function-timeout": "^1.0.1", "time-span": "^5.1.0" } }, "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg=="], + + "supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], + + "supports-hyperlinks": ["supports-hyperlinks@3.0.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA=="], + + "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + + "temp-dir": ["temp-dir@3.0.0", "", {}, "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw=="], + + "tempy": ["tempy@3.1.0", "", { "dependencies": { "is-stream": "^3.0.0", "temp-dir": "^3.0.0", "type-fest": "^2.12.2", "unique-string": "^3.0.0" } }, "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g=="], + + "tesseract.js": ["tesseract.js@5.1.1", "", { "dependencies": { "bmp-js": "^0.1.0", "idb-keyval": "^6.2.0", "is-electron": "^2.2.2", "is-url": "^1.2.4", "node-fetch": "^2.6.9", "opencollective-postinstall": "^2.0.3", "regenerator-runtime": "^0.13.3", "tesseract.js-core": "^5.1.1", "wasm-feature-detect": "^1.2.11", "zlibjs": "^0.3.1" } }, "sha512-lzVl/Ar3P3zhpUT31NjqeCo1f+D5+YfpZ5J62eo2S14QNVOmHBTtbchHm/YAbOOOzCegFnKf4B3Qih9LuldcYQ=="], + + "tesseract.js-core": ["tesseract.js-core@5.1.1", "", {}, "sha512-KX3bYSU5iGcO1XJa+QGPbi+Zjo2qq6eBhNjSGR5E5q0JtzkoipJKOUQD7ph8kFyteCEfEQ0maWLu8MCXtvX5uQ=="], + + "text-extensions": ["text-extensions@2.4.0", "", {}, "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + + "through2": ["through2@2.0.5", "", { "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ=="], + + "time-span": ["time-span@5.1.0", "", { "dependencies": { "convert-hrtime": "^5.0.0" } }, "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA=="], + + "tiny-relative-date": ["tiny-relative-date@1.3.0", "", {}, "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A=="], + + "tinyexec": ["tinyexec@0.3.0", "", {}, "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg=="], + + "tinytim": ["tinytim@0.1.1", "", {}, "sha512-NIpsp9lBIxPNzB++HnMmUd4byzJSVbbO4F+As1Gb1IG/YQT5QvmBDjpx8SpDS8fhGC+t+Qw8ldQgbcAIaU+2cA=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "toposource": ["toposource@1.2.0", "", {}, "sha512-sb8zWvXUWJ+jqnHM/+ud7muOT3wi0lVL/DFHH+CxTViSngzhRrIm8nensUOcxuLUNAMdsfE9DxZjLX3GhCTJKg=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "tracer": ["tracer@1.3.0", "", { "dependencies": { "colors": "1.4.0", "dateformat": "4.5.1", "mkdirp": "^1.0.4", "tinytim": "0.1.1" } }, "sha512-8G2okIuUNThM1W8HU9YUCsQgxROw9VpewE2f8Tsw3B90b0acvBiATqnUIvv07qG/aBTs7ALDr7tLVUVD86kPPA=="], + + "traverse": ["traverse@0.6.8", "", {}, "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA=="], + + "treeverse": ["treeverse@3.0.0", "", {}, "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="], + + "ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="], + + "tslib": ["tslib@2.6.3", "", {}, "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="], + + "tuf-js": ["tuf-js@2.2.1", "", { "dependencies": { "@tufjs/models": "2.0.1", "debug": "^4.3.4", "make-fetch-happen": "^13.0.1" } }, "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA=="], + + "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], + + "turbo": ["turbo@2.4.4", "", { "optionalDependencies": { "turbo-darwin-64": "2.4.4", "turbo-darwin-arm64": "2.4.4", "turbo-linux-64": "2.4.4", "turbo-linux-arm64": "2.4.4", "turbo-windows-64": "2.4.4", "turbo-windows-arm64": "2.4.4" }, "bin": { "turbo": "bin/turbo" } }, "sha512-N9FDOVaY3yz0YCOhYIgOGYad7+m2ptvinXygw27WPLQvcZDl3+0Sa77KGVlLSiuPDChOUEnTKE9VJwLSi9BPGQ=="], + + "turbo-darwin-64": ["turbo-darwin-64@2.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-5kPvRkLAfmWI0MH96D+/THnDMGXlFNmjeqNRj5grLKiry+M9pKj3pRuScddAXPdlxjO5Ptz06UNaOQrrYGTx1g=="], + + "turbo-darwin-arm64": ["turbo-darwin-arm64@2.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/gtHPqbGQXDFhrmy+Q/MFW2HUTUlThJ97WLLSe4bxkDrKHecDYhAjbZ4rN3MM93RV9STQb3Tqy4pZBtsd4DfCw=="], + + "turbo-linux-64": ["turbo-linux-64@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-SR0gri4k0bda56hw5u9VgDXLKb1Q+jrw4lM7WAhnNdXvVoep4d6LmnzgMHQQR12Wxl3KyWPbkz9d1whL6NTm2Q=="], + + "turbo-linux-arm64": ["turbo-linux-arm64@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-COXXwzRd3vslQIfJhXUklgEqlwq35uFUZ7hnN+AUyXx7hUOLIiD5NblL+ETrHnhY4TzWszrbwUMfe2BYWtaPQg=="], + + "turbo-windows-64": ["turbo-windows-64@2.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-PV9rYNouGz4Ff3fd6sIfQy5L7HT9a4fcZoEv8PKRavU9O75G7PoDtm8scpHU10QnK0QQNLbE9qNxOAeRvF0fJg=="], + + "turbo-windows-arm64": ["turbo-windows-arm64@2.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-403sqp9t5sx6YGEC32IfZTVWkRAixOQomGYB8kEc6ZD+//LirSxzeCHCnM8EmSXw7l57U1G+Fb0kxgTcKPU/Lg=="], + + "type-fest": ["type-fest@4.23.0", "", {}, "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w=="], + + "typed-emitter": ["typed-emitter@2.1.0", "", { "optionalDependencies": { "rxjs": "*" } }, "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA=="], + + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + + "uglify-js": ["uglify-js@3.19.0", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-wNKHUY2hYYkf6oSFfhwwiHo4WCHzHmzcXsqXYTN9ja3iApYIFbb2U6ics9hBcYLHcYGQoAlwnZlTrf3oF+BL/Q=="], + + "undici": ["undici@6.19.8", "", {}, "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g=="], + + "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + + "unicode-emoji-modifier-base": ["unicode-emoji-modifier-base@1.0.0", "", {}, "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "unique-filename": ["unique-filename@3.0.0", "", { "dependencies": { "unique-slug": "^4.0.0" } }, "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g=="], + + "unique-slug": ["unique-slug@4.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ=="], + + "unique-string": ["unique-string@3.0.0", "", { "dependencies": { "crypto-random-string": "^4.0.0" } }, "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ=="], + + "universal-user-agent": ["universal-user-agent@7.0.2", "", {}, "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "url-join": ["url-join@5.0.0", "", {}, "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "valibot": ["valibot@0.30.0", "", {}, "sha512-5POBdbSkM+3nvJ6ZlyQHsggisfRtyT4tVTo1EIIShs6qCdXJnyWU5TJ68vr8iTg5zpOLjXLRiBqNx+9zwZz/rA=="], + + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + + "validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="], + + "walk-up-path": ["walk-up-path@3.0.1", "", {}, "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA=="], + + "wasm-feature-detect": ["wasm-feature-detect@1.6.2", "", {}, "sha512-4dnaZ+Fq/q+BbMlTIfaNS851i+0zmHzui++NUZdskESRu3xwB6g6x2FnGvBdWtpijqO5yuj1l+EUTJGc4S4DKg=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="], + + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yocto-queue": ["yocto-queue@1.1.1", "", {}, "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g=="], + + "yoctocolors": ["yoctocolors@2.1.1", "", {}, "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ=="], + + "zlibjs": ["zlibjs@0.3.1", "", {}, "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w=="], + + "@actions/http-client/undici": ["undici@5.28.4", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g=="], + + "@babel/highlight/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], + + "@codedependant/semantic-release-docker/execa": ["execa@4.1.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "human-signals": "^1.1.1", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } }, "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA=="], + + "@commitlint/parse/conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="], + + "@commitlint/parse/conventional-commits-parser": ["conventional-commits-parser@5.0.0", "", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^2.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.mjs" } }, "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA=="], + + "@commitlint/top-level/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="], + + "@discordjs/builders/discord-api-types": ["discord-api-types@0.37.97", "", {}, "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA=="], + + "@discordjs/formatters/discord-api-types": ["discord-api-types@0.37.97", "", {}, "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA=="], + + "@discordjs/rest/discord-api-types": ["discord-api-types@0.37.97", "", {}, "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA=="], + + "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.0", "", {}, "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw=="], + + "@discordjs/ws/@discordjs/rest": ["@discordjs/rest@2.3.0", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "0.37.83", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.2", "undici": "6.13.0" } }, "sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg=="], + + "@discordjs/ws/@discordjs/util": ["@discordjs/util@1.1.0", "", {}, "sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg=="], + + "@discordjs/ws/@types/ws": ["@types/ws@8.5.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w=="], + + "@discordjs/ws/@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.4", "", {}, "sha512-ZL62PFXEIeGUI8btfJ5S8Flc286eU1ZUSjwyFQtIGXfRUDPZKO+CDJMYb1R71LjGWRZ4n202O+a6FGjsgTw58g=="], + + "@discordjs/ws/discord-api-types": ["discord-api-types@0.37.83", "", {}, "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA=="], + + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@libsql/hrana-client/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "@libsql/isomorphic-ws/@types/ws": ["@types/ws@8.5.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w=="], + + "@npmcli/arborist/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "@npmcli/arborist/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], + + "@npmcli/arborist/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "@npmcli/config/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "@npmcli/fs/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "@npmcli/git/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "@npmcli/git/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "@npmcli/metavuln-calculator/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], + + "@npmcli/metavuln-calculator/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "@npmcli/package-json/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "@npmcli/package-json/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], + + "@npmcli/package-json/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "@npmcli/promise-spawn/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "@npmcli/run-script/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="], + + "@saithodev/semantic-release-backmerge/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "@saithodev/semantic-release-backmerge/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "@saithodev/semantic-release-backmerge/semantic-release": ["semantic-release@22.0.12", "", { "dependencies": { "@semantic-release/commit-analyzer": "^11.0.0", "@semantic-release/error": "^4.0.0", "@semantic-release/github": "^9.0.0", "@semantic-release/npm": "^11.0.0", "@semantic-release/release-notes-generator": "^12.0.0", "aggregate-error": "^5.0.0", "cosmiconfig": "^8.0.0", "debug": "^4.0.0", "env-ci": "^10.0.0", "execa": "^8.0.0", "figures": "^6.0.0", "find-versions": "^5.1.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", "hook-std": "^3.0.0", "hosted-git-info": "^7.0.0", "import-from-esm": "^1.3.1", "lodash-es": "^4.17.21", "marked": "^9.0.0", "marked-terminal": "^6.0.0", "micromatch": "^4.0.2", "p-each-series": "^3.0.0", "p-reduce": "^3.0.0", "read-pkg-up": "^11.0.0", "resolve-from": "^5.0.0", "semver": "^7.3.2", "semver-diff": "^4.0.0", "signale": "^1.2.1", "yargs": "^17.5.1" }, "bin": { "semantic-release": "bin/semantic-release.js" } }, "sha512-0mhiCR/4sZb00RVFJIUlMuiBkW3NMpVIW2Gse7noqEMoFGkvfPPAImEQbkBV8xga4KOPP4FdTRYuLLy32R1fPw=="], + + "@semantic-release/commit-analyzer/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "@semantic-release/commit-analyzer/import-from-esm": ["import-from-esm@1.3.4", "", { "dependencies": { "debug": "^4.3.4", "import-meta-resolve": "^4.0.0" } }, "sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg=="], + + "@semantic-release/exec/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "@semantic-release/exec/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "@semantic-release/git/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "@semantic-release/git/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "@semantic-release/github/@semantic-release/error": ["@semantic-release/error@4.0.0", "", {}, "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ=="], + + "@semantic-release/github/aggregate-error": ["aggregate-error@5.0.0", "", { "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" } }, "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw=="], + + "@semantic-release/github/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "@semantic-release/npm/@semantic-release/error": ["@semantic-release/error@4.0.0", "", {}, "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ=="], + + "@semantic-release/npm/aggregate-error": ["aggregate-error@5.0.0", "", { "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" } }, "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw=="], + + "@semantic-release/npm/execa": ["execa@9.3.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^7.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^5.2.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.0.0" } }, "sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg=="], + + "@semantic-release/npm/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "@semantic-release/release-notes-generator/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "@semantic-release/release-notes-generator/get-stream": ["get-stream@7.0.1", "", {}, "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ=="], + + "@semantic-release/release-notes-generator/import-from-esm": ["import-from-esm@1.3.4", "", { "dependencies": { "debug": "^4.3.4", "import-meta-resolve": "^4.0.0" } }, "sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg=="], + + "agent-base/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "cacache/p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], + + "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "cli-highlight/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], + + "config-chain/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "conventional-changelog-writer/meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="], + + "conventional-changelog-writer/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "conventional-commits-parser/meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="], + + "crypto-random-string/type-fest": ["type-fest@1.4.0", "", {}, "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="], + + "discord.js/@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], + + "discord.js/discord-api-types": ["discord-api-types@0.37.100", "", {}, "sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA=="], + + "env-ci/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], + + "esbuild-register/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "execa/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "git-raw-commits/split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "global-directory/ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], + + "globby/@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], + + "globby/path-type": ["path-type@5.0.0", "", {}, "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg=="], + + "globby/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "http-proxy-agent/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "https-proxy-agent/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "import-from-esm/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "init-package-json/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "libnpmexec/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "libnpmpublish/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "libnpmversion/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], + + "libnpmversion/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "load-json-file/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="], + + "locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "node-gyp/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "node-gyp/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "normalize-package-data/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "npm/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "npm/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], + + "npm/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "npm/p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], + + "npm/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "npm/supports-color": ["supports-color@9.4.0", "", {}, "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw=="], + + "npm/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "npm-install-checks/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "npm-package-arg/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "npm-package-arg/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "npm-pick-manifest/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "parse-conflict-json/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], + + "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], + + "portainer-service-webhook/@actions/core": ["@actions/core@1.10.1", "", { "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" } }, "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g=="], + + "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "read-package-json-fast/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], + + "read-pkg/parse-json": ["parse-json@8.1.0", "", { "dependencies": { "@babel/code-frame": "^7.22.13", "index-to-position": "^0.1.2", "type-fest": "^4.7.1" } }, "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA=="], + + "read-pkg/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "semantic-release/@semantic-release/error": ["@semantic-release/error@4.0.0", "", {}, "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ=="], + + "semantic-release/aggregate-error": ["aggregate-error@5.0.0", "", { "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" } }, "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw=="], + + "semantic-release/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "semantic-release/execa": ["execa@9.3.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^7.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^5.2.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.0.0" } }, "sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg=="], + + "semantic-release/p-reduce": ["p-reduce@3.0.0", "", {}, "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q=="], + + "semantic-release/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "semver-diff/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "signale/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], + + "signale/figures": ["figures@2.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA=="], + + "socks-proxy-agent/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "spdx-correct/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], + + "supports-hyperlinks/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tar/fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], + + "tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + + "tempy/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], + + "tempy/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], + + "tuf-js/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + + "validate-npm-package-license/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "@codedependant/semantic-release-docker/execa/get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + + "@codedependant/semantic-release-docker/execa/human-signals": ["human-signals@1.1.1", "", {}, "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="], + + "@codedependant/semantic-release-docker/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "@codedependant/semantic-release-docker/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "@codedependant/semantic-release-docker/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "@codedependant/semantic-release-docker/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "@commitlint/parse/conventional-commits-parser/split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "@commitlint/top-level/find-up/locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], + + "@commitlint/top-level/find-up/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "@discordjs/ws/@discordjs/rest/undici": ["undici@6.13.0", "", {}, "sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.0.1", "", {}, "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="], + + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + + "@npmcli/git/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "@npmcli/promise-spawn/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "@npmcli/run-script/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "@saithodev/semantic-release-backmerge/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "@saithodev/semantic-release-backmerge/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "@saithodev/semantic-release-backmerge/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "@saithodev/semantic-release-backmerge/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "@saithodev/semantic-release-backmerge/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "@saithodev/semantic-release-backmerge/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/commit-analyzer": ["@semantic-release/commit-analyzer@11.1.0", "", { "dependencies": { "conventional-changelog-angular": "^7.0.0", "conventional-commits-filter": "^4.0.0", "conventional-commits-parser": "^5.0.0", "debug": "^4.0.0", "import-from-esm": "^1.0.3", "lodash-es": "^4.17.21", "micromatch": "^4.0.2" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "sha512-cXNTbv3nXR2hlzHjAMgbuiQVtvWHTlwwISt60B+4NZv01y/QRY7p2HcJm8Eh2StzcTJoNnflvKjHH/cjFS7d5g=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/error": ["@semantic-release/error@4.0.0", "", {}, "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github": ["@semantic-release/github@9.2.6", "", { "dependencies": { "@octokit/core": "^5.0.0", "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/plugin-retry": "^6.0.0", "@octokit/plugin-throttling": "^8.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", "dir-glob": "^3.0.1", "globby": "^14.0.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "issue-parser": "^6.0.0", "lodash-es": "^4.17.21", "mime": "^4.0.0", "p-filter": "^4.0.0", "url-join": "^5.0.0" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "sha512-shi+Lrf6exeNZF+sBhK+P011LSbhmIAoUEgEY6SsxF8irJ+J2stwI5jkyDQ+4gzYyDImzV6LCKdYB9FXnQRWKA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/npm": ["@semantic-release/npm@11.0.3", "", { "dependencies": { "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "execa": "^8.0.0", "fs-extra": "^11.0.0", "lodash-es": "^4.17.21", "nerf-dart": "^1.0.0", "normalize-url": "^8.0.0", "npm": "^10.5.0", "rc": "^1.2.8", "read-pkg": "^9.0.0", "registry-auth-token": "^5.0.0", "semver": "^7.1.2", "tempy": "^3.0.0" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "sha512-KUsozQGhRBAnoVg4UMZj9ep436VEGwT536/jwSqB7vcEfA6oncCUU7UIYTRdLx7GvTtqn0kBjnkfLVkcnBa2YQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/release-notes-generator": ["@semantic-release/release-notes-generator@12.1.0", "", { "dependencies": { "conventional-changelog-angular": "^7.0.0", "conventional-changelog-writer": "^7.0.0", "conventional-commits-filter": "^4.0.0", "conventional-commits-parser": "^5.0.0", "debug": "^4.0.0", "get-stream": "^7.0.0", "import-from-esm": "^1.0.3", "into-stream": "^7.0.0", "lodash-es": "^4.17.21", "read-pkg-up": "^11.0.0" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "sha512-g6M9AjUKAZUZnxaJZnouNBeDNTCUrJ5Ltj+VJ60gJeDaRRahcHsry9HW8yKrnKkKNkx5lbWiEP1FPMqVNQz8Kg=="], + + "@saithodev/semantic-release-backmerge/semantic-release/aggregate-error": ["aggregate-error@5.0.0", "", { "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" } }, "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/env-ci": ["env-ci@10.0.0", "", { "dependencies": { "execa": "^8.0.0", "java-properties": "^1.0.2" } }, "sha512-U4xcd/utDYFgMh0yWj07R1H6L5fwhVbmxBCpnL0DbVSDZVnsC82HONw0wxtxNkIAcua3KtbomQvIk5xFZGAQJw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], + + "@saithodev/semantic-release-backmerge/semantic-release/find-versions": ["find-versions@5.1.0", "", { "dependencies": { "semver-regex": "^4.0.5" } }, "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg=="], + + "@saithodev/semantic-release-backmerge/semantic-release/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "@saithodev/semantic-release-backmerge/semantic-release/import-from-esm": ["import-from-esm@1.3.4", "", { "dependencies": { "debug": "^4.3.4", "import-meta-resolve": "^4.0.0" } }, "sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg=="], + + "@saithodev/semantic-release-backmerge/semantic-release/marked": ["marked@9.1.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q=="], + + "@saithodev/semantic-release-backmerge/semantic-release/marked-terminal": ["marked-terminal@6.3.0", "", { "dependencies": { "ansi-escapes": "^6.2.0", "chalk": "^5.3.0", "cli-highlight": "^2.1.11", "cli-table3": "^0.6.3", "node-emoji": "^2.1.3", "supports-hyperlinks": "^3.0.0" }, "peerDependencies": { "marked": ">=1 <13" } }, "sha512-icQT4cpNHOC3uxYr0DjZx8yYbFVnb40dzbvbqe9G2CRpFPnCe/RjfPXzP3xKm2vrd8sw4Sqsc3O+IsoPMzE/Iw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/p-reduce": ["p-reduce@3.0.0", "", {}, "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q=="], + + "@saithodev/semantic-release-backmerge/semantic-release/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "@semantic-release/commit-analyzer/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "@semantic-release/exec/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "@semantic-release/exec/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "@semantic-release/exec/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "@semantic-release/exec/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "@semantic-release/exec/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "@semantic-release/exec/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "@semantic-release/git/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "@semantic-release/git/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "@semantic-release/git/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "@semantic-release/git/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "@semantic-release/git/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "@semantic-release/git/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "@semantic-release/github/aggregate-error/clean-stack": ["clean-stack@5.2.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ=="], + + "@semantic-release/github/aggregate-error/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "@semantic-release/github/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "@semantic-release/npm/aggregate-error/clean-stack": ["clean-stack@5.2.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ=="], + + "@semantic-release/npm/aggregate-error/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "@semantic-release/npm/execa/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "@semantic-release/npm/execa/human-signals": ["human-signals@7.0.0", "", {}, "sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q=="], + + "@semantic-release/npm/execa/npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + + "@semantic-release/release-notes-generator/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "agent-base/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "cli-highlight/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "cli-highlight/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "cli-highlight/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], + + "cli-highlight/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + + "env-ci/execa/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + + "env-ci/execa/human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], + + "env-ci/execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], + + "env-ci/execa/npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + + "env-ci/execa/onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], + + "env-ci/execa/strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], + + "esbuild-register/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "http-proxy-agent/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "https-proxy-agent/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "import-from-esm/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "node-gyp/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "npm/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "semantic-release/aggregate-error/clean-stack": ["clean-stack@5.2.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ=="], + + "semantic-release/aggregate-error/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "semantic-release/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "semantic-release/execa/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "semantic-release/execa/human-signals": ["human-signals@7.0.0", "", {}, "sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q=="], + + "semantic-release/execa/npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + + "socks-proxy-agent/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "tar/fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "tuf-js/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "wrap-ansi-cjs/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "@commitlint/top-level/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/commit-analyzer/conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/commit-analyzer/conventional-commits-filter": ["conventional-commits-filter@4.0.0", "", {}, "sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/commit-analyzer/conventional-commits-parser": ["conventional-commits-parser@5.0.0", "", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^2.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.mjs" } }, "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/core": ["@octokit/core@5.2.0", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.3.1", "@octokit/request-error": "^5.1.0", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.1", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-retry": ["@octokit/plugin-retry@6.0.1", "", { "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": ">=5" } }, "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-throttling": ["@octokit/plugin-throttling@8.2.0", "", { "dependencies": { "@octokit/types": "^12.2.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": "^5.0.0" } }, "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/issue-parser": ["issue-parser@6.0.0", "", { "dependencies": { "lodash.capitalize": "^4.2.1", "lodash.escaperegexp": "^4.1.2", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.uniqby": "^4.7.0" } }, "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/release-notes-generator/conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/release-notes-generator/conventional-changelog-writer": ["conventional-changelog-writer@7.0.1", "", { "dependencies": { "conventional-commits-filter": "^4.0.0", "handlebars": "^4.7.7", "json-stringify-safe": "^5.0.1", "meow": "^12.0.1", "semver": "^7.5.2", "split2": "^4.0.0" }, "bin": { "conventional-changelog-writer": "cli.mjs" } }, "sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/release-notes-generator/conventional-commits-filter": ["conventional-commits-filter@4.0.0", "", {}, "sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/release-notes-generator/conventional-commits-parser": ["conventional-commits-parser@5.0.0", "", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^2.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.mjs" } }, "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/release-notes-generator/get-stream": ["get-stream@7.0.1", "", {}, "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/aggregate-error/clean-stack": ["clean-stack@5.2.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/aggregate-error/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "@saithodev/semantic-release-backmerge/semantic-release/execa/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/execa/human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/execa/npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/execa/onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/execa/strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/marked-terminal/ansi-escapes": ["ansi-escapes@6.2.1", "", {}, "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig=="], + + "@semantic-release/github/aggregate-error/clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "@semantic-release/npm/aggregate-error/clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "@semantic-release/npm/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "cli-highlight/chalk/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "env-ci/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "env-ci/execa/onetime/mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], + + "semantic-release/aggregate-error/clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "semantic-release/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "wrap-ansi-cjs/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "@commitlint/top-level/find-up/locate-path/p-locate/p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/commit-analyzer/conventional-commits-parser/split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.0", "", { "dependencies": { "@octokit/request": "^8.3.0", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/core/@octokit/request": ["@octokit/request@8.4.0", "", { "dependencies": { "@octokit/endpoint": "^9.0.1", "@octokit/request-error": "^5.1.0", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/core/@octokit/request-error": ["@octokit/request-error@5.1.0", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/core/before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/core/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-retry/@octokit/request-error": ["@octokit/request-error@5.1.0", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-retry/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-throttling/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/release-notes-generator/conventional-changelog-writer/split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/release-notes-generator/conventional-commits-parser/split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "@saithodev/semantic-release-backmerge/semantic-release/aggregate-error/clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/execa/onetime/mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], + + "cli-highlight/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@9.0.5", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-retry/@octokit/request-error/@octokit/types": ["@octokit/types@13.5.0", "", { "dependencies": { "@octokit/openapi-types": "^22.2.0" } }, "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-retry/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], + + "@saithodev/semantic-release-backmerge/semantic-release/@semantic-release/github/@octokit/plugin-throttling/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], + } +} diff --git a/bun.lockb b/bun.lockb deleted file mode 100755 index 9637065e7518eb76f5ce187b6a0354c634b11bb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 291856 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 j0ZBaQOm^g7?!sK72Mi1{Na8o<$l5V diff --git a/package.json b/package.json index 37d48f5..3f35d59 100644 --- a/package.json +++ b/package.json @@ -29,23 +29,23 @@ ], "packageManager": "bun@1.1.20", "devDependencies": { - "@anolilab/multi-semantic-release": "^1.1.3", - "@biomejs/biome": "^1.9.3", - "@codedependant/semantic-release-docker": "^5.0.3", - "@commitlint/cli": "^19.5.0", - "@commitlint/config-conventional": "^19.5.0", + "@anolilab/multi-semantic-release": "^1.1.10", + "@biomejs/biome": "^1.9.4", + "@codedependant/semantic-release-docker": "^5.1.0", + "@commitlint/cli": "^19.7.1", + "@commitlint/config-conventional": "^19.7.1", "@saithodev/semantic-release-backmerge": "^4.0.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@tsconfig/strictest": "^2.0.5", - "@types/bun": "^1.1.11", + "@types/bun": "^1.2.4", "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.7.21", + "lefthook": "^1.11.2", "portainer-service-webhook": "https://github.com/newarifrh/portainer-service-webhook#v1", - "semantic-release": "^24.1.2", - "turbo": "^2.1.3", - "typescript": "^5.6.3" + "semantic-release": "^24.2.3", + "turbo": "^2.4.4", + "typescript": "^5.8.2" }, "trustedDependencies": [ "@biomejs/biome", From 479812e199b52cdb295a5746e0767306afab3413 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 4 Mar 2025 02:27:48 +0700 Subject: [PATCH 301/312] fix: fix typings and formatting --- apis/websocket/scripts/build.ts | 4 +- apis/websocket/src/classes/Client.ts | 2 +- bots/discord/config.js | 6 +-- bots/discord/package.json | 2 +- .../src/commands/support/train/chat.ts | 6 +-- .../commands/support/train/context-menu.ts | 4 +- .../discord/interactionCreate/chatCommand.ts | 3 +- .../messageCreate/stickyMessageReset.ts | 42 +++++++++++-------- .../messageReactionAdd/correctResponse.ts | 4 +- bots/discord/src/utils/discord/moderation.ts | 2 +- bots/discord/src/utils/duration.ts | 2 +- packages/api/src/classes/ClientWebSocket.ts | 2 +- packages/shared/src/utils/serialization.ts | 2 +- 13 files changed, 43 insertions(+), 38 deletions(-) diff --git a/apis/websocket/scripts/build.ts b/apis/websocket/scripts/build.ts index effc23d..cfdf7be 100644 --- a/apis/websocket/scripts/build.ts +++ b/apis/websocket/scripts/build.ts @@ -1,10 +1,10 @@ import { createLogger } from '@revanced/bot-shared' -import { cp, rm } from 'fs/promises' +import { cp, exists, rm } from 'fs/promises' const logger = createLogger() logger.info('Cleaning previous build...') -await rm('./dist', { recursive: true }) +if (await exists('./dist')) await rm('./dist', { recursive: true }) logger.info('Building WebSocket API...') await Bun.build({ diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index 291a621..2299fcd 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -110,7 +110,7 @@ export default class Client { protected _toBuffer(data: RawData) { if (data instanceof Buffer) return data if (data instanceof ArrayBuffer) return Buffer.from(data) - return Buffer.concat(data) + return Buffer.concat(data as Uint8Array[]) } } diff --git a/bots/discord/config.js b/bots/discord/config.js index 66b1b14..1b04f24 100644 --- a/bots/discord/config.js +++ b/bots/discord/config.js @@ -19,8 +19,8 @@ export default { }, timeout: 60000, forceSendTimeout: 300000, - } - } + }, + }, }, moderation: { cure: { @@ -77,7 +77,7 @@ export default { attachments: { scanAttachments: true, allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp', 'text/plain'], - maxTextFileSize: 512000 + maxTextFileSize: 512000, }, responses: [ { diff --git a/bots/discord/package.json b/bots/discord/package.json index 0718c78..50e7545 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.102", "drizzle-kit": "^0.22.8" } -} \ No newline at end of file +} diff --git a/bots/discord/src/commands/support/train/chat.ts b/bots/discord/src/commands/support/train/chat.ts index 572d8c5..45ae34c 100644 --- a/bots/discord/src/commands/support/train/chat.ts +++ b/bots/discord/src/commands/support/train/chat.ts @@ -1,9 +1,9 @@ import Command from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import { config } from '../../../context' -import type { FetchMessageOptions, MessageResolvable } from 'discord.js' -import type { ConfigMessageScanResponseLabelConfig } from 'config.schema' import { createSuccessEmbed } from '$/utils/discord/embeds' +import type { ConfigMessageScanResponseLabelConfig } from 'config.schema' +import type { FetchMessageOptions, MessageResolvable } from 'discord.js' +import { config } from '../../../context' const msRcConfig = config.messageScan?.humanCorrections?.allow diff --git a/bots/discord/src/commands/support/train/context-menu.ts b/bots/discord/src/commands/support/train/context-menu.ts index 50dab3f..cb343cd 100644 --- a/bots/discord/src/commands/support/train/context-menu.ts +++ b/bots/discord/src/commands/support/train/context-menu.ts @@ -1,8 +1,8 @@ import Command from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import { config } from '../../../context' -import { type APIStringSelectComponent, ComponentType } from 'discord.js' import type { ConfigMessageScanResponseLabelConfig } from 'config.schema' +import { type APIStringSelectComponent, ComponentType } from 'discord.js' +import { config } from '../../../context' const msRcConfig = config.messageScan?.humanCorrections?.allow diff --git a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts index 8807d72..3ae139e 100644 --- a/bots/discord/src/events/discord/interactionCreate/chatCommand.ts +++ b/bots/discord/src/events/discord/interactionCreate/chatCommand.ts @@ -9,8 +9,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => { const command = discord.commands[interaction.commandName] logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag} via chat`) - if (!command) - return void logger.error(`Chat command ${interaction.commandName} not implemented but registered!!!`) + if (!command) return void logger.error(`Chat command ${interaction.commandName} not implemented but registered!!!`) try { logger.debug(`Command ${interaction.commandName} being executed via chat`) diff --git a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts index e55122b..8a7a4f5 100644 --- a/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts +++ b/bots/discord/src/events/discord/messageCreate/stickyMessageReset.ts @@ -8,28 +8,34 @@ withContext(on, 'messageCreate', async ({ discord, logger }, msg) => { if (!store) return if (store.timerActive) { - if (!store.forceTimerActive && store.forceTimerMs) { - logger.debug( - `Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer and clearing existing timer`, - ) + // Timer is already active, so we try to start the force timer + if (store.forceTimerMs) { + // Force timer isn't active, so we start it + if (!store.forceTimerActive) { + logger.debug( + `Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer and clearing existing timer`, + ) - // Clear the timer - clearTimeout(store.timer) - store.timerActive = false + // Clear the timer + clearTimeout(store.timer) + store.timerActive = false - // (Re)start the force timer - store.forceTimerActive = true - if (!store.forceTimer) - store.forceTimer = setTimeout( - () => - store.send(true).then(() => { - store.forceTimerActive = false - }), - store.forceTimerMs, - ) as NodeJS.Timeout - else store.forceTimer.refresh() + // (Re)start the force timer + store.forceTimerActive = true + if (!store.forceTimer) + store.forceTimer = setTimeout( + () => + store.send(true).then(() => { + store.forceTimerActive = false + }), + store.forceTimerMs, + ) as NodeJS.Timeout + else store.forceTimer.refresh() + // Force timer is already active, so we force send + } else store.send() } } else if (!store.forceTimerActive) { + // Both timers aren't active, so we start the timer store.timerActive = true if (!store.timer) store.timer = setTimeout(store.send, store.timerMs) as NodeJS.Timeout } diff --git a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index 04b765c..23a536a 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -20,10 +20,10 @@ const PossibleReactions = Object.values(Reactions) as string[] withContext(on, 'messageReactionAdd', async (context, rct, user) => { if (user.bot) return - + const { database: db, logger, config } = context const { messageScan: msConfig } = config - + // If there's no config, we can't do anything if (!msConfig?.humanCorrections) return diff --git a/bots/discord/src/utils/discord/moderation.ts b/bots/discord/src/utils/discord/moderation.ts index f8a4ad0..64704fa 100644 --- a/bots/discord/src/utils/discord/moderation.ts +++ b/bots/discord/src/utils/discord/moderation.ts @@ -57,7 +57,7 @@ export const cureNickname = async (member: GuildMember) => { cured = member.user.username.length >= 3 ? member.user.username - : config.moderation?.cure?.defaultName ?? 'Server member' + : (config.moderation?.cure?.defaultName ?? 'Server member') if (cured.toLowerCase() === name.toLowerCase()) return diff --git a/bots/discord/src/utils/duration.ts b/bots/discord/src/utils/duration.ts index 8e3074a..a9c1407 100644 --- a/bots/discord/src/utils/duration.ts +++ b/bots/discord/src/utils/duration.ts @@ -1,6 +1,6 @@ import parse from 'parse-duration' -parse[''] = parse['s'] +parse[''] = parse['s']! parse['mo'] = parse['M'] = parse['month']! const defaultUnitValue = parse['']! diff --git a/packages/api/src/classes/ClientWebSocket.ts b/packages/api/src/classes/ClientWebSocket.ts index 9495322..e4c55d9 100755 --- a/packages/api/src/classes/ClientWebSocket.ts +++ b/packages/api/src/classes/ClientWebSocket.ts @@ -207,7 +207,7 @@ export class ClientWebSocketManager { protected _toBuffer(data: RawData) { if (data instanceof Buffer) return data if (data instanceof ArrayBuffer) return Buffer.from(data) - return Buffer.concat(data) + return Buffer.concat(data as Uint8Array[]) } } diff --git a/packages/shared/src/utils/serialization.ts b/packages/shared/src/utils/serialization.ts index 59db862..5f9ba26 100755 --- a/packages/shared/src/utils/serialization.ts +++ b/packages/shared/src/utils/serialization.ts @@ -18,6 +18,6 @@ export function serializePacket(packet: Packet) { * @returns A packet */ export function deserializePacket(buffer: Buffer) { - const data = BSON.deserialize(buffer) + const data = BSON.deserialize(buffer as Uint8Array) return parse(PacketSchema, data) as Packet } From a21aa348d7f32cd0ee65b371e9594520c0a9d3f1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 4 Mar 2025 02:35:19 +0700 Subject: [PATCH 302/312] fix: update repo url --- apis/websocket/package.json | 6 +++--- bots/discord/package.json | 6 +++--- docs/0_development_environment.md | 8 ++++---- package.json | 8 ++++---- packages/api/package.json | 6 +++--- packages/shared/package.json | 6 +++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 358830a..732ea6d 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", + "url": "git+https://github.com/revanced/revanced-bots.git", "directory": "apis/websocket" }, "author": "Palm (https://palmdevs.me)", @@ -23,9 +23,9 @@ ], "license": "GPL-3.0-or-later", "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" + "url": "https://github.com/revanced/revanced-bots/issues" }, - "homepage": "https://github.com/revanced/revanced-helper#readme", + "homepage": "https://github.com/revanced/revanced-bots#readme", "dependencies": { "@revanced/bot-shared": "workspace:*", "@sapphire/async-queue": "^1.5.3", diff --git a/bots/discord/package.json b/bots/discord/package.json index 50e7545..c8a0e63 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -14,7 +14,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", + "url": "git+https://github.com/revanced/revanced-bots.git", "directory": "bots/discord" }, "author": "Palm (https://palmdevs.me)", @@ -24,9 +24,9 @@ ], "license": "GPL-3.0-or-later", "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" + "url": "https://github.com/revanced/revanced-bots/issues" }, - "homepage": "https://github.com/revanced/revanced-helper#readme", + "homepage": "https://github.com/revanced/revanced-bots#readme", "dependencies": { "@discordjs/builders": "^1.9.0", "@discordjs/rest": "^2.4.0", diff --git a/docs/0_development_environment.md b/docs/0_development_environment.md index 817d41a..15784bf 100644 --- a/docs/0_development_environment.md +++ b/docs/0_development_environment.md @@ -1,7 +1,7 @@ # 🏗️ Setting up the development environment -> [!IMPORTANT] -> **This project uses [Bun](https://bun.sh) to run and bundle the code.** +> [!IMPORTANT] +> **This project uses [Bun](https://bun.sh) to run and bundle the code.** > Compatibility with other runtimes (Node.js, Deno, ...) are not guaranteed and most package scripts won't work. To start developing, you'll need to set up the development environment first. @@ -11,8 +11,8 @@ To start developing, you'll need to set up the development environment first. 2. Clone the mono-repository ```sh - git clone https://github.com/revanced/revanced-helper.git && - cd revanced-helper + git clone https://github.com/revanced/revanced-bots.git && + cd revanced-bots ``` 3. Install dependencies diff --git a/package.json b/package.json index 3f35d59..1de79a9 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "revanced-helper", + "name": "revanced-bots", "description": "🤖 Bots assisting ReVanced on multiple platforms", "private": true, "version": "0.0.0", @@ -15,13 +15,13 @@ "flint:check": "biome check .", "clint": "commitlint --edit" }, - "homepage": "https://github.com/revanced/revanced-helper#readme", + "homepage": "https://github.com/revanced/revanced-bots#readme", "repository": { "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git" + "url": "git+https://github.com/revanced/revanced-bots.git" }, "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" + "url": "https://github.com/revanced/revanced-bots/issues" }, "contributors": [ "Palm (https://palmdevs.me)", diff --git a/packages/api/package.json b/packages/api/package.json index 920e1f6..01eb1d3 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", + "url": "git+https://github.com/revanced/revanced-bots.git", "directory": "packages/api" }, "author": "Palm (https://palmdevs.me)", @@ -23,9 +23,9 @@ ], "license": "GPL-3.0-or-later", "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" + "url": "https://github.com/revanced/revanced-bots/issues" }, - "homepage": "https://github.com/revanced/revanced-helper#readme", + "homepage": "https://github.com/revanced/revanced-bots#readme", "dependencies": { "@revanced/bot-shared": "workspace:*", "ws": "^8.18.0" diff --git a/packages/shared/package.json b/packages/shared/package.json index 88180dc..e3c4089 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -16,7 +16,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", + "url": "git+https://github.com/revanced/revanced-bots.git", "directory": "packages/shared" }, "author": "Palm (https://palmdevs.me)", @@ -26,9 +26,9 @@ ], "license": "GPL-3.0-or-later", "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" + "url": "https://github.com/revanced/revanced-bots/issues" }, - "homepage": "https://github.com/revanced/revanced-helper#readme", + "homepage": "https://github.com/revanced/revanced-bots#readme", "dependencies": { "bson": "^6.8.0", "chalk": "^5.3.0", From d290417ff319cbfaf727fb93a8e526f628d7a84b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 3 Mar 2025 19:36:29 +0000 Subject: [PATCH 303/312] chore(release): 1.0.0-dev.10 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.10](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.0-dev.9...@revanced/bot-websocket-api@1.0.0-dev.10) (2025-03-03) ### Bug Fixes * fix typings and formatting ([479812e](https://github.com/revanced/revanced-bots/commit/479812e199b52cdb295a5746e0767306afab3413)) * update repo url ([a21aa34](https://github.com/revanced/revanced-bots/commit/a21aa348d7f32cd0ee65b371e9594520c0a9d3f1)) --- apis/websocket/CHANGELOG.md | 8 ++++++++ apis/websocket/package.json | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index 311a7fa..d86352b 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,11 @@ +# @revanced/bot-websocket-api [1.0.0-dev.10](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.0-dev.9...@revanced/bot-websocket-api@1.0.0-dev.10) (2025-03-03) + + +### Bug Fixes + +* fix typings and formatting ([479812e](https://github.com/revanced/revanced-bots/commit/479812e199b52cdb295a5746e0767306afab3413)) +* update repo url ([a21aa34](https://github.com/revanced/revanced-bots/commit/a21aa348d7f32cd0ee65b371e9594520c0a9d3f1)) + # @revanced/bot-websocket-api [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.8...@revanced/bot-websocket-api@1.0.0-dev.9) (2024-08-03) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 732ea6d..549fee1 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.9", + "version": "1.0.0-dev.10", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { @@ -37,4 +37,4 @@ "@types/ws": "^8.5.12", "typed-emitter": "^2.1.0" } -} +} \ No newline at end of file From c06033e5730f82438e8052b9b519a8f8e2d25437 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 4 Mar 2025 02:38:53 +0700 Subject: [PATCH 304/312] fix(bots/discord/scripts/build): check if dist dir exists before cleaning --- bots/discord/scripts/build.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bots/discord/scripts/build.ts b/bots/discord/scripts/build.ts index 13a9f87..f382cfe 100644 --- a/bots/discord/scripts/build.ts +++ b/bots/discord/scripts/build.ts @@ -1,10 +1,10 @@ import { createLogger } from '@revanced/bot-shared' -import { cp, rm } from 'fs/promises' +import { cp, exists, rm } from 'fs/promises' const logger = createLogger() logger.warn('Cleaning previous build...') -await rm('./dist', { recursive: true }) +if (await exists('./dist')) await rm('./dist', { recursive: true }) logger.info('Building bot...') await Bun.build({ From 75a57b0e161a834a998ac28ae1885a33a7693b27 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 3 Mar 2025 19:40:39 +0000 Subject: [PATCH 305/312] chore(release): 1.0.0-dev.36 [skip ci] # @revanced/discord-bot [1.0.0-dev.36](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.35...@revanced/discord-bot@1.0.0-dev.36) (2025-03-03) ### Bug Fixes * **bots/discord/scripts/build:** check if dist dir exists before cleaning ([c06033e](https://github.com/revanced/revanced-bots/commit/c06033e5730f82438e8052b9b519a8f8e2d25437)) * **bots/discord/utils/duration:** make second the default unit ([5d1af3c](https://github.com/revanced/revanced-bots/commit/5d1af3c31c3379b6a13684dfb07583737908c8aa)) * **bots/discord:** add GuildMember partial ([8e3946a](https://github.com/revanced/revanced-bots/commit/8e3946a66602838715787090008c7bfaf72b67e9)) * **bots/discord:** decrease length of an option in `ban` command ([22d3eea](https://github.com/revanced/revanced-bots/commit/22d3eea88d532792c1237d1a1ab18bc02e57816a)) * **bots/discord:** delete expired appliedPresets entries after unapplying ([14c98e8](https://github.com/revanced/revanced-bots/commit/14c98e87df1ec4fd762bbc48ca4c06470cb110a2)) * fix typings and formatting ([479812e](https://github.com/revanced/revanced-bots/commit/479812e199b52cdb295a5746e0767306afab3413)) * update repo url ([a21aa34](https://github.com/revanced/revanced-bots/commit/a21aa348d7f32cd0ee65b371e9594520c0a9d3f1)) ### Features * **bots/discord:** add more month aliases to duration parser ([c2009ca](https://github.com/revanced/revanced-bots/commit/c2009ca6d42e4387bc5f375d76ecf72991b7fe32)) --- bots/discord/CHANGELOG.md | 18 ++++++++++++++++++ bots/discord/package.json | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 4d4f0c6..18dffff 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,21 @@ +# @revanced/discord-bot [1.0.0-dev.36](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.35...@revanced/discord-bot@1.0.0-dev.36) (2025-03-03) + + +### Bug Fixes + +* **bots/discord/scripts/build:** check if dist dir exists before cleaning ([c06033e](https://github.com/revanced/revanced-bots/commit/c06033e5730f82438e8052b9b519a8f8e2d25437)) +* **bots/discord/utils/duration:** make second the default unit ([5d1af3c](https://github.com/revanced/revanced-bots/commit/5d1af3c31c3379b6a13684dfb07583737908c8aa)) +* **bots/discord:** add GuildMember partial ([8e3946a](https://github.com/revanced/revanced-bots/commit/8e3946a66602838715787090008c7bfaf72b67e9)) +* **bots/discord:** decrease length of an option in `ban` command ([22d3eea](https://github.com/revanced/revanced-bots/commit/22d3eea88d532792c1237d1a1ab18bc02e57816a)) +* **bots/discord:** delete expired appliedPresets entries after unapplying ([14c98e8](https://github.com/revanced/revanced-bots/commit/14c98e87df1ec4fd762bbc48ca4c06470cb110a2)) +* fix typings and formatting ([479812e](https://github.com/revanced/revanced-bots/commit/479812e199b52cdb295a5746e0767306afab3413)) +* update repo url ([a21aa34](https://github.com/revanced/revanced-bots/commit/a21aa348d7f32cd0ee65b371e9594520c0a9d3f1)) + + +### Features + +* **bots/discord:** add more month aliases to duration parser ([c2009ca](https://github.com/revanced/revanced-bots/commit/c2009ca6d42e4387bc5f375d76ecf72991b7fe32)) + # @revanced/discord-bot [1.0.0-dev.35](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.34...@revanced/discord-bot@1.0.0-dev.35) (2024-10-17) diff --git a/bots/discord/package.json b/bots/discord/package.json index c8a0e63..27dc863 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.35", + "version": "1.0.0-dev.36", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.102", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file From d138af46d1f25a11b6f8ab3790ecaa70b1d716a9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 9 Mar 2025 06:32:40 +0700 Subject: [PATCH 306/312] fix(bots/discord/utils/duration): fix specified default unit not working --- bots/discord/src/utils/duration.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/bots/discord/src/utils/duration.ts b/bots/discord/src/utils/duration.ts index a9c1407..43616fa 100644 --- a/bots/discord/src/utils/duration.ts +++ b/bots/discord/src/utils/duration.ts @@ -3,15 +3,12 @@ import parse from 'parse-duration' parse[''] = parse['s']! parse['mo'] = parse['M'] = parse['month']! -const defaultUnitValue = parse['']! - export const parseDuration = (duration: string, defaultUnit?: parse.Units) => { + const defaultUnitValue = parse['']! if (defaultUnit) parse[''] = parse[defaultUnit]! - return ( - // biome-ignore lint/suspicious/noAssignInExpressions: Expression is ignored - // biome-ignore lint/style/noCommaOperator: The last expression (parse call) is returned, it is not confusing - (parse[''] = defaultUnitValue), parse(duration, 'ms') ?? Number.NaN - ) + const result = parse(duration, 'ms') ?? Number.NaN + parse[''] = defaultUnitValue + return result } export const durationToString = (duration: number) => { From c97fffb32f9c6b0ee3333b454a635ea9d846241b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 8 Mar 2025 23:34:30 +0000 Subject: [PATCH 307/312] chore(release): 1.0.0-dev.37 [skip ci] # @revanced/discord-bot [1.0.0-dev.37](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.36...@revanced/discord-bot@1.0.0-dev.37) (2025-03-08) ### Bug Fixes * **bots/discord/utils/duration:** fix specified default unit not working ([d138af4](https://github.com/revanced/revanced-bots/commit/d138af46d1f25a11b6f8ab3790ecaa70b1d716a9)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 18dffff..3498213 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.37](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.36...@revanced/discord-bot@1.0.0-dev.37) (2025-03-08) + + +### Bug Fixes + +* **bots/discord/utils/duration:** fix specified default unit not working ([d138af4](https://github.com/revanced/revanced-bots/commit/d138af46d1f25a11b6f8ab3790ecaa70b1d716a9)) + # @revanced/discord-bot [1.0.0-dev.36](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.35...@revanced/discord-bot@1.0.0-dev.36) (2025-03-03) diff --git a/bots/discord/package.json b/bots/discord/package.json index 27dc863..16dae63 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.36", + "version": "1.0.0-dev.37", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From ba1a467e20adbe81101a41a90651181323b61044 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 4 Apr 2025 23:50:28 +0700 Subject: [PATCH 308/312] chore: update deps --- apis/websocket/package.json | 10 +- bots/discord/package.json | 16 +-- bun.lock | 209 +++++++++++++++++------------------ package.json | 10 +- packages/api/package.json | 4 +- packages/shared/package.json | 4 +- 6 files changed, 123 insertions(+), 130 deletions(-) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 549fee1..82c479d 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -28,13 +28,13 @@ "homepage": "https://github.com/revanced/revanced-bots#readme", "dependencies": { "@revanced/bot-shared": "workspace:*", - "@sapphire/async-queue": "^1.5.3", - "chalk": "^5.3.0", + "@sapphire/async-queue": "^1.5.5", + "chalk": "^5.4.1", "tesseract.js": "^5.1.1", - "ws": "^8.18.0" + "ws": "^8.18.1" }, "devDependencies": { - "@types/ws": "^8.5.12", + "@types/ws": "^8.18.1", "typed-emitter": "^2.1.0" } -} \ No newline at end of file +} diff --git a/bots/discord/package.json b/bots/discord/package.json index 16dae63..70e62aa 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -28,19 +28,19 @@ }, "homepage": "https://github.com/revanced/revanced-bots#readme", "dependencies": { - "@discordjs/builders": "^1.9.0", - "@discordjs/rest": "^2.4.0", + "@discordjs/builders": "^1.10.1", + "@discordjs/rest": "^2.4.3", "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", - "chalk": "^5.3.0", - "decancer": "^3.2.4", - "discord.js": "^14.16.3", + "chalk": "^5.4.1", + "decancer": "^3.2.8", + "discord.js": "^14.18.0", "drizzle-orm": "^0.31.4", - "parse-duration": "^1.1.0" + "parse-duration": "^1.1.2" }, "devDependencies": { "@libsql/client": "^0.7.0", - "discord-api-types": "^0.37.102", + "discord-api-types": "^0.37.119", "drizzle-kit": "^0.22.8" } -} \ No newline at end of file +} diff --git a/bun.lock b/bun.lock index 9acd2b6..f09a920 100644 --- a/bun.lock +++ b/bun.lock @@ -7,54 +7,54 @@ "@anolilab/multi-semantic-release": "^1.1.10", "@biomejs/biome": "^1.9.4", "@codedependant/semantic-release-docker": "^5.1.0", - "@commitlint/cli": "^19.7.1", - "@commitlint/config-conventional": "^19.7.1", + "@commitlint/cli": "^19.8.0", + "@commitlint/config-conventional": "^19.8.0", "@saithodev/semantic-release-backmerge": "^4.0.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@tsconfig/strictest": "^2.0.5", - "@types/bun": "^1.2.4", + "@types/bun": "^1.2.8", "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.11.2", + "lefthook": "^1.11.6", "portainer-service-webhook": "https://github.com/newarifrh/portainer-service-webhook#v1", "semantic-release": "^24.2.3", - "turbo": "^2.4.4", + "turbo": "^2.5.0", "typescript": "^5.8.2", }, }, "apis/websocket": { "name": "@revanced/bot-websocket-api", - "version": "1.0.0-dev.9", + "version": "1.0.0-dev.10", "dependencies": { "@revanced/bot-shared": "workspace:*", - "@sapphire/async-queue": "^1.5.3", - "chalk": "^5.3.0", + "@sapphire/async-queue": "^1.5.5", + "chalk": "^5.4.1", "tesseract.js": "^5.1.1", - "ws": "^8.18.0", + "ws": "^8.18.1", }, "devDependencies": { - "@types/ws": "^8.5.12", + "@types/ws": "^8.18.1", "typed-emitter": "^2.1.0", }, }, "bots/discord": { "name": "@revanced/discord-bot", - "version": "1.0.0-dev.35", + "version": "1.0.0-dev.36", "dependencies": { - "@discordjs/builders": "^1.9.0", - "@discordjs/rest": "^2.4.0", + "@discordjs/builders": "^1.10.1", + "@discordjs/rest": "^2.4.3", "@revanced/bot-api": "workspace:*", "@revanced/bot-shared": "workspace:*", - "chalk": "^5.3.0", - "decancer": "^3.2.4", - "discord.js": "^14.16.3", + "chalk": "^5.4.1", + "decancer": "^3.2.8", + "discord.js": "^14.18.0", "drizzle-orm": "^0.31.4", - "parse-duration": "^1.1.0", + "parse-duration": "^1.1.2", }, "devDependencies": { "@libsql/client": "^0.7.0", - "discord-api-types": "^0.37.102", + "discord-api-types": "^0.37.119", "drizzle-kit": "^0.22.8", }, }, @@ -63,10 +63,10 @@ "version": "0.1.0", "dependencies": { "@revanced/bot-shared": "workspace:*", - "ws": "^8.18.0", + "ws": "^8.18.1", }, "devDependencies": { - "@types/ws": "^8.5.12", + "@types/ws": "^8.18.1", "typed-emitter": "^2.1.0", }, }, @@ -90,7 +90,6 @@ "patchedDependencies": { "@semantic-release/npm@12.0.1": "patches/@semantic-release%2Fnpm@12.0.1.patch", "drizzle-kit@0.22.8": "patches/drizzle-kit@0.22.8.patch", - "decancer@3.2.4": "patches/decancer@3.2.4.patch", }, "packages": { "@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="], @@ -131,51 +130,51 @@ "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], - "@commitlint/cli": ["@commitlint/cli@19.7.1", "", { "dependencies": { "@commitlint/format": "^19.5.0", "@commitlint/lint": "^19.7.1", "@commitlint/load": "^19.6.1", "@commitlint/read": "^19.5.0", "@commitlint/types": "^19.5.0", "tinyexec": "^0.3.0", "yargs": "^17.0.0" }, "bin": { "commitlint": "cli.js" } }, "sha512-iObGjR1tE/PfDtDTEfd+tnRkB3/HJzpQqRTyofS2MPPkDn1mp3DBC8SoPDayokfAy+xKhF8+bwRCJO25Nea0YQ=="], + "@commitlint/cli": ["@commitlint/cli@19.8.0", "", { "dependencies": { "@commitlint/format": "^19.8.0", "@commitlint/lint": "^19.8.0", "@commitlint/load": "^19.8.0", "@commitlint/read": "^19.8.0", "@commitlint/types": "^19.8.0", "tinyexec": "^0.3.0", "yargs": "^17.0.0" }, "bin": { "commitlint": "./cli.js" } }, "sha512-t/fCrLVu+Ru01h0DtlgHZXbHV2Y8gKocTR5elDOqIRUzQd0/6hpt2VIWOj9b3NDo7y4/gfxeR2zRtXq/qO6iUg=="], - "@commitlint/config-conventional": ["@commitlint/config-conventional@19.7.1", "", { "dependencies": { "@commitlint/types": "^19.5.0", "conventional-changelog-conventionalcommits": "^7.0.2" } }, "sha512-fsEIF8zgiI/FIWSnykdQNj/0JE4av08MudLTyYHm4FlLWemKoQvPNUYU2M/3tktWcCEyq7aOkDDgtjrmgWFbvg=="], + "@commitlint/config-conventional": ["@commitlint/config-conventional@19.8.0", "", { "dependencies": { "@commitlint/types": "^19.8.0", "conventional-changelog-conventionalcommits": "^7.0.2" } }, "sha512-9I2kKJwcAPwMoAj38hwqFXG0CzS2Kj+SAByPUQ0SlHTfb7VUhYVmo7G2w2tBrqmOf7PFd6MpZ/a1GQJo8na8kw=="], - "@commitlint/config-validator": ["@commitlint/config-validator@19.5.0", "", { "dependencies": { "@commitlint/types": "^19.5.0", "ajv": "^8.11.0" } }, "sha512-CHtj92H5rdhKt17RmgALhfQt95VayrUo2tSqY9g2w+laAXyk7K/Ef6uPm9tn5qSIwSmrLjKaXK9eiNuxmQrDBw=="], + "@commitlint/config-validator": ["@commitlint/config-validator@19.8.0", "", { "dependencies": { "@commitlint/types": "^19.8.0", "ajv": "^8.11.0" } }, "sha512-+r5ZvD/0hQC3w5VOHJhGcCooiAVdynFlCe2d6I9dU+PvXdV3O+fU4vipVg+6hyLbQUuCH82mz3HnT/cBQTYYuA=="], - "@commitlint/ensure": ["@commitlint/ensure@19.5.0", "", { "dependencies": { "@commitlint/types": "^19.5.0", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1", "lodash.startcase": "^4.4.0", "lodash.upperfirst": "^4.3.1" } }, "sha512-Kv0pYZeMrdg48bHFEU5KKcccRfKmISSm9MvgIgkpI6m+ohFTB55qZlBW6eYqh/XDfRuIO0x4zSmvBjmOwWTwkg=="], + "@commitlint/ensure": ["@commitlint/ensure@19.8.0", "", { "dependencies": { "@commitlint/types": "^19.8.0", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1", "lodash.startcase": "^4.4.0", "lodash.upperfirst": "^4.3.1" } }, "sha512-kNiNU4/bhEQ/wutI1tp1pVW1mQ0QbAjfPRo5v8SaxoVV+ARhkB8Wjg3BSseNYECPzWWfg/WDqQGIfV1RaBFQZg=="], - "@commitlint/execute-rule": ["@commitlint/execute-rule@19.5.0", "", {}, "sha512-aqyGgytXhl2ejlk+/rfgtwpPexYyri4t8/n4ku6rRJoRhGZpLFMqrZ+YaubeGysCP6oz4mMA34YSTaSOKEeNrg=="], + "@commitlint/execute-rule": ["@commitlint/execute-rule@19.8.0", "", {}, "sha512-fuLeI+EZ9x2v/+TXKAjplBJWI9CNrHnyi5nvUQGQt4WRkww/d95oVRsc9ajpt4xFrFmqMZkd/xBQHZDvALIY7A=="], - "@commitlint/format": ["@commitlint/format@19.5.0", "", { "dependencies": { "@commitlint/types": "^19.5.0", "chalk": "^5.3.0" } }, "sha512-yNy088miE52stCI3dhG/vvxFo9e4jFkU1Mj3xECfzp/bIS/JUay4491huAlVcffOoMK1cd296q0W92NlER6r3A=="], + "@commitlint/format": ["@commitlint/format@19.8.0", "", { "dependencies": { "@commitlint/types": "^19.8.0", "chalk": "^5.3.0" } }, "sha512-EOpA8IERpQstxwp/WGnDArA7S+wlZDeTeKi98WMOvaDLKbjptuHWdOYYr790iO7kTCif/z971PKPI2PkWMfOxg=="], - "@commitlint/is-ignored": ["@commitlint/is-ignored@19.7.1", "", { "dependencies": { "@commitlint/types": "^19.5.0", "semver": "^7.6.0" } }, "sha512-3IaOc6HVg2hAoGleRK3r9vL9zZ3XY0rf1RsUf6jdQLuaD46ZHnXBiOPTyQ004C4IvYjSWqJwlh0/u2P73aIE3g=="], + "@commitlint/is-ignored": ["@commitlint/is-ignored@19.8.0", "", { "dependencies": { "@commitlint/types": "^19.8.0", "semver": "^7.6.0" } }, "sha512-L2Jv9yUg/I+jF3zikOV0rdiHUul9X3a/oU5HIXhAJLE2+TXTnEBfqYP9G5yMw/Yb40SnR764g4fyDK6WR2xtpw=="], - "@commitlint/lint": ["@commitlint/lint@19.7.1", "", { "dependencies": { "@commitlint/is-ignored": "^19.7.1", "@commitlint/parse": "^19.5.0", "@commitlint/rules": "^19.6.0", "@commitlint/types": "^19.5.0" } }, "sha512-LhcPfVjcOcOZA7LEuBBeO00o3MeZa+tWrX9Xyl1r9PMd5FWsEoZI9IgnGqTKZ0lZt5pO3ZlstgnRyY1CJJc9Xg=="], + "@commitlint/lint": ["@commitlint/lint@19.8.0", "", { "dependencies": { "@commitlint/is-ignored": "^19.8.0", "@commitlint/parse": "^19.8.0", "@commitlint/rules": "^19.8.0", "@commitlint/types": "^19.8.0" } }, "sha512-+/NZKyWKSf39FeNpqhfMebmaLa1P90i1Nrb1SrA7oSU5GNN/lksA4z6+ZTnsft01YfhRZSYMbgGsARXvkr/VLQ=="], - "@commitlint/load": ["@commitlint/load@19.6.1", "", { "dependencies": { "@commitlint/config-validator": "^19.5.0", "@commitlint/execute-rule": "^19.5.0", "@commitlint/resolve-extends": "^19.5.0", "@commitlint/types": "^19.5.0", "chalk": "^5.3.0", "cosmiconfig": "^9.0.0", "cosmiconfig-typescript-loader": "^6.1.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "lodash.uniq": "^4.5.0" } }, "sha512-kE4mRKWWNju2QpsCWt428XBvUH55OET2N4QKQ0bF85qS/XbsRGG1MiTByDNlEVpEPceMkDr46LNH95DtRwcsfA=="], + "@commitlint/load": ["@commitlint/load@19.8.0", "", { "dependencies": { "@commitlint/config-validator": "^19.8.0", "@commitlint/execute-rule": "^19.8.0", "@commitlint/resolve-extends": "^19.8.0", "@commitlint/types": "^19.8.0", "chalk": "^5.3.0", "cosmiconfig": "^9.0.0", "cosmiconfig-typescript-loader": "^6.1.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "lodash.uniq": "^4.5.0" } }, "sha512-4rvmm3ff81Sfb+mcWT5WKlyOa+Hd33WSbirTVUer0wjS1Hv/Hzr07Uv1ULIV9DkimZKNyOwXn593c+h8lsDQPQ=="], - "@commitlint/message": ["@commitlint/message@19.5.0", "", {}, "sha512-R7AM4YnbxN1Joj1tMfCyBryOC5aNJBdxadTZkuqtWi3Xj0kMdutq16XQwuoGbIzL2Pk62TALV1fZDCv36+JhTQ=="], + "@commitlint/message": ["@commitlint/message@19.8.0", "", {}, "sha512-qs/5Vi9bYjf+ZV40bvdCyBn5DvbuelhR6qewLE8Bh476F7KnNyLfdM/ETJ4cp96WgeeHo6tesA2TMXS0sh5X4A=="], - "@commitlint/parse": ["@commitlint/parse@19.5.0", "", { "dependencies": { "@commitlint/types": "^19.5.0", "conventional-changelog-angular": "^7.0.0", "conventional-commits-parser": "^5.0.0" } }, "sha512-cZ/IxfAlfWYhAQV0TwcbdR1Oc0/r0Ik1GEessDJ3Lbuma/MRO8FRQX76eurcXtmhJC//rj52ZSZuXUg0oIX0Fw=="], + "@commitlint/parse": ["@commitlint/parse@19.8.0", "", { "dependencies": { "@commitlint/types": "^19.8.0", "conventional-changelog-angular": "^7.0.0", "conventional-commits-parser": "^5.0.0" } }, "sha512-YNIKAc4EXvNeAvyeEnzgvm1VyAe0/b3Wax7pjJSwXuhqIQ1/t2hD3OYRXb6D5/GffIvaX82RbjD+nWtMZCLL7Q=="], - "@commitlint/read": ["@commitlint/read@19.5.0", "", { "dependencies": { "@commitlint/top-level": "^19.5.0", "@commitlint/types": "^19.5.0", "git-raw-commits": "^4.0.0", "minimist": "^1.2.8", "tinyexec": "^0.3.0" } }, "sha512-TjS3HLPsLsxFPQj6jou8/CZFAmOP2y+6V4PGYt3ihbQKTY1Jnv0QG28WRKl/d1ha6zLODPZqsxLEov52dhR9BQ=="], + "@commitlint/read": ["@commitlint/read@19.8.0", "", { "dependencies": { "@commitlint/top-level": "^19.8.0", "@commitlint/types": "^19.8.0", "git-raw-commits": "^4.0.0", "minimist": "^1.2.8", "tinyexec": "^0.3.0" } }, "sha512-6ywxOGYajcxK1y1MfzrOnwsXO6nnErna88gRWEl3qqOOP8MDu/DTeRkGLXBFIZuRZ7mm5yyxU5BmeUvMpNte5w=="], - "@commitlint/resolve-extends": ["@commitlint/resolve-extends@19.5.0", "", { "dependencies": { "@commitlint/config-validator": "^19.5.0", "@commitlint/types": "^19.5.0", "global-directory": "^4.0.1", "import-meta-resolve": "^4.0.0", "lodash.mergewith": "^4.6.2", "resolve-from": "^5.0.0" } }, "sha512-CU/GscZhCUsJwcKTJS9Ndh3AKGZTNFIOoQB2n8CmFnizE0VnEuJoum+COW+C1lNABEeqk6ssfc1Kkalm4bDklA=="], + "@commitlint/resolve-extends": ["@commitlint/resolve-extends@19.8.0", "", { "dependencies": { "@commitlint/config-validator": "^19.8.0", "@commitlint/types": "^19.8.0", "global-directory": "^4.0.1", "import-meta-resolve": "^4.0.0", "lodash.mergewith": "^4.6.2", "resolve-from": "^5.0.0" } }, "sha512-CLanRQwuG2LPfFVvrkTrBR/L/DMy3+ETsgBqW1OvRxmzp/bbVJW0Xw23LnnExgYcsaFtos967lul1CsbsnJlzQ=="], - "@commitlint/rules": ["@commitlint/rules@19.6.0", "", { "dependencies": { "@commitlint/ensure": "^19.5.0", "@commitlint/message": "^19.5.0", "@commitlint/to-lines": "^19.5.0", "@commitlint/types": "^19.5.0" } }, "sha512-1f2reW7lbrI0X0ozZMesS/WZxgPa4/wi56vFuJENBmed6mWq5KsheN/nxqnl/C23ioxpPO/PL6tXpiiFy5Bhjw=="], + "@commitlint/rules": ["@commitlint/rules@19.8.0", "", { "dependencies": { "@commitlint/ensure": "^19.8.0", "@commitlint/message": "^19.8.0", "@commitlint/to-lines": "^19.8.0", "@commitlint/types": "^19.8.0" } }, "sha512-IZ5IE90h6DSWNuNK/cwjABLAKdy8tP8OgGVGbXe1noBEX5hSsu00uRlLu6JuruiXjWJz2dZc+YSw3H0UZyl/mA=="], - "@commitlint/to-lines": ["@commitlint/to-lines@19.5.0", "", {}, "sha512-R772oj3NHPkodOSRZ9bBVNq224DOxQtNef5Pl8l2M8ZnkkzQfeSTr4uxawV2Sd3ui05dUVzvLNnzenDBO1KBeQ=="], + "@commitlint/to-lines": ["@commitlint/to-lines@19.8.0", "", {}, "sha512-3CKLUw41Cur8VMjh16y8LcsOaKbmQjAKCWlXx6B0vOUREplp6em9uIVhI8Cv934qiwkbi2+uv+mVZPnXJi1o9A=="], - "@commitlint/top-level": ["@commitlint/top-level@19.5.0", "", { "dependencies": { "find-up": "^7.0.0" } }, "sha512-IP1YLmGAk0yWrImPRRc578I3dDUI5A2UBJx9FbSOjxe9sTlzFiwVJ+zeMLgAtHMtGZsC8LUnzmW1qRemkFU4ng=="], + "@commitlint/top-level": ["@commitlint/top-level@19.8.0", "", { "dependencies": { "find-up": "^7.0.0" } }, "sha512-Rphgoc/omYZisoNkcfaBRPQr4myZEHhLPx2/vTXNLjiCw4RgfPR1wEgUpJ9OOmDCiv5ZyIExhprNLhteqH4FuQ=="], - "@commitlint/types": ["@commitlint/types@19.5.0", "", { "dependencies": { "@types/conventional-commits-parser": "^5.0.0", "chalk": "^5.3.0" } }, "sha512-DSHae2obMSMkAtTBSOulg5X7/z+rGLxcXQIkg3OmWvY6wifojge5uVMydfhUvs7yQj+V7jNmRZ2Xzl8GJyqRgg=="], + "@commitlint/types": ["@commitlint/types@19.8.0", "", { "dependencies": { "@types/conventional-commits-parser": "^5.0.0", "chalk": "^5.3.0" } }, "sha512-LRjP623jPyf3Poyfb0ohMj8I3ORyBDOwXAgxxVPbSD0unJuW2mJWeiRfaQinjtccMqC5Wy1HOMfa4btKjbNxbg=="], - "@discordjs/builders": ["@discordjs/builders@1.9.0", "", { "dependencies": { "@discordjs/formatters": "^0.5.0", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "0.37.97", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg=="], + "@discordjs/builders": ["@discordjs/builders@1.10.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.0", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.37.119", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng=="], "@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], - "@discordjs/formatters": ["@discordjs/formatters@0.5.0", "", { "dependencies": { "discord-api-types": "0.37.97" } }, "sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g=="], + "@discordjs/formatters": ["@discordjs/formatters@0.6.0", "", { "dependencies": { "discord-api-types": "^0.37.114" } }, "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw=="], - "@discordjs/rest": ["@discordjs/rest@2.4.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "0.37.97", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.19.8" } }, "sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw=="], + "@discordjs/rest": ["@discordjs/rest@2.4.3", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.37.119", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.1" } }, "sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA=="], "@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="], - "@discordjs/ws": ["@discordjs/ws@1.1.1", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.3.0", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "0.37.83", "tslib": "^2.6.2", "ws": "^8.16.0" } }, "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA=="], + "@discordjs/ws": ["@discordjs/ws@1.2.1", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.4.3", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.37.119", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ=="], "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], @@ -335,7 +334,7 @@ "@saithodev/semantic-release-backmerge": ["@saithodev/semantic-release-backmerge@4.0.1", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.1.0", "debug": "^4.3.4", "execa": "^5.1.1", "lodash": "^4.17.21", "semantic-release": "^22.0.7" } }, "sha512-WDsU28YrXSLx0xny7FgFlEk8DCKGcj6OOhA+4Q9k3te1jJD1GZuqY8sbIkVQaw9cqJ7CT+fCZUN6QDad8JW4Dg=="], - "@sapphire/async-queue": ["@sapphire/async-queue@1.5.3", "", {}, "sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w=="], + "@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="], "@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="], @@ -383,7 +382,7 @@ "@tufjs/models": ["@tufjs/models@2.0.1", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^9.0.4" } }, "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg=="], - "@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="], + "@types/bun": ["@types/bun@1.2.8", "", { "dependencies": { "bun-types": "1.2.7" } }, "sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w=="], "@types/conventional-commits-parser": ["@types/conventional-commits-parser@5.0.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ=="], @@ -393,29 +392,33 @@ "@types/semver": ["@types/semver@7.5.8", "", {}, "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="], - "@types/ws": ["@types/ws@8.5.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ=="], + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@vierofernando/decancer-android-arm-eabi": ["@vierofernando/decancer-android-arm-eabi@3.2.4", "", { "os": "android", "cpu": "arm" }, "sha512-eZ4IWFzQqbP1eO7UNOtJFDahZHSqR74kV0yt0QbuAZQv3X7A0gXvoQNc1EnPQCkb5JrOhwjt59IPI8Z7eZlBJw=="], + "@vierofernando/decancer-android-arm-eabi": ["@vierofernando/decancer-android-arm-eabi@3.2.8", "", { "os": "android", "cpu": "arm" }, "sha512-MnecYS0LDZUXmSc4fE6C4ecGwzDCI0cmWpGyud6hndpFCxficCZZn0816EQTieAoJzUr2TkQ6iiJnZcTx5nVMA=="], - "@vierofernando/decancer-android-arm64": ["@vierofernando/decancer-android-arm64@3.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-+q6e/wqPJANnn0AzdEYDaxC2XfZmdqKachUVUkyj2SKgUmPgZoU04R3jPumgxx+Vg9IgplxOuNdFlEfmoYW83w=="], + "@vierofernando/decancer-android-arm64": ["@vierofernando/decancer-android-arm64@3.2.8", "", { "os": "android", "cpu": "arm64" }, "sha512-rTFNDrvEz6YI3PyGY8lTkCmhOVGopazeMi6LKj0KFRk7b3GkFdTlZNr4+RQMzs+PgB2+jPaWKo2fU0enmPHaRg=="], - "@vierofernando/decancer-darwin-arm64": ["@vierofernando/decancer-darwin-arm64@3.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-O/peTIPTUPR/lK3f/8EWSUAEwZjTB5xb1PnBaaOpmmAzHH9fh1HNGYx9KqX+8l5MG3mB2fwyu7US0yN1iAjNdw=="], + "@vierofernando/decancer-darwin-arm64": ["@vierofernando/decancer-darwin-arm64@3.2.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HTjC+1A9zTukbyL7KASasAcy/0WTtc5BLe9pOT2TuSs+kGgrNWo+YIXZ1/XtcUgrpXwGdWzjp4EQo/vGbQvGHw=="], - "@vierofernando/decancer-linux-arm-gnueabihf": ["@vierofernando/decancer-linux-arm-gnueabihf@3.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-qnShXa/mFmfBO54HkfGUCL5B6o31C3XUdaPO934PffIF6wPCN6cj0KInnkGcJHYZLn83GNryDB9T8SK7UgnJOA=="], + "@vierofernando/decancer-darwin-x64": ["@vierofernando/decancer-darwin-x64@3.2.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Y2KI9t9AesqoE0PhKG8XaBfCHGw/sNci6fRViK2wjuoVx3gZdBcNKkF1OO/oCDHFaBNi43l9FVPLs1erPQIAow=="], - "@vierofernando/decancer-linux-arm64-gnu": ["@vierofernando/decancer-linux-arm64-gnu@3.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-xAKBSMRUQHzJzaysFUs0S0ziYF6cbhc12ELzn/MnvCkfJ2yNuWuazxYzzhfPdxKeMxV/ooNe18elX/0Z7Xl4Og=="], + "@vierofernando/decancer-freebsd-x64": ["@vierofernando/decancer-freebsd-x64@3.2.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-L68J4pC/2cNL9MRQpLD5VFiu0zXlfjgwHcXdK/VR9reT8GOWvw7aQnPKFEu7FduJtldoZZt25uGZr2fO+7EWCA=="], - "@vierofernando/decancer-linux-arm64-musl": ["@vierofernando/decancer-linux-arm64-musl@3.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-eZHBK7HeTQI5f0KjnDSlASl+rjGVqwyoXeVxoeYGQxEi3XFyeOfmi2oaCqTjOr2wfXhZ+cCRz+1WOr0K+dAaaA=="], + "@vierofernando/decancer-linux-arm-gnueabihf": ["@vierofernando/decancer-linux-arm-gnueabihf@3.2.8", "", { "os": "linux", "cpu": "arm" }, "sha512-gu40wyOYI+ozm1zCnByA7ooC0zLPEl3+wYra0Px+n+WZI7kErQXNgJmLHqy3QmDZiphlXSJoFcO4rlvd3jpJzQ=="], - "@vierofernando/decancer-linux-x64-gnu": ["@vierofernando/decancer-linux-x64-gnu@3.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-33Ynp93KPcjRIG9Ug5Ywl36cLPfj07r9r1CQFmor86hL8FdikQDIWVDEj9oTE4Edp9oQTilUBgz6JecC8v0iCw=="], + "@vierofernando/decancer-linux-arm64-gnu": ["@vierofernando/decancer-linux-arm64-gnu@3.2.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-FlLSYNo/isdIcJo1pMr2TqehcidRidcMih2xmM4f/c/RengjBljiIfi3OFYPPVTPAhOMn56L88i9PYIqd2jlZA=="], - "@vierofernando/decancer-linux-x64-musl": ["@vierofernando/decancer-linux-x64-musl@3.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Ef6qT9ht0XrSQInXjHfsARTovYNBkiIr0sUWfz9bbi0wKgRn+2/YwdgmRLaubwMWp2bMdijN49myfjAVmPZpqw=="], + "@vierofernando/decancer-linux-arm64-musl": ["@vierofernando/decancer-linux-arm64-musl@3.2.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-A70AAzdRD7CMx8lnRHl4Qst83nsqpzEvNmXbXkbd9FXntao3P+VW7kHcJkWWl51xUYopCZ8geFRgA4SPBQFbUg=="], - "@vierofernando/decancer-win32-arm64-msvc": ["@vierofernando/decancer-win32-arm64-msvc@3.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-/uLoF2jqbdWvTCaCTx/bibZg85AVgRn3J5m/3i4Wu7Rzgarlbm7nZIvGqIv2Kn5bqbaX+VPvSCZHTCY3posWpg=="], + "@vierofernando/decancer-linux-x64-gnu": ["@vierofernando/decancer-linux-x64-gnu@3.2.8", "", { "os": "linux", "cpu": "x64" }, "sha512-kGru28vMBBgz5vpeFKbB3qshfAqgW/RGhjs2GPc73iQ6LX7zLdaD3U8RCKCVPEFDyuN/TpSw1XOckth8sIMdRA=="], - "@vierofernando/decancer-win32-ia32-msvc": ["@vierofernando/decancer-win32-ia32-msvc@3.2.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-FAIyRWfpW1ioh4rwse61bp0YN7jxutJuF3zNFsxdOgc7tuxYq7ML0NGZxI5wSVtER0HkoktrFPmMOKKg3aWGEg=="], + "@vierofernando/decancer-linux-x64-musl": ["@vierofernando/decancer-linux-x64-musl@3.2.8", "", { "os": "linux", "cpu": "x64" }, "sha512-Aox1+NGM6xduAaInxmfBdc3+0kqjzLdxE3QffNo0QAk6k4ew2NUjdTStvQ/0FHjXe9fmgXDjhrbPLGgh+V/1MQ=="], - "@vierofernando/decancer-win32-x64-msvc": ["@vierofernando/decancer-win32-x64-msvc@3.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-9C1k77zT6fXmsDqkJHPJIbJkkw8bgDB4XgvGrLjLJBqg5Dp8GEBSXB2BsnRyep8cQG15jORx/dA4FpaI8UXUgQ=="], + "@vierofernando/decancer-win32-arm64-msvc": ["@vierofernando/decancer-win32-arm64-msvc@3.2.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-buaLM+VaTi3OKEdf6zhg/jZF783CDBbSUapCJYVFxieMCD1RUp0di0jFmVvPfoY/Kj2QfjYuwZJDHXQv3Ds54Q=="], + + "@vierofernando/decancer-win32-ia32-msvc": ["@vierofernando/decancer-win32-ia32-msvc@3.2.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-h3UWbzM3acOXegnEG0GHHC1yEoRpTAyMsOIoGLtsUVr1u/DrEUhOYNBiiQMCa1+9GjZsiEPzIGVoEh5kqPuwSg=="], + + "@vierofernando/decancer-win32-x64-msvc": ["@vierofernando/decancer-win32-x64-msvc@3.2.8", "", { "os": "win32", "cpu": "x64" }, "sha512-zWwNSpt5A69rmjtwx31Nw/KocMNvzFfoi66dK6CtCoLraSpUVXds7BOAGDbaY7eZWdgFBOuePHqy8WMkxrPnoQ=="], "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="], @@ -465,17 +468,17 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "bson": ["bson@6.8.0", "", {}, "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ=="], + "bson": ["bson@6.10.3", "", {}, "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="], + "bun-types": ["bun-types@1.2.7", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-P4hHhk7kjF99acXqKvltyuMQ2kf/rzIw3ylEDpCxDS9Xa0X0Yp/gJu/vDCucmWpiur5qJ0lwB2bWzOXa2GlHqA=="], "cacache": ["cacache@18.0.4", "", { "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^4.0.0", "ssri": "^10.0.0", "tar": "^6.1.11", "unique-filename": "^3.0.0" } }, "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], @@ -541,7 +544,7 @@ "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "decancer": ["decancer@3.2.4", "", { "optionalDependencies": { "@vierofernando/decancer-android-arm-eabi": "3.2.4", "@vierofernando/decancer-android-arm64": "3.2.4", "@vierofernando/decancer-darwin-arm64": "3.2.4", "@vierofernando/decancer-darwin-x64": "3.2.4", "@vierofernando/decancer-linux-arm-gnueabihf": "3.2.4", "@vierofernando/decancer-linux-arm64-gnu": "3.2.4", "@vierofernando/decancer-linux-arm64-musl": "3.2.4", "@vierofernando/decancer-linux-x64-gnu": "3.2.4", "@vierofernando/decancer-linux-x64-musl": "3.2.4", "@vierofernando/decancer-win32-arm64-msvc": "3.2.4", "@vierofernando/decancer-win32-ia32-msvc": "3.2.4", "@vierofernando/decancer-win32-x64-msvc": "3.2.4" } }, "sha512-96iSooOgFfb8VUWQShcmBf2R6H3yVmYJDCywiSLQUtRL5rHnpn2TYQq55LXUQG/746U94ebww0cE34/BzibcIA=="], + "decancer": ["decancer@3.2.8", "", { "optionalDependencies": { "@vierofernando/decancer-android-arm-eabi": "3.2.8", "@vierofernando/decancer-android-arm64": "3.2.8", "@vierofernando/decancer-darwin-arm64": "3.2.8", "@vierofernando/decancer-darwin-x64": "3.2.8", "@vierofernando/decancer-freebsd-x64": "3.2.8", "@vierofernando/decancer-linux-arm-gnueabihf": "3.2.8", "@vierofernando/decancer-linux-arm64-gnu": "3.2.8", "@vierofernando/decancer-linux-arm64-musl": "3.2.8", "@vierofernando/decancer-linux-x64-gnu": "3.2.8", "@vierofernando/decancer-linux-x64-musl": "3.2.8", "@vierofernando/decancer-win32-arm64-msvc": "3.2.8", "@vierofernando/decancer-win32-ia32-msvc": "3.2.8", "@vierofernando/decancer-win32-x64-msvc": "3.2.8" } }, "sha512-KGz0d/6gV0aPqaDb11ZPV38kBdqQ8198Mo2ZY8lW59XGq9o6uRIy+k3AIqmj9vZLKDniP7uVXstj0IBOYkzM6w=="], "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], @@ -557,9 +560,9 @@ "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], - "discord-api-types": ["discord-api-types@0.37.102", "", {}, "sha512-5+m5twqG8n77rLhKuh2c/971UWszEL/c3KbdvVLUBTPXuS8PbYC/7W7NYhwP02qowjj6CHoKYZbD0ppOUCsT6g=="], + "discord-api-types": ["discord-api-types@0.37.119", "", {}, "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg=="], - "discord.js": ["discord.js@14.16.3", "", { "dependencies": { "@discordjs/builders": "^1.9.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.5.0", "@discordjs/rest": "^2.4.0", "@discordjs/util": "^1.1.1", "@discordjs/ws": "1.1.1", "@sapphire/snowflake": "3.5.3", "discord-api-types": "0.37.100", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "tslib": "^2.6.3", "undici": "6.19.8" } }, "sha512-EPCWE9OkA9DnFFNrO7Kl1WHHDYFXu3CNVFJg63bfU7hVtjZGyhShwZtSBImINQRWxWP2tgo2XI+QhdXx28r0aA=="], + "discord.js": ["discord.js@14.18.0", "", { "dependencies": { "@discordjs/builders": "^1.10.1", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.0", "@discordjs/rest": "^2.4.3", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.1", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.37.119", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "tslib": "^2.6.3", "undici": "6.21.1" } }, "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw=="], "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], @@ -771,27 +774,27 @@ "just-diff-apply": ["just-diff-apply@5.5.0", "", {}, "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="], - "lefthook": ["lefthook@1.11.2", "", { "optionalDependencies": { "lefthook-darwin-arm64": "1.11.2", "lefthook-darwin-x64": "1.11.2", "lefthook-freebsd-arm64": "1.11.2", "lefthook-freebsd-x64": "1.11.2", "lefthook-linux-arm64": "1.11.2", "lefthook-linux-x64": "1.11.2", "lefthook-openbsd-arm64": "1.11.2", "lefthook-openbsd-x64": "1.11.2", "lefthook-windows-arm64": "1.11.2", "lefthook-windows-x64": "1.11.2" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-/5royc/WbL2KTfFJ54wEdvxUZOBXwc54v/fW2Bz4LMOkAA3LWIxnoUiybSiauu+nhdTG98qERxH1YHwF2wZlAA=="], + "lefthook": ["lefthook@1.11.6", "", { "optionalDependencies": { "lefthook-darwin-arm64": "1.11.6", "lefthook-darwin-x64": "1.11.6", "lefthook-freebsd-arm64": "1.11.6", "lefthook-freebsd-x64": "1.11.6", "lefthook-linux-arm64": "1.11.6", "lefthook-linux-x64": "1.11.6", "lefthook-openbsd-arm64": "1.11.6", "lefthook-openbsd-x64": "1.11.6", "lefthook-windows-arm64": "1.11.6", "lefthook-windows-x64": "1.11.6" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-j0VmMM50WlPDassmgvapRum9po29Tv1BXzBNFpzGkk9E91CEG9jKik/OHyH/r/na+q8qNIUUyPL6QQuTN/UhQQ=="], - "lefthook-darwin-arm64": ["lefthook-darwin-arm64@1.11.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8DpvrybtWdt6UmfZk+hA8daYXr6zkpJVogZ8M49BQx6ISSKUaC03xzO1m4MrAsoKok77ka4JAidYhOa2gCu15A=="], + "lefthook-darwin-arm64": ["lefthook-darwin-arm64@1.11.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gWgdWrKgZgX+bKc6Vs/x7JkO+58lLOpRzpteLx//82D0MKVPlNZwjd4zz4AbIBXtM4Hcj+6gSsOzQ7QDXxjVvQ=="], - "lefthook-darwin-x64": ["lefthook-darwin-x64@1.11.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-DrL1SOT8lJksjudRu6fTZTp3M0EbpCP2RQ22MDT71clS8BMrFL8x3h9Ziw+uNH76j9zA241tW5zMxWMSv+foAA=="], + "lefthook-darwin-x64": ["lefthook-darwin-x64@1.11.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ia0TjTKuYcSaDTuoCnbWtpPZ2VEoKzgn33OB90VjNaSVs4ooE0PIdpO+w00x1elqIaf1pbrpq6HgeB26Du8KbQ=="], - "lefthook-freebsd-arm64": ["lefthook-freebsd-arm64@1.11.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-AliG4Wi8BNC27hCSnuFBeUXh/eA3fppnUbQQPISy/G94yfwRkzyml9MZzvb7HKmUpw1LT0sq9RQ6FQPxBZ2DYA=="], + "lefthook-freebsd-arm64": ["lefthook-freebsd-arm64@1.11.6", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-PxIwj+hmjLahyzEmcIfalIBDhgklAQCavwM4sGCgbzDi4/+VQX+4aEs4pQqtd7v3aohmjtO/4n2emzTI8donww=="], - "lefthook-freebsd-x64": ["lefthook-freebsd-x64@1.11.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V6cgRCoi5+jcq6XBIdRYraeEOK1UhBrtL/XZlNypAIkhPoBtfTP9u2wSprGMDzZvJCRriLXZxV/d0v94laKXzA=="], + "lefthook-freebsd-x64": ["lefthook-freebsd-x64@1.11.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-3o1lMKxz1VtWaP/o117wgUn3ZOpefMoSf+8LuiTzI3/PDprIuzgyw2nXKlBZAMDpNPHMNnJeQNts9XLMRmkldg=="], - "lefthook-linux-arm64": ["lefthook-linux-arm64@1.11.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-VKcK7sjIK8UpXX/qK6Fxa0Lnwr4gzRtlXDS17jzxThcyFk8iGBpQ+9ZnPLv2yAaEIzmGhJUG9sDgOb9IQ5kpBQ=="], + "lefthook-linux-arm64": ["lefthook-linux-arm64@1.11.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-nKPFZ5cA9f5tVn0ybDVqcXXlpTHZqo05N4KQRhWTj5Nem+JoD2YzJIlvZhdJhUrldERqj6deDMXChH5T3z4Rrw=="], - "lefthook-linux-x64": ["lefthook-linux-x64@1.11.2", "", { "os": "linux", "cpu": "x64" }, "sha512-aGa2Krph14YwSW7KF0PrlCBK9P7V/Z4oFklonmz3r2Fjm8EdhA750y7OQvA9KerXRleIb5SaUH/cz1azG/izeQ=="], + "lefthook-linux-x64": ["lefthook-linux-x64@1.11.6", "", { "os": "linux", "cpu": "x64" }, "sha512-naN8dllLCOEeP+wznLnq+oXrs1dvt/iMLkcl+pOPWLqFccPfDiHzr8V8GslaTa+rSFsAnvjR7SJIOi5C29xedA=="], - "lefthook-openbsd-arm64": ["lefthook-openbsd-arm64@1.11.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-f7owNQ9Ki6Y07KBgdXdH28EYO0eBdZuGTpIggMeHNhYFVDavxuINP2BjmbXtzpUu8K5BX6exGx0umtWhRhXbvQ=="], + "lefthook-openbsd-arm64": ["lefthook-openbsd-arm64@1.11.6", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-dPxhJfYQ667T+U3pz1+O3mTRNHzXH/BvPlXSH+oy8uiSry4AtVNRXkVvXPUcpLlrAy6HuFYodsrpCIlWFeYwiQ=="], - "lefthook-openbsd-x64": ["lefthook-openbsd-x64@1.11.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HKv6PV64vOjqPrlxAqo07N9+Z34jdPDBfeExqi0ldR7vACFaBJFIdhWCLLP+3uQUrNKc8GXlikqplZn8MgRSQw=="], + "lefthook-openbsd-x64": ["lefthook-openbsd-x64@1.11.6", "", { "os": "openbsd", "cpu": "x64" }, "sha512-9D26kcSsjiW4D0AuVDdi+0ZqrsOzRWOpMS/kcUbLfrU99yCvma0rMTqKbbDMkVur/znS7qL53oGahXCXDNA+IQ=="], - "lefthook-windows-arm64": ["lefthook-windows-arm64@1.11.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-042jCKZ/H+lS6XYoMIf2FWMP2hxXqfAT52UW6lYObIOvQ5xu/epUXFjtmXRyYxCv57No3JYYMg1Yr06xdzTKkQ=="], + "lefthook-windows-arm64": ["lefthook-windows-arm64@1.11.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-xdCenr4+BFnfBEhiXj6GJp02EPmcwTAGa7NYm6hVTfDwGXw24tuLv7lpnGjgK3kovN6EukgLH1FYkeyDOBEMnA=="], - "lefthook-windows-x64": ["lefthook-windows-x64@1.11.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1Map6Ck2AyfY6ptN9T19N41HFKFqRTzmILtGaRGJABEzHiE4+gSWcq5YT1R6cCtkVlewD3Lx+J/80D/Kb/cVtw=="], + "lefthook-windows-x64": ["lefthook-windows-x64@1.11.6", "", { "os": "win32", "cpu": "x64" }, "sha512-Fg2GzLhzeDV/GX8+ydrI0wBOytQWpPkNdngx+a8B/feCDbwjAiFklDG5oV4ytuWrtg1JPEEWLJd6nHefj4wtHA=="], "libnpmaccess": ["libnpmaccess@8.0.6", "", { "dependencies": { "npm-package-arg": "^11.0.2", "npm-registry-fetch": "^17.0.1" } }, "sha512-uM8DHDEfYG6G5gVivVl+yQd4pH3uRclHC59lzIbSvy7b5FEwR+mU49Zq1jEyRtRFv7+M99mUW9S0wL/4laT4lw=="], @@ -977,7 +980,7 @@ "parse-conflict-json": ["parse-conflict-json@3.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^3.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw=="], - "parse-duration": ["parse-duration@1.1.0", "", {}, "sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ=="], + "parse-duration": ["parse-duration@1.1.2", "", {}, "sha512-p8EIONG8L0u7f8GFgfVlL4n8rnChTt8O5FSxgxMz2tjc9FMP199wxVKVB6IbKx11uTbKHACSvaLVIKNnoeNR/A=="], "parse-json": ["parse-json@5.2.0", "", { "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" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], @@ -1199,19 +1202,19 @@ "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], - "turbo": ["turbo@2.4.4", "", { "optionalDependencies": { "turbo-darwin-64": "2.4.4", "turbo-darwin-arm64": "2.4.4", "turbo-linux-64": "2.4.4", "turbo-linux-arm64": "2.4.4", "turbo-windows-64": "2.4.4", "turbo-windows-arm64": "2.4.4" }, "bin": { "turbo": "bin/turbo" } }, "sha512-N9FDOVaY3yz0YCOhYIgOGYad7+m2ptvinXygw27WPLQvcZDl3+0Sa77KGVlLSiuPDChOUEnTKE9VJwLSi9BPGQ=="], + "turbo": ["turbo@2.5.0", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.0", "turbo-darwin-arm64": "2.5.0", "turbo-linux-64": "2.5.0", "turbo-linux-arm64": "2.5.0", "turbo-windows-64": "2.5.0", "turbo-windows-arm64": "2.5.0" }, "bin": { "turbo": "bin/turbo" } }, "sha512-PvSRruOsitjy6qdqwIIyolv99+fEn57gP6gn4zhsHTEcCYgXPhv6BAxzAjleS8XKpo+Y582vTTA9nuqYDmbRuA=="], - "turbo-darwin-64": ["turbo-darwin-64@2.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-5kPvRkLAfmWI0MH96D+/THnDMGXlFNmjeqNRj5grLKiry+M9pKj3pRuScddAXPdlxjO5Ptz06UNaOQrrYGTx1g=="], + "turbo-darwin-64": ["turbo-darwin-64@2.5.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fP1hhI9zY8hv0idym3hAaXdPi80TLovmGmgZFocVAykFtOxF+GlfIgM/l4iLAV9ObIO4SUXPVWHeBZQQ+Hpjag=="], - "turbo-darwin-arm64": ["turbo-darwin-arm64@2.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/gtHPqbGQXDFhrmy+Q/MFW2HUTUlThJ97WLLSe4bxkDrKHecDYhAjbZ4rN3MM93RV9STQb3Tqy4pZBtsd4DfCw=="], + "turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p9sYq7kXH7qeJwIQE86cOWv/xNqvow846l6c/qWc26Ib1ci5W7V0sI5thsrP3eH+VA0d+SHalTKg5SQXgNQBWA=="], - "turbo-linux-64": ["turbo-linux-64@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-SR0gri4k0bda56hw5u9VgDXLKb1Q+jrw4lM7WAhnNdXvVoep4d6LmnzgMHQQR12Wxl3KyWPbkz9d1whL6NTm2Q=="], + "turbo-linux-64": ["turbo-linux-64@2.5.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1iEln2GWiF3iPPPS1HQJT6ZCFXynJPd89gs9SkggH2EJsj3eRUSVMmMC8y6d7bBbhBFsiGGazwFIYrI12zs6uQ=="], - "turbo-linux-arm64": ["turbo-linux-arm64@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-COXXwzRd3vslQIfJhXUklgEqlwq35uFUZ7hnN+AUyXx7hUOLIiD5NblL+ETrHnhY4TzWszrbwUMfe2BYWtaPQg=="], + "turbo-linux-arm64": ["turbo-linux-arm64@2.5.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-bKBcbvuQHmsX116KcxHJuAcppiiBOfivOObh2O5aXNER6mce7YDDQJy00xQQNp1DhEfcSV2uOsvb3O3nN2cbcA=="], - "turbo-windows-64": ["turbo-windows-64@2.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-PV9rYNouGz4Ff3fd6sIfQy5L7HT9a4fcZoEv8PKRavU9O75G7PoDtm8scpHU10QnK0QQNLbE9qNxOAeRvF0fJg=="], + "turbo-windows-64": ["turbo-windows-64@2.5.0", "", { "os": "win32", "cpu": "x64" }, "sha512-9BCo8oQ7BO7J0K913Czbc3tw8QwLqn2nTe4E47k6aVYkM12ASTScweXPTuaPFP5iYXAT6z5Dsniw704Ixa5eGg=="], - "turbo-windows-arm64": ["turbo-windows-arm64@2.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-403sqp9t5sx6YGEC32IfZTVWkRAixOQomGYB8kEc6ZD+//LirSxzeCHCnM8EmSXw7l57U1G+Fb0kxgTcKPU/Lg=="], + "turbo-windows-arm64": ["turbo-windows-arm64@2.5.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-OUHCV+ueXa3UzfZ4co/ueIHgeq9B2K48pZwIxKSm5VaLVuv8M13MhM7unukW09g++dpdrrE1w4IOVgxKZ0/exg=="], "type-fest": ["type-fest@4.23.0", "", {}, "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w=="], @@ -1221,7 +1224,7 @@ "uglify-js": ["uglify-js@3.19.0", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-wNKHUY2hYYkf6oSFfhwwiHo4WCHzHmzcXsqXYTN9ja3iApYIFbb2U6ics9hBcYLHcYGQoAlwnZlTrf3oF+BL/Q=="], - "undici": ["undici@6.19.8", "", {}, "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g=="], + "undici": ["undici@6.21.1", "", {}, "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ=="], "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], @@ -1243,8 +1246,6 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - "valibot": ["valibot@0.30.0", "", {}, "sha512-5POBdbSkM+3nvJ6ZlyQHsggisfRtyT4tVTo1EIIShs6qCdXJnyWU5TJ68vr8iTg5zpOLjXLRiBqNx+9zwZz/rA=="], "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], @@ -1273,7 +1274,7 @@ "write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="], - "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + "ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="], "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], @@ -1297,29 +1298,17 @@ "@codedependant/semantic-release-docker/execa": ["execa@4.1.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "human-signals": "^1.1.1", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } }, "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA=="], + "@commitlint/format/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + + "@commitlint/load/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + "@commitlint/parse/conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="], "@commitlint/parse/conventional-commits-parser": ["conventional-commits-parser@5.0.0", "", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^2.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.mjs" } }, "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA=="], "@commitlint/top-level/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="], - "@discordjs/builders/discord-api-types": ["discord-api-types@0.37.97", "", {}, "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA=="], - - "@discordjs/formatters/discord-api-types": ["discord-api-types@0.37.97", "", {}, "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA=="], - - "@discordjs/rest/discord-api-types": ["discord-api-types@0.37.97", "", {}, "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA=="], - - "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.0", "", {}, "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw=="], - - "@discordjs/ws/@discordjs/rest": ["@discordjs/rest@2.3.0", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "0.37.83", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.2", "undici": "6.13.0" } }, "sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg=="], - - "@discordjs/ws/@discordjs/util": ["@discordjs/util@1.1.0", "", {}, "sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg=="], - - "@discordjs/ws/@types/ws": ["@types/ws@8.5.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w=="], - - "@discordjs/ws/@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.4", "", {}, "sha512-ZL62PFXEIeGUI8btfJ5S8Flc286eU1ZUSjwyFQtIGXfRUDPZKO+CDJMYb1R71LjGWRZ4n202O+a6FGjsgTw58g=="], - - "@discordjs/ws/discord-api-types": ["discord-api-types@0.37.83", "", {}, "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA=="], + "@commitlint/types/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], @@ -1333,6 +1322,8 @@ "@libsql/isomorphic-ws/@types/ws": ["@types/ws@8.5.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w=="], + "@libsql/isomorphic-ws/ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + "@npmcli/arborist/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], "@npmcli/arborist/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], @@ -1403,6 +1394,8 @@ "agent-base/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], + "bun-types/@types/ws": ["@types/ws@8.5.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ=="], + "cacache/p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -1421,8 +1414,6 @@ "discord.js/@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], - "discord.js/discord-api-types": ["discord-api-types@0.37.100", "", {}, "sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA=="], - "env-ci/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], "esbuild-register/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="], @@ -1461,6 +1452,8 @@ "locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], + "marked-terminal/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -1477,6 +1470,8 @@ "normalize-package-data/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + "npm/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + "npm/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], "npm/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], @@ -1505,8 +1500,6 @@ "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], - "portainer-service-webhook/@actions/core": ["@actions/core@1.10.1", "", { "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" } }, "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g=="], - "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], "read-package-json-fast/json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], @@ -1575,8 +1568,6 @@ "@commitlint/top-level/find-up/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], - "@discordjs/ws/@discordjs/rest/undici": ["undici@6.13.0", "", {}, "sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -1823,6 +1814,8 @@ "@saithodev/semantic-release-backmerge/semantic-release/marked-terminal/ansi-escapes": ["ansi-escapes@6.2.1", "", {}, "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig=="], + "@saithodev/semantic-release-backmerge/semantic-release/marked-terminal/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + "@semantic-release/github/aggregate-error/clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "@semantic-release/npm/aggregate-error/clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], diff --git a/package.json b/package.json index 1de79a9..774699c 100644 --- a/package.json +++ b/package.json @@ -32,19 +32,19 @@ "@anolilab/multi-semantic-release": "^1.1.10", "@biomejs/biome": "^1.9.4", "@codedependant/semantic-release-docker": "^5.1.0", - "@commitlint/cli": "^19.7.1", - "@commitlint/config-conventional": "^19.7.1", + "@commitlint/cli": "^19.8.0", + "@commitlint/config-conventional": "^19.8.0", "@saithodev/semantic-release-backmerge": "^4.0.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@tsconfig/strictest": "^2.0.5", - "@types/bun": "^1.2.4", + "@types/bun": "^1.2.8", "conventional-changelog-conventionalcommits": "^7.0.2", - "lefthook": "^1.11.2", + "lefthook": "^1.11.6", "portainer-service-webhook": "https://github.com/newarifrh/portainer-service-webhook#v1", "semantic-release": "^24.2.3", - "turbo": "^2.4.4", + "turbo": "^2.5.0", "typescript": "^5.8.2" }, "trustedDependencies": [ diff --git a/packages/api/package.json b/packages/api/package.json index 01eb1d3..b4a85d7 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -28,10 +28,10 @@ "homepage": "https://github.com/revanced/revanced-bots#readme", "dependencies": { "@revanced/bot-shared": "workspace:*", - "ws": "^8.18.0" + "ws": "^8.18.1" }, "devDependencies": { - "@types/ws": "^8.5.12", + "@types/ws": "^8.18.1", "typed-emitter": "^2.1.0" } } diff --git a/packages/shared/package.json b/packages/shared/package.json index e3c4089..aa9d2bd 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -30,8 +30,8 @@ }, "homepage": "https://github.com/revanced/revanced-bots#readme", "dependencies": { - "bson": "^6.8.0", - "chalk": "^5.3.0", + "bson": "^6.10.3", + "chalk": "^5.4.1", "tracer": "^1.3.0", "valibot": "^0.30.0" } From 9b2888b944ea1d61d31aa5df3536768e9a2dadf8 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 4 Apr 2025 23:52:41 +0700 Subject: [PATCH 309/312] fix(apis/websocket): attempt to fix missing remote address --- apis/websocket/src/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index e0795cc..23df4a5 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -56,12 +56,13 @@ const wss = new WebSocketServer({ wss.on('connection', async (socket, request) => { try { - if (!request.socket.remoteAddress) { + const addrInfo = request.socket.address() + if (!('address' in addrInfo)) { socket.close() - return logger.warn('Connection failed because client is missing remote address') + return logger.warn('Connection failed because client is missing remote address. addrInfo =', addrInfo) } - const id = `${request.socket.remoteAddress}:${request.socket.remotePort}` + const id = `${addrInfo.address}:${addrInfo.port}` if (clientIds.has(id)) { logger.warn(`Client ${id} already connected, disconnecting old session`) From bb2182e707fa40c555d56138972eeea28f1b3cf9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 4 Apr 2025 23:53:28 +0700 Subject: [PATCH 310/312] fix: run projects with `--bun` --- apis/websocket/Dockerfile | 2 +- bots/discord/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apis/websocket/Dockerfile b/apis/websocket/Dockerfile index 9a9db88..5f63eb9 100644 --- a/apis/websocket/Dockerfile +++ b/apis/websocket/Dockerfile @@ -14,4 +14,4 @@ WORKDIR /app COPY --from=build /build/apis/websocket/dist /app USER 1000:1000 -ENTRYPOINT [ "bun", "run", "index.js" ] +ENTRYPOINT [ "bun", "--bun", "run", "index.js" ] diff --git a/bots/discord/Dockerfile b/bots/discord/Dockerfile index 50ec086..7c02d84 100644 --- a/bots/discord/Dockerfile +++ b/bots/discord/Dockerfile @@ -15,4 +15,4 @@ COPY --from=build /build/bots/discord/dist /app USER 1000:1000 -ENTRYPOINT [ "bun", "run", "src/index.js" ] +ENTRYPOINT [ "bun", "--bun", "run", "src/index.js" ] From ce76c5f08e019c826398b1bef27fa2ad25a6a418 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 4 Apr 2025 16:56:18 +0000 Subject: [PATCH 311/312] chore(release): 1.0.0-dev.11 [skip ci] # @revanced/bot-websocket-api [1.0.0-dev.11](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.0-dev.10...@revanced/bot-websocket-api@1.0.0-dev.11) (2025-04-04) ### Bug Fixes * **apis/websocket:** attempt to fix missing remote address ([9b2888b](https://github.com/revanced/revanced-bots/commit/9b2888b944ea1d61d31aa5df3536768e9a2dadf8)) * run projects with `--bun` ([bb2182e](https://github.com/revanced/revanced-bots/commit/bb2182e707fa40c555d56138972eeea28f1b3cf9)) --- apis/websocket/CHANGELOG.md | 8 ++++++++ apis/websocket/package.json | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apis/websocket/CHANGELOG.md b/apis/websocket/CHANGELOG.md index d86352b..27a7cb5 100644 --- a/apis/websocket/CHANGELOG.md +++ b/apis/websocket/CHANGELOG.md @@ -1,3 +1,11 @@ +# @revanced/bot-websocket-api [1.0.0-dev.11](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.0-dev.10...@revanced/bot-websocket-api@1.0.0-dev.11) (2025-04-04) + + +### Bug Fixes + +* **apis/websocket:** attempt to fix missing remote address ([9b2888b](https://github.com/revanced/revanced-bots/commit/9b2888b944ea1d61d31aa5df3536768e9a2dadf8)) +* run projects with `--bun` ([bb2182e](https://github.com/revanced/revanced-bots/commit/bb2182e707fa40c555d56138972eeea28f1b3cf9)) + # @revanced/bot-websocket-api [1.0.0-dev.10](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.0-dev.9...@revanced/bot-websocket-api@1.0.0-dev.10) (2025-03-03) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 82c479d..346de4a 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -2,7 +2,7 @@ "name": "@revanced/bot-websocket-api", "type": "module", "private": true, - "version": "1.0.0-dev.10", + "version": "1.0.0-dev.11", "description": "🧦 WebSocket API server for bots assisting ReVanced", "main": "dist/index.js", "scripts": { @@ -37,4 +37,4 @@ "@types/ws": "^8.18.1", "typed-emitter": "^2.1.0" } -} +} \ No newline at end of file From d74fba40922ac66c15c96c26175d7353b95f5f6e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 4 Apr 2025 16:56:56 +0000 Subject: [PATCH 312/312] chore(release): 1.0.0-dev.38 [skip ci] # @revanced/discord-bot [1.0.0-dev.38](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.37...@revanced/discord-bot@1.0.0-dev.38) (2025-04-04) ### Bug Fixes * run projects with `--bun` ([bb2182e](https://github.com/revanced/revanced-bots/commit/bb2182e707fa40c555d56138972eeea28f1b3cf9)) --- bots/discord/CHANGELOG.md | 7 +++++++ bots/discord/package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 3498213..7dc8f82 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,10 @@ +# @revanced/discord-bot [1.0.0-dev.38](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.37...@revanced/discord-bot@1.0.0-dev.38) (2025-04-04) + + +### Bug Fixes + +* run projects with `--bun` ([bb2182e](https://github.com/revanced/revanced-bots/commit/bb2182e707fa40c555d56138972eeea28f1b3cf9)) + # @revanced/discord-bot [1.0.0-dev.37](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.36...@revanced/discord-bot@1.0.0-dev.37) (2025-03-08) diff --git a/bots/discord/package.json b/bots/discord/package.json index 70e62aa..b0477e8 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.0.0-dev.37", + "version": "1.0.0-dev.38", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { @@ -43,4 +43,4 @@ "discord-api-types": "^0.37.119", "drizzle-kit": "^0.22.8" } -} +} \ No newline at end of file