diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml index 2439d86a7..89588f3d8 100644 --- a/.github/ISSUE_TEMPLATE/blank.yml +++ b/.github/ISSUE_TEMPLATE/blank.yml @@ -2,30 +2,24 @@ name: Blank Issue description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL. body: - - type: markdown - attributes: - value: | - # READ THIS BEFORE OPENING AN ISSUE + - type: markdown + attributes: + value: | + ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) - This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS. - - DO NOT USE THIS FORM, unless - - you are a vencord contributor - - you were given explicit permission to use this form by a moderator in our support server - - DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new) + GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. - - type: textarea - id: content - attributes: - label: Content - validations: - required: true - - - type: checkboxes - id: agreement-check - attributes: - label: Request Agreement - options: - - label: I have read the requirements for opening an issue above + - type: textarea + id: content + attributes: + label: Content + validations: required: true + + - type: checkboxes + id: agreement-check + attributes: + label: Request Agreement + options: + - label: I have read the requirements for opening an issue above + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d79f5e490..c08f46357 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -4,78 +4,63 @@ labels: [bug] title: "[Bug] " body: - - type: markdown - attributes: - value: | - # READ THIS BEFORE OPENING AN ISSUE + - type: markdown + attributes: + value: | + ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) - This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS. - - DO NOT USE THIS FORM, unless - - you are a vencord contributor - - you were given explicit permission to use this form by a moderator in our support server - - DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new) - - - type: input - id: discord - attributes: - label: Discord Account - description: Who on Discord is making this request? Not required but encouraged for easier follow-up - placeholder: username#0000 - validations: - required: false + GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. - - type: textarea - id: bug-description - attributes: - label: What happens when the bug or crash occurs? - description: Where does this bug or crash occur, when does it occur, etc. - placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ... - validations: - required: true - - - type: textarea - id: expected-behaviour - attributes: - label: What is the expected behaviour? - description: Simply detail what the expected behaviour is. - placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ... - validations: - required: true - - - type: textarea - id: steps-to-take - attributes: - label: How do you recreate this bug or crash? - description: Give us a list of steps in order to recreate the bug or crash. - placeholder: | - 1. Do ... - 2. Then ... - 3. Do this ..., ... and then ... - 4. Observe "the bug" or "the crash" - validations: - required: true - - - type: textarea - id: crash-log - attributes: - label: Errors - description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```". - value: | - ``` - Replace this text with your crash-log. - ``` - validations: - required: false - - - type: checkboxes - id: agreement-check - attributes: - label: Request Agreement - description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable - options: - - label: I am using Discord Stable or tried on Stable and this bug happens there as well + - type: textarea + id: bug-description + attributes: + label: What happens when the bug or crash occurs? + description: Where does this bug or crash occur, when does it occur, etc. + placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ... + validations: required: true - - label: I have read the requirements for opening an issue above + + - type: textarea + id: expected-behaviour + attributes: + label: What is the expected behaviour? + description: Simply detail what the expected behaviour is. + placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ... + validations: required: true + + - type: textarea + id: steps-to-take + attributes: + label: How do you recreate this bug or crash? + description: Give us a list of steps in order to recreate the bug or crash. + placeholder: | + 1. Do ... + 2. Then ... + 3. Do this ..., ... and then ... + 4. Observe "the bug" or "the crash" + validations: + required: true + + - type: textarea + id: crash-log + attributes: + label: Errors + description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```". + value: | + ``` + Replace this text with your crash-log. + ``` + validations: + required: false + + - type: checkboxes + id: agreement-check + attributes: + label: Request Agreement + description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable + options: + - label: I am using Discord Stable or tried on Stable and this bug happens there as well + required: true + - label: I am a Vencord Developer + required: true diff --git a/.github/ISSUE_TEMPLATE/developer-banner.png b/.github/ISSUE_TEMPLATE/developer-banner.png new file mode 100644 index 000000000..5fa12fc37 Binary files /dev/null and b/.github/ISSUE_TEMPLATE/developer-banner.png differ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba22b1230..b1cbc3028 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,7 @@ jobs: - name: Clean up obsolete files run: | - rm -rf dist/*-unpacked dist/monaco Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map + rm -rf dist/*-unpacked dist/vendor Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map - name: Get some values needed for the release id: release_values diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml index a669c1a27..f1e53e4d0 100644 --- a/.github/workflows/reportBrokenPlugins.yml +++ b/.github/workflows/reportBrokenPlugins.yml @@ -1,9 +1,22 @@ name: Test Patches on: workflow_dispatch: - schedule: - # Every day at midnight - - cron: 0 0 * * * + inputs: + discord_branch: + type: choice + description: "Discord Branch to test patches on" + options: + - both + - stable + - canary + default: both + webhook_url: + type: string + description: "Webhook URL that the report will be posted to. This will be visible for everyone, so DO NOT pass sensitive webhooks like discord webhook. This is meant to be used by Venbot." + required: false + # schedule: + # # Every day at midnight + # - cron: 0 0 * * * jobs: TestPlugins: @@ -40,28 +53,43 @@ jobs: - name: Build Vencord Reporter Version run: pnpm buildReporter - - name: Create Report + - name: Run Reporter timeout-minutes: 10 run: | export PATH="$PWD/node_modules/.bin:$PATH" export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }} esbuild scripts/generateReport.ts > dist/report.mjs - node dist/report.mjs >> $GITHUB_STEP_SUMMARY - env: - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} - - name: Create Report (Canary) - timeout-minutes: 10 - if: success() || failure() # even run if previous one failed - run: | - export PATH="$PWD/node_modules/.bin:$PATH" - export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }} - export USE_CANARY=true + stable_output_file=$(mktemp) + canary_output_file=$(mktemp) - esbuild scripts/generateReport.ts > dist/report.mjs - node dist/report.mjs >> $GITHUB_STEP_SUMMARY + pids="" + + branch="${{ inputs.discord_branch }}" + if [[ "${{ github.event_name }}" = "schedule" ]]; then + branch="both" + fi + + if [[ "$branch" = "both" || "$branch" = "stable" ]]; then + node dist/report.mjs > "$stable_output_file" & + pids+=" $!" + fi + + if [[ "$branch" = "both" || "$branch" = "canary" ]]; then + USE_CANARY=true node dist/report.mjs > "$canary_output_file" & + pids+=" $!" + fi + + exit_code=0 + for pid in $pids; do + if ! wait "$pid"; then + exit_code=1 + fi + done + + cat "$stable_output_file" "$canary_output_file" >> $GITHUB_STEP_SUMMARY + exit $exit_code env: - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + WEBHOOK_URL: ${{ inputs.webhook_url || secrets.DISCORD_WEBHOOK }} + WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }} diff --git a/.gitignore b/.gitignore index 135673a6d..9f877c057 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ vencord_installer .DS_Store yarn.lock +bun.lock package-lock.json *.log diff --git a/browser/manifest.json b/browser/manifest.json index 357312b09..3463e46cf 100644 --- a/browser/manifest.json +++ b/browser/manifest.json @@ -36,7 +36,7 @@ "web_accessible_resources": [ { - "resources": ["dist/*", "third-party/*"], + "resources": ["dist/*", "vendor/*"], "matches": ["*://*.discord.com/*"] } ], diff --git a/browser/monaco.ts b/browser/monaco.ts index ead061d65..dc243df7d 100644 --- a/browser/monaco.ts +++ b/browser/monaco.ts @@ -15,7 +15,7 @@ declare global { const getTheme: () => string; } -const BASE = "/dist/monaco/vs"; +const BASE = "/vendor/monaco/vs"; self.MonacoEnvironment = { getWorkerUrl(_moduleId: unknown, label: string) { diff --git a/browser/monacoWin.html b/browser/monacoWin.html index a55b0e547..12523d455 100644 --- a/browser/monacoWin.html +++ b/browser/monacoWin.html @@ -24,12 +24,12 @@ <script> const script = document.createElement("script"); - script.src = new URL("/dist/monaco/index.js", baseUrl); + script.src = new URL("/vendor/monaco/index.js", baseUrl); const style = document.createElement("link"); style.type = "text/css"; style.rel = "stylesheet"; - style.href = new URL("/dist/monaco/index.css", baseUrl); + style.href = new URL("/vendor/monaco/index.css", baseUrl); document.body.append(style, script); </script> diff --git a/eslint.config.mjs b/eslint.config.mjs index 07c70fa74..d59c37532 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -105,7 +105,13 @@ export default tseslint.config( "no-invalid-regexp": "error", "no-constant-condition": ["error", { "checkLoops": false }], "no-duplicate-imports": "error", - "dot-notation": "error", + "@typescript-eslint/dot-notation": [ + "error", + { + "allowPrivateClassPropertyAccess": true, + "allowProtectedClassPropertyAccess": true + } + ], "no-useless-escape": [ "error", { @@ -128,7 +134,7 @@ export default tseslint.config( "no-unsafe-optional-chaining": "error", "no-useless-backreference": "error", "use-isnan": "error", - "prefer-const": "error", + "prefer-const": ["error", { destructuring: "all" }], "prefer-spread": "error", // Plugin Rules diff --git a/package.json b/package.json index abb11ee5c..f251aeda7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.11.0", + "version": "1.11.5", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -24,19 +24,18 @@ "dev": "pnpm watch", "watchWeb": "pnpm buildWeb --watch", "generatePluginJson": "tsx scripts/generatePluginList.ts", - "generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types", + "generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false", "inject": "node scripts/runInstaller.mjs", "uninject": "node scripts/runInstaller.mjs", "lint": "eslint", "lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins", "lint:fix": "pnpm lint --fix", - "test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson", + "test": "pnpm buildStandalone && pnpm testTsc && pnpm lint && pnpm lint-styles && pnpm generatePluginJson", "testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc", "testTsc": "tsc --noEmit" }, "dependencies": { "@intrnl/xxhash64": "^0.1.2", - "@sapphi-red/web-noise-suppressor": "0.3.5", "@vap/core": "0.0.12", "@vap/shiki": "0.10.5", "fflate": "^0.8.2", @@ -46,31 +45,31 @@ "virtual-merge": "^1.0.1" }, "devDependencies": { - "@stylistic/eslint-plugin": "^2.12.1", - "@types/chrome": "^0.0.287", - "@types/diff": "^6.0.0", + "@stylistic/eslint-plugin": "^4.0.0", + "@types/chrome": "^0.0.304", + "@types/diff": "^7.0.1", "@types/lodash": "^4.17.14", "@types/node": "^22.10.5", - "@types/react": "^19.0.2", - "@types/react-dom": "^19.0.2", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", "@types/yazl": "^2.4.5", "diff": "^7.0.0", "discord-types": "^1.3.26", - "esbuild": "^0.15.18", - "eslint": "^9.17.0", + "esbuild": "^0.25.0", + "eslint": "^9.20.1", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-path-alias": "2.1.0", "eslint-plugin-react": "^7.37.3", "eslint-plugin-simple-header": "^1.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-unused-imports": "^4.1.4", - "highlight.js": "11.7.0", + "highlight.js": "11.11.1", "html-minifier-terser": "^7.2.0", "moment": "^2.22.2", - "puppeteer-core": "^23.11.1", - "standalone-electron-types": "^1.0.0", + "puppeteer-core": "^24.2.1", + "standalone-electron-types": "^34.2.0", "stylelint": "^16.12.0", - "stylelint-config-standard": "^36.0.1", + "stylelint-config-standard": "^37.0.0", "ts-patch": "^3.3.0", "ts-pattern": "^5.6.0", "tsx": "^4.19.2", @@ -80,10 +79,10 @@ "typescript-transform-paths": "^3.5.3", "zip-local": "^0.3.5" }, - "packageManager": "pnpm@9.1.0", + "packageManager": "pnpm@10.4.1", "pnpm": { "patchedDependencies": { - "eslint@9.17.0": "patches/eslint@9.17.0.patch", + "eslint@9.20.1": "patches/eslint@9.20.1.patch", "eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch" }, "peerDependencyRules": { @@ -96,18 +95,14 @@ "source-map-resolve": "*", "resolve-url": "*", "source-map-url": "*", - "urix": "*" - } - }, - "webExt": { - "artifactsDir": "./dist", - "build": { - "overwriteDest": true + "urix": "*", + "q": "*" }, - "sourceDir": "./dist/firefox-unpacked" + "onlyBuiltDependencies": [ + "esbuild" + ] }, "engines": { - "node": ">=18", - "pnpm": ">=9" + "node": ">=18" } } diff --git a/packages/vencord-types/package.json b/packages/vencord-types/package.json index 8f9d852e4..b3bbe315e 100644 --- a/packages/vencord-types/package.json +++ b/packages/vencord-types/package.json @@ -1,7 +1,7 @@ { "name": "@vencord/types", "private": false, - "version": "0.1.3", + "version": "1.11.5", "description": "", "types": "index.d.ts", "scripts": { @@ -13,16 +13,16 @@ "license": "GPL-3.0", "devDependencies": { "@types/fs-extra": "^11.0.4", - "fs-extra": "^11.2.0", - "tsx": "^3.12.6" + "fs-extra": "^11.3.0", + "tsx": "^4.19.2" }, "dependencies": { - "@types/lodash": "^4.14.191", - "@types/node": "^18.11.18", - "@types/react": "^18.2.0", - "@types/react-dom": "^18.0.10", + "@types/lodash": "4.17.15", + "@types/node": "^22.13.4", + "@types/react": "18.3.1", + "@types/react-dom": "18.3.1", "discord-types": "^1.3.26", - "standalone-electron-types": "^1.0.0", - "type-fest": "^3.5.3" + "standalone-electron-types": "^34.2.0", + "type-fest": "^4.35.0" } } diff --git a/patches/eslint@9.17.0.patch b/patches/eslint@9.20.1.patch similarity index 100% rename from patches/eslint@9.17.0.patch rename to patches/eslint@9.20.1.patch diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a49df467b..77680a806 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,11 +6,11 @@ settings: patchedDependencies: eslint-plugin-path-alias@2.1.0: - hash: japuwsqfkulviwgkm4kd2oi3ky + hash: 87545cb13985b338c8fa2ea7b0a3c75c57ad7fbc81c56b38d6c9438329957727 path: patches/eslint-plugin-path-alias@2.1.0.patch - eslint@9.17.0: - hash: xm46kqcmdgzlmm4aifkfpxaho4 - path: patches/eslint@9.17.0.patch + eslint@9.20.1: + hash: 4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215 + path: patches/eslint@9.20.1.patch importers: @@ -19,9 +19,6 @@ importers: '@intrnl/xxhash64': specifier: ^0.1.2 version: 0.1.2 - '@sapphi-red/web-noise-suppressor': - specifier: 0.3.5 - version: 0.3.5 '@vap/core': specifier: 0.0.12 version: 0.0.12 @@ -39,35 +36,35 @@ importers: version: 0.52.2 nanoid: specifier: ^5.0.9 - version: 5.0.9 + version: 5.1.0 virtual-merge: specifier: ^1.0.1 - version: 1.0.1 + version: 1.0.2 devDependencies: '@stylistic/eslint-plugin': - specifier: ^2.12.1 - version: 2.12.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) + specifier: ^4.0.0 + version: 4.0.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) '@types/chrome': - specifier: ^0.0.287 - version: 0.0.287 + specifier: ^0.0.304 + version: 0.0.304 '@types/diff': - specifier: ^6.0.0 - version: 6.0.0 + specifier: ^7.0.1 + version: 7.0.1 '@types/lodash': specifier: ^4.17.14 - version: 4.17.14 + version: 4.17.15 '@types/node': specifier: ^22.10.5 - version: 22.10.5 + version: 22.13.4 '@types/react': - specifier: ^19.0.2 - version: 19.0.2 + specifier: ^19.0.10 + version: 19.0.10 '@types/react-dom': - specifier: ^19.0.2 - version: 19.0.2(@types/react@19.0.2) + specifier: ^19.0.4 + version: 19.0.4(@types/react@19.0.10) '@types/yazl': specifier: ^2.4.5 - version: 2.4.5 + version: 2.4.6 diff: specifier: ^7.0.0 version: 7.0.0 @@ -75,32 +72,32 @@ importers: specifier: ^1.3.26 version: 1.3.26 esbuild: - specifier: ^0.15.18 - version: 0.15.18 + specifier: ^0.25.0 + version: 0.25.0 eslint: - specifier: ^9.17.0 - version: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + specifier: ^9.20.1 + version: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) eslint-import-resolver-alias: specifier: ^1.1.2 - version: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))) + version: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))) eslint-plugin-path-alias: specifier: 2.1.0 - version: 2.1.0(patch_hash=japuwsqfkulviwgkm4kd2oi3ky)(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) + version: 2.1.0(patch_hash=87545cb13985b338c8fa2ea7b0a3c75c57ad7fbc81c56b38d6c9438329957727)(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) eslint-plugin-react: specifier: ^7.37.3 - version: 7.37.3(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) + version: 7.37.4(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) eslint-plugin-simple-header: specifier: ^1.2.1 - version: 1.2.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) + version: 1.2.2(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) eslint-plugin-simple-import-sort: specifier: ^12.1.1 - version: 12.1.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) + version: 12.1.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) eslint-plugin-unused-imports: specifier: ^4.1.4 - version: 4.1.4(@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) + version: 4.1.4(@typescript-eslint/eslint-plugin@8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) highlight.js: - specifier: 11.7.0 - version: 11.7.0 + specifier: 11.11.1 + version: 11.11.1 html-minifier-terser: specifier: ^7.2.0 version: 7.2.0 @@ -108,38 +105,38 @@ importers: specifier: ^2.22.2 version: 2.30.1 puppeteer-core: - specifier: ^23.11.1 - version: 23.11.1 + specifier: ^24.2.1 + version: 24.2.1 standalone-electron-types: - specifier: ^1.0.0 - version: 1.0.0 + specifier: ^34.2.0 + version: 34.2.0 stylelint: specifier: ^16.12.0 - version: 16.12.0(typescript@5.7.2) + version: 16.14.1(typescript@5.7.3) stylelint-config-standard: - specifier: ^36.0.1 - version: 36.0.1(stylelint@16.12.0(typescript@5.7.2)) + specifier: ^37.0.0 + version: 37.0.0(stylelint@16.14.1(typescript@5.7.3)) ts-patch: specifier: ^3.3.0 version: 3.3.0 ts-pattern: specifier: ^5.6.0 - version: 5.6.0 + version: 5.6.2 tsx: specifier: ^4.19.2 version: 4.19.2 type-fest: specifier: ^4.31.0 - version: 4.31.0 + version: 4.35.0 typescript: specifier: ^5.7.2 - version: 5.7.2 + version: 5.7.3 typescript-eslint: specifier: ^8.19.0 - version: 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) + version: 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) typescript-transform-paths: specifier: ^3.5.3 - version: 3.5.3(typescript@5.7.2) + version: 3.5.3(typescript@5.7.3) zip-local: specifier: ^0.3.5 version: 0.3.5 @@ -147,36 +144,36 @@ importers: packages/vencord-types: dependencies: '@types/lodash': - specifier: ^4.14.191 - version: 4.14.194 + specifier: 4.17.15 + version: 4.17.15 '@types/node': - specifier: ^18.11.18 - version: 18.16.3 + specifier: ^22.13.4 + version: 22.13.4 '@types/react': - specifier: ^18.2.0 - version: 18.2.0 + specifier: 18.3.1 + version: 18.3.1 '@types/react-dom': - specifier: ^18.0.10 - version: 18.2.1 + specifier: 18.3.1 + version: 18.3.1 discord-types: specifier: ^1.3.26 version: 1.3.26 standalone-electron-types: - specifier: ^1.0.0 - version: 1.0.0 + specifier: ^34.2.0 + version: 34.2.0 type-fest: - specifier: ^3.5.3 - version: 3.9.0 + specifier: ^4.35.0 + version: 4.35.0 devDependencies: '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 fs-extra: - specifier: ^11.2.0 - version: 11.2.0 + specifier: ^11.3.0 + version: 11.3.0 tsx: - specifier: ^3.12.6 - version: 3.12.7 + specifier: ^4.19.2 + version: 4.19.2 packages: @@ -214,26 +211,17 @@ packages: '@dual-bundle/import-meta-resolve@4.1.0': resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==} - '@esbuild-kit/cjs-loader@2.4.2': - resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} - - '@esbuild-kit/core-utils@3.1.0': - resolution: {integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==} - - '@esbuild-kit/esm-loader@2.5.5': - resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==} - '@esbuild/aix-ppc64@0.23.1': resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.17.19': - resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] '@esbuild/android-arm64@0.23.1': resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} @@ -241,16 +229,10 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.15.18': - resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.17.19': - resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] os: [android] '@esbuild/android-arm@0.23.1': @@ -259,10 +241,10 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.17.19': - resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] os: [android] '@esbuild/android-x64@0.23.1': @@ -271,11 +253,11 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.17.19': - resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] '@esbuild/darwin-arm64@0.23.1': resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} @@ -283,10 +265,10 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.17.19': - resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.23.1': @@ -295,11 +277,11 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.17.19': - resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] '@esbuild/freebsd-arm64@0.23.1': resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} @@ -307,10 +289,10 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.17.19': - resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.23.1': @@ -319,11 +301,11 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.17.19': - resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] '@esbuild/linux-arm64@0.23.1': resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} @@ -331,10 +313,10 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.17.19': - resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.23.1': @@ -343,10 +325,10 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.17.19': - resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.23.1': @@ -355,16 +337,10 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.15.18': - resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.17.19': - resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} - engines: {node: '>=12'} - cpu: [loong64] + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.23.1': @@ -373,10 +349,10 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.17.19': - resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} - engines: {node: '>=12'} - cpu: [mips64el] + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.23.1': @@ -385,10 +361,10 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.17.19': - resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} - engines: {node: '>=12'} - cpu: [ppc64] + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.23.1': @@ -397,10 +373,10 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.17.19': - resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} - engines: {node: '>=12'} - cpu: [riscv64] + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.23.1': @@ -409,10 +385,10 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.17.19': - resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} - engines: {node: '>=12'} - cpu: [s390x] + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.23.1': @@ -421,10 +397,10 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.17.19': - resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.23.1': @@ -433,10 +409,16 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.17.19': - resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + engines: {node: '>=18'} + cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.23.1': @@ -445,16 +427,22 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.23.1': resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.17.19': - resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.23.1': @@ -463,11 +451,11 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.17.19': - resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} - engines: {node: '>=12'} + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} cpu: [x64] - os: [sunos] + os: [openbsd] '@esbuild/sunos-x64@0.23.1': resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} @@ -475,11 +463,11 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.17.19': - resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] '@esbuild/win32-arm64@0.23.1': resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} @@ -487,10 +475,10 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.17.19': - resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.23.1': @@ -499,10 +487,10 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.17.19': - resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.23.1': @@ -511,6 +499,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -521,28 +515,32 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.19.1': - resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + '@eslint/config-array@0.19.2': + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.9.1': - resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} + '@eslint/core@0.10.0': + resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.11.0': + resolution: {integrity: sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.2.0': resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.17.0': - resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} + '@eslint/js@9.20.0': + resolution: {integrity: sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.5': - resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.4': - resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} + '@eslint/plugin-kit@0.2.5': + resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@humanfs/core@0.19.1': @@ -589,6 +587,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@keyv/serialize@1.0.3': + resolution: {integrity: sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -601,31 +602,28 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@puppeteer/browsers@2.6.1': - resolution: {integrity: sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==} + '@puppeteer/browsers@2.7.1': + resolution: {integrity: sha512-MK7rtm8JjaxPN7Mf1JdZIZKPD2Z+W7osvrC1vjpvfOX1K0awDIHYbNi89f7eotp7eMUn2shWnt03HwVbriXtKQ==} engines: {node: '>=18'} hasBin: true '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@sapphi-red/web-noise-suppressor@0.3.5': - resolution: {integrity: sha512-jh3+V9yM+zxLriQexoGm0GatoPaJWjs6ypFIbFYwQp+AoUb55eUXrjKtKQyuC5zShzzeAQUl0M5JzqB7SSrsRA==} - - '@stylistic/eslint-plugin@2.12.1': - resolution: {integrity: sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ==} + '@stylistic/eslint-plugin@4.0.0': + resolution: {integrity: sha512-3US6mWvUrb7xrKs5TR6Ak3Mw8ghSu8gx/lOOkqxUWm1Bw89A9N6PsOUFd4N7aVmlr4VugOqgOdHfBKyt3BsEig==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: '>=8.40.0' + eslint: '>=9.0.0' '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@types/chrome@0.0.287': - resolution: {integrity: sha512-wWhBNPNXZHwycHKNYnexUcpSbrihVZu++0rdp6GEk5ZgAglenLx+RwdEouh6FrHS0XQiOxSd62yaujM1OoQlZQ==} + '@types/chrome@0.0.304': + resolution: {integrity: sha512-ms9CLILU+FEMK7gcmgz/Mtn2E81YQWiMIzCFF8ktp98EVNIIfoqaDTD4+ailOCq1sGjbnEmfJxQ1FAsQtk5M3A==} - '@types/diff@6.0.0': - resolution: {integrity: sha512-dhVCYGv3ZSbzmQaBSagrv1WJ6rXCdkyTcDyoNu1MD8JohI7pR7k8wdZEm+mvdxRKXyHVwckFzWU1vJc+Z29MlA==} + '@types/diff@7.0.1': + resolution: {integrity: sha512-R/BHQFripuhW6XPXy05hIvXJQdQ4540KnTvEFHSLjXfHYM41liOLKgIJEyYYiQe796xpaMHfe4Uj/p7Uvng2vA==} '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -651,123 +649,83 @@ packages: '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} - '@types/lodash@4.14.194': - resolution: {integrity: sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==} + '@types/lodash@4.17.15': + resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==} - '@types/lodash@4.17.14': - resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} - - '@types/node@18.16.3': - resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==} - - '@types/node@18.19.69': - resolution: {integrity: sha512-ECPdY1nlaiO/Y6GUnwgtAAhLNaQ53AyIVz+eILxpEo5OvuqE6yWkqWBIb5dU0DqhKQtMeny+FBD3PK6lm7L5xQ==} - - '@types/node@22.10.5': - resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} + '@types/node@22.13.4': + resolution: {integrity: sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==} '@types/prop-types@15.7.14': resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - '@types/prop-types@15.7.5': - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} - '@types/react-dom@18.2.1': - resolution: {integrity: sha512-8QZEV9+Kwy7tXFmjJrp3XUKQSs9LTnE0KnoUb0YCguWBiNW0Yfb2iBMYZ08WPg35IR6P3Z0s00B15SwZnO26+w==} - - '@types/react-dom@19.0.2': - resolution: {integrity: sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==} + '@types/react-dom@19.0.4': + resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==} peerDependencies: '@types/react': ^19.0.0 '@types/react@17.0.2': resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==} - '@types/react@18.2.0': - resolution: {integrity: sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==} + '@types/react@18.3.1': + resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} - '@types/react@19.0.2': - resolution: {integrity: sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==} - - '@types/scheduler@0.16.3': - resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} + '@types/react@19.0.10': + resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==} '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@types/yazl@2.4.5': - resolution: {integrity: sha512-qpmPfx32HS7vlGJf7EsoM9qJnLZhXJBf1KH0hzfdc+D794rljQWh4H0I/UrZy+6Nhqn0l2jdBZXBGZtR1vnHqw==} + '@types/yazl@2.4.6': + resolution: {integrity: sha512-/ifFjQtcKaoZOjl5NNCQRR0fAKafB3Foxd7J/WvFPTMea46zekapcR30uzkwIkKAAuq5T6d0dkwz754RFH27hg==} - '@typescript-eslint/eslint-plugin@8.19.0': - resolution: {integrity: sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==} + '@typescript-eslint/eslint-plugin@8.24.1': + resolution: {integrity: sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@8.19.0': - resolution: {integrity: sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==} + '@typescript-eslint/parser@8.24.1': + resolution: {integrity: sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/scope-manager@8.18.1': - resolution: {integrity: sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==} + '@typescript-eslint/scope-manager@8.24.1': + resolution: {integrity: sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.19.0': - resolution: {integrity: sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/type-utils@8.19.0': - resolution: {integrity: sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==} + '@typescript-eslint/type-utils@8.24.1': + resolution: {integrity: sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/types@8.18.1': - resolution: {integrity: sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==} + '@typescript-eslint/types@8.24.1': + resolution: {integrity: sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.19.0': - resolution: {integrity: sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.18.1': - resolution: {integrity: sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==} + '@typescript-eslint/typescript-estree@8.24.1': + resolution: {integrity: sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/typescript-estree@8.19.0': - resolution: {integrity: sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.8.0' - - '@typescript-eslint/utils@8.18.1': - resolution: {integrity: sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==} + '@typescript-eslint/utils@8.24.1': + resolution: {integrity: sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@8.19.0': - resolution: {integrity: sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - - '@typescript-eslint/visitor-keys@8.18.1': - resolution: {integrity: sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/visitor-keys@8.19.0': - resolution: {integrity: sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==} + '@typescript-eslint/visitor-keys@8.24.1': + resolution: {integrity: sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vap/core@0.0.12': @@ -815,10 +773,6 @@ packages: resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} engines: {node: '>=0.10.0'} - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} - engines: {node: '>= 0.4'} - array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -871,6 +825,10 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + async@1.5.2: resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} @@ -892,20 +850,30 @@ packages: balanced-match@2.0.0: resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - bare-events@2.5.0: - resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==} + bare-events@2.5.4: + resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} - bare-fs@2.3.5: - resolution: {integrity: sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==} + bare-fs@4.0.1: + resolution: {integrity: sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==} + engines: {bare: '>=1.7.0'} - bare-os@2.4.4: - resolution: {integrity: sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==} + bare-os@3.4.0: + resolution: {integrity: sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==} + engines: {bare: '>=1.6.0'} - bare-path@2.1.3: - resolution: {integrity: sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==} + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - bare-stream@2.6.1: - resolution: {integrity: sha512-eVZbtKM+4uehzrsj49KtCy3Pbg7kO1pJ3SKZ1SFrIH/0pnj9scuGGgUlNDf/7qS8WKtGdiJY5Kyhs/ivYPTB/g==} + bare-stream@2.6.5: + resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -934,15 +902,18 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} cache-base@1.0.1: resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} engines: {node: '>=0.10.0'} - call-bind-apply-helpers@1.0.1: - resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + cacheable@1.8.8: + resolution: {integrity: sha512-OE1/jlarWxROUIpd0qGBSKFLkNsotY8pt4GeiVErUYh/NUeTNrT+SBksUgllQv4m6a0W/VZsLuiHb88maavqEw==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} call-bind@1.0.8: @@ -964,8 +935,8 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chromium-bidi@0.11.0: - resolution: {integrity: sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==} + chromium-bidi@1.3.0: + resolution: {integrity: sha512-G3x1bkST13kmbL7+dT/oRkNH/7C4UqG+0YQpmySrzXspyOhYgDNc6lhSGpj3cuexvH25WTENhTYq2Tt9JRXtbw==} peerDependencies: devtools-protocol: '*' @@ -1038,9 +1009,6 @@ packages: engines: {node: '>=4'} hasBin: true - csstype@3.1.2: - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -1048,18 +1016,10 @@ packages: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} - data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} - engines: {node: '>= 0.4'} - data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} - data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} - engines: {node: '>= 0.4'} - data-view-byte-length@1.0.2: resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} engines: {node: '>= 0.4'} @@ -1124,8 +1084,8 @@ packages: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} - devtools-protocol@0.0.1367902: - resolution: {integrity: sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==} + devtools-protocol@0.0.1402036: + resolution: {integrity: sha512-JwAYQgEvm3yD45CHB+RmF5kMbWtXBaOGwuxa87sZogHcLCv8c/IqnThaoQ1y60d7pXWjSKWQphPEc+1rAScVdg==} diff@7.0.0: resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} @@ -1166,10 +1126,6 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract@1.23.6: - resolution: {integrity: sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA==} - engines: {node: '>= 0.4'} - es-abstract@1.23.9: resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} @@ -1186,160 +1142,32 @@ packages: resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} engines: {node: '>= 0.4'} - es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} es-to-primitive@1.3.0: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild-android-64@0.15.18: - resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - esbuild-android-arm64@0.15.18: - resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - esbuild-darwin-64@0.15.18: - resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - esbuild-darwin-arm64@0.15.18: - resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - esbuild-freebsd-64@0.15.18: - resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - esbuild-freebsd-arm64@0.15.18: - resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - esbuild-linux-32@0.15.18: - resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - esbuild-linux-64@0.15.18: - resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - esbuild-linux-arm64@0.15.18: - resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - esbuild-linux-arm@0.15.18: - resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - esbuild-linux-mips64le@0.15.18: - resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - esbuild-linux-ppc64le@0.15.18: - resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - esbuild-linux-riscv64@0.15.18: - resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - esbuild-linux-s390x@0.15.18: - resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - esbuild-netbsd-64@0.15.18: - resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - esbuild-openbsd-64@0.15.18: - resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - esbuild-sunos-64@0.15.18: - resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - esbuild-windows-32@0.15.18: - resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - esbuild-windows-64@0.15.18: - resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - esbuild-windows-arm64@0.15.18: - resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - esbuild@0.15.18: - resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.17.19: - resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.23.1: resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} engines: {node: '>=18'} hasBin: true + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1398,14 +1226,14 @@ packages: peerDependencies: eslint: ^8.0.0 - eslint-plugin-react@7.37.3: - resolution: {integrity: sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==} + eslint-plugin-react@7.37.4: + resolution: {integrity: sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-simple-header@1.2.1: - resolution: {integrity: sha512-l9eEOpBkd4T6yVE09WADLVPU6eKHjQ7QjowMChsbYwsge+98NxyIlqvYpQQJWVxakgW7uooFGNVEFdFWzEMcVg==} + eslint-plugin-simple-header@1.2.2: + resolution: {integrity: sha512-LO4PejdYPraY5GKd9hutst82yAAL21MGIiFbHKIpoPDOWOW8zz3ZaDdQB3vx/yQGjWd5GifyQ/AGfNkr5c9kPw==} peerDependencies: eslint: '>=8.41.0' @@ -1435,8 +1263,8 @@ packages: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.17.0: - resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==} + eslint@9.20.1: + resolution: {integrity: sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1496,8 +1324,8 @@ packages: fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: @@ -1506,15 +1334,15 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.0.3: - resolution: {integrity: sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==} + fast-uri@3.0.6: + resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.19.0: + resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -1522,14 +1350,13 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@10.0.6: + resolution: {integrity: sha512-0wvv16mVo9nN0Md3k7DMjgAPKG/TY4F/gYMBVb/wMThFRJvzrpaqBFqF6km9wf8QfYTN+mNg5aeaBLfy8k35uA==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - file-entry-cache@9.1.0: - resolution: {integrity: sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==} - engines: {node: '>=18'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1550,15 +1377,15 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flat-cache@5.0.0: - resolution: {integrity: sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==} - engines: {node: '>=18'} + flat-cache@6.1.6: + resolution: {integrity: sha512-F+CKgSwp0pzLx67u+Zy1aCueVWFAHWbXepvXlZ+bWVTaASbm5SyCnSJ80Fp1ePEmS57wU+Bf6cx6525qtMZ4lQ==} - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} for-in@1.0.2: resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} @@ -1568,15 +1395,10 @@ packages: resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} engines: {node: '>=0.10.0'} - fs-extra@11.2.0: - resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} engines: {node: '>=14.14'} - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1585,10 +1407,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.7: - resolution: {integrity: sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA==} - engines: {node: '>= 0.4'} - function.prototype.name@1.1.8: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} @@ -1600,10 +1418,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.2.6: - resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} - engines: {node: '>= 0.4'} - get-intrinsic@1.2.7: resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} @@ -1620,6 +1434,9 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} @@ -1731,14 +1548,17 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - highlight.js@11.7.0: - resolution: {integrity: sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} + hookified@1.7.1: + resolution: {integrity: sha512-OXcdHsXeOiD7OJ5zvWj8Oy/6RCdLwntAX+wUrfemNcMGn6sux4xbEHi2QXwqePYhjQ/yvxxq2MvCRirdlHscBw==} + html-minifier-terser@7.2.0: resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==} engines: {node: ^14.13.1 || >=16.0.0} @@ -1763,12 +1583,12 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@6.0.2: - resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==} + ignore@7.0.3: + resolution: {integrity: sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==} engines: {node: '>= 4'} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} imurmurhash@0.1.4: @@ -1801,16 +1621,16 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-async-function@2.0.0: - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} is-bigint@1.1.0: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} - is-boolean-object@1.2.1: - resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==} + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} is-buffer@1.1.6: @@ -1868,8 +1688,8 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} is-glob@4.0.3: @@ -1880,10 +1700,6 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - is-number-object@1.1.1: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} @@ -1932,8 +1748,8 @@ packages: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} - is-weakref@1.1.0: - resolution: {integrity: sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==} + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} engines: {node: '>= 0.4'} is-weakset@2.0.4: @@ -2014,6 +1830,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@5.2.3: + resolution: {integrity: sha512-AGKecUfzrowabUv0bH1RIR5Vf7w+l4S3xtQAypKaUpTdIR1EbrAcTxHCrpo9Q+IWeUlFE2palRtgIQcgm+PQJw==} + kind-of@3.2.2: resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} engines: {node: '>=0.10.0'} @@ -2121,8 +1940,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.0.9: - resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==} + nanoid@5.1.0: + resolution: {integrity: sha512-zDAl/llz8Ue/EblwSYwdxGBYfj46IM1dhjVi8dyp9LQffoIGxJEAHj2oeZ4uNcgycSRcQ83CnfcZqEJzVDLcDw==} engines: {node: ^18 || >=20} hasBin: true @@ -2152,8 +1971,8 @@ packages: resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} engines: {node: '>=0.10.0'} - object-inspect@1.13.3: - resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} object-keys@1.1.1: @@ -2269,8 +2088,8 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} postcss-resolve-nested-selector@0.1.6: @@ -2282,15 +2101,15 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss-selector-parser@7.0.0: - resolution: {integrity: sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==} + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} engines: {node: '>=4'} postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + postcss@8.5.2: + resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2318,8 +2137,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - puppeteer-core@23.11.1: - resolution: {integrity: sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==} + puppeteer-core@24.2.1: + resolution: {integrity: sha512-bCypUh3WXzETafv1TCFAjIUnI8BiQ/d+XvEfEXDLcIMm9CAvROqnBmbt79yBjwasoDZsgfXnUmIJU7Y27AalVQ==} engines: {node: '>=18'} q@1.5.1: @@ -2333,24 +2152,17 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - queue-tick@1.0.1: - resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - reflect.getprototypeof@1.0.9: - resolution: {integrity: sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} regex-not@1.0.2: resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} engines: {node: '>=0.10.0'} - regexp.prototype.flags@1.5.3: - resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} - engines: {node: '>= 0.4'} - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -2430,6 +2242,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2494,8 +2311,8 @@ packages: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} - socks@2.8.3: - resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + socks@2.8.4: + resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} source-map-js@1.2.1: @@ -2528,15 +2345,15 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - standalone-electron-types@1.0.0: - resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} + standalone-electron-types@34.2.0: + resolution: {integrity: sha512-+BIrNe0TdZBBBRS3G/F7cPbuBipSZylSjSrJu9+sjw84+vz36a5oZ7l9NeH7aXgMWmPzPZKsTm+K0nOIvhENJQ==} static-extend@0.1.2: resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} engines: {node: '>=0.10.0'} - streamx@2.21.1: - resolution: {integrity: sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==} + streamx@2.22.0: + resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -2573,20 +2390,20 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - stylelint-config-recommended@14.0.1: - resolution: {integrity: sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==} + stylelint-config-recommended@15.0.0: + resolution: {integrity: sha512-9LejMFsat7L+NXttdHdTq94byn25TD+82bzGRiV1Pgasl99pWnwipXS5DguTpp3nP1XjvLXVnEJIuYBfsRjRkA==} engines: {node: '>=18.12.0'} peerDependencies: - stylelint: ^16.1.0 + stylelint: ^16.13.0 - stylelint-config-standard@36.0.1: - resolution: {integrity: sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==} + stylelint-config-standard@37.0.0: + resolution: {integrity: sha512-+6eBlbSTrOn/il2RlV0zYGQwRTkr+WtzuVSs1reaWGObxnxLpbcspCUYajVQHonVfxVw2U+h42azGhrBvcg8OA==} engines: {node: '>=18.12.0'} peerDependencies: - stylelint: ^16.1.0 + stylelint: ^16.13.0 - stylelint@16.12.0: - resolution: {integrity: sha512-F8zZ3L/rBpuoBZRvI4JVT20ZanPLXfQLzMOZg1tzPflRVh9mKpOZ8qcSIhh1my3FjAjZWG4T2POwGnmn6a6hbg==} + stylelint@16.14.1: + resolution: {integrity: sha512-oqCL7AC3786oTax35T/nuLL8p2C3k/8rHKAooezrPGRvUX0wX+qqs5kMWh5YYT4PHQgVDobHT4tw55WgpYG6Sw==} engines: {node: '>=18.12.0'} hasBin: true @@ -2594,8 +2411,8 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - supports-hyperlinks@3.1.0: - resolution: {integrity: sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==} + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} engines: {node: '>=14.18'} supports-preserve-symlinks-flag@1.0.0: @@ -2609,8 +2426,8 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - tar-fs@3.0.6: - resolution: {integrity: sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==} + tar-fs@3.0.8: + resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==} tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -2623,9 +2440,6 @@ packages: text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - to-object-path@0.3.0: resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} engines: {node: '>=0.10.0'} @@ -2638,18 +2452,18 @@ packages: resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} engines: {node: '>=0.10.0'} - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} + ts-api-utils@2.0.1: + resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} + engines: {node: '>=18.12'} peerDependencies: - typescript: '>=4.2.0' + typescript: '>=4.8.4' ts-patch@3.3.0: resolution: {integrity: sha512-zAOzDnd5qsfEnjd9IGy1IRuvA7ygyyxxdxesbhMdutt8AHFjD8Vw8hU2rMF89HX1BKRWFYqKHrO8Q6lw0NeUZg==} hasBin: true - ts-pattern@5.6.0: - resolution: {integrity: sha512-SL8u60X5+LoEy9tmQHWCdPc2hhb2pKI6I1tU5Jue3v8+iRqZdcT3mWPwKKJy1fMfky6uha82c8ByHAE8PMhKHw==} + ts-pattern@5.6.2: + resolution: {integrity: sha512-d4IxJUXROL5NCa3amvMg6VQW2HVtZYmUTPfvVtO7zJWGYLJ+mry9v2OmYm+z67aniQoQ8/yFNadiEwtNS9qQiw==} tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -2657,10 +2471,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@3.12.7: - resolution: {integrity: sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==} - hasBin: true - tsx@4.19.2: resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} engines: {node: '>=18.0.0'} @@ -2670,12 +2480,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@3.9.0: - resolution: {integrity: sha512-hR8JP2e8UiH7SME5JZjsobBlEiatFoxpzCP+R3ZeCo7kAaG1jXQE5X/buLzogM6GJu8le9Y4OcfNuIQX0rZskA==} - engines: {node: '>=14.16'} - - type-fest@4.31.0: - resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==} + type-fest@4.35.0: + resolution: {integrity: sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==} engines: {node: '>=16'} typed-array-buffer@1.0.3: @@ -2697,8 +2503,8 @@ packages: typed-query-selector@2.12.0: resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} - typescript-eslint@8.19.0: - resolution: {integrity: sha512-Ni8sUkVWYK4KAcTtPjQ/UTiRk6jcsuDhPpxULapUDi8A/l8TSBk+t1GtJA1RsCzIJg0q6+J7bf35AwQigENWRQ==} + typescript-eslint@8.24.1: + resolution: {integrity: sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2709,8 +2515,8 @@ packages: peerDependencies: typescript: '>=3.6.5' - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} hasBin: true @@ -2718,12 +2524,6 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - unbzip2-stream@1.4.3: - resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} @@ -2753,8 +2553,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - virtual-merge@1.0.1: - resolution: {integrity: sha512-h7rzV6n5fZJbDu2lP4iu+IOtsZ00uqECFUxFePK1uY0pz/S5B7FNDJpmdDVfyGL7poyJECEHfTaIpJaknNkU0Q==} + virtual-merge@1.0.2: + resolution: {integrity: sha512-5hxklfyTUWMKYaLuoriOf9Xqmt3oWtfAiZw0M3ITeeNmVdKhcnys7rYyfBHqvy/hlELP0hQ4u7o1r5HBXbm6sg==} vscode-oniguruma@1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} @@ -2841,8 +2641,8 @@ packages: zip-local@0.3.5: resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==} - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zod@3.24.2: + resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} snapshots: @@ -2865,187 +2665,179 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 - '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.0.0)': + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': dependencies: - postcss-selector-parser: 7.0.0 + postcss-selector-parser: 7.1.0 '@dual-bundle/import-meta-resolve@4.1.0': {} - '@esbuild-kit/cjs-loader@2.4.2': - dependencies: - '@esbuild-kit/core-utils': 3.1.0 - get-tsconfig: 4.8.1 - - '@esbuild-kit/core-utils@3.1.0': - dependencies: - esbuild: 0.17.19 - source-map-support: 0.5.21 - - '@esbuild-kit/esm-loader@2.5.5': - dependencies: - '@esbuild-kit/core-utils': 3.1.0 - get-tsconfig: 4.8.1 - '@esbuild/aix-ppc64@0.23.1': optional: true - '@esbuild/android-arm64@0.17.19': + '@esbuild/aix-ppc64@0.25.0': optional: true '@esbuild/android-arm64@0.23.1': optional: true - '@esbuild/android-arm@0.15.18': - optional: true - - '@esbuild/android-arm@0.17.19': + '@esbuild/android-arm64@0.25.0': optional: true '@esbuild/android-arm@0.23.1': optional: true - '@esbuild/android-x64@0.17.19': + '@esbuild/android-arm@0.25.0': optional: true '@esbuild/android-x64@0.23.1': optional: true - '@esbuild/darwin-arm64@0.17.19': + '@esbuild/android-x64@0.25.0': optional: true '@esbuild/darwin-arm64@0.23.1': optional: true - '@esbuild/darwin-x64@0.17.19': + '@esbuild/darwin-arm64@0.25.0': optional: true '@esbuild/darwin-x64@0.23.1': optional: true - '@esbuild/freebsd-arm64@0.17.19': + '@esbuild/darwin-x64@0.25.0': optional: true '@esbuild/freebsd-arm64@0.23.1': optional: true - '@esbuild/freebsd-x64@0.17.19': + '@esbuild/freebsd-arm64@0.25.0': optional: true '@esbuild/freebsd-x64@0.23.1': optional: true - '@esbuild/linux-arm64@0.17.19': + '@esbuild/freebsd-x64@0.25.0': optional: true '@esbuild/linux-arm64@0.23.1': optional: true - '@esbuild/linux-arm@0.17.19': + '@esbuild/linux-arm64@0.25.0': optional: true '@esbuild/linux-arm@0.23.1': optional: true - '@esbuild/linux-ia32@0.17.19': + '@esbuild/linux-arm@0.25.0': optional: true '@esbuild/linux-ia32@0.23.1': optional: true - '@esbuild/linux-loong64@0.15.18': - optional: true - - '@esbuild/linux-loong64@0.17.19': + '@esbuild/linux-ia32@0.25.0': optional: true '@esbuild/linux-loong64@0.23.1': optional: true - '@esbuild/linux-mips64el@0.17.19': + '@esbuild/linux-loong64@0.25.0': optional: true '@esbuild/linux-mips64el@0.23.1': optional: true - '@esbuild/linux-ppc64@0.17.19': + '@esbuild/linux-mips64el@0.25.0': optional: true '@esbuild/linux-ppc64@0.23.1': optional: true - '@esbuild/linux-riscv64@0.17.19': + '@esbuild/linux-ppc64@0.25.0': optional: true '@esbuild/linux-riscv64@0.23.1': optional: true - '@esbuild/linux-s390x@0.17.19': + '@esbuild/linux-riscv64@0.25.0': optional: true '@esbuild/linux-s390x@0.23.1': optional: true - '@esbuild/linux-x64@0.17.19': + '@esbuild/linux-s390x@0.25.0': optional: true '@esbuild/linux-x64@0.23.1': optional: true - '@esbuild/netbsd-x64@0.17.19': + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': optional: true '@esbuild/netbsd-x64@0.23.1': optional: true + '@esbuild/netbsd-x64@0.25.0': + optional: true + '@esbuild/openbsd-arm64@0.23.1': optional: true - '@esbuild/openbsd-x64@0.17.19': + '@esbuild/openbsd-arm64@0.25.0': optional: true '@esbuild/openbsd-x64@0.23.1': optional: true - '@esbuild/sunos-x64@0.17.19': + '@esbuild/openbsd-x64@0.25.0': optional: true '@esbuild/sunos-x64@0.23.1': optional: true - '@esbuild/win32-arm64@0.17.19': + '@esbuild/sunos-x64@0.25.0': optional: true '@esbuild/win32-arm64@0.23.1': optional: true - '@esbuild/win32-ia32@0.17.19': + '@esbuild/win32-arm64@0.25.0': optional: true '@esbuild/win32-ia32@0.23.1': optional: true - '@esbuild/win32-x64@0.17.19': + '@esbuild/win32-ia32@0.25.0': optional: true '@esbuild/win32-x64@0.23.1': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))': + '@esbuild/win32-x64@0.25.0': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))': dependencies: - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/config-array@0.19.1': + '@eslint/config-array@0.19.2': dependencies: - '@eslint/object-schema': 2.1.5 + '@eslint/object-schema': 2.1.6 debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/core@0.9.1': + '@eslint/core@0.10.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/core@0.11.0': dependencies: '@types/json-schema': 7.0.15 @@ -3056,19 +2848,20 @@ snapshots: espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.17.0': {} + '@eslint/js@9.20.0': {} - '@eslint/object-schema@2.1.5': {} + '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.4': + '@eslint/plugin-kit@0.2.5': dependencies: + '@eslint/core': 0.10.0 levn: 0.4.1 '@humanfs/core@0.19.1': {} @@ -3108,6 +2901,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@keyv/serialize@1.0.3': + dependencies: + buffer: 6.0.3 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3118,29 +2915,27 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.19.0 - '@puppeteer/browsers@2.6.1': + '@puppeteer/browsers@2.7.1': dependencies: debug: 4.4.0 extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.5.0 - semver: 7.6.3 - tar-fs: 3.0.6 - unbzip2-stream: 1.4.3 + semver: 7.7.1 + tar-fs: 3.0.8 yargs: 17.7.2 transitivePeerDependencies: + - bare-buffer - supports-color '@rtsao/scc@1.1.0': {} - '@sapphi-red/web-noise-suppressor@0.3.5': {} - - '@stylistic/eslint-plugin@2.12.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2)': + '@stylistic/eslint-plugin@4.0.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3)': dependencies: - '@typescript-eslint/utils': 8.18.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + '@typescript-eslint/utils': 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) eslint-visitor-keys: 4.2.0 espree: 10.3.0 estraverse: 5.3.0 @@ -3151,12 +2946,12 @@ snapshots: '@tootallnate/quickjs-emscripten@0.23.0': {} - '@types/chrome@0.0.287': + '@types/chrome@0.0.304': dependencies: '@types/filesystem': 0.0.36 '@types/har-format': 1.2.16 - '@types/diff@6.0.0': {} + '@types/diff@7.0.1': {} '@types/estree@1.0.6': {} @@ -3169,7 +2964,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 18.16.3 + '@types/node': 22.13.4 '@types/har-format@1.2.16': {} @@ -3179,172 +2974,122 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 18.16.3 + '@types/node': 22.13.4 - '@types/lodash@4.14.194': {} + '@types/lodash@4.17.15': {} - '@types/lodash@4.17.14': {} - - '@types/node@18.16.3': {} - - '@types/node@18.19.69': - dependencies: - undici-types: 5.26.5 - - '@types/node@22.10.5': + '@types/node@22.13.4': dependencies: undici-types: 6.20.0 '@types/prop-types@15.7.14': {} - '@types/prop-types@15.7.5': {} - - '@types/react-dom@18.2.1': + '@types/react-dom@18.3.1': dependencies: - '@types/react': 18.2.0 + '@types/react': 18.3.1 - '@types/react-dom@19.0.2(@types/react@19.0.2)': + '@types/react-dom@19.0.4(@types/react@19.0.10)': dependencies: - '@types/react': 19.0.2 + '@types/react': 19.0.10 '@types/react@17.0.2': dependencies: '@types/prop-types': 15.7.14 csstype: 3.1.3 - '@types/react@18.2.0': + '@types/react@18.3.1': dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.3 - csstype: 3.1.2 + '@types/prop-types': 15.7.14 + csstype: 3.1.3 - '@types/react@19.0.2': + '@types/react@19.0.10': dependencies: csstype: 3.1.3 - '@types/scheduler@0.16.3': {} - '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.10.5 + '@types/node': 22.13.4 optional: true - '@types/yazl@2.4.5': + '@types/yazl@2.4.6': dependencies: - '@types/node': 22.10.5 + '@types/node': 22.13.4 - '@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2)': + '@typescript-eslint/eslint-plugin@8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) - '@typescript-eslint/scope-manager': 8.19.0 - '@typescript-eslint/type-utils': 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) - '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.19.0 - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + '@typescript-eslint/parser': 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.24.1 + '@typescript-eslint/type-utils': 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) + '@typescript-eslint/utils': 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.24.1 + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 + ts-api-utils: 2.0.1(typescript@5.7.3) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2)': + '@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3)': dependencies: - '@typescript-eslint/scope-manager': 8.19.0 - '@typescript-eslint/types': 8.19.0 - '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.19.0 + '@typescript-eslint/scope-manager': 8.24.1 + '@typescript-eslint/types': 8.24.1 + '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.24.1 debug: 4.4.0 - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) - typescript: 5.7.2 + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.18.1': + '@typescript-eslint/scope-manager@8.24.1': dependencies: - '@typescript-eslint/types': 8.18.1 - '@typescript-eslint/visitor-keys': 8.18.1 + '@typescript-eslint/types': 8.24.1 + '@typescript-eslint/visitor-keys': 8.24.1 - '@typescript-eslint/scope-manager@8.19.0': + '@typescript-eslint/type-utils@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3)': dependencies: - '@typescript-eslint/types': 8.19.0 - '@typescript-eslint/visitor-keys': 8.19.0 - - '@typescript-eslint/type-utils@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2)': - dependencies: - '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) - '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) + '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) + '@typescript-eslint/utils': 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) debug: 4.4.0 - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + ts-api-utils: 2.0.1(typescript@5.7.3) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.18.1': {} + '@typescript-eslint/types@8.24.1': {} - '@typescript-eslint/types@8.19.0': {} - - '@typescript-eslint/typescript-estree@8.18.1(typescript@5.7.2)': + '@typescript-eslint/typescript-estree@8.24.1(typescript@5.7.3)': dependencies: - '@typescript-eslint/types': 8.18.1 - '@typescript-eslint/visitor-keys': 8.18.1 + '@typescript-eslint/types': 8.24.1 + '@typescript-eslint/visitor-keys': 8.24.1 debug: 4.4.0 - fast-glob: 3.3.2 + fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 + semver: 7.7.1 + ts-api-utils: 2.0.1(typescript@5.7.3) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.19.0(typescript@5.7.2)': + '@typescript-eslint/utils@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3)': dependencies: - '@typescript-eslint/types': 8.19.0 - '@typescript-eslint/visitor-keys': 8.19.0 - debug: 4.4.0 - fast-glob: 3.3.2 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) + '@typescript-eslint/scope-manager': 8.24.1 + '@typescript-eslint/types': 8.24.1 + '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.18.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2)': + '@typescript-eslint/visitor-keys@8.24.1': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) - '@typescript-eslint/scope-manager': 8.18.1 - '@typescript-eslint/types': 8.18.1 - '@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2) - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2)': - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) - '@typescript-eslint/scope-manager': 8.19.0 - '@typescript-eslint/types': 8.19.0 - '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.18.1': - dependencies: - '@typescript-eslint/types': 8.18.1 - eslint-visitor-keys: 4.2.0 - - '@typescript-eslint/visitor-keys@8.19.0': - dependencies: - '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/types': 8.24.1 eslint-visitor-keys: 4.2.0 '@vap/core@0.0.12': @@ -3375,7 +3120,7 @@ snapshots: ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.3 + fast-uri: 3.0.6 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -3391,11 +3136,6 @@ snapshots: arr-union@3.1.0: {} - array-buffer-byte-length@1.0.1: - dependencies: - call-bind: 1.0.8 - is-array-buffer: 3.0.5 - array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.3 @@ -3405,9 +3145,9 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.6 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.6 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.2.7 is-string: 1.1.1 array-union@2.1.0: {} @@ -3418,10 +3158,10 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.6 + es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-shim-unscopables: 1.0.2 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 array.prototype.findlastindex@1.2.5: dependencies: @@ -3429,39 +3169,39 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-shim-unscopables: 1.0.2 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 array.prototype.flat@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.6 - es-shim-unscopables: 1.0.2 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.6 - es-shim-unscopables: 1.0.2 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 array.prototype.tosorted@1.1.4: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.6 + es-abstract: 1.23.9 es-errors: 1.3.0 - es-shim-unscopables: 1.0.2 + es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: dependencies: - array-buffer-byte-length: 1.0.1 + array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.6 + es-abstract: 1.23.9 es-errors: 1.3.0 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 is-array-buffer: 3.0.5 assign-symbols@1.0.0: {} @@ -3472,13 +3212,15 @@ snapshots: astral-regex@2.0.0: {} + async-function@1.0.0: {} + async@1.5.2: {} atob@2.1.2: {} available-typed-arrays@1.0.7: dependencies: - possible-typed-array-names: 1.0.0 + possible-typed-array-names: 1.1.0 b4a@1.6.7: {} @@ -3486,27 +3228,31 @@ snapshots: balanced-match@2.0.0: {} - bare-events@2.5.0: + bare-events@2.5.4: optional: true - bare-fs@2.3.5: + bare-fs@4.0.1: dependencies: - bare-events: 2.5.0 - bare-path: 2.1.3 - bare-stream: 2.6.1 + bare-events: 2.5.4 + bare-path: 3.0.0 + bare-stream: 2.6.5(bare-events@2.5.4) + transitivePeerDependencies: + - bare-buffer optional: true - bare-os@2.4.4: + bare-os@3.4.0: optional: true - bare-path@2.1.3: + bare-path@3.0.0: dependencies: - bare-os: 2.4.4 + bare-os: 3.4.0 optional: true - bare-stream@2.6.1: + bare-stream@2.6.5(bare-events@2.5.4): dependencies: - streamx: 2.21.1 + streamx: 2.22.0 + optionalDependencies: + bare-events: 2.5.4 optional: true base64-js@1.5.1: {} @@ -3540,7 +3286,7 @@ snapshots: buffer-from@1.1.2: {} - buffer@5.7.1: + buffer@6.0.3: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 @@ -3557,21 +3303,26 @@ snapshots: union-value: 1.0.1 unset-value: 1.0.0 - call-bind-apply-helpers@1.0.1: + cacheable@1.8.8: + dependencies: + hookified: 1.7.1 + keyv: 5.2.3 + + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 call-bind@1.0.8: dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 set-function-length: 1.2.2 call-bound@1.0.3: dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.2.7 callsites@3.1.0: {} @@ -3586,11 +3337,11 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chromium-bidi@0.11.0(devtools-protocol@0.0.1367902): + chromium-bidi@1.3.0(devtools-protocol@0.0.1402036): dependencies: - devtools-protocol: 0.0.1367902 + devtools-protocol: 0.0.1402036 mitt: 3.0.1 - zod: 3.23.8 + zod: 3.24.2 class-utils@0.3.6: dependencies: @@ -3632,14 +3383,14 @@ snapshots: copy-descriptor@0.1.1: {} - cosmiconfig@9.0.0(typescript@5.7.2): + cosmiconfig@9.0.0(typescript@5.7.3): dependencies: env-paths: 2.2.1 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 cross-spawn@7.0.6: dependencies: @@ -3656,30 +3407,16 @@ snapshots: cssesc@3.0.0: {} - csstype@3.1.2: {} - csstype@3.1.3: {} data-uri-to-buffer@6.0.2: {} - data-view-buffer@1.0.1: - dependencies: - call-bind: 1.0.8 - es-errors: 1.3.0 - is-data-view: 1.0.2 - data-view-buffer@1.0.2: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 is-data-view: 1.0.2 - data-view-byte-length@1.0.1: - dependencies: - call-bind: 1.0.8 - es-errors: 1.3.0 - is-data-view: 1.0.2 - data-view-byte-length@1.0.2: dependencies: call-bound: 1.0.3 @@ -3739,7 +3476,7 @@ snapshots: escodegen: 2.1.0 esprima: 4.0.1 - devtools-protocol@0.0.1367902: {} + devtools-protocol@0.0.1402036: {} diff@7.0.0: {} @@ -3763,7 +3500,7 @@ snapshots: dunder-proto@1.0.1: dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 @@ -3781,57 +3518,6 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-abstract@1.23.6: - dependencies: - array-buffer-byte-length: 1.0.1 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.3 - data-view-buffer: 1.0.1 - data-view-byte-length: 1.0.1 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.7 - get-intrinsic: 1.2.6 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.0 - math-intrinsics: 1.1.0 - object-inspect: 1.13.3 - object-keys: 1.1.1 - object.assign: 4.1.7 - regexp.prototype.flags: 1.5.3 - safe-array-concat: 1.1.3 - safe-regex-test: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.18 - es-abstract@1.23.9: dependencies: array-buffer-byte-length: 1.0.2 @@ -3844,7 +3530,7 @@ snapshots: data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 function.prototype.name: 1.1.8 @@ -3865,9 +3551,9 @@ snapshots: is-shared-array-buffer: 1.0.4 is-string: 1.1.1 is-typed-array: 1.1.15 - is-weakref: 1.1.0 + is-weakref: 1.1.1 math-intrinsics: 1.1.0 - object-inspect: 1.13.3 + object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 own-keys: 1.0.1 @@ -3909,16 +3595,10 @@ snapshots: iterator.prototype: 1.1.5 safe-array-concat: 1.1.3 - es-object-atoms@1.0.0: + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.0.3: - dependencies: - get-intrinsic: 1.2.6 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 @@ -3926,7 +3606,7 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-shim-unscopables@1.0.2: + es-shim-unscopables@1.1.0: dependencies: hasown: 2.0.2 @@ -3936,116 +3616,6 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild-android-64@0.15.18: - optional: true - - esbuild-android-arm64@0.15.18: - optional: true - - esbuild-darwin-64@0.15.18: - optional: true - - esbuild-darwin-arm64@0.15.18: - optional: true - - esbuild-freebsd-64@0.15.18: - optional: true - - esbuild-freebsd-arm64@0.15.18: - optional: true - - esbuild-linux-32@0.15.18: - optional: true - - esbuild-linux-64@0.15.18: - optional: true - - esbuild-linux-arm64@0.15.18: - optional: true - - esbuild-linux-arm@0.15.18: - optional: true - - esbuild-linux-mips64le@0.15.18: - optional: true - - esbuild-linux-ppc64le@0.15.18: - optional: true - - esbuild-linux-riscv64@0.15.18: - optional: true - - esbuild-linux-s390x@0.15.18: - optional: true - - esbuild-netbsd-64@0.15.18: - optional: true - - esbuild-openbsd-64@0.15.18: - optional: true - - esbuild-sunos-64@0.15.18: - optional: true - - esbuild-windows-32@0.15.18: - optional: true - - esbuild-windows-64@0.15.18: - optional: true - - esbuild-windows-arm64@0.15.18: - optional: true - - esbuild@0.15.18: - optionalDependencies: - '@esbuild/android-arm': 0.15.18 - '@esbuild/linux-loong64': 0.15.18 - esbuild-android-64: 0.15.18 - esbuild-android-arm64: 0.15.18 - esbuild-darwin-64: 0.15.18 - esbuild-darwin-arm64: 0.15.18 - esbuild-freebsd-64: 0.15.18 - esbuild-freebsd-arm64: 0.15.18 - esbuild-linux-32: 0.15.18 - esbuild-linux-64: 0.15.18 - esbuild-linux-arm: 0.15.18 - esbuild-linux-arm64: 0.15.18 - esbuild-linux-mips64le: 0.15.18 - esbuild-linux-ppc64le: 0.15.18 - esbuild-linux-riscv64: 0.15.18 - esbuild-linux-s390x: 0.15.18 - esbuild-netbsd-64: 0.15.18 - esbuild-openbsd-64: 0.15.18 - esbuild-sunos-64: 0.15.18 - esbuild-windows-32: 0.15.18 - esbuild-windows-64: 0.15.18 - esbuild-windows-arm64: 0.15.18 - - esbuild@0.17.19: - optionalDependencies: - '@esbuild/android-arm': 0.17.19 - '@esbuild/android-arm64': 0.17.19 - '@esbuild/android-x64': 0.17.19 - '@esbuild/darwin-arm64': 0.17.19 - '@esbuild/darwin-x64': 0.17.19 - '@esbuild/freebsd-arm64': 0.17.19 - '@esbuild/freebsd-x64': 0.17.19 - '@esbuild/linux-arm': 0.17.19 - '@esbuild/linux-arm64': 0.17.19 - '@esbuild/linux-ia32': 0.17.19 - '@esbuild/linux-loong64': 0.17.19 - '@esbuild/linux-mips64el': 0.17.19 - '@esbuild/linux-ppc64': 0.17.19 - '@esbuild/linux-riscv64': 0.17.19 - '@esbuild/linux-s390x': 0.17.19 - '@esbuild/linux-x64': 0.17.19 - '@esbuild/netbsd-x64': 0.17.19 - '@esbuild/openbsd-x64': 0.17.19 - '@esbuild/sunos-x64': 0.17.19 - '@esbuild/win32-arm64': 0.17.19 - '@esbuild/win32-ia32': 0.17.19 - '@esbuild/win32-x64': 0.17.19 - esbuild@0.23.1: optionalDependencies: '@esbuild/aix-ppc64': 0.23.1 @@ -4073,6 +3643,34 @@ snapshots: '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -4085,9 +3683,9 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))): + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))): dependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) eslint-import-resolver-node@0.3.9: dependencies: @@ -4097,17 +3695,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + '@typescript-eslint/parser': 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -4116,9 +3714,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4130,22 +3728,22 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) + '@typescript-eslint/parser': 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-path-alias@2.1.0(patch_hash=japuwsqfkulviwgkm4kd2oi3ky)(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)): + eslint-plugin-path-alias@2.1.0(patch_hash=87545cb13985b338c8fa2ea7b0a3c75c57ad7fbc81c56b38d6c9438329957727)(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): dependencies: - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) find-pkg: 2.0.0 get-tsconfig: 4.8.1 nanomatch: 1.2.13 transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.3(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)): + eslint-plugin-react@7.37.4(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 @@ -4153,7 +3751,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -4167,19 +3765,19 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-simple-header@1.2.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)): + eslint-plugin-simple-header@1.2.2(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): dependencies: - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) - eslint-plugin-simple-import-sort@12.1.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)): + eslint-plugin-simple-import-sort@12.1.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): dependencies: - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): dependencies: - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) + '@typescript-eslint/eslint-plugin': 8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) eslint-scope@8.2.0: dependencies: @@ -4190,15 +3788,15 @@ snapshots: eslint-visitor-keys@4.2.0: {} - eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4): + eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 - '@eslint/core': 0.9.1 + '@eslint/config-array': 0.19.2 + '@eslint/core': 0.11.0 '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.17.0 - '@eslint/plugin-kit': 0.2.4 + '@eslint/js': 9.20.0 + '@eslint/plugin-kit': 0.2.5 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.1 @@ -4278,7 +3876,7 @@ snapshots: fast-fifo@1.3.2: {} - fast-glob@3.3.2: + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -4290,11 +3888,11 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.0.3: {} + fast-uri@3.0.6: {} fastest-levenshtein@1.0.16: {} - fastq@1.17.1: + fastq@1.19.0: dependencies: reusify: 1.0.4 @@ -4304,14 +3902,14 @@ snapshots: fflate@0.8.2: {} + file-entry-cache@10.0.6: + dependencies: + flat-cache: 6.1.6 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 - file-entry-cache@9.1.0: - dependencies: - flat-cache: 5.0.0 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -4331,17 +3929,18 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.2 + flatted: 3.3.3 keyv: 4.5.4 - flat-cache@5.0.0: + flat-cache@6.1.6: dependencies: - flatted: 3.3.2 - keyv: 4.5.4 + cacheable: 1.8.8 + flatted: 3.3.3 + hookified: 1.7.1 - flatted@3.3.2: {} + flatted@3.3.3: {} - for-each@0.3.3: + for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -4351,28 +3950,17 @@ snapshots: dependencies: map-cache: 0.2.2 - fs-extra@11.2.0: + fs-extra@11.3.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 - fsevents@2.3.2: - optional: true - fsevents@2.3.3: optional: true function-bind@1.1.2: {} - function.prototype.name@1.1.7: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - functions-have-names: 1.2.3 - hasown: 2.0.2 - is-callable: 1.2.7 - function.prototype.name@1.1.8: dependencies: call-bind: 1.0.8 @@ -4386,25 +3974,12 @@ snapshots: get-caller-file@2.0.5: {} - get-intrinsic@1.2.6: - dependencies: - call-bind-apply-helpers: 1.0.1 - dunder-proto: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - function-bind: 1.1.2 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - get-intrinsic@1.2.7: dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 @@ -4415,7 +3990,7 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 get-stream@5.2.0: dependencies: @@ -4425,7 +4000,11 @@ snapshots: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 + + get-tsconfig@4.10.0: + dependencies: + resolve-pkg-maps: 1.0.0 get-tsconfig@4.8.1: dependencies: @@ -4492,7 +4071,7 @@ snapshots: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.2 + fast-glob: 3.3.3 ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 @@ -4546,12 +4125,14 @@ snapshots: dependencies: function-bind: 1.1.2 - highlight.js@11.7.0: {} + highlight.js@11.11.1: {} homedir-polyfill@1.0.3: dependencies: parse-passwd: 1.0.0 + hookified@1.7.1: {} + html-minifier-terser@7.2.0: dependencies: camel-case: 4.1.2 @@ -4582,9 +4163,9 @@ snapshots: ignore@5.3.2: {} - ignore@6.0.2: {} + ignore@7.0.3: {} - import-fresh@3.3.0: + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 @@ -4614,19 +4195,23 @@ snapshots: dependencies: call-bind: 1.0.8 call-bound: 1.0.3 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 is-arrayish@0.2.1: {} - is-async-function@2.0.0: + is-async-function@2.1.1: dependencies: + async-function: 1.0.0 + call-bound: 1.0.3 + get-proto: 1.0.1 has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 is-bigint@1.1.0: dependencies: has-bigints: 1.1.0 - is-boolean-object@1.2.1: + is-boolean-object@1.2.2: dependencies: call-bound: 1.0.3 has-tostringtag: 1.0.2 @@ -4650,7 +4235,7 @@ snapshots: is-data-view@1.0.2: dependencies: call-bound: 1.0.3 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 is-typed-array: 1.1.15 is-date-object@1.1.0: @@ -4682,9 +4267,12 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.0.10: + is-generator-function@1.1.0: dependencies: + call-bound: 1.0.3 + get-proto: 1.0.1 has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 is-glob@4.0.3: dependencies: @@ -4692,8 +4280,6 @@ snapshots: is-map@2.0.3: {} - is-negative-zero@2.0.3: {} - is-number-object@1.1.1: dependencies: call-bound: 1.0.3 @@ -4741,14 +4327,14 @@ snapshots: is-weakmap@2.0.2: {} - is-weakref@1.1.0: + is-weakref@1.1.1: dependencies: call-bound: 1.0.3 is-weakset@2.0.4: dependencies: call-bound: 1.0.3 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 is-windows@1.0.2: {} @@ -4769,7 +4355,7 @@ snapshots: iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 get-intrinsic: 1.2.7 get-proto: 1.0.1 has-symbols: 1.1.0 @@ -4820,6 +4406,10 @@ snapshots: dependencies: json-buffer: 3.0.1 + keyv@5.2.3: + dependencies: + '@keyv/serialize': 1.0.3 + kind-of@3.2.2: dependencies: is-buffer: 1.1.6 @@ -4905,7 +4495,7 @@ snapshots: nanoid@3.3.8: {} - nanoid@5.0.9: {} + nanoid@5.1.0: {} nanomatch@1.2.13: dependencies: @@ -4942,7 +4532,7 @@ snapshots: define-property: 0.2.5 kind-of: 3.2.2 - object-inspect@1.13.3: {} + object-inspect@1.13.4: {} object-keys@1.1.1: {} @@ -4955,7 +4545,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.3 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 @@ -4963,14 +4553,14 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 object.fromentries@2.0.8: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.6 - es-object-atoms: 1.0.0 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: @@ -4987,7 +4577,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.3 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 once@1.4.0: dependencies: @@ -5077,22 +4667,22 @@ snapshots: picomatch@4.0.2: {} - possible-typed-array-names@1.0.0: {} + possible-typed-array-names@1.1.0: {} postcss-resolve-nested-selector@0.1.6: {} - postcss-safe-parser@7.0.1(postcss@8.4.49): + postcss-safe-parser@7.0.1(postcss@8.5.2): dependencies: - postcss: 8.4.49 + postcss: 8.5.2 - postcss-selector-parser@7.0.0: + postcss-selector-parser@7.1.0: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 postcss-value-parser@4.2.0: {} - postcss@8.4.49: + postcss@8.5.2: dependencies: nanoid: 3.3.8 picocolors: 1.1.1 @@ -5130,15 +4720,16 @@ snapshots: punycode@2.3.1: {} - puppeteer-core@23.11.1: + puppeteer-core@24.2.1: dependencies: - '@puppeteer/browsers': 2.6.1 - chromium-bidi: 0.11.0(devtools-protocol@0.0.1367902) + '@puppeteer/browsers': 2.7.1 + chromium-bidi: 1.3.0(devtools-protocol@0.0.1402036) debug: 4.4.0 - devtools-protocol: 0.0.1367902 + devtools-protocol: 0.0.1402036 typed-query-selector: 2.12.0 ws: 8.18.0 transitivePeerDependencies: + - bare-buffer - bufferutil - supports-color - utf-8-validate @@ -5147,19 +4738,17 @@ snapshots: queue-microtask@1.2.3: {} - queue-tick@1.0.1: {} - react-is@16.13.1: {} - reflect.getprototypeof@1.0.9: + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - dunder-proto: 1.0.1 - es-abstract: 1.23.6 + es-abstract: 1.23.9 es-errors: 1.3.0 - get-intrinsic: 1.2.6 - gopd: 1.2.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.2.7 + get-proto: 1.0.1 which-builtin-type: 1.2.1 regex-not@1.0.2: @@ -5167,13 +4756,6 @@ snapshots: extend-shallow: 3.0.2 safe-regex: 1.1.0 - regexp.prototype.flags@1.5.3: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-errors: 1.3.0 - set-function-name: 2.0.2 - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -5210,7 +4792,7 @@ snapshots: resolve@2.0.0-next.5: dependencies: - is-core-module: 2.16.0 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -5226,7 +4808,7 @@ snapshots: dependencies: call-bind: 1.0.8 call-bound: 1.0.3 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 has-symbols: 1.1.0 isarray: 2.0.5 @@ -5249,12 +4831,14 @@ snapshots: semver@7.6.3: {} + semver@7.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -5269,7 +4853,7 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 set-value@2.0.1: dependencies: @@ -5287,27 +4871,27 @@ snapshots: side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 - object-inspect: 1.13.3 + object-inspect: 1.13.4 side-channel-map@1.0.1: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.6 - object-inspect: 1.13.3 + get-intrinsic: 1.2.7 + object-inspect: 1.13.4 side-channel-weakmap@1.0.2: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.6 - object-inspect: 1.13.3 + get-intrinsic: 1.2.7 + object-inspect: 1.13.4 side-channel-map: 1.0.1 side-channel@1.1.0: dependencies: es-errors: 1.3.0 - object-inspect: 1.13.3 + object-inspect: 1.13.4 side-channel-list: 1.0.0 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -5341,11 +4925,11 @@ snapshots: dependencies: agent-base: 7.1.3 debug: 4.4.0 - socks: 2.8.3 + socks: 2.8.4 transitivePeerDependencies: - supports-color - socks@2.8.3: + socks@2.8.4: dependencies: ip-address: 9.0.5 smart-buffer: 4.2.0 @@ -5377,22 +4961,21 @@ snapshots: sprintf-js@1.1.3: {} - standalone-electron-types@1.0.0: + standalone-electron-types@34.2.0: dependencies: - '@types/node': 18.19.69 + '@types/node': 22.13.4 static-extend@0.1.2: dependencies: define-property: 0.2.5 object-copy: 0.1.0 - streamx@2.21.1: + streamx@2.22.0: dependencies: fast-fifo: 1.3.2 - queue-tick: 1.0.1 text-decoder: 1.2.3 optionalDependencies: - bare-events: 2.5.0 + bare-events: 2.5.4 string-width@4.2.3: dependencies: @@ -5407,7 +4990,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 get-intrinsic: 1.2.7 gopd: 1.2.0 has-symbols: 1.1.0 @@ -5419,7 +5002,7 @@ snapshots: string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.23.6 + es-abstract: 1.23.9 string.prototype.trim@1.2.10: dependencies: @@ -5427,8 +5010,8 @@ snapshots: call-bound: 1.0.3 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.6 - es-object-atoms: 1.0.0 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: @@ -5436,13 +5019,13 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.3 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 strip-ansi@6.0.1: dependencies: @@ -5452,36 +5035,36 @@ snapshots: strip-json-comments@3.1.1: {} - stylelint-config-recommended@14.0.1(stylelint@16.12.0(typescript@5.7.2)): + stylelint-config-recommended@15.0.0(stylelint@16.14.1(typescript@5.7.3)): dependencies: - stylelint: 16.12.0(typescript@5.7.2) + stylelint: 16.14.1(typescript@5.7.3) - stylelint-config-standard@36.0.1(stylelint@16.12.0(typescript@5.7.2)): + stylelint-config-standard@37.0.0(stylelint@16.14.1(typescript@5.7.3)): dependencies: - stylelint: 16.12.0(typescript@5.7.2) - stylelint-config-recommended: 14.0.1(stylelint@16.12.0(typescript@5.7.2)) + stylelint: 16.14.1(typescript@5.7.3) + stylelint-config-recommended: 15.0.0(stylelint@16.14.1(typescript@5.7.3)) - stylelint@16.12.0(typescript@5.7.2): + stylelint@16.14.1(typescript@5.7.3): dependencies: '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.0.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) '@dual-bundle/import-meta-resolve': 4.1.0 balanced-match: 2.0.0 colord: 2.9.3 - cosmiconfig: 9.0.0(typescript@5.7.2) + cosmiconfig: 9.0.0(typescript@5.7.3) css-functions-list: 3.2.3 css-tree: 3.1.0 debug: 4.4.0 - fast-glob: 3.3.2 + fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 - file-entry-cache: 9.1.0 + file-entry-cache: 10.0.6 global-modules: 2.0.0 globby: 11.1.0 globjoin: 0.1.4 html-tags: 3.3.1 - ignore: 6.0.2 + ignore: 7.0.3 imurmurhash: 0.1.4 is-plain-object: 5.0.0 known-css-properties: 0.35.0 @@ -5490,14 +5073,14 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.1 - postcss: 8.4.49 + postcss: 8.5.2 postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 7.0.1(postcss@8.4.49) - postcss-selector-parser: 7.0.0 + postcss-safe-parser: 7.0.1(postcss@8.5.2) + postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 string-width: 4.2.3 - supports-hyperlinks: 3.1.0 + supports-hyperlinks: 3.2.0 svg-tags: 1.0.0 table: 6.9.0 write-file-atomic: 5.0.1 @@ -5509,7 +5092,7 @@ snapshots: dependencies: has-flag: 4.0.0 - supports-hyperlinks@3.1.0: + supports-hyperlinks@3.2.0: dependencies: has-flag: 4.0.0 supports-color: 7.2.0 @@ -5526,19 +5109,21 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - tar-fs@3.0.6: + tar-fs@3.0.8: dependencies: pump: 3.0.2 tar-stream: 3.1.7 optionalDependencies: - bare-fs: 2.3.5 - bare-path: 2.1.3 + bare-fs: 4.0.1 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer tar-stream@3.1.7: dependencies: b4a: 1.6.7 fast-fifo: 1.3.2 - streamx: 2.21.1 + streamx: 2.22.0 terser@5.37.0: dependencies: @@ -5551,8 +5136,6 @@ snapshots: dependencies: b4a: 1.6.7 - through@2.3.8: {} - to-object-path@0.3.0: dependencies: kind-of: 3.2.2 @@ -5568,9 +5151,9 @@ snapshots: regex-not: 1.0.2 safe-regex: 1.1.0 - ts-api-utils@1.4.3(typescript@5.7.2): + ts-api-utils@2.0.1(typescript@5.7.3): dependencies: - typescript: 5.7.2 + typescript: 5.7.3 ts-patch@3.3.0: dependencies: @@ -5581,7 +5164,7 @@ snapshots: semver: 7.6.3 strip-ansi: 6.0.1 - ts-pattern@5.6.0: {} + ts-pattern@5.6.2: {} tsconfig-paths@3.15.0: dependencies: @@ -5592,18 +5175,10 @@ snapshots: tslib@2.8.1: {} - tsx@3.12.7: - dependencies: - '@esbuild-kit/cjs-loader': 2.4.2 - '@esbuild-kit/core-utils': 3.1.0 - '@esbuild-kit/esm-loader': 2.5.5 - optionalDependencies: - fsevents: 2.3.2 - tsx@4.19.2: dependencies: esbuild: 0.23.1 - get-tsconfig: 4.8.1 + get-tsconfig: 4.10.0 optionalDependencies: fsevents: 2.3.3 @@ -5611,9 +5186,7 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@3.9.0: {} - - type-fest@4.31.0: {} + type-fest@4.35.0: {} typed-array-buffer@1.0.3: dependencies: @@ -5624,7 +5197,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 @@ -5633,39 +5206,39 @@ snapshots: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.9 + reflect.getprototypeof: 1.0.10 typed-array-length@1.0.7: dependencies: call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 - possible-typed-array-names: 1.0.0 - reflect.getprototypeof: 1.0.9 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 typed-query-selector@2.12.0: {} - typescript-eslint@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2): + typescript-eslint@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2))(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) - '@typescript-eslint/parser': 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) - '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) - typescript: 5.7.2 + '@typescript-eslint/eslint-plugin': 8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) + '@typescript-eslint/parser': 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) + '@typescript-eslint/utils': 8.24.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.7.3) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - typescript-transform-paths@3.5.3(typescript@5.7.2): + typescript-transform-paths@3.5.3(typescript@5.7.3): dependencies: minimatch: 9.0.5 - typescript: 5.7.2 + typescript: 5.7.3 - typescript@5.7.2: {} + typescript@5.7.3: {} unbox-primitive@1.1.0: dependencies: @@ -5674,13 +5247,6 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - unbzip2-stream@1.4.3: - dependencies: - buffer: 5.7.1 - through: 2.3.8 - - undici-types@5.26.5: {} - undici-types@6.20.0: {} union-value@1.0.1: @@ -5707,7 +5273,7 @@ snapshots: util-deprecate@1.0.2: {} - virtual-merge@1.0.1: {} + virtual-merge@1.0.2: {} vscode-oniguruma@1.7.0: {} @@ -5716,7 +5282,7 @@ snapshots: which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 - is-boolean-object: 1.2.1 + is-boolean-object: 1.2.2 is-number-object: 1.1.1 is-string: 1.1.1 is-symbol: 1.1.1 @@ -5724,14 +5290,14 @@ snapshots: which-builtin-type@1.2.1: dependencies: call-bound: 1.0.3 - function.prototype.name: 1.1.7 + function.prototype.name: 1.1.8 has-tostringtag: 1.0.2 - is-async-function: 2.0.0 + is-async-function: 2.1.1 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 - is-generator-function: 1.0.10 + is-generator-function: 1.1.0 is-regex: 1.2.1 - is-weakref: 1.1.0 + is-weakref: 1.1.1 isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 @@ -5749,7 +5315,7 @@ snapshots: available-typed-arrays: 1.0.7 call-bind: 1.0.8 call-bound: 1.0.3 - for-each: 0.3.3 + for-each: 0.3.5 gopd: 1.2.0 has-tostringtag: 1.0.2 @@ -5810,4 +5376,4 @@ snapshots: jszip: 2.7.0 q: 1.5.1 - zod@3.23.8: {} + zod@3.24.2: {} diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 623f9f940..9c2b49708 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -17,38 +17,41 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import esbuild from "esbuild"; +// @ts-check + import { readdir } from "fs/promises"; import { join } from "path"; -import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch, buildOrWatchAll, stringifyValues } from "./common.mjs"; -const defines = { +const defines = stringifyValues({ IS_STANDALONE, IS_DEV, IS_REPORTER, IS_UPDATER_DISABLED, IS_WEB: false, IS_EXTENSION: false, - VERSION: JSON.stringify(VERSION), + VERSION, BUILD_TIMESTAMP -}; +}); -if (defines.IS_STANDALONE === false) +if (defines.IS_STANDALONE === "false") { // If this is a local build (not standalone), optimize // for the specific platform we're on defines["process.platform"] = JSON.stringify(process.platform); +} /** - * @type {esbuild.BuildOptions} + * @type {import("esbuild").BuildOptions} */ const nodeCommonOpts = { ...commonOpts, + define: defines, format: "cjs", platform: "node", target: ["esnext"], - external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external], - define: defines + // @ts-ignore this is never undefined + external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external] }; const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`; @@ -102,25 +105,27 @@ const globNativesPlugin = { } }; -await Promise.all([ +/** @type {import("esbuild").BuildOptions[]} */ +const buildConfigs = ([ // Discord Desktop main & renderer & preload - esbuild.build({ + { ...nodeCommonOpts, entryPoints: ["src/main/index.ts"], outfile: "dist/patcher.js", footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") }, sourcemap, - define: { - ...defines, - IS_DISCORD_DESKTOP: true, - IS_VESKTOP: false - }, plugins: [ + // @ts-ignore this is never undefined ...nodeCommonOpts.plugins, globNativesPlugin - ] - }), - esbuild.build({ + ], + define: { + ...defines, + IS_DISCORD_DESKTOP: "true", + IS_VESKTOP: "false" + } + }, + { ...commonOpts, entryPoints: ["src/Vencord.ts"], outfile: "dist/renderer.js", @@ -135,11 +140,11 @@ await Promise.all([ ], define: { ...defines, - IS_DISCORD_DESKTOP: true, - IS_VESKTOP: false + IS_DISCORD_DESKTOP: "true", + IS_VESKTOP: "false" } - }), - esbuild.build({ + }, + { ...nodeCommonOpts, entryPoints: ["src/preload.ts"], outfile: "dist/preload.js", @@ -147,29 +152,29 @@ await Promise.all([ sourcemap, define: { ...defines, - IS_DISCORD_DESKTOP: true, - IS_VESKTOP: false + IS_DISCORD_DESKTOP: "true", + IS_VESKTOP: "false" } - }), + }, // Vencord Desktop main & renderer & preload - esbuild.build({ + { ...nodeCommonOpts, entryPoints: ["src/main/index.ts"], outfile: "dist/vencordDesktopMain.js", footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") }, sourcemap, - define: { - ...defines, - IS_DISCORD_DESKTOP: false, - IS_VESKTOP: true - }, plugins: [ ...nodeCommonOpts.plugins, globNativesPlugin - ] - }), - esbuild.build({ + ], + define: { + ...defines, + IS_DISCORD_DESKTOP: "false", + IS_VESKTOP: "true" + } + }, + { ...commonOpts, entryPoints: ["src/Vencord.ts"], outfile: "dist/vencordDesktopRenderer.js", @@ -184,11 +189,11 @@ await Promise.all([ ], define: { ...defines, - IS_DISCORD_DESKTOP: false, - IS_VESKTOP: true + IS_DISCORD_DESKTOP: "false", + IS_VESKTOP: "true" } - }), - esbuild.build({ + }, + { ...nodeCommonOpts, entryPoints: ["src/preload.ts"], outfile: "dist/vencordDesktopPreload.js", @@ -196,14 +201,10 @@ await Promise.all([ sourcemap, define: { ...defines, - IS_DISCORD_DESKTOP: false, - IS_VESKTOP: true + IS_DISCORD_DESKTOP: "false", + IS_VESKTOP: "true" } - }), -]).catch(err => { - console.error("Build failed"); - console.error(err.message); - // make ci fail - if (!commonOpts.watch) - process.exitCode = 1; -}); + } +]); + +await buildOrWatchAll(buildConfigs); diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index deab86610..33168ff97 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -17,29 +17,30 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import esbuild from "esbuild"; +// @ts-check + import { readFileSync } from "fs"; import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises"; import { join } from "path"; import Zip from "zip-local"; -import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins, buildOrWatchAll, stringifyValues } from "./common.mjs"; /** - * @type {esbuild.BuildOptions} + * @type {import("esbuild").BuildOptions} */ const commonOptions = { ...commonOpts, entryPoints: ["browser/Vencord.ts"], - globalName: "Vencord", format: "iife", + globalName: "Vencord", external: ["~plugins", "~git-hash", "/assets/*"], + target: ["esnext"], plugins: [ globPlugins("web"), ...commonRendererPlugins ], - target: ["esnext"], - define: { + define: stringifyValues({ IS_WEB: true, IS_EXTENSION: false, IS_STANDALONE: true, @@ -48,9 +49,9 @@ const commonOptions = { IS_DISCORD_DESKTOP: false, IS_VESKTOP: false, IS_UPDATER_DISABLED: true, - VERSION: JSON.stringify(VERSION), + VERSION, BUILD_TIMESTAMP - } + }) }; const MonacoWorkerEntryPoints = [ @@ -58,70 +59,59 @@ const MonacoWorkerEntryPoints = [ "vs/editor/editor.worker.js" ]; -const RnNoiseFiles = [ - "dist/rnnoise.wasm", - "dist/rnnoise_simd.wasm", - "dist/rnnoise/workletProcessor.js", - "LICENSE" +/** @type {import("esbuild").BuildOptions[]} */ +const buildConfigs = [ + { + entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`), + bundle: true, + minify: true, + format: "iife", + outbase: "node_modules/monaco-editor/esm/", + outdir: "dist/vendor/monaco" + }, + { + entryPoints: ["browser/monaco.ts"], + bundle: true, + minify: true, + format: "iife", + outfile: "dist/vendor/monaco/index.js", + loader: { + ".ttf": "file" + } + }, + { + ...commonOptions, + outfile: "dist/browser.js", + footer: { js: "//# sourceURL=VencordWeb" } + }, + { + ...commonOptions, + outfile: "dist/extension.js", + define: { + ...commonOptions.define, + IS_EXTENSION: "true" + }, + footer: { js: "//# sourceURL=VencordWeb" } + }, + { + ...commonOptions, + inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], + define: { + ...commonOptions.define, + window: "unsafeWindow", + }, + outfile: "dist/Vencord.user.js", + banner: { + js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${VERSION}.${new Date().getTime()}`) + }, + footer: { + // UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local + js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});" + } + } ]; -await Promise.all( - [ - esbuild.build({ - entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`), - bundle: true, - minify: true, - format: "iife", - outbase: "node_modules/monaco-editor/esm/", - outdir: "dist/monaco" - }), - esbuild.build({ - entryPoints: ["browser/monaco.ts"], - bundle: true, - minify: true, - format: "iife", - outfile: "dist/monaco/index.js", - loader: { - ".ttf": "file" - } - }), - esbuild.build({ - ...commonOptions, - outfile: "dist/browser.js", - footer: { js: "//# sourceURL=VencordWeb" } - }), - esbuild.build({ - ...commonOptions, - outfile: "dist/extension.js", - define: { - ...commonOptions?.define, - IS_EXTENSION: true, - }, - footer: { js: "//# sourceURL=VencordWeb" } - }), - esbuild.build({ - ...commonOptions, - inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], - define: { - ...(commonOptions?.define), - window: "unsafeWindow", - }, - outfile: "dist/Vencord.user.js", - banner: { - js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${VERSION}.${new Date().getTime()}`) - }, - footer: { - // UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local - js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});" - } - }) - ] -).catch(err => { - console.error("Build failed"); - console.error(err.message); - if (!commonOpts.watch) - process.exit(1); -});; +await buildOrWatchAll(buildConfigs); /** * @type {(dir: string) => Promise<string[]>} @@ -155,16 +145,13 @@ async function buildExtension(target, files) { const entries = { "dist/Vencord.js": await readFile("dist/extension.js"), "dist/Vencord.css": await readFile("dist/extension.css"), - ...await loadDir("dist/monaco"), - ...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file => - [`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)] - ))), + ...await loadDir("dist/vendor/monaco", "dist/"), ...Object.fromEntries(await Promise.all(files.map(async f => { let content = await readFile(join("browser", f)); if (f.startsWith("manifest")) { const json = JSON.parse(content.toString("utf-8")); json.version = VERSION; - content = new TextEncoder().encode(JSON.stringify(json)); + content = Buffer.from(new TextEncoder().encode(JSON.stringify(json))); } return [ @@ -210,7 +197,6 @@ if (!process.argv.includes("--skip-extension")) { Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip"); console.info("Packed Firefox Extension written to dist/extension-firefox.zip"); - } else { await appendCssRuntime; } diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index e88f1e2b9..920e59267 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -16,11 +16,13 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +// @ts-check + import "../suppressExperimentalWarnings.js"; import "../checkNodeVersion.js"; import { exec, execSync } from "child_process"; -import esbuild from "esbuild"; +import esbuild, { build, context } from "esbuild"; import { constants as FsConstants, readFileSync } from "fs"; import { access, readdir, readFile } from "fs/promises"; import { minify as minifyHtml } from "html-minifier-terser"; @@ -31,7 +33,7 @@ import { getPluginTarget } from "../utils.mjs"; import { builtinModules } from "module"; /** @type {import("../../package.json")} */ -const PackageJSON = JSON.parse(readFileSync("package.json")); +const PackageJSON = JSON.parse(readFileSync("package.json", "utf-8")); export const VERSION = PackageJSON.version; // https://reproducible-builds.org/docs/source-date-epoch/ @@ -54,6 +56,34 @@ export const banner = { `.trim() }; +/** + * JSON.stringify all values in an object + * @type {(obj: Record<string, any>) => Record<string, string>} + */ +export function stringifyValues(obj) { + for (const key in obj) { + obj[key] = JSON.stringify(obj[key]); + } + return obj; +} + +/** + * @param {import("esbuild").BuildOptions[]} buildConfigs + */ +export async function buildOrWatchAll(buildConfigs) { + if (watch) { + await Promise.all(buildConfigs.map(cfg => + context(cfg).then(ctx => ctx.watch()) + )); + } else { + await Promise.all(buildConfigs.map(cfg => build(cfg))) + .catch(error => { + console.error(error.message); + process.exit(1); // exit immediately to skip the rest of the builds + }); + } +} + const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/; /** * @param {string} base @@ -311,18 +341,16 @@ export const banImportPlugin = (filter, message) => ({ export const commonOpts = { logLevel: "info", bundle: true, - watch, - minify: !watch, - sourcemap: watch ? "inline" : "", + minify: !watch && !IS_REPORTER, + sourcemap: watch ? "inline" : "external", legalComments: "linked", banner, plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin], external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"], inject: ["./scripts/build/inject/react.mjs"], + jsx: "transform", jsxFactory: "VencordCreateElement", - jsxFragment: "VencordFragment", - // Work around https://github.com/evanw/esbuild/issues/2460 - tsconfig: "./scripts/build/tsconfig.esbuild.json" + jsxFragment: "VencordFragment" }; const escapedBuiltinModules = builtinModules @@ -335,5 +363,6 @@ export const commonRendererPlugins = [ banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), + // @ts-ignore this is never undefined ...commonOpts.plugins ]; diff --git a/scripts/build/tsconfig.esbuild.json b/scripts/build/tsconfig.esbuild.json deleted file mode 100644 index e3e28a14d..000000000 --- a/scripts/build/tsconfig.esbuild.json +++ /dev/null @@ -1,7 +0,0 @@ -// Work around https://github.com/evanw/esbuild/issues/2460 -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "jsx": "react" - } -} diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 24af628bd..9502d382e 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -16,24 +16,27 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -/* eslint-disable no-fallthrough */ - -// eslint-disable-next-line spaced-comment /// <reference types="../src/globals" /> -// eslint-disable-next-line spaced-comment /// <reference types="../src/modules" /> +import { createHmac } from "crypto"; import { readFileSync } from "fs"; import pup, { JSHandle } from "puppeteer-core"; -for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) { +const logStderr = (...data: any[]) => console.error(`${CANARY ? "CANARY" : "STABLE"} ---`, ...data); + +for (const variable of ["CHROMIUM_BIN"]) { if (!process.env[variable]) { - console.error(`Missing environment variable ${variable}`); + logStderr(`Missing environment variable ${variable}`); process.exit(1); } } const CANARY = process.env.USE_CANARY === "true"; +let metaData = { + buildNumber: "Unknown Build Number", + buildHash: "Unknown Build Hash" +}; const browser = await pup.launch({ headless: true, @@ -51,14 +54,17 @@ async function maybeGetError(handle: JSHandle): Promise<string | undefined> { .catch(() => undefined); } +interface PatchInfo { + plugin: string; + type: string; + id: string; + match: string; + error?: string; +}; + const report = { - badPatches: [] as { - plugin: string; - type: string; - id: string; - match: string; - error?: string; - }[], + badPatches: [] as PatchInfo[], + slowPatches: [] as PatchInfo[], badStarts: [] as { plugin: string; error: string; @@ -128,56 +134,88 @@ async function printReport() { console.log(); - if (process.env.DISCORD_WEBHOOK) { - await fetch(process.env.DISCORD_WEBHOOK, { - method: "POST", - headers: { - "Content-Type": "application/json" + if (process.env.WEBHOOK_URL) { + const patchesToEmbed = (title: string, patches: PatchInfo[], color: number) => ({ + title, + color, + description: patches.map(p => { + const lines = [ + `**__${p.plugin} (${p.type}):__**`, + `ID: \`${p.id}\``, + `Match: ${toCodeBlock(p.match, "Match: ".length, true)}` + ]; + if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`); + + return lines.join("\n"); + }).join("\n\n"), + }); + + const embeds = [ + { + author: { + name: `Discord ${CANARY ? "Canary" : "Stable"} (${metaData.buildNumber})`, + url: `https://nelly.tools/builds/app/${metaData.buildHash}`, + icon_url: CANARY ? "https://cdn.discordapp.com/emojis/1252721945699549327.png?size=128" : "https://cdn.discordapp.com/emojis/1252721943463985272.png?size=128" + }, + color: CANARY ? 0xfbb642 : 0x5865f2 }, - body: JSON.stringify({ - description: "Here's the latest Vencord Report!", - username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""), - embeds: [ - { - title: "Bad Patches", - description: report.badPatches.map(p => { - const lines = [ - `**__${p.plugin} (${p.type}):__**`, - `ID: \`${p.id}\``, - `Match: ${toCodeBlock(p.match, "Match: ".length, true)}` - ]; - if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`); - return lines.join("\n"); - }).join("\n\n") || "None", - color: report.badPatches.length ? 0xff0000 : 0x00ff00 - }, - { - title: "Bad Webpack Finds", - description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None", - color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00 - }, - { - title: "Bad Starts", - description: report.badStarts.map(p => { - const lines = [ - `**__${p.plugin}:__**`, - toCodeBlock(p.error, 0, true) - ]; - return lines.join("\n"); - } - ).join("\n\n") || "None", - color: report.badStarts.length ? 0xff0000 : 0x00ff00 - }, - { - title: "Discord Errors", - description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None", - color: report.otherErrors.length ? 0xff0000 : 0x00ff00 - } - ] - }) + report.badPatches.length > 0 && patchesToEmbed("Bad Patches", report.badPatches, 0xff0000), + report.slowPatches.length > 0 && patchesToEmbed("Slow Patches", report.slowPatches, 0xf0b232), + report.badWebpackFinds.length > 0 && { + title: "Bad Webpack Finds", + description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None", + color: 0xff0000 + }, + report.badStarts.length > 0 && { + title: "Bad Starts", + description: report.badStarts.map(p => { + const lines = [ + `**__${p.plugin}:__**`, + toCodeBlock(p.error, 0, true) + ]; + return lines.join("\n"); + } + ).join("\n\n") || "None", + color: 0xff0000 + }, + report.otherErrors.length > 0 && { + title: "Discord Errors", + description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None", + color: 0xff0000 + } + ].filter(Boolean); + + if (embeds.length === 1) { + embeds.push({ + title: "No issues found", + description: "Seems like everything is working fine (for now) <:shipit:1330992641466433556>", + color: 0x00ff00 + }); + } + + const body = JSON.stringify({ + username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""), + embeds + }); + + const headers = { + "Content-Type": "application/json" + }; + + // functions similar to https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries + // used by venbot to ensure webhook invocations are genuine (since we will pass the webhook url as a workflow input which is publicly visible) + // generate a secret with something like `openssl rand -hex 128` + if (process.env.WEBHOOK_SECRET) { + headers["X-Signature"] = "sha256=" + createHmac("sha256", process.env.WEBHOOK_SECRET).update(body).digest("hex"); + } + + await fetch(process.env.WEBHOOK_URL, { + method: "POST", + headers, + body }).then(res => { - if (!res.ok) console.error(`Webhook failed with status ${res.status}`); - else console.error("Posted to Discord Webhook successfully"); + if (!res.ok) logStderr(`Webhook failed with status ${res.status}`); + else logStderr("Posted to Webhook successfully"); }); } } @@ -186,10 +224,13 @@ page.on("console", async e => { const level = e.type(); const rawArgs = e.args(); - async function getText() { + async function getText(skipFirst = true) { + let args = e.args(); + if (skipFirst) args = args.slice(1); + try { return await Promise.all( - e.args().map(async a => { + args.map(async a => { return await maybeGetError(a) || await a.jsonValue(); }) ).then(a => a.join(" ").trim()); @@ -202,6 +243,12 @@ page.on("console", async e => { const isVencord = firstArg === "[Vencord]"; const isDebug = firstArg === "[PUP_DEBUG]"; + const isReporterMeta = firstArg === "[REPORTER_META]"; + + if (isReporterMeta) { + metaData = await rawArgs[1].jsonValue() as any; + return; + } outer: if (isVencord) { @@ -215,18 +262,21 @@ page.on("console", async e => { switch (tag) { case "WebpackInterceptor:": - const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!; - if (!patchFailMatch) break; + const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/); + const patchSlowMatch = message.match(/Patch by (.+?) (took [\d.]+?ms) \(Module id is (.+?)\): (.+)/); + const match = patchFailMatch ?? patchSlowMatch; + if (!match) break; - console.error(await getText()); + logStderr(await getText()); process.exitCode = 1; - const [, plugin, type, id, regex] = patchFailMatch; - report.badPatches.push({ + const [, plugin, type, id, regex] = match; + const list = patchFailMatch ? report.badPatches : report.slowPatches; + list.push({ plugin, type, id, - match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"), + match: regex, error: await maybeGetError(e.args()[3]) }); @@ -235,7 +285,7 @@ page.on("console", async e => { const failedToStartMatch = message.match(/Failed to start (.+)/); if (!failedToStartMatch) break; - console.error(await getText()); + logStderr(await getText()); process.exitCode = 1; const [, name] = failedToStartMatch; @@ -246,7 +296,7 @@ page.on("console", async e => { break; case "LazyChunkLoader:": - console.error(await getText()); + logStderr(await getText()); switch (message) { case "A fatal error occurred:": @@ -255,7 +305,7 @@ page.on("console", async e => { break; case "Reporter:": - console.error(await getText()); + logStderr(await getText()); switch (message) { case "A fatal error occurred:": @@ -273,47 +323,36 @@ page.on("console", async e => { } if (isDebug) { - console.error(await getText()); + logStderr(await getText()); } else if (level === "error") { - const text = await getText(); + const text = await getText(false); if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) { if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) { report.ignoredErrors.push(text); } else { - console.error("[Unexpected Error]", text); + logStderr("[Unexpected Error]", text); report.otherErrors.push(text); } } } }); -page.on("error", e => console.error("[Error]", e.message)); +page.on("error", e => logStderr("[Error]", e.message)); page.on("pageerror", e => { if (e.message.includes("Sentry successfully disabled")) return; - if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) { - console.error("[Page Error]", e.message); + if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) { + logStderr("[Page Error]", e.message); report.otherErrors.push(e.message); } else { report.ignoredErrors.push(e.message); } }); -async function reporterRuntime(token: string) { - Vencord.Webpack.waitFor( - "loginToken", - m => { - console.log("[PUP_DEBUG]", "Logging in with token..."); - m.loginToken(token); - } - ); -} - await page.evaluateOnNewDocument(` if (location.host.endsWith("discord.com")) { ${readFileSync("./dist/browser.js", "utf-8")}; - (${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)}); } `); diff --git a/src/Vencord.ts b/src/Vencord.ts index c4c6d4705..63508eb06 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -23,6 +23,7 @@ export * as Util from "./utils"; export * as QuickCss from "./utils/quickCss"; export * as Updater from "./utils/updater"; export * as Webpack from "./webpack"; +export * as WebpackPatcher from "./webpack/patchWebpack"; export { PlainSettings, Settings }; import "./utils/quickCss"; diff --git a/src/VencordNative.ts b/src/VencordNative.ts index 42e697452..3bed5a592 100644 --- a/src/VencordNative.ts +++ b/src/VencordNative.ts @@ -4,11 +4,11 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import type { Settings } from "@api/Settings"; import { PluginIpcMappings } from "@main/ipcPlugins"; import type { UserThemeHeader } from "@main/themes"; import { IpcEvents } from "@shared/IpcEvents"; import { IpcRes } from "@utils/types"; -import type { Settings } from "api/Settings"; import { ipcRenderer } from "electron"; function invoke<T = any>(event: IpcEvents, ...args: any[]) { diff --git a/src/api/Badges.ts b/src/api/Badges.ts index 7a041f1ee..ee2f3a30c 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -57,7 +57,7 @@ const Badges = new Set<ProfileBadge>(); * Register a new badge with the Badges API * @param badge The badge to register */ -export function addBadge(badge: ProfileBadge) { +export function addProfileBadge(badge: ProfileBadge) { badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true }); Badges.add(badge); } @@ -66,7 +66,7 @@ export function addBadge(badge: ProfileBadge) { * Unregister a badge from the Badges API * @param badge The badge to remove */ -export function removeBadge(badge: ProfileBadge) { +export function removeProfileBadge(badge: ProfileBadge) { return Badges.delete(badge); } @@ -100,20 +100,3 @@ export interface BadgeUserArgs { userId: string; guildId: string; } - -interface ConnectedAccount { - type: string; - id: string; - name: string; - verified: boolean; -} - -interface Profile { - connectedAccounts: ConnectedAccount[]; - premiumType: number; - premiumSince: string; - premiumGuildSince?: any; - lastFetched: number; - profileFetchFailed: boolean; - application?: any; -} diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index d38f4ff50..6f4285ff5 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -9,7 +9,7 @@ import "./ChatButton.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; import { waitFor } from "@webpack"; -import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; +import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; @@ -74,9 +74,9 @@ export interface ChatBarProps { }; } -export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null; +export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null; -const buttonFactories = new Map<string, ChatBarButton>(); +const buttonFactories = new Map<string, ChatBarButtonFactory>(); const logger = new Logger("ChatButtons"); export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) { @@ -91,7 +91,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) { } } -export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button); +export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button); export const removeChatBarButton = (id: string) => buttonFactories.delete(id); export interface ChatBarButtonProps { @@ -110,7 +110,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => { <Button aria-label={props.tooltip} size="" - look={ButtonLooks.BLANK} + look={Button.Looks.BLANK} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`} diff --git a/src/api/ContextMenu.ts b/src/api/ContextMenu.ts index 4a1627471..a632aea6f 100644 --- a/src/api/ContextMenu.ts +++ b/src/api/ContextMenu.ts @@ -122,7 +122,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra } interface ContextMenuProps { - contextMenuApiArguments?: Array<any>; + contextMenuAPIArguments?: Array<any>; navId: string; children: Array<ReactElement<any> | null>; "aria-label": string; @@ -136,7 +136,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) { children: cloneMenuChildren(props.children), }; - props.contextMenuApiArguments ??= []; + props.contextMenuAPIArguments ??= []; const contextMenuPatches = navPatches.get(props.navId); if (!Array.isArray(props.children)) props.children = [props.children]; @@ -144,7 +144,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) { if (contextMenuPatches) { for (const patch of contextMenuPatches) { try { - patch(props.children, ...props.contextMenuApiArguments); + patch(props.children, ...props.contextMenuAPIArguments); } catch (err) { ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err); } @@ -153,7 +153,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) { for (const patch of globalPatches) { try { - patch(props.navId, props.children, ...props.contextMenuApiArguments); + patch(props.navId, props.children, ...props.contextMenuAPIArguments); } catch (err) { ContextMenuLogger.error("Global patch errored,", err); } diff --git a/src/api/MemberListDecorators.ts b/src/api/MemberListDecorators.tsx similarity index 52% rename from src/api/MemberListDecorators.ts rename to src/api/MemberListDecorators.tsx index ba5ec8d14..ab5a618bf 100644 --- a/src/api/MemberListDecorators.ts +++ b/src/api/MemberListDecorators.tsx @@ -16,6 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import ErrorBoundary from "@components/ErrorBoundary"; import { Channel, User } from "discord-types/general/index.js"; import { JSX } from "react"; @@ -39,27 +40,39 @@ interface DecoratorProps { user: User; [key: string]: any; } -export type Decorator = (props: DecoratorProps) => JSX.Element | null; +export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null; type OnlyIn = "guilds" | "dms"; -export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>(); +export const decoratorsFactories = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>(); -export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) { - decorators.set(identifier, { decorator, onlyIn }); +export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) { + decoratorsFactories.set(identifier, { render, onlyIn }); } -export function removeDecorator(identifier: string) { - decorators.delete(identifier); +export function removeMemberListDecorator(identifier: string) { + decoratorsFactories.delete(identifier); } -export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] { +export function __getDecorators(props: DecoratorProps): JSX.Element { const isInGuild = !!(props.guildId); - return Array.from(decorators.values(), decoratorObj => { - const { decorator, onlyIn } = decoratorObj; - // this can most likely be done cleaner - if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) { - return decorator(props); + + const decorators = Array.from( + decoratorsFactories.entries(), + ([key, { render: Decorator, onlyIn }]) => { + if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild)) + return null; + + return ( + <ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}> + <Decorator {...props} /> + </ErrorBoundary> + ); } - return null; - }); + ); + + return ( + <div className="vc-member-list-decorators-wrapper"> + {decorators} + </div> + ); } diff --git a/src/api/MessageAccessories.ts b/src/api/MessageAccessories.tsx similarity index 63% rename from src/api/MessageAccessories.ts rename to src/api/MessageAccessories.tsx index 8454732f4..71664e93a 100644 --- a/src/api/MessageAccessories.ts +++ b/src/api/MessageAccessories.tsx @@ -16,28 +16,29 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { JSX } from "react"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { JSX, ReactNode } from "react"; -export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>; -export type Accessory = { - callback: AccessoryCallback; +export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode; +export type MessageAccessory = { + render: MessageAccessoryFactory; position?: number; }; -export const accessories = new Map<String, Accessory>(); +export const accessories = new Map<string, MessageAccessory>(); -export function addAccessory( +export function addMessageAccessory( identifier: string, - callback: AccessoryCallback, + render: MessageAccessoryFactory, position?: number ) { accessories.set(identifier, { - callback, + render, position, }); } -export function removeAccessory(identifier: string) { +export function removeMessageAccessory(identifier: string) { accessories.delete(identifier); } @@ -45,15 +46,12 @@ export function _modifyAccessories( elements: JSX.Element[], props: Record<string, any> ) { - for (const accessory of accessories.values()) { - let accessories = accessory.callback(props); - if (accessories == null) - continue; - - if (!Array.isArray(accessories)) - accessories = [accessories]; - else if (accessories.length === 0) - continue; + for (const [key, accessory] of accessories.entries()) { + const res = ( + <ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}> + <accessory.render {...props} /> + </ErrorBoundary> + ); elements.splice( accessory.position != null @@ -62,7 +60,7 @@ export function _modifyAccessories( : accessory.position : elements.length, 0, - ...accessories.filter(e => e != null) as JSX.Element[] + res ); } diff --git a/src/api/MessageDecorations.ts b/src/api/MessageDecorations.tsx similarity index 58% rename from src/api/MessageDecorations.ts rename to src/api/MessageDecorations.tsx index 0d69ab11c..1b94c18d9 100644 --- a/src/api/MessageDecorations.ts +++ b/src/api/MessageDecorations.tsx @@ -16,10 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import ErrorBoundary from "@components/ErrorBoundary"; import { Channel, Message } from "discord-types/general/index.js"; import { JSX } from "react"; -interface DecorationProps { +export interface MessageDecorationProps { author: { /** * Will be username if the user has no nickname @@ -45,20 +46,31 @@ interface DecorationProps { message: Message; [key: string]: any; } -export type Decoration = (props: DecorationProps) => JSX.Element | null; +export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null; -export const decorations = new Map<string, Decoration>(); +export const decorationsFactories = new Map<string, MessageDecorationFactory>(); -export function addDecoration(identifier: string, decoration: Decoration) { - decorations.set(identifier, decoration); +export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) { + decorationsFactories.set(identifier, decoration); } -export function removeDecoration(identifier: string) { - decorations.delete(identifier); +export function removeMessageDecoration(identifier: string) { + decorationsFactories.delete(identifier); } -export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] { - return [...decorations.values()].map(decoration => { - return decoration(props); - }); +export function __addDecorationsToMessage(props: MessageDecorationProps): JSX.Element { + const decorations = Array.from( + decorationsFactories.entries(), + ([key, Decoration]) => ( + <ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}> + <Decoration {...props} /> + </ErrorBoundary> + ) + ); + + return ( + <div className="vc-message-decorations-wrapper"> + {decorations} + </div> + ); } diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index d6eba748f..1b55ff340 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -73,11 +73,11 @@ export interface MessageExtra { openWarningPopout: (props: any) => any; } -export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>; -export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>; +export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>; +export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>; -const sendListeners = new Set<SendListener>(); -const editListeners = new Set<EditListener>(); +const sendListeners = new Set<MessageSendListener>(); +const editListeners = new Set<MessageEditListener>(); export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) { extra.replyOptions = replyOptions; @@ -111,29 +111,29 @@ export async function _handlePreEdit(channelId: string, messageId: string, messa /** * Note: This event fires off before a message is sent, allowing you to edit the message. */ -export function addPreSendListener(listener: SendListener) { +export function addMessagePreSendListener(listener: MessageSendListener) { sendListeners.add(listener); return listener; } /** * Note: This event fires off before a message's edit is applied, allowing you to further edit the message. */ -export function addPreEditListener(listener: EditListener) { +export function addMessagePreEditListener(listener: MessageEditListener) { editListeners.add(listener); return listener; } -export function removePreSendListener(listener: SendListener) { +export function removeMessagePreSendListener(listener: MessageSendListener) { return sendListeners.delete(listener); } -export function removePreEditListener(listener: EditListener) { +export function removeMessagePreEditListener(listener: MessageEditListener) { return editListeners.delete(listener); } // Message clicks -type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void; +export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void; -const listeners = new Set<ClickListener>(); +const listeners = new Set<MessageClickListener>(); export function _handleClick(message: Message, channel: Channel, event: MouseEvent) { // message object may be outdated, so (try to) fetch latest one @@ -147,11 +147,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve } } -export function addClickListener(listener: ClickListener) { +export function addMessageClickListener(listener: MessageClickListener) { listeners.add(listener); return listener; } -export function removeClickListener(listener: ClickListener) { +export function removeMessageClickListener(listener: MessageClickListener) { return listeners.delete(listener); } diff --git a/src/api/MessagePopover.tsx b/src/api/MessagePopover.tsx index eb68ed2d6..717879546 100644 --- a/src/api/MessagePopover.tsx +++ b/src/api/MessagePopover.tsx @@ -23,7 +23,7 @@ import type { ComponentType, MouseEventHandler } from "react"; const logger = new Logger("MessagePopover"); -export interface ButtonItem { +export interface MessagePopoverButtonItem { key?: string, label: string, icon: ComponentType<any>, @@ -33,23 +33,23 @@ export interface ButtonItem { onContextMenu?: MouseEventHandler<HTMLButtonElement>; } -export type getButtonItem = (message: Message) => ButtonItem | null; +export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null; -export const buttons = new Map<string, getButtonItem>(); +export const buttons = new Map<string, MessagePopoverButtonFactory>(); -export function addButton( +export function addMessagePopoverButton( identifier: string, - item: getButtonItem, + item: MessagePopoverButtonFactory, ) { buttons.set(identifier, item); } -export function removeButton(identifier: string) { +export function removeMessagePopoverButton(identifier: string) { buttons.delete(identifier); } export function _buildPopoverElements( - Component: React.ComponentType<ButtonItem>, + Component: React.ComponentType<MessagePopoverButtonItem>, message: Message ) { const items: React.ReactNode[] = []; diff --git a/src/api/ServerList.ts b/src/api/ServerList.tsx similarity index 64% rename from src/api/ServerList.ts rename to src/api/ServerList.tsx index 462745b04..7a673c9df 100644 --- a/src/api/ServerList.ts +++ b/src/api/ServerList.tsx @@ -16,41 +16,36 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Logger } from "@utils/Logger"; -import { JSX } from "react"; - -const logger = new Logger("ServerListAPI"); +import ErrorBoundary from "@components/ErrorBoundary"; +import { ComponentType } from "react"; export const enum ServerListRenderPosition { Above, In, } -const renderFunctionsAbove = new Set<Function>(); -const renderFunctionsIn = new Set<Function>(); +const componentsAbove = new Set<ComponentType>(); +const componentsBelow = new Set<ComponentType>(); function getRenderFunctions(position: ServerListRenderPosition) { - return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn; + return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow; } -export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) { +export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) { getRenderFunctions(position).add(renderFunction); } -export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) { +export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) { getRenderFunctions(position).delete(renderFunction); } export const renderAll = (position: ServerListRenderPosition) => { - const ret: Array<JSX.Element> = []; - - for (const renderFunction of getRenderFunctions(position)) { - try { - ret.unshift(renderFunction()); - } catch (e) { - logger.error("Failed to render server list element:", e); - } - } - - return ret; + return Array.from( + getRenderFunctions(position), + (Component, i) => ( + <ErrorBoundary noop key={i}> + <Component /> + </ErrorBoundary> + ) + ); }; diff --git a/src/api/Settings.ts b/src/api/Settings.ts index c99d030d0..08d2f8cac 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -32,9 +32,10 @@ export interface Settings { autoUpdate: boolean; autoUpdateNotification: boolean, useQuickCss: boolean; + eagerPatches: boolean; + enabledThemes: string[]; enableReactDevtools: boolean; themeLinks: string[]; - enabledThemes: string[]; frameless: boolean; transparent: boolean; winCtrlQ: boolean; @@ -81,6 +82,7 @@ const DefaultSettings: Settings = { autoUpdateNotification: true, useQuickCss: true, themeLinks: [], + eagerPatches: IS_REPORTER, enabledThemes: [], enableReactDevtools: false, frameless: false, @@ -220,6 +222,17 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) { } } +export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) { + const settings = SettingsStore.plain.plugins[pluginName]; + if (!settings) return; + + if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return; + + settings[newSetting] = settings[oldSetting]; + delete settings[oldSetting]; + SettingsStore.markAsChanged(); +} + export function definePluginSettings< Def extends SettingsDefinition, Checks extends SettingsChecks<Def>, diff --git a/src/components/DonateButton.tsx b/src/components/DonateButton.tsx index c027fcf27..ee2f3ed38 100644 --- a/src/components/DonateButton.tsx +++ b/src/components/DonateButton.tsx @@ -17,16 +17,22 @@ */ import { Button } from "@webpack/common"; +import { ButtonProps } from "@webpack/types"; import { Heart } from "./Heart"; -export default function DonateButton(props: any) { +export default function DonateButton({ + look = Button.Looks.LINK, + color = Button.Colors.TRANSPARENT, + ...props +}: Partial<ButtonProps>) { return ( <Button {...props} - look={Button.Looks.LINK} - color={Button.Colors.TRANSPARENT} + look={look} + color={color} onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")} + innerClassName="vc-donate-button" > <Heart /> Donate diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 60ff1faf2..bb2df3421 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => { componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps }); - logger.error("A component threw an Error\n", error); - logger.error("Component Stack", errorInfo.componentStack); + logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack); } render() { diff --git a/src/components/Heart.tsx b/src/components/Heart.tsx index 017b41645..600a4c723 100644 --- a/src/components/Heart.tsx +++ b/src/components/Heart.tsx @@ -16,14 +16,18 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -export function Heart() { +import { classes } from "@utils/misc"; +import { SVGProps } from "react"; + +export function Heart(props: SVGProps<SVGSVGElement>) { return ( <svg aria-hidden="true" - height="16" viewBox="0 0 16 16" + height="16" width="16" - style={{ marginRight: "0.5em", transform: "translateY(2px)" }} + {...props} + className={classes("vc-heart-icon", props.className)} > <path fill="#db61a2" diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 4dc92e42a..7baeba081 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -37,6 +37,7 @@ import { Constructor } from "type-fest"; import { PluginMeta } from "~plugins"; import { + ISettingCustomElementProps, ISettingElementProps, SettingBooleanComponent, SettingCustomComponent, @@ -74,14 +75,15 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; } return newUser; } -const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = { +const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = { [OptionType.STRING]: SettingTextComponent, [OptionType.NUMBER]: SettingNumericComponent, [OptionType.BIGINT]: SettingNumericComponent, [OptionType.BOOLEAN]: SettingBooleanComponent, [OptionType.SELECT]: SettingSelectComponent, [OptionType.SLIDER]: SettingSliderComponent, - [OptionType.COMPONENT]: SettingCustomComponent + [OptionType.COMPONENT]: SettingCustomComponent, + [OptionType.CUSTOM]: () => null, }; export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) { @@ -129,7 +131,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti for (const [key, value] of Object.entries(tempSettings)) { const option = plugin.options[key]; pluginSettings[key] = value; - option?.onChange?.(value); + + if (option.type === OptionType.CUSTOM) continue; if (option?.restartNeeded) restartNeeded = true; } if (restartNeeded) onRestartNeeded(); @@ -141,7 +144,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>; } else { const options = Object.entries(plugin.options).map(([key, setting]) => { - if (setting.hidden) return null; + if (setting.type === OptionType.CUSTOM || setting.hidden) return null; function onChange(newValue: any) { setTempSettings(s => ({ ...s, [key]: newValue })); diff --git a/src/components/PluginSettings/components/SettingCustomComponent.tsx b/src/components/PluginSettings/components/SettingCustomComponent.tsx index af7192f3f..25e8c9c6a 100644 --- a/src/components/PluginSettings/components/SettingCustomComponent.tsx +++ b/src/components/PluginSettings/components/SettingCustomComponent.tsx @@ -18,8 +18,8 @@ import { PluginOptionComponent } from "@utils/types"; -import { ISettingElementProps } from "."; +import { ISettingCustomElementProps } from "."; -export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) { +export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) { return option.component({ setValue: onChange, setError: onError, option }); } diff --git a/src/components/PluginSettings/components/index.ts b/src/components/PluginSettings/components/index.ts index d307b4e68..c38f209b7 100644 --- a/src/components/PluginSettings/components/index.ts +++ b/src/components/PluginSettings/components/index.ts @@ -18,7 +18,7 @@ import { DefinedSettings, PluginOptionBase } from "@utils/types"; -export interface ISettingElementProps<T extends PluginOptionBase> { +interface ISettingElementPropsBase<T> { option: T; onChange(newValue: any): void; pluginSettings: { @@ -30,6 +30,9 @@ export interface ISettingElementProps<T extends PluginOptionBase> { definedSettings?: DefinedSettings; } +export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>; +export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>; + export * from "../../Badge"; export * from "./SettingBooleanComponent"; export * from "./SettingCustomComponent"; diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 959a4d01e..371c3082c 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -69,7 +69,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) { <Forms.FormText className={cl("dep-text")}> Restart now to apply new plugins and their settings </Forms.FormText> - <Button onClick={() => location.reload()}> + <Button onClick={() => location.reload()} className={cl("restart-button")}> Restart </Button> </> @@ -158,8 +158,8 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on className={classes(ButtonClasses.button, cl("info-button"))} > {plugin.options && !isObjectEmpty(plugin.options) - ? <CogWheel /> - : <InfoIcon />} + ? <CogWheel className={cl("info-icon")} /> + : <InfoIcon className={cl("info-icon")} />} </button> } /> diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index d3d182e58..a4f9aeee1 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/PluginSettings/styles.css @@ -63,10 +63,7 @@ height: 8em; display: flex; flex-direction: column; -} - -.vc-plugins-info-card div { - line-height: 32px; + gap: 0.25em; } .vc-plugins-restart-card { @@ -76,11 +73,11 @@ color: var(--info-warning-text); } -.vc-plugins-restart-card button { +.vc-plugins-restart-button { margin-top: 0.5em; background: var(--info-warning-foreground) !important; } -.vc-plugins-info-button svg:not(:hover, :focus) { +.vc-plugins-info-icon:not(:hover, :focus) { color: var(--text-muted); } diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index f3a8e1ddf..f930a40d2 100644 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -65,7 +65,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError } const canonicalMatch = canonicalizeMatch(new RegExp(match)); try { - const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin"); + const canonicalReplace = canonicalizeReplace(replacement, 'Vencord.Plugins.plugins["YourPlugin"]'); var patched = src.replace(canonicalMatch, canonicalReplace as string); setReplacementError(void 0); } catch (e) { diff --git a/src/components/VencordSettings/SpecialCard.tsx b/src/components/VencordSettings/SpecialCard.tsx new file mode 100644 index 000000000..6fd952f44 --- /dev/null +++ b/src/components/VencordSettings/SpecialCard.tsx @@ -0,0 +1,77 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import "./specialCard.css"; + +import { classNameFactory } from "@api/Styles"; +import { Card, Clickable, Forms, React } from "@webpack/common"; +import type { PropsWithChildren } from "react"; + +const cl = classNameFactory("vc-special-"); + +interface StyledCardProps { + title: string; + subtitle?: string; + description: string; + cardImage?: string; + backgroundImage?: string; + backgroundColor?: string; + buttonTitle?: string; + buttonOnClick?: () => void; +} + +export function SpecialCard({ title, subtitle, description, cardImage, backgroundImage, backgroundColor, buttonTitle, buttonOnClick: onClick, children }: PropsWithChildren<StyledCardProps>) { + const cardStyle: React.CSSProperties = { + backgroundColor: backgroundColor || "#9c85ef", + backgroundImage: `url(${backgroundImage || ""})`, + }; + + return ( + <Card className={cl("card", "card-special")} style={cardStyle}> + <div className={cl("card-flex")}> + <div className={cl("card-flex-main")}> + <Forms.FormTitle className={cl("title")} tag="h5">{title}</Forms.FormTitle> + <Forms.FormText className={cl("subtitle")}>{subtitle}</Forms.FormText> + <Forms.FormText className={cl("text")}>{description}</Forms.FormText> + + {children} + </div> + {cardImage && ( + <div className={cl("image-container")}> + <img + role="presentation" + src={cardImage} + alt="" + className={cl("image")} + /> + </div> + )} + </div> + {buttonTitle && ( + <> + <Forms.FormDivider className={cl("seperator")} /> + <Clickable onClick={onClick} className={cl("hyperlink")}> + <Forms.FormText className={cl("hyperlink-text")}> + {buttonTitle} + </Forms.FormText> + </Clickable> + </> + )} + </Card> + ); +} diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index 97f82e777..24fd0fb6d 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -20,29 +20,38 @@ import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import DonateButton from "@components/DonateButton"; +import { openContributorModal } from "@components/PluginSettings/ContributorModal"; import { openPluginModal } from "@components/PluginSettings/PluginModal"; import { gitRemote } from "@shared/vencordUserAgent"; +import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants"; import { Margins } from "@utils/margins"; -import { identity } from "@utils/misc"; +import { identity, isPluginDev } from "@utils/misc"; import { relaunch, showItemInFolder } from "@utils/native"; import { useAwaiter } from "@utils/react"; -import { Button, Card, Forms, React, Select, Switch } from "@webpack/common"; +import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common"; +import BadgeAPI from "../../plugins/_api/badges"; import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from ".."; import { openNotificationSettingsModal } from "./NotificationSettings"; import { QuickAction, QuickActionCard } from "./quickActions"; import { SettingsTab, wrapTab } from "./shared"; +import { SpecialCard } from "./SpecialCard"; const cl = classNameFactory("vc-settings-"); const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png"; const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png"; +const VENNIE_DONATOR_IMAGE = "https://cdn.discordapp.com/emojis/1238120638020063377.png"; +const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1026533070955872337.png"; + +const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048"; +const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048"; + type KeysOfType<Object, Type> = { [K in keyof Object]: Object[K] extends Type ? K : never; }[keyof Object]; - function VencordSettings() { const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, { fallbackValue: "Loading..." @@ -55,6 +64,8 @@ function VencordSettings() { const isMac = navigator.platform.toLowerCase().startsWith("mac"); const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac; + const user = UserStore.getCurrentUser(); + const Switches: Array<false | { key: KeysOfType<typeof settings, boolean>; title: string; @@ -99,7 +110,44 @@ function VencordSettings() { return ( <SettingsTab title="Vencord Settings"> - <DonateCard image={donateImage} /> + {isDonor(user?.id) + ? ( + <SpecialCard + title="Donations" + subtitle="Thank you for donating!" + description="All Vencord users can see your badge! You can change it at any time by messaging @vending.machine." + cardImage={VENNIE_DONATOR_IMAGE} + backgroundImage={DONOR_BACKGROUND_IMAGE} + backgroundColor="#ED87A9" + > + <DonateButtonComponent /> + </SpecialCard> + ) + : ( + <SpecialCard + title="Support the Project" + description="Please consider supporting the development of Vencord by donating!" + cardImage={donateImage} + backgroundImage={DONOR_BACKGROUND_IMAGE} + backgroundColor="#c3a3ce" + > + <DonateButtonComponent /> + </SpecialCard> + ) + } + {isPluginDev(user?.id) && ( + <SpecialCard + title="Contributions" + subtitle="Thank you for contributing!" + description="Since you've contributed to Vencord you now have a cool new badge!" + cardImage={COZY_CONTRIB_IMAGE} + backgroundImage={CONTRIB_BACKGROUND_IMAGE} + backgroundColor="#EDCC87" + buttonTitle="See what you've contributed to" + buttonOnClick={() => openContributorModal(user)} + /> + )} + <Forms.FormSection title="Quick Actions"> <QuickActionCard> <QuickAction @@ -239,31 +287,19 @@ function VencordSettings() { ); } -interface DonateCardProps { - image: string; -} - -function DonateCard({ image }: DonateCardProps) { +function DonateButtonComponent() { return ( - <Card className={cl("card", "donate")}> - <div> - <Forms.FormTitle tag="h5">Support the Project</Forms.FormTitle> - <Forms.FormText>Please consider supporting the development of Vencord by donating!</Forms.FormText> - <DonateButton style={{ transform: "translateX(-1em)" }} /> - </div> - <img - role="presentation" - src={image} - alt="" - height={128} - style={{ - imageRendering: image === SHIGGY_DONATE_IMAGE ? "pixelated" : void 0, - marginLeft: "auto", - transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : void 0 - }} - /> - </Card> + <DonateButton + look={Button.Looks.FILLED} + color={Button.Colors.WHITE} + style={{ marginTop: "1em" }} + /> ); } +function isDonor(userId: string): boolean { + const donorBadges = BadgeAPI.getDonorBadges(userId); + return GuildMemberStore.getMember(VENCORD_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID) || !!donorBadges; +} + export default wrapTab(VencordSettings, "Vencord Settings"); diff --git a/src/components/VencordSettings/quickActions.css b/src/components/VencordSettings/quickActions.css index 471c394ee..b8bc00718 100644 --- a/src/components/VencordSettings/quickActions.css +++ b/src/components/VencordSettings/quickActions.css @@ -1,12 +1,17 @@ .vc-settings-quickActions-card { display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, max-content)); + grid-template-columns: repeat(3, 1fr); gap: 0.5em; - justify-content: center; - padding: 0.5em 0; + padding: 0.5em; margin-bottom: 1em; } +@media (width <=1040px) { + .vc-settings-quickActions-card { + grid-template-columns: repeat(2, 1fr); + } +} + .vc-settings-quickActions-pill { all: unset; background: var(--background-secondary); @@ -14,12 +19,16 @@ display: flex; align-items: center; gap: 0.5em; - padding: 8px 12px; - border-radius: 9999px; + padding: 8px 9px; + border-radius: 8px; + transition: 0.1s ease-out; + box-sizing: border-box; } .vc-settings-quickActions-pill:hover { background: var(--background-secondary-alt); + transform: translateY(-1px); + box-shadow: var(--elevation-high); } .vc-settings-quickActions-pill:focus-visible { @@ -30,4 +39,4 @@ .vc-settings-quickActions-img { width: 24px; height: 24px; -} +} \ No newline at end of file diff --git a/src/components/VencordSettings/specialCard.css b/src/components/VencordSettings/specialCard.css new file mode 100644 index 000000000..07b628f56 --- /dev/null +++ b/src/components/VencordSettings/specialCard.css @@ -0,0 +1,92 @@ +.vc-donate-button { + overflow: visible !important; +} + +.vc-donate-button .vc-heart-icon { + transition: transform 0.3s; +} + +.vc-donate-button:hover .vc-heart-icon { + transform: scale(1.1); + z-index: 10; + position: relative; +} + +.vc-settings-card { + padding: 1em; + margin-bottom: 1em; +} + +.vc-special-card-special { + padding: 1em 1.5em; + margin-bottom: 1em; + background-size: cover; + background-position: center; +} + +.vc-special-card-flex { + display: flex; + flex-direction: row; +} + +.vc-special-card-flex-main { + width: 100%; +} + +.vc-special-title { + color: black; +} + +.vc-special-subtitle { + color: black; + font-size: 1.2em; + font-weight: bold; + margin-top: 0.5em; +} + +.vc-special-text { + color: black; + font-size: 1em; + margin-top: .75em; + white-space: pre-line; +} + +.vc-special-seperator { + margin-top: .75em; + border-top: 1px solid white; + opacity: 0.4; +} + +.vc-special-hyperlink { + margin-top: 1em; + cursor: pointer; + + .vc-special-hyperlink-text { + color: black; + font-size: 1em; + font-weight: bold; + text-align: center; + transition: text-decoration 0.5s; + cursor: pointer; + } + + &:hover .vc-special-hyperlink-text { + text-decoration: underline; + } +} + +.vc-special-image-container { + display: flex; + justify-content: center; + align-items: center; + margin-left: 1em; + flex-shrink: 0; + width: 100px; + height: 100px; + border-radius: 50%; + background-color: white; +} + +.vc-special-image { + width: 65%; +} diff --git a/src/components/iconStyles.css b/src/components/iconStyles.css index ca4075da0..e6d49a263 100644 --- a/src/components/iconStyles.css +++ b/src/components/iconStyles.css @@ -5,3 +5,8 @@ .vc-owner-crown-icon { color: var(--text-warning); } + +.vc-heart-icon { + margin-right: 0.5em; + translate: 0 2px; +} diff --git a/src/debug/Tracer.ts b/src/debug/Tracer.ts index 7d80f425c..37ea4cc05 100644 --- a/src/debug/Tracer.ts +++ b/src/debug/Tracer.ts @@ -23,35 +23,61 @@ if (IS_DEV || IS_REPORTER) { var logger = new Logger("Tracer", "#FFD166"); } -const noop = function () { }; - -export const beginTrace = !(IS_DEV || IS_REPORTER) ? noop : +export const beginTrace = !(IS_DEV || IS_REPORTER) ? () => { } : function beginTrace(name: string, ...args: any[]) { - if (name in traces) + if (name in traces) { throw new Error(`Trace ${name} already exists!`); + } traces[name] = [performance.now(), args]; }; -export const finishTrace = !(IS_DEV || IS_REPORTER) ? noop : function finishTrace(name: string) { - const end = performance.now(); +export const finishTrace = !(IS_DEV || IS_REPORTER) ? () => 0 : + function finishTrace(name: string) { + const end = performance.now(); - const [start, args] = traces[name]; - delete traces[name]; + const [start, args] = traces[name]; + delete traces[name]; - logger.debug(`${name} took ${end - start}ms`, args); -}; + const totalTime = end - start; + logger.debug(`${name} took ${totalTime}ms`, args); + + return totalTime; + }; type Func = (...args: any[]) => any; type TraceNameMapper<F extends Func> = (...args: Parameters<F>) => string; -const noopTracer = - <F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) => f; +function noopTracerWithResults<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) { + return function (this: unknown, ...args: Parameters<F>): [ReturnType<F>, number] { + return [f.apply(this, args), 0]; + }; +} + +function noopTracer<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) { + return f; +} + +export const traceFunctionWithResults = !(IS_DEV || IS_REPORTER) + ? noopTracerWithResults + : function traceFunctionWithResults<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): (this: unknown, ...args: Parameters<F>) => [ReturnType<F>, number] { + return function (this: unknown, ...args: Parameters<F>) { + const traceName = mapper?.(...args) ?? name; + + beginTrace(traceName, ...arguments); + try { + return [f.apply(this, args), finishTrace(traceName)]; + } catch (e) { + finishTrace(traceName); + throw e; + } + }; + }; export const traceFunction = !(IS_DEV || IS_REPORTER) ? noopTracer : function traceFunction<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): F { - return function (this: any, ...args: Parameters<F>) { + return function (this: unknown, ...args: Parameters<F>) { const traceName = mapper?.(...args) ?? name; beginTrace(traceName, ...arguments); diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index c7f8047db..f8c71caea 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -8,23 +8,26 @@ import { Logger } from "@utils/Logger"; import { canonicalizeMatch } from "@utils/patches"; import * as Webpack from "@webpack"; import { wreq } from "@webpack"; - -const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); +import { AnyModuleFactory, ModuleFactory } from "@webpack/wreq.d"; export async function loadLazyChunks() { + const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); + try { LazyChunkLoaderLogger.log("Loading all chunks..."); - const validChunks = new Set<number>(); - const invalidChunks = new Set<number>(); - const deferredRequires = new Set<number>(); + const validChunks = new Set<PropertyKey>(); + const invalidChunks = new Set<PropertyKey>(); + const deferredRequires = new Set<PropertyKey>(); - let chunksSearchingResolve: (value: void | PromiseLike<void>) => void; - const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r); + const { promise: chunksSearchingDone, resolve: chunksSearchingResolve } = Promise.withResolvers<void>(); // True if resolved, false otherwise const chunksSearchPromises = [] as Array<() => boolean>; + /* This regex loads all language packs which makes webpack finds testing extremely slow, so for now, lets use one which doesnt include those + const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i(?:\.\i)?\.bind\(\i,"?([^)]+?)"?(?:,[^)]+?)?\)\)/g); + */ const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g); let foundCssDebuggingLoad = false; @@ -34,12 +37,15 @@ export async function loadLazyChunks() { const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&")); const lazyChunks = factoryCode.matchAll(LazyChunkRegex); - const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>(); + const validChunkGroups = new Set<[chunkIds: PropertyKey[], entryPoint: PropertyKey]>(); const shouldForceDefer = false; await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { - const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : []; + const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => { + const numChunkId = Number(m[1]); + return Number.isNaN(numChunkId) ? m[1] : numChunkId; + }) : []; if (chunkIds.length === 0) { return; @@ -62,7 +68,7 @@ export async function loadLazyChunks() { const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) .then(r => r.text()) - .then(t => t.includes("importScripts(")); + .then(t => /importScripts\(|self\.postMessage/.test(t)); if (isWorkerAsset) { invalidChunks.add(id); @@ -74,7 +80,8 @@ export async function loadLazyChunks() { } if (!invalidChunkGroup) { - validChunkGroups.add([chunkIds, Number(entryPoint)]); + const numEntryPoint = Number(entryPoint); + validChunkGroups.add([chunkIds, Number.isNaN(numEntryPoint) ? entryPoint : numEntryPoint]); } })); @@ -82,7 +89,7 @@ export async function loadLazyChunks() { await Promise.all( Array.from(validChunkGroups) .map(([chunkIds]) => - Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { }))) + Promise.all(chunkIds.map(id => wreq.e(id))) ) ); @@ -94,7 +101,7 @@ export async function loadLazyChunks() { continue; } - if (wreq.m[entryPoint]) wreq(entryPoint as any); + if (wreq.m[entryPoint]) wreq(entryPoint); } catch (err) { console.error(err); } @@ -122,41 +129,44 @@ export async function loadLazyChunks() { }, 0); } - Webpack.factoryListeners.add(factory => { + function factoryListener(factory: AnyModuleFactory | ModuleFactory) { let isResolved = false; - searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true); + searchAndLoadLazyChunks(String(factory)) + .then(() => isResolved = true) + .catch(() => isResolved = true); chunksSearchPromises.push(() => isResolved); - }); + } - for (const factoryId in wreq.m) { - let isResolved = false; - searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true); - - chunksSearchPromises.push(() => isResolved); + Webpack.factoryListeners.add(factoryListener); + for (const moduleId in wreq.m) { + factoryListener(wreq.m[moduleId]); } await chunksSearchingDone; + Webpack.factoryListeners.delete(factoryListener); // Require deferred entry points for (const deferredRequire of deferredRequires) { - wreq!(deferredRequire as any); + wreq(deferredRequire); } // All chunks Discord has mapped to asset files, even if they are not used anymore - const allChunks = [] as number[]; + const allChunks = [] as PropertyKey[]; // Matches "id" or id: - for (const currentMatch of wreq!.u.toString().matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) { + for (const currentMatch of String(wreq.u).matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) { const id = currentMatch[1] ?? currentMatch[2]; if (id == null) continue; - allChunks.push(Number(id)); + const numId = Number(id); + allChunks.push(Number.isNaN(numId) ? id : numId); } if (allChunks.length === 0) throw new Error("Failed to get all chunks"); - // Chunks that are not loaded (not used) by Discord code anymore + // Chunks which our regex could not catch to load + // It will always contain WebWorker assets, and also currently contains some language packs which are loaded differently const chunksLeft = allChunks.filter(id => { return !(validChunks.has(id) || invalidChunks.has(id)); }); @@ -164,14 +174,11 @@ export async function loadLazyChunks() { await Promise.all(chunksLeft.map(async id => { const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) .then(r => r.text()) - .then(t => t.includes("importScripts(")); + .then(t => /importScripts\(|self\.postMessage/.test(t)); - // Loads and requires a chunk + // Loads the chunk. Currently this only happens with the language packs which are loaded differently if (!isWorkerAsset) { - await wreq.e(id as any); - // Technically, the id of the chunk does not match the entry point - // But, still try it because we have no way to get the actual entry point - if (wreq.m[id]) wreq(id as any); + await wreq.e(id); } })); diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts index ddd5e5f18..2ca83b7fa 100644 --- a/src/debug/runReporter.ts +++ b/src/debug/runReporter.ts @@ -6,28 +6,56 @@ import { Logger } from "@utils/Logger"; import * as Webpack from "@webpack"; -import { patches } from "plugins"; +import { getBuildNumber, patchTimings } from "@webpack/patcher"; +import { addPatch, patches } from "../plugins"; import { loadLazyChunks } from "./loadLazyChunks"; -const ReporterLogger = new Logger("Reporter"); - async function runReporter() { + const ReporterLogger = new Logger("Reporter"); + try { ReporterLogger.log("Starting test..."); - let loadLazyChunksResolve: (value: void | PromiseLike<void>) => void; - const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r); + const { promise: loadLazyChunksDone, resolve: loadLazyChunksResolve } = Promise.withResolvers<void>(); + + // The main patch for starting the reporter chunk loading + addPatch({ + find: '"Could not find app-mount"', + replacement: { + match: /(?<="use strict";)/, + replace: "Vencord.Webpack._initReporter();" + } + }, "Vencord Reporter"); + + // @ts-ignore + Vencord.Webpack._initReporter = function () { + // initReporter is called in the patched entry point of Discord + // setImmediate to only start searching for lazy chunks after Discord initialized the app + setTimeout(() => loadLazyChunks().then(loadLazyChunksResolve), 0); + }; - Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve))); await loadLazyChunksDone; + if (IS_REPORTER && IS_WEB && !IS_VESKTOP) { + console.log("[REPORTER_META]", { + buildNumber: getBuildNumber(), + buildHash: window.GLOBAL_ENV.SENTRY_TAGS.buildId + }); + } + for (const patch of patches) { if (!patch.all) { new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`); } } + for (const [plugin, moduleId, match, totalTime] of patchTimings) { + if (totalTime > 5) { + new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`); + } + } + for (const [searchType, args] of Webpack.lazyWebpackSearchHistory) { let method = searchType; @@ -50,9 +78,9 @@ async function runReporter() { result = await Webpack.extractAndLoadChunks(code, matcher); if (result === false) result = null; } else if (method === "mapMangledModule") { - const [code, mapper] = args; + const [code, mapper, includeBlacklistedExports] = args; - result = Webpack.mapMangledModule(code, mapper); + result = Webpack.mapMangledModule(code, mapper, includeBlacklistedExports); if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail"); } else { // @ts-ignore @@ -62,14 +90,21 @@ async function runReporter() { if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail"); } catch (e) { let logMessage = searchType; - if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`; - else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`; - else if (method === "mapMangledModule") { + if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") { + if (args[0].$$vencordProps != null) { + logMessage += `(${args[0].$$vencordProps.map(arg => `"${arg}"`).join(", ")})`; + } else { + logMessage += `(${args[0].toString().slice(0, 147)}...)`; + } + } else if (method === "extractAndLoadChunks") { + logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`; + } else if (method === "mapMangledModule") { const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null); logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`; + } else { + logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`; } - else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`; ReporterLogger.log("Webpack Find Fail:", logMessage); } @@ -81,4 +116,6 @@ async function runReporter() { } } -runReporter(); +// Run after the Vencord object has been created. +// We need to add extra properties to it, and it is only created after all of Vencord code has ran +setTimeout(runReporter, 0); diff --git a/src/globals.d.ts b/src/globals.d.ts index e20ca4b71..4456564cc 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -64,13 +64,8 @@ declare global { export var Vesktop: any; export var VesktopNative: any; - interface Window { - webpackChunkdiscord_app: { - push(chunk: any): any; - pop(): any; - }; + interface Window extends Record<PropertyKey, any> { _: LoDashStatic; - [k: string]: any; } } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index acdfb1f1f..22e96834f 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -28,7 +28,7 @@ import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { isPluginDev } from "@utils/misc"; -import { closeModal, Modals, openModal } from "@utils/modal"; +import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; import { Forms, Toasts, UserStore } from "@webpack/common"; import { User } from "discord-types/general"; @@ -102,8 +102,9 @@ export default definePlugin({ } }, + userProfileBadge: ContributorBadge, + async start() { - Vencord.Api.Badges.addBadge(ContributorBadge); await loadBadges(); }, @@ -143,8 +144,8 @@ export default definePlugin({ closeModal(modalKey); VencordNative.native.openExternal("https://github.com/sponsors/Vendicated"); }}> - <Modals.ModalRoot {...props}> - <Modals.ModalHeader> + <ModalRoot {...props}> + <ModalHeader> <Flex style={{ width: "100%", justifyContent: "center" }}> <Forms.FormTitle tag="h2" @@ -158,8 +159,8 @@ export default definePlugin({ Vencord Donor </Forms.FormTitle> </Flex> - </Modals.ModalHeader> - <Modals.ModalContent> + </ModalHeader> + <ModalContent> <Flex> <img role="presentation" @@ -182,13 +183,13 @@ export default definePlugin({ Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!! </Forms.FormText> </div> - </Modals.ModalContent> - <Modals.ModalFooter> + </ModalContent> + <ModalFooter> <Flex style={{ width: "100%", justifyContent: "center" }}> <DonateButton /> </Flex> - </Modals.ModalFooter> - </Modals.ModalRoot> + </ModalFooter> + </ModalRoot> </ErrorBoundary> )); }, diff --git a/src/plugins/_api/chatButtons.ts b/src/plugins/_api/chatButtons.ts index 578861e2e..184b01584 100644 --- a/src/plugins/_api/chatButtons.ts +++ b/src/plugins/_api/chatButtons.ts @@ -12,11 +12,16 @@ export default definePlugin({ description: "API to add buttons to the chat input", authors: [Devs.Ven], - patches: [{ - find: '"sticker")', - replacement: { - match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/, - replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&" + patches: [ + { + find: '"sticker")', + replacement: { + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/, + replace: (m, not, children) => not + ? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&` + : `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||` + } } - }] + ] }); diff --git a/src/plugins/_api/contextMenu.ts b/src/plugins/_api/contextMenu.ts index 01619546d..debc55273 100644 --- a/src/plugins/_api/contextMenu.ts +++ b/src/plugins/_api/contextMenu.ts @@ -34,12 +34,22 @@ export default definePlugin({ } }, { - find: ".Menu,{", + find: "navId:", all: true, - replacement: { - match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g, - replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[]," - } + noWarn: true, + replacement: [ + { + match: /navId:(?=.+?([,}].*?\)))/g, + replace: (m, rest) => { + // Check if this navId: match is a destructuring statement, ignore it if it is + const destructuringMatch = rest.match(/}=.+/); + if (destructuringMatch == null) { + return `contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],${m}`; + } + return m; + } + } + ] } ] }); diff --git a/src/plugins/_api/dynamicImageModalApi.ts b/src/plugins/_api/dynamicImageModalApi.ts index 2ce51400d..4d74cff24 100644 --- a/src/plugins/_api/dynamicImageModalApi.ts +++ b/src/plugins/_api/dynamicImageModalApi.ts @@ -16,8 +16,8 @@ export default definePlugin({ { find: "SCALE_DOWN:", replacement: { - match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/, - replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))` + match: /(?<="IMAGE"===\i\?)\i(?=\?)/, + replace: "true" } } ] diff --git a/src/plugins/_api/memberListDecorators.ts b/src/plugins/_api/memberListDecorators/index.tsx similarity index 83% rename from src/plugins/_api/memberListDecorators.ts rename to src/plugins/_api/memberListDecorators/index.tsx index 0dba3608f..39c82a1ed 100644 --- a/src/plugins/_api/memberListDecorators.ts +++ b/src/plugins/_api/memberListDecorators/index.tsx @@ -19,10 +19,15 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import managedStyle from "./style.css?managed"; + export default definePlugin({ name: "MemberListDecoratorsAPI", description: "API to add decorators to member list (both in servers and DMs)", authors: [Devs.TheSun, Devs.Ven], + + managedStyle, + patches: [ { find: ".lostPermission)", @@ -32,7 +37,7 @@ export default definePlugin({ replace: "$&vencordProps=$1," }, { match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/, - replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps))," + replace: "$&(typeof vencordProps=='undefined'?null:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps))," } ] }, @@ -40,8 +45,8 @@ export default definePlugin({ find: "PrivateChannel.renderAvatar", replacement: { match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/, - replace: "decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]" + replace: "decorators:[Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]),$1?$2:null]" } } - ], + ] }); diff --git a/src/plugins/_api/memberListDecorators/style.css b/src/plugins/_api/memberListDecorators/style.css new file mode 100644 index 000000000..bd4afda9d --- /dev/null +++ b/src/plugins/_api/memberListDecorators/style.css @@ -0,0 +1,11 @@ +.vc-member-list-decorators-wrapper { + display: flex; + align-items: center; + justify-content: center; + gap: 0.25em; +} + +.vc-member-list-decorators-wrapper:not(:empty) { + /* Margin to match default Discord decorators */ + margin-left: 0.25em; +} diff --git a/src/plugins/_api/menuItemDemangler.ts b/src/plugins/_api/menuItemDemangler.ts new file mode 100644 index 000000000..b6a03fe88 --- /dev/null +++ b/src/plugins/_api/menuItemDemangler.ts @@ -0,0 +1,68 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import { canonicalizeMatch } from "@utils/patches"; +import definePlugin from "@utils/types"; + +// duplicate values have multiple branches with different types. Just include all to be safe +const nameMap = { + radio: "MenuRadioItem", + separator: "MenuSeparator", + checkbox: "MenuCheckboxItem", + groupstart: "MenuGroup", + + control: "MenuControlItem", + compositecontrol: "MenuControlItem", + + item: "MenuItem", + customitem: "MenuItem", +}; + +export default definePlugin({ + name: "MenuItemDemanglerAPI", + description: "Demangles Discord's Menu Item module", + authors: [Devs.Ven], + required: true, + patches: [ + { + find: '"Menu API', + replacement: { + match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s, + replace: (m, mod) => { + const nameAssignments = [] as string[]; + + // if (t.type === m.MenuItem) + const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g); + // push({type:"item"}) + const pushTypeRe = /type:"(\w+)"/g; + + let typeMatch: RegExpExecArray | null; + // for each if (t.type === ...) + while ((typeMatch = typeCheckRe.exec(m)) !== null) { + // extract the current menu item + const item = typeMatch[1]; + // Set the starting index of the second regex to that of the first to start + // matching from after the if + pushTypeRe.lastIndex = typeCheckRe.lastIndex; + // extract the first type: "..." + const type = pushTypeRe.exec(m)?.[1]; + if (type && type in nameMap) { + const name = nameMap[type]; + nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`); + } + } + if (nameAssignments.length < 6) { + console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length); + } + + // Merge all our redefines with the actual module + return `${nameAssignments.join(";")};${m}`; + }, + }, + }, + ], +}); diff --git a/src/plugins/_api/messageDecorations.ts b/src/plugins/_api/messageDecorations/index.tsx similarity index 87% rename from src/plugins/_api/messageDecorations.ts rename to src/plugins/_api/messageDecorations/index.tsx index fb63a6dde..10866baea 100644 --- a/src/plugins/_api/messageDecorations.ts +++ b/src/plugins/_api/messageDecorations/index.tsx @@ -19,17 +19,22 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import managedStyle from "./style.css?managed"; + export default definePlugin({ name: "MessageDecorationsAPI", description: "API to add decorations to messages", authors: [Devs.TheSun], + + managedStyle, + patches: [ { find: '"Message Username"', replacement: { match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/, - replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])" + replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])" } } - ], + ] }); diff --git a/src/plugins/_api/messageDecorations/style.css b/src/plugins/_api/messageDecorations/style.css new file mode 100644 index 000000000..5c13669c4 --- /dev/null +++ b/src/plugins/_api/messageDecorations/style.css @@ -0,0 +1,18 @@ +.vc-message-decorations-wrapper { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.25em; +} + +.vc-message-decorations-wrapper:not(:empty) { + /* Margin to match default Discord decorators */ + margin-left: 0.25em; + + /* Align vertically */ + position: relative; + vertical-align: top; + top: 0.1rem; + height: calc(1rem + 4px); + max-height: calc(1rem + 4px) +} diff --git a/src/plugins/_api/messageEvents.ts b/src/plugins/_api/messageEvents.ts index 97ed1746d..9dfc55e27 100644 --- a/src/plugins/_api/messageEvents.ts +++ b/src/plugins/_api/messageEvents.ts @@ -37,12 +37,9 @@ export default definePlugin({ { find: ".handleSendMessage,onResize", replacement: { - // props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); - // Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid) - match: /(\{openWarningPopout:.{0,100}type:this.props.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);)(?<=\)\(({.+?})\)\.then.+?)/, - // props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true }; - replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" + - `${rest1}async ${rest2}` + + // https://regex101.com/r/hBlXpl/1 + match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/, + replace: (m, parsedMessage, channel, replyOptions, extra) => m + `if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` + "return{shouldClear:false,shouldRefocus:true};" } @@ -52,8 +49,7 @@ export default definePlugin({ replacement: { match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/, replace: (m, message, channel, event) => - // the message param is shadowed by the event param, so need to alias them - `const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});` + `const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg,vcChan,${event});` } } ] diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index d552037fe..30920a067 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -20,6 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, StartAt } from "@utils/types"; +import { WebpackRequire } from "@webpack/wreq.d"; const settings = definePluginSettings({ disableAnalytics: { @@ -81,9 +82,9 @@ export default definePlugin({ Object.defineProperty(Function.prototype, "g", { configurable: true, - set(v: any) { + set(this: WebpackRequire, globalObj: WebpackRequire["g"]) { Object.defineProperty(this, "g", { - value: v, + value: globalObj, configurable: true, enumerable: true, writable: true @@ -92,11 +93,11 @@ export default definePlugin({ // Ensure this is most likely the Sentry WebpackInstance. // Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: <g></g>) to include it const { stack } = new Error(); - if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || !String(this).includes("exports:{}") || this.c != null) { + if (this.c != null || !stack?.includes("http") || !String(this).includes("exports:{}")) { return; } - const assetPath = stack?.match(/\/assets\/.+?\.js/)?.[0]; + const assetPath = stack.match(/http.+?(?=:\d+?:\d+?$)/m)?.[0]; if (!assetPath) { return; } @@ -106,7 +107,8 @@ export default definePlugin({ srcRequest.send(); // Final condition to see if this is the Sentry WebpackInstance - if (!srcRequest.responseText.includes("window.DiscordSentry=")) { + // This is matching window.DiscordSentry=, but without `window` to avoid issues on some proxies + if (!srcRequest.responseText.includes(".DiscordSentry=")) { return; } diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index d58c7a98c..a9e34f784 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -65,7 +65,8 @@ export default definePlugin({ replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}` }, { - match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/, + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/, replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})` } ] @@ -157,6 +158,9 @@ export default definePlugin({ aboveActivity: getIntlMessage("ACTIVITY_SETTINGS") }; + if (!names[settingsLocation] || names[settingsLocation].endsWith("_SETTINGS")) + return firstChild === "PREMIUM"; + return header === names[settingsLocation]; } catch { return firstChild === "PREMIUM"; diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 8687f843b..03639aea7 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -16,14 +16,13 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addAccessory } from "@api/MessageAccessories"; import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; -import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants"; +import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, KNOWN_ISSUES_CHANNEL_ID, REGULAR_ROLE_ID, SUPPORT_CHANNEL_ID, VENBOT_USER_ID, VENCORD_GUILD_ID } from "@utils/constants"; import { sendMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; @@ -41,9 +40,6 @@ import plugins, { PluginMeta } from "~plugins"; import SettingsPlugin from "./settings"; -const VENCORD_GUILD_ID = "1015060230222131221"; -const VENBOT_USER_ID = "1017176847865352332"; -const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920"; const CodeBlockRe = /```js\n(.+?)```/s; const AllowedChannelIds = [ @@ -53,9 +49,9 @@ const AllowedChannelIds = [ ]; const TrustedRolesIds = [ - "1026534353167208489", // contributor - "1026504932959977532", // regular - "1042507929485586532", // donor + CONTRIB_ROLE_ID, // contributor + REGULAR_ROLE_ID, // regular + DONOR_ROLE_ID, // donor ]; const AsyncFunction = async function () { }.constructor; @@ -143,7 +139,7 @@ export default definePlugin({ required: true, description: "Helps us provide support to you", authors: [Devs.Ven], - dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"], + dependencies: ["UserSettingsAPI"], settings, @@ -236,6 +232,85 @@ export default definePlugin({ } }, + renderMessageAccessory(props) { + const buttons = [] as JSX.Element[]; + + const shouldAddUpdateButton = + !IS_UPDATER_DISABLED + && ( + (props.channel.id === KNOWN_ISSUES_CHANNEL_ID) || + (props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID) + ) + && props.message.content?.includes("update"); + + if (shouldAddUpdateButton) { + buttons.push( + <Button + key="vc-update" + color={Button.Colors.GREEN} + onClick={async () => { + try { + if (await forceUpdate()) + showToast("Success! Restarting...", Toasts.Type.SUCCESS); + else + showToast("Already up to date!", Toasts.Type.MESSAGE); + } catch (e) { + new Logger(this.name).error("Error while updating:", e); + showToast("Failed to update :(", Toasts.Type.FAILURE); + } + }} + > + Update Now + </Button> + ); + } + + if (props.channel.id === SUPPORT_CHANNEL_ID) { + if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) { + buttons.push( + <Button + key="vc-dbg" + onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })} + > + Run /vencord-debug + </Button>, + <Button + key="vc-plg-list" + onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })} + > + Run /vencord-plugins + </Button> + ); + } + + if (props.message.author.id === VENBOT_USER_ID) { + const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || ""); + if (match) { + buttons.push( + <Button + key="vc-run-snippet" + onClick={async () => { + try { + await AsyncFunction(match[1])(); + showToast("Success!", Toasts.Type.SUCCESS); + } catch (e) { + new Logger(this.name).error("Error while running snippet:", e); + showToast("Failed to run snippet :(", Toasts.Type.FAILURE); + } + }} + > + Run Snippet + </Button> + ); + } + } + } + + return buttons.length + ? <Flex>{buttons}</Flex> + : null; + }, + renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => { const userId = channel.getRecipientId(); if (!isPluginDev(userId)) return null; @@ -250,85 +325,4 @@ export default definePlugin({ </Card> ); }, { noop: true }), - - start() { - addAccessory("vencord-debug", props => { - const buttons = [] as JSX.Element[]; - - const shouldAddUpdateButton = - !IS_UPDATER_DISABLED - && ( - (props.channel.id === KNOWN_ISSUES_CHANNEL_ID) || - (props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID) - ) - && props.message.content?.includes("update"); - - if (shouldAddUpdateButton) { - buttons.push( - <Button - key="vc-update" - color={Button.Colors.GREEN} - onClick={async () => { - try { - if (await forceUpdate()) - showToast("Success! Restarting...", Toasts.Type.SUCCESS); - else - showToast("Already up to date!", Toasts.Type.MESSAGE); - } catch (e) { - new Logger(this.name).error("Error while updating:", e); - showToast("Failed to update :(", Toasts.Type.FAILURE); - } - }} - > - Update Now - </Button> - ); - } - - if (props.channel.id === SUPPORT_CHANNEL_ID) { - if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) { - buttons.push( - <Button - key="vc-dbg" - onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })} - > - Run /vencord-debug - </Button>, - <Button - key="vc-plg-list" - onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })} - > - Run /vencord-plugins - </Button> - ); - } - - if (props.message.author.id === VENBOT_USER_ID) { - const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || ""); - if (match) { - buttons.push( - <Button - key="vc-run-snippet" - onClick={async () => { - try { - await AsyncFunction(match[1])(); - showToast("Success!", Toasts.Type.SUCCESS); - } catch (e) { - new Logger(this.name).error("Error while running snippet:", e); - showToast("Failed to run snippet :(", Toasts.Type.FAILURE); - } - }} - > - Run Snippet - </Button> - ); - } - } - } - - return buttons.length - ? <Flex>{buttons}</Flex> - : null; - }); - }, }); diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index e2dc220b7..a2fed5d79 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -23,7 +23,7 @@ const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cann const styles = findByPropsLazy("accountProfilePopoutWrapper"); let openAlternatePopout = false; -let accountPanelRef: React.MutableRefObject<Record<PropertyKey, any> | null> = { current: null }; +let accountPanelRef: React.RefObject<Record<PropertyKey, any> | null> = { current: null }; const AccountPanelContextMenu = ErrorBoundary.wrap(() => { const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]); diff --git a/src/plugins/alwaysAnimate/index.ts b/src/plugins/alwaysAnimate/index.ts index fc528466f..a5297445b 100644 --- a/src/plugins/alwaysAnimate/index.ts +++ b/src/plugins/alwaysAnimate/index.ts @@ -43,8 +43,8 @@ export default definePlugin({ // Status emojis find: "#{intl::GUILD_OWNER}),children:", replacement: { - match: /(?<=\.activityEmoji,.+?animate:)\i/, - replace: "!0" + match: /(\.CUSTOM_STATUS.+?animate:)\i/, + replace: (_, rest) => `${rest}!0` } }, { diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index 61e048dce..94c507f33 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -23,7 +23,7 @@ import definePlugin, { ReporterTestable } from "@utils/types"; import { findByCodeLazy } from "@webpack"; import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common"; -const fetchApplicationsRPC = findByCodeLazy("APPLICATION_RPC(", "Client ID"); +const fetchApplicationsRPC = findByCodeLazy('"Invalid Origin"', ".application"); async function lookupAsset(applicationId: string, key: string): Promise<string> { return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 53d24ed93..5203b14d2 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -17,14 +17,13 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { useStateFromStores } from "@webpack/common"; +import { findComponentByCodeLazy, findStoreLazy } from "@webpack"; +import { Animations, useStateFromStores } from "@webpack/common"; import type { CSSProperties } from "react"; import { ExpandedGuildFolderStore, settings } from "."; const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); -const Animations = findByPropsLazy("a", "animated", "useTransition"); const GuildsBar = findComponentByCodeLazy('("guildsnav")'); export default ErrorBoundary.wrap(guildsBarProps => { diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index d9d68f138..ded8b6114 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -173,8 +173,8 @@ export default definePlugin({ // Disable expanding and collapsing folders transition in the normal GuildsBar sidebar { predicate: () => !settings.store.keepIcons, - match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/, - replace: "$self.shouldShowTransition(arguments[0])&&" + match: /(?=,\{from:\{height)/, + replace: "&&$self.shouldShowTransition(arguments[0])" }, // If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded { @@ -185,8 +185,8 @@ export default definePlugin({ { // Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, - match: /(?<=\.isExpanded\),children:\[)/, - replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&" + match: /\.isExpanded\),.{0,30}children:\[/, + replace: "$&$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&" }, { // Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index 1029c07e2..afef63908 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -83,7 +83,7 @@ export default definePlugin({ if (!role) return; if (role.colorString) { - children.push( + children.unshift( <Menu.MenuItem id="vc-copy-role-color" label="Copy Role Color" @@ -93,6 +93,20 @@ export default definePlugin({ ); } + if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) { + children.unshift( + <Menu.MenuItem + id="vc-edit-role" + label="Edit Role" + action={async () => { + await GuildSettingsActions.open(guild.id, "ROLES"); + GuildSettingsActions.selectRole(id); + }} + icon={PencilIcon} + /> + ); + } + if (role.icon) { children.push( <Menu.MenuItem @@ -110,20 +124,6 @@ export default definePlugin({ ); } - - if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) { - children.push( - <Menu.MenuItem - id="vc-edit-role" - label="Edit Role" - action={async () => { - await GuildSettingsActions.open(guild.id, "ROLES"); - GuildSettingsActions.selectRole(id); - }} - icon={PencilIcon} - /> - ); - } } } }); diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx index 9347c398c..8f8ef141f 100644 --- a/src/plugins/betterSessions/index.tsx +++ b/src/plugins/betterSessions/index.tsx @@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { Constants, React, RestAPI, Tooltip } from "@webpack/common"; import { RenameButton } from "./components/RenameButton"; @@ -34,7 +34,7 @@ const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open"); const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer"); const SessionIconClasses = findByPropsLazy("sessionIcon"); -const BlobMask = findExportedComponentLazy("BlobMask"); +const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:"); const settings = definePluginSettings({ backgroundCheck: { diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index 5a97b0a6e..461dfaac8 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -101,8 +101,8 @@ export default definePlugin({ find: 'minimal:"contentColumnMinimal"', replacement: [ { - match: /\(0,\i\.useTransition\)\((\i)/, - replace: "(_cb=>_cb(void 0,$1))||$&" + match: /(?=\(0,\i\.\i\)\((\i),\{from:\{position:"absolute")/, + replace: "(_cb=>_cb(void 0,$1))||" }, { match: /\i\.animated\.div/, diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts index c08d43282..29827a5e9 100644 --- a/src/plugins/betterUploadButton/index.ts +++ b/src/plugins/betterUploadButton/index.ts @@ -26,10 +26,18 @@ export default definePlugin({ patches: [ { find: '"ChannelAttachButton"', - replacement: { - match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/, - replace: "$&onClick:$1,onContextMenu:$2.onClick,", - }, + replacement: [ + { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/, + replace: "$&onClick:$1,onContextMenu:$2.onClick,", + noWarn: true + }, + { + match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/, + replace: "$&,onClick:$1,onContextMenu:$2.onClick,", + }, + ] }, ], }); diff --git a/src/plugins/blurNsfw/index.ts b/src/plugins/blurNsfw/index.ts index 948de0ac6..c4023f098 100644 --- a/src/plugins/blurNsfw/index.ts +++ b/src/plugins/blurNsfw/index.ts @@ -24,14 +24,14 @@ let style: HTMLStyleElement; function setCss() { style.textContent = ` - .vc-nsfw-img [class^=imageWrapper] img, - .vc-nsfw-img [class^=wrapperPaused] video { + .vc-nsfw-img [class^=imageContainer], + .vc-nsfw-img [class^=wrapperPaused] { filter: blur(${Settings.plugins.BlurNSFW.blurAmount}px); transition: filter 0.2s; - } - .vc-nsfw-img [class^=imageWrapper]:hover img, - .vc-nsfw-img [class^=wrapperPaused]:hover video { - filter: unset; + + &:hover { + filter: blur(0); + } } `; } @@ -54,7 +54,7 @@ export default definePlugin({ options: { blurAmount: { type: OptionType.NUMBER, - description: "Blur Amount", + description: "Blur Amount (in pixels)", default: 10, onChange: setCss } diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index e16abc4a1..bdcca7772 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -75,8 +75,8 @@ export default definePlugin({ patches: [{ find: "renderConnectionStatus(){", replacement: { - match: /(?<=renderConnectionStatus\(\){.+\.channel,children:).+?}\):\i(?=}\))/, - replace: "[$&, $self.renderTimer(this.props.channel.id)]" + match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/, + replace: "$1[$2,$self.renderTimer(this.props.channel.id)]" } }], diff --git a/src/plugins/clearURLs/index.ts b/src/plugins/clearURLs/index.ts index d1be6c6f5..f00c751d4 100644 --- a/src/plugins/clearURLs/index.ts +++ b/src/plugins/clearURLs/index.ts @@ -17,11 +17,7 @@ */ import { - addPreEditListener, - addPreSendListener, - MessageObject, - removePreEditListener, - removePreSendListener + MessageObject } from "@api/MessageEvents"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; @@ -36,7 +32,18 @@ export default definePlugin({ name: "ClearURLs", description: "Removes tracking garbage from URLs", authors: [Devs.adryd], - dependencies: ["MessageEventsAPI"], + + start() { + this.createRules(); + }, + + onBeforeMessageSend(_, msg) { + return this.onSend(msg); + }, + + onBeforeMessageEdit(_cid, _mid, msg) { + return this.onSend(msg); + }, escapeRegExp(str: string) { return (str && reHasRegExpChar.test(str)) @@ -133,17 +140,4 @@ export default definePlugin({ ); } }, - - start() { - this.createRules(); - this.preSend = addPreSendListener((_, msg) => this.onSend(msg)); - this.preEdit = addPreEditListener((_cid, _mid, msg) => - this.onSend(msg) - ); - }, - - stop() { - removePreSendListener(this.preSend); - removePreEditListener(this.preEdit); - }, }); diff --git a/src/plugins/clientTheme/clientTheme.css b/src/plugins/clientTheme/clientTheme.css index 64aefdcf5..795b5457e 100644 --- a/src/plugins/clientTheme/clientTheme.css +++ b/src/plugins/clientTheme/clientTheme.css @@ -1,29 +1,29 @@ -.client-theme-settings { +.vc-clientTheme-settings { display: flex; flex-direction: column; } -.client-theme-container { +.vc-clientTheme-container { display: flex; flex-direction: row; justify-content: space-between; } -.client-theme-settings-labels { +.vc-clientTheme-labels { display: flex; flex-direction: column; justify-content: flex-start; } -.client-theme-container > [class^="colorSwatch"] > [class^="swatch"] { +.vc-clientTheme-container [class^="swatch"] { border: thin solid var(--background-modifier-accent) !important; } -.client-theme-warning * { +.vc-clientTheme-warning-text { color: var(--text-danger); } -.client-theme-contrast-warning { +.vc-clientTheme-contrast-warning { background-color: var(--background-primary); padding: 0.5rem; border-radius: .5rem; diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 4c1668aae..c7e0e50de 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -7,6 +7,7 @@ import "./clientTheme.css"; import { definePluginSettings } from "@api/Settings"; +import { classNameFactory } from "@api/Styles"; import { Devs } from "@utils/constants"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; @@ -14,6 +15,8 @@ import definePlugin, { OptionType, StartAt } from "@utils/types"; import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common"; +const cl = classNameFactory("vc-clientTheme-"); + const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); const colorPresets = [ @@ -60,9 +63,9 @@ function ThemeSettings() { } return ( - <div className="client-theme-settings"> - <div className="client-theme-container"> - <div className="client-theme-settings-labels"> + <div className={cl("settings")}> + <div className={cl("container")}> + <div className={cl("settings-labels")}> <Forms.FormTitle tag="h3">Theme Color</Forms.FormTitle> <Forms.FormText>Add a color to your Discord client theme</Forms.FormText> </div> @@ -76,10 +79,10 @@ function ThemeSettings() { {(contrastWarning || nitroThemeEnabled) && (<> <Forms.FormDivider className={classes(Margins.top8, Margins.bottom8)} /> <div className={`client-theme-contrast-warning ${contrastWarning ? (isLightTheme ? "theme-dark" : "theme-light") : ""}`}> - <div className="client-theme-warning"> - <Forms.FormText>Warning, your theme won't look good:</Forms.FormText> - {contrastWarning && <Forms.FormText>Selected color won't contrast well with text</Forms.FormText>} - {nitroThemeEnabled && <Forms.FormText>Nitro themes aren't supported</Forms.FormText>} + <div className={cl("warning")}> + <Forms.FormText className={cl("warning-text")}>Warning, your theme won't look good:</Forms.FormText> + {contrastWarning && <Forms.FormText className={cl("warning-text")}>Selected color won't contrast well with text</Forms.FormText>} + {nitroThemeEnabled && <Forms.FormText className={cl("warning-text")}>Nitro themes aren't supported</Forms.FormText>} </div> {(contrastWarning && fixableContrast) && <Button onClick={() => setTheme(oppositeTheme)} color={Button.Colors.RED}>Switch to {oppositeTheme} mode</Button>} {(nitroThemeEnabled) && <Button onClick={() => setTheme(theme)} color={Button.Colors.RED}>Disable Nitro Theme</Button>} @@ -91,15 +94,12 @@ function ThemeSettings() { const settings = definePluginSettings({ color: { - description: "Color your Discord client theme will be based around. Light mode isn't supported", type: OptionType.COMPONENT, default: "313338", - component: () => <ThemeSettings /> + component: ThemeSettings }, resetColor: { - description: "Reset Theme Color", type: OptionType.COMPONENT, - default: "313338", component: () => ( <Button onClick={() => onPickColor(0x313338)}> Reset Theme Color @@ -126,6 +126,7 @@ export default definePlugin({ stop() { document.getElementById("clientThemeVars")?.remove(); document.getElementById("clientThemeOffsets")?.remove(); + document.getElementById("clientThemeLightModeFixes")?.remove(); } }); diff --git a/src/plugins/consoleJanitor/index.ts b/src/plugins/consoleJanitor/index.ts index d251ff740..34f5653c4 100644 --- a/src/plugins/consoleJanitor/index.ts +++ b/src/plugins/consoleJanitor/index.ts @@ -69,8 +69,8 @@ export default definePlugin({ { find: "https://github.com/highlightjs/highlight.js/issues/2277", replacement: { - match: /(?<=&&\()console.log\(`Deprecated.+?`\),/, - replace: "" + match: /\(console.log\(`Deprecated.+?`\),/, + replace: "(" } }, { @@ -95,10 +95,9 @@ export default definePlugin({ } }, { - find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");', - all: true, + find: '"AppCrashedFatalReport: getLastCrash not supported."', replacement: { - match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/, + match: /console\.log\("AppCrashedFatalReport: getLastCrash not supported\."\);/, replace: "" } }, diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index be23d37c3..c0a4cb920 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -63,7 +63,7 @@ function makeShortcuts() { default: const uniqueMatches = [...new Set(matches)]; if (uniqueMatches.length > 1) - console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches); + console.warn(`Warning: This filter matches ${uniqueMatches.length} exports. Make it more specific!\n`, uniqueMatches); return matches[0]; } @@ -82,6 +82,8 @@ function makeShortcuts() { wp: Webpack, wpc: { getter: () => Webpack.cache }, wreq: { getter: () => Webpack.wreq }, + wpPatcher: { getter: () => Vencord.WebpackPatcher }, + wpInstances: { getter: () => Vencord.WebpackPatcher.allWebpackInstances }, wpsearch: search, wpex: extract, wpexs: (code: string) => extract(findModuleId(code)!), @@ -165,11 +167,38 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) { const currentVal = val.getter(); if (!currentVal || val.preload === false) return currentVal; - const value = currentVal[SYM_LAZY_GET] - ? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED] - : currentVal; + function unwrapProxy(value: any) { + if (value[SYM_LAZY_GET]) { + forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]; + } else if (value.$$vencordInternal) { + return forceLoad ? value.$$vencordInternal() : value; + } - if (value) define(window.shortcutList, key, { value }); + return value; + } + + const value = unwrapProxy(currentVal); + if (typeof value === "object" && value !== null) { + const descriptors = Object.getOwnPropertyDescriptors(value); + + for (const propKey in descriptors) { + if (value[propKey] == null) continue; + + const descriptor = descriptors[propKey]; + if (descriptor.writable === true || descriptor.set != null) { + const currentValue = value[propKey]; + const newValue = unwrapProxy(currentValue); + if (newValue != null && currentValue !== newValue) { + value[propKey] = newValue; + } + } + } + } + + if (value != null) { + define(window.shortcutList, key, { value }); + define(window, key, { value }); + } return value; } diff --git a/src/plugins/copyEmojiMarkdown/index.tsx b/src/plugins/copyEmojiMarkdown/index.tsx index a9c018a91..58e7303a3 100644 --- a/src/plugins/copyEmojiMarkdown/index.tsx +++ b/src/plugins/copyEmojiMarkdown/index.tsx @@ -33,11 +33,11 @@ function getEmojiMarkdown(target: Target, copyUnicode: boolean): string { : `:${emojiName}:`; } - const extension = target?.firstChild.src.match( - /https:\/\/cdn\.discordapp\.com\/emojis\/\d+\.(\w+)/ - )?.[1]; + const url = new URL(target.firstChild.src); + const hasParam = url.searchParams.get("animated") === "true"; + const isGif = url.pathname.endsWith(".gif"); - return `<${extension === "gif" ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`; + return `<${(hasParam || isGif) ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`; } const settings = definePluginSettings({ @@ -55,7 +55,7 @@ export default definePlugin({ settings, contextMenus: { - "expression-picker"(children, { target }: { target: Target }) { + "expression-picker"(children, { target }: { target: Target; }) { if (target.dataset.type !== "emoji") return; children.push( diff --git a/src/plugins/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts index 4a1b73765..b24f7a909 100644 --- a/src/plugins/ctrlEnterSend/index.ts +++ b/src/plugins/ctrlEnterSend/index.ts @@ -42,10 +42,11 @@ export default definePlugin({ // Only one of the two patches will be at effect; Discord often updates to switch between them. // See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673 { - find: ".ENTER&&(!", + find: ".selectPreviousCommandOption(", replacement: { - match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/, - replace: "$self.shouldSubmit($1, $2)" + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/, + replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})` } }, { diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index b7e90e096..7cb60384e 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -121,6 +121,7 @@ function DearrowButton({ component }: { component: Component<Props>; }) { height="24px" viewBox="0 0 36 36" aria-label="Toggle Dearrow" + className="vc-dearrow-icon" > <path fill="#1213BD" diff --git a/src/plugins/dearrow/styles.css b/src/plugins/dearrow/styles.css index fc7e9e320..f3f577a58 100644 --- a/src/plugins/dearrow/styles.css +++ b/src/plugins/dearrow/styles.css @@ -1,4 +1,4 @@ -.vc-dearrow-toggle-off svg { +.vc-dearrow-toggle-off .vc-dearrow-icon { filter: grayscale(1); } diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 69a7a1a59..4801cf753 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -50,12 +50,24 @@ export default definePlugin({ find: ".decorationGridItem,", replacement: [ { - match: /(?<==)\i=>{let{children.{20,100}decorationGridItem/, - replace: "$self.DecorationGridItem=$&" + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /(?<==)\i=>{let{children.{20,200}decorationGridItem/, + replace: "$self.DecorationGridItem=$&", + noWarn: true }, { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert match: /(?<==)\i=>{let{user:\i,avatarDecoration/, - replace: "$self.DecorationGridDecoration=$&" + replace: "$self.DecorationGridDecoration=$&", + noWarn: true + }, + { + match: /(?<==)\i=>{var{children.{20,200}decorationGridItem/, + replace: "$self.DecorationGridItem=$&", + }, + { + match: /(?<==)\i=>{var{user:\i,avatarDecoration/, + replace: "$self.DecorationGridDecoration=$&", }, // Remove NEW label from decor avatar decorations { diff --git a/src/plugins/decor/settings.tsx b/src/plugins/decor/settings.tsx index 0d3628cc6..8e82bbae2 100644 --- a/src/plugins/decor/settings.tsx +++ b/src/plugins/decor/settings.tsx @@ -17,7 +17,6 @@ import DecorSection from "./ui/components/DecorSection"; export const settings = definePluginSettings({ changeDecoration: { type: OptionType.COMPONENT, - description: "Change your avatar decoration", component() { if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText> Enable Decor and restart your client to change your avatar decoration. diff --git a/src/plugins/decor/ui/components/DecorationContextMenu.tsx b/src/plugins/decor/ui/components/DecorationContextMenu.tsx index 7451bb229..7c1542f65 100644 --- a/src/plugins/decor/ui/components/DecorationContextMenu.tsx +++ b/src/plugins/decor/ui/components/DecorationContextMenu.tsx @@ -5,7 +5,7 @@ */ import { CopyIcon, DeleteIcon } from "@components/Icons"; -import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "webpack/common"; +import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "@webpack/common"; import { Decoration } from "../../lib/api"; import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore"; diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx index a495907b2..4ac2e993d 100644 --- a/src/plugins/devCompanion.dev/index.tsx +++ b/src/plugins/devCompanion.dev/index.tsx @@ -160,7 +160,7 @@ function initWs(isManual = false) { return reply("Expected exactly one 'find' matches, found " + keys.length); const mod = candidates[keys[0]]; - let src = String(mod.original ?? mod).replaceAll("\n", ""); + let src = String(mod).replaceAll("\n", ""); if (src.startsWith("function(")) { src = "0," + src; @@ -173,7 +173,7 @@ function initWs(isManual = false) { try { const matcher = canonicalizeMatch(parseNode(match)); - const replacement = canonicalizeReplace(parseNode(replace), "PlaceHolderPluginName"); + const replacement = canonicalizeReplace(parseNode(replace), 'Vencord.Plugins.plugins["PlaceHolderPluginName"]'); const newSource = src.replace(matcher, replacement as string); diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index df8b4cd87..4bdf194ce 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents"; +import { addMessagePreEditListener, addMessagePreSendListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; @@ -235,7 +235,7 @@ export default definePlugin({ } }, { - find: ".PREMIUM_LOCKED;", + find: ".GUILD_SUBSCRIPTION_UNAVAILABLE;", group: true, predicate: () => settings.store.enableEmojiBypass, replacement: [ @@ -256,8 +256,11 @@ export default definePlugin({ }, { // Disallow the emoji for premium locked if the intention doesn't allow it - match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/, - replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})` + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /(!)?(\i\.\i\.canUseEmojisEverywhere\(\i\))/, + replace: (m, not) => not + ? `(${m}&&!${IS_BYPASSEABLE_INTENTION})` + : `(${m}||${IS_BYPASSEABLE_INTENTION})` }, { // Allow animated emojis to be used if the intention allows it @@ -853,7 +856,7 @@ export default definePlugin({ }); } - this.preSend = addPreSendListener(async (channelId, messageObj, extra) => { + this.preSend = addMessagePreSendListener(async (channelId, messageObj, extra) => { const { guildId } = this; let hasBypass = false; @@ -941,7 +944,7 @@ export default definePlugin({ return { cancel: false }; }); - this.preEdit = addPreEditListener(async (channelId, __, messageObj) => { + this.preEdit = addMessagePreEditListener(async (channelId, __, messageObj) => { if (!s.enableEmojiBypass) return; let hasBypass = false; @@ -973,7 +976,7 @@ export default definePlugin({ }, stop() { - removePreSendListener(this.preSend); - removePreEditListener(this.preEdit); + removeMessagePreSendListener(this.preSend); + removeMessagePreEditListener(this.preEdit); } }); diff --git a/src/plugins/fixSpotifyEmbeds.desktop/native.ts b/src/plugins/fixSpotifyEmbeds.desktop/native.ts index e15e4a441..79602609c 100644 --- a/src/plugins/fixSpotifyEmbeds.desktop/native.ts +++ b/src/plugins/fixSpotifyEmbeds.desktop/native.ts @@ -9,7 +9,7 @@ import { app } from "electron"; app.on("browser-window-created", (_, win) => { win.webContents.on("frame-created", (_, { frame }) => { - frame.once("dom-ready", () => { + frame?.once("dom-ready", () => { if (frame.url.startsWith("https://open.spotify.com/embed/")) { const settings = RendererSettings.store.plugins?.FixSpotifyEmbeds; if (!settings?.enabled) return; diff --git a/src/plugins/fixYoutubeEmbeds.desktop/native.ts b/src/plugins/fixYoutubeEmbeds.desktop/native.ts index 003cba9e3..950940b06 100644 --- a/src/plugins/fixYoutubeEmbeds.desktop/native.ts +++ b/src/plugins/fixYoutubeEmbeds.desktop/native.ts @@ -9,7 +9,7 @@ import { app } from "electron"; app.on("browser-window-created", (_, win) => { win.webContents.on("frame-created", (_, { frame }) => { - frame.once("dom-ready", () => { + frame?.once("dom-ready", () => { if (frame.url.startsWith("https://www.youtube.com/")) { const settings = RendererSettings.store.plugins?.FixYoutubeEmbeds; if (!settings?.enabled) return; diff --git a/src/plugins/friendInvites/index.ts b/src/plugins/friendInvites/index.ts index 3c5a324fd..0af611bc9 100644 --- a/src/plugins/friendInvites/index.ts +++ b/src/plugins/friendInvites/index.ts @@ -31,7 +31,7 @@ export default definePlugin({ { name: "create friend invite", description: "Generates a friend invite link.", - inputType: ApplicationCommandInputType.BOT, + inputType: ApplicationCommandInputType.BUILT_IN, execute: async (args, ctx) => { const invite = await FriendInvites.createFriendInvite(); @@ -48,7 +48,7 @@ export default definePlugin({ { name: "view friend invites", description: "View a list of all generated friend invites.", - inputType: ApplicationCommandInputType.BOT, + inputType: ApplicationCommandInputType.BUILT_IN, execute: async (_, ctx) => { const invites = await FriendInvites.getAllFriendInvites(); const friendInviteList = invites.map(i => @@ -67,7 +67,7 @@ export default definePlugin({ { name: "revoke friend invites", description: "Revokes all generated friend invites.", - inputType: ApplicationCommandInputType.BOT, + inputType: ApplicationCommandInputType.BUILT_IN, execute: async (_, ctx) => { await FriendInvites.revokeFriendInvites(); diff --git a/src/plugins/fullSearchContext/index.tsx b/src/plugins/fullSearchContext/index.tsx index 3f9e82109..7c0dbea68 100644 --- a/src/plugins/fullSearchContext/index.tsx +++ b/src/plugins/fullSearchContext/index.tsx @@ -22,11 +22,11 @@ import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import { NoopComponent } from "@utils/react"; import definePlugin from "@utils/types"; -import { filters, findByPropsLazy, waitFor } from "@webpack"; +import { filters, findByCodeLazy, waitFor } from "@webpack"; import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common"; import { Message } from "discord-types/general"; -const { useMessageMenu } = findByPropsLazy("useMessageMenu"); +const useMessageMenu = findByCodeLazy(".MESSAGE,commandTargetId:"); interface CopyIdMenuItemProps { id: string; diff --git a/src/plugins/fullUserInChatbox/index.tsx b/src/plugins/fullUserInChatbox/index.tsx index 5a0c41c01..ceeb56926 100644 --- a/src/plugins/fullUserInChatbox/index.tsx +++ b/src/plugins/fullUserInChatbox/index.tsx @@ -8,6 +8,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { findComponentByCodeLazy } from "@webpack"; +import { UserStore, useStateFromStores } from "@webpack/common"; import { ReactNode } from "react"; const UserMentionComponent = findComponentByCodeLazy(".USER_MENTION)"); @@ -34,14 +35,19 @@ export default definePlugin({ } ], - UserMentionComponent: ErrorBoundary.wrap((props: UserMentionComponentProps) => ( - <UserMentionComponent + UserMentionComponent: ErrorBoundary.wrap((props: UserMentionComponentProps) => { + const user = useStateFromStores([UserStore], () => UserStore.getUser(props.id)); + if (user == null) { + return props.originalComponent(); + } + + return <UserMentionComponent // This seems to be constant className="mention" userId={props.id} channelId={props.channelId} - /> - ), { + />; + }, { fallback: ({ wrappedProps: { originalComponent } }) => originalComponent() }) }); diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index 32d72fdbf..37d0ca4cd 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -17,16 +17,15 @@ */ import { definePluginSettings } from "@api/Settings"; -import { disableStyle, enableStyle } from "@api/Styles"; import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findComponentByCodeLazy } from "@webpack"; -import style from "./style.css?managed"; +import managedStyle from "./style.css?managed"; -const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:"); +const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON"); const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!; @@ -90,6 +89,8 @@ export default definePlugin({ dependencies: ["UserSettingsAPI"], settings, + managedStyle, + patches: [ { find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", @@ -102,11 +103,4 @@ export default definePlugin({ GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }), - start() { - enableStyle(style); - }, - - stop() { - disableStyle(style); - } }); diff --git a/src/plugins/hideAttachments/index.tsx b/src/plugins/hideAttachments/index.tsx index fe7f4ab92..fe9e68999 100644 --- a/src/plugins/hideAttachments/index.tsx +++ b/src/plugins/hideAttachments/index.tsx @@ -16,80 +16,92 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import "./styles.css"; + import { get, set } from "@api/DataStore"; -import { addButton, removeButton } from "@api/MessagePopover"; +import { updateMessage } from "@api/MessageUpdater"; +import { migratePluginSettings } from "@api/Settings"; import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; +import { classes } from "@utils/misc"; import definePlugin from "@utils/types"; import { ChannelStore } from "@webpack/common"; - -let style: HTMLStyleElement; +import { MessageSnapshot } from "@webpack/types"; const KEY = "HideAttachments_HiddenIds"; -let hiddenMessages: Set<string> = new Set(); -const getHiddenMessages = () => get(KEY).then(set => { - hiddenMessages = set ?? new Set<string>(); +let hiddenMessages = new Set<string>(); + +async function getHiddenMessages() { + hiddenMessages = await get(KEY) ?? new Set(); return hiddenMessages; -}); +} + const saveHiddenMessages = (ids: Set<string>) => set(KEY, ids); +migratePluginSettings("HideMedia", "HideAttachments"); + export default definePlugin({ - name: "HideAttachments", - description: "Hide attachments and Embeds for individual messages via hover button", + name: "HideMedia", + description: "Hide attachments and embeds for individual messages via hover button", authors: [Devs.Ven], - dependencies: ["MessagePopoverAPI"], + dependencies: ["MessageUpdaterAPI"], + + patches: [{ + find: "this.renderAttachments(", + replacement: { + match: /(?<=\i=)this\.render(?:Attachments|Embeds|StickersAccessories)\((\i)\)/g, + replace: "$self.shouldHide($1?.id)?null:$&" + } + }], + + renderMessagePopoverButton(msg) { + // @ts-ignore - discord-types lags behind discord. + const hasAttachmentsInShapshots = msg.messageSnapshots.some( + (snapshot: MessageSnapshot) => snapshot?.message.attachments.length + ); + + if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null; + + const isHidden = hiddenMessages.has(msg.id); + + return { + label: isHidden ? "Show Media" : "Hide Media", + icon: isHidden ? ImageVisible : ImageInvisible, + message: msg, + channel: ChannelStore.getChannel(msg.channel_id), + onClick: () => this.toggleHide(msg.channel_id, msg.id) + }; + }, + + renderMessageAccessory({ message }) { + if (!this.shouldHide(message.id)) return null; + + return ( + <span className={classes("vc-hideAttachments-accessory", !message.content && "vc-hideAttachments-no-content")}> + Media Hidden + </span> + ); + }, async start() { - style = document.createElement("style"); - style.id = "VencordHideAttachments"; - document.head.appendChild(style); - await getHiddenMessages(); - await this.buildCss(); - - addButton("HideAttachments", msg => { - if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null; - - const isHidden = hiddenMessages.has(msg.id); - - return { - label: isHidden ? "Show Attachments" : "Hide Attachments", - icon: isHidden ? ImageVisible : ImageInvisible, - message: msg, - channel: ChannelStore.getChannel(msg.channel_id), - onClick: () => this.toggleHide(msg.id) - }; - }); }, stop() { - style.remove(); hiddenMessages.clear(); - removeButton("HideAttachments"); }, - async buildCss() { - const elements = [...hiddenMessages].map(id => `#message-accessories-${id}`).join(","); - style.textContent = ` - :is(${elements}) :is([class*="embedWrapper"], [class*="clickableSticker"]) { - /* important is not necessary, but add it to make sure bad themes won't break it */ - display: none !important; - } - :is(${elements})::after { - content: "Attachments hidden"; - color: var(--text-muted); - font-size: 80%; - } - `; + shouldHide(messageId: string) { + return hiddenMessages.has(messageId); }, - async toggleHide(id: string) { + async toggleHide(channelId: string, messageId: string) { const ids = await getHiddenMessages(); - if (!ids.delete(id)) - ids.add(id); + if (!ids.delete(messageId)) + ids.add(messageId); await saveHiddenMessages(ids); - await this.buildCss(); + updateMessage(channelId, messageId); } }); diff --git a/src/plugins/hideAttachments/styles.css b/src/plugins/hideAttachments/styles.css new file mode 100644 index 000000000..4cbd10a6f --- /dev/null +++ b/src/plugins/hideAttachments/styles.css @@ -0,0 +1,10 @@ +.vc-hideAttachments-accessory { + color: var(--text-muted); + margin-top: 0.5em; + font-style: italic; + font-weight: 400; +} + +.vc-hideAttachments-no-content { + margin-top: 0; +} diff --git a/src/plugins/iLoveSpam/index.ts b/src/plugins/iLoveSpam/index.ts index bb0b20539..e30417455 100644 --- a/src/plugins/iLoveSpam/index.ts +++ b/src/plugins/iLoveSpam/index.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "hasFlag:{writable", replacement: { - match: /if\((\i)<=(?:1<<30|1073741824)\)return/, + match: /if\((\i)<=(?:0x40000000|(?:1<<30|1073741824))\)return/, replace: "if($1===(1<<20))return false;$&", }, }, diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index fac83687d..9e9610f6a 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -4,7 +4,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import * as DataStore from "@api/DataStore"; import { definePluginSettings, Settings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; @@ -13,7 +12,7 @@ import { Devs } from "@utils/constants"; import { Margins } from "@utils/margins"; import definePlugin, { OptionType } from "@utils/types"; import { findStoreLazy } from "@webpack"; -import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common"; +import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "@webpack/common"; const enum ActivitiesTypes { Game, @@ -62,7 +61,7 @@ const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(ac function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) { const s = settings.use(["ignoredActivities"]); - const { ignoredActivities = [] } = s; + const { ignoredActivities } = s; if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)"); return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)"); @@ -71,11 +70,9 @@ function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) { function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) { e.stopPropagation(); - const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id); - if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity); - else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex); - - recalculateActivities(); + const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id); + if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity); + else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1); } function recalculateActivities() { @@ -150,8 +147,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) { const settings = definePluginSettings({ importCustomRPC: { type: OptionType.COMPONENT, - description: "", - component: () => <ImportCustomRPCComponent /> + component: ImportCustomRPCComponent }, listMode: { type: OptionType.SELECT, @@ -171,7 +167,6 @@ const settings = definePluginSettings({ }, idsList: { type: OptionType.COMPONENT, - description: "", default: "", onChange(newValue: string) { const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean)); @@ -209,14 +204,13 @@ const settings = definePluginSettings({ description: "Ignore all competing activities (These are normally special game activities)", default: false, onChange: recalculateActivities + }, + ignoredActivities: { + type: OptionType.CUSTOM, + default: [] as IgnoredActivity[], + onChange: recalculateActivities } -}).withPrivateSettings<{ - ignoredActivities: IgnoredActivity[]; -}>(); - -function getIgnoredActivities() { - return settings.store.ignoredActivities ??= []; -} +}); function isActivityTypeIgnored(type: number, id?: string) { if (id && settings.store.idsList.includes(id)) { @@ -247,7 +241,7 @@ export default definePlugin({ find: '"LocalActivityStore"', replacement: [ { - match: /HANG_STATUS.+?(?=!\i\(\)\(\i,\i\)&&)(?<=(\i)\.push.+?)/, + match: /\.LISTENING.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/, replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` } ] @@ -284,29 +278,14 @@ export default definePlugin({ ], async start() { - // Migrate allowedIds - if (Settings.plugins.IgnoreActivities.allowedIds) { - settings.store.idsList = Settings.plugins.IgnoreActivities.allowedIds; - delete Settings.plugins.IgnoreActivities.allowedIds; // Remove allowedIds - } - - const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities"); - - if (oldIgnoredActivitiesData != null) { - settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values()) - .map(activity => ({ ...activity, name: "Unknown Name" })); - - DataStore.del("IgnoreActivities_ignoredActivities"); - } - - if (getIgnoredActivities().length !== 0) { + if (settings.store.ignoredActivities.length !== 0) { const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[]; - for (const [index, ignoredActivity] of getIgnoredActivities().entries()) { + for (const [index, ignoredActivity] of settings.store.ignoredActivities.entries()) { if (ignoredActivity.type !== ActivitiesTypes.Game) continue; if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) { - getIgnoredActivities().splice(index, 1); + settings.store.ignoredActivities.splice(index, 1); } } } @@ -316,11 +295,11 @@ export default definePlugin({ if (isActivityTypeIgnored(props.type, props.application_id)) return false; if (props.application_id != null) { - return !getIgnoredActivities().some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id)); + return !settings.store.ignoredActivities.some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id)); } else { const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath; if (exePath) { - return !getIgnoredActivities().some(activity => activity.id === exePath); + return !settings.store.ignoredActivities.some(activity => activity.id === exePath); } } diff --git a/src/plugins/imageZoom/components/Magnifier.tsx b/src/plugins/imageZoom/components/Magnifier.tsx index 31fa7a117..009165ff2 100644 --- a/src/plugins/imageZoom/components/Magnifier.tsx +++ b/src/plugins/imageZoom/components/Magnifier.tsx @@ -195,6 +195,7 @@ export const Magnifier = ErrorBoundary.wrap<MagnifierProps>(({ instance, size: i /> ) : ( <img + className={cl("image")} ref={imageRef} style={{ position: "absolute", diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 06e1dcd55..934a5c775 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -18,7 +18,6 @@ import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; -import { disableStyle, enableStyle } from "@api/Styles"; import { makeRange } from "@components/PluginSettings/components"; import { debounce } from "@shared/debounce"; import { Devs } from "@utils/constants"; @@ -29,7 +28,7 @@ import type { Root } from "react-dom/client"; import { Magnifier, MagnifierProps } from "./components/Magnifier"; import { ELEMENT_ID } from "./constants"; -import styles from "./styles.css?managed"; +import managedStyle from "./styles.css?managed"; export const settings = definePluginSettings({ saveZoomValues: { @@ -81,7 +80,12 @@ export const settings = definePluginSettings({ }); -const imageContextMenuPatch: NavContextMenuPatchCallback = children => { +const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => { + // Discord re-uses the image context menu for links to for the copy and open buttons + if ("href" in props) return; + // emojis in user statuses + if (props.target?.classList?.contains("emoji")) return; + const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]); children.push( @@ -155,6 +159,8 @@ export default definePlugin({ authors: [Devs.Aria], tags: ["ImageUtilities"], + managedStyle, + patches: [ { find: ".contain,SCALE_DOWN:", @@ -247,14 +253,12 @@ export default definePlugin({ }, start() { - enableStyle(styles); this.element = document.createElement("div"); this.element.classList.add("MagnifierContainer"); document.body.appendChild(this.element); }, stop() { - disableStyle(styles); // so componenetWillUnMount gets called if Magnifier component is still alive this.root && this.root.unmount(); this.element?.remove(); diff --git a/src/plugins/imageZoom/styles.css b/src/plugins/imageZoom/styles.css index 63a51e297..109521778 100644 --- a/src/plugins/imageZoom/styles.css +++ b/src/plugins/imageZoom/styles.css @@ -18,7 +18,7 @@ border-radius: 0; } -.vc-imgzoom-nearest-neighbor>img { +.vc-imgzoom-nearest-neighbor > .vc-imgzoom-image { image-rendering: pixelated; /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */ diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index 1cde814e7..f0be9ee54 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -34,7 +34,7 @@ export default definePlugin({ { find: "#{intl::FRIENDS_ALL_HEADER}", replacement: { - match: /toString\(\)\}\);case (\i\.\i)\.BLOCKED/, + match: /toString\(\)\}\);case (\i\.\i)\.PENDING/, replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED' }, }, @@ -50,15 +50,15 @@ export default definePlugin({ { find: "#{intl::FRIENDS_SECTION_ONLINE}", replacement: { - match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/, - replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&" - }, + match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/, + replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}` + } }, // Sections content { find: '"FriendsStore"', replacement: { - match: /(?<=case (\i\.\i)\.BLOCKED:return (\i)\.type===\i\.\i\.BLOCKED)/, + match: /(?<=case (\i\.\i)\.SUGGESTIONS:return \d+===(\i)\.type)/, replace: ";case $1.IMPLICIT:return $2.type===5" }, }, diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 129e42a0d..4a2688681 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -16,13 +16,22 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { addProfileBadge, removeProfileBadge } from "@api/Badges"; +import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { registerCommand, unregisterCommand } from "@api/Commands"; import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu"; -import { Settings } from "@api/Settings"; +import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators"; +import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories"; +import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations"; +import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents"; +import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover"; +import { Settings, SettingsStore } from "@api/Settings"; +import { disableStyle, enableStyle } from "@api/Styles"; import { Logger } from "@utils/Logger"; -import { canonicalizeFind } from "@utils/patches"; -import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types"; +import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches"; +import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types"; import { FluxDispatcher } from "@webpack/common"; +import { patches } from "@webpack/patcher"; import { FluxEvents } from "@webpack/types"; import Plugins from "~plugins"; @@ -33,7 +42,7 @@ const logger = new Logger("PluginManager", "#a6d189"); export const PMLogger = logger; export const plugins = Plugins; -export const patches = [] as Patch[]; +export { patches }; /** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */ let enabledPluginsSubscribedFlux = false; @@ -50,7 +59,7 @@ export function isPluginEnabled(p: string) { ) ?? false; } -export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string) { +export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string, pluginPath = `Vencord.Plugins.plugins[${JSON.stringify(pluginName)}]`) { const patch = newPatch as Patch; patch.plugin = pluginName; @@ -66,10 +75,12 @@ export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string) { patch.replacement = [patch.replacement]; } - if (IS_REPORTER) { - patch.replacement.forEach(r => { - delete r.predicate; - }); + for (const replacement of patch.replacement) { + canonicalizeReplacement(replacement, pluginPath); + + if (IS_REPORTER) { + delete replacement.predicate; + } } patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate()); @@ -83,6 +94,13 @@ function isReporterTestable(p: Plugin, part: ReporterTestable) { : (p.reporterTestable & part) === part; } +const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [ + "onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick", + "renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton" +]; + +const neededApiPlugins = new Set<string>(); + // First round-trip to mark and force enable dependencies // // FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only @@ -106,22 +124,46 @@ for (const p of pluginsValues) if (isPluginEnabled(p.name)) { dep.isDependency = true; }); - if (p.commands?.length) { - Plugins.CommandsAPI.isDependency = true; - settings.CommandsAPI.enabled = true; + if (p.commands?.length) neededApiPlugins.add("CommandsAPI"); + if (p.onBeforeMessageEdit || p.onBeforeMessageSend || p.onMessageClick) neededApiPlugins.add("MessageEventsAPI"); + if (p.renderChatBarButton) neededApiPlugins.add("ChatInputButtonAPI"); + if (p.renderMemberListDecorator) neededApiPlugins.add("MemberListDecoratorsAPI"); + if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI"); + if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI"); + if (p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI"); + if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI"); + + for (const key of pluginKeysToBind) { + p[key] &&= p[key].bind(p) as any; } } +for (const p of neededApiPlugins) { + Plugins[p].isDependency = true; + settings[p].enabled = true; +} + for (const p of pluginsValues) { if (p.settings) { - p.settings.pluginName = p.name; p.options ??= {}; - for (const [name, def] of Object.entries(p.settings.def)) { + + p.settings.pluginName = p.name; + for (const name in p.settings.def) { + const def = p.settings.def[name]; const checks = p.settings.checks?.[name]; p.options[name] = { ...def, ...checks }; } } + if (p.options) { + for (const name in p.options) { + const opt = p.options[name]; + if (opt.onChange != null) { + SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, opt.onChange); + } + } + } + if (p.patches && isPluginEnabled(p.name)) { if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) { for (const patch of p.patches) { @@ -215,7 +257,11 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc } export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) { - const { name, commands, contextMenus } = p; + const { + name, commands, contextMenus, managedStyle, userProfileBadge, + onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, + renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton + } = p; if (p.start) { logger.info("Starting plugin", name); @@ -249,7 +295,6 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: subscribePluginFluxEvents(p, FluxDispatcher); } - if (contextMenus) { logger.debug("Adding context menus patches of plugin", name); for (const navId in contextMenus) { @@ -257,11 +302,29 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: } } + if (managedStyle) enableStyle(managedStyle); + + if (userProfileBadge) addProfileBadge(userProfileBadge); + + if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit); + if (onBeforeMessageSend) addMessagePreSendListener(onBeforeMessageSend); + if (onMessageClick) addMessageClickListener(onMessageClick); + + if (renderChatBarButton) addChatBarButton(name, renderChatBarButton); + if (renderMemberListDecorator) addMemberListDecorator(name, renderMemberListDecorator); + if (renderMessageDecoration) addMessageDecoration(name, renderMessageDecoration); + if (renderMessageAccessory) addMessageAccessory(name, renderMessageAccessory); + if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton); + return true; }, p => `startPlugin ${p.name}`); export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) { - const { name, commands, contextMenus } = p; + const { + name, commands, contextMenus, managedStyle, userProfileBadge, + onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, + renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton + } = p; if (p.stop) { logger.info("Stopping plugin", name); @@ -300,5 +363,19 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu } } + if (managedStyle) disableStyle(managedStyle); + + if (userProfileBadge) removeProfileBadge(userProfileBadge); + + if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit); + if (onBeforeMessageSend) removeMessagePreSendListener(onBeforeMessageSend); + if (onMessageClick) removeMessageClickListener(onMessageClick); + + if (renderChatBarButton) removeChatBarButton(name); + if (renderMemberListDecorator) removeMemberListDecorator(name); + if (renderMessageDecoration) removeMessageDecoration(name); + if (renderMessageAccessory) removeMessageAccessory(name); + if (renderMessagePopoverButton) removeMessagePopoverButton(name); + return true; }, p => `stopPlugin ${p.name}`); diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index 1af8f4e5d..d6a39cbaf 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -16,8 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addChatBarButton, ChatBarButton } from "@api/ChatButtons"; -import { addButton, removeButton } from "@api/MessagePopover"; +import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { updateMessage } from "@api/MessageUpdater"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; @@ -66,7 +65,7 @@ function Indicator() { } -const ChatBarIcon: ChatBarButton = ({ isMainChat }) => { +const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => { if (!isMainChat) return null; return ( @@ -104,7 +103,7 @@ export default definePlugin({ name: "InvisibleChat", description: "Encrypt your Messages in a non-suspicious way!", authors: [Devs.SammCheese], - dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI", "MessageUpdaterAPI"], + dependencies: ["MessageUpdaterAPI"], reporterTestable: ReporterTestable.Patches, settings, @@ -125,36 +124,31 @@ export default definePlugin({ /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/, ), async start() { - addButton("InvisibleChat", message => { - return this.INV_REGEX.test(message?.content) - ? { - label: "Decrypt Message", - icon: this.popOverIcon, - message: message, - channel: ChannelStore.getChannel(message.channel_id), - onClick: async () => { - const res = await iteratePasswords(message); - - if (res) - this.buildEmbed(message, res); - else - buildDecModal({ message }); - } - } - : null; - }); - - addChatBarButton("InvisibleChat", ChatBarIcon); - const { default: StegCloak } = await getStegCloak(); steggo = new StegCloak(true, false); }, - stop() { - removeButton("InvisibleChat"); - removeButton("InvisibleChat"); + renderMessagePopoverButton(message) { + return this.INV_REGEX.test(message?.content) + ? { + label: "Decrypt Message", + icon: this.popOverIcon, + message: message, + channel: ChannelStore.getChannel(message.channel_id), + onClick: async () => { + const res = await iteratePasswords(message); + + if (res) + this.buildEmbed(message, res); + else + buildDecModal({ message }); + } + } + : null; }, + renderChatBarButton: ChatBarIcon, + // Gets the Embed of a Link async getEmbed(url: URL): Promise<Object | {}> { const { body } = await RestAPI.post({ diff --git a/src/plugins/ircColors/README.md b/src/plugins/ircColors/README.md new file mode 100644 index 000000000..9d9c7634b --- /dev/null +++ b/src/plugins/ircColors/README.md @@ -0,0 +1,17 @@ +# IrcColors + +Makes username colors in chat unique, like in IRC clients + +![Chat with IrcColors and Compact++ enabled](https://github.com/Vendicated/Vencord/assets/33988779/88e05c0b-a60a-4d10-949e-8b46e1d7226c) + +Improves chat readability by assigning every user an unique nickname color, +making distinguishing between different users easier. Inspired by the feature +in many IRC clients, such as HexChat or WeeChat. + +Keep in mind this overrides role colors in chat, so if you wish to know +someone's role color without checking their profile, enable the role dot: go to +**User Settings**, **Accessibility** and switch **Role Colors** to **Show role +colors next to names**. + +Created for use with the [Compact++](https://gitlab.com/Grzesiek11/compactplusplus-discord-theme) +theme. diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts new file mode 100644 index 000000000..af926043d --- /dev/null +++ b/src/plugins/ircColors/index.ts @@ -0,0 +1,109 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { definePluginSettings } from "@api/Settings"; +import { hash as h64 } from "@intrnl/xxhash64"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { useMemo } from "@webpack/common"; + +// Calculate a CSS color string based on the user ID +function calculateNameColorForUser(id?: string) { + const { lightness } = settings.use(["lightness"]); + const idHash = useMemo(() => id ? h64(id) : null, [id]); + + return idHash && `hsl(${idHash % 360n}, 100%, ${lightness}%)`; +} + +const settings = definePluginSettings({ + lightness: { + description: "Lightness, in %. Change if the colors are too light or too dark", + type: OptionType.NUMBER, + default: 70, + }, + memberListColors: { + description: "Replace role colors in the member list", + restartNeeded: true, + type: OptionType.BOOLEAN, + default: true + }, + applyColorOnlyToUsersWithoutColor: { + description: "Apply colors only to users who don't have a predefined color", + restartNeeded: false, + type: OptionType.BOOLEAN, + default: false + }, + applyColorOnlyInDms: { + description: "Apply colors only in direct messages; do not apply colors in servers.", + restartNeeded: false, + type: OptionType.BOOLEAN, + default: false + } +}); + +export default definePlugin({ + name: "IrcColors", + description: "Makes username colors in chat unique, like in IRC clients", + authors: [Devs.Grzesiek11, Devs.jamesbt365], + settings, + + patches: [ + { + find: '="SYSTEM_TAG"', + replacement: { + match: /(?<=className:\i\.username,style:.{0,50}:void 0,)/, + replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])}," + } + }, + { + find: "#{intl::GUILD_OWNER}),children:", + replacement: { + match: /(?<=\.MEMBER_LIST}\),\[\]\),)(.+?color:)null!=.{0,50}?(?=,)/, + replace: (_, rest) => `ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest}ircColor` + }, + predicate: () => settings.store.memberListColors + } + ], + + calculateNameColorForMessageContext(context: any) { + const id = context?.message?.author?.id; + const colorString = context?.author?.colorString; + const color = calculateNameColorForUser(id); + + if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) { + return colorString; + } + + return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString) + ? color + : colorString; + }, + calculateNameColorForListContext(context: any) { + const id = context?.user?.id; + const colorString = context?.colorString; + const color = calculateNameColorForUser(id); + + if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) { + return colorString; + } + + return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString) + ? color + : colorString; + } +}); diff --git a/src/plugins/lastfm/index.tsx b/src/plugins/lastfm/index.tsx index 02fd694f8..77fa27841 100644 --- a/src/plugins/lastfm/index.tsx +++ b/src/plugins/lastfm/index.tsx @@ -86,7 +86,7 @@ const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f"; const logger = new Logger("LastFMRichPresence"); -const presenceStore = findByPropsLazy("getLocalPresence"); +const PresenceStore = findByPropsLazy("getLocalPresence"); async function getApplicationAsset(key: string): Promise<string> { return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; @@ -124,6 +124,11 @@ const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: true, }, + hideWithActivity: { + description: "Hide Last.fm presence if you have any other presence", + type: OptionType.BOOLEAN, + default: false, + }, statusName: { description: "custom status text", type: OptionType.STRING, @@ -274,12 +279,16 @@ export default definePlugin({ }, async getActivity(): Promise<Activity | null> { + if (settings.store.hideWithActivity) { + if (PresenceStore.getActivities().some(a => a.application_id !== applicationId)) { + return null; + } + } + if (settings.store.hideWithSpotify) { - for (const activity of presenceStore.getActivities()) { - if (activity.type === ActivityType.LISTENING && activity.application_id !== applicationId) { - // there is already music status because of Spotify or richerCider (probably more) - return null; - } + if (PresenceStore.getActivities().some(a => a.type === ActivityType.LISTENING && a.application_id !== applicationId)) { + // there is already music status because of Spotify or richerCider (probably more) + return null; } } diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index 67bbc4ce8..ad7491cc3 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -65,10 +65,18 @@ export default definePlugin({ patches: [ { find: "{isSidebarVisible:", - replacement: { - match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, - replace: ":[$1?.startsWith('members')?$self.render():null,$2" - }, + replacement: [ + { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, + replace: ":[$1?.startsWith('members')?$self.render():null,$2", + noWarn: true + }, + { + match: /(?<=var\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, + replace: ":[$1?.startsWith('members')?$self.render():null,$2", + }, + ], predicate: () => settings.store.memberList }, { diff --git a/src/plugins/mentionAvatars/index.tsx b/src/plugins/mentionAvatars/index.tsx index 53ab93e38..c4a3adce1 100644 --- a/src/plugins/mentionAvatars/index.tsx +++ b/src/plugins/mentionAvatars/index.tsx @@ -57,7 +57,7 @@ export default definePlugin({ { find: ".ROLE_MENTION)", replacement: { - match: /children:\[\i&&.{0,50}\.RoleDot.{0,300},\i(?=\])/, + match: /children:\[\i&&.{0,100}className:\i.roleDot,.{0,200},\i(?=\])/, replace: "$&,$self.renderRoleIcon(arguments[0])" } }], diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts index 7437cace7..19ccaa955 100644 --- a/src/plugins/messageClickActions/index.ts +++ b/src/plugins/messageClickActions/index.ts @@ -16,7 +16,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addClickListener, removeClickListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -57,66 +56,64 @@ export default definePlugin({ name: "MessageClickActions", description: "Hold Backspace and click to delete, double click to edit/reply", authors: [Devs.Ven], - dependencies: ["MessageEventsAPI"], settings, start() { document.addEventListener("keydown", keydown); document.addEventListener("keyup", keyup); - - this.onClick = addClickListener((msg: any, channel, event) => { - const isMe = msg.author.id === UserStore.getCurrentUser().id; - if (!isDeletePressed) { - if (event.detail < 2) return; - if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return; - if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return; - if (msg.deleted === true) return; - - if (isMe) { - if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return; - - MessageActions.startEditMessage(channel.id, msg.id, msg.content); - event.preventDefault(); - } else { - if (!settings.store.enableDoubleClickToReply) return; - - const EPHEMERAL = 64; - if (msg.hasFlag(EPHEMERAL)) return; - - const isShiftPress = event.shiftKey && !settings.store.requireModifier; - const NoReplyMention = Vencord.Plugins.plugins.NoReplyMention as any as typeof import("../noReplyMention").default; - const shouldMention = Vencord.Plugins.isPluginEnabled("NoReplyMention") - ? NoReplyMention.shouldMention(msg, isShiftPress) - : !isShiftPress; - - FluxDispatcher.dispatch({ - type: "CREATE_PENDING_REPLY", - channel, - message: msg, - shouldMention, - showMentionToggle: channel.guild_id !== null - }); - } - } else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) { - if (msg.deleted) { - FluxDispatcher.dispatch({ - type: "MESSAGE_DELETE", - channelId: channel.id, - id: msg.id, - mlDeleted: true - }); - } else { - MessageActions.deleteMessage(channel.id, msg.id); - } - event.preventDefault(); - } - }); }, stop() { - removeClickListener(this.onClick); document.removeEventListener("keydown", keydown); document.removeEventListener("keyup", keyup); - } + }, + + onMessageClick(msg: any, channel, event) { + const isMe = msg.author.id === UserStore.getCurrentUser().id; + if (!isDeletePressed) { + if (event.detail < 2) return; + if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return; + if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return; + if (msg.deleted === true) return; + + if (isMe) { + if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return; + + MessageActions.startEditMessage(channel.id, msg.id, msg.content); + event.preventDefault(); + } else { + if (!settings.store.enableDoubleClickToReply) return; + + const EPHEMERAL = 64; + if (msg.hasFlag(EPHEMERAL)) return; + + const isShiftPress = event.shiftKey && !settings.store.requireModifier; + const NoReplyMention = Vencord.Plugins.plugins.NoReplyMention as any as typeof import("../noReplyMention").default; + const shouldMention = Vencord.Plugins.isPluginEnabled("NoReplyMention") + ? NoReplyMention.shouldMention(msg, isShiftPress) + : !isShiftPress; + + FluxDispatcher.dispatch({ + type: "CREATE_PENDING_REPLY", + channel, + message: msg, + shouldMention, + showMentionToggle: channel.guild_id !== null + }); + } + } else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) { + if (msg.deleted) { + FluxDispatcher.dispatch({ + type: "MESSAGE_DELETE", + channelId: channel.id, + id: msg.id, + mlDeleted: true + }); + } else { + MessageActions.deleteMessage(channel.id, msg.id); + } + event.preventDefault(); + } + }, }); diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index e4e5b8771..f0d5f9600 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -9,7 +9,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import definePlugin, { OptionType } from "@utils/types"; -import { findExportedComponentLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@webpack"; import { SnowflakeUtils, Tooltip } from "@webpack/common"; import { Message } from "discord-types/general"; @@ -26,7 +26,7 @@ interface Diff { } const DISCORD_KT_DELAY = 1471228928; -const HiddenVisually = findExportedComponentLazy("HiddenVisually"); +const HiddenVisually = findComponentByCodeLazy(".hiddenVisually]:"); export default definePlugin({ name: "MessageLatency", @@ -162,7 +162,7 @@ export default definePlugin({ </> } </Tooltip>; - }); + }, { noop: true }); }, Icon({ delta, fill, props }: { diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 98a153937..c248167f6 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addAccessory, removeAccessory } from "@api/MessageAccessories"; +import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories"; import { updateMessage } from "@api/MessageUpdater"; import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; @@ -120,11 +120,11 @@ const settings = definePluginSettings({ }, clearMessageCache: { type: OptionType.COMPONENT, - description: "Clear the linked message cache", - component: () => + component: () => ( <Button onClick={() => messageCache.clear()}> Clear the linked message cache </Button> + ) } }); @@ -373,7 +373,7 @@ export default definePlugin({ settings, start() { - addAccessory("messageLinkEmbed", props => { + addMessageAccessory("messageLinkEmbed", props => { if (!messageLinkRegex.test(props.message.content)) return null; @@ -391,6 +391,6 @@ export default definePlugin({ }, stop() { - removeAccessory("messageLinkEmbed"); + removeMessageAccessory("messageLinkEmbed"); } }); diff --git a/src/plugins/messageLogger/deleteStyleText.css b/src/plugins/messageLogger/deleteStyleText.css index a4e9a93c1..a114b7de8 100644 --- a/src/plugins/messageLogger/deleteStyleText.css +++ b/src/plugins/messageLogger/deleteStyleText.css @@ -1,24 +1,8 @@ -/* Message content highlighting */ -.messagelogger-deleted [class*="contents"] > :is(div, h1, h2, h3, p) { - color: var(--status-danger, #f04747) !important; -} - -/* Markdown title highlighting */ -.messagelogger-deleted [class*="contents"] :is(h1, h2, h3) { - color: var(--status-danger, #f04747) !important; -} - -/* Bot "thinking" text highlighting */ -.messagelogger-deleted [class*="colorStandard"] { - color: var(--status-danger, #f04747) !important; -} - -/* Embed highlighting */ -.messagelogger-deleted article :is(div, span, h1, h2, h3, p) { - color: var(--status-danger, #f04747) !important; -} - -.messagelogger-deleted a { - color: var(--red-460, #be3535) !important; - text-decoration: underline; +.messagelogger-deleted { + --text-normal: var(--status-danger, #f04747); + --interactive-normal: var(--status-danger, #f04747); + --text-muted: var(--status-danger, #f04747); + --embed-title: var(--red-460, #be3535); + --text-link: var(--red-460, #be3535); + --header-primary: var(--red-460, #be3535); } diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 333de9a4e..dee58f2f9 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -211,7 +211,8 @@ export default definePlugin({ collapseDeleted: { type: OptionType.BOOLEAN, description: "Whether to collapse deleted messages, similar to blocked messages", - default: false + default: false, + restartNeeded: true, }, logEdits: { type: OptionType.BOOLEAN, @@ -441,15 +442,10 @@ export default definePlugin({ { // Attachment renderer find: ".removeMosaicItemHoverButton", - group: true, replacement: [ { - match: /(className:\i,item:\i),/, - replace: "$1,item: deleted," - }, - { - match: /\[\i\.obscured\]:.+?,/, - replace: "$& 'messagelogger-deleted-attachment': deleted," + match: /\[\i\.obscured\]:.+?,(?<=item:(\i).+?)/, + replace: '$&"messagelogger-deleted-attachment":$1.originalItem?.deleted,' } ] }, @@ -500,7 +496,7 @@ export default definePlugin({ { // Message context base menu - find: "useMessageMenu:", + find: ".MESSAGE,commandTargetId:", replacement: [ { // Remove the first section if message is deleted diff --git a/src/plugins/messageLogger/messageLogger.css b/src/plugins/messageLogger/messageLogger.css index 2759129d9..a76e98888 100644 --- a/src/plugins/messageLogger/messageLogger.css +++ b/src/plugins/messageLogger/messageLogger.css @@ -4,12 +4,12 @@ .messagelogger-deleted :is( - video, + .messagelogger-deleted-attachment, .emoji, [data-type="sticker"], - iframe, - .messagelogger-deleted-attachment, - [class|="inlineMediaEmbed"] + [class*="embedIframe"], + [class*="embedSpotify"], + [class*="imageContainer"] ) { filter: grayscale(1) !important; transition: 150ms filter ease-in-out; @@ -17,18 +17,14 @@ &[class*="hiddenMosaicItem_"] { filter: grayscale(1) blur(var(--custom-message-attachment-spoiler-blur-radius, 44px)) !important; } + + &:hover { + filter: grayscale(0) !important; + } } -.messagelogger-deleted -:is( - video, - .emoji, - [data-type="sticker"], - iframe, - .messagelogger-deleted-attachment, - [class|="inlineMediaEmbed"] -):hover { - filter: grayscale(0) !important; +.messagelogger-deleted [class*="spoilerWarning"] { + color: var(--status-danger); } .theme-dark .messagelogger-edited { diff --git a/src/plugins/messageTags/index.ts b/src/plugins/messageTags/index.ts index 5ba4ab94a..49e88c42d 100644 --- a/src/plugins/messageTags/index.ts +++ b/src/plugins/messageTags/index.ts @@ -18,7 +18,7 @@ import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands"; import * as DataStore from "@api/DataStore"; -import { Settings } from "@api/Settings"; +import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -29,23 +29,23 @@ const MessageTagsMarker = Symbol("MessageTags"); interface Tag { name: string; message: string; - enabled: boolean; } -const getTags = () => DataStore.get(DATA_KEY).then<Tag[]>(t => t ?? []); -const getTag = (name: string) => DataStore.get(DATA_KEY).then<Tag | null>((t: Tag[]) => (t ?? []).find((tt: Tag) => tt.name === name) ?? null); -const addTag = async (tag: Tag) => { - const tags = await getTags(); - tags.push(tag); - DataStore.set(DATA_KEY, tags); - return tags; -}; -const removeTag = async (name: string) => { - let tags = await getTags(); - tags = await tags.filter((t: Tag) => t.name !== name); - DataStore.set(DATA_KEY, tags); - return tags; -}; +function getTags() { + return settings.store.tagsList; +} + +function getTag(name: string) { + return settings.store.tagsList[name] ?? null; +} + +function addTag(tag: Tag) { + settings.store.tagsList[tag.name] = tag; +} + +function removeTag(name: string) { + delete settings.store.tagsList[name]; +} function createTagCommand(tag: Tag) { registerCommand({ @@ -53,14 +53,14 @@ function createTagCommand(tag: Tag) { description: tag.name, inputType: ApplicationCommandInputType.BUILT_IN_TEXT, execute: async (_, ctx) => { - if (!await getTag(tag.name)) { + if (!getTag(tag.name)) { sendBotMessage(ctx.channel.id, { content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)` }); return { content: `/${tag.name}` }; } - if (Settings.plugins.MessageTags.clyde) sendBotMessage(ctx.channel.id, { + if (settings.store.clyde) sendBotMessage(ctx.channel.id, { content: `${EMOTE} The tag **${tag.name}** has been sent!` }); return { content: tag.message.replaceAll("\\n", "\n") }; @@ -69,22 +69,38 @@ function createTagCommand(tag: Tag) { }, "CustomTags"); } +const settings = definePluginSettings({ + clyde: { + name: "Clyde message on send", + description: "If enabled, clyde will send you an ephemeral message when a tag was used.", + type: OptionType.BOOLEAN, + default: true + }, + tagsList: { + type: OptionType.CUSTOM, + default: {} as Record<string, Tag>, + } +}); export default definePlugin({ name: "MessageTags", description: "Allows you to save messages and to use them with a simple command.", authors: [Devs.Luna], - options: { - clyde: { - name: "Clyde message on send", - description: "If enabled, clyde will send you an ephemeral message when a tag was used.", - type: OptionType.BOOLEAN, - default: true - } - }, + settings, async start() { - for (const tag of await getTags()) createTagCommand(tag); + // TODO(OptionType.CUSTOM Related): Remove DataStore tags migration once enough time has passed + const oldTags = await DataStore.get<Tag[]>(DATA_KEY); + if (oldTags != null) { + // @ts-ignore + settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag]))); + await DataStore.del(DATA_KEY); + } + + const tags = getTags(); + for (const tagName in tags) { + createTagCommand(tags[tagName]); + } }, commands: [ @@ -153,19 +169,18 @@ export default definePlugin({ const name: string = findOption(args[0].options, "tag-name", ""); const message: string = findOption(args[0].options, "message", ""); - if (await getTag(name)) + if (getTag(name)) return sendBotMessage(ctx.channel.id, { content: `${EMOTE} A Tag with the name **${name}** already exists!` }); const tag = { name: name, - enabled: true, message: message }; createTagCommand(tag); - await addTag(tag); + addTag(tag); sendBotMessage(ctx.channel.id, { content: `${EMOTE} Successfully created the tag **${name}**!` @@ -175,13 +190,13 @@ export default definePlugin({ case "delete": { const name: string = findOption(args[0].options, "tag-name", ""); - if (!await getTag(name)) + if (!getTag(name)) return sendBotMessage(ctx.channel.id, { content: `${EMOTE} A Tag with the name **${name}** does not exist!` }); unregisterCommand(name); - await removeTag(name); + removeTag(name); sendBotMessage(ctx.channel.id, { content: `${EMOTE} Successfully deleted the tag **${name}**!` @@ -192,10 +207,8 @@ export default definePlugin({ sendBotMessage(ctx.channel.id, { embeds: [ { - // @ts-ignore title: "All Tags:", - // @ts-ignore - description: (await getTags()) + description: Object.values(getTags()) .map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`) .join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`, // @ts-ignore @@ -208,7 +221,7 @@ export default definePlugin({ } case "preview": { const name: string = findOption(args[0].options, "tag-name", ""); - const tag = await getTag(name); + const tag = getTag(name); if (!tag) return sendBotMessage(ctx.channel.id, { diff --git a/src/plugins/moreUserTags/index.tsx b/src/plugins/moreUserTags/index.tsx deleted file mode 100644 index 8029b4833..000000000 --- a/src/plugins/moreUserTags/index.tsx +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * 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 <https://www.gnu.org/licenses/>. -*/ - -import { definePluginSettings } from "@api/Settings"; -import { Flex } from "@components/Flex"; -import { Devs } from "@utils/constants"; -import { getIntlMessage } from "@utils/discord"; -import { Margins } from "@utils/margins"; -import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy, findLazy } from "@webpack"; -import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip } from "@webpack/common"; -import type { Permissions, RC } from "@webpack/types"; -import type { Channel, Guild, Message, User } from "discord-types/general"; - -interface Tag { - // name used for identifying, must be alphanumeric + underscores - name: string; - // name shown on the tag itself, can be anything probably; automatically uppercase'd - displayName: string; - description: string; - permissions?: Permissions[]; - condition?(message: Message | null, user: User, channel: Channel): boolean; -} - -interface TagSetting { - text: string; - showInChat: boolean; - showInNotChat: boolean; -} -interface TagSettings { - WEBHOOK: TagSetting, - OWNER: TagSetting, - ADMINISTRATOR: TagSetting, - MODERATOR_STAFF: TagSetting, - MODERATOR: TagSetting, - VOICE_MODERATOR: TagSetting, - TRIAL_MODERATOR: TagSetting, - [k: string]: TagSetting; -} - -// PermissionStore.computePermissions will not work here since it only gets permissions for the current user -const computePermissions: (options: { - user?: { id: string; } | string | null; - context?: Guild | Channel | null; - overwrites?: Channel["permissionOverwrites"] | null; - checkElevated?: boolean /* = true */; - excludeGuildPermissions?: boolean /* = false */; -}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()"); - -const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; }; - -const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot(); - -const tags: Tag[] = [ - { - name: "WEBHOOK", - displayName: "Webhook", - description: "Messages sent by webhooks", - condition: isWebhook - }, { - name: "OWNER", - displayName: "Owner", - description: "Owns the server", - condition: (_, user, channel) => GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id - }, { - name: "ADMINISTRATOR", - displayName: "Admin", - description: "Has the administrator permission", - permissions: ["ADMINISTRATOR"] - }, { - name: "MODERATOR_STAFF", - displayName: "Staff", - description: "Can manage the server, channels or roles", - permissions: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES"] - }, { - name: "MODERATOR", - displayName: "Mod", - description: "Can manage messages or kick/ban people", - permissions: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"] - }, { - name: "VOICE_MODERATOR", - displayName: "VC Mod", - description: "Can manage voice chats", - permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"] - }, { - name: "CHAT_MODERATOR", - displayName: "Chat Mod", - description: "Can timeout people", - permissions: ["MODERATE_MEMBERS"] - } -]; -const defaultSettings = Object.fromEntries( - tags.map(({ name, displayName }) => [name, { text: displayName, showInChat: true, showInNotChat: true }]) -) as TagSettings; - -function SettingsComponent() { - const tagSettings = settings.store.tagSettings ??= defaultSettings; - - return ( - <Flex flexDirection="column"> - {tags.map(t => ( - <Card key={t.name} style={{ padding: "1em 1em 0" }}> - <Forms.FormTitle style={{ width: "fit-content" }}> - <Tooltip text={t.description}> - {({ onMouseEnter, onMouseLeave }) => ( - <div - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} - > - {t.displayName} Tag <Tag type={Tag.Types[t.name]} /> - </div> - )} - </Tooltip> - </Forms.FormTitle> - - <TextInput - type="text" - value={tagSettings[t.name]?.text ?? t.displayName} - placeholder={`Text on tag (default: ${t.displayName})`} - onChange={v => tagSettings[t.name].text = v} - className={Margins.bottom16} - /> - - <Switch - value={tagSettings[t.name]?.showInChat ?? true} - onChange={v => tagSettings[t.name].showInChat = v} - hideBorder - > - Show in messages - </Switch> - - <Switch - value={tagSettings[t.name]?.showInNotChat ?? true} - onChange={v => tagSettings[t.name].showInNotChat = v} - hideBorder - > - Show in member list and profiles - </Switch> - </Card> - ))} - </Flex> - ); -} - -const settings = definePluginSettings({ - dontShowForBots: { - description: "Don't show extra tags for bots (excluding webhooks)", - type: OptionType.BOOLEAN - }, - dontShowBotTag: { - description: "Only show extra tags for bots / Hide [BOT] text", - type: OptionType.BOOLEAN - }, - tagSettings: { - type: OptionType.COMPONENT, - component: SettingsComponent, - description: "fill me" - } -}); - -export default definePlugin({ - name: "MoreUserTags", - description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)", - authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN], - settings, - patches: [ - // add tags to the tag list - { - find: ".ORIGINAL_POSTER=", - replacement: { - match: /(?=(\i)\[\i\.BOT)/, - replace: "$self.genTagTypes($1);" - } - }, - { - find: "#{intl::DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL}", - replacement: [ - // make the tag show the right text - { - match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(.{0,40}#{intl::APP_TAG}\))/, - replace: (_, origSwitch, variant, tags, displayedText, originalText) => - `${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}],${originalText})}` - }, - // show OP tags correctly - { - match: /(\i)=(\i)===\i(?:\.\i)?\.ORIGINAL_POSTER/, - replace: "$1=$self.isOPTag($2)" - }, - // add HTML data attributes (for easier theming) - { - match: /.botText,children:(\i)}\)]/, - replace: "$&,'data-tag':$1.toLowerCase()" - } - ], - }, - // in messages - { - find: ".Types.ORIGINAL_POSTER", - replacement: { - match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/, - replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null" - } - }, - // in the member list - { - find: "#{intl::GUILD_OWNER}),children:", - replacement: { - match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/, - replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'" - } - }, - // pass channel id down props to be used in profiles - { - find: ".hasAvatarForGuild(null==", - replacement: { - match: /(?=usernameIcon:)/, - replace: "moreTags_channelId:arguments[0].channelId," - } - }, - { - find: "#{intl::USER_PROFILE_PRONOUNS}", - replacement: { - match: /(?=,hideBotTag:!0)/, - replace: ",moreTags_channelId:arguments[0].moreTags_channelId" - } - }, - // in profiles - { - find: ",overrideDiscriminator:", - group: true, - replacement: [ - { - // prevent channel id from getting ghosted - // it's either this or extremely long lookbehind - match: /user:\i,nick:\i,/, - replace: "$&moreTags_channelId," - }, { - match: /,botType:(\i),botVerified:(\i),(?!discriminatorClass:)(?<=user:(\i).+?)/g, - replace: ",botType:$self.getTag({user:$3,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),botVerified:$2," - } - ] - }, - ], - - start() { - settings.store.tagSettings ??= defaultSettings; - - // newly added field might be missing from old users - settings.store.tagSettings.CHAT_MODERATOR ??= { - text: "Chat Mod", - showInChat: true, - showInNotChat: true - }; - }, - - getPermissions(user: User, channel: Channel): string[] { - const guild = GuildStore.getGuild(channel?.guild_id); - if (!guild) return []; - - const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites }); - return Object.entries(PermissionsBits) - .map(([perm, permInt]) => - permissions & permInt ? perm : "" - ) - .filter(Boolean); - }, - - genTagTypes(obj) { - let i = 100; - tags.forEach(({ name }) => { - obj[name] = ++i; - obj[i] = name; - obj[`${name}-BOT`] = ++i; - obj[i] = `${name}-BOT`; - obj[`${name}-OP`] = ++i; - obj[i] = `${name}-OP`; - }); - }, - - isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]), - - getTagText(passedTagName: string, originalText: string) { - try { - const [tagName, variant] = passedTagName.split("-"); - if (!passedTagName) return getIntlMessage("APP_TAG"); - const tag = tags.find(({ name }) => tagName === name); - if (!tag) return getIntlMessage("APP_TAG"); - if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return getIntlMessage("APP_TAG"); - - const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName; - switch (variant) { - case "OP": - return `${getIntlMessage("BOT_TAG_FORUM_ORIGINAL_POSTER")} • ${tagText}`; - case "BOT": - return `${getIntlMessage("APP_TAG")} • ${tagText}`; - default: - return tagText; - } - } catch { - return originalText; - } - }, - - getTag({ - message, user, channelId, origType, location, channel - }: { - message?: Message, - user: User & { isClyde(): boolean; }, - channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; }, - channelId?: string; - origType?: number; - location: "chat" | "not-chat"; - }): number | null { - if (!user) - return null; - if (location === "chat" && user.id === "1") - return Tag.Types.OFFICIAL; - if (user.isClyde()) - return Tag.Types.AI; - - let type = typeof origType === "number" ? origType : null; - - channel ??= ChannelStore.getChannel(channelId!) as any; - if (!channel) return type; - - const settings = this.settings.store; - const perms = this.getPermissions(user, channel); - - for (const tag of tags) { - if (location === "chat" && !settings.tagSettings[tag.name].showInChat) continue; - if (location === "not-chat" && !settings.tagSettings[tag.name].showInNotChat) continue; - - // If the owner tag is disabled, and the user is the owner of the guild, - // avoid adding other tags because the owner will always match the condition for them - if ( - tag.name !== "OWNER" && - GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id && - (location === "chat" && !settings.tagSettings.OWNER.showInChat) || - (location === "not-chat" && !settings.tagSettings.OWNER.showInNotChat) - ) continue; - - if ( - tag.permissions?.some(perm => perms.includes(perm)) || - (tag.condition?.(message!, user, channel)) - ) { - if ((channel.isForumPost() || channel.isMediaPost()) && channel.ownerId === user.id) - type = Tag.Types[`${tag.name}-OP`]; - else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag) - type = Tag.Types[`${tag.name}-BOT`]; - else - type = Tag.Types[tag.name]; - break; - } - } - return type; - } -}); diff --git a/src/plugins/noBlockedMessages/index.ts b/src/plugins/noBlockedMessages/index.ts index 48ca63d18..95b53c6b3 100644 --- a/src/plugins/noBlockedMessages/index.ts +++ b/src/plugins/noBlockedMessages/index.ts @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Settings } from "@api/Settings"; +import { definePluginSettings, migratePluginSetting } from "@api/Settings"; import { Devs } from "@utils/constants"; import { runtimeHashMessageKey } from "@utils/intlHash"; import { Logger } from "@utils/Logger"; @@ -32,10 +32,29 @@ interface MessageDeleteProps { collapsedReason: () => any; } +// Remove this migration once enough time has passed +migratePluginSetting("NoBlockedMessages", "ignoreBlockedMessages", "ignoreMessages"); +const settings = definePluginSettings({ + ignoreMessages: { + description: "Completely ignores incoming messages from blocked and ignored (if enabled) users", + type: OptionType.BOOLEAN, + default: false, + restartNeeded: true + }, + applyToIgnoredUsers: { + description: "Additionally apply to 'ignored' users", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: false + } +}); + export default definePlugin({ name: "NoBlockedMessages", - description: "Hides all blocked messages from chat completely.", - authors: [Devs.rushii, Devs.Samu], + description: "Hides all blocked/ignored messages from chat completely", + authors: [Devs.rushii, Devs.Samu, Devs.jamesbt365], + settings, + patches: [ { find: "#{intl::BLOCKED_MESSAGES_HIDE}", @@ -51,38 +70,40 @@ export default definePlugin({ '"ReadStateStore"' ].map(find => ({ find, - predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true, + predicate: () => settings.store.ignoreMessages, replacement: [ { match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/, - replace: (_, _funcName, props) => `if($self.isBlocked(${props}.message))return;` + replace: (_, _funcName, props) => `if($self.shouldIgnoreMessage(${props}.message))return;` } ] })) ], - options: { - ignoreBlockedMessages: { - description: "Completely ignores (recent) incoming messages from blocked users (locally).", - type: OptionType.BOOLEAN, - default: false, - restartNeeded: true, - }, - }, - isBlocked(message: Message) { + shouldIgnoreMessage(message: Message) { try { - return RelationshipStore.isBlocked(message.author.id); + if (RelationshipStore.isBlocked(message.author.id)) { + return true; + } + return settings.store.applyToIgnoredUsers && RelationshipStore.isIgnored(message.author.id); } catch (e) { - new Logger("NoBlockedMessages").error("Failed to check if user is blocked:", e); + new Logger("NoBlockedMessages").error("Failed to check if user is blocked or ignored:", e); + return false; } }, - shouldHide(props: MessageDeleteProps) { + shouldHide(props: MessageDeleteProps): boolean { try { - return props.collapsedReason() === i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")](); + const collapsedReason = props.collapsedReason(); + const blockedReason = i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")](); + const ignoredReason = settings.store.applyToIgnoredUsers + ? i18n.t[runtimeHashMessageKey("IGNORED_MESSAGE_COUNT")]() + : null; + + return collapsedReason === blockedReason || collapsedReason === ignoredReason; } catch (e) { console.error(e); + return false; } - return false; } }); diff --git a/src/plugins/noScreensharePreview/index.ts b/src/plugins/noScreensharePreview/index.ts deleted file mode 100644 index d4bb9c1eb..000000000 --- a/src/plugins/noScreensharePreview/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * 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 <https://www.gnu.org/licenses/>. -*/ - -import { getUserSettingLazy } from "@api/UserSettings"; -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -const DisableStreamPreviews = getUserSettingLazy<boolean>("voiceAndVideo", "disableStreamPreviews")!; - -// @TODO: Delete this plugin in the future -export default definePlugin({ - name: "NoScreensharePreview", - description: "Disables screenshare previews from being sent.", - authors: [Devs.Nuckyz], - - start() { - if (!DisableStreamPreviews.getSetting()) { - DisableStreamPreviews.updateSetting(true); - } - }, - - stop() { - if (DisableStreamPreviews.getSetting()) { - DisableStreamPreviews.updateSetting(false); - } - } -}); diff --git a/src/plugins/notificationVolume/index.ts b/src/plugins/notificationVolume/index.ts index bc3c7539d..d320d76f1 100644 --- a/src/plugins/notificationVolume/index.ts +++ b/src/plugins/notificationVolume/index.ts @@ -25,9 +25,9 @@ export default definePlugin({ settings, patches: [ { - find: "_ensureAudio(){", + find: "ensureAudio(){", replacement: { - match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/, + match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/g, replace: "$self.settings.store.notificationVolume/100*" }, }, diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index 09fc2f3b7..1c90b5290 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -100,8 +100,9 @@ export default definePlugin({ replace: "true" }, { - match: /!\(0,\i\.isDesktop\)\(\)/, - replace: "false" + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /(!)?\(0,\i\.isDesktop\)\(\)/, + replace: (_, not) => not ? "false" : "true" } ] }, diff --git a/src/plugins/permissionFreeWill/index.ts b/src/plugins/permissionFreeWill/index.ts index c45cbff60..8a6135145 100644 --- a/src/plugins/permissionFreeWill/index.ts +++ b/src/plugins/permissionFreeWill/index.ts @@ -46,8 +46,9 @@ export default definePlugin({ find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}", replacement: [ { - match: /{(\i:function\(\){return \i},?){2}}/, - replace: m => m.replaceAll(canonicalizeMatch(/return \i/g), "return ()=>Promise.resolve(true)") + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /{(?:\i:(?:function\(\){return |\(\)=>)\i}?,?){2}}/, + replace: m => m.replaceAll(canonicalizeMatch(/(function\(\){return |\(\)=>)\i/g), "$1()=>Promise.resolve(true)") } ], predicate: () => settings.store.onboarding diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index 341971ff8..02662fe97 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -157,7 +157,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea src={user.getAvatarURL(void 0, void 0, false)} /> )} - <Text variant="text-md/normal"> + <Text variant="text-md/normal" className={cl("modal-list-item-text")}> { permission.type === PermissionType.Role ? role?.name ?? "Unknown Role" diff --git a/src/plugins/permissionsViewer/styles.css b/src/plugins/permissionsViewer/styles.css index b7e420964..2ca61025d 100644 --- a/src/plugins/permissionsViewer/styles.css +++ b/src/plugins/permissionsViewer/styles.css @@ -73,7 +73,7 @@ background-color: var(--background-modifier-selected); } -.vc-permviewer-modal-list-item > div { +.vc-permviewer-modal-list-item-text { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; diff --git a/src/plugins/pinDms/components/CreateCategoryModal.tsx b/src/plugins/pinDms/components/CreateCategoryModal.tsx index 0568c1adb..8c0fc6599 100644 --- a/src/plugins/pinDms/components/CreateCategoryModal.tsx +++ b/src/plugins/pinDms/components/CreateCategoryModal.tsx @@ -6,12 +6,11 @@ import { classNameFactory } from "@api/Styles"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal"; -import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy } from "@webpack"; -import { Button, Forms, Text, TextInput, Toasts, useEffect, useState } from "@webpack/common"; +import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; +import { Button, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common"; import { DEFAULT_COLOR, SWATCHES } from "../constants"; -import { categories, Category, createCategory, getCategory, updateCategory } from "../data"; -import { forceUpdate } from "../index"; +import { categoryLen, createCategory, getCategory } from "../data"; interface ColorPickerProps { color: number | null; @@ -31,7 +30,7 @@ interface ColorPickerWithSwatchesProps { } const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); -const ColorPickerWithSwatches = findExportedComponentLazy<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker"); +const ColorPickerWithSwatches = findComponentByCodeLazy<ColorPickerWithSwatchesProps>('id:"color-picker"'); export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/); @@ -39,45 +38,45 @@ const cl = classNameFactory("vc-pindms-modal-"); interface Props { categoryId: string | null; - initalChannelId: string | null; + initialChannelId: string | null; modalProps: ModalProps; } function useCategory(categoryId: string | null, initalChannelId: string | null) { - const [category, setCategory] = useState<Category | null>(null); - - useEffect(() => { - if (categoryId) - setCategory(getCategory(categoryId)!); - else if (initalChannelId) - setCategory({ + const category = useMemo(() => { + if (categoryId) { + return getCategory(categoryId); + } else if (initalChannelId) { + return { id: Toasts.genId(), - name: `Pin Category ${categories.length + 1}`, + name: `Pin Category ${categoryLen() + 1}`, color: DEFAULT_COLOR, collapsed: false, channels: [initalChannelId] - }); + }; + } }, [categoryId, initalChannelId]); - return { - category, - setCategory - }; + return category; } -export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Props) { - const { category, setCategory } = useCategory(categoryId, initalChannelId); - +export function NewCategoryModal({ categoryId, modalProps, initialChannelId }: Props) { + const category = useCategory(categoryId, initialChannelId); if (!category) return null; - const onSave = async (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>) => { - e.preventDefault(); - if (!categoryId) - await createCategory(category); - else - await updateCategory(category); + const [name, setName] = useState(category.name); + const [color, setColor] = useState(category.color); + + const onSave = (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>) => { + e.preventDefault(); + + category.name = name; + category.color = color; + + if (!categoryId) { + createCategory(category); + } - forceUpdate(); modalProps.onClose(); }; @@ -93,25 +92,25 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr <Forms.FormSection> <Forms.FormTitle>Name</Forms.FormTitle> <TextInput - value={category.name} - onChange={e => setCategory({ ...category, name: e })} + value={name} + onChange={e => setName(e)} /> </Forms.FormSection> <Forms.FormDivider /> <Forms.FormSection> <Forms.FormTitle>Color</Forms.FormTitle> <ColorPickerWithSwatches - key={category.name} + key={category.id} defaultColor={DEFAULT_COLOR} colors={SWATCHES} - onChange={c => setCategory({ ...category, color: c! })} - value={category.color} + onChange={c => setColor(c!)} + value={color} renderDefaultButton={() => null} renderCustomButton={() => ( <ColorPicker - color={category.color} - onChange={c => setCategory({ ...category, color: c! })} - key={category.name} + color={color} + onChange={c => setColor(c!)} + key={category.id} showEyeDropper={false} /> )} @@ -119,7 +118,7 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr </Forms.FormSection> </ModalContent> <ModalFooter> - <Button type="submit" onClick={onSave} disabled={!category.name}>{categoryId ? "Save" : "Create"}</Button> + <Button type="submit" onClick={onSave} disabled={!name}>{categoryId ? "Save" : "Create"}</Button> </ModalFooter> </form> </ModalRoot> @@ -129,6 +128,6 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr export const openCategoryModal = (categoryId: string | null, channelId: string | null) => openModalLazy(async () => { await requireSettingsMenu(); - return modalProps => <NewCategoryModal categoryId={categoryId} modalProps={modalProps} initalChannelId={channelId} />; + return modalProps => <NewCategoryModal categoryId={categoryId} modalProps={modalProps} initialChannelId={channelId} />; }); diff --git a/src/plugins/pinDms/components/contextMenu.tsx b/src/plugins/pinDms/components/contextMenu.tsx index aa5d1993e..6fc4e6743 100644 --- a/src/plugins/pinDms/components/contextMenu.tsx +++ b/src/plugins/pinDms/components/contextMenu.tsx @@ -7,8 +7,8 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Menu } from "@webpack/common"; -import { addChannelToCategory, canMoveChannelInDirection, categories, isPinned, moveChannel, removeChannelFromCategory } from "../data"; -import { forceUpdate, PinOrder, settings } from "../index"; +import { addChannelToCategory, canMoveChannelInDirection, currentUserCategories, isPinned, moveChannel, removeChannelFromCategory } from "../data"; +import { PinOrder, settings } from "../index"; import { openCategoryModal } from "./CreateCategoryModal"; function createPinMenuItem(channelId: string) { @@ -31,12 +31,12 @@ function createPinMenuItem(channelId: string) { <Menu.MenuSeparator /> { - categories.map(category => ( + currentUserCategories.map(category => ( <Menu.MenuItem key={category.id} id={`pin-category-${category.id}`} label={category.name} - action={() => addChannelToCategory(channelId, category.id).then(forceUpdate)} + action={() => addChannelToCategory(channelId, category.id)} /> )) } @@ -49,7 +49,7 @@ function createPinMenuItem(channelId: string) { id="unpin-dm" label="Unpin DM" color="danger" - action={() => removeChannelFromCategory(channelId).then(forceUpdate)} + action={() => removeChannelFromCategory(channelId)} /> { @@ -57,7 +57,7 @@ function createPinMenuItem(channelId: string) { <Menu.MenuItem id="move-up" label="Move Up" - action={() => moveChannel(channelId, -1).then(forceUpdate)} + action={() => moveChannel(channelId, -1)} /> ) } @@ -67,7 +67,7 @@ function createPinMenuItem(channelId: string) { <Menu.MenuItem id="move-down" label="Move Down" - action={() => moveChannel(channelId, 1).then(forceUpdate)} + action={() => moveChannel(channelId, 1)} /> ) } diff --git a/src/plugins/pinDms/data.ts b/src/plugins/pinDms/data.ts index a4e40dde0..d689bd2af 100644 --- a/src/plugins/pinDms/data.ts +++ b/src/plugins/pinDms/data.ts @@ -6,10 +6,10 @@ import * as DataStore from "@api/DataStore"; import { Settings } from "@api/Settings"; +import { useForceUpdater } from "@utils/react"; import { UserStore } from "@webpack/common"; -import { DEFAULT_COLOR } from "./constants"; -import { forceUpdate, PinOrder, PrivateChannelSortStore, settings } from "./index"; +import { PinOrder, PrivateChannelSortStore, settings } from "./index"; export interface Category { id: string; @@ -24,104 +24,92 @@ const CATEGORY_MIGRATED_PINDMS_KEY = "PinDMsMigratedPinDMs"; const CATEGORY_MIGRATED_KEY = "PinDMsMigratedOldCategories"; const OLD_CATEGORY_KEY = "BetterPinDMsCategories-"; - -export let categories: Category[] = []; - -export async function saveCats(cats: Category[]) { - const { id } = UserStore.getCurrentUser(); - await DataStore.set(CATEGORY_BASE_KEY + id, cats); -} +let forceUpdateDms: (() => void) | undefined = undefined; +export let currentUserCategories: Category[] = []; export async function init() { - const id = UserStore.getCurrentUser()?.id; - await initCategories(id); - await migrateData(id); - forceUpdate(); + await migrateData(); + + const userId = UserStore.getCurrentUser()?.id; + if (userId == null) return; + + currentUserCategories = settings.store.userBasedCategoryList[userId] ??= []; + forceUpdateDms?.(); } -export async function initCategories(userId: string) { - categories = await DataStore.get<Category[]>(CATEGORY_BASE_KEY + userId) ?? []; +export function usePinnedDms() { + forceUpdateDms = useForceUpdater(); + settings.use(["pinOrder", "canCollapseDmSection", "dmSectionCollapsed", "userBasedCategoryList"]); } export function getCategory(id: string) { - return categories.find(c => c.id === id); + return currentUserCategories.find(c => c.id === id); } -export async function createCategory(category: Category) { - categories.push(category); - await saveCats(categories); +export function getCategoryByIndex(index: number) { + return currentUserCategories[index]; } -export async function updateCategory(category: Category) { - const index = categories.findIndex(c => c.id === category.id); - if (index === -1) return; - - categories[index] = category; - await saveCats(categories); +export function createCategory(category: Category) { + currentUserCategories.push(category); } -export async function addChannelToCategory(channelId: string, categoryId: string) { - const category = categories.find(c => c.id === categoryId); - if (!category) return; +export function addChannelToCategory(channelId: string, categoryId: string) { + const category = currentUserCategories.find(c => c.id === categoryId); + if (category == null) return; if (category.channels.includes(channelId)) return; category.channels.push(channelId); - await saveCats(categories); - } -export async function removeChannelFromCategory(channelId: string) { - const category = categories.find(c => c.channels.includes(channelId)); - if (!category) return; +export function removeChannelFromCategory(channelId: string) { + const category = currentUserCategories.find(c => c.channels.includes(channelId)); + if (category == null) return; category.channels = category.channels.filter(c => c !== channelId); - await saveCats(categories); } -export async function removeCategory(categoryId: string) { - const catagory = categories.find(c => c.id === categoryId); - if (!catagory) return; +export function removeCategory(categoryId: string) { + const categoryIndex = currentUserCategories.findIndex(c => c.id === categoryId); + if (categoryIndex === -1) return; - // catagory?.channels.forEach(c => removeChannelFromCategory(c)); - categories = categories.filter(c => c.id !== categoryId); - await saveCats(categories); + currentUserCategories.splice(categoryIndex, 1); } -export async function collapseCategory(id: string, value = true) { - const category = categories.find(c => c.id === id); - if (!category) return; +export function collapseCategory(id: string, value = true) { + const category = currentUserCategories.find(c => c.id === id); + if (category == null) return; category.collapsed = value; - await saveCats(categories); } -// utils +// Utils export function isPinned(id: string) { - return categories.some(c => c.channels.includes(id)); + return currentUserCategories.some(c => c.channels.includes(id)); } export function categoryLen() { - return categories.length; + return currentUserCategories.length; } export function getAllUncollapsedChannels() { if (settings.store.pinOrder === PinOrder.LastMessage) { const sortedChannels = PrivateChannelSortStore.getPrivateChannelIds(); - return categories.filter(c => !c.collapsed).flatMap(c => sortedChannels.filter(channel => c.channels.includes(channel))); + return currentUserCategories.filter(c => !c.collapsed).flatMap(c => sortedChannels.filter(channel => c.channels.includes(channel))); } - return categories.filter(c => !c.collapsed).flatMap(c => c.channels); + return currentUserCategories.filter(c => !c.collapsed).flatMap(c => c.channels); } export function getSections() { - return categories.reduce((acc, category) => { + return currentUserCategories.reduce((acc, category) => { acc.push(category.channels.length === 0 ? 1 : category.channels.length); return acc; }, [] as number[]); } -// move categories +// Move categories export const canMoveArrayInDirection = (array: any[], index: number, direction: -1 | 1) => { const a = array[index]; const b = array[index + direction]; @@ -130,18 +118,18 @@ export const canMoveArrayInDirection = (array: any[], index: number, direction: }; export const canMoveCategoryInDirection = (id: string, direction: -1 | 1) => { - const index = categories.findIndex(m => m.id === id); - return canMoveArrayInDirection(categories, index, direction); + const categoryIndex = currentUserCategories.findIndex(m => m.id === id); + return canMoveArrayInDirection(currentUserCategories, categoryIndex, direction); }; export const canMoveCategory = (id: string) => canMoveCategoryInDirection(id, -1) || canMoveCategoryInDirection(id, 1); export const canMoveChannelInDirection = (channelId: string, direction: -1 | 1) => { - const category = categories.find(c => c.channels.includes(channelId)); - if (!category) return false; + const category = currentUserCategories.find(c => c.channels.includes(channelId)); + if (category == null) return false; - const index = category.channels.indexOf(channelId); - return canMoveArrayInDirection(category.channels, index, direction); + const channelIndex = category.channels.indexOf(channelId); + return canMoveArrayInDirection(category.channels, channelIndex, direction); }; @@ -150,70 +138,44 @@ function swapElementsInArray(array: any[], index1: number, index2: number) { [array[index1], array[index2]] = [array[index2], array[index1]]; } -// stolen from PinDMs -export async function moveCategory(id: string, direction: -1 | 1) { - const a = categories.findIndex(m => m.id === id); +export function moveCategory(id: string, direction: -1 | 1) { + const a = currentUserCategories.findIndex(m => m.id === id); const b = a + direction; - swapElementsInArray(categories, a, b); - - await saveCats(categories); + swapElementsInArray(currentUserCategories, a, b); } -export async function moveChannel(channelId: string, direction: -1 | 1) { - const category = categories.find(c => c.channels.includes(channelId)); - if (!category) return; +export function moveChannel(channelId: string, direction: -1 | 1) { + const category = currentUserCategories.find(c => c.channels.includes(channelId)); + if (category == null) return; const a = category.channels.indexOf(channelId); const b = a + direction; swapElementsInArray(category.channels, a, b); - - await saveCats(categories); } - - -// migrate data -const getPinDMsPins = () => (Settings.plugins.PinDMs.pinnedDMs || void 0)?.split(",") as string[] | undefined; - -async function migratePinDMs() { - if (categories.some(m => m.id === "oldPins")) { - return await DataStore.set(CATEGORY_MIGRATED_PINDMS_KEY, true); +// TODO(OptionType.CUSTOM Related): Remove DataStore PinnedDms migration once enough time has passed +async function migrateData() { + if (Settings.plugins.PinDMs.dmSectioncollapsed != null) { + settings.store.dmSectionCollapsed = Settings.plugins.PinDMs.dmSectioncollapsed; + delete Settings.plugins.PinDMs.dmSectioncollapsed; } - const pindmspins = getPinDMsPins(); + const dataStoreKeys = await DataStore.keys(); + const pinDmsKeys = dataStoreKeys.map(key => String(key)).filter(key => key.startsWith(CATEGORY_BASE_KEY)); - // we dont want duplicate pins - const difference = [...new Set(pindmspins)]?.filter(m => !categories.some(c => c.channels.includes(m))); - if (difference?.length) { - categories.push({ - id: "oldPins", - name: "Pins", - color: DEFAULT_COLOR, - channels: difference - }); + if (pinDmsKeys.length === 0) return; + + for (const pinDmsKey of pinDmsKeys) { + const categories = await DataStore.get<Category[]>(pinDmsKey); + if (categories == null) continue; + + const userId = pinDmsKey.replace(CATEGORY_BASE_KEY, ""); + settings.store.userBasedCategoryList[userId] = categories; + + await DataStore.del(pinDmsKey); } - await DataStore.set(CATEGORY_MIGRATED_PINDMS_KEY, true); -} - -async function migrateOldCategories(userId: string) { - const oldCats = await DataStore.get<Category[]>(OLD_CATEGORY_KEY + userId); - // dont want to migrate if the user has already has categories. - if (categories.length === 0 && oldCats?.length) { - categories.push(...(oldCats.filter(m => m.id !== "oldPins"))); - } - await DataStore.set(CATEGORY_MIGRATED_KEY, true); -} - -export async function migrateData(userId: string) { - const m1 = await DataStore.get(CATEGORY_MIGRATED_KEY), m2 = await DataStore.get(CATEGORY_MIGRATED_PINDMS_KEY); - if (m1 && m2) return; - - // want to migrate the old categories first and then slove any conflicts with the PinDMs pins - if (!m1) await migrateOldCategories(userId); - if (!m2) await migratePinDMs(); - - await saveCats(categories); + await Promise.all([DataStore.del(CATEGORY_MIGRATED_PINDMS_KEY), DataStore.del(CATEGORY_MIGRATED_KEY), DataStore.del(OLD_CATEGORY_KEY)]); } diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx index 8cbb03bfc..59fee9c0f 100644 --- a/src/plugins/pinDms/index.tsx +++ b/src/plugins/pinDms/index.tsx @@ -12,13 +12,13 @@ import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin, { OptionType, StartAt } from "@utils/types"; import { findByPropsLazy, findStoreLazy } from "@webpack"; -import { ContextMenuApi, FluxDispatcher, Menu, React } from "@webpack/common"; +import { Clickable, ContextMenuApi, FluxDispatcher, Menu, React } from "@webpack/common"; import { Channel } from "discord-types/general"; import { contextMenus } from "./components/contextMenu"; import { openCategoryModal, requireSettingsMenu } from "./components/CreateCategoryModal"; import { DEFAULT_CHUNK_SIZE } from "./constants"; -import { canMoveCategory, canMoveCategoryInDirection, categories, Category, categoryLen, collapseCategory, getAllUncollapsedChannels, getSections, init, isPinned, moveCategory, removeCategory } from "./data"; +import { canMoveCategory, canMoveCategoryInDirection, Category, categoryLen, collapseCategory, getAllUncollapsedChannels, getCategoryByIndex, getSections, init, isPinned, moveCategory, removeCategory, usePinnedDms } from "./data"; interface ChannelComponentProps { children: React.ReactNode, @@ -26,13 +26,11 @@ interface ChannelComponentProps { selected: boolean; } - const headerClasses = findByPropsLazy("privateChannelsHeaderContainer"); export const PrivateChannelSortStore = findStoreLazy("PrivateChannelSortStore") as { getPrivateChannelIds: () => string[]; }; export let instance: any; -export const forceUpdate = () => instance?.props?._forceUpdate?.(); export const enum PinOrder { LastMessage, @@ -46,21 +44,28 @@ export const settings = definePluginSettings({ options: [ { label: "Most recent message", value: PinOrder.LastMessage, default: true }, { label: "Custom (right click channels to reorder)", value: PinOrder.Custom } - ], - onChange: () => forceUpdate() + ] }, - - dmSectioncollapsed: { + canCollapseDmSection: { type: OptionType.BOOLEAN, - description: "Collapse DM sections", + description: "Allow uncategorised DMs section to be collapsable", + default: false + }, + dmSectionCollapsed: { + type: OptionType.BOOLEAN, + description: "Collapse DM section", default: false, - onChange: () => forceUpdate() + hidden: true + }, + userBasedCategoryList: { + type: OptionType.CUSTOM, + default: {} as Record<string, Category[]> } }); export default definePlugin({ name: "PinDMs", - description: "Allows you to pin private channels to the top of your DM list. To pin/unpin or reorder pins, right click DMs", + description: "Allows you to pin private channels to the top of your DM list. To pin/unpin or re-order pins, right click DMs", authors: [Devs.Ven, Devs.Aria], settings, contextMenus, @@ -124,8 +129,8 @@ export default definePlugin({ { find: ".FRIENDS},\"friends\"", replacement: { - match: /let{showLibrary:\i,.+?showDMHeader:.+?,/, - replace: "let forceUpdate = Vencord.Util.useForceUpdater();$&_forceUpdate:forceUpdate," + match: /let{showLibrary:\i,/, + replace: "$self.usePinnedDms();$&" } }, @@ -149,6 +154,7 @@ export default definePlugin({ } }, ], + sections: null as number[] | null, set _instance(i: any) { @@ -162,6 +168,7 @@ export default definePlugin({ CONNECTION_OPEN: init, }, + usePinnedDms, isPinned, categoryLen, getSections, @@ -186,11 +193,11 @@ export default definePlugin({ }, makeSpanProps() { - return { + return settings.store.canCollapseDmSection ? { onClick: () => this.collapseDMList(), role: "button", style: { cursor: "pointer" } - }; + } : undefined; }, getChunkSize() { @@ -210,30 +217,27 @@ export default definePlugin({ }, isChannelIndex(sectionIndex: number, channelIndex: number) { - if (settings.store.dmSectioncollapsed && sectionIndex !== 0) + if (settings.store.canCollapseDmSection && settings.store.dmSectionCollapsed && sectionIndex !== 0) { return true; - const cat = categories[sectionIndex - 1]; - return this.isCategoryIndex(sectionIndex) && (cat?.channels?.length === 0 || cat?.channels[channelIndex]); - }, + } - isDMSectioncollapsed() { - return settings.store.dmSectioncollapsed; + const category = getCategoryByIndex(sectionIndex - 1); + return this.isCategoryIndex(sectionIndex) && (category?.channels?.length === 0 || category?.channels[channelIndex]); }, collapseDMList() { - settings.store.dmSectioncollapsed = !settings.store.dmSectioncollapsed; - forceUpdate(); + settings.store.dmSectionCollapsed = !settings.store.dmSectionCollapsed; }, isChannelHidden(categoryIndex: number, channelIndex: number) { if (categoryIndex === 0) return false; - if (settings.store.dmSectioncollapsed && this.getSections().length + 1 === categoryIndex) + if (settings.store.canCollapseDmSection && settings.store.dmSectionCollapsed && this.getSections().length + 1 === categoryIndex) return true; if (!this.instance || !this.isChannelIndex(categoryIndex, channelIndex)) return false; - const category = categories[categoryIndex - 1]; + const category = getCategoryByIndex(categoryIndex - 1); if (!category) return false; return category.collapsed && this.instance.props.selectedChannelId !== this.getCategoryChannels(category)[channelIndex]; @@ -251,18 +255,12 @@ export default definePlugin({ }, renderCategory: ErrorBoundary.wrap(({ section }: { section: number; }) => { - const category = categories[section - 1]; - + const category = getCategoryByIndex(section - 1); if (!category) return null; return ( - <h2 - className={classes(headerClasses.privateChannelsHeaderContainer, "vc-pindms-section-container", category.collapsed ? "vc-pindms-collapsed" : "")} - style={{ color: `#${category.color.toString(16).padStart(6, "0")}` }} - onClick={async () => { - await collapseCategory(category.id, !category.collapsed); - forceUpdate(); - }} + <Clickable + onClick={() => collapseCategory(category.id, !category.collapsed)} onContextMenu={e => { ContextMenuApi.openContextMenu(e, () => ( <Menu.Menu @@ -284,14 +282,14 @@ export default definePlugin({ canMoveCategoryInDirection(category.id, -1) && <Menu.MenuItem id="vc-pindms-move-category-up" label="Move Up" - action={() => moveCategory(category.id, -1).then(() => forceUpdate())} + action={() => moveCategory(category.id, -1)} /> } { canMoveCategoryInDirection(category.id, 1) && <Menu.MenuItem id="vc-pindms-move-category-down" label="Move Down" - action={() => moveCategory(category.id, 1).then(() => forceUpdate())} + action={() => moveCategory(category.id, 1)} /> } </> @@ -304,7 +302,7 @@ export default definePlugin({ id="vc-pindms-delete-category" color="danger" label="Delete Category" - action={() => removeCategory(category.id).then(() => forceUpdate())} + action={() => removeCategory(category.id)} /> @@ -312,13 +310,18 @@ export default definePlugin({ )); }} > - <span className={headerClasses.headerText}> - {category?.name ?? "uh oh"} - </span> - <svg className="vc-pindms-collapse-icon" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"> - <path fill="currentColor" d="M9.3 5.3a1 1 0 0 0 0 1.4l5.29 5.3-5.3 5.3a1 1 0 1 0 1.42 1.4l6-6a1 1 0 0 0 0-1.4l-6-6a1 1 0 0 0-1.42 0Z"></path> - </svg> - </h2> + <h2 + className={classes(headerClasses.privateChannelsHeaderContainer, "vc-pindms-section-container", category.collapsed ? "vc-pindms-collapsed" : "")} + style={{ color: `#${category.color.toString(16).padStart(6, "0")}` }} + > + <span className={headerClasses.headerText}> + {category?.name ?? "uh oh"} + </span> + <svg className="vc-pindms-collapse-icon" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"> + <path fill="currentColor" d="M9.3 5.3a1 1 0 0 0 0 1.4l5.29 5.3-5.3 5.3a1 1 0 1 0 1.42 1.4l6-6a1 1 0 0 0 0-1.4l-6-6a1 1 0 0 0-1.42 0Z"></path> + </svg> + </h2> + </Clickable> ); }, { noop: true }), @@ -341,7 +344,7 @@ export default definePlugin({ }, getChannel(sectionIndex: number, index: number, channels: Record<string, Channel>) { - const category = categories[sectionIndex - 1]; + const category = getCategoryByIndex(sectionIndex - 1); if (!category) return { channel: null, category: null }; const channelId = this.getCategoryChannels(category)[index]; diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index 1dc76e9d3..7829295a0 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -18,14 +18,14 @@ import "./style.css"; -import { addBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeBadge } from "@api/Badges"; -import { addDecorator, removeDecorator } from "@api/MemberListDecorators"; -import { addDecoration, removeDecoration } from "@api/MessageDecorations"; +import { addProfileBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeProfileBadge } from "@api/Badges"; +import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators"; +import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations"; import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack"; import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; import { User } from "discord-types/general"; @@ -70,7 +70,9 @@ const Icons = { }; type Platform = keyof typeof Icons; -const StatusUtils = findByPropsLazy("useStatusFillColor", "StatusTypes"); +const { useStatusFillColor } = mapMangledModuleLazy(".concat(.5625*", { + useStatusFillColor: filters.byCode(".hex") +}); const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => { const tooltip = platform === "embedded" @@ -79,7 +81,7 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: const Icon = Icons[platform] ?? Icons.desktop; - return <Icon color={StatusUtils.useStatusFillColor(status)} tooltip={tooltip} small={small} />; + return <Icon color={useStatusFillColor(status)} tooltip={tooltip} small={small} />; }; function ensureOwnStatus(user: User) { @@ -131,7 +133,7 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] { })); } -const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => { +const PlatformIndicator = ({ user, small = false }: { user: User; small?: boolean; }) => { if (!user || user.bot) return null; ensureOwnStatus(user); @@ -153,11 +155,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma return ( <span className="vc-platform-indicator" - style={{ - marginLeft: wantMargin ? 4 : 0, - top: wantTopMargin ? 2 : 0, - gap: 2 - }} + style={{ gap: "2px" }} > {icons} </span> @@ -172,26 +170,26 @@ const badge: ProfileBadge = { const indicatorLocations = { list: { description: "In the member list", - onEnable: () => addDecorator("platform-indicator", props => + onEnable: () => addMemberListDecorator("platform-indicator", props => <ErrorBoundary noop> <PlatformIndicator user={props.user} small={true} /> </ErrorBoundary> ), - onDisable: () => removeDecorator("platform-indicator") + onDisable: () => removeMemberListDecorator("platform-indicator") }, badges: { description: "In user profiles, as badges", - onEnable: () => addBadge(badge), - onDisable: () => removeBadge(badge) + onEnable: () => addProfileBadge(badge), + onDisable: () => removeProfileBadge(badge) }, messages: { description: "Inside messages", - onEnable: () => addDecoration("platform-indicator", props => + onEnable: () => addMessageDecoration("platform-indicator", props => <ErrorBoundary noop> - <PlatformIndicator user={props.message?.author} wantTopMargin={true} /> + <PlatformIndicator user={props.message?.author} /> </ErrorBoundary> ), - onDisable: () => removeDecoration("platform-indicator") + onDisable: () => removeMessageDecoration("platform-indicator") } }; diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx index fe6b227a5..7b03e31d7 100644 --- a/src/plugins/previewMessage/index.tsx +++ b/src/plugins/previewMessage/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; +import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { generateId, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import definePlugin, { StartAt } from "@utils/types"; @@ -73,7 +73,7 @@ const getAttachments = async (channelId: string) => ); -const PreviewButton: ChatBarButton = ({ isMainChat, isEmpty, type: { attachments } }) => { +const PreviewButton: ChatBarButtonFactory = ({ isMainChat, isEmpty, type: { attachments } }) => { const channelId = SelectedChannelStore.getChannelId(); const draft = useStateFromStores([DraftStore], () => getDraft(channelId)); @@ -121,11 +121,9 @@ export default definePlugin({ name: "PreviewMessage", description: "Lets you preview your message before sending it.", authors: [Devs.Aria], - dependencies: ["ChatInputButtonAPI"], // start early to ensure we're the first plugin to add our button // This makes the popping in less awkward startAt: StartAt.Init, - start: () => addChatBarButton("previewMessage", PreviewButton), - stop: () => removeChatBarButton("previewMessage"), + renderChatBarButton: PreviewButton, }); diff --git a/src/plugins/quickMention/index.tsx b/src/plugins/quickMention/index.tsx index df86e9b70..8d275354f 100644 --- a/src/plugins/quickMention/index.tsx +++ b/src/plugins/quickMention/index.tsx @@ -16,7 +16,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addButton, removeButton } from "@api/MessagePopover"; import { Devs } from "@utils/constants"; import { insertTextIntoChatInputBox } from "@utils/discord"; import definePlugin from "@utils/types"; @@ -26,24 +25,18 @@ export default definePlugin({ name: "QuickMention", authors: [Devs.kemo], description: "Adds a quick mention button to the message actions bar", - dependencies: ["MessagePopoverAPI"], - start() { - addButton("QuickMention", msg => { - const channel = ChannelStore.getChannel(msg.channel_id); - if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null; + renderMessagePopoverButton(msg) { + const channel = ChannelStore.getChannel(msg.channel_id); + if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null; - return { - label: "Quick Mention", - icon: this.Icon, - message: msg, - channel, - onClick: () => insertTextIntoChatInputBox(`<@${msg.author.id}> `) - }; - }); - }, - stop() { - removeButton("QuickMention"); + return { + label: "Quick Mention", + icon: this.Icon, + message: msg, + channel, + onClick: () => insertTextIntoChatInputBox(`<@${msg.author.id}> `) + }; }, Icon: () => ( diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts index 4a7060c59..f6ca5b459 100644 --- a/src/plugins/quickReply/index.ts +++ b/src/plugins/quickReply/index.ts @@ -196,7 +196,7 @@ function nextReply(isUp: boolean) { channel, message, shouldMention: shouldMention(message), - showMentionToggle: channel.isPrivate() && message.author.id !== meId, + showMentionToggle: !channel.isPrivate() && message.author.id !== meId, _isQuickReply: true }); ComponentDispatch.dispatchToLastSubscribed("TEXTAREA_FOCUS"); diff --git a/src/plugins/reviewDB/auth.tsx b/src/plugins/reviewDB/auth.tsx index 4cd81f2ea..8d9789dd7 100644 --- a/src/plugins/reviewDB/auth.tsx +++ b/src/plugins/reviewDB/auth.tsx @@ -7,15 +7,12 @@ import { DataStore } from "@api/index"; import { Logger } from "@utils/Logger"; import { openModal } from "@utils/modal"; -import { findByPropsLazy } from "@webpack"; -import { showToast, Toasts, UserStore } from "@webpack/common"; +import { OAuth2AuthorizeModal, showToast, Toasts, UserStore } from "@webpack/common"; import { ReviewDBAuth } from "./entities"; const DATA_STORE_KEY = "rdb-auth"; -const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal"); - export let Auth: ReviewDBAuth = {}; export async function initAuth() { diff --git a/src/plugins/reviewDB/components/BlockedUserModal.tsx b/src/plugins/reviewDB/components/BlockedUserModal.tsx index 43c81eb52..8b8271746 100644 --- a/src/plugins/reviewDB/components/BlockedUserModal.tsx +++ b/src/plugins/reviewDB/components/BlockedUserModal.tsx @@ -39,7 +39,7 @@ function BlockedUser({ user, isBusy, setIsBusy }: { user: ReviewDBUser; isBusy: return ( <div className={cl("block-modal-row")}> - <img src={user.profilePhoto} alt="" /> + <img className={cl("block-modal-avatar")} src={user.profilePhoto} alt="" /> <Forms.FormText className={cl("block-modal-username")}>{user.username}</Forms.FormText> <UnblockButton onClick={isBusy ? undefined : async () => { diff --git a/src/plugins/reviewDB/components/ReviewModal.tsx b/src/plugins/reviewDB/components/ReviewModal.tsx index 71ac021f0..1485022da 100644 --- a/src/plugins/reviewDB/components/ReviewModal.tsx +++ b/src/plugins/reviewDB/components/ReviewModal.tsx @@ -65,7 +65,7 @@ function Modal({ modalProps, modalKey, discordId, name, type }: { modalProps: an </ModalContent> <ModalFooter className={cl("modal-footer")}> - <div> + <div className={cl("modal-footer-wrapper")}> {ownReview && ( <ReviewComponent refetch={refetch} diff --git a/src/plugins/reviewDB/settings.tsx b/src/plugins/reviewDB/settings.tsx index eeebd0aa1..2b58d080c 100644 --- a/src/plugins/reviewDB/settings.tsx +++ b/src/plugins/reviewDB/settings.tsx @@ -27,7 +27,6 @@ import { cl } from "./utils"; export const settings = definePluginSettings({ authorize: { type: OptionType.COMPONENT, - description: "Authorize with ReviewDB", component: () => ( <Button onClick={() => authorize()}> Authorize with ReviewDB @@ -56,7 +55,6 @@ export const settings = definePluginSettings({ }, buttons: { type: OptionType.COMPONENT, - description: "ReviewDB buttons", component: () => ( <div className={cl("button-grid")} > <Button onClick={openBlockModal}>Manage Blocked Users</Button> diff --git a/src/plugins/reviewDB/style.css b/src/plugins/reviewDB/style.css index 190b8f620..c62c300e9 100644 --- a/src/plugins/reviewDB/style.css +++ b/src/plugins/reviewDB/style.css @@ -16,16 +16,11 @@ border: 1px solid var(--profile-message-input-border-color); } -.vc-rdb-modal-footer > div { +.vc-rdb-modal-footer-wrapper { width: 100%; margin: 6px 16px; } -/* When input becomes disabled(while sending review), input adds unneccesary padding to left, this prevents it */ -.vc-rdb-input > div > div { - padding-left: 0 !important; -} - .vc-rdb-placeholder { margin-bottom: 4px; font-weight: bold; @@ -69,7 +64,7 @@ border-radius: 8px; } -.vc-rdb-review-comment img { +.vc-rdb-review-comment [class*="avatar"] { vertical-align: text-top; } @@ -117,13 +112,13 @@ align-items: center; } -.vc-rdb-block-modal-row img { +.vc-rdb-block-modal-avatar { border-radius: 50%; height: 2em; width: 2em; } -.vc-rdb-block-modal img::before { +.vc-rdb-block-modal-avatar::before { content: ""; display: block; width: 100%; diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 7b811943d..ffa2b5a25 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -84,8 +84,14 @@ export default definePlugin({ find: ".USER_MENTION)", replacement: [ { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/, - replace: "$&,color:$self.getColorInt($1?.id,$2?.id)" + replace: "$&,color:$self.getColorInt($1?.id,$2?.id)", + noWarn: true + }, + { + match: /(?<=onContextMenu:\i,color:)\i(?=\},\i\),\{children)(?<=user:(\i),channel:(\i).{0,500}?)/, + replace: "$self.getColorInt($1?.id,$2?.id)", } ], predicate: () => settings.store.chatMentions @@ -124,11 +130,11 @@ export default definePlugin({ }, // Voice Users { - find: "renderPrioritySpeaker(){", + find: ".usernameSpeaking]:", replacement: [ { - match: /renderName\(\){.+?usernameSpeaking\]:.+?(?=children)/, - replace: "$&style:$self.getColorStyle(this?.props?.user?.id,this?.props?.guildId)," + match: /\.usernameSpeaking\]:.+?,(?=children)(?<=guildId:(\i),.+?user:(\i).+?)/, + replace: "$&style:$self.getColorStyle($2.id,$1)," } ], predicate: () => settings.store.voiceUsers diff --git a/src/plugins/sendTimestamps/index.tsx b/src/plugins/sendTimestamps/index.tsx index 2306c20cd..57b9de0df 100644 --- a/src/plugins/sendTimestamps/index.tsx +++ b/src/plugins/sendTimestamps/index.tsx @@ -18,8 +18,7 @@ import "./styles.css"; -import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; -import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; +import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { definePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { Devs } from "@utils/constants"; @@ -69,15 +68,16 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi return ( <ModalRoot {...rootProps}> <ModalHeader className={cl("modal-header")}> - <Forms.FormTitle tag="h2"> + <Forms.FormTitle tag="h2" className={cl("modal-title")}> Timestamp Picker </Forms.FormTitle> - <ModalCloseButton onClick={close} /> + <ModalCloseButton onClick={close} className={cl("modal-close-button")} /> </ModalHeader> <ModalContent className={cl("modal-content")}> <input + className={cl("date-picker")} type="datetime-local" value={value} onChange={e => setValue(e.currentTarget.value)} @@ -87,23 +87,25 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi /> <Forms.FormTitle>Timestamp Format</Forms.FormTitle> - <Select - options={ - Formats.map(m => ({ - label: m, - value: m - })) - } - isSelected={v => v === format} - select={v => setFormat(v)} - serialize={v => v} - renderOptionLabel={o => ( - <div className={cl("format-label")}> - {Parser.parse(formatTimestamp(time, o.value))} - </div> - )} - renderOptionValue={() => rendered} - /> + <div className={cl("format-select")}> + <Select + options={ + Formats.map(m => ({ + label: m, + value: m + })) + } + isSelected={v => v === format} + select={v => setFormat(v)} + serialize={v => v} + renderOptionLabel={o => ( + <div className={cl("format-label")}> + {Parser.parse(formatTimestamp(time, o.value))} + </div> + )} + renderOptionValue={() => rendered} + /> + </div> <Forms.FormTitle className={Margins.bottom8}>Preview</Forms.FormTitle> <Forms.FormText className={cl("preview-text")}> @@ -123,7 +125,7 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi ); } -const ChatBarIcon: ChatBarButton = ({ isMainChat }) => { +const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => { if (!isMainChat) return null; return ( @@ -160,22 +162,14 @@ export default definePlugin({ name: "SendTimestamps", description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!", authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11], - dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], - settings, - start() { - addChatBarButton("SendTimestamps", ChatBarIcon); - this.listener = addPreSendListener((_, msg) => { - if (settings.store.replaceMessageContents) { - msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime); - } - }); - }, + renderChatBarButton: ChatBarIcon, - stop() { - removeChatBarButton("SendTimestamps"); - removePreSendListener(this.listener); + onBeforeMessageSend(_, msg) { + if (settings.store.replaceMessageContents) { + msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime); + } }, settingsAboutComponent() { diff --git a/src/plugins/sendTimestamps/styles.css b/src/plugins/sendTimestamps/styles.css index 033d5c9d5..e7efbe59e 100644 --- a/src/plugins/sendTimestamps/styles.css +++ b/src/plugins/sendTimestamps/styles.css @@ -1,4 +1,4 @@ -.vc-st-modal-content input { +.vc-st-date-picker { background-color: var(--input-background); color: var(--text-normal); width: 95%; @@ -12,35 +12,28 @@ font-size: 100%; } -.vc-st-format-label, -.vc-st-format-label span { - background-color: transparent; -} - -.vc-st-modal-content [class|="select"] { +.vc-st-format-select { margin-bottom: 1em; + + --background-modifier-accent: transparent; } -.vc-st-modal-content [class|="select"] span { - background-color: var(--input-background); +.vc-st-format-label { + --background-modifier-accent: transparent; } .vc-st-modal-header { place-content: center space-between; } -.vc-st-modal-header h1 { +.vc-st-modal-title { margin: 0; } -.vc-st-modal-header button { +.vc-st-modal-close-button { padding: 0; } .vc-st-preview-text { margin-bottom: 1em; } - -.vc-st-button svg { - transform: scale(1.1) translateY(1px); -} diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index be77ca1c7..9f2d3008d 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -31,7 +31,8 @@ export function openGuildInfoModal(guild: Guild) { const enum Tabs { ServerInfo, Friends, - BlockedUsers + BlockedUsers, + IgnoredUsers } interface GuildProps { @@ -44,7 +45,8 @@ interface RelationshipProps extends GuildProps { const fetched = { friends: false, - blocked: false + blocked: false, + ignored: false }; function renderTimestamp(timestamp: number) { @@ -56,10 +58,12 @@ function renderTimestamp(timestamp: number) { function GuildInfoModal({ guild }: GuildProps) { const [friendCount, setFriendCount] = useState<number>(); const [blockedCount, setBlockedCount] = useState<number>(); + const [ignoredCount, setIgnoredCount] = useState<number>(); useEffect(() => { fetched.friends = false; fetched.blocked = false; + fetched.ignored = false; }, []); const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo); @@ -90,6 +94,7 @@ function GuildInfoModal({ guild }: GuildProps) { <div className={cl("header")}> {iconUrl ? <img + className={cl("icon")} src={iconUrl} alt="" onClick={() => openImageModal({ @@ -132,12 +137,19 @@ function GuildInfoModal({ guild }: GuildProps) { > Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""} </TabBar.Item> + <TabBar.Item + className={cl("tab", { selected: currentTab === Tabs.IgnoredUsers })} + id={Tabs.IgnoredUsers} + > + Ignored Users{ignoredCount !== undefined ? ` (${ignoredCount})` : ""} + </TabBar.Item> </TabBar> <div className={cl("tab-content")}> {currentTab === Tabs.ServerInfo && <ServerInfoTab guild={guild} />} {currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />} {currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />} + {currentTab === Tabs.IgnoredUsers && <IgnoredUserTab guild={guild} setCount={setIgnoredCount} />} </div> </div> ); @@ -159,6 +171,7 @@ function Owner(guildId: string, owner: User) { return ( <div className={cl("owner")}> <img + className={cl("owner-avatar")} src={ownerAvatarUrl} alt="" onClick={() => openImageModal({ @@ -211,7 +224,13 @@ function BlockedUsersTab({ guild, setCount }: RelationshipProps) { return UserList("blocked", guild, blockedIds, setCount); } -function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setCount: (count: number) => void) { +function IgnoredUserTab({ guild, setCount }: RelationshipProps) { + const ignoredIds = Object.keys(RelationshipStore.getRelationships()).filter(id => RelationshipStore.isIgnored(id)); + return UserList("ignored", guild, ignoredIds, setCount); +} + + +function UserList(type: "friends" | "blocked" | "ignored", guild: Guild, ids: string[], setCount: (count: number) => void) { const missing = [] as string[]; const members = [] as string[]; diff --git a/src/plugins/serverInfo/styles.css b/src/plugins/serverInfo/styles.css index 8c88e4f40..274b7d138 100644 --- a/src/plugins/serverInfo/styles.css +++ b/src/plugins/serverInfo/styles.css @@ -21,7 +21,7 @@ margin: 0.5em; } -.vc-gp-header img { +.vc-gp-icon { width: 48px; height: 48px; cursor: pointer; @@ -82,7 +82,7 @@ gap: 0.2em; } -.vc-gp-owner img { +.vc-gp-owner-avatar { height: 20px; border-radius: 50%; cursor: pointer; diff --git a/src/plugins/shikiCodeblocks.desktop/components/Code.tsx b/src/plugins/shikiCodeblocks.desktop/components/Code.tsx index 8deca5882..2794234df 100644 --- a/src/plugins/shikiCodeblocks.desktop/components/Code.tsx +++ b/src/plugins/shikiCodeblocks.desktop/components/Code.tsx @@ -84,9 +84,9 @@ export const Code = ({ } const codeTableRows = lines.map((line, i) => ( - <tr key={i}> - <td style={{ color: theme.plainColor }}>{i + 1}</td> - <td>{line}</td> + <tr className={cl("table-row")} key={i}> + <td className={cl("table-cell")} style={{ color: theme.plainColor }}>{i + 1}</td> + <td className={cl("table-cell")}>{line}</td> </tr> )); diff --git a/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx b/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx index dd1401939..2d62af6e1 100644 --- a/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx +++ b/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx @@ -102,7 +102,7 @@ export const Highlighter = ({ color: themeBase.plainColor, }} > - <code> + <code className={cl("code")}> <Header langName={langName} useDevIcon={useDevIcon} diff --git a/src/plugins/shikiCodeblocks.desktop/shiki.css b/src/plugins/shikiCodeblocks.desktop/shiki.css index 8674147e5..32bf1c2e9 100644 --- a/src/plugins/shikiCodeblocks.desktop/shiki.css +++ b/src/plugins/shikiCodeblocks.desktop/shiki.css @@ -1,13 +1,13 @@ -.shiki-container { +.vc-shiki-container { border: 4px; background-color: var(--background-secondary); } -.shiki-root { +.vc-shiki-root { border-radius: 4px; } -.shiki-root code { +.vc-shiki-root .vc-shiki-code { display: block; overflow-x: auto; padding: 0.5em; @@ -20,16 +20,16 @@ border: none; } -.shiki-devicon { +.vc-shiki-devicon { margin-right: 8px; user-select: none; } -.shiki-plain code { +.vc-shiki-plain .vc-shiki-code { padding-top: 8px; } -.shiki-btns { +.vc-shiki-btns { font-size: 1em; position: absolute; right: 0; @@ -37,25 +37,25 @@ opacity: 0; } -.shiki-root:hover .shiki-btns { +.vc-shiki-root:hover .vc-shiki-btns { opacity: 1; } -.shiki-btn { +.vc-shiki-btn { border-radius: 4px 4px 0 0; padding: 4px 8px; user-select: none; } -.shiki-btn ~ .shiki-btn { +.vc-shiki-btn ~ .vc-shiki-btn { margin-left: 4px; } -.shiki-btn:last-child { +.vc-shiki-btn:last-child { border-radius: 4px 0; } -.shiki-spinner-container { +.vc-shiki-spinner-container { align-items: center; background-color: rgb(0 0 0 / 60%); display: flex; @@ -64,11 +64,11 @@ inset: 0; } -.shiki-preview { +.vc-shiki-preview { margin-bottom: 2em; } -.shiki-lang { +.vc-shiki-lang { padding: 0 5px; margin-bottom: 6px; font-weight: bold; @@ -77,25 +77,25 @@ align-items: center; } -.shiki-table { +.vc-shiki-table { border-collapse: collapse; width: 100%; } -.shiki-table tr { +.vc-shiki-table-row { height: 19px; width: 100%; } -.shiki-root td:first-child { +.vc-shiki-root .vc-shiki-table-cell:first-child { border-right: 1px solid transparent; padding-left: 5px; padding-right: 8px; user-select: none; } -.shiki-root td:last-child { +.vc-shiki-root .vc-shiki-table-cell:last-child { padding-left: 8px; - word-break: break-word; + overflow-wrap: break-word; width: 100%; } diff --git a/src/plugins/shikiCodeblocks.desktop/utils/misc.ts b/src/plugins/shikiCodeblocks.desktop/utils/misc.ts index e0c526342..1319a2f24 100644 --- a/src/plugins/shikiCodeblocks.desktop/utils/misc.ts +++ b/src/plugins/shikiCodeblocks.desktop/utils/misc.ts @@ -23,7 +23,7 @@ import { resolveLang } from "../api/languages"; import { HighlighterProps } from "../components/Highlighter"; import { HljsSetting } from "../types"; -export const cl = classNameFactory("shiki-"); +export const cl = classNameFactory("vc-shiki-"); export const shouldUseHljs = ({ lang, diff --git a/src/plugins/showConnections/VerifiedIcon.tsx b/src/plugins/showConnections/VerifiedIcon.tsx index d748f326c..0e64527e5 100644 --- a/src/plugins/showConnections/VerifiedIcon.tsx +++ b/src/plugins/showConnections/VerifiedIcon.tsx @@ -33,6 +33,7 @@ export function VerifiedIcon() { forcedIconColor={forcedIconColor} size={16} tooltipText={getIntlMessage("CONNECTION_VERIFIED")} + className="vc-sc-tooltip-icon" /> ); } diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 46629c770..f99c0be96 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -125,7 +125,7 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect <span className="vc-sc-tooltip"> <span className="vc-sc-connection-name">{connection.name}</span> {connection.verified && <VerifiedIcon />} - <TooltipIcon height={16} width={16} /> + <TooltipIcon height={16} width={16} className="vc-sc-tooltip-icon" /> </span> } key={connection.id} diff --git a/src/plugins/showConnections/styles.css b/src/plugins/showConnections/styles.css index cead5201c..5bb16e0fd 100644 --- a/src/plugins/showConnections/styles.css +++ b/src/plugins/showConnections/styles.css @@ -14,6 +14,6 @@ word-break: break-all; } -.vc-sc-tooltip svg { +.vc-sc-tooltip-icon { min-width: 16px; } diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 15cd17c46..bbe286af5 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -18,6 +18,7 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; +import { classes } from "@utils/misc"; import { formatDuration } from "@utils/text"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; @@ -25,7 +26,7 @@ import type { Channel } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions"; import { sortPermissionOverwrites } from "../../permissionsViewer/utils"; -import { settings } from ".."; +import { cl, settings } from ".."; const enum SortOrderTypes { LATEST_ACTIVITY = 0, @@ -168,19 +169,19 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { }, [channelId]); return ( - <div className={ChatScrollClasses.auto + " " + ChatScrollClasses.customTheme + " " + ChatClasses.chatContent + " " + "shc-lock-screen-outer-container"}> - <div className="shc-lock-screen-container"> - <img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> + <div className={classes(ChatScrollClasses.auto, ChatScrollClasses.customTheme, ChatScrollClasses.managedReactiveScroller)}> + <div className={cl("container")}> + <img className={cl("logo")} src={HiddenChannelLogo} /> - <div className="shc-lock-screen-heading-container"> - <Text variant="heading-xxl/bold">This is a {!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) ? "hidden" : "locked"} {ChannelTypesToChannelNames[type]} channel.</Text> + <div className={cl("heading-container")}> + <Text variant="heading-xxl/bold">This is a {!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) ? "hidden" : "locked"} {ChannelTypesToChannelNames[type]} channel</Text> {channel.isNSFW() && <Tooltip text="NSFW"> {({ onMouseLeave, onMouseEnter }) => ( <svg onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter} - className="shc-lock-screen-heading-nsfw-icon" + className={cl("heading-nsfw-icon")} width="32" height="32" viewBox="0 0 48 48" @@ -202,7 +203,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { )} {channel.isForumChannel() && topic && topic.length > 0 && ( - <div className="shc-lock-screen-topic-container"> + <div className={cl("topic-container")}> {Parser.parseTopic(topic, false, { channelId })} </div> )} @@ -213,7 +214,6 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { <Timestamp timestamp={new Date(SnowflakeUtils.extractTimestamp(lastMessageId))} /> </Text> } - {lastPinTimestamp && <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={new Date(lastPinTimestamp)} /></Text> } @@ -247,7 +247,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { <Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text> } {defaultReactionEmoji != null && - <div className="shc-lock-screen-default-emoji-container"> + <div className={cl("default-emoji-container")}> <Text variant="text-md/normal">Default reaction emoji:</Text> {Parser.defaultRules[defaultReactionEmoji.emojiName ? "emoji" : "customEmoji"].react({ name: defaultReactionEmoji.emojiName @@ -258,29 +258,29 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { src: defaultReactionEmoji.emojiName ? EmojiUtils.getURL(defaultReactionEmoji.emojiName) : void 0 - }, void 0, { key: "0" })} + }, void 0, { key: 0 })} </div> } {channel.hasFlag(ChannelFlags.REQUIRE_TAG) && <Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text> } {availableTags && availableTags.length > 0 && - <div className="shc-lock-screen-tags-container"> + <div className={cl("tags-container")}> <Text variant="text-lg/bold">Available tags:</Text> - <div className="shc-lock-screen-tags"> + <div className={cl("tags")}> {availableTags.map(tag => <TagComponent tag={tag} key={tag.id} />)} </div> </div> } - <div className="shc-lock-screen-allowed-users-and-roles-container"> - <div className="shc-lock-screen-allowed-users-and-roles-container-title"> - {Settings.plugins.PermissionsViewer.enabled && ( + <div className={cl("allowed-users-and-roles-container")}> + <div className={cl("allowed-users-and-roles-container-title")}> + {Vencord.Plugins.isPluginEnabled("PermissionsViewer") && ( <Tooltip text="Permission Details"> {({ onMouseLeave, onMouseEnter }) => ( <button onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter} - className="shc-lock-screen-allowed-users-and-roles-container-permdetails-btn" + className={cl("allowed-users-and-roles-container-permdetails-btn")} onClick={() => openRolesAndUsersPermissionsModal(permissions, GuildStore.getGuild(channel.guild_id), channel.name)} > <svg @@ -300,7 +300,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { <button onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter} - className="shc-lock-screen-allowed-users-and-roles-container-toggle-btn" + className={cl("allowed-users-and-roles-container-toggle-btn")} onClick={() => settings.store.defaultAllowedUsersAndRolesDropdownState = !defaultAllowedUsersAndRolesDropdownState} > <svg diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 181a6bc99..36abe30ec 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -19,8 +19,10 @@ import "./style.css"; import { definePluginSettings } from "@api/Settings"; +import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { classes } from "@utils/misc"; import { canonicalizeMatch } from "@utils/patches"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; @@ -31,6 +33,8 @@ import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen"; const ChannelListClasses = findByPropsLazy("modeMuted", "modeSelected", "unread", "icon"); +export const cl = classNameFactory("vc-shc-"); + const enum ShowMode { LockIcon, HiddenIconWithMutedStyle @@ -88,7 +92,7 @@ export default definePlugin({ }, // Make channels we dont have access to be the same level as normal ones { - match: /(activeJoinedRelevantThreads:.{0,50}VIEW_CHANNEL.+?renderLevel:(.+?),threadIds.+?renderLevel:).+?(?=,threadIds)/g, + match: /(this\.record\)\?{renderLevel:(.+?),threadIds.+?renderLevel:).+?(?=,threadIds)/g, replace: (_, rest, defaultRenderLevel) => `${rest}${defaultRenderLevel}` }, // Remove permission checking for getRenderLevel function @@ -108,8 +112,11 @@ export default definePlugin({ }, { // Prevent Discord from trying to connect to hidden voice channels - match: /(?=&&\i\.\i\.selectVoiceChannel\((\i)\.id\))/, - replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /(?=(\|\||&&)\i\.\i\.selectVoiceChannel\((\i)\.id\))/, + replace: (_, condition, channel) => condition === "||" + ? `||$self.isHiddenChannel(${channel})` + : `&&!$self.isHiddenChannel(${channel})` }, { // Make Discord show inside the channel if clicking on a hidden or locked channel @@ -122,8 +129,11 @@ export default definePlugin({ { find: ".AUDIENCE),{isSubscriptionGated", replacement: { - match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/, - replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})` + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /(!)?(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/, + replace: (m, not, channel) => not + ? `${m}&&!$self.isHiddenChannel(${channel})` + : `${m}||$self.isHiddenChannel(${channel})` } }, { @@ -173,8 +183,11 @@ export default definePlugin({ }, // Make voice channels also appear as muted if they are muted { - match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)if\((\i)\)return (\i\.MUTED);/, - replace: (_, otherClasses, isMuted, mutedClassExpression) => `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return "";` + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)(if\()?(\i)(?:\)return |\?)(\i\.MUTED)/, + replace: (_, otherClasses, isIf, isMuted, mutedClassExpression) => isIf + ? `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return ""` + : `${isMuted}?${mutedClassExpression}:"",${otherClasses}${isMuted}?""` } ] }, @@ -184,13 +197,14 @@ export default definePlugin({ { // Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle, - match: /\.LOCKED;if\((?<={channel:(\i).+?)/, - replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&` + // FIXME(Bundler change related): Remove old compatiblity once enough time has passed + match: /(?<=\.LOCKED(?:;if\(|:))(?<={channel:(\i).+?)/, + replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&` }, { // Hide unreads predicate: () => settings.store.hideUnreads === true, - match: /{channel:(\i),name:\i,.+?unread:(\i).+?;/, + match: /\.subtitle,.+?;(?=return\(0,\i\.jsxs?\))(?<={channel:(\i),name:\i,.+?unread:(\i).+?)/, replace: (m, channel, unread) => `${m}${unread}=$self.isHiddenChannel(${channel})?false:${unread};` } ] @@ -224,12 +238,12 @@ export default definePlugin({ find: "Missing channel in Channel.renderHeaderToolbar", replacement: [ { - match: /(?<="renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_TEXT:)(?=.+?(\i\.push.{0,50}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/, - replace: (_, pushNotificationButtonExpression, channel, isLurking) => `if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}` + match: /"renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_TEXT:(?=.+?(\i\.push.{0,50}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/, + replace: (m, pushNotificationButtonExpression, channel, isLurking) => `${m}if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}` }, { - match: /(?<="renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_MEDIA:)(?=.+?(\i\.push.{0,40}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/, - replace: (_, pushNotificationButtonExpression, channel, isLurking) => `if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}` + match: /"renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_MEDIA:(?=.+?(\i\.push.{0,40}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/, + replace: (m, pushNotificationButtonExpression, channel, isLurking) => `${m}if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}` }, { match: /"renderMobileToolbar",\(\)=>{.+?case \i\.\i\.GUILD_DIRECTORY:(?<=let{channel:(\i).+?)/, @@ -471,7 +485,7 @@ export default definePlugin({ } }, { - find: '="NowPlayingViewStore",', + find: '"NowPlayingViewStore"', replacement: { // Make active now voice states on hidden channels match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/, @@ -539,7 +553,7 @@ export default definePlugin({ aria-hidden={true} role="img" > - <path className="shc-evenodd-fill-current-color" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" /> + <path fill="currentcolor" fillRule="evenodd" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" /> </svg> ), { noop: true }), @@ -549,14 +563,14 @@ export default definePlugin({ <svg onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter} - className={ChannelListClasses.icon + " " + "shc-hidden-channel-icon"} + className={classes(ChannelListClasses.icon, cl("hidden-channel-icon"))} width="24" height="24" viewBox="0 0 24 24" aria-hidden={true} role="img" > - <path className="shc-evenodd-fill-current-color" d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" /> + <path fill="currentcolor" fillRule="evenodd" d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" /> </svg> )} </Tooltip> diff --git a/src/plugins/showHiddenChannels/style.css b/src/plugins/showHiddenChannels/style.css index 301ad75d4..56e50a2fd 100644 --- a/src/plugins/showHiddenChannels/style.css +++ b/src/plugins/showHiddenChannels/style.css @@ -1,43 +1,31 @@ -.shc-lock-screen-outer-container { - overflow: hidden scroll; - flex: 1 1 auto; - height: 100%; - width: 100%; -} - -.shc-lock-screen-container { +.vc-shc-container { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; + gap: 0.65em; + margin: 0.5em 0; min-height: 100%; } -.shc-lock-screen-container > * { - margin: 5px; +.vc-shc-logo { + width: 12em; + height: 12em; } -.shc-lock-screen-logo { - width: 180px; - height: 180px; -} - -.shc-lock-screen-heading-container { +.vc-shc-heading-container { display: flex; flex-direction: row; align-items: center; + gap: 0.5em; } -.shc-lock-screen-heading-container > * { - margin: inherit; -} - -.shc-lock-screen-heading-nsfw-icon { +.vc-shc-heading-nsfw-icon { color: var(--text-normal); } -.shc-lock-screen-topic-container { +.vc-shc-topic-container { color: var(--text-normal); background: var(--bg-overlay-3, var(--background-secondary)); border-radius: 5px; @@ -45,91 +33,75 @@ max-width: 70vw; } -.shc-lock-screen-tags-container { +.vc-shc-default-emoji-container { + display: flex; + flex-direction: row; + align-items: center; + background: var(--bg-overlay-3, var(--background-secondary)); + border-radius: 8px; + padding: 0.75em; + margin-left: 0.75em; +} + +.vc-shc-tags-container { + display: flex; + flex-direction: column; background: var(--bg-overlay-3, var(--background-secondary)); border-radius: 5px; - padding: 10px; + padding: 0.75em; + gap: 0.75em; max-width: 70vw; } -.shc-lock-screen-tags-container > * { - margin: inherit; -} - -.shc-lock-screen-tags { +.vc-shc-tags { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; - gap: 8px; + gap: 0.35em; } -.shc-evenodd-fill-current-color { - fill-rule: evenodd; - fill: currentcolor; -} - -.shc-hidden-channel-icon { - margin-left: 6px; - z-index: 0; - cursor: not-allowed; -} - -.shc-lock-screen-default-emoji-container { - display: flex; - flex-direction: row; - align-items: center; -} - -.shc-lock-screen-default-emoji-container > [class^="emojiContainer"] { - background: var(--bg-overlay-3, var(--background-secondary)); - border-radius: 8px; - padding: 5px 6px; - margin-left: 5px; -} - -.shc-lock-screen-allowed-users-and-roles-container { +.vc-shc-allowed-users-and-roles-container { display: flex; flex-direction: column; align-items: center; background: var(--bg-overlay-3, var(--background-secondary)); border-radius: 5px; - padding: 10px; + padding: 0.75em; max-width: 70vw; } -.shc-lock-screen-allowed-users-and-roles-container-title { +.vc-shc-allowed-users-and-roles-container-title { display: flex; flex-direction: row; align-items: center; + gap: 0.5em; } -.shc-lock-screen-allowed-users-and-roles-container-toggle-btn { +.vc-shc-allowed-users-and-roles-container-toggle-btn { all: unset; - margin-left: 5px; cursor: pointer; display: flex; align-items: center; -} - -.shc-lock-screen-allowed-users-and-roles-container-toggle-btn > svg { color: var(--text-normal); } -.shc-lock-screen-allowed-users-and-roles-container-permdetails-btn { +.vc-shc-allowed-users-and-roles-container-permdetails-btn { all: unset; - margin-right: 5px; cursor: pointer; display: flex; align-items: center; -} - -.shc-lock-screen-allowed-users-and-roles-container-permdetails-btn > svg { color: var(--text-normal); } -.shc-lock-screen-allowed-users-and-roles-container > [class^="members"] { - margin-left: 10px; +.vc-shc-allowed-users-and-roles-container > [class^="members"] { + margin-left: 12px; flex-wrap: wrap; justify-content: center; } + +.vc-shc-hidden-channel-icon { + cursor: not-allowed; + margin-left: 6px; + z-index: 0; +} diff --git a/src/plugins/showTimeoutDuration/index.tsx b/src/plugins/showTimeoutDuration/index.tsx index 1395cf808..2c7c8a854 100644 --- a/src/plugins/showTimeoutDuration/index.tsx +++ b/src/plugins/showTimeoutDuration/index.tsx @@ -76,8 +76,8 @@ export default definePlugin({ find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}", replacement: [ { - match: /(\i)\.Tooltip,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/, - replace: "$self.TooltipWrapper,{message:arguments[0].message,$2" + match: /\i\.\i,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/, + replace: "$self.TooltipWrapper,{message:arguments[0].message,$1" } ] } diff --git a/src/plugins/silentMessageToggle/index.tsx b/src/plugins/silentMessageToggle/index.tsx index d7a7ed7f0..00e605099 100644 --- a/src/plugins/silentMessageToggle/index.tsx +++ b/src/plugins/silentMessageToggle/index.tsx @@ -16,8 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; -import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents"; +import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; +import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -41,7 +41,7 @@ const settings = definePluginSettings({ } }); -const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => { +const SilentMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => { const [enabled, setEnabled] = useState(lastState); function setEnabledValue(value: boolean) { @@ -50,15 +50,15 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => { } useEffect(() => { - const listener: SendListener = (_, message) => { + const listener: MessageSendListener = (_, message) => { if (enabled) { if (settings.store.autoDisable) setEnabledValue(false); if (!message.content.startsWith("@silent ")) message.content = "@silent " + message.content; } }; - addPreSendListener(listener); - return () => void removePreSendListener(listener); + addMessagePreSendListener(listener); + return () => void removeMessagePreSendListener(listener); }, [enabled]); if (!isMainChat) return null; @@ -91,9 +91,7 @@ export default definePlugin({ name: "SilentMessageToggle", authors: [Devs.Nuckyz, Devs.CatNoir], description: "Adds a button to the chat bar to toggle sending a silent message.", - dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], settings, - start: () => addChatBarButton("SilentMessageToggle", SilentMessageToggle), - stop: () => removeChatBarButton("SilentMessageToggle") + renderChatBarButton: SilentMessageToggle, }); diff --git a/src/plugins/silentTyping/index.tsx b/src/plugins/silentTyping/index.tsx index d06ae8373..92bdbc49e 100644 --- a/src/plugins/silentTyping/index.tsx +++ b/src/plugins/silentTyping/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; +import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; @@ -43,7 +43,7 @@ const settings = definePluginSettings({ } }); -const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => { +const SilentTypingToggle: ChatBarButtonFactory = ({ isMainChat }) => { const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]); const toggle = () => settings.store.isEnabled = !settings.store.isEnabled; @@ -96,11 +96,12 @@ export default definePlugin({ name: "SilentTyping", authors: [Devs.Ven, Devs.Rini, Devs.ImBanana], description: "Hide that you are typing", - dependencies: ["ChatInputButtonAPI"], settings, + contextMenus: { "textarea-context": ChatBarContextCheckbox }, + patches: [ { find: '.dispatch({type:"TYPING_START_LOCAL"', @@ -136,6 +137,5 @@ export default definePlugin({ FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId }); }, - start: () => addChatBarButton("SilentTyping", SilentTypingToggle), - stop: () => removeChatBarButton("SilentTyping"), + renderChatBarButton: SilentTypingToggle, }); diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index d708c279f..6a91bb8fe 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -19,6 +19,7 @@ import "./spotifyStyles.css"; import { Settings } from "@api/Settings"; +import { classNameFactory } from "@api/Styles"; import { Flex } from "@components/Flex"; import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; import { debounce } from "@shared/debounce"; @@ -28,7 +29,7 @@ import { ContextMenuApi, FluxDispatcher, Forms, Menu, React, useEffect, useState import { SpotifyStore, Track } from "./SpotifyStore"; -const cl = (className: string) => `vc-spotify-${className}`; +const cl = classNameFactory("vc-spotify-"); function msToHuman(ms: number) { const minutes = ms / 1000 / 60; @@ -40,7 +41,7 @@ function msToHuman(ms: number) { function Svg(path: string, label: string) { return () => ( <svg - className={classes(cl("button-icon"), cl(label))} + className={cl("button-icon", label)} height="24" width="24" viewBox="0 0 24 24" @@ -126,7 +127,7 @@ function Controls() { return ( <Flex className={cl("button-row")} style={{ gap: 0 }}> <Button - className={classes(cl("button"), cl(shuffle ? "shuffle-on" : "shuffle-off"))} + className={classes(cl("button"), cl("shuffle"), cl(shuffle ? "shuffle-on" : "shuffle-off"))} onClick={() => SpotifyStore.setShuffle(!shuffle)} > <Shuffle /> @@ -143,7 +144,7 @@ function Controls() { <SkipNext /> </Button> <Button - className={classes(cl("button"), cl(repeatClassName))} + className={classes(cl("button"), cl("repeat"), cl(repeatClassName))} onClick={() => SpotifyStore.setRepeat(nextRepeat)} style={{ position: "relative" }} > @@ -285,11 +286,12 @@ function Info({ track }: { track: Track; }) { </> ); - if (coverExpanded && img) return ( - <div id={cl("album-expanded-wrapper")}> - {i} - </div> - ); + if (coverExpanded && img) + return ( + <div id={cl("album-expanded-wrapper")}> + {i} + </div> + ); return ( <div id={cl("info-wrapper")}> diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts index 5c0c5fe4e..65ee2c154 100644 --- a/src/plugins/spotifyControls/SpotifyStore.ts +++ b/src/plugins/spotifyControls/SpotifyStore.ts @@ -77,7 +77,7 @@ export const SpotifyStore = proxyLazyWebpack(() => { class SpotifyStore extends Store { public mPosition = 0; - private start = 0; + public _start = 0; public track: Track | null = null; public device: Device | null = null; @@ -100,26 +100,26 @@ export const SpotifyStore = proxyLazyWebpack(() => { public get position(): number { let pos = this.mPosition; if (this.isPlaying) { - pos += Date.now() - this.start; + pos += Date.now() - this._start; } return pos; } public set position(p: number) { this.mPosition = p; - this.start = Date.now(); + this._start = Date.now(); } prev() { - this.req("post", "/previous"); + this._req("post", "/previous"); } next() { - this.req("post", "/next"); + this._req("post", "/next"); } setVolume(percent: number) { - this.req("put", "/volume", { + this._req("put", "/volume", { query: { volume_percent: Math.round(percent) } @@ -131,17 +131,17 @@ export const SpotifyStore = proxyLazyWebpack(() => { } setPlaying(playing: boolean) { - this.req("put", playing ? "/play" : "/pause"); + this._req("put", playing ? "/play" : "/pause"); } setRepeat(state: Repeat) { - this.req("put", "/repeat", { + this._req("put", "/repeat", { query: { state } }); } setShuffle(state: boolean) { - this.req("put", "/shuffle", { + this._req("put", "/shuffle", { query: { state } }).then(() => { this.shuffle = state; @@ -154,7 +154,7 @@ export const SpotifyStore = proxyLazyWebpack(() => { this.isSettingPosition = true; - return this.req("put", "/seek", { + return this._req("put", "/seek", { query: { position_ms: Math.round(ms) } @@ -164,7 +164,7 @@ export const SpotifyStore = proxyLazyWebpack(() => { }); } - private req(method: "post" | "get" | "put", route: string, data: any = {}) { + _req(method: "post" | "get" | "put", route: string, data: any = {}) { if (this.device?.is_active) (data.query ??= {}).device_id = this.device.id; diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css index 893dc8175..d6cbad459 100644 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ b/src/plugins/spotifyControls/spotifyStyles.css @@ -30,22 +30,17 @@ background-color: var(--background-modifier-selected); } -.vc-spotify-button svg { +.vc-spotify-button-icon { height: 24px; width: 24px; } -[class*="vc-spotify-shuffle"] > svg, -[class*="vc-spotify-repeat"] > svg { +.vc-spotify-shuffle .vc-spotify-button-icon, +.vc-spotify-repeat .vc-spotify-button-icon { width: 22px; height: 22px; } -.vc-spotify-button svg path { - width: 100%; - height: 100%; -} - /* .vc-spotify-button:hover { filter: brightness(1.3); } */ @@ -87,12 +82,19 @@ gap: 0.5em; } -#vc-spotify-info-wrapper img { +#vc-spotify-album-image { height: 90%; object-fit: contain; + border-radius: 3px; + transition: filter 0.2s; } -#vc-spotify-album-expanded-wrapper img { +#vc-spotify-album-image:hover { + filter: brightness(1.2); + cursor: pointer; +} + +#vc-spotify-album-expanded-wrapper #vc-spotify-album-image { width: 100%; object-fit: contain; } @@ -137,16 +139,6 @@ cursor: pointer; } -#vc-spotify-album-image { - border-radius: 3px; - transition: filter 0.2s; -} - -#vc-spotify-album-image:hover { - filter: brightness(1.2); - cursor: pointer; -} - #vc-spotify-progress-bar { position: relative; color: var(--text-normal); diff --git a/src/plugins/startupTimings/index.tsx b/src/plugins/startupTimings/index.tsx index aabc786a4..ac7f3f0c8 100644 --- a/src/plugins/startupTimings/index.tsx +++ b/src/plugins/startupTimings/index.tsx @@ -27,12 +27,22 @@ export default definePlugin({ authors: [Devs.Megu], patches: [{ find: "#{intl::ACTIVITY_SETTINGS}", - replacement: { - match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/, - replace: (_, commaOrSemi, settings, elements) => "" + - `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` + - `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})` - } + replacement: [ + { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/, + replace: (_, commaOrSemi, settings, elements) => "" + + `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` + + `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`, + noWarn: true + }, + { + match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+\)\)\}\))(?=\)\})/, + replace: (_, commaOrSemi, settings, elements) => "" + + `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` + + `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`, + }, + ] }], StartupTimingPage }); diff --git a/src/plugins/textReplace/index.tsx b/src/plugins/textReplace/index.tsx index 615477d07..d5d6f4dc8 100644 --- a/src/plugins/textReplace/index.tsx +++ b/src/plugins/textReplace/index.tsx @@ -17,13 +17,11 @@ */ import { DataStore } from "@api/index"; -import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import { Flex } from "@components/Flex"; import { DeleteIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; -import { useForceUpdater } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; import { Button, Forms, React, TextInput, useState } from "@webpack/common"; @@ -35,8 +33,6 @@ type Rule = Record<"find" | "replace" | "onlyIfIncludes", string>; interface TextReplaceProps { title: string; rulesArray: Rule[]; - rulesKey: string; - update: () => void; } const makeEmptyRule: () => Rule = () => ({ @@ -46,34 +42,35 @@ const makeEmptyRule: () => Rule = () => ({ }); const makeEmptyRuleArray = () => [makeEmptyRule()]; -let stringRules = makeEmptyRuleArray(); -let regexRules = makeEmptyRuleArray(); - const settings = definePluginSettings({ replace: { type: OptionType.COMPONENT, - description: "", component: () => { - const update = useForceUpdater(); + const { stringRules, regexRules } = settings.use(["stringRules", "regexRules"]); + return ( <> <TextReplace title="Using String" rulesArray={stringRules} - rulesKey={STRING_RULES_KEY} - update={update} /> <TextReplace title="Using Regex" rulesArray={regexRules} - rulesKey={REGEX_RULES_KEY} - update={update} /> <TextReplaceTesting /> </> ); } }, + stringRules: { + type: OptionType.CUSTOM, + default: makeEmptyRuleArray(), + }, + regexRules: { + type: OptionType.CUSTOM, + default: makeEmptyRuleArray(), + } }); function stringToRegex(str: string) { @@ -120,28 +117,24 @@ function Input({ initialValue, onChange, placeholder }: { ); } -function TextReplace({ title, rulesArray, rulesKey, update }: TextReplaceProps) { +function TextReplace({ title, rulesArray }: TextReplaceProps) { const isRegexRules = title === "Using Regex"; async function onClickRemove(index: number) { if (index === rulesArray.length - 1) return; rulesArray.splice(index, 1); - - await DataStore.set(rulesKey, rulesArray); - update(); } async function onChange(e: string, index: number, key: string) { - if (index === rulesArray.length - 1) + if (index === rulesArray.length - 1) { rulesArray.push(makeEmptyRule()); + } rulesArray[index][key] = e; - if (rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1) + if (rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1) { rulesArray.splice(index, 1); - - await DataStore.set(rulesKey, rulesArray); - update(); + } } return ( @@ -208,29 +201,26 @@ function TextReplaceTesting() { } function applyRules(content: string): string { - if (content.length === 0) + if (content.length === 0) { return content; - - if (stringRules) { - for (const rule of stringRules) { - if (!rule.find) continue; - if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue; - - content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, ""); - } } - if (regexRules) { - for (const rule of regexRules) { - if (!rule.find) continue; - if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue; + for (const rule of settings.store.stringRules) { + if (!rule.find) continue; + if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue; - try { - const regex = stringToRegex(rule.find); - content = content.replace(regex, rule.replace.replaceAll("\\n", "\n")); - } catch (e) { - new Logger("TextReplace").error(`Invalid regex: ${rule.find}`); - } + content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, ""); + } + + for (const rule of settings.store.regexRules) { + if (!rule.find) continue; + if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue; + + try { + const regex = stringToRegex(rule.find); + content = content.replace(regex, rule.replace.replaceAll("\\n", "\n")); + } catch (e) { + new Logger("TextReplace").error(`Invalid regex: ${rule.find}`); } } @@ -244,22 +234,27 @@ export default definePlugin({ name: "TextReplace", description: "Replace text in your messages. You can find pre-made rules in the #textreplace-rules channel in Vencord's Server", authors: [Devs.AutumnVN, Devs.TheKodeToad], - dependencies: ["MessageEventsAPI"], settings, - async start() { - stringRules = await DataStore.get(STRING_RULES_KEY) ?? makeEmptyRuleArray(); - regexRules = await DataStore.get(REGEX_RULES_KEY) ?? makeEmptyRuleArray(); - - this.preSend = addPreSendListener((channelId, msg) => { - // Channel used for sharing rules, applying rules here would be messy - if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return; - msg.content = applyRules(msg.content); - }); + onBeforeMessageSend(channelId, msg) { + // Channel used for sharing rules, applying rules here would be messy + if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return; + msg.content = applyRules(msg.content); }, - stop() { - removePreSendListener(this.preSend); + async start() { + // TODO(OptionType.CUSTOM Related): Remove DataStore rules migrations once enough time has passed + const oldStringRules = await DataStore.get<Rule[]>(STRING_RULES_KEY); + if (oldStringRules != null) { + settings.store.stringRules = oldStringRules; + await DataStore.del(STRING_RULES_KEY); + } + + const oldRegexRules = await DataStore.get<Rule[]>(REGEX_RULES_KEY); + if (oldRegexRules != null) { + settings.store.regexRules = oldRegexRules; + await DataStore.del(REGEX_RULES_KEY); + } } }); diff --git a/src/plugins/translate/TranslateIcon.tsx b/src/plugins/translate/TranslateIcon.tsx index fa1d9abf6..1b77fb94c 100644 --- a/src/plugins/translate/TranslateIcon.tsx +++ b/src/plugins/translate/TranslateIcon.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { ChatBarButton } from "@api/ChatButtons"; +import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { classes } from "@utils/misc"; import { openModal } from "@utils/modal"; import { Alerts, Forms, Tooltip, useEffect, useState } from "@webpack/common"; @@ -40,7 +40,7 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?: export let setShouldShowTranslateEnabledTooltip: undefined | ((show: boolean) => void); -export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => { +export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => { const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]); const [shouldShowTranslateEnabledTooltip, setter] = useState(false); diff --git a/src/plugins/translate/TranslateModal.tsx b/src/plugins/translate/TranslateModal.tsx index 7a32d1b75..786f81054 100644 --- a/src/plugins/translate/TranslateModal.tsx +++ b/src/plugins/translate/TranslateModal.tsx @@ -76,7 +76,7 @@ export function TranslateModal({ rootProps }: { rootProps: ModalProps; }) { return ( <ModalRoot {...rootProps}> <ModalHeader className={cl("modal-header")}> - <Forms.FormTitle tag="h2"> + <Forms.FormTitle tag="h2" className={cl("modal-title")}> Translate </Forms.FormTitle> <ModalCloseButton onClick={rootProps.onClose} /> diff --git a/src/plugins/translate/TranslationAccessory.tsx b/src/plugins/translate/TranslationAccessory.tsx index 8e8f4c174..9b6393cc9 100644 --- a/src/plugins/translate/TranslationAccessory.tsx +++ b/src/plugins/translate/TranslationAccessory.tsx @@ -55,7 +55,7 @@ export function TranslationAccessory({ message }: { message: Message; }) { return ( <span className={cl("accessory")}> - <TranslateIcon width={16} height={16} /> + <TranslateIcon width={16} height={16} className={cl("accessory-icon")} /> {Parser.parse(translation.text)} {" "} (translated from {translation.sourceLanguage} - <Dismiss onDismiss={() => setTranslation(undefined)} />) diff --git a/src/plugins/translate/index.tsx b/src/plugins/translate/index.tsx index de61cef9c..363897d1b 100644 --- a/src/plugins/translate/index.tsx +++ b/src/plugins/translate/index.tsx @@ -18,11 +18,7 @@ import "./styles.css"; -import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; -import { addAccessory, removeAccessory } from "@api/MessageAccessories"; -import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; -import { addButton, removeButton } from "@api/MessagePopover"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { ChannelStore, Menu } from "@webpack/common"; @@ -51,11 +47,12 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => )); }; +let tooltipTimeout: any; + export default definePlugin({ name: "Translate", description: "Translate messages with Google Translate or DeepL", authors: [Devs.Ven, Devs.AshtonMemer], - dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"], settings, contextMenus: { "message": messageCtxPatch @@ -63,45 +60,34 @@ export default definePlugin({ // not used, just here in case some other plugin wants it or w/e translate, - start() { - addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />); + renderMessageAccessory: props => <TranslationAccessory message={props.message} />, - addChatBarButton("vc-translate", TranslateChatBarIcon); + renderChatBarButton: TranslateChatBarIcon, - addButton("vc-translate", message => { - if (!message.content) return null; + renderMessagePopoverButton(message) { + if (!message.content) return null; - return { - label: "Translate", - icon: TranslateIcon, - message, - channel: ChannelStore.getChannel(message.channel_id), - onClick: async () => { - const trans = await translate("received", message.content); - handleTranslate(message.id, trans); - } - }; - }); - - let tooltipTimeout: any; - this.preSend = addPreSendListener(async (_, message) => { - if (!settings.store.autoTranslate) return; - if (!message.content) return; - - setShouldShowTranslateEnabledTooltip?.(true); - clearTimeout(tooltipTimeout); - tooltipTimeout = setTimeout(() => setShouldShowTranslateEnabledTooltip?.(false), 2000); - - const trans = await translate("sent", message.content); - message.content = trans.text; - - }); + return { + label: "Translate", + icon: TranslateIcon, + message, + channel: ChannelStore.getChannel(message.channel_id), + onClick: async () => { + const trans = await translate("received", message.content); + handleTranslate(message.id, trans); + } + }; }, - stop() { - removePreSendListener(this.preSend); - removeChatBarButton("vc-translate"); - removeButton("vc-translate"); - removeAccessory("vc-translation"); - }, + async onBeforeMessageSend(_, message) { + if (!settings.store.autoTranslate) return; + if (!message.content) return; + + setShouldShowTranslateEnabledTooltip?.(true); + clearTimeout(tooltipTimeout); + tooltipTimeout = setTimeout(() => setShouldShowTranslateEnabledTooltip?.(false), 2000); + + const trans = await translate("sent", message.content); + message.content = trans.text; + } }); diff --git a/src/plugins/translate/styles.css b/src/plugins/translate/styles.css index 64b6c9b9f..c07c9e362 100644 --- a/src/plugins/translate/styles.css +++ b/src/plugins/translate/styles.css @@ -6,7 +6,7 @@ place-content: center space-between; } -.vc-trans-modal-header h1 { +.vc-trans-modal-title { margin: 0; } @@ -17,7 +17,7 @@ font-weight: 400; } -.vc-trans-accessory svg { +.vc-trans-accessory-icon { margin-right: 0.25em; } diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index e6a1b3b4f..e6903bcde 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -23,12 +23,12 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; -import { findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack"; +import { findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { buildSeveralUsers } from "../typingTweaks"; -const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots"); +const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const TypingStore = findStoreLazy("TypingStore"); @@ -100,16 +100,24 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s {props => ( <div className="vc-typing-indicator" {...props}> {((settings.store.indicatorMode & IndicatorMode.Avatars) === IndicatorMode.Avatars) && ( - <UserSummaryItem - users={typingUsersArray.map(id => UserStore.getUser(id))} - guildId={guildId} - renderIcon={false} - max={3} - showDefaultAvatarsForNullUsers - showUserPopout - size={16} - className="vc-typing-indicator-avatars" - /> + <div + onClick={e => { + e.stopPropagation(); + e.preventDefault(); + }} + onKeyPress={e => e.stopPropagation()} + > + <UserSummaryItem + users={typingUsersArray.map(id => UserStore.getUser(id))} + guildId={guildId} + renderIcon={false} + max={3} + showDefaultAvatarsForNullUsers + showUserPopout + size={16} + className="vc-typing-indicator-avatars" + /> + </div> )} {((settings.store.indicatorMode & IndicatorMode.Dots) === IndicatorMode.Dots) && ( <div className="vc-typing-indicator-dots"> diff --git a/src/plugins/unindent/index.ts b/src/plugins/unindent/index.ts index a197ef4e9..d8853a93f 100644 --- a/src/plugins/unindent/index.ts +++ b/src/plugins/unindent/index.ts @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { addPreEditListener, addPreSendListener, MessageObject, removePreEditListener, removePreSendListener } from "@api/MessageEvents"; +import { MessageObject } from "@api/MessageEvents"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; @@ -24,7 +24,7 @@ export default definePlugin({ name: "Unindent", description: "Trims leading indentation from codeblocks", authors: [Devs.Ven], - dependencies: ["MessageEventsAPI"], + patches: [ { find: "inQuote:", @@ -55,13 +55,11 @@ export default definePlugin({ }); }, - start() { - this.preSend = addPreSendListener((_, msg) => this.unindentMsg(msg)); - this.preEdit = addPreEditListener((_cid, _mid, msg) => this.unindentMsg(msg)); + onBeforeMessageSend(_, msg) { + return this.unindentMsg(msg); }, - stop() { - removePreSendListener(this.preSend); - removePreEditListener(this.preEdit); + onBeforeMessageEdit(_cid, _mid, msg) { + return this.unindentMsg(msg); } }); diff --git a/src/plugins/unsuppressEmbeds/index.tsx b/src/plugins/unsuppressEmbeds/index.tsx index 16debf711..2df64b72e 100644 --- a/src/plugins/unsuppressEmbeds/index.tsx +++ b/src/plugins/unsuppressEmbeds/index.tsx @@ -21,12 +21,18 @@ import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common"; +import { MessageSnapshot } from "@webpack/types"; + const EMBED_SUPPRESSED = 1 << 2; -const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => { +const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, messageSnapshots, embeds, flags, id: messageId } }) => { const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0; - if (!isEmbedSuppressed && !embeds.length) return; + const hasEmbedsInSnapshots = messageSnapshots.some( + (snapshot: MessageSnapshot) => snapshot?.message.embeds.length + ); + + if (!isEmbedSuppressed && !embeds.length && !hasEmbedsInSnapshots) return; const hasEmbedPerms = channel.isPrivate() || !!(PermissionStore.getChannelPermissions({ id: channel.id }) & PermissionsBits.EMBED_LINKS); if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return; diff --git a/src/plugins/userMessagesPronouns/index.ts b/src/plugins/userMessagesPronouns/index.ts index 27b162b90..1699251ee 100644 --- a/src/plugins/userMessagesPronouns/index.ts +++ b/src/plugins/userMessagesPronouns/index.ts @@ -41,11 +41,20 @@ export default definePlugin({ }, { find: '="SYSTEM_TAG"', - replacement: { - // Add next to username (compact mode) - match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g, - replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0])," - } + replacement: [ + { + // Add next to username (compact mode) + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g, + replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),", + noWarn: true + }, + { + // Add next to username (compact mode) + match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\)\),(?=\i)/g, + replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),", + }, + ] } ], diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx index e8924f829..9029cdc58 100644 --- a/src/plugins/userVoiceShow/components.tsx +++ b/src/plugins/userVoiceShow/components.tsx @@ -22,7 +22,7 @@ const VoiceStateStore = findStoreLazy("VoiceStateStore"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const Avatar = findComponentByCodeLazy(".status)/2):0"); -const GroupDMAvatars = findComponentByCodeLazy(".AvatarSizeSpecs[", "getAvatarURL"); +const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL"); const ActionButtonClasses = findByPropsLazy("actionButton", "highlight"); @@ -128,17 +128,15 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) { ); } -interface VoiceChannelIndicatorProps { +export interface VoiceChannelIndicatorProps { userId: string; - isMessageIndicator?: boolean; - isProfile?: boolean; isActionButton?: boolean; shouldHighlight?: boolean; } const clickTimers = {} as Record<string, any>; -export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndicator, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { +export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined); const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId); @@ -182,7 +180,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndi {props => { const iconProps: IconProps = { ...props, - className: classes(isMessageIndicator && cl("message-indicator"), (!isProfile && !isActionButton) && cl("speaker-margin"), isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight), + className: classes(isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight), size: isActionButton ? 20 : undefined, onClick }; diff --git a/src/plugins/userVoiceShow/index.tsx b/src/plugins/userVoiceShow/index.tsx index e0d5d8abd..3d119c433 100644 --- a/src/plugins/userVoiceShow/index.tsx +++ b/src/plugins/userVoiceShow/index.tsx @@ -18,8 +18,8 @@ import "./style.css"; -import { addDecorator, removeDecorator } from "@api/MemberListDecorators"; -import { addDecoration, removeDecoration } from "@api/MessageDecorations"; +import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators"; +import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -60,7 +60,7 @@ export default definePlugin({ find: "#{intl::USER_PROFILE_LOAD_ERROR}", replacement: { match: /(\.fetchError.+?\?)null/, - replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId,isProfile:true})` + replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId})` }, predicate: () => settings.store.showInUserProfileModal }, @@ -96,16 +96,16 @@ export default definePlugin({ start() { if (settings.store.showInMemberList) { - addDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />); + addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />); } if (settings.store.showInMessages) { - addDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />); + addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} />); } }, stop() { - removeDecorator("UserVoiceShow"); - removeDecoration("UserVoiceShow"); + removeMemberListDecorator("UserVoiceShow"); + removeMessageDecoration("UserVoiceShow"); }, VoiceChannelIndicator diff --git a/src/plugins/userVoiceShow/style.css b/src/plugins/userVoiceShow/style.css index d172975b8..f9fd56fbf 100644 --- a/src/plugins/userVoiceShow/style.css +++ b/src/plugins/userVoiceShow/style.css @@ -13,16 +13,6 @@ color: var(--interactive-hover); } -.vc-uvs-speaker-margin { - margin-left: 4px; -} - -.vc-uvs-message-indicator { - display: inline-flex; - top: 2.5px; - position: relative; -} - .vc-uvs-tooltip-container { max-width: 300px; } diff --git a/src/plugins/usrbg/index.css b/src/plugins/usrbg/index.css deleted file mode 100644 index 69c5b1857..000000000 --- a/src/plugins/usrbg/index.css +++ /dev/null @@ -1,12 +0,0 @@ -:is([class*="userProfile"], [class*="userPopout"]) [class*="bannerPremium"] { - background: center / cover no-repeat; -} - -[class*="NonPremium"]:has([class*="bannerPremium"]) [class*="avatarPositionNormal"], -[class*="PremiumWithoutBanner"]:has([class*="bannerPremium"]) [class*="avatarPositionPremiumNoBanner"] { - top: 76px; -} - -[style*="background-image"] [class*="background_"] { - background-color: transparent !important; -} diff --git a/src/plugins/usrbg/index.tsx b/src/plugins/usrbg/index.tsx index 788a79ae7..df823102a 100644 --- a/src/plugins/usrbg/index.tsx +++ b/src/plugins/usrbg/index.tsx @@ -17,13 +17,10 @@ */ import { definePluginSettings } from "@api/Settings"; -import { enableStyle } from "@api/Styles"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import style from "./index.css?managed"; - const API_URL = "https://usrbg.is-hardly.online/users"; interface UsrbgApiReturn { @@ -115,8 +112,6 @@ export default definePlugin({ }, async start() { - enableStyle(style); - const res = await fetch(API_URL); if (res.ok) { this.data = await res.json(); diff --git a/src/plugins/vencordToolbox/index.css b/src/plugins/vencordToolbox/index.css index a1c85ad50..642375b4a 100644 --- a/src/plugins/vencordToolbox/index.css +++ b/src/plugins/vencordToolbox/index.css @@ -1,16 +1,16 @@ .vc-toolbox-btn, -.vc-toolbox-btn>svg { +.vc-toolbox-icon { -webkit-app-region: no-drag; } -.vc-toolbox-btn>svg { +.vc-toolbox-icon { color: var(--interactive-normal); } -.vc-toolbox-btn[class*="selected"]>svg { +.vc-toolbox-btn[class*="selected"] .vc-toolbox-icon { color: var(--interactive-active); } -.vc-toolbox-btn:hover>svg { +.vc-toolbox-btn:hover .vc-toolbox-icon { color: var(--interactive-hover); } diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index 00805fbd3..c59df00a5 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -23,11 +23,11 @@ import { Settings, useSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { findExportedComponentLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@webpack"; import { Menu, Popout, useState } from "@webpack/common"; import type { ReactNode } from "react"; -const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider"); +const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"'); function VencordPopout(onClose: () => void) { const { useQuickCss } = useSettings(["useQuickCss"]); @@ -88,7 +88,7 @@ function VencordPopout(onClose: () => void) { function VencordPopoutIcon(isShown: boolean) { return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27 27" width={24} height={24}> + <svg viewBox="0 0 27 27" width={24} height={24} className="vc-toolbox-icon"> <path fill="currentColor" d={isShown ? "M9 0h1v1h1v2h1v2h3V3h1V1h1V0h1v2h1v2h1v7h-1v-1h-3V9h1V6h-1v4h-3v1h1v-1h2v1h3v1h-1v1h-3v2h1v1h1v1h1v3h-1v4h-2v-1h-1v-4h-1v4h-1v1h-2v-4H9v-3h1v-1h1v-1h1v-2H9v-1H8v-1h3V6h-1v3h1v1H8v1H7V4h1V2h1M5 19h2v1h1v1h1v3H4v-1h2v-1H4v-2h1m15-1h2v1h1v2h-2v1h2v1h-5v-3h1v-1h1m4 3h4v1h-4" : "M0 0h7v1H6v1H5v1H4v1H3v1H2v1h5v1H0V6h1V5h1V4h1V3h1V2h1V1H0m13 2h5v1h-1v1h-1v1h-1v1h3v1h-5V7h1V6h1V5h1V4h-3m8 5h1v5h1v-1h1v1h-1v1h1v-1h1v1h-1v3h-1v1h-2v1h-1v1h1v-1h2v-1h1v2h-1v1h-2v1h-1v-1h-1v1h-6v-1h-1v-1h-1v-2h1v1h2v1h3v1h1v-1h-1v-1h-3v-1h-4v-4h1v-2h1v-1h1v-1h1v2h1v1h1v-1h1v1h-1v1h2v-2h1v-2h1v-1h1M8 14h2v1H9v4h1v2h1v1h1v1h1v1h4v1h-6v-1H5v-1H4v-5h1v-1h1v-2h2m17 3h1v3h-1v1h-1v1h-1v2h-2v-2h2v-1h1v-1h1m1 0h1v3h-1v1h-2v-1h1v-1h1"} /> </svg> ); diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index c53116b4b..afd9d48c5 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -193,10 +193,18 @@ export default definePlugin({ // Avatar component used in User DMs "User Profile" popup in the right and Profiles Modal pfp { find: ".overlay:void 0,status:", - replacement: { - match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/, - replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)}," - }, + replacement: [ + { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/, + replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},", + noWarn: true + }, + { + match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",.{0,100}className:\i,/, + replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},", + } + ], all: true }, // Banners @@ -220,16 +228,16 @@ export default definePlugin({ { find: ".cursorPointer:null,children", replacement: { - match: /.Avatar,.+?src:(.+?\))(?=[,}])/, - replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})` + match: /(?=,src:(\i.getAvatarURL\(.+?[)]))/, + replace: (_, avatarUrl) => `,onClick:()=>$self.openAvatar(${avatarUrl})` } }, // User Dms top large icon { find: 'experimentLocation:"empty_messages"', replacement: { - match: /.Avatar,.+?src:(.+?\))(?=[,}])/, - replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})` + match: /(?<=SIZE_80,)(?=src:(.+?\))[,}])/, + replace: (_, avatarUrl) => `onClick:()=>$self.openAvatar(${avatarUrl}),` } } ] diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx index 8ee1ca8d7..ddcbd3b46 100644 --- a/src/plugins/viewRaw/index.tsx +++ b/src/plugins/viewRaw/index.tsx @@ -17,18 +17,17 @@ */ import { NavContextMenuPatchCallback } from "@api/ContextMenu"; -import { addButton, removeButton } from "@api/MessagePopover"; import { definePluginSettings } from "@api/Settings"; import { CodeBlock } from "@components/CodeBlock"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; -import { getIntlMessage } from "@utils/discord"; +import { getCurrentGuild, getIntlMessage } from "@utils/discord"; import { Margins } from "@utils/margins"; import { copyWithToast } from "@utils/misc"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ChannelStore, Forms, Menu, Text } from "@webpack/common"; +import { Button, ChannelStore, Forms, GuildStore, Menu, Text } from "@webpack/common"; import { Message } from "discord-types/general"; @@ -119,7 +118,7 @@ const settings = definePluginSettings({ } }); -function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenuPatchCallback { +function MakeContextCallback(name: "Guild" | "Role" | "User" | "Channel"): NavContextMenuPatchCallback { return (children, props) => { const value = props[name.toLowerCase()]; if (!value) return; @@ -145,58 +144,71 @@ function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenu }; } +const devContextCallback: NavContextMenuPatchCallback = (children, { id }: { id: string; }) => { + const guild = getCurrentGuild(); + if (!guild) return; + + const role = GuildStore.getRole(guild.id, id); + if (!role) return; + + children.push( + <Menu.MenuItem + id={"vc-view-role-raw"} + label="View Raw" + action={() => openViewRawModal(JSON.stringify(role, null, 4), "Role")} + icon={CopyIcon} + /> + ); +}; + export default definePlugin({ name: "ViewRaw", description: "Copy and view the raw content/data of any message, channel or guild", authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna], - dependencies: ["MessagePopoverAPI"], settings, + contextMenus: { "guild-context": MakeContextCallback("Guild"), + "guild-settings-role-context": MakeContextCallback("Role"), "channel-context": MakeContextCallback("Channel"), "thread-context": MakeContextCallback("Channel"), "gdm-context": MakeContextCallback("Channel"), - "user-context": MakeContextCallback("User") + "user-context": MakeContextCallback("User"), + "dev-context": devContextCallback }, - start() { - addButton("ViewRaw", msg => { - const handleClick = () => { - if (settings.store.clickMethod === "Right") { - copyWithToast(msg.content); - } else { - openViewRawModalMessage(msg); - } - }; + renderMessagePopoverButton(msg) { + const handleClick = () => { + if (settings.store.clickMethod === "Right") { + copyWithToast(msg.content); + } else { + openViewRawModalMessage(msg); + } + }; - const handleContextMenu = e => { - if (settings.store.clickMethod === "Left") { - e.preventDefault(); - e.stopPropagation(); - copyWithToast(msg.content); - } else { - e.preventDefault(); - e.stopPropagation(); - openViewRawModalMessage(msg); - } - }; + const handleContextMenu = e => { + if (settings.store.clickMethod === "Left") { + e.preventDefault(); + e.stopPropagation(); + copyWithToast(msg.content); + } else { + e.preventDefault(); + e.stopPropagation(); + openViewRawModalMessage(msg); + } + }; - const label = settings.store.clickMethod === "Right" - ? "Copy Raw (Left Click) / View Raw (Right Click)" - : "View Raw (Left Click) / Copy Raw (Right Click)"; + const label = settings.store.clickMethod === "Right" + ? "Copy Raw (Left Click) / View Raw (Right Click)" + : "View Raw (Left Click) / Copy Raw (Right Click)"; - return { - label, - icon: CopyIcon, - message: msg, - channel: ChannelStore.getChannel(msg.channel_id), - onClick: handleClick, - onContextMenu: handleContextMenu - }; - }); - }, - - stop() { - removeButton("ViewRaw"); + return { + label, + icon: CopyIcon, + message: msg, + channel: ChannelStore.getChannel(msg.channel_id), + onClick: handleClick, + onContextMenu: handleContextMenu + }; } }); diff --git a/src/plugins/voiceMessages/styles.css b/src/plugins/voiceMessages/styles.css index 1e2b1433a..4f2e1d57e 100644 --- a/src/plugins/voiceMessages/styles.css +++ b/src/plugins/voiceMessages/styles.css @@ -9,10 +9,6 @@ margin-bottom: 1em; } -.vc-vmsg-modal audio { - width: 100%; -} - .vc-vmsg-preview { color: var(--text-normal); border-radius: 24px; diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 661238052..0af47ded8 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -20,10 +20,13 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { saveFile } from "@utils/web"; -import { findByPropsLazy } from "@webpack"; +import { filters, mapMangledModuleLazy } from "@webpack"; import { Clipboard, ComponentDispatch } from "@webpack/common"; -const ctxMenuCallbacks = findByPropsLazy("contextMenuCallbackNative"); +const ctxMenuCallbacks = mapMangledModuleLazy('.tagName)==="TEXTAREA"||', { + contextMenuCallbackWeb: filters.byCode('.tagName)==="INPUT"||'), + contextMenuCallbackNative: filters.byCode('.tagName)==="TEXTAREA"||') +}); async function fetchImage(url: string) { const res = await fetch(url); diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index 803cc7156..aea57fef2 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -93,7 +93,7 @@ function makeRenderMoreUsers(users: User[]) { }; } -function handleClickAvatar(event: React.MouseEvent<HTMLElement, MouseEvent>) { +function handleClickAvatar(event: React.UIEvent<HTMLElement, Event>) { event.stopPropagation(); } @@ -165,7 +165,7 @@ export default definePlugin({ <div style={{ marginLeft: "0.5em", transform: "scale(0.9)" }} > - <div onClick={handleClickAvatar}> + <div onClick={handleClickAvatar} onKeyPress={handleClickAvatar}> <UserSummaryItem users={users} guildId={ChannelStore.getChannel(message.channel_id)?.guild_id} diff --git a/src/plugins/youtubeAdblock.desktop/native.ts b/src/plugins/youtubeAdblock.desktop/native.ts index 8cc6a3232..ae05d6462 100644 --- a/src/plugins/youtubeAdblock.desktop/native.ts +++ b/src/plugins/youtubeAdblock.desktop/native.ts @@ -10,7 +10,7 @@ import adguard from "file://adguard.js?minify"; app.on("browser-window-created", (_, win) => { win.webContents.on("frame-created", (_, { frame }) => { - frame.once("dom-ready", () => { + frame?.once("dom-ready", () => { if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return; if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) { diff --git a/src/shared/SettingsStore.ts b/src/shared/SettingsStore.ts index 4109704bc..0b6aa25b6 100644 --- a/src/shared/SettingsStore.ts +++ b/src/shared/SettingsStore.ts @@ -6,6 +6,9 @@ import { LiteralUnion } from "type-fest"; +export const SYM_IS_PROXY = Symbol("SettingsStore.isProxy"); +export const SYM_GET_RAW_TARGET = Symbol("SettingsStore.getRawTarget"); + // Resolves a possibly nested prop in the form of "some.nested.prop" to type of T.some.nested.prop type ResolvePropDeep<T, P> = P extends `${infer Pre}.${infer Suf}` ? Pre extends keyof T @@ -28,6 +31,11 @@ interface SettingsStoreOptions { // merges the SettingsStoreOptions type into the class export interface SettingsStore<T extends object> extends SettingsStoreOptions { } +interface ProxyContext<T extends object = any> { + root: T; + path: string; +} + /** * The SettingsStore allows you to easily create a mutable store that * has support for global and path-based change listeners. @@ -35,6 +43,90 @@ export interface SettingsStore<T extends object> extends SettingsStoreOptions { export class SettingsStore<T extends object> { private pathListeners = new Map<string, Set<(newData: any) => void>>(); private globalListeners = new Set<(newData: T, path: string) => void>(); + private readonly proxyContexts = new WeakMap<any, ProxyContext<T>>(); + + private readonly proxyHandler: ProxyHandler<any> = (() => { + const self = this; + + return { + get(target, key: any, receiver) { + if (key === SYM_IS_PROXY) { + return true; + } + + if (key === SYM_GET_RAW_TARGET) { + return target; + } + + let v = Reflect.get(target, key, receiver); + + const proxyContext = self.proxyContexts.get(target); + if (proxyContext == null) { + return v; + } + + const { root, path } = proxyContext; + + if (!(key in target) && self.getDefaultValue != null) { + v = self.getDefaultValue({ + target, + key, + root, + path + }); + } + + if (typeof v === "object" && v !== null && !v[SYM_IS_PROXY]) { + const getPath = `${path}${path && "."}${key}`; + return self.makeProxy(v, root, getPath); + } + + return v; + }, + set(target, key: string, value) { + if (value?.[SYM_IS_PROXY]) { + value = value[SYM_GET_RAW_TARGET]; + } + + if (target[key] === value) { + return true; + } + + if (!Reflect.set(target, key, value)) { + return false; + } + + const proxyContext = self.proxyContexts.get(target); + if (proxyContext == null) { + return true; + } + + const { root, path } = proxyContext; + + const setPath = `${path}${path && "."}${key}`; + self.notifyListeners(setPath, value, root); + + return true; + }, + deleteProperty(target, key: string) { + if (!Reflect.deleteProperty(target, key)) { + return false; + } + + const proxyContext = self.proxyContexts.get(target); + if (proxyContext == null) { + return true; + } + + const { root, path } = proxyContext; + + const deletePath = `${path}${path && "."}${key}`; + self.notifyListeners(deletePath, undefined, root); + + return true; + } + }; + })(); /** * The store object. Making changes to this object will trigger the applicable change listeners @@ -51,39 +143,35 @@ export class SettingsStore<T extends object> { Object.assign(this, options); } - private makeProxy(object: any, root: T = object, path: string = "") { - const self = this; - - return new Proxy(object, { - get(target, key: string) { - let v = target[key]; - - if (!(key in target) && self.getDefaultValue) { - v = self.getDefaultValue({ - target, - key, - root, - path - }); - } - - if (typeof v === "object" && v !== null && !Array.isArray(v)) - return self.makeProxy(v, root, `${path}${path && "."}${key}`); - - return v; - }, - set(target, key: string, value) { - if (target[key] === value) return true; - - Reflect.set(target, key, value); - const setPath = `${path}${path && "."}${key}`; - - self.globalListeners.forEach(cb => cb(value, setPath)); - self.pathListeners.get(setPath)?.forEach(cb => cb(value)); - - return true; - } + private makeProxy(object: any, root: T = object, path = "") { + this.proxyContexts.set(object, { + root, + path }); + + return new Proxy(object, this.proxyHandler); + } + + private notifyListeners(pathStr: string, value: any, root: T) { + const paths = pathStr.split("."); + + // Because we support any type of settings with OptionType.CUSTOM, and those objects get proxied recursively, + // the path ends up including all the nested paths (plugins.pluginName.settingName.example.one). + // So, we need to extract the top-level setting path (plugins.pluginName.settingName), + // to be able to notify globalListeners and top-level setting name listeners (let { settingName } = settings.use(["settingName"]), + // with the new value + if (paths.length > 2 && paths[0] === "plugins") { + const settingPath = paths.slice(0, 3); + const settingPathStr = settingPath.join("."); + const settingValue = settingPath.reduce((acc, curr) => acc[curr], root); + + this.globalListeners.forEach(cb => cb(root, settingPathStr)); + this.pathListeners.get(settingPathStr)?.forEach(cb => cb(settingValue)); + } else { + this.globalListeners.forEach(cb => cb(root, pathStr)); + } + + this.pathListeners.get(pathStr)?.forEach(cb => cb(value)); } /** diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts index 22a381360..aec7292a3 100644 --- a/src/utils/Logger.ts +++ b/src/utils/Logger.ts @@ -32,7 +32,7 @@ export class Logger { constructor(public name: string, public color: string = "white") { } private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") { - if (IS_REPORTER && IS_WEB) { + if (IS_REPORTER && IS_WEB && !IS_VESKTOP) { console[level]("[Vencord]", this.name + ":", ...args); return; } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e75825912..6760d4d06 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -16,9 +16,14 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -export const WEBPACK_CHUNK = "webpackChunkdiscord_app"; export const REACT_GLOBAL = "Vencord.Webpack.Common.React"; +export const VENBOT_USER_ID = "1017176847865352332"; +export const VENCORD_GUILD_ID = "1015060230222131221"; +export const DONOR_ROLE_ID = "1042507929485586532"; +export const CONTRIB_ROLE_ID = "1026534353167208489"; +export const REGULAR_ROLE_ID = "1026504932959977532"; export const SUPPORT_CHANNEL_ID = "1026515880080842772"; +export const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920"; export interface Dev { name: string; @@ -579,6 +584,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "jamesbt365", id: 158567567487795200n, }, + samsam: { + name: "samsam", + id: 836452332387565589n, + }, } satisfies Record<string, Dev>); // iife so #__PURE__ works correctly diff --git a/src/utils/discord.css b/src/utils/discord.css index 12d15694b..746fb564b 100644 --- a/src/utils/discord.css +++ b/src/utils/discord.css @@ -17,7 +17,6 @@ @media(width <= 485px) { .vc-image-modal { - display: relative; overflow: visible; overflow: initial; } diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts index e46e44ad7..526c5514b 100644 --- a/src/utils/lazy.ts +++ b/src/utils/lazy.ts @@ -20,9 +20,9 @@ export function makeLazy<T>(factory: () => T, attempts = 5): () => T { let tries = 0; let cache: T; return () => { - if (!cache && attempts > tries++) { + if (cache === undefined && attempts > tries++) { cache = factory(); - if (!cache && attempts === tries) + if (cache === undefined && attempts === tries) console.error("Lazy factory failed:", factory); } return cache; diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 28c371c5b..adca15d3b 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -100,6 +100,11 @@ export function pluralise(amount: number, singular: string, plural = singular + return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`; } +export function interpolateIfDefined(strings: TemplateStringsArray, ...args: any[]) { + if (args.some(arg => arg == null)) return ""; + return String.raw({ raw: strings }, ...args); +} + export function tryOrElse<T>(func: () => T, fallback: T): T { try { const res = func(); diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 83b2f0553..d06e58036 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { findByPropsLazy, findModuleId, proxyLazyWebpack, wreq } from "@webpack"; +import { filters, findModuleId, mapMangledModuleLazy, proxyLazyWebpack, wreq } from "@webpack"; import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react"; import { LazyComponent } from "./react"; @@ -49,7 +49,7 @@ export interface ModalOptions { type RenderFunction = (props: ModalProps) => ReactNode | Promise<ReactNode>; -export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as { +interface Modals { ModalRoot: ComponentType<PropsWithChildren<{ transitionState: ModalTransitionState; size?: ModalSize; @@ -99,7 +99,21 @@ export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as { hideOnFullscreen?: boolean; className?: string; }>; -}; +} + +export const Modals: Modals = mapMangledModuleLazy(':"thin")', { + ModalRoot: filters.componentByCode('.MODAL,"aria-labelledby":'), + ModalHeader: filters.componentByCode(",id:"), + ModalContent: filters.componentByCode(".content,"), + ModalFooter: filters.componentByCode(".footer,"), + ModalCloseButton: filters.componentByCode(".close]:") +}); + +export const ModalRoot = LazyComponent(() => Modals.ModalRoot); +export const ModalHeader = LazyComponent(() => Modals.ModalHeader); +export const ModalContent = LazyComponent(() => Modals.ModalContent); +export const ModalFooter = LazyComponent(() => Modals.ModalFooter); +export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton); export type MediaModalItem = { url: string; @@ -135,38 +149,33 @@ export const openMediaModal: (props: MediaModalProps) => void = proxyLazyWebpack return Object.values<any>(openMediaModalModule).find(v => String(v).includes("modalKey:")); }); -export const ModalRoot = LazyComponent(() => Modals.ModalRoot); -export const ModalHeader = LazyComponent(() => Modals.ModalHeader); -export const ModalContent = LazyComponent(() => Modals.ModalContent); -export const ModalFooter = LazyComponent(() => Modals.ModalFooter); -export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton); +interface ModalAPI { + /** + * Wait for the render promise to resolve, then open a modal with it. + * This is equivalent to render().then(openModal) + * You should use the Modal components exported by this file + */ + openModalLazy: (render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }) => Promise<string>; + /** + * Open a Modal with the given render function. + * You should use the Modal components exported by this file + */ + openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string; + /** + * Close a modal by its key + */ + closeModal: (modalKey: string, contextKey?: string) => void; + /** + * Close all open modals + */ + closeAllModals: () => void; +} -export const ModalAPI = findByPropsLazy("openModalLazy"); - -/** - * Wait for the render promise to resolve, then open a modal with it. - * This is equivalent to render().then(openModal) - * You should use the Modal components exported by this file - */ -export const openModalLazy: (render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }) => Promise<string> - = proxyLazyWebpack(() => ModalAPI.openModalLazy); - -/** - * Open a Modal with the given render function. - * You should use the Modal components exported by this file - */ -export const openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string - = proxyLazyWebpack(() => ModalAPI.openModal); - -/** - * Close a modal by its key - */ -export const closeModal: (modalKey: string, contextKey?: string) => void - = proxyLazyWebpack(() => ModalAPI.closeModal); - -/** - * Close all open modals - */ -export const closeAllModals: () => void - = proxyLazyWebpack(() => ModalAPI.closeAllModals); +export const ModalAPI: ModalAPI = mapMangledModuleLazy(".modalKey?", { + openModalLazy: filters.byCode(".modalKey?"), + openModal: filters.byCode(",instant:"), + closeModal: filters.byCode(".onCloseCallback()"), + closeAllModals: filters.byCode(".getState();for") +}); +export const { openModalLazy, openModal, closeModal, closeAllModals } = ModalAPI; diff --git a/src/utils/patches.ts b/src/utils/patches.ts index 097c64560..b212e6241 100644 --- a/src/utils/patches.ts +++ b/src/utils/patches.ts @@ -41,16 +41,17 @@ export function canonicalizeMatch<T extends RegExp | string>(match: T): T { } const canonSource = partialCanon.replaceAll("\\i", String.raw`(?:[A-Za-z_$][\w$]*)`); - return new RegExp(canonSource, match.flags) as T; + const canonRegex = new RegExp(canonSource, match.flags); + canonRegex.toString = match.toString.bind(match); + + return canonRegex as T; } -export function canonicalizeReplace<T extends string | ReplaceFn>(replace: T, pluginName: string): T { - const self = `Vencord.Plugins.plugins[${JSON.stringify(pluginName)}]`; - +export function canonicalizeReplace<T extends string | ReplaceFn>(replace: T, pluginPath: string): T { if (typeof replace !== "function") - return replace.replaceAll("$self", self) as T; + return replace.replaceAll("$self", pluginPath) as T; - return ((...args) => replace(...args).replaceAll("$self", self)) as T; + return ((...args) => replace(...args).replaceAll("$self", pluginPath)) as T; } export function canonicalizeDescriptor<T>(descriptor: TypedPropertyDescriptor<T>, canonicalize: (value: T) => T) { @@ -65,12 +66,12 @@ export function canonicalizeDescriptor<T>(descriptor: TypedPropertyDescriptor<T> return descriptor; } -export function canonicalizeReplacement(replacement: Pick<PatchReplacement, "match" | "replace">, plugin: string) { +export function canonicalizeReplacement(replacement: Pick<PatchReplacement, "match" | "replace">, pluginPath: string) { const descriptors = Object.getOwnPropertyDescriptors(replacement); descriptors.match = canonicalizeDescriptor(descriptors.match, canonicalizeMatch); descriptors.replace = canonicalizeDescriptor( descriptors.replace, - replace => canonicalizeReplace(replace, plugin), + replace => canonicalizeReplace(replace, pluginPath), ); Object.defineProperties(replacement, descriptors); } diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts index f19928ac4..6ec3e527a 100644 --- a/src/utils/settingsSync.ts +++ b/src/utils/settingsSync.ts @@ -60,7 +60,7 @@ export async function downloadSettingsBackup() { } } -const toast = (type: number, message: string) => +const toast = (type: string, message: string) => Toasts.show({ type, message, diff --git a/src/utils/types.ts b/src/utils/types.ts index 02760d964..6a791c86e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -16,8 +16,15 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { ProfileBadge } from "@api/Badges"; +import { ChatBarButtonFactory } from "@api/ChatButtons"; import { Command } from "@api/Commands"; import { NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { MemberListDecoratorFactory } from "@api/MemberListDecorators"; +import { MessageAccessoryFactory } from "@api/MessageAccessories"; +import { MessageDecorationFactory } from "@api/MessageDecorations"; +import { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents"; +import { MessagePopoverButtonFactory } from "@api/MessagePopover"; import { FluxEvents } from "@webpack/types"; import { JSX } from "react"; import { Promisable } from "type-fest"; @@ -34,8 +41,17 @@ export interface PatchReplacement { match: string | RegExp; /** The replacement string or function which returns the string for the patch replacement */ replace: string | ReplaceFn; - /** A function which returns whether this patch replacement should be applied */ + /** Do not warn if this replacement did no changes */ + noWarn?: boolean; + /** + * A function which returns whether this patch replacement should be applied. + * This is ran before patches are registered, so if this returns false, the patch will never be registered. + */ predicate?(): boolean; + /** The minimum build number for this patch to be applied */ + fromBuild?: number; + /** The maximum build number for this patch to be applied */ + toBuild?: number; } export interface Patch { @@ -50,8 +66,15 @@ export interface Patch { noWarn?: boolean; /** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */ group?: boolean; - /** A function which returns whether this patch should be applied */ + /** + * A function which returns whether this patch replacement should be applied. + * This is ran before patches are registered, so if this returns false, the patch will never be registered. + */ predicate?(): boolean; + /** The minimum build number for this patch to be applied */ + fromBuild?: number; + /** The maximum build number for this patch to be applied */ + toBuild?: number; } export interface PluginAuthor { @@ -142,6 +165,25 @@ export interface PluginDef { toolboxActions?: Record<string, () => void>; tags?: string[]; + + /** + * Managed style to automatically enable and disable when the plugin is enabled or disabled + */ + managedStyle?: string; + + userProfileBadge?: ProfileBadge; + + onMessageClick?: MessageClickListener; + onBeforeMessageSend?: MessageSendListener; + onBeforeMessageEdit?: MessageEditListener; + + renderMessagePopoverButton?: MessagePopoverButtonFactory; + renderMessageAccessory?: MessageAccessoryFactory; + renderMessageDecoration?: MessageDecorationFactory; + + renderMemberListDecorator?: MemberListDecoratorFactory; + + renderChatBarButton?: ChatBarButtonFactory; } export const enum StartAt { @@ -168,6 +210,7 @@ export const enum OptionType { SELECT, SLIDER, COMPONENT, + CUSTOM } export type SettingsDefinition = Record<string, PluginSettingDef>; @@ -176,15 +219,16 @@ export type SettingsChecks<D extends SettingsDefinition> = { (IsDisabled<DefinedSettings<D>> & IsValid<PluginSettingType<D[K]>, DefinedSettings<D>>); }; -export type PluginSettingDef = ( - | PluginSettingStringDef - | PluginSettingNumberDef - | PluginSettingBooleanDef - | PluginSettingSelectDef - | PluginSettingSliderDef - | PluginSettingComponentDef - | PluginSettingBigIntDef -) & PluginSettingCommon; +export type PluginSettingDef = + (PluginSettingCustomDef & Pick<PluginSettingCommon, "onChange">) | + (PluginSettingComponentDef & Omit<PluginSettingCommon, "description" | "placeholder">) | (( + | PluginSettingStringDef + | PluginSettingNumberDef + | PluginSettingBooleanDef + | PluginSettingSelectDef + | PluginSettingSliderDef + | PluginSettingBigIntDef + ) & PluginSettingCommon); export interface PluginSettingCommon { description: string; @@ -204,12 +248,14 @@ export interface PluginSettingCommon { */ target?: "WEB" | "DESKTOP" | "BOTH"; } + interface IsDisabled<D = unknown> { /** * Checks if this setting should be disabled */ disabled?(this: D): boolean; } + interface IsValid<T, D = unknown> { /** * Prevents the user from saving settings if this is false or a string @@ -238,12 +284,18 @@ export interface PluginSettingSelectDef { type: OptionType.SELECT; options: readonly PluginSettingSelectOption[]; } + export interface PluginSettingSelectOption { label: string; value: string | number | boolean; default?: boolean; } +export interface PluginSettingCustomDef { + type: OptionType.CUSTOM; + default?: any; +} + export interface PluginSettingSliderDef { type: OptionType.SLIDER; /** @@ -292,8 +344,10 @@ type PluginSettingType<O extends PluginSettingDef> = O extends PluginSettingStri O extends PluginSettingBooleanDef ? boolean : O extends PluginSettingSelectDef ? O["options"][number]["value"] : O extends PluginSettingSliderDef ? number : - O extends PluginSettingComponentDef ? any : + O extends PluginSettingComponentDef ? O extends { default: infer Default; } ? Default : any : + O extends PluginSettingCustomDef ? O extends { default: infer Default; } ? Default : any : never; + type PluginSettingDefaultType<O extends PluginSettingDef> = O extends PluginSettingSelectDef ? ( O["options"] extends { default?: boolean; }[] ? O["options"][number]["value"] : undefined ) : O extends { default: infer T; } ? T : undefined; @@ -345,13 +399,15 @@ export type PluginOptionsItem = | PluginOptionBoolean | PluginOptionSelect | PluginOptionSlider - | PluginOptionComponent; + | PluginOptionComponent + | PluginOptionCustom; export type PluginOptionString = PluginSettingStringDef & PluginSettingCommon & IsDisabled & IsValid<string>; export type PluginOptionNumber = (PluginSettingNumberDef | PluginSettingBigIntDef) & PluginSettingCommon & IsDisabled & IsValid<number | BigInt>; export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon & IsDisabled & IsValid<boolean>; export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid<PluginSettingSelectOption>; export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid<number>; -export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon; +export type PluginOptionComponent = PluginSettingComponentDef & Omit<PluginSettingCommon, "description" | "placeholder">; +export type PluginOptionCustom = PluginSettingCustomDef & Pick<PluginSettingCommon, "onChange">; export type PluginNative<PluginExports extends Record<string, (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any>> = { [key in keyof PluginExports]: diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index 11526c0f3..2681d51d3 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -16,76 +16,87 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { filters, findByPropsLazy, waitFor } from "@webpack"; +import { LazyComponent } from "@utils/lazyReact"; +import { filters, mapMangledModuleLazy, waitFor } from "@webpack"; import { waitForComponent } from "./internal"; import * as t from "./types/components"; -export let Forms = {} as { - FormTitle: t.FormTitle, - FormSection: t.FormSection, - FormDivider: t.FormDivider, - FormText: t.FormText, + +const FormTitle = waitForComponent<t.FormTitle>("FormTitle", filters.componentByCode('["defaultMargin".concat', '="h5"')); +const FormText = waitForComponent<t.FormText>("FormText", filters.componentByCode(".SELECTABLE),", ".DISABLED:")); +const FormSection = waitForComponent<t.FormSection>("FormSection", filters.componentByCode(".titleId)&&")); +const FormDivider = waitForComponent<t.FormDivider>("FormDivider", filters.componentByCode(".divider,", ",style:", '"div"', /\.divider,\i\),style:/)); + +export const Forms = { + FormTitle, + FormText, + FormSection, + FormDivider }; -export let Icons = {} as t.Icons; +export const Card = waitForComponent<t.Card>("Card", filters.componentByCode(".editable),", ".outline:")); +export const Button = waitForComponent<t.Button>("Button", filters.componentByCode("#{intl::A11Y_LOADING_STARTED}))),!1")); +export const Switch = waitForComponent<t.Switch>("Switch", filters.componentByCode(".labelRow,ref:", ".disabledText")); + +const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", { + Tooltip: filters.componentByCode("this.renderTooltip()]"), + TooltipContainer: filters.componentByCode('="div"') +}) as { + Tooltip: t.Tooltip, + TooltipContainer: t.TooltipContainer; +}; + +export const Tooltip = LazyComponent(() => Tooltips.Tooltip); +export const TooltipContainer = LazyComponent(() => Tooltips.TooltipContainer); + +export const TextInput = waitForComponent<t.TextInput>("TextInput", filters.componentByCode(".error]:this.hasError()")); +export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.componentByCode("this.getPaddingRight()},id:")); +export const Text = waitForComponent<t.Text>("Text", filters.componentByCode('case"always-white"')); +export const Heading = waitForComponent<t.Heading>("Heading", filters.componentByCode(">6?{", "variant:")); +export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('.selectPositionTop]:"top"===', '"Escape"===')); +export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode('.selectPositionTop]:"top"===', ".multi]:")); +export const Slider = waitForComponent<t.Slider>("Slider", filters.componentByCode('"markDash".concat(')); +export const Popout = waitForComponent<t.Popout>("Popout", filters.componentByCode("ref:this.ref,", "renderPopout:this.renderPopout,")); +export const Dialog = waitForComponent<t.Dialog>("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1')); +export const TabBar = waitForComponent("TabBar", filters.componentByCode("ref:this.tabBarRef,className:")); +export const Paginator = waitForComponent<t.Paginator>("Paginator", filters.componentByCode('rel:"prev",children:')); +export const Clickable = waitForComponent<t.Clickable>("Clickable", filters.componentByCode("this.context?this.renderNonInteractive():")); +export const Avatar = waitForComponent<t.Avatar>("Avatar", filters.componentByCode(".size-1.375*")); + +export let createScroller: (scrollbarClassName: string, fadeClassName: string, customThemeClassName: string) => t.ScrollerThin; +export let scrollerClasses: Record<string, string>; +waitFor(filters.byCode('="ltr",orientation:', "customTheme:", "forwardRef"), m => createScroller = m); +waitFor(["thin", "auto", "customTheme"], m => scrollerClasses = m); + +export const ScrollerNone = LazyComponent(() => createScroller(scrollerClasses.none, scrollerClasses.fade, scrollerClasses.customTheme)); +export const ScrollerThin = LazyComponent(() => createScroller(scrollerClasses.thin, scrollerClasses.fade, scrollerClasses.customTheme)); +export const ScrollerAuto = LazyComponent(() => createScroller(scrollerClasses.auto, scrollerClasses.fade, scrollerClasses.customTheme)); + +const { FocusLock_ } = mapMangledModuleLazy("attachTo:null!==", { + FocusLock_: filters.componentByCode(".containerRef") +}) as { + FocusLock_: t.FocusLock; +}; + +export const FocusLock = LazyComponent(() => FocusLock_); -export let Card: t.Card; -export let Button: t.Button; -export let Switch: t.Switch; -export let Tooltip: t.Tooltip; -export let TooltipContainer: t.TooltipContainer; -export let TextInput: t.TextInput; -export let TextArea: t.TextArea; -export let Text: t.Text; -export let Heading: t.Heading; -export let Select: t.Select; -export let SearchableSelect: t.SearchableSelect; -export let Slider: t.Slider; -export let ButtonLooks: t.ButtonLooks; -export let Popout: t.Popout; -export let Dialog: t.Dialog; -export let TabBar: any; -export let Paginator: t.Paginator; -export let ScrollerThin: t.ScrollerThin; -export let Clickable: t.Clickable; -export let Avatar: t.Avatar; -export let FocusLock: t.FocusLock; -// token lagger real -/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */ export let useToken: t.useToken; +waitFor(m => { + if (typeof m !== "function") { + return false; + } + + const str = String(m); + return str.includes(".resolve({theme:null") && !str.includes("useMemo"); +}, m => useToken = m); export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)")); -export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}")); +export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.componentByCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}")); export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]); +export const OAuth2AuthorizeModal = waitForComponent("OAuth2AuthorizeModal", filters.componentByCode(".authorize),children:", ".contentBackground")); -export const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal"); - -waitFor(["FormItem", "Button"], m => { - ({ - useToken, - Card, - Button, - FormSwitch: Switch, - Tooltip, - TooltipContainer, - TextInput, - TextArea, - Text, - Select, - SearchableSelect, - Slider, - ButtonLooks, - TabBar, - Popout, - Dialog, - Paginator, - ScrollerThin, - Clickable, - Avatar, - FocusLock, - Heading - } = m); - Forms = m; - Icons = m; +export const Animations = mapMangledModuleLazy(".assign({colorNames:", { + Transition: filters.componentByCode('["items","children"]', ",null,"), + animated: filters.byProps("div", "text") }); diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts index d528390ef..9896b3a20 100644 --- a/src/webpack/common/menu.ts +++ b/src/webpack/common/menu.ts @@ -17,16 +17,29 @@ */ // eslint-disable-next-line path-alias/no-relative -import { filters, mapMangledModuleLazy, waitFor } from "../webpack"; +import { filters, mapMangledModuleLazy, waitFor, wreq } from "../webpack"; import type * as t from "./types/menu"; -export let Menu = {} as t.Menu; +export const Menu = {} as t.Menu; -waitFor(["MenuItem", "MenuSliderControl"], m => Menu = m); +// Relies on .name properties added by the MenuItemDemanglerAPI +waitFor(m => m.name === "MenuCheckboxItem", (_, id) => { + // we have to do this manual require by ID because m is in this case the MenuCheckBoxItem instead of the entire module + const module = wreq(id); + + for (const e of Object.values(module)) { + if (typeof e === "function" && e.name.startsWith("Menu")) { + Menu[e.name] = e; + } + } +}); + +waitFor(filters.componentByCode('path:["empty"]'), m => Menu.Menu = m); +waitFor(filters.componentByCode("sliderContainer", "slider", "handleSize:16", "=100"), m => Menu.MenuSliderControl = m); +waitFor(filters.componentByCode('role:"searchbox', "top:2", "query:"), m => Menu.MenuSearchControl = m); export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', { closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"), openContextMenu: filters.byCode("renderLazy:"), openContextMenuLazy: e => typeof e === "function" && e.toString().length < 100 }); - diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 8579f8b92..518f13e22 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -29,7 +29,7 @@ export type GenericStore = t.FluxStore & Record<string, any>; export const DraftType = findByPropsLazy("ChannelMessage", "SlashCommand"); -export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & { +export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & GenericStore & { getMessages(chanId: string): any; }; @@ -50,6 +50,7 @@ export let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore; export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & { /** Get the date (as a string) that the relationship was created */ getSince(userId: string): string; + isIgnored(userId: string): boolean; }; export let EmojiStore: t.EmojiStore; diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 260a763a7..469cd4289 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -16,16 +16,14 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; +import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; -import { IconNames } from "./iconNames"; export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>; export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`; export type Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>; -export type ButtonLooks = Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; export type TextProps = PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & { variant?: TextVariant; @@ -154,7 +152,7 @@ export type ComboboxPopout = ComponentType<PropsWithChildren<{ }>>; -export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size"> & { +export interface ButtonProps extends PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size">> { /** Button.Looks.FILLED */ look?: string; /** Button.Colors.BRAND */ @@ -174,7 +172,9 @@ export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonEl submittingStartedLabel?: string; submittingFinishedLabel?: string; -}>> & { +} + +export type Button = ComponentType<ButtonProps> & { BorderColors: Record<"BLACK" | "BRAND" | "BRAND_NEW" | "GREEN" | "LINK" | "PRIMARY" | "RED" | "TRANSPARENT" | "WHITE" | "YELLOW", string>; Colors: Record<"BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT" | "BRAND_NEW" | "CUSTOM", string>; Hovers: Record<"DEFAULT" | "BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT", string>; @@ -471,15 +471,9 @@ export type ScrollerThin = ComponentType<PropsWithChildren<{ onScroll?(): void; }>>; -export type Clickable = ComponentType<PropsWithChildren<{ - className?: string; - - href?: string; - ignoreKeyPress?: boolean; - - onClick?(): void; - onKeyPress?(): void; -}>>; +export type Clickable = <T extends "a" | "div" | "span" | "li" = "div">(props: PropsWithChildren<ComponentPropsWithRef<T>> & { + tag?: T; +}) => ReactNode; export type Avatar = ComponentType<PropsWithChildren<{ className?: string; @@ -502,7 +496,7 @@ export type Avatar = ComponentType<PropsWithChildren<{ }>>; type FocusLock = ComponentType<PropsWithChildren<{ - containerRef: RefObject<HTMLElement>; + containerRef: Ref<HTMLElement>; }>>; export type Icon = ComponentType<JSX.IntrinsicElements["svg"] & { @@ -510,4 +504,3 @@ export type Icon = ComponentType<JSX.IntrinsicElements["svg"] & { colorClass?: string; } & Record<string, any>>; -export type Icons = Record<IconNames, Icon>; diff --git a/src/webpack/common/types/iconNames.d.ts b/src/webpack/common/types/iconNames.d.ts deleted file mode 100644 index f09b666b2..000000000 --- a/src/webpack/common/types/iconNames.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { LiteralUnion } from "type-fest"; - -// copy(Object.keys(findByProps("EyeIcon")).filter(k => k.endsWith("Icon")).map(JSON.stringify).join("|")) - -export type IconNames = LiteralUnion< - "AIcon" | "AccessibilityIcon" | "AchievementsIcon" | "ActivitiesIcon" | "ActivitiesPlusIcon" | "AirplayIcon" | "AnalyticsIcon" | "AngleBracketsIcon" | "AnnouncementsChatIcon" | "AnnouncementsIcon" | "AnnouncementsLockIcon" | "AnnouncementsWarningIcon" | "AppleBrandLightIcon" | "AppleNeutralIcon" | "AppsIcon" | "ArrowAngleDownLeftIcon" | "ArrowAngleLeftDownIcon" | "ArrowAngleLeftUpIcon" | "ArrowAngleRightDownIcon" | "ArrowAngleRightUpIcon" | "ArrowAngleUpLeftIcon" | "ArrowLargeDownIcon" | "ArrowLargeLeftIcon" | "ArrowLargeRightIcon" | "ArrowLargeUpIcon" | "ArrowSmallDownIcon" | "ArrowSmallLeftIcon" | "ArrowSmallRightIcon" | "ArrowSmallUpIcon" | "ArrowsLeftRightIcon" | "ArrowsUpDownIcon" | "AsteriskIcon" | "AtIcon" | "AttachmentIcon" | "BIcon" | "BackspaceIcon" | "BadgeIcon" | "BeakerIcon" | "BellIcon" | "BellSlashIcon" | "BellZIcon" | "BicycleIcon" | "BillIcon" | "BluetoothIcon" | "BlurBackgroundIcon" | "BoldIcon" | "BookCheckIcon" | "BookmarkIcon" | "BookmarkOutlineIcon" | "BoostTier1Icon" | "BoostTier1SimpleIcon" | "BoostTier2Icon" | "BoostTier2SimpleIcon" | "BoostTier3Icon" | "BoostTier3SimpleIcon" | "BrowserCheckeredIcon" | "BrowserIcon" | "BrowserLinkIcon" | "BrowserPlusIcon" | "BrowserQuestionMarkIcon" | "BugIcon" | "CalendarIcon" | "CalendarMinusIcon" | "CalendarPlusIcon" | "CalendarRetryIcon" | "CalendarXIcon" | "CameraIcon" | "CameraSwapIcon" | "CarIcon" | "ChannelListIcon" | "ChannelListMagnifyingGlassIcon" | "ChannelListMinusIcon" | "ChannelListPlusIcon" | "ChannelListRetryIcon" | "ChannelNotificationIcon" | "ChannelsFollowedIcon" | "ChatArrowRightIcon" | "ChatCheckIcon" | "ChatDotsIcon" | "ChatEyeIcon" | "ChatIcon" | "ChatMarkUnreadIcon" | "ChatMinusIcon" | "ChatPlusIcon" | "ChatRetryIcon" | "ChatSlowModeIcon" | "ChatSmileIcon" | "ChatSpeakIcon" | "ChatWarningIcon" | "ChatXIcon" | "CheckmarkLargeBoldIcon" | "CheckmarkLargeIcon" | "CheckmarkSmallBoldIcon" | "CheckmarkSmallIcon" | "ChevronLargeDownIcon" | "ChevronLargeLeftIcon" | "ChevronLargeRightIcon" | "ChevronLargeUpIcon" | "ChevronSmallDownIcon" | "ChevronSmallLeftIcon" | "ChevronSmallRightIcon" | "ChevronSmallUpIcon" | "CircleCheckIcon" | "CircleInformationIcon" | "CircleMinusIcon" | "CirclePlayIcon" | "CirclePlusIcon" | "CircleQuestionIcon" | "CircleWarningIcon" | "CircleXIcon" | "ClipboardCheckIcon" | "ClipboardListIcon" | "ClipsGalleryIcon" | "ClipsIcon" | "ClockIcon" | "ClockWarningIcon" | "ClockXIcon" | "CloudDownloadIcon" | "ClydeIcon" | "CollapseListIcon" | "CompassIcon" | "ConnectionAverageIcon" | "ConnectionBadIcon" | "ConnectionFineIcon" | "ConnectionUnknownIcon" | "ContactsIcon" | "CopyIcon" | "CreditCardIcon" | "CropIcon" | "CrownIcon" | "CrunchyrollBrandLightIcon" | "CrunchyrollNeutralIcon" | "DenyIcon" | "DoorEnterIcon" | "DoorExitIcon" | "DoubleCheckmarkIcon" | "DownloadIcon" | "DpadIcon" | "DragIcon" | "EducationIcon" | "EmbedIcon" | "EnvelopeIcon" | "ExpandGifIcon" | "ExperimentalLootboxIcon" | "ExperimentalPineappleSpongebobIcon" | "EyeDropperIcon" | "EyeIcon" | "EyePlusIcon" | "EyeSlashIcon" | "FacebookNeutralIcon" | "FileDenyIcon" | "FileIcon" | "FileUpIcon" | "FileWarningIcon" | "FiltersHorizontalIcon" | "FireIcon" | "FlagIcon" | "FlagMinusIcon" | "FlagPlusIcon" | "FlagRetryIcon" | "FlashIcon" | "FlipHorizontalIcon" | "FlipVerticalIcon" | "FolderIcon" | "FolderPlusIcon" | "FoodIcon" | "ForumIcon" | "ForumLockIcon" | "ForumWarningIcon" | "FriendsIcon" | "FullscreenEnterIcon" | "FullscreenExitIcon" | "GameControllerIcon" | "GifIcon" | "GiftIcon" | "GlobeEarthIcon" | "GridHorizontalIcon" | "GridSquareIcon" | "GridVerticalIcon" | "GroupArrowDownIcon" | "GroupArrowRightIcon" | "GroupIcon" | "GroupMinusIcon" | "GroupPlusIcon" | "GroupRetryIcon" | "HammerIcon" | "HammerMinusIcon" | "HammerPlusIcon" | "HammerRetryIcon" | "HammerXIcon" | "HandRequestSpeakIcon" | "HandRequestSpeakListIcon" | "HashmarkIcon" | "HdIcon" | "HeadphonesDenyIcon" | "HeadphonesIcon" | "HeadphonesSlashIcon" | "HeartIcon" | "HeartOutlineIcon" | "HomeIcon" | "HomeSlashIcon" | "HourglassIcon" | "HubIcon" | "IdIcon" | "ImageFileIcon" | "ImageFileUpIcon" | "ImageIcon" | "ImageLockIcon" | "ImagePlusIcon" | "ImageSparkleIcon" | "ImageTextIcon" | "ImageWarningIcon" | "ImagesIcon" | "InboxIcon" | "InstagramNeutralIcon" | "InventoryIcon" | "ItalicIcon" | "KeyIcon" | "KeyboardIcon" | "LanguageIcon" | "LaptopPhoneIcon" | "LettersIcon" | "LightbulbIcon" | "LinkExternalMediumIcon" | "LinkExternalSmallIcon" | "LinkIcon" | "LinkPlusIcon" | "ListBulletsIcon" | "ListNumberedIcon" | "LocationIcon" | "LockIcon" | "LockUnlockedIcon" | "MagicWandIcon" | "MagnifyingGlassIcon" | "ManaIcon" | "MaximizeIcon" | "MedalIcon" | "MenuIcon" | "MicrophoneArrowRightIcon" | "MicrophoneDenyIcon" | "MicrophoneIcon" | "MicrophoneSlashIcon" | "MinimizeIcon" | "MinusIcon" | "MobilePhoneControllerIcon" | "MobilePhoneIcon" | "MobilePhonePlusIcon" | "MobilePhoneSettingsIcon" | "MobilePhoneShareIcon" | "MobilePhoneSpeakerIcon" | "MobilePhoneVideoIcon" | "MobilePhoneXIcon" | "ModerationIcon" | "MoreHorizontalIcon" | "MoreVerticalIcon" | "MusicIcon" | "MusicSlashIcon" | "NatureIcon" | "NearbyScanIcon" | "NewUserIcon" | "NewUserSimpleIcon" | "NintendoSwitchNeutralIcon" | "NitroWheelIcon" | "ObjectIcon" | "PaintPaletteIcon" | "PaintbrushThickIcon" | "PaintbrushThickMinusIcon" | "PaintbrushThickPlusIcon" | "PaintbrushThickRetryIcon" | "PaintbrushThinIcon" | "PaintbrushThinMinusIcon" | "PaintbrushThinPlusIcon" | "PaintbrushThinRetryIcon" | "PaperIcon" | "PaperPlusIcon" | "PauseIcon" | "PencilIcon" | "PencilSparkleIcon" | "PhoneCallIcon" | "PhoneHangUpIcon" | "PhoneIcon" | "PiggyBankIcon" | "PinIcon" | "PinUprightIcon" | "PinUprightSlashIcon" | "PlayIcon" | "PlaystationNeutralIcon" | "PlusLargeIcon" | "PlusMediumIcon" | "PlusSmallIcon" | "PollsIcon" | "PrivacyAndSafetyIcon" | "PuzzlePieceIcon" | "PuzzlePieceMinusIcon" | "PuzzlePiecePlusIcon" | "PuzzlePieceRetryIcon" | "QrCodeIcon" | "QuestsIcon" | "QuoteIcon" | "ReactionIcon" | "ReceiptIcon" | "RecordPlayerIcon" | "RedoIcon" | "RefreshIcon" | "RemixIcon" | "RetryIcon" | "RibbonIcon" | "RobotIcon" | "RotateIcon" | "ScienceIcon" | "ScreenArrowIcon" | "ScreenIcon" | "ScreenSlashIcon" | "ScreenStreamIcon" | "ScreenSystemRequirementsIcon" | "ScreenXIcon" | "SendMessageIcon" | "ServerGridIcon" | "ServerIcon" | "SettingsArrowUpIcon" | "SettingsCircleIcon" | "SettingsIcon" | "SettingsInfoIcon" | "SettingsPlusIcon" | "ShareIcon" | "ShieldAtIcon" | "ShieldIcon" | "ShieldLockIcon" | "ShieldUserIcon" | "ShopIcon" | "ShopMinusIcon" | "ShopPlusIcon" | "ShopSparkleIcon" | "SignPostIcon" | "SlashBoxIcon" | "SlashIcon" | "SlashMinusIcon" | "SlashPlusIcon" | "SlashRetryIcon" | "SoundboardIcon" | "SoundboardSlashIcon" | "SparklesIcon" | "SpeedometerIcon" | "SpoilerIcon" | "StaffBadgeIcon" | "StageIcon" | "StageListIcon" | "StageLockIcon" | "StageMinusIcon" | "StageModeratorIcon" | "StagePlusIcon" | "StageRetryIcon" | "StageXIcon" | "StampIcon" | "StarIcon" | "StarOutlineIcon" | "StarShootingIcon" | "StickerDeadIcon" | "StickerIcon" | "StickerMinusIcon" | "StickerPlusIcon" | "StickerRetryIcon" | "StickerSadIcon" | "StickerSmallIcon" | "StickerWink1Icon" | "StickerWink2Icon" | "StopIcon" | "StrikethroughIcon" | "SuperReactionIcon" | "TagIcon" | "TagsIcon" | "TextIcon" | "TextLockIcon" | "TextWarningIcon" | "ThemeDarkIcon" | "ThemeLightIcon" | "ThemeMidnightIcon" | "ThreadIcon" | "ThreadLockIcon" | "ThreadMinusIcon" | "ThreadPlusIcon" | "ThreadRetryIcon" | "ThreadWarningIcon" | "ThumbsDownIcon" | "ThumbsUpIcon" | "TicketIcon" | "TiktokNeutralIcon" | "TimerIcon" | "TopicsIcon" | "TrainIcon" | "TrashIcon" | "TreehouseIcon" | "TrophyIcon" | "TvIcon" | "TwitterNeutralIcon" | "UnderlineIcon" | "UndoIcon" | "UnknownGameIcon" | "UnsendIcon" | "UploadIcon" | "UserArrowDiagonalBottomRightIcon" | "UserCheckIcon" | "UserCircleIcon" | "UserCircleStatusIcon" | "UserClockIcon" | "UserIcon" | "UserMinusIcon" | "UserPlayIcon" | "UserPlusIcon" | "UserRetryIcon" | "UserSquareIcon" | "UserStatusIcon" | "VideoIcon" | "VideoLockIcon" | "VideoSlashIcon" | "VoiceBluetoothIcon" | "VoiceLockIcon" | "VoiceLowIcon" | "VoiceNormalIcon" | "VoiceWarningIcon" | "VoiceXIcon" | "WalletIcon" | "WarningIcon" | "WaveformIcon" | "WaveformSlashIcon" | "WebhookIcon" | "WebhookPlusIcon" | "WidgetsIcon" | "WidgetsMinusIcon" | "WidgetsPlusIcon" | "WidgetsRetryIcon" | "WindowLaunchIcon" | "WindowReturnIcon" | "WindowTopIcon" | "WindowTopOutlineIcon" | "WrenchIcon" | "XLargeBoldIcon" | "XLargeIcon" | "XNeutralIcon" | "XSmallBoldIcon" | "XSmallIcon" | "XboxNeutralIcon" | "YoutubeNeutralIcon", - string ->; diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index 9ca7dfc94..67148303b 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -16,7 +16,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { DraftType } from "@webpack/common"; import { Channel, Guild, Role } from "discord-types/general"; import { FluxDispatcher, FluxEvents } from "./utils"; @@ -229,7 +228,7 @@ export class ThemeStore extends FluxStore { } export type useStateFromStores = <T>( - stores: t.FluxStore[], + stores: any[], mapper: () => T, dependencies?: any, isEqual?: (old: T, newer: T) => boolean diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index de1ce1829..cfea5d760 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Channel, Guild, GuildMember, User } from "discord-types/general"; +import { Channel, Guild, GuildMember, Message, User } from "discord-types/general"; import type { ReactNode } from "react"; import { LiteralUnion } from "type-fest"; @@ -133,6 +133,10 @@ export type Permissions = "CREATE_INSTANT_INVITE" export type PermissionsBits = Record<Permissions, bigint>; +export interface MessageSnapshot { + message: Message; +} + export interface Locale { name: string; value: string; diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 1bdf236ac..fd555c217 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -16,7 +16,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { runtimeHashMessageKey } from "@utils/intlHash"; import type { Channel } from "discord-types/general"; // eslint-disable-next-line path-alias/no-relative @@ -58,9 +57,9 @@ export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = ma export const lodash: typeof import("lodash") = findByPropsLazy("debounce", "cloneDeep"); export const i18n = mapMangledModuleLazy('defaultLocale:"en-US"', { - intl: filters.byProps("string", "format"), - t: filters.byProps(runtimeHashMessageKey("DISCORD")) -}); + t: m => m?.[Symbol.toStringTag] === "IntlMessagesProxy", + intl: m => m != null && Object.getPrototypeOf(m)?.withFormatters != null +}, true); export let SnowflakeUtils: t.SnowflakeUtils; waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m); @@ -71,10 +70,15 @@ export let Alerts: t.Alerts; waitFor(["show", "close"], m => Alerts = m); const ToastType = { - MESSAGE: 0, - SUCCESS: 1, - FAILURE: 2, - CUSTOM: 3 + MESSAGE: "message", + SUCCESS: "success", + FAILURE: "failure", + CUSTOM: "custom", + CLIP: "clip", + LINK: "link", + FORWARD: "forward", + BOOKMARK: "bookmark", + CLOCK: "clock" }; const ToastPosition = { TOP: 0, @@ -87,7 +91,7 @@ export interface ToastData { /** * Toasts.Type */ - type: number, + type: string, options?: ToastOptions; } @@ -110,7 +114,7 @@ export const Toasts = { ...{} as { show(data: ToastData): void; pop(): void; - create(message: string, type: number, options?: ToastOptions): ToastData; + create(message: string, type: string, options?: ToastOptions): ToastData; } }; diff --git a/src/webpack/index.ts b/src/webpack/index.ts index 036c2a3fc..6f1fd25b8 100644 --- a/src/webpack/index.ts +++ b/src/webpack/index.ts @@ -18,3 +18,4 @@ export * as Common from "./common"; export * from "./webpack"; +export * from "./wreq.d"; diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index fb640cea8..9b66a5b4e 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -1,370 +1,640 @@ /* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * 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 <https://www.gnu.org/licenses/>. -*/ + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated, Nuckyz, and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ -import { WEBPACK_CHUNK } from "@utils/constants"; +import { Settings } from "@api/Settings"; +import { makeLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; +import { interpolateIfDefined } from "@utils/misc"; import { canonicalizeReplacement } from "@utils/patches"; -import { PatchReplacement } from "@utils/types"; -import { WebpackInstance } from "discord-types/other"; +import { Patch, PatchReplacement } from "@utils/types"; -import { traceFunction } from "../debug/Tracer"; -import { patches } from "../plugins"; -import { _initWebpack, beforeInitListeners, factoryListeners, moduleListeners, subscriptions, wreq } from "."; +import { traceFunctionWithResults } from "../debug/Tracer"; +import { _blacklistBadModules, _initWebpack, factoryListeners, findModuleFactory, moduleListeners, waitForSubscriptions, wreq } from "./webpack"; +import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, PatchedModuleFactory, WebpackRequire } from "./wreq.d"; + +export const patches = [] as Patch[]; + +export const SYM_IS_PROXIED_FACTORY = Symbol("WebpackPatcher.isProxiedFactory"); +export const SYM_ORIGINAL_FACTORY = Symbol("WebpackPatcher.originalFactory"); +export const SYM_PATCHED_SOURCE = Symbol("WebpackPatcher.patchedSource"); +export const SYM_PATCHED_BY = Symbol("WebpackPatcher.patchedBy"); +export const allWebpackInstances = new Set<AnyWebpackRequire>(); + +export const patchTimings = [] as Array<[plugin: string, moduleId: PropertyKey, match: PatchReplacement["match"], totalTime: number]>; + +export const getBuildNumber = makeLazy(() => { + try { + function matchBuildNumber(factoryStr: string) { + const buildNumberMatch = factoryStr.match(/.concat\("(\d+?)"\)/); + if (buildNumberMatch == null) { + return -1; + } + + return Number(buildNumberMatch[1]); + } + + const hardcodedFactoryStr = String(wreq.m[128014]); + if (hardcodedFactoryStr.includes("Trying to open a changelog for an invalid build number")) { + const hardcodedBuildNumber = matchBuildNumber(hardcodedFactoryStr); + + if (hardcodedBuildNumber !== -1) { + return hardcodedBuildNumber; + } + } + + const moduleFactory = findModuleFactory("Trying to open a changelog for an invalid build number"); + return matchBuildNumber(String(moduleFactory)); + } catch { + return -1; + } +}); + +export function getFactoryPatchedSource(moduleId: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) { + return webpackRequire.m[moduleId]?.[SYM_PATCHED_SOURCE]; +} + +export function getFactoryPatchedBy(moduleId: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) { + return webpackRequire.m[moduleId]?.[SYM_PATCHED_BY]; +} const logger = new Logger("WebpackInterceptor", "#8caaee"); -let webpackChunk: any[]; +/** Whether we tried to fallback to the WebpackRequire of the factory, or disabled patches */ +let wreqFallbackApplied = false; -// Patch the window webpack chunk setter to monkey patch the push method before any chunks are pushed -// This way we can patch the factory of everything being pushed to the modules array -Object.defineProperty(window, WEBPACK_CHUNK, { - configurable: true, - - get: () => webpackChunk, - set: v => { - if (v?.push) { - if (!v.push.$$vencordOriginal) { - logger.info(`Patching ${WEBPACK_CHUNK}.push`); - patchPush(v); - - // @ts-ignore - delete window[WEBPACK_CHUNK]; - window[WEBPACK_CHUNK] = v; - } - } - - webpackChunk = v; +const define: typeof Reflect.defineProperty = (target, p, attributes) => { + if (Object.hasOwn(attributes, "value")) { + attributes.writable = true; } -}); -// wreq.m is the webpack module factory. -// normally, this is populated via webpackGlobal.push, which we patch below. -// However, Discord has their .m prepopulated. -// Thus, we use this hack to immediately access their wreq.m and patch all already existing factories -Object.defineProperty(Function.prototype, "m", { - configurable: true, + return Reflect.defineProperty(target, p, { + configurable: true, + enumerable: true, + ...attributes + }); +}; - set(v: any) { - Object.defineProperty(this, "m", { - value: v, - configurable: true, - enumerable: true, - writable: true - }); +// wreq.m is the Webpack object containing module factories. It is pre-populated with factories, and is also populated via webpackGlobal.push +// We use this setter to intercept when wreq.m is defined and setup our setters which decide whether we should patch these module factories +// and the Webpack instance where they are being defined. - // When using react devtools or other extensions, we may also catch their webpack here. - // This ensures we actually got the right one +// Factories can be patched in two ways. Eagerly or lazily. +// If we are patching eagerly, pre-populated factories are patched immediately and new factories are patched when set. +// Else, we only patch them when called. + +// Factories are always wrapped in a proxy, which allows us to intercept the call to them, patch if they werent eagerly patched, +// and call them with our wrapper which notifies our listeners. + +// wreq.m is also wrapped in a proxy to intercept when new factories are set, patch them eargely, if enabled, and wrap them in the factory proxy. + +// If this is the main Webpack, we also set up the internal references to WebpackRequire. +define(Function.prototype, "m", { + enumerable: false, + + set(this: AnyWebpackRequire, originalModules: AnyWebpackRequire["m"]) { + define(this, "m", { value: originalModules }); + + // Ensure this is likely one of Discord main Webpack instances. + // We may catch Discord bundled libs, React Devtools or other extensions Webpack instances here. const { stack } = new Error(); - if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || Array.isArray(v)) { + if (!stack?.includes("http") || stack.match(/at \d+? \(/) || !String(this).includes("exports:{}")) { return; } - const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? ""; - logger.info("Found Webpack module factory", fileName); + const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1]; - patchFactories(v); + // Define a setter for the bundlePath property of WebpackRequire. Only Webpack instances which include chunk loading functionality, + // like the main Discord Webpack, have this property. + // So if the setter is called with the Discord bundlePath, this means we should patch this instance and initialize the internal references to WebpackRequire. + define(this, "p", { + enumerable: false, - // Define a setter for the bundlePath property of WebpackRequire. Only the main Webpack has this property. - // So if the setter is called, this means we can initialize the internal references to WebpackRequire. - Object.defineProperty(this, "p", { - configurable: true, + set(this: AnyWebpackRequire, bundlePath: NonNullable<AnyWebpackRequire["p"]>) { + define(this, "p", { value: bundlePath }); + clearTimeout(bundlePathTimeout); - set(this: WebpackInstance, bundlePath: string) { - Object.defineProperty(this, "p", { - value: bundlePath, - configurable: true, - enumerable: true, - writable: true - }); - - clearTimeout(setterTimeout); - if (bundlePath !== "/assets/") return; - - logger.info(`Main Webpack found in ${fileName}, initializing internal references to WebpackRequire`); - _initWebpack(this); - - for (const beforeInitListener of beforeInitListeners) { - beforeInitListener(this); + if (bundlePath !== "/assets/") { + return; } + + if (wreq == null && this.c != null) { + logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire"); + _initWebpack(this as WebpackRequire); + } + + patchThisInstance(); } }); - // setImmediate to clear this property setter if this is not the main Webpack. - // If this is the main Webpack, wreq.p will always be set before the timeout runs. - const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0); + + // In the past, the sentry Webpack instance which we also wanted to patch used to rely on chunks being loaded before initting sentry. + // This Webpack instance did not include actual chunk loading, and only awaited for them to be loaded, which means it did not include the bundlePath property. + // To keep backwards compability, in case this is ever the case again, and keep patching this type of instance, we explicity patch instances which include wreq.O and not wreq.p. + // Since we cannot check what is the bundlePath of the instance to filter for the Discord bundlePath, we only patch it if wreq.p is not included, + // which means the instance relies on another instance which does chunk loading, and that makes it very likely to only target Discord Webpack instances like the old sentry. + + // Instead of patching when wreq.O is defined, wait for when wreq.O.j is defined, since that will be one of the last things to happen, + // which can assure wreq.p could have already been defined before. + define(this, "O", { + enumerable: false, + + set(this: AnyWebpackRequire, onChunksLoaded: NonNullable<AnyWebpackRequire["O"]>) { + define(this, "O", { value: onChunksLoaded }); + clearTimeout(onChunksLoadedTimeout); + + const wreq = this; + define(onChunksLoaded, "j", { + enumerable: false, + + set(this: NonNullable<AnyWebpackRequire["O"]>, j: NonNullable<AnyWebpackRequire["O"]>["j"]) { + define(this, "j", { value: j }); + + if (wreq.p == null) { + patchThisInstance(); + } + } + }); + } + }); + + // If neither of these properties setters were triggered, delete them as they are not needed anymore. + const bundlePathTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0); + const onChunksLoadedTimeout = setTimeout(() => Reflect.deleteProperty(this, "O"), 0); + + /** + * Patch the current Webpack instance assigned to `this` context. + * This should only be called if this instance was later found to be one we need to patch. + */ + const patchThisInstance = () => { + logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`); + allWebpackInstances.add(this); + + // Proxy (and maybe patch) pre-populated factories + for (const moduleId in originalModules) { + updateExistingOrProxyFactory(originalModules, moduleId, originalModules[moduleId], originalModules, true); + } + + define(originalModules, Symbol.toStringTag, { + value: "ModuleFactories", + enumerable: false + }); + + const proxiedModuleFactories = new Proxy(originalModules, moduleFactoriesHandler); + /* + If Webpack ever decides to set module factories using the variable of the modules object directly, instead of wreq.m, switch the proxy to the prototype + Reflect.setPrototypeOf(originalModules, new Proxy(originalModules, moduleFactoriesHandler)); + */ + + define(this, "m", { value: proxiedModuleFactories }); + + // Overwrite Webpack's defineExports function to define the export descriptors configurable. + // This is needed so we can later blacklist specific exports from Webpack search by making them non-enumerable + this.d = function (exports, definition) { + for (const key in definition) { + if (Object.hasOwn(definition, key) && !Object.hasOwn(exports, key)) { + Object.defineProperty(exports, key, { + enumerable: true, + configurable: true, + get: definition[key], + }); + } + } + }; + }; } }); -function patchPush(webpackGlobal: any) { - function handlePush(chunk: any) { - try { - patchFactories(chunk[1]); - } catch (err) { - logger.error("Error in handlePush", err); +// The proxy for patching eagerly and/or wrapping factories in their proxy. +const moduleFactoriesHandler: ProxyHandler<AnyWebpackRequire["m"]> = { + /* + If Webpack ever decides to set module factories using the variable of the modules object directly instead of wreq.m, we need to switch the proxy to the prototype + and that requires defining additional traps for keeping the object working + + // Proxies on the prototype don't intercept "get" when the property is in the object itself. But in case it isn't we need to return undefined, + // to avoid Reflect.get having no effect and causing a stack overflow + get(target, p, receiver) { + return undefined; + }, + // Same thing as get + has(target, p) { + return false; + }, + */ + + set: updateExistingOrProxyFactory +}; + +// The proxy for patching lazily and/or running factories with our wrapper. +const moduleFactoryHandler: ProxyHandler<MaybePatchedModuleFactory> = { + apply(target, thisArg: unknown, argArray: Parameters<AnyModuleFactory>) { + // SYM_ORIGINAL_FACTORY means the factory has already been patched + if (target[SYM_ORIGINAL_FACTORY] != null) { + return runFactoryWithWrap(target as PatchedModuleFactory, thisArg, argArray); } - return handlePush.$$vencordOriginal.call(webpackGlobal, chunk); + // SAFETY: Factories have `name` as their key in the module factories object, and that is always their module id + const moduleId: string = target.name; + + const patchedFactory = patchFactory(moduleId, target); + return runFactoryWithWrap(patchedFactory, thisArg, argArray); + }, + + get(target, p, receiver) { + if (p === SYM_IS_PROXIED_FACTORY) { + return true; + } + + const originalFactory: AnyModuleFactory = target[SYM_ORIGINAL_FACTORY] ?? target; + + // Redirect these properties to the original factory, including making `toString` return the original factory `toString` + if (p === "toString" || p === SYM_PATCHED_SOURCE || p === SYM_PATCHED_BY) { + const v = Reflect.get(originalFactory, p, originalFactory); + return p === "toString" ? v.bind(originalFactory) : v; + } + + return Reflect.get(target, p, receiver); + } +}; + +function updateExistingOrProxyFactory(moduleFactories: AnyWebpackRequire["m"], moduleId: PropertyKey, newFactory: AnyModuleFactory, receiver: any, ignoreExistingInTarget = false) { + if (updateExistingFactory(moduleFactories, moduleId, newFactory, receiver, ignoreExistingInTarget)) { + return true; } - handlePush.$$vencordOriginal = webpackGlobal.push; - handlePush.toString = handlePush.$$vencordOriginal.toString.bind(handlePush.$$vencordOriginal); - // Webpack overwrites .push with its own push like so: `d.push = n.bind(null, d.push.bind(d));` - // it wraps the old push (`d.push.bind(d)`). this old push is in this case our handlePush. - // If we then repatched the new push, we would end up with recursive patching, which leads to our patches - // being applied multiple times. - // Thus, override bind to use the original push - handlePush.bind = (...args: unknown[]) => handlePush.$$vencordOriginal.bind(...args); + notifyFactoryListeners(moduleId, newFactory); - Object.defineProperty(webpackGlobal, "push", { - configurable: true, - - get: () => handlePush, - set(v) { - handlePush.$$vencordOriginal = v; - } - }); + const proxiedFactory = new Proxy(Settings.eagerPatches ? patchFactory(moduleId, newFactory) : newFactory, moduleFactoryHandler); + return Reflect.set(moduleFactories, moduleId, proxiedFactory, receiver); } -let webpackNotInitializedLogged = false; - -function patchFactories(factories: Record<string, (module: any, exports: any, require: WebpackInstance) => void>) { - for (const id in factories) { - let mod = factories[id]; - - const originalMod = mod; - const patchedBy = new Set(); - - const factory = factories[id] = function (module: any, exports: any, require: WebpackInstance) { - if (wreq == null && IS_DEV) { - if (!webpackNotInitializedLogged) { - webpackNotInitializedLogged = true; - logger.error("WebpackRequire was not initialized, running modules without patches instead."); - } - - return void originalMod(module, exports, require); - } - - try { - mod(module, exports, require); - } catch (err) { - // Just rethrow discord errors - if (mod === originalMod) throw err; - - logger.error("Error in patched module", err); - return void originalMod(module, exports, require); - } - - exports = module.exports; - - if (!exports) return; - - // There are (at the time of writing) 11 modules exporting the window - // Make these non enumerable to improve webpack search performance - if (require.c) { - let foundWindow = false; - - if (exports === window) { - foundWindow = true; - } else if (typeof exports === "object") { - if (exports?.default === window) { - foundWindow = true; - } else { - for (const nested in exports) if (nested.length <= 3) { - if (exports[nested] === window) { - foundWindow = true; - } - } - } - } - - if (foundWindow) { - Object.defineProperty(require.c, id, { - value: require.c[id], - enumerable: false, - configurable: true, - writable: true - }); - - return; - } - } - - for (const callback of moduleListeners) { - try { - callback(exports, id); - } catch (err) { - logger.error("Error in Webpack module listener:\n", err, callback); - } - } - - for (const [filter, callback] of subscriptions) { - try { - if (exports && filter(exports)) { - subscriptions.delete(filter); - callback(exports, id); - } else if (typeof exports === "object") { - if (exports.default && filter(exports.default)) { - subscriptions.delete(filter); - callback(exports.default, id); - } else { - for (const nested in exports) if (nested.length <= 3) { - if (exports[nested] && filter(exports[nested])) { - subscriptions.delete(filter); - callback(exports[nested], id); - } - } - } - } - } catch (err) { - logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback); - } - } - } as any as { toString: () => string, original: any, (...args: any[]): void; $$vencordPatchedSource?: string; }; - - factory.toString = originalMod.toString.bind(originalMod); - factory.original = originalMod; - - for (const factoryListener of factoryListeners) { - try { - factoryListener(originalMod); - } catch (err) { - logger.error("Error in Webpack factory listener:\n", err, factoryListener); - } +/** + * Update a duplicated factory that exists in any of the Webpack instances we track with a new original factory. + * + * @param moduleFactories The module factories where this new original factory is being set + * @param moduleId The id of the module + * @param newFactory The new original factory + * @param receiver The receiver of the factory + * @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactories where it is being set + * @returns Whether the original factory was updated, or false if it doesn't exist in any of the tracked Webpack instances + */ +function updateExistingFactory(moduleFactories: AnyWebpackRequire["m"], moduleId: PropertyKey, newFactory: AnyModuleFactory, receiver: any, ignoreExistingInTarget) { + let existingFactory: AnyModuleFactory | undefined; + let moduleFactoriesWithFactory: AnyWebpackRequire["m"] | undefined; + for (const wreq of allWebpackInstances) { + if (ignoreExistingInTarget && wreq.m === moduleFactories) { + continue; } - // Discords Webpack chunks for some ungodly reason contain random - // newlines. Cyn recommended this workaround and it seems to work fine, - // however this could potentially break code, so if anything goes weird, - // this is probably why. - // Additionally, `[actual newline]` is one less char than "\n", so if Discord - // ever targets newer browsers, the minifier could potentially use this trick and - // cause issues. - // - // 0, prefix is to turn it into an expression: 0,function(){} would be invalid syntax without the 0, - let code: string = "0," + mod.toString().replaceAll("\n", ""); + if (Object.hasOwn(wreq.m, moduleId)) { + existingFactory = wreq.m[moduleId]; + moduleFactoriesWithFactory = wreq.m; + break; + } + } - for (let i = 0; i < patches.length; i++) { - const patch = patches[i]; + if (existingFactory != null) { + // If existingFactory exists in any of the Webpack instances we track, it's either wrapped in our proxy, or it has already been required. + // In the case it is wrapped in our proxy, and the instance we are setting does not already have it, we need to make sure the instance contains our proxy too. + if (moduleFactoriesWithFactory !== moduleFactories && existingFactory[SYM_IS_PROXIED_FACTORY]) { + Reflect.set(moduleFactories, moduleId, existingFactory, receiver); + } + // Else, if it is not wrapped in our proxy, set this new original factory in all the instances + else { + defineInWebpackInstances(moduleId, newFactory); + } - const moduleMatches = typeof patch.find === "string" - ? code.includes(patch.find) - : patch.find.test(code); + // Update existingFactory with the new original, if it does have a current original factory + if (existingFactory[SYM_ORIGINAL_FACTORY] != null) { + existingFactory[SYM_ORIGINAL_FACTORY] = newFactory; + } - if (!moduleMatches) continue; + // Persist patched source and patched by in the new original factory + if (IS_DEV) { + newFactory[SYM_PATCHED_SOURCE] = existingFactory[SYM_PATCHED_SOURCE]; + newFactory[SYM_PATCHED_BY] = existingFactory[SYM_PATCHED_BY]; + } - patchedBy.add(patch.plugin); + return true; + } - const executePatch = traceFunction(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => code.replace(match, replace)); - const previousMod = mod; - const previousCode = code; + return false; +} - // We change all patch.replacement to array in plugins/index - for (const replacement of patch.replacement as PatchReplacement[]) { - const lastMod = mod; - const lastCode = code; +/** + * Define a module factory in all the Webpack instances we track. + * + * @param moduleId The id of the module + * @param factory The factory + */ +function defineInWebpackInstances(moduleId: PropertyKey, factory: AnyModuleFactory) { + for (const wreq of allWebpackInstances) { + define(wreq.m, moduleId, { value: factory }); + } +} - canonicalizeReplacement(replacement, patch.plugin); +/** + * Notify all factory listeners. + * + * @param moduleId The id of the module + * @param factory The original factory to notify for + */ +function notifyFactoryListeners(moduleId: PropertyKey, factory: AnyModuleFactory) { + for (const factoryListener of factoryListeners) { + try { + factoryListener(factory, moduleId); + } catch (err) { + logger.error("Error in Webpack factory listener:\n", err, factoryListener); + } + } +} - try { - const newCode = executePatch(replacement.match, replacement.replace as string); - if (newCode === code) { - if (!patch.noWarn) { - logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); - if (IS_DEV) { - logger.debug("Function Source:\n", code); - } +/** + * Run a (possibly) patched module factory with a wrapper which notifies our listeners. + * + * @param patchedFactory The (possibly) patched module factory + * @param thisArg The `value` of the call to the factory + * @param argArray The arguments of the call to the factory + */ +function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unknown, argArray: Parameters<MaybePatchedModuleFactory>) { + const originalFactory = patchedFactory[SYM_ORIGINAL_FACTORY]; + + if (patchedFactory === originalFactory) { + // @ts-expect-error Clear up ORIGINAL_FACTORY if the factory did not have any patch applied + delete patchedFactory[SYM_ORIGINAL_FACTORY]; + } + + let [module, exports, require] = argArray; + + // Restore the original factory in all the module factories objects, discarding our proxy and allowing it to be garbage collected + defineInWebpackInstances(module.id, originalFactory); + + if (wreq == null) { + if (!wreqFallbackApplied) { + wreqFallbackApplied = true; + + // Make sure the require argument is actually the WebpackRequire function + if (typeof require === "function" && require.m != null && require.c != null) { + const { stack } = new Error(); + const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1]; + + logger.warn( + "WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called wrapped module factory (" + + `id: ${String(module.id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` + + ")" + ); + + // Could technically be wrong, but it's better than nothing + _initWebpack(require as WebpackRequire); + } else if (IS_DEV) { + logger.error("WebpackRequire was not initialized, running modules without patches instead."); + return originalFactory.apply(thisArg, argArray); + } + } else if (IS_DEV) { + return originalFactory.apply(thisArg, argArray); + } + } + + let factoryReturn: unknown; + try { + factoryReturn = patchedFactory.apply(thisArg, argArray); + } catch (err) { + // Just re-throw Discord errors + if (patchedFactory === originalFactory) { + throw err; + } + + logger.error("Error in patched module factory:\n", err); + return originalFactory.apply(thisArg, argArray); + } + + exports = module.exports; + + if (typeof require === "function" && require.c) { + if (_blacklistBadModules(require.c, exports, module.id)) { + return factoryReturn; + } + } + + if (exports == null) { + return factoryReturn; + } + + for (const callback of moduleListeners) { + try { + callback(exports, module.id); + } catch (err) { + logger.error("Error in Webpack module listener:\n", err, callback); + } + } + + for (const [filter, callback] of waitForSubscriptions) { + try { + if (filter(exports)) { + waitForSubscriptions.delete(filter); + callback(exports, module.id); + continue; + } + + if (typeof exports !== "object") { + continue; + } + + for (const exportKey in exports) { + const exportValue = exports[exportKey]; + + if (exportValue != null && filter(exportValue)) { + waitForSubscriptions.delete(filter); + callback(exportValue, module.id); + break; + } + } + } catch (err) { + logger.error("Error while firing callback for Webpack waitFor subscription:\n", err, filter, callback); + } + } + + return factoryReturn; +} + +/** + * Patches a module factory. + * + * @param moduleId The id of the module + * @param originalFactory The original module factory + * @returns The patched module factory + */ +function patchFactory(moduleId: PropertyKey, originalFactory: AnyModuleFactory): PatchedModuleFactory { + // 0, prefix to turn it into an expression: 0,function(){} would be invalid syntax without the 0, + let code: string = "0," + String(originalFactory); + let patchedSource = code; + let patchedFactory = originalFactory; + + const patchedBy = new Set<string>(); + + for (let i = 0; i < patches.length; i++) { + const patch = patches[i]; + + const buildNumber = getBuildNumber(); + const shouldCheckBuildNumber = buildNumber !== -1; + + if ( + shouldCheckBuildNumber && + (patch.fromBuild != null && buildNumber < patch.fromBuild) || + (patch.toBuild != null && buildNumber > patch.toBuild) + ) { + patches.splice(i--, 1); + continue; + } + + const moduleMatches = typeof patch.find === "string" + ? code.includes(patch.find) + : (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code)); + + if (!moduleMatches) { + continue; + } + + const executePatch = traceFunctionWithResults(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => { + if (typeof match !== "string" && match.global) { + match.lastIndex = 0; + } + + return code.replace(match, replace); + }); + + const previousCode = code; + const previousFactory = originalFactory; + let markedAsPatched = false; + + // We change all patch.replacement to array in plugins/index + for (const replacement of patch.replacement as PatchReplacement[]) { + if ( + shouldCheckBuildNumber && + (replacement.fromBuild != null && buildNumber < replacement.fromBuild) || + (replacement.toBuild != null && buildNumber > replacement.toBuild) + ) { + continue; + } + + // TODO: remove once Vesktop has been updated to use addPatch + if (patch.plugin === "Vesktop") { + canonicalizeReplacement(replacement, "VCDP"); + } + + const lastCode = code; + const lastFactory = originalFactory; + + try { + const [newCode, totalTime] = executePatch(replacement.match, replacement.replace as string); + + if (IS_REPORTER) { + patchTimings.push([patch.plugin, moduleId, replacement.match, totalTime]); + } + + if (newCode === code) { + if (!(patch.noWarn || replacement.noWarn)) { + logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(moduleId)}): ${replacement.match}`); + if (IS_DEV) { + logger.debug("Function Source:\n", code); } - - if (patch.group) { - logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`); - mod = previousMod; - code = previousCode; - patchedBy.delete(patch.plugin); - break; - } - - continue; } - code = newCode; - mod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`); - } catch (err) { - logger.error(`Patch by ${patch.plugin} errored (Module id is ${id}): ${replacement.match}\n`, err); - - if (IS_DEV) { - const changeSize = code.length - lastCode.length; - const match = lastCode.match(replacement.match)!; - - // Use 200 surrounding characters of context - const start = Math.max(0, match.index! - 200); - const end = Math.min(lastCode.length, match.index! + match[0].length + 200); - // (changeSize may be negative) - const endPatched = end + changeSize; - - const context = lastCode.slice(start, end); - const patchedContext = code.slice(start, endPatched); - - // inline require to avoid including it in !IS_DEV builds - const diff = (require("diff") as typeof import("diff")).diffWordsWithSpace(context, patchedContext); - let fmt = "%c %s "; - const elements = [] as string[]; - for (const d of diff) { - const color = d.removed - ? "red" - : d.added - ? "lime" - : "grey"; - fmt += "%c%s"; - elements.push("color:" + color, d.value); - } - - logger.errorCustomFmt(...Logger.makeTitle("white", "Before"), context); - logger.errorCustomFmt(...Logger.makeTitle("white", "After"), patchedContext); - const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff"); - logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements); - } - - patchedBy.delete(patch.plugin); - if (patch.group) { - logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} errored`); - mod = previousMod; + logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`); code = previousCode; + patchedFactory = previousFactory; + + if (markedAsPatched) { + patchedBy.delete(patch.plugin); + } + break; } - mod = lastMod; - code = lastCode; + continue; } - } - if (!patch.all) patches.splice(i--, 1); + const pluginsList = [...patchedBy]; + if (!patchedBy.has(patch.plugin)) { + pluginsList.push(patch.plugin); + } + + code = newCode; + patchedSource = `// Webpack Module ${String(moduleId)} - Patched by ${pluginsList.join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(moduleId)}`; + patchedFactory = (0, eval)(patchedSource); + + if (!patchedBy.has(patch.plugin)) { + patchedBy.add(patch.plugin); + markedAsPatched = true; + } + } catch (err) { + logger.error(`Patch by ${patch.plugin} errored (Module id is ${String(moduleId)}): ${replacement.match}\n`, err); + + if (IS_DEV) { + diffErroredPatch(code, lastCode, lastCode.match(replacement.match)!); + } + + if (markedAsPatched) { + patchedBy.delete(patch.plugin); + } + + if (patch.group) { + logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} errored`); + code = previousCode; + patchedFactory = previousFactory; + break; + } + + code = lastCode; + patchedFactory = lastFactory; + } } - if (IS_DEV) { - if (mod !== originalMod) { - factory.$$vencordPatchedSource = String(mod); - } else if (wreq != null) { - const existingFactory = wreq.m[id]; - - if (existingFactory != null) { - factory.$$vencordPatchedSource = existingFactory.$$vencordPatchedSource; - } - } + if (!patch.all) { + patches.splice(i--, 1); } } + + patchedFactory[SYM_ORIGINAL_FACTORY] = originalFactory; + + if (IS_DEV && patchedFactory !== originalFactory) { + originalFactory[SYM_PATCHED_SOURCE] = patchedSource; + originalFactory[SYM_PATCHED_BY] = patchedBy; + } + + return patchedFactory as PatchedModuleFactory; +} + +function diffErroredPatch(code: string, lastCode: string, match: RegExpMatchArray) { + const changeSize = code.length - lastCode.length; + + // Use 200 surrounding characters of context + const start = Math.max(0, match.index! - 200); + const end = Math.min(lastCode.length, match.index! + match[0].length + 200); + // (changeSize may be negative) + const endPatched = end + changeSize; + + const context = lastCode.slice(start, end); + const patchedContext = code.slice(start, endPatched); + + // Inline require to avoid including it in !IS_DEV builds + const diff = (require("diff") as typeof import("diff")).diffWordsWithSpace(context, patchedContext); + let fmt = "%c %s "; + const elements: string[] = []; + for (const d of diff) { + const color = d.removed + ? "red" + : d.added + ? "lime" + : "grey"; + fmt += "%c%s"; + elements.push("color:" + color, d.value); + } + + logger.errorCustomFmt(...Logger.makeTitle("white", "Before"), context); + logger.errorCustomFmt(...Logger.makeTitle("white", "After"), patchedContext); + const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff"); + logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements); } diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 19519d647..3a7fe2a52 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -20,9 +20,10 @@ import { makeLazy, proxyLazy } from "@utils/lazy"; import { LazyComponent } from "@utils/lazyReact"; import { Logger } from "@utils/Logger"; import { canonicalizeMatch } from "@utils/patches"; -import type { WebpackInstance } from "discord-types/other"; import { traceFunction } from "../debug/Tracer"; +import { Flux } from "./common"; +import { AnyModuleFactory, AnyWebpackRequire, ModuleExports, WebpackRequire } from "./wreq"; const logger = new Logger("Webpack"); @@ -33,8 +34,8 @@ export let _resolveReady: () => void; */ export const onceReady = new Promise<void>(r => _resolveReady = r); -export let wreq: WebpackInstance; -export let cache: WebpackInstance["c"]; +export let wreq: WebpackRequire; +export let cache: WebpackRequire["c"]; export type FilterFn = (mod: any) => boolean; @@ -56,40 +57,110 @@ export const filters = { : m => props.every(p => m[p] !== void 0), byCode: (...code: CodeFilter): FilterFn => { - code = code.map(canonicalizeMatch); - return m => { + const parsedCode = code.map(canonicalizeMatch); + const filter = m => { if (typeof m !== "function") return false; - return stringMatches(Function.prototype.toString.call(m), code); + return stringMatches(Function.prototype.toString.call(m), parsedCode); }; + + filter.$$vencordProps = [...code]; + return filter; }, byStoreName: (name: StoreNameFilter): FilterFn => m => m.constructor?.displayName === name, componentByCode: (...code: CodeFilter): FilterFn => { - const filter = filters.byCode(...code); - return m => { - if (filter(m)) return true; - if (!m.$$typeof) return false; - if (m.type) - return m.type.render - ? filter(m.type.render) // memo + forwardRef - : filter(m.type); // memo - if (m.render) return filter(m.render); // forwardRef + const byCodeFilter = filters.byCode(...code); + const filter = m => { + let inner = m; + + while (inner != null) { + if (byCodeFilter(inner)) return true; + else if (!inner.$$typeof) return false; + else if (inner.type) inner = inner.type; // memos + else if (inner.render) inner = inner.render; // forwardRefs + else return false; + } + return false; }; + + filter.$$vencordProps = [...code]; + return filter; } }; -export type CallbackFn = (mod: any, id: string) => void; +export type CallbackFn = (module: ModuleExports, id: PropertyKey) => void; +export type FactoryListernFn = (factory: AnyModuleFactory, moduleId: PropertyKey) => void; -export const subscriptions = new Map<FilterFn, CallbackFn>(); +export const waitForSubscriptions = new Map<FilterFn, CallbackFn>(); export const moduleListeners = new Set<CallbackFn>(); -export const factoryListeners = new Set<(factory: (module: any, exports: any, require: WebpackInstance) => void) => void>(); -export const beforeInitListeners = new Set<(wreq: WebpackInstance) => void>(); +export const factoryListeners = new Set<FactoryListernFn>(); -export function _initWebpack(webpackRequire: WebpackInstance) { +export function _initWebpack(webpackRequire: WebpackRequire) { wreq = webpackRequire; cache = webpackRequire.c; + + Reflect.defineProperty(webpackRequire.c, Symbol.toStringTag, { + value: "ModuleCache", + configurable: true, + writable: true, + enumerable: false + }); +} + +// Credits to Zerebos for implementing this in BD, thus giving the idea for us to implement it too +const TypedArray = Object.getPrototypeOf(Int8Array); + +const PROXY_CHECK = "is this a proxy that returns values for any key?"; +function shouldIgnoreValue(value: any) { + if (value == null) return true; + if (value === window) return true; + if (value === document || value === document.documentElement) return true; + if (value[Symbol.toStringTag] === "DOMTokenList" || value[Symbol.toStringTag] === "IntlMessagesProxy") return true; + // Discord might export a Proxy that returns non-null values for any property key which would pass all findByProps filters. + // One example of this is their i18n Proxy. However, that is already covered by the IntlMessagesProxy check above. + // As a fallback if they ever change the name or add a new Proxy, use a unique string to detect such proxies and ignore them + if (value[PROXY_CHECK] !== void 0) { + // their i18n Proxy "caches" by setting each accessed property to the return, so try to delete + Reflect.deleteProperty(value, PROXY_CHECK); + return true; + } + if (value instanceof TypedArray) return true; + + return false; +} + +function makePropertyNonEnumerable(target: Record<PropertyKey, any>, key: PropertyKey) { + const descriptor = Object.getOwnPropertyDescriptor(target, key); + if (descriptor == null) return; + + Reflect.defineProperty(target, key, { + ...descriptor, + enumerable: false + }); +} + +export function _blacklistBadModules(requireCache: NonNullable<AnyWebpackRequire["c"]>, exports: ModuleExports, moduleId: PropertyKey) { + if (shouldIgnoreValue(exports)) { + makePropertyNonEnumerable(requireCache, moduleId); + return true; + } + + if (typeof exports !== "object") { + return false; + } + + let hasOnlyBadProperties = true; + for (const exportKey in exports) { + if (shouldIgnoreValue(exports[exportKey])) { + makePropertyNonEnumerable(exports, exportKey); + } else { + hasOnlyBadProperties = false; + } + } + + return hasOnlyBadProperties; } let devToolsOpen = false; @@ -118,7 +189,7 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn for (const key in cache) { const mod = cache[key]; - if (!mod.loaded || !mod?.exports) continue; + if (!mod?.loaded || mod.exports == null) continue; if (filter(mod.exports)) { return isWaitFor ? [mod.exports, key] : mod.exports; @@ -126,13 +197,7 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn if (typeof mod.exports !== "object") continue; - if (mod.exports.default && filter(mod.exports.default)) { - const found = mod.exports.default; - return isWaitFor ? [found, key] : found; - } - - // the length check makes search about 20% faster - for (const nestedMod in mod.exports) if (nestedMod.length <= 3) { + for (const nestedMod in mod.exports) { const nested = mod.exports[nestedMod]; if (nested && filter(nested)) { return isWaitFor ? [nested, key] : nested; @@ -154,16 +219,15 @@ export function findAll(filter: FilterFn) { const ret = [] as any[]; for (const key in cache) { const mod = cache[key]; - if (!mod.loaded || !mod?.exports) continue; + if (!mod?.loaded || mod.exports == null) continue; if (filter(mod.exports)) ret.push(mod.exports); - else if (typeof mod.exports !== "object") + + if (typeof mod.exports !== "object") continue; - if (mod.exports.default && filter(mod.exports.default)) - ret.push(mod.exports.default); - else for (const nestedMod in mod.exports) if (nestedMod.length <= 3) { + for (const nestedMod in mod.exports) { const nested = mod.exports[nestedMod]; if (nested && filter(nested)) ret.push(nested); } @@ -202,7 +266,7 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns outer: for (const key in cache) { const mod = cache[key]; - if (!mod.loaded || !mod?.exports) continue; + if (!mod?.loaded || mod.exports == null) continue; for (let j = 0; j < length; j++) { const filter = filters[j]; @@ -219,23 +283,15 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns if (typeof mod.exports !== "object") continue; - if (mod.exports.default && filter(mod.exports.default)) { - results[j] = mod.exports.default; - filters[j] = undefined; - if (++found === length) break outer; - break; - } - - for (const nestedMod in mod.exports) - if (nestedMod.length <= 3) { - const nested = mod.exports[nestedMod]; - if (nested && filter(nested)) { - results[j] = nested; - filters[j] = undefined; - if (++found === length) break outer; - continue outer; - } + for (const nestedMod in mod.exports) { + const nested = mod.exports[nestedMod]; + if (nested && filter(nested)) { + results[j] = nested; + filters[j] = undefined; + if (++found === length) break outer; + continue outer; } + } } } @@ -372,7 +428,10 @@ export function findByCodeLazy(...code: CodeFilter) { * Find a store by its displayName */ export function findStore(name: StoreNameFilter) { - const res = find(filters.byStoreName(name), { isIndirect: true }); + const res = Flux.Store.getAll + ? Flux.Store.getAll().find(filters.byStoreName(name)) + : find(filters.byStoreName(name), { isIndirect: true }); + if (!res) handleModuleNotFound("findStore", name); return res; @@ -440,12 +499,27 @@ export function findExportedComponentLazy<T extends object = any>(...props: Prop }); } +function getAllPropertyNames(object: Record<PropertyKey, any>, includeNonEnumerable: boolean) { + const names = new Set<PropertyKey>(); + + const getKeys = includeNonEnumerable ? Object.getOwnPropertyNames : Object.keys; + do { + getKeys(object).forEach(name => name !== "__esModule" && names.add(name)); + object = Object.getPrototypeOf(object); + } while (object != null); + + return names; +} + /** * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) * then maps it into an easily usable module via the specified mappers. * * @param code The code to look for * @param mappers Mappers to create the non mangled exports + * @param includeBlacklistedExports Whether to include blacklisted exports in the search. + * These exports are dangerous. Accessing properties on them may throw errors + * or always return values (so a byProps filter will always return true) * @returns Unmangled exports as specified in mappers * * @example mapMangledModule("headerIdIsManaged:", { @@ -453,7 +527,7 @@ export function findExportedComponentLazy<T extends object = any>(...props: Prop * closeModal: filters.byCode("key==") * }) */ -export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>): Record<S, any> { +export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>, includeBlacklistedExports = false): Record<S, any> { const exports = {} as Record<S, any>; const id = findModuleId(...Array.isArray(code) ? code : [code]); @@ -461,8 +535,9 @@ export const mapMangledModule = traceFunction("mapMangledModule", function mapMa return exports; const mod = wreq(id as any); + const keys = getAllPropertyNames(mod, includeBlacklistedExports); outer: - for (const key in mod) { + for (const key of keys) { const member = mod[key]; for (const newName in mappers) { // if the current mapper matches this module @@ -476,24 +551,13 @@ export const mapMangledModule = traceFunction("mapMangledModule", function mapMa }); /** - * {@link mapMangledModule}, lazy. - - * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) - * then maps it into an easily usable module via the specified mappers. - * - * @param code The code to look for - * @param mappers Mappers to create the non mangled exports - * @returns Unmangled exports as specified in mappers - * - * @example mapMangledModule("headerIdIsManaged:", { - * openModal: filters.byCode("headerIdIsManaged:"), - * closeModal: filters.byCode("key==") - * }) + * lazy mapMangledModule + * @see {@link mapMangledModule} */ -export function mapMangledModuleLazy<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>): Record<S, any> { - if (IS_REPORTER) lazyWebpackSearchHistory.push(["mapMangledModule", [code, mappers]]); +export function mapMangledModuleLazy<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>, includeBlacklistedExports = false): Record<S, any> { + if (IS_REPORTER) lazyWebpackSearchHistory.push(["mapMangledModule", [code, mappers, includeBlacklistedExports]]); - return proxyLazy(() => mapMangledModule(code, mappers)); + return proxyLazy(() => mapMangledModule(code, mappers, includeBlacklistedExports)); } export const DefaultExtractAndLoadChunksRegex = /(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/; @@ -505,7 +569,7 @@ export const ChunkIdsRegex = /\("([^"]+?)"\)/g; * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory * @returns A promise that resolves with a boolean whether the chunks were loaded */ -export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = DefaultExtractAndLoadChunksRegex) { +export async function extractAndLoadChunks(code: CodeFilter, matcher = DefaultExtractAndLoadChunksRegex) { const module = findModuleFactory(...code); if (!module) { const err = new Error("extractAndLoadChunks: Couldn't find module factory"); @@ -518,7 +582,7 @@ export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = D return false; } - const match = module.toString().match(canonicalizeMatch(matcher)); + const match = String(module).match(canonicalizeMatch(matcher)); if (!match) { const err = new Error("extractAndLoadChunks: Couldn't find chunk loading in module factory code"); logger.warn(err, "Code:", code, "Matcher:", matcher); @@ -531,8 +595,9 @@ export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = D } const [, rawChunkIds, entryPointId] = match; - if (Number.isNaN(Number(entryPointId))) { - const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the chunk ids array, or the entry point id returned as the second group wasn't a number"); + + if (entryPointId == null) { + const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the chunk ids array or the entry point id"); logger.warn(err, "Code:", code, "Matcher:", matcher); // Strict behaviour in DevBuilds to fail early and make sure the issue is found @@ -542,12 +607,19 @@ export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = D return false; } + const numEntryPoint = Number(entryPointId); + const entryPoint = Number.isNaN(numEntryPoint) ? entryPointId : numEntryPoint; + if (rawChunkIds) { - const chunkIds = Array.from(rawChunkIds.matchAll(ChunkIdsRegex)).map((m: any) => Number(m[1])); + const chunkIds = Array.from(rawChunkIds.matchAll(ChunkIdsRegex)).map(m => { + const numChunkId = Number(m[1]); + return Number.isNaN(numChunkId) ? m[1] : numChunkId; + }); + await Promise.all(chunkIds.map(id => wreq.e(id))); } - if (wreq.m[entryPointId] == null) { + if (wreq.m[entryPoint] == null) { const err = new Error("extractAndLoadChunks: Entry point is not loaded in the module factories, perhaps one of the chunks failed to load"); logger.warn(err, "Code:", code, "Matcher:", matcher); @@ -558,7 +630,7 @@ export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = D return false; } - wreq(Number(entryPointId)); + wreq(entryPoint); return true; } @@ -595,7 +667,7 @@ export function waitFor(filter: string | PropsFilter | FilterFn, callback: Callb if (existing) return void callback(existing, id); } - subscriptions.set(filter, callback); + waitForSubscriptions.set(filter, callback); } /** @@ -611,7 +683,7 @@ export function search(...code: CodeFilter) { const factories = wreq.m; for (const id in factories) { - const factory = factories[id].original ?? factories[id]; + const factory = factories[id]; if (stringMatches(factory.toString(), code)) results[id] = factory; diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts new file mode 100644 index 000000000..2b356f9d1 --- /dev/null +++ b/src/webpack/wreq.d.ts @@ -0,0 +1,238 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated, Nuckyz and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { SYM_ORIGINAL_FACTORY, SYM_PATCHED_BY, SYM_PATCHED_SOURCE } from "./patchWebpack"; + +export type ModuleExports = any; + +export type Module = { + id: PropertyKey; + loaded: boolean; + exports: ModuleExports; +}; + +/** exports can be anything, however initially it is always an empty object */ +export type ModuleFactory = (this: ModuleExports, module: Module, exports: ModuleExports, require: WebpackRequire) => void; + +/** Keys here can be symbols too, but we can't properly type them */ +export type AsyncModulePromise = Promise<ModuleExports> & { + "__webpack_queues__": (fnQueue: ((queue: any[]) => any)) => any; + "__webpack_exports__": ModuleExports; + "__webpack_error__"?: any; +}; + +export type AsyncModuleBody = ( + handleAsyncDependencies: (deps: AsyncModulePromise[]) => + Promise<() => ModuleExports[]> | (() => ModuleExports[]), + asyncResult: (error?: any) => void +) => Promise<void>; + +export type EnsureChunkHandlers = { + /** + * Ensures the js file for this chunk is loaded, or starts to load if it's not. + * @param chunkId The chunk id + * @param promises The promises array to add the loading promise to + */ + j: (this: EnsureChunkHandlers, chunkId: PropertyKey, promises: Promise<void[]>) => void; + /** + * Ensures the css file for this chunk is loaded, or starts to load if it's not. + * @param chunkId The chunk id + * @param promises The promises array to add the loading promise to. This array will likely contain the promise of the js file too + */ + css: (this: EnsureChunkHandlers, chunkId: PropertyKey, promises: Promise<void[]>) => void; + /** + * Trigger for prefetching next chunks. This is called after ensuring a chunk is loaded and internally looks up + * a map to see if the chunk that just loaded has next chunks to prefetch. + * + * Note that this does not add an extra promise to the promises array, and instead only executes the prefetching after + * calling Promise.all on the promises array. + * @param chunkId The chunk id + * @param promises The promises array of ensuring the chunk is loaded + */ + prefetch: (this: EnsureChunkHandlers, chunkId: PropertyKey, promises: Promise<void[]>) => void; +}; + +export type PrefetchChunkHandlers = { + /** + * Prefetches the js file for this chunk. + * @param chunkId The chunk id + */ + j: (this: PrefetchChunkHandlers, chunkId: PropertyKey) => void; +}; + +export type ScriptLoadDone = (event: Event) => void; + +export type OnChunksLoaded = ((this: WebpackRequire, result: any, chunkIds: PropertyKey[] | undefined | null, callback: () => any, priority: number) => any) & { + /** Check if a chunk has been loaded */ + j: (this: OnChunksLoaded, chunkId: PropertyKey) => boolean; +}; + +export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & { + /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ + m: Record<PropertyKey, ModuleFactory>; + /** The module cache, where all modules which have been WebpackRequire'd are stored */ + c: Record<PropertyKey, Module>; + // /** + // * Export star. Sets properties of "fromObject" to "toObject" as getters that return the value from "fromObject", like this: + // * @example + // * const fromObject = { a: 1 }; + // * Object.keys(fromObject).forEach(key => { + // * if (key !== "default" && !Object.hasOwn(toObject, key)) { + // * Object.defineProperty(toObject, key, { + // * get: () => fromObject[key], + // * enumerable: true + // * }); + // * } + // * }); + // * @returns fromObject + // */ + // es: (this: WebpackRequire, fromObject: AnyRecord, toObject: AnyRecord) => AnyRecord; + /** + * Creates an async module. A module that which has top level await, or requires an export from an async module. + * + * The body function must be an async function. "module.exports" will become an {@link AsyncModulePromise}. + * + * The body function will be called with a function to handle requires that import from an async module, and a function to resolve this async module. An example on how to handle async dependencies: + * @example + * const factory = (module, exports, wreq) => { + * wreq.a(module, async (handleAsyncDependencies, asyncResult) => { + * try { + * const asyncRequireA = wreq(...); + * + * const asyncDependencies = handleAsyncDependencies([asyncRequire]); + * const [requireAResult] = asyncDependencies.then != null ? (await asyncDependencies)() : asyncDependencies; + * + * // Use the required module + * console.log(requireAResult); + * + * // Mark this async module as resolved + * asyncResult(); + * } catch(error) { + * // Mark this async module as rejected with an error + * asyncResult(error); + * } + * }, false); // false because our module does not have an await after dealing with the async requires + * } + */ + a: (this: WebpackRequire, module: Module, body: AsyncModuleBody, hasAwaitAfterDependencies?: boolean) => void; + /** getDefaultExport function for compatibility with non-harmony modules */ + n: (this: WebpackRequire, exports: any) => () => ModuleExports; + /** + * Create a fake namespace object, useful for faking an __esModule with a default export. + * + * mode & 1: Value is a module id, require it + * + * mode & 2: Merge all properties of value into the namespace + * + * mode & 4: Return value when already namespace object + * + * mode & 16: Return value when it's Promise-like + * + * mode & (8|1): Behave like require + */ + t: (this: WebpackRequire, value: any, mode: number) => any; + /** + * Define getter functions for harmony exports. For every prop in "definiton" (the module exports), set a getter in "exports" for the getter function in the "definition", like this: + * @example + * const exports = {}; + * const definition = { exportName: () => someExportedValue }; + * for (const key in definition) { + * if (Object.hasOwn(definition, key) && !Object.hasOwn(exports, key)) { + * Object.defineProperty(exports, key, { + * get: definition[key], + * enumerable: true + * }); + * } + * } + * // exports is now { exportName: someExportedValue } (but each value is actually a getter) + */ + d: (this: WebpackRequire, exports: Record<PropertyKey, any>, definiton: Record<PropertyKey, () => ModuleExports>) => void; + /** The ensure chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */ + f: EnsureChunkHandlers; + /** + * The ensure chunk function, it ensures a chunk is loaded, or loads if needed. + * Internally it uses the handlers in {@link WebpackRequire.f} to load/ensure the chunk is loaded. + */ + e: (this: WebpackRequire, chunkId: PropertyKey) => Promise<void[]>; + /** The prefetch chunk handlers, which are used to prefetch the files of the chunks */ + F: PrefetchChunkHandlers; + /** + * The prefetch chunk function. + * Internally it uses the handlers in {@link WebpackRequire.F} to prefetch a chunk. + */ + E: (this: WebpackRequire, chunkId: PropertyKey) => void; + /** Get the filename for the css part of a chunk */ + k: (this: WebpackRequire, chunkId: PropertyKey) => string; + /** Get the filename for the js part of a chunk */ + u: (this: WebpackRequire, chunkId: PropertyKey) => string; + /** The global object, will likely always be the window */ + g: typeof globalThis; + /** Harmony module decorator. Decorates a module as an ES Module, and prevents Node.js "module.exports" from being set */ + hmd: (this: WebpackRequire, module: Module) => any; + /** Shorthand for Object.prototype.hasOwnProperty */ + o: typeof Object.prototype.hasOwnProperty; + /** + * Function to load a script tag. "done" is called when the loading has finished or a timeout has occurred. + * "done" will be attached to existing scripts loading if src === url or data-webpack === `${uniqueName}:${key}`, + * so it will be called when that existing script finishes loading. + */ + l: (this: WebpackRequire, url: string, done: ScriptLoadDone, key?: string | number, chunkId?: PropertyKey) => void; + /** Defines __esModule on the exports, marking ES Modules compatibility as true */ + r: (this: WebpackRequire, exports: ModuleExports) => void; + /** Node.js module decorator. Decorates a module as a Node.js module */ + nmd: (this: WebpackRequire, module: Module) => any; + /** + * Register deferred code which will be executed when the passed chunks are loaded. + * + * If chunkIds is defined, it defers the execution of the callback and returns undefined. + * + * If chunkIds is undefined, and no deferred code exists or can be executed, it returns the value of the result argument. + * + * If chunkIds is undefined, and some deferred code can already be executed, it returns the result of the callback function of the last deferred code. + * + * When (priority & 1) it will wait for all other handlers with lower priority to be executed before itself is executed. + */ + O: OnChunksLoaded; + /** + * Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports". + * @returns The exports argument, but now assigned with the exports of the wasm instance + */ + v: (this: WebpackRequire, exports: ModuleExports, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise<any>; + /** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */ + p: string; + /** The runtime id of the current runtime */ + j: string; + /** Document baseURI or WebWorker location.href */ + b: string; + + /* rspack only */ + + /** rspack version */ + rv: (this: WebpackRequire) => string; + /** rspack unique id */ + ruid: string; +}; + +// Utility section for Vencord + +export type AnyWebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & Partial<Omit<WebpackRequire, "m">> & { + /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ + m: Record<PropertyKey, AnyModuleFactory>; +}; + +/** exports can be anything, however initially it is always an empty object */ +export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: ModuleExports, require: AnyWebpackRequire) => void) & { + [SYM_PATCHED_SOURCE]?: string; + [SYM_PATCHED_BY]?: Set<string>; +}; + +export type PatchedModuleFactory = AnyModuleFactory & { + [SYM_ORIGINAL_FACTORY]: AnyModuleFactory; + [SYM_PATCHED_SOURCE]?: string; + [SYM_PATCHED_BY]?: Set<string>; +}; + +export type MaybePatchedModuleFactory = PatchedModuleFactory | AnyModuleFactory; diff --git a/tsconfig.json b/tsconfig.json index db6d0918d..d2a42bd57 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,9 @@ "@shared/*": ["./shared/*"], "@webpack/types": ["./webpack/common/types"], "@webpack/common": ["./webpack/common"], - "@webpack": ["./webpack/webpack"] + "@webpack": ["./webpack/webpack"], + "@webpack/patcher": ["./webpack/patchWebpack"], + "@webpack/wreq.d": ["./webpack/wreq.d"], }, "plugins": [