diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml index 2439d86a..89588f3d 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 d79f5e49..c08f4635 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 00000000..5fa12fc3 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 ba22b123..b1cbc302 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 a669c1a2..f1e53e4d 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 135673a6..9f877c05 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 357312b0..3463e46c 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 ead061d6..dc243df7 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 a55b0e54..12523d45 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 67327b93..d59c3753 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -134,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 a7dca579..bc4d7586 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.11.2", + "version": "1.11.8", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -24,66 +24,65 @@ "dev": "pnpm watch", "watchWeb": "pnpm buildWeb --watch", "generatePluginJson": "tsx scripts/generatePluginList.ts", - "generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types", - "inject": "node scripts/runInstaller.mjs", - "uninject": "node scripts/runInstaller.mjs", + "generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false", + "inject": "node scripts/runInstaller.mjs -- --install", + "uninject": "node scripts/runInstaller.mjs -- --uninstall", "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", "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", "monaco-editor": "^0.52.2", - "nanoid": "^5.0.9", + "nanoid": "^5.1.5", "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.2.0", + "@types/chrome": "^0.0.312", + "@types/diff": "^7.0.2", "@types/lodash": "^4.17.14", - "@types/node": "^22.10.5", - "@types/react": "^19.0.2", - "@types/react-dom": "^19.0.2", + "@types/node": "^22.13.13", + "@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.1", + "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", - "stylelint": "^16.12.0", - "stylelint-config-standard": "^36.0.1", + "puppeteer-core": "^24.4.0", + "standalone-electron-types": "^34.2.0", + "stylelint": "^16.17.0", + "stylelint-config-standard": "^37.0.0", "ts-patch": "^3.3.0", "ts-pattern": "^5.6.0", - "tsx": "^4.19.2", - "type-fest": "^4.31.0", - "typescript": "^5.7.2", - "typescript-eslint": "^8.19.0", - "typescript-transform-paths": "^3.5.3", + "tsx": "^4.19.3", + "type-fest": "^4.38.0", + "typescript": "^5.8.2", + "typescript-eslint": "^8.28.0", + "typescript-transform-paths": "^3.5.5", "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 8f9d852e..b3bbe315 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 a49df467..2e493972 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 @@ -38,36 +35,36 @@ importers: specifier: ^0.52.2 version: 0.52.2 nanoid: - specifier: ^5.0.9 - version: 5.0.9 + specifier: ^5.1.5 + version: 5.1.5 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.2.0 + version: 4.2.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) '@types/chrome': - specifier: ^0.0.287 - version: 0.0.287 + specifier: ^0.0.312 + version: 0.0.312 '@types/diff': - specifier: ^6.0.0 - version: 6.0.0 + specifier: ^7.0.2 + version: 7.0.2 '@types/lodash': specifier: ^4.17.14 - version: 4.17.14 + version: 4.17.15 '@types/node': - specifier: ^22.10.5 - version: 22.10.5 + specifier: ^22.13.13 + version: 22.13.13 '@types/react': - specifier: ^19.0.2 - version: 19.0.2 + specifier: ^19.0.10 + version: 19.0.12 '@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.12) '@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.1 + version: 0.25.1 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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(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.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(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.4.0 + version: 24.4.0 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) + specifier: ^16.17.0 + version: 16.17.0(typescript@5.8.2) 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.17.0(typescript@5.8.2)) 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 + specifier: ^4.19.3 + version: 4.19.3 type-fest: - specifier: ^4.31.0 - version: 4.31.0 + specifier: ^4.38.0 + version: 4.38.0 typescript: - specifier: ^5.7.2 - version: 5.7.2 + specifier: ^5.8.2 + version: 5.8.2 typescript-eslint: - specifier: ^8.19.0 - version: 8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) + specifier: ^8.28.0 + version: 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) typescript-transform-paths: - specifier: ^3.5.3 - version: 3.5.3(typescript@5.7.2) + specifier: ^3.5.5 + version: 3.5.5(typescript@5.8.2) 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.13 '@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.38.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.3 packages: @@ -214,305 +211,158 @@ 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==} + '@esbuild/aix-ppc64@0.25.1': + resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} 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/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + '@esbuild/android-arm64@0.25.1': + resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} engines: {node: '>=18'} 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] - os: [android] - - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + '@esbuild/android-arm@0.25.1': + resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.17.19': - resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + '@esbuild/android-x64@0.25.1': + resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.17.19': - resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + '@esbuild/darwin-arm64@0.25.1': + resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.17.19': - resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + '@esbuild/darwin-x64@0.25.1': + resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} engines: {node: '>=18'} 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/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + '@esbuild/freebsd-arm64@0.25.1': + resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.17.19': - resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + '@esbuild/freebsd-x64@0.25.1': + resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} engines: {node: '>=18'} 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/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + '@esbuild/linux-arm64@0.25.1': + resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.17.19': - resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + '@esbuild/linux-arm@0.25.1': + resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.17.19': - resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + '@esbuild/linux-ia32@0.25.1': + resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} engines: {node: '>=18'} 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] - os: [linux] - - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + '@esbuild/linux-loong64@0.25.1': + resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.17.19': - resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + '@esbuild/linux-mips64el@0.25.1': + resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.17.19': - resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + '@esbuild/linux-ppc64@0.25.1': + resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.17.19': - resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + '@esbuild/linux-riscv64@0.25.1': + resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.17.19': - resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + '@esbuild/linux-s390x@0.25.1': + resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.17.19': - resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + '@esbuild/linux-x64@0.25.1': + resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.17.19': - resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/netbsd-arm64@0.25.1': + resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} + engines: {node: '>=18'} + cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + '@esbuild/netbsd-x64@0.25.1': + resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + '@esbuild/openbsd-arm64@0.25.1': + resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.17.19': - resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + '@esbuild/openbsd-x64@0.25.1': + resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.17.19': - resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + '@esbuild/sunos-x64@0.25.1': + resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.17.19': - resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + '@esbuild/win32-arm64@0.25.1': + resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.17.19': - resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + '@esbuild/win32-ia32@0.25.1': + resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.17.19': - resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + '@esbuild/win32-x64@0.25.1': + resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + '@eslint-community/eslint-utils@4.5.1': + resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -521,28 +371,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.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==} + '@eslint/core@0.12.0': + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.17.0': - resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} 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/js@9.20.0': + resolution: {integrity: sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.4': - resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} + '@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.7': + resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@humanfs/core@0.19.1': @@ -561,8 +415,8 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.1': - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} engines: {node: '>=18.18'} '@intrnl/xxhash64@0.1.2': @@ -589,6 +443,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,34 +458,31 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@puppeteer/browsers@2.6.1': - resolution: {integrity: sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==} + '@puppeteer/browsers@2.8.0': + resolution: {integrity: sha512-yTwt2KWRmCQAfhvbCRjebaSX8pV1//I0Y3g+A7f/eS7gf0l4eRJoUCvcYdVtboeU4CTOZQuqYbZNS8aBYb8ROQ==} 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.2.0': + resolution: {integrity: sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==} 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.312': + resolution: {integrity: sha512-m204djOoU/YqF1dRw3YLk2L3tcDTYREUERDQDUAmROK3Rv94nLbMJ4uvjzr/Yj/A7SyEpTrpoCuXBKTuerEdqg==} - '@types/diff@6.0.0': - resolution: {integrity: sha512-dhVCYGv3ZSbzmQaBSagrv1WJ6rXCdkyTcDyoNu1MD8JohI7pR7k8wdZEm+mvdxRKXyHVwckFzWU1vJc+Z29MlA==} + '@types/diff@7.0.2': + resolution: {integrity: sha512-JSWRMozjFKsGlEjiiKajUjIJVKuKdE3oVy2DNtK+fUo8q82nhFZ2CPQwicAIkXrofahDXrWJ7mjelvZphMS98Q==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} '@types/filesystem@0.0.36': resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} @@ -651,123 +505,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.13': + resolution: {integrity: sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==} '@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.12': + resolution: {integrity: sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==} '@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.28.0': + resolution: {integrity: sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==} 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: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.19.0': - resolution: {integrity: sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==} + '@typescript-eslint/parser@8.28.0': + resolution: {integrity: sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==} 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: '>=4.8.4 <5.9.0' - '@typescript-eslint/scope-manager@8.18.1': - resolution: {integrity: sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==} + '@typescript-eslint/scope-manager@8.28.0': + resolution: {integrity: sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==} 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.28.0': + resolution: {integrity: sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==} 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: '>=4.8.4 <5.9.0' - '@typescript-eslint/types@8.18.1': - resolution: {integrity: sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==} + '@typescript-eslint/types@8.28.0': + resolution: {integrity: sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==} 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.28.0': + resolution: {integrity: sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.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.28.0': + resolution: {integrity: sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==} 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: '>=4.8.4 <5.9.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.28.0': + resolution: {integrity: sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vap/core@0.0.12': @@ -781,8 +595,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} hasBin: true @@ -815,10 +629,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'} @@ -839,8 +649,8 @@ packages: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} - array.prototype.findlastindex@1.2.5: - resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} engines: {node: '>= 0.4'} array.prototype.flat@1.3.3: @@ -871,6 +681,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 +706,35 @@ 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.2: + resolution: {integrity: sha512-S5mmkMesiduMqnz51Bfh0Et9EX0aTCJxhsI4bvzFFLs8Z1AV8RDHadfY5CyLwdoLHgXbNBEN1gQcbEtGwuvixw==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true - bare-os@2.4.4: - resolution: {integrity: sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==} + bare-os@3.6.1: + resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + engines: {bare: '>=1.14.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,23 +763,26 @@ 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.9: + resolution: {integrity: sha512-FicwAUyWnrtnd4QqYAoRlNs44/a1jTL7XDKqm5gJ90wz1DQPlC7U2Rd1Tydpv+E7WAr4sQHuw8Q8M3nZMAyecQ==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} call-bind@1.0.8: resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} - call-bound@1.0.3: - resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} callsites@3.1.0: @@ -964,8 +796,8 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chromium-bidi@0.11.0: - resolution: {integrity: sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==} + chromium-bidi@2.1.2: + resolution: {integrity: sha512-vtRWBK2uImo5/W2oG6/cDkkHSm+2t6VHgnj+Rcwhb0pP74OoUb4GipyRX/T/y39gYQPhioP0DPShn+A7P6CHNw==} peerDependencies: devtools-protocol: '*' @@ -1038,9 +870,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 +877,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 +945,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.1413902: + resolution: {integrity: sha512-yRtvFD8Oyk7C9Os3GmnFZLu53yAfsnyw1s+mLmHHUK0GQEc9zthHWvS1r67Zqzm5t7v56PILHIVZ7kmFMaL2yQ==} diff@7.0.0: resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} @@ -1166,10 +987,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,157 +1003,24 @@ 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==} + esbuild@0.25.1: + resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} engines: {node: '>=18'} hasBin: true @@ -1398,14 +1082,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' @@ -1423,8 +1107,8 @@ packages: '@typescript-eslint/eslint-plugin': optional: true - eslint-scope@8.2.0: - resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: @@ -1435,8 +1119,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 +1180,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 +1190,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.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -1522,14 +1206,13 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@10.0.7: + resolution: {integrity: sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw==} + 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 +1233,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.7: + resolution: {integrity: sha512-qwZ4xf1v1m7Rc9XiORly31YaChvKt6oNVHuqqZcoED/7O+ToyNVGobKsIAopY9ODcWpEDKEBAbrSOCBHtNQvew==} - 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 +1251,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 +1263,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,12 +1274,8 @@ 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==} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} get-proto@1.0.1: @@ -1620,8 +1290,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} get-uri@6.0.4: resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} @@ -1731,14 +1401,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.8.1: + resolution: {integrity: sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA==} + html-minifier-terser@7.2.0: resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==} engines: {node: ^14.13.1 || >=16.0.0} @@ -1763,12 +1436,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 +1474,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: @@ -1820,10 +1493,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.16.0: - resolution: {integrity: sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==} - engines: {node: '>= 0.4'} - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -1868,8 +1537,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 +1549,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 +1597,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 +1679,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@5.3.2: + resolution: {integrity: sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==} + kind-of@3.2.2: resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} engines: {node: '>=0.10.0'} @@ -2116,13 +1784,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 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.5: + resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} engines: {node: ^18 || >=20} hasBin: true @@ -2152,8 +1820,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: @@ -2168,8 +1836,8 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} - object.entries@1.1.8: - resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} engines: {node: '>= 0.4'} object.fromentries@2.0.8: @@ -2207,8 +1875,8 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - pac-proxy-agent@7.1.0: - resolution: {integrity: sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==} + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} engines: {node: '>= 14'} pac-resolver@7.0.1: @@ -2269,8 +1937,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 +1950,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.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2318,8 +1986,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.4.0: + resolution: {integrity: sha512-eFw66gCnWo0X8Hyf9KxxJtms7a61NJVMiSaWfItsFPzFBsjsWdmcNlBdsA1WVwln6neoHhsG+uTVesKmTREn/g==} engines: {node: '>=18'} q@1.5.1: @@ -2333,24 +2001,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'} @@ -2399,8 +2060,8 @@ packages: resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} engines: {node: '>=0.12'} - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} run-parallel@1.2.0: @@ -2425,8 +2086,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true @@ -2494,8 +2155,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 +2189,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 +2234,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.17.0: + resolution: {integrity: sha512-I9OwVIWRMqVm2Br5iTbrfSqGRPWQUlvm6oXO1xZuYYu0Gpduy67N8wXOZv15p6E/JdlZiAtQaIoLKZEWk5hrjw==} engines: {node: '>=18.12.0'} hasBin: true @@ -2594,8 +2255,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,23 +2270,20 @@ 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==} - terser@5.37.0: - resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} + terser@5.39.0: + resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==} engines: {node: '>=10'} hasBin: true 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 +2296,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.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + 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,12 +2315,8 @@ 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==} + tsx@4.19.3: + resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} engines: {node: '>=18.0.0'} hasBin: true @@ -2670,12 +2324,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.38.0: + resolution: {integrity: sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg==} engines: {node: '>=16'} typed-array-buffer@1.0.3: @@ -2697,20 +2347,20 @@ 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.28.0: + resolution: {integrity: sha512-jfZtxJoHm59bvoCMYCe2BM0/baMswRhMmYhy+w6VfcyHrjxZ0OJe0tGasydCpIpA+A/WIJhTyZfb3EtwNC/kHQ==} 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: '>=4.8.4 <5.9.0' - typescript-transform-paths@3.5.3: - resolution: {integrity: sha512-5y2l2iPKNHKOj08/1i+02+ljBVUhWcXQLXomiOXCmNpiTuSxIkj0dM1LUE7OOAt53+/6KidY+sFTCP781J64Eg==} + typescript-transform-paths@3.5.5: + resolution: {integrity: sha512-RMK86wKe/4+ad+3kMT9SKAs3K0tUHLe7hF+MLbD6VpC9VUmFuKorhf3pHz+qO5GdS4mUp2ncNUo14j6ws9UvBQ==} peerDependencies: typescript: '>=3.6.5' - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} hasBin: true @@ -2718,12 +2368,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 +2397,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==} @@ -2774,8 +2418,8 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.18: - resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} which@1.3.1: @@ -2807,8 +2451,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2841,8 +2485,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,210 +2509,131 @@ 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': + '@esbuild/aix-ppc64@0.25.1': + optional: true + + '@esbuild/android-arm64@0.25.1': + optional: true + + '@esbuild/android-arm@0.25.1': + optional: true + + '@esbuild/android-x64@0.25.1': + optional: true + + '@esbuild/darwin-arm64@0.25.1': + optional: true + + '@esbuild/darwin-x64@0.25.1': + optional: true + + '@esbuild/freebsd-arm64@0.25.1': + optional: true + + '@esbuild/freebsd-x64@0.25.1': + optional: true + + '@esbuild/linux-arm64@0.25.1': + optional: true + + '@esbuild/linux-arm@0.25.1': + optional: true + + '@esbuild/linux-ia32@0.25.1': + optional: true + + '@esbuild/linux-loong64@0.25.1': + optional: true + + '@esbuild/linux-mips64el@0.25.1': + optional: true + + '@esbuild/linux-ppc64@0.25.1': + optional: true + + '@esbuild/linux-riscv64@0.25.1': + optional: true + + '@esbuild/linux-s390x@0.25.1': + optional: true + + '@esbuild/linux-x64@0.25.1': + optional: true + + '@esbuild/netbsd-arm64@0.25.1': + optional: true + + '@esbuild/netbsd-x64@0.25.1': + optional: true + + '@esbuild/openbsd-arm64@0.25.1': + optional: true + + '@esbuild/openbsd-x64@0.25.1': + optional: true + + '@esbuild/sunos-x64@0.25.1': + optional: true + + '@esbuild/win32-arm64@0.25.1': + optional: true + + '@esbuild/win32-ia32@0.25.1': + optional: true + + '@esbuild/win32-x64@0.25.1': + optional: true + + '@eslint-community/eslint-utils@4.5.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))': 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': - optional: true - - '@esbuild/android-arm64@0.23.1': - optional: true - - '@esbuild/android-arm@0.15.18': - optional: true - - '@esbuild/android-arm@0.17.19': - optional: true - - '@esbuild/android-arm@0.23.1': - optional: true - - '@esbuild/android-x64@0.17.19': - optional: true - - '@esbuild/android-x64@0.23.1': - optional: true - - '@esbuild/darwin-arm64@0.17.19': - optional: true - - '@esbuild/darwin-arm64@0.23.1': - optional: true - - '@esbuild/darwin-x64@0.17.19': - optional: true - - '@esbuild/darwin-x64@0.23.1': - optional: true - - '@esbuild/freebsd-arm64@0.17.19': - optional: true - - '@esbuild/freebsd-arm64@0.23.1': - optional: true - - '@esbuild/freebsd-x64@0.17.19': - optional: true - - '@esbuild/freebsd-x64@0.23.1': - optional: true - - '@esbuild/linux-arm64@0.17.19': - optional: true - - '@esbuild/linux-arm64@0.23.1': - optional: true - - '@esbuild/linux-arm@0.17.19': - optional: true - - '@esbuild/linux-arm@0.23.1': - optional: true - - '@esbuild/linux-ia32@0.17.19': - optional: true - - '@esbuild/linux-ia32@0.23.1': - optional: true - - '@esbuild/linux-loong64@0.15.18': - optional: true - - '@esbuild/linux-loong64@0.17.19': - optional: true - - '@esbuild/linux-loong64@0.23.1': - optional: true - - '@esbuild/linux-mips64el@0.17.19': - optional: true - - '@esbuild/linux-mips64el@0.23.1': - optional: true - - '@esbuild/linux-ppc64@0.17.19': - optional: true - - '@esbuild/linux-ppc64@0.23.1': - optional: true - - '@esbuild/linux-riscv64@0.17.19': - optional: true - - '@esbuild/linux-riscv64@0.23.1': - optional: true - - '@esbuild/linux-s390x@0.17.19': - optional: true - - '@esbuild/linux-s390x@0.23.1': - optional: true - - '@esbuild/linux-x64@0.17.19': - optional: true - - '@esbuild/linux-x64@0.23.1': - optional: true - - '@esbuild/netbsd-x64@0.17.19': - optional: true - - '@esbuild/netbsd-x64@0.23.1': - optional: true - - '@esbuild/openbsd-arm64@0.23.1': - optional: true - - '@esbuild/openbsd-x64@0.17.19': - optional: true - - '@esbuild/openbsd-x64@0.23.1': - optional: true - - '@esbuild/sunos-x64@0.17.19': - optional: true - - '@esbuild/sunos-x64@0.23.1': - optional: true - - '@esbuild/win32-arm64@0.17.19': - optional: true - - '@esbuild/win32-arm64@0.23.1': - optional: true - - '@esbuild/win32-ia32@0.17.19': - optional: true - - '@esbuild/win32-ia32@0.23.1': - optional: true - - '@esbuild/win32-x64@0.17.19': - optional: true - - '@esbuild/win32-x64@0.23.1': - optional: true - - '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))': - 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.11.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.2.0': + '@eslint/core@0.12.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 debug: 4.4.0 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.7': dependencies: + '@eslint/core': 0.12.0 levn: 0.4.1 '@humanfs/core@0.19.1': {} @@ -3082,7 +2647,7 @@ snapshots: '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.1': {} + '@humanwhocodes/retry@0.4.2': {} '@intrnl/xxhash64@0.1.2': {} @@ -3108,6 +2673,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 +2687,27 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.19.1 - '@puppeteer/browsers@2.6.1': + '@puppeteer/browsers@2.8.0': 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.2.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': 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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) eslint-visitor-keys: 4.2.0 espree: 10.3.0 estraverse: 5.3.0 @@ -3151,14 +2718,14 @@ snapshots: '@tootallnate/quickjs-emscripten@0.23.0': {} - '@types/chrome@0.0.287': + '@types/chrome@0.0.312': dependencies: '@types/filesystem': 0.0.36 '@types/har-format': 1.2.16 - '@types/diff@6.0.0': {} + '@types/diff@7.0.2': {} - '@types/estree@1.0.6': {} + '@types/estree@1.0.7': {} '@types/filesystem@0.0.36': dependencies: @@ -3169,7 +2736,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 18.16.3 + '@types/node': 22.13.13 '@types/har-format@1.2.16': {} @@ -3179,172 +2746,122 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 18.16.3 + '@types/node': 22.13.13 - '@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.13': 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': 19.0.12 - '@types/react-dom@19.0.2(@types/react@19.0.2)': + '@types/react-dom@19.0.4(@types/react@19.0.12)': dependencies: - '@types/react': 19.0.2 + '@types/react': 19.0.12 '@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.12': 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.13 optional: true - '@types/yazl@2.4.5': + '@types/yazl@2.4.6': dependencies: - '@types/node': 22.10.5 + '@types/node': 22.13.13 - '@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.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': 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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + '@typescript-eslint/scope-manager': 8.28.0 + '@typescript-eslint/type-utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.28.0 + 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.1.0(typescript@5.8.2) + typescript: 5.8.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.19.0(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2)': + '@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': 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.28.0 + '@typescript-eslint/types': 8.28.0 + '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.28.0 debug: 4.4.0 - eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) - typescript: 5.7.2 + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + typescript: 5.8.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.18.1': + '@typescript-eslint/scope-manager@8.28.0': dependencies: - '@typescript-eslint/types': 8.18.1 - '@typescript-eslint/visitor-keys': 8.18.1 + '@typescript-eslint/types': 8.28.0 + '@typescript-eslint/visitor-keys': 8.28.0 - '@typescript-eslint/scope-manager@8.19.0': + '@typescript-eslint/type-utils@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': 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.28.0(typescript@5.8.2) + '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) 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.1.0(typescript@5.8.2) + typescript: 5.8.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.18.1': {} + '@typescript-eslint/types@8.28.0': {} - '@typescript-eslint/types@8.19.0': {} - - '@typescript-eslint/typescript-estree@8.18.1(typescript@5.7.2)': + '@typescript-eslint/typescript-estree@8.28.0(typescript@5.8.2)': dependencies: - '@typescript-eslint/types': 8.18.1 - '@typescript-eslint/visitor-keys': 8.18.1 + '@typescript-eslint/types': 8.28.0 + '@typescript-eslint/visitor-keys': 8.28.0 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.1.0(typescript@5.8.2) + typescript: 5.8.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.19.0(typescript@5.7.2)': + '@typescript-eslint/utils@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)': 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.5.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) + '@typescript-eslint/scope-manager': 8.28.0 + '@typescript-eslint/types': 8.28.0 + '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + typescript: 5.8.2 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.28.0': 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.28.0 eslint-visitor-keys: 4.2.0 '@vap/core@0.0.12': @@ -3357,11 +2874,11 @@ snapshots: vscode-oniguruma: 1.7.0 vscode-textmate: 5.2.0 - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2(acorn@8.14.1): dependencies: - acorn: 8.14.0 + acorn: 8.14.1 - acorn@8.14.0: {} + acorn@8.14.1: {} agent-base@7.1.3: {} @@ -3375,7 +2892,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,23 +2908,18 @@ 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 + call-bound: 1.0.4 is-array-buffer: 3.0.5 array-includes@3.1.8: 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.3.0 is-string: 1.1.1 array-union@2.1.0: {} @@ -3418,50 +2930,51 @@ 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: + array.prototype.findlastindex@1.2.6: dependencies: call-bind: 1.0.8 + call-bound: 1.0.4 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.3.0 is-array-buffer: 3.0.5 assign-symbols@1.0.0: {} @@ -3472,13 +2985,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 +3001,29 @@ 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.2: 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) optional: true - bare-os@2.4.4: + bare-os@3.6.1: optional: true - bare-path@2.1.3: + bare-path@3.0.0: dependencies: - bare-os: 2.4.4 + bare-os: 3.6.1 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 +3057,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,22 +3074,27 @@ snapshots: union-value: 1.0.1 unset-value: 1.0.0 - call-bind-apply-helpers@1.0.1: + cacheable@1.8.9: + dependencies: + hookified: 1.8.1 + keyv: 5.3.2 + + 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.3.0 set-function-length: 1.2.2 - call-bound@1.0.3: + call-bound@1.0.4: dependencies: - call-bind-apply-helpers: 1.0.1 - get-intrinsic: 1.2.7 + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 callsites@3.1.0: {} @@ -3586,11 +3108,11 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chromium-bidi@0.11.0(devtools-protocol@0.0.1367902): + chromium-bidi@2.1.2(devtools-protocol@0.0.1413902): dependencies: - devtools-protocol: 0.0.1367902 + devtools-protocol: 0.0.1413902 mitt: 3.0.1 - zod: 3.23.8 + zod: 3.24.2 class-utils@0.3.6: dependencies: @@ -3632,14 +3154,14 @@ snapshots: copy-descriptor@0.1.1: {} - cosmiconfig@9.0.0(typescript@5.7.2): + cosmiconfig@9.0.0(typescript@5.8.2): 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.8.2 cross-spawn@7.0.6: dependencies: @@ -3656,39 +3178,25 @@ 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 + call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 data-view-byte-length@1.0.2: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 data-view-byte-offset@1.0.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 @@ -3739,7 +3247,7 @@ snapshots: escodegen: 2.1.0 esprima: 4.0.1 - devtools-protocol@0.0.1367902: {} + devtools-protocol@0.0.1413902: {} diff@7.0.0: {} @@ -3763,7 +3271,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,74 +3289,23 @@ 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 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 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 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 get-proto: 1.0.1 get-symbol-description: 1.1.0 globalthis: 1.0.4 @@ -3865,9 +3322,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 @@ -3884,7 +3341,7 @@ snapshots: typed-array-byte-offset: 1.0.4 typed-array-length: 1.0.7 unbox-primitive: 1.1.0 - which-typed-array: 1.1.18 + which-typed-array: 1.1.19 es-define-property@1.0.1: {} @@ -3893,13 +3350,13 @@ snapshots: es-iterator-helpers@1.2.1: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 globalthis: 1.0.4 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -3909,24 +3366,18 @@ 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 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 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,142 +3387,33 @@ 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: + esbuild@0.25.1: 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 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 + '@esbuild/aix-ppc64': 0.25.1 + '@esbuild/android-arm': 0.25.1 + '@esbuild/android-arm64': 0.25.1 + '@esbuild/android-x64': 0.25.1 + '@esbuild/darwin-arm64': 0.25.1 + '@esbuild/darwin-x64': 0.25.1 + '@esbuild/freebsd-arm64': 0.25.1 + '@esbuild/freebsd-x64': 0.25.1 + '@esbuild/linux-arm': 0.25.1 + '@esbuild/linux-arm64': 0.25.1 + '@esbuild/linux-ia32': 0.25.1 + '@esbuild/linux-loong64': 0.25.1 + '@esbuild/linux-mips64el': 0.25.1 + '@esbuild/linux-ppc64': 0.25.1 + '@esbuild/linux-riscv64': 0.25.1 + '@esbuild/linux-s390x': 0.25.1 + '@esbuild/linux-x64': 0.25.1 + '@esbuild/netbsd-arm64': 0.25.1 + '@esbuild/netbsd-x64': 0.25.1 + '@esbuild/openbsd-arm64': 0.25.1 + '@esbuild/openbsd-x64': 0.25.1 + '@esbuild/sunos-x64': 0.25.1 + '@esbuild/win32-arm64': 0.25.1 + '@esbuild/win32-ia32': 0.25.1 + '@esbuild/win32-x64': 0.25.1 escalade@3.2.0: {} @@ -4085,9 +3427,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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)) eslint-import-resolver-node@0.3.9: dependencies: @@ -4097,28 +3439,28 @@ 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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + 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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 + array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(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 +3472,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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) 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 + get-tsconfig: 4.10.0 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,12 +3495,12 @@ 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 minimatch: 3.1.2 - object.entries: 1.1.8 + object.entries: 1.1.9 object.fromentries: 2.0.8 object.values: 1.2.1 prop-types: 15.8.1 @@ -4167,21 +3509,21 @@ 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.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(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.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) - eslint-scope@8.2.0: + eslint-scope@8.3.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 @@ -4190,26 +3532,26 @@ 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.5.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/eslintrc': 3.2.0 - '@eslint/js': 9.17.0 - '@eslint/plugin-kit': 0.2.4 + '@eslint/config-array': 0.19.2 + '@eslint/core': 0.11.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.20.0 + '@eslint/plugin-kit': 0.2.7 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 - '@types/estree': 1.0.6 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.7 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.0 escape-string-regexp: 4.0.0 - eslint-scope: 8.2.0 + eslint-scope: 8.3.0 eslint-visitor-keys: 4.2.0 espree: 10.3.0 esquery: 1.6.0 @@ -4231,8 +3573,8 @@ snapshots: espree@10.3.0: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 4.2.0 esprima@4.0.1: {} @@ -4278,7 +3620,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,13 +3632,13 @@ 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.1: dependencies: - reusify: 1.0.4 + reusify: 1.1.0 fd-slicer@1.1.0: dependencies: @@ -4304,14 +3646,14 @@ snapshots: fflate@0.8.2: {} + file-entry-cache@10.0.7: + dependencies: + flat-cache: 6.1.7 + 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 +3673,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.7: dependencies: - flatted: 3.3.2 - keyv: 4.5.4 + cacheable: 1.8.9 + flatted: 3.3.3 + hookified: 1.8.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,32 +3694,21 @@ 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 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 hasown: 2.0.2 @@ -4386,25 +3718,12 @@ snapshots: get-caller-file@2.0.5: {} - get-intrinsic@1.2.6: + get-intrinsic@1.3.0: dependencies: - call-bind-apply-helpers: 1.0.1 - dunder-proto: 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 - 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 - 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 +3734,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: @@ -4423,11 +3742,11 @@ snapshots: get-symbol-description@1.1.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.6 + get-intrinsic: 1.3.0 - get-tsconfig@4.8.1: + get-tsconfig@4.10.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -4492,7 +3811,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 +3865,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.8.1: {} + html-minifier-terser@7.2.0: dependencies: camel-case: 4.1.2 @@ -4560,7 +3881,7 @@ snapshots: entities: 4.5.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.37.0 + terser: 5.39.0 html-tags@3.3.1: {} @@ -4582,9 +3903,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 @@ -4613,32 +3934,32 @@ snapshots: is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 - get-intrinsic: 1.2.6 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 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.4 + 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 + call-bound: 1.0.4 has-tostringtag: 1.0.2 is-buffer@1.1.6: {} is-callable@1.2.7: {} - is-core-module@2.16.0: - dependencies: - hasown: 2.0.2 - is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -4649,13 +3970,13 @@ snapshots: is-data-view@1.0.2: dependencies: - call-bound: 1.0.3 - get-intrinsic: 1.2.6 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 is-typed-array: 1.1.15 is-date-object@1.1.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-tostringtag: 1.0.2 is-descriptor@0.1.7: @@ -4678,13 +3999,16 @@ snapshots: is-finalizationregistry@1.1.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.0.10: + is-generator-function@1.1.0: dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 is-glob@4.0.3: dependencies: @@ -4692,11 +4016,9 @@ snapshots: is-map@2.0.3: {} - is-negative-zero@2.0.3: {} - is-number-object@1.1.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-tostringtag: 1.0.2 is-number@3.0.0: @@ -4713,7 +4035,7 @@ snapshots: is-regex@1.2.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -4722,33 +4044,33 @@ snapshots: is-shared-array-buffer@1.0.4: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 is-string@1.1.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-tostringtag: 1.0.2 is-symbol@1.1.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-symbols: 1.1.0 safe-regex-test: 1.1.0 is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.18 + which-typed-array: 1.1.19 is-weakmap@2.0.2: {} - is-weakref@1.1.0: + is-weakref@1.1.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 is-weakset@2.0.4: dependencies: - call-bound: 1.0.3 - get-intrinsic: 1.2.6 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 is-windows@1.0.2: {} @@ -4769,8 +4091,8 @@ snapshots: iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.7 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 get-proto: 1.0.1 has-symbols: 1.1.0 set-function-name: 2.0.2 @@ -4820,6 +4142,10 @@ snapshots: dependencies: json-buffer: 3.0.1 + keyv@5.3.2: + dependencies: + '@keyv/serialize': 1.0.3 + kind-of@3.2.2: dependencies: is-buffer: 1.1.6 @@ -4903,9 +4229,9 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.8: {} + nanoid@3.3.11: {} - nanoid@5.0.9: {} + nanoid@5.1.5: {} nanomatch@1.2.13: dependencies: @@ -4942,7 +4268,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: {} @@ -4953,24 +4279,25 @@ snapshots: object.assign@4.1.7: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 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 - object.entries@1.1.8: + object.entries@1.1.9: dependencies: call-bind: 1.0.8 + call-bound: 1.0.4 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: @@ -4985,9 +4312,9 @@ snapshots: object.values@1.2.1: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 once@1.4.0: dependencies: @@ -5004,7 +4331,7 @@ snapshots: own-keys@1.0.1: dependencies: - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 @@ -5016,7 +4343,7 @@ snapshots: dependencies: p-limit: 3.1.0 - pac-proxy-agent@7.1.0: + pac-proxy-agent@7.2.0: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.3 @@ -5077,24 +4404,24 @@ 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.3): dependencies: - postcss: 8.4.49 + postcss: 8.5.3 - 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.3: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -5115,7 +4442,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 - pac-proxy-agent: 7.1.0 + pac-proxy-agent: 7.2.0 proxy-from-env: 1.1.0 socks-proxy-agent: 8.0.5 transitivePeerDependencies: @@ -5130,15 +4457,16 @@ snapshots: punycode@2.3.1: {} - puppeteer-core@23.11.1: + puppeteer-core@24.4.0: dependencies: - '@puppeteer/browsers': 2.6.1 - chromium-bidi: 0.11.0(devtools-protocol@0.0.1367902) + '@puppeteer/browsers': 2.8.0 + chromium-bidi: 2.1.2(devtools-protocol@0.0.1413902) debug: 4.4.0 - devtools-protocol: 0.0.1367902 + devtools-protocol: 0.0.1413902 typed-query-selector: 2.12.0 - ws: 8.18.0 + ws: 8.18.1 transitivePeerDependencies: + - bare-buffer - bufferutil - supports-color - utf-8-validate @@ -5147,19 +4475,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.3.0 + get-proto: 1.0.1 which-builtin-type: 1.2.1 regex-not@1.0.2: @@ -5167,13 +4493,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 @@ -5204,19 +4523,19 @@ snapshots: resolve@1.22.10: 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 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 ret@0.1.15: {} - reusify@1.0.4: {} + reusify@1.1.0: {} run-parallel@1.2.0: dependencies: @@ -5225,8 +4544,8 @@ snapshots: safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 - get-intrinsic: 1.2.6 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 has-symbols: 1.1.0 isarray: 2.0.5 @@ -5237,7 +4556,7 @@ snapshots: safe-regex-test@1.1.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-regex: 1.2.1 @@ -5247,14 +4566,14 @@ snapshots: semver@6.3.1: {} - 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.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -5269,7 +4588,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 +4606,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 + call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.6 - object-inspect: 1.13.3 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 side-channel-weakmap@1.0.2: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.6 - object-inspect: 1.13.3 + get-intrinsic: 1.3.0 + 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 +4660,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 +4696,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.13 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: @@ -5403,12 +4721,12 @@ snapshots: string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.7 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 gopd: 1.2.0 has-symbols: 1.1.0 internal-slot: 1.1.0 @@ -5419,30 +4737,30 @@ 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: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 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: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 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 +4770,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.17.0(typescript@5.8.2)): dependencies: - stylelint: 16.12.0(typescript@5.7.2) + stylelint: 16.17.0(typescript@5.8.2) - stylelint-config-standard@36.0.1(stylelint@16.12.0(typescript@5.7.2)): + stylelint-config-standard@37.0.0(stylelint@16.17.0(typescript@5.8.2)): 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.17.0(typescript@5.8.2) + stylelint-config-recommended: 15.0.0(stylelint@16.17.0(typescript@5.8.2)) - stylelint@16.12.0(typescript@5.7.2): + stylelint@16.17.0(typescript@5.8.2): 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.8.2) 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.7 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 +4808,14 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.1 - postcss: 8.4.49 + postcss: 8.5.3 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.3) + 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 +4827,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,24 +4844,26 @@ 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.2 + 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: + terser@5.39.0: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.14.0 + acorn: 8.14.1 commander: 2.20.3 source-map-support: 0.5.21 @@ -5551,8 +4871,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 +4886,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.1.0(typescript@5.8.2): dependencies: - typescript: 5.7.2 + typescript: 5.8.2 ts-patch@3.3.0: dependencies: @@ -5578,10 +4896,10 @@ snapshots: global-prefix: 4.0.0 minimist: 1.2.8 resolve: 1.22.10 - semver: 7.6.3 + semver: 7.7.1 strip-ansi: 6.0.1 - ts-pattern@5.6.0: {} + ts-pattern@5.6.2: {} tsconfig-paths@3.15.0: dependencies: @@ -5592,18 +4910,10 @@ snapshots: tslib@2.8.1: {} - tsx@3.12.7: + tsx@4.19.3: 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 + esbuild: 0.25.1 + get-tsconfig: 4.10.0 optionalDependencies: fsevents: 2.3.3 @@ -5611,20 +4921,18 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@3.9.0: {} - - type-fest@4.31.0: {} + type-fest@4.38.0: {} typed-array-buffer@1.0.3: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-typed-array: 1.1.15 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,54 +4941,47 @@ 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.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2): 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.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2) + eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215) + typescript: 5.8.2 transitivePeerDependencies: - supports-color - typescript-transform-paths@3.5.3(typescript@5.7.2): + typescript-transform-paths@3.5.5(typescript@5.8.2): dependencies: minimatch: 9.0.5 - typescript: 5.7.2 + typescript: 5.8.2 - typescript@5.7.2: {} + typescript@5.8.2: {} unbox-primitive@1.1.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-bigints: 1.1.0 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 +5008,7 @@ snapshots: util-deprecate@1.0.2: {} - virtual-merge@1.0.1: {} + virtual-merge@1.0.2: {} vscode-oniguruma@1.7.0: {} @@ -5716,26 +5017,26 @@ 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 which-builtin-type@1.2.1: dependencies: - call-bound: 1.0.3 - function.prototype.name: 1.1.7 + call-bound: 1.0.4 + 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 - which-typed-array: 1.1.18 + which-typed-array: 1.1.19 which-collection@1.0.2: dependencies: @@ -5744,12 +5045,13 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-typed-array@1.1.18: + which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 - call-bound: 1.0.3 - for-each: 0.3.3 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 @@ -5780,7 +5082,7 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.18.0: {} + ws@8.18.1: {} y18n@5.0.8: {} @@ -5810,4 +5112,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 623f9f94..0d796ddb 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", @@ -179,16 +184,16 @@ await Promise.all([ globalName: "Vencord", sourcemap, plugins: [ - globPlugins("vencordDesktop"), + globPlugins("vesktop"), ...commonRendererPlugins ], 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 deab8661..33168ff9 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 e88f1e2b..9bcbc7f0 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 @@ -99,7 +129,7 @@ export const makeAllPackagesExternalPlugin = { }; /** - * @type {(kind: "web" | "discordDesktop" | "vencordDesktop") => import("esbuild").Plugin} + * @type {(kind: "web" | "discordDesktop" | "vesktop") => import("esbuild").Plugin} */ export const globPlugins = kind => ({ name: "glob-plugins", @@ -138,7 +168,7 @@ export const globPlugins = kind => ({ (target === "web" && kind === "discordDesktop") || (target === "desktop" && kind === "web") || (target === "discordDesktop" && kind !== "discordDesktop") || - (target === "vencordDesktop" && kind !== "vencordDesktop"); + (target === "vesktop" && kind !== "vesktop"); if (excluded) { const name = await resolvePluginName(fullDir, file); @@ -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 e3e28a14..00000000 --- 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/generatePluginList.ts b/scripts/generatePluginList.ts index 3d7c16c0..2f7ac731 100644 --- a/scripts/generatePluginList.ts +++ b/scripts/generatePluginList.ts @@ -39,7 +39,7 @@ interface PluginData { hasCommands: boolean; required: boolean; enabledByDefault: boolean; - target: "discordDesktop" | "vencordDesktop" | "desktop" | "web" | "dev"; + target: "discordDesktop" | "vesktop" | "desktop" | "web" | "dev"; filePath: string; } @@ -163,7 +163,7 @@ async function parseFile(fileName: string) { const target = getPluginTarget(fileName); if (target) { - if (!["web", "discordDesktop", "vencordDesktop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`); + if (!["web", "discordDesktop", "vesktop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`); data.target = target as any; } diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 24af628b..9502d382 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/scripts/runInstaller.mjs b/scripts/runInstaller.mjs index 145ea5a5..3151ca5b 100644 --- a/scripts/runInstaller.mjs +++ b/scripts/runInstaller.mjs @@ -118,8 +118,11 @@ const installerBin = await ensureBinary(); console.log("Now running Installer..."); +const argStart = process.argv.indexOf("--"); +const args = argStart === -1 ? [] : process.argv.slice(argStart + 1); + try { - execFileSync(installerBin, { + execFileSync(installerBin, args, { stdio: "inherit", env: { ...process.env, diff --git a/src/Vencord.ts b/src/Vencord.ts index c4c6d470..63508eb0 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 42e69745..3bed5a59 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/ChatButtons.tsx b/src/api/ChatButtons.tsx index c24e3886..6f4285ff 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"; @@ -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/Commands/index.ts b/src/api/Commands/index.ts index 2b7a4de6..af6a6fdf 100644 --- a/src/api/Commands/index.ts +++ b/src/api/Commands/index.ts @@ -31,6 +31,7 @@ export const commands = {} as Record<string, Command>; // hack for plugins being evaluated before we can grab these from webpack const OptPlaceholder = Symbol("OptionalMessageOption") as any as Option; const ReqPlaceholder = Symbol("RequiredMessageOption") as any as Option; + /** * Optional message option named "message" you can use in commands. * Used in "tableflip" or "shrug" @@ -44,11 +45,16 @@ export let OptionalMessageOption: Option = OptPlaceholder; */ export let RequiredMessageOption: Option = ReqPlaceholder; +// Discord's command list has random gaps for some reason, which can cause issues while rendering the commands +// Add this offset to every added command to keep them unique +let commandIdOffset: number; + export const _init = function (cmds: Command[]) { try { BUILT_IN = cmds; OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0]; RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0]; + commandIdOffset = Math.abs(BUILT_IN.map(x => Number(x.id)).sort((x, y) => x - y)[0]) - BUILT_IN.length; } catch (e) { new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds); } @@ -142,7 +148,7 @@ export function registerCommand<C extends Command>(command: C, plugin: string) { command.isVencordCommand = true; command.untranslatedName ??= command.name; command.untranslatedDescription ??= command.description; - command.id ??= `-${BUILT_IN.length + 1}`; + command.id ??= `-${BUILT_IN.length + commandIdOffset + 1}`; command.applicationId ??= "-1"; // BUILT_IN; command.type ??= ApplicationCommandType.CHAT_INPUT; command.inputType ??= ApplicationCommandInputType.BUILT_IN_TEXT; diff --git a/src/api/ContextMenu.ts b/src/api/ContextMenu.ts index 4a162747..a632aea6 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.tsx b/src/api/MemberListDecorators.tsx index 2199f4a6..ab5a618b 100644 --- a/src/api/MemberListDecorators.tsx +++ b/src/api/MemberListDecorators.tsx @@ -43,20 +43,21 @@ interface DecoratorProps { export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null; type OnlyIn = "guilds" | "dms"; -export const decorators = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>(); +export const decoratorsFactories = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>(); export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) { - decorators.set(identifier, { render, onlyIn }); + decoratorsFactories.set(identifier, { render, onlyIn }); } export function removeMemberListDecorator(identifier: string) { - decorators.delete(identifier); + 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.entries(), + + const decorators = Array.from( + decoratorsFactories.entries(), ([key, { render: Decorator, onlyIn }]) => { if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild)) return null; @@ -68,4 +69,10 @@ export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] { ); } ); + + return ( + <div className="vc-member-list-decorators-wrapper"> + {decorators} + </div> + ); } diff --git a/src/api/MessageDecorations.tsx b/src/api/MessageDecorations.tsx index 740c9587..1b94c18d 100644 --- a/src/api/MessageDecorations.tsx +++ b/src/api/MessageDecorations.tsx @@ -48,23 +48,29 @@ export interface MessageDecorationProps { } export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null; -export const decorations = new Map<string, MessageDecorationFactory>(); +export const decorationsFactories = new Map<string, MessageDecorationFactory>(); export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) { - decorations.set(identifier, decoration); + decorationsFactories.set(identifier, decoration); } export function removeMessageDecoration(identifier: string) { - decorations.delete(identifier); + decorationsFactories.delete(identifier); } -export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] { - return Array.from( - decorations.entries(), +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/Notifications/styles.css b/src/api/Notifications/styles.css index 98dff6df..ad5c9cbc 100644 --- a/src/api/Notifications/styles.css +++ b/src/api/Notifications/styles.css @@ -11,6 +11,10 @@ width: 100%; } +.visual-refresh .vc-notification-root { + background-color: var(--bg-overlay-floating, var(--background-base-low)); +} + .vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) { position: absolute; z-index: 2147483647; diff --git a/src/api/Settings.ts b/src/api/Settings.ts index c99d030d..08d2f8ca 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 c027fcf2..ee2f3ed3 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/Heart.tsx b/src/components/Heart.tsx index 017b4164..600a4c72 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 3f7965d5..7baeba08 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,7 +75,7 @@ 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, diff --git a/src/components/PluginSettings/components/SettingCustomComponent.tsx b/src/components/PluginSettings/components/SettingCustomComponent.tsx index af7192f3..25e8c9c6 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/SettingTextComponent.tsx b/src/components/PluginSettings/components/SettingTextComponent.tsx index cb45f28c..36b8b13a 100644 --- a/src/components/PluginSettings/components/SettingTextComponent.tsx +++ b/src/components/PluginSettings/components/SettingTextComponent.tsx @@ -51,6 +51,7 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings, onChange={handleChange} placeholder={option.placeholder ?? "Enter a value"} disabled={option.disabled?.call(definedSettings) ?? false} + maxLength={null} {...option.componentProps} /> {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} diff --git a/src/components/PluginSettings/components/index.ts b/src/components/PluginSettings/components/index.ts index d307b4e6..c38f209b 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 959a4d01..27eb10a3 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> } /> @@ -177,10 +177,10 @@ function ExcludedPluginsList({ search }: { search: string; }) { const matchingExcludedPlugins = Object.entries(ExcludedPlugins) .filter(([name]) => name.toLowerCase().includes(search)); - const ExcludedReasons: Record<"web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev", string> = { + const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "desktop" | "dev", string> = { desktop: "Discord Desktop app or Vesktop", discordDesktop: "Discord Desktop app", - vencordDesktop: "Vesktop app", + vesktop: "Vesktop app", web: "Vesktop app and the Web version of Discord", dev: "Developer version of Vencord" }; diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index d3d182e5..a4f9aeee 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/Switch.tsx b/src/components/Switch.tsx index 10904e14..87af25e2 100644 --- a/src/components/Switch.tsx +++ b/src/components/Switch.tsx @@ -27,7 +27,7 @@ interface SwitchProps { disabled?: boolean; } -const SWITCH_ON = "var(--green-360)"; +const SWITCH_ON = "var(--brand-500)"; const SWITCH_OFF = "var(--primary-400)"; const SwitchClasses = findByPropsLazy("slider", "input", "container"); diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index f3a8e1dd..f930a40d 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 00000000..6fd952f4 --- /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 97f82e77..54da5a3f 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="You can manage your perks 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/addonCard.css b/src/components/VencordSettings/addonCard.css index e46e4c29..67bdece5 100644 --- a/src/components/VencordSettings/addonCard.css +++ b/src/components/VencordSettings/addonCard.css @@ -11,6 +11,11 @@ box-sizing: border-box; } +.visual-refresh .vc-addon-card { + background-color: var(--card-primary-bg); + border: 1px solid var(--border-subtle); +} + .vc-addon-card-disabled { opacity: 0.6; } @@ -21,6 +26,11 @@ box-shadow: var(--elevation-high); } +.visual-refresh .vc-addon-card:hover { + /* same as non-hover, here to overwrite the non-refresh hover background */ + background-color: var(--card-primary-bg); +} + .vc-addon-header { margin-top: auto; display: flex; diff --git a/src/components/VencordSettings/quickActions.css b/src/components/VencordSettings/quickActions.css index 471c394e..39137818 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 { @@ -27,7 +36,15 @@ outline-offset: 2px; } +.visual-refresh .vc-settings-quickActions-pill { + background: var(--button-secondary-background); +} + +.visual-refresh .vc-settings-quickActions-pill:hover { + background: var(--button-secondary-background-hover); +} + .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 00000000..07b628f5 --- /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 ca4075da..e6d49a26 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 7d80f425..37ea4cc0 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 c7f8047d..f8c71cae 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 ddd5e5f1..2ca83b7f 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 e20ca4b7..4456564c 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/modules.d.ts b/src/modules.d.ts index b6e6b247..082b169d 100644 --- a/src/modules.d.ts +++ b/src/modules.d.ts @@ -25,7 +25,7 @@ declare module "~plugins" { folderName: string; userPlugin: boolean; }>; - export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev">; + export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vesktop" | "desktop" | "dev">; } declare module "~pluginNatives" { diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 58b2e32c..87974e39 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"; @@ -65,27 +65,25 @@ export default definePlugin({ { find: ".FULL_SIZE]:26", replacement: { - match: /(?<=(\i)=\(0,\i\.\i\)\(\i\);)return 0===\i.length\?/, - replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&" + match: /(?=;return 0===(\i)\.length\?)(?<=(\i)\.useMemo.+?)/, + replace: ";$1=$2.useMemo(()=>[...$self.getBadges(arguments[0].displayProfile),...$1],[$1])" } }, { - find: ".description,delay:", + find: "#{intl::PROFILE_USER_BADGES}", replacement: [ { - // alt: "", aria-hidden: false, src: originalSrc - match: /alt:" ","aria-hidden":!0,src:(?=.{0,20}(\i)\.icon)/, - // ...badge.props, ..., src: badge.image ?? ... - replace: "...$1.props,$& $1.image??" + match: /(alt:" ","aria-hidden":!0,src:)(.+?)(?=,)(?<=href:(\i)\.link.+?)/, + replace: (_, rest, originalSrc, badge) => `...${badge}.props,${rest}${badge}.image??(${originalSrc})` }, { match: /(?<="aria-label":(\i)\.description,.{0,200})children:/, - replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :" + replace: "children:$1.component?$self.renderBadgeComponent({...$1}) :" }, // conditionally override their onClick with badge.onClick if it exists { match: /href:(\i)\.link/, - replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&" + replace: "...($1.onClick&&{onClick:vcE=>$1.onClick(vcE,$1)}),$&" } ] } @@ -144,8 +142,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" @@ -159,8 +157,8 @@ export default definePlugin({ Vencord Donor </Forms.FormTitle> </Flex> - </Modals.ModalHeader> - <Modals.ModalContent> + </ModalHeader> + <ModalContent> <Flex> <img role="presentation" @@ -183,13 +181,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 578861e2..d4e1ea8b 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)\.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 01619546..debc5527 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 2ce51400..759ef001 100644 --- a/src/plugins/_api/dynamicImageModalApi.ts +++ b/src/plugins/_api/dynamicImageModalApi.ts @@ -14,10 +14,17 @@ export default definePlugin({ description: "Allows you to omit either width or height when opening an image modal", patches: [ { - find: "SCALE_DOWN:", + find: ".contain,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" + } + }, + { + find: ".dimensionlessImage,", + replacement: { + match: /(?<="IMAGE"===\i&&\(\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 0dba3608..39c82a1e 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 00000000..bd4afda9 --- /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 00000000..b6a03fe8 --- /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 84% rename from src/plugins/_api/messageDecorations.ts rename to src/plugins/_api/messageDecorations/index.tsx index fb63a6dd..590e50bd 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])" + match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?renderPopout:.+?(?=\])/, + 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 00000000..5c13669c --- /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 97ed1746..9dfc55e2 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 d552037f..30920a06 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 d58c7a98..a9e34f78 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 72b68324..825bbc20 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -22,7 +22,7 @@ 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_CATEGORY_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"; @@ -32,7 +32,8 @@ import { onlyOnce } from "@utils/onlyOnce"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; import { checkForUpdates, isOutdated, update } from "@utils/updater"; -import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; +import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; +import { Channel } from "discord-types/general"; import { JSX } from "react"; import gitHash from "~git-hash"; @@ -40,27 +41,24 @@ 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 = [ - SUPPORT_CHANNEL_ID, +const AdditionalAllowedChannelIds = [ "1024286218801926184", // Vencord > #bot-spam - "1033680203433660458", // Vencord > #v ]; 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; const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!; +const isSupportAllowedChannel = (channel: Channel) => channel.parent_id === SUPPORT_CATEGORY_ID || AdditionalAllowedChannelIds.includes(channel.id); + async function forceUpdate() { const outdated = await checkForUpdates(); if (outdated) { @@ -158,20 +156,21 @@ export default definePlugin({ { name: "vencord-debug", description: "Send Vencord debug info", - predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), + predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || isSupportAllowedChannel(ctx.channel), execute: async () => ({ content: await generateDebugInfoMessage() }) }, { name: "vencord-plugins", description: "Send Vencord plugin list", - predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), + predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || isSupportAllowedChannel(ctx.channel), execute: () => ({ content: generatePluginList() }) } ], flux: { async CHANNEL_SELECT({ channelId }) { - if (channelId !== SUPPORT_CHANNEL_ID) return; + const isSupportChannel = channelId === SUPPORT_CHANNEL_ID || ChannelStore.getChannel(channelId)?.parent_id === SUPPORT_CATEGORY_ID; + if (!isSupportChannel) return; const selfId = UserStore.getCurrentUser()?.id; if (!selfId || isPluginDev(selfId)) return; @@ -242,7 +241,7 @@ export default definePlugin({ !IS_UPDATER_DISABLED && ( (props.channel.id === KNOWN_ISSUES_CHANNEL_ID) || - (props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID) + (props.channel.parent_id === SUPPORT_CATEGORY_ID && props.message.author.id === VENBOT_USER_ID) ) && props.message.content?.includes("update"); @@ -268,7 +267,7 @@ export default definePlugin({ ); } - if (props.channel.id === SUPPORT_CHANNEL_ID) { + if (props.channel.parent_id === SUPPORT_CATEGORY_ID && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel)) { if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) { buttons.push( <Button diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index a2fed5d7..2b212d34 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -73,19 +73,19 @@ export default definePlugin({ group: true, replacement: [ { - match: /(?<=\.AVATAR_SIZE\);)/, - replace: "$self.useAccountPanelRef();" + match: /let{speaking:\i/, + replace: "$self.useAccountPanelRef();$&" }, { match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})` }, { - match: /\.AVATAR,children:.+?(?=renderPopout:)/, - replace: "$&onRequestClose:$self.onPopoutClose," + match: /\.AVATAR,children:.+?onRequestClose:\(\)=>\{/, + replace: "$&$self.onPopoutClose();" }, { - match: /(?<=\.avatarWrapper,)/, + match: /(?<=#{intl::SET_STATUS}\),)/, replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu," } ] diff --git a/src/plugins/alwaysAnimate/index.ts b/src/plugins/alwaysAnimate/index.ts index fc528466..8edae08b 100644 --- a/src/plugins/alwaysAnimate/index.ts +++ b/src/plugins/alwaysAnimate/index.ts @@ -43,16 +43,16 @@ export default definePlugin({ // Status emojis find: "#{intl::GUILD_OWNER}),children:", replacement: { - match: /(?<=\.activityEmoji,.+?animate:)\i/, - replace: "!0" + match: /(\.CUSTOM_STATUS.+?animate:)\i/, + replace: "$1!0" } }, { // Guild Banner find: ".animatedBannerHoverLayer,onMouseEnter:", replacement: { - match: /(?<=guildBanner:\i,animate:)\i(?=}\))/, - replace: "!0" + match: /(\.headerContent.+?guildBanner:\i,animate:)\i/, + replace: "$1!0" } } ] diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index 61e048dc..94c507f3 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 53d24ed9..d2ffe6bb 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 => { @@ -46,7 +45,8 @@ export default ErrorBoundary.wrap(guildsBarProps => { // Also display flex otherwise to fix scrolling const barStyle = { display: isFullscreen ? "none" : "flex", - } as CSSProperties; + gridArea: "betterFoldersSidebar" + } satisfies CSSProperties; if (!guilds || !settings.store.sidebarAnim) { return visible diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index d9d68f13..3bcf0335 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -16,6 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import "./sidebarFix.css"; + import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; @@ -173,8 +175,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,25 +187,44 @@ 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 predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/, replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)?null:" + }, + { + // Discord adds a slight bottom margin of 4px when it's expanded + // Which looks off when there's nothing open in the folder + predicate: () => !settings.store.keepIcons, + match: /(?=className:.{0,50}folderIcon)/, + replace: "style:arguments[0]?.isBetterFolders?{}:{marginBottom:0}," } ] }, { find: "APPLICATION_LIBRARY,render:", predicate: () => settings.store.sidebar, - replacement: { - // Render the Better Folders sidebar - match: /(container.{0,50}({className:\i\.guilds,themeOverride:\i})\))/, - replace: "$1,$self.FolderSideBar({...$2})" - } + group: true, + replacement: [ + { + // Render the Better Folders sidebar + // Discord has two different places where they render the sidebar. + // One is for visual refresh, one is not, + // and each has a bunch of conditions &&ed in front of it. + // Add the betterFolders sidebar to both, keeping the conditions Discord uses. + match: /(?<=[[,])((?:!?\i&&)+)\(.{0,50}({className:\i\.guilds,themeOverride:\i})\)/g, + replace: (m, conditions, props) => `${m},${conditions}$self.FolderSideBar(${props})` + }, + { + // Add grid styles to fix aligment with other visual refresh elements + match: /(?<=className:)(\i\.base)(?=,)/, + replace: "`${$self.gridStyle} ${$1}`" + } + ] }, { find: "#{intl::DISCODO_DISABLED}", @@ -257,6 +278,8 @@ export default definePlugin({ } }, + gridStyle: "vc-betterFolders-sidebar-grid", + getGuildTree(isBetterFolders: boolean, originalTree: any, expandedFolderIds?: Set<any>) { return useMemo(() => { if (!isBetterFolders || expandedFolderIds == null) return originalTree; diff --git a/src/plugins/betterFolders/sidebarFix.css b/src/plugins/betterFolders/sidebarFix.css new file mode 100644 index 00000000..7a048eb7 --- /dev/null +++ b/src/plugins/betterFolders/sidebarFix.css @@ -0,0 +1,9 @@ +/* These area names need to be hardcoded. Only betterFoldersSidebar is added by the plugin. */ + +.visual-refresh .vc-betterFolders-sidebar-grid { + grid-template-columns: [start] min-content [guildsEnd] min-content [sidebarEnd] min-content [channelsEnd] 1fr [end]; /* stylelint-disable-line value-keyword-case */ + grid-template-areas: + "titleBar titleBar titleBar titleBar" + "guildsList betterFoldersSidebar notice notice" + "guildsList betterFoldersSidebar channelsList page"; +} diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index 1029c07e..afef6390 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 9347c398..8f8ef141 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 5a97b0a6..84e338ef 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/, @@ -139,11 +139,12 @@ export default definePlugin({ // This is the very outer layer of the entire ui, so we can't wrap this in an ErrorBoundary // without possibly also catching unrelated errors of children. // - // Thus, we sanity check webpack modules & do this really hacky try catch to hopefully prevent hard crashes if something goes wrong. - // try catch will only catch errors in the Layer function (hence why it's called as a plain function rather than a component), but - // not in children + // Thus, we sanity check webpack modules Layer(props: LayerProps) { - if (!FocusLock || !ComponentDispatch || !Classes) { + try { + // @ts-ignore + [FocusLock.$$vencordInternal(), ComponentDispatch, Classes].forEach(e => e.test); + } catch { new Logger("BetterSettings").error("Failed to find some components"); return props.children; } diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts index c08d4328..29827a5e 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 948de0ac..c4023f09 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/clientTheme/clientTheme.css b/src/plugins/clientTheme/clientTheme.css index 64aefdcf..795b5457 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 4c1668aa..2b77d00d 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,18 +126,20 @@ export default definePlugin({ stop() { document.getElementById("clientThemeVars")?.remove(); document.getElementById("clientThemeOffsets")?.remove(); + document.getElementById("clientThemeLightModeFixes")?.remove(); } }); -const variableRegex = /(--primary-\d{3}-hsl):.*?(\S*)%;/g; +const visualRefreshVariableRegex = /(--neutral-\d{1,3}-hsl):.*?(\S*)%;/g; +const oldVariableRegex = /(--primary-\d{3}-hsl):.*?(\S*)%;/g; const lightVariableRegex = /^--primary-[1-5]\d{2}-hsl/g; const darkVariableRegex = /^--primary-[5-9]\d{2}-hsl/g; // generates variables per theme by: // - matching regex (so we can limit what variables are included in light/dark theme, otherwise text becomes unreadable) // - offset from specified center (light/dark theme get different offsets because light uses 100 for background-primary, while dark uses 600) -function genThemeSpecificOffsets(variableLightness: Record<string, number>, regex: RegExp, centerVariable: string): string { - return Object.entries(variableLightness).filter(([key]) => key.search(regex) > -1) +function genThemeSpecificOffsets(variableLightness: Record<string, number>, regex: RegExp | null, centerVariable: string): string { + return Object.entries(variableLightness).filter(([key]) => regex == null || key.search(regex) > -1) .map(([key, lightness]) => { const lightnessOffset = lightness - variableLightness[centerVariable]; const plusOrMinus = lightnessOffset >= 0 ? "+" : "-"; @@ -146,25 +148,28 @@ function genThemeSpecificOffsets(variableLightness: Record<string, number>, rege .join("\n"); } - function generateColorOffsets(styles) { - const variableLightness = {} as Record<string, number>; + const oldVariableLightness = {} as Record<string, number>; + const visualRefreshVariableLightness = {} as Record<string, number>; // Get lightness values of --primary variables - let variableMatch = variableRegex.exec(styles); - while (variableMatch !== null) { - const [, variable, lightness] = variableMatch; - variableLightness[variable] = parseFloat(lightness); - variableMatch = variableRegex.exec(styles); + for (const [, variable, lightness] of styles.matchAll(oldVariableRegex)) { + oldVariableLightness[variable] = parseFloat(lightness); + } + + for (const [, variable, lightness] of styles.matchAll(visualRefreshVariableRegex)) { + visualRefreshVariableLightness[variable] = parseFloat(lightness); } createStyleSheet("clientThemeOffsets", [ - `.theme-light {\n ${genThemeSpecificOffsets(variableLightness, lightVariableRegex, "--primary-345-hsl")} \n}`, - `.theme-dark {\n ${genThemeSpecificOffsets(variableLightness, darkVariableRegex, "--primary-600-hsl")} \n}`, + `.theme-light {\n ${genThemeSpecificOffsets(oldVariableLightness, lightVariableRegex, "--primary-345-hsl")} \n}`, + `.theme-dark {\n ${genThemeSpecificOffsets(oldVariableLightness, darkVariableRegex, "--primary-600-hsl")} \n}`, + `.visual-refresh.theme-light {\n ${genThemeSpecificOffsets(visualRefreshVariableLightness, null, "--neutral-2-hsl")} \n}`, + `.visual-refresh.theme-dark {\n ${genThemeSpecificOffsets(visualRefreshVariableLightness, null, "--neutral-69-hsl")} \n}`, ].join("\n\n")); } -function generateLightModeFixes(styles) { +function generateLightModeFixes(styles: string) { const groupLightUsesW500Regex = /\.theme-light[^{]*\{[^}]*var\(--white-500\)[^}]*}/gm; // get light capturing groups that mention --white-500 const relevantStyles = [...styles.matchAll(groupLightUsesW500Regex)].flat(); diff --git a/src/plugins/consoleJanitor/index.ts b/src/plugins/consoleJanitor/index.tsx similarity index 62% rename from src/plugins/consoleJanitor/index.ts rename to src/plugins/consoleJanitor/index.tsx index d251ff74..d32f525e 100644 --- a/src/plugins/consoleJanitor/index.ts +++ b/src/plugins/consoleJanitor/index.tsx @@ -5,8 +5,11 @@ */ import { definePluginSettings } from "@api/Settings"; +import { ErrorBoundary, Flex } from "@components/index"; import { Devs } from "@utils/constants"; -import definePlugin, { OptionType, StartAt } from "@utils/types"; +import { Margins } from "@utils/margins"; +import definePlugin, { defineDefault, OptionType, StartAt } from "@utils/types"; +import { Checkbox, Forms, Text } from "@webpack/common"; const Noop = () => { }; const NoopLogger = { @@ -24,6 +27,48 @@ const NoopLogger = { const logAllow = new Set(); +interface AllowLevels { + error: boolean; + warn: boolean; + trace: boolean; + log: boolean; + info: boolean; + debug: boolean; +} + +interface AllowLevelSettingProps { + settingKey: keyof AllowLevels; +} + +function AllowLevelSetting({ settingKey }: AllowLevelSettingProps) { + const { allowLevel } = settings.use(["allowLevel"]); + const value = allowLevel[settingKey]; + + return ( + <Checkbox + value={value} + onChange={(_, newValue) => settings.store.allowLevel[settingKey] = newValue} + size={20} + > + <Text variant="text-sm/normal">{settingKey[0].toUpperCase() + settingKey.slice(1)}</Text> + </Checkbox> + ); +} + +const AllowLevelSettings = ErrorBoundary.wrap(() => { + return ( + <Forms.FormSection> + <Forms.FormTitle tag="h3">Filter List</Forms.FormTitle> + <Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Always allow loggers of these types</Forms.FormText> + <Flex flexDirection="row"> + {Object.keys(settings.store.allowLevel).map(key => ( + <AllowLevelSetting key={key} settingKey={key as keyof AllowLevels} /> + ))} + </Flex> + </Forms.FormSection> + ); +}); + const settings = definePluginSettings({ disableLoggers: { type: OptionType.BOOLEAN, @@ -45,6 +90,18 @@ const settings = definePluginSettings({ logAllow.clear(); newVal.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow)); } + }, + allowLevel: { + type: OptionType.COMPONENT, + component: AllowLevelSettings, + default: defineDefault<AllowLevels>({ + error: true, + warn: false, + trace: false, + log: false, + info: false, + debug: false + }) } }); @@ -60,17 +117,19 @@ export default definePlugin({ this.settings.store.whitelistedLoggers?.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow)); }, + Noop, NoopLogger: () => NoopLogger, - shouldLog(logger: string) { - return logAllow.has(logger); + + shouldLog(logger: string, level: keyof AllowLevels) { + return logAllow.has(logger) || settings.store.allowLevel[level] === true; }, patches: [ { find: "https://github.com/highlightjs/highlight.js/issues/2277", replacement: { - match: /(?<=&&\()console.log\(`Deprecated.+?`\),/, - replace: "" + match: /\(console.log\(`Deprecated.+?`\),/, + replace: "(" } }, { @@ -90,16 +149,15 @@ export default definePlugin({ { find: "is not a valid locale.", replacement: { - match: /\i\.error\(""\.concat\(\i," is not a valid locale."\)\);/, - replace: "" + match: /\i\.error(?=\(""\.concat\(\i," is not a valid locale."\)\))/, + replace: "$self.Noop" } }, { - find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");', - all: true, + find: '"AppCrashedFatalReport: getLastCrash not supported."', replacement: { - match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/, - replace: "" + match: /console\.log(?=\("AppCrashedFatalReport: getLastCrash not supported\."\))/, + replace: "$self.Noop" } }, { @@ -137,13 +195,13 @@ export default definePlugin({ replace: "" } }, - // Patches discords generic logger function + // Patches Discord generic logger function { find: "Σ:", predicate: () => settings.store.disableLoggers, replacement: { match: /(?<=&&)(?=console)/, - replace: "$self.shouldLog(arguments[0])&&" + replace: "$self.shouldLog(arguments[0],arguments[1])&&" } }, { diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index be23d37c..5afdbdd9 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)!), @@ -151,13 +153,16 @@ function makeShortcuts() { openModal: { getter: () => ModalAPI.openModal }, openModalLazy: { getter: () => ModalAPI.openModalLazy }, - Stores: { - getter: () => Object.fromEntries( - Common.Flux.Store.getAll() - .map(store => [store.getName(), store] as const) - .filter(([name]) => name.length > 1) - ) - } + Stores: Webpack.fluxStores, + + // e.g. "2024-05_desktop_visual_refresh", 0 + setExperiment: (id: string, bucket: number) => { + Common.FluxDispatcher.dispatch({ + type: "EXPERIMENT_OVERRIDE_BUCKET", + experimentId: id, + experimentBucket: bucket, + }); + }, }; } @@ -165,11 +170,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 a9c018a9..58e7303a 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/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index f6bc2c8b..7303ba6b 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -173,6 +173,15 @@ export default definePlugin({ } catch (err) { CrashHandlerLogger.debug("Failed to pop all layers.", err); } + try { + FluxDispatcher.dispatch({ + type: "DEV_TOOLS_SETTINGS_UPDATE", + settings: { displayTools: false, lastOpenTabId: "analytics" } + }); + } catch (err) { + CrashHandlerLogger.debug("Failed to close DevTools.", err); + } + if (settings.store.attemptToNavigateToHome) { try { NavigationRouter.transitionToGuild("@me"); @@ -181,7 +190,6 @@ export default definePlugin({ } } - // Set isRecovering to false before setting the state to allow us to handle the next crash error correcty, in case it happens setImmediate(() => isRecovering = false); diff --git a/src/plugins/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts index 4a1b7376..b24f7a90 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/customidle/index.ts b/src/plugins/customidle/index.ts index 7d353f15..9ffa889f 100644 --- a/src/plugins/customidle/index.ts +++ b/src/plugins/customidle/index.ts @@ -47,19 +47,11 @@ export default definePlugin({ { match: /\i\.\i\.dispatch\({type:"IDLE",idle:!1}\)/, replace: "$self.handleOnline()" - }, - { - match: /(setInterval\(\i,\.25\*)\i\.\i/, - replace: "$1$self.getIntervalDelay()" // For web installs } ] } ], - getIntervalDelay() { - return Math.min(6e5, this.getIdleTimeout()); - }, - handleOnline() { if (!settings.store.remainInIdle) { FluxDispatcher.dispatch({ diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index b7e90e09..7cb60384 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 fc7e9e32..f3f577a5 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 69a7a1a5..63963d09 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 { @@ -87,7 +99,7 @@ export default definePlugin({ }, // Current user area, at bottom of channels/dm list { - find: "renderAvatarWithPopout(){", + find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", replacement: [ // Use Decor avatar decoration hook { diff --git a/src/plugins/decor/settings.tsx b/src/plugins/decor/settings.tsx index 0d3628cc..8e82bbae 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/DecorSection.tsx b/src/plugins/decor/ui/components/DecorSection.tsx index ff044f8c..4648ef8d 100644 --- a/src/plugins/decor/ui/components/DecorSection.tsx +++ b/src/plugins/decor/ui/components/DecorSection.tsx @@ -5,7 +5,7 @@ */ import { Flex } from "@components/Flex"; -import { findByCodeLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@webpack"; import { Button, useEffect } from "@webpack/common"; import { useAuthorizationStore } from "../../lib/stores/AuthorizationStore"; @@ -13,7 +13,7 @@ import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDeco import { cl } from "../"; import { openChangeDecorationModal } from "../modals/ChangeDecorationModal"; -const CustomizationSection = findByCodeLazy(".customizationSectionBackground"); +const CustomizationSection = findComponentByCodeLazy(".customizationSectionBackground"); export interface DecorSectionProps { hideTitle?: boolean; diff --git a/src/plugins/decor/ui/components/DecorationContextMenu.tsx b/src/plugins/decor/ui/components/DecorationContextMenu.tsx index 7451bb22..7c1542f6 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/decor/ui/modals/CreateDecorationModal.tsx b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx index eb39c16d..4afb7464 100644 --- a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx +++ b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx @@ -19,7 +19,7 @@ import { AvatarDecorationModalPreview } from "../components"; const FileUpload = findComponentByCodeLazy("fileUploadInput,"); -const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE=3]="POSITIVE', { +const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', { HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO"), HelpMessage: filters.byCode(".iconDiv") }); diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx index 19d95446..7a9a1930 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/emoteCloner/index.tsx b/src/plugins/emoteCloner/index.tsx index 98bd4642..ffc2307e 100644 --- a/src/plugins/emoteCloner/index.tsx +++ b/src/plugins/emoteCloner/index.tsx @@ -25,11 +25,14 @@ import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/moda import definePlugin from "@utils/types"; import { findByCodeLazy, findStoreLazy } from "@webpack"; import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; +import { Guild } from "discord-types/general"; import { Promisable } from "type-fest"; const StickersStore = findStoreLazy("StickersStore"); const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START"); +const getGuildMaxEmojiSlots = findByCodeLazy(".additionalEmojiSlots") as (guild: Guild) => number; + interface Sticker { t: "Sticker"; description: string; @@ -125,7 +128,7 @@ function getGuildCandidates(data: Data) { const { isAnimated } = data as Emoji; - const emojiSlots = g.getMaxEmojiSlots(); + const emojiSlots = getGuildMaxEmojiSlots(g); const { emojis } = EmojiStore.getGuilds()[g.id]; let count = 0; diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index d87170ad..4bdf194c 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -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 diff --git a/src/plugins/fixSpotifyEmbeds.desktop/native.ts b/src/plugins/fixSpotifyEmbeds.desktop/native.ts index e15e4a44..79602609 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 003cba9e..950940b0 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 3c5a324f..0af611bc 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 3f9e8210..7c0dbea6 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 5a0c41c0..ceeb5692 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 32d72fdb..97885177 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")!; @@ -70,6 +69,7 @@ function GameActivityToggleButton() { icon={makeIcon(showCurrentGame)} role="switch" aria-checked={!showCurrentGame} + redGlow={!showCurrentGame} onClick={() => ShowCurrentGame.updateSetting(old => !old)} /> ); @@ -90,11 +90,13 @@ export default definePlugin({ dependencies: ["UserSettingsAPI"], settings, + managedStyle, + patches: [ { find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", replacement: { - match: /this\.renderNameZone\(\).+?children:\[/, + match: /className:\i\.buttons,.{0,50}children:\[/, replace: "$&$self.GameActivityToggleButton()," } } @@ -102,11 +104,4 @@ export default definePlugin({ GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }), - start() { - enableStyle(style); - }, - - stop() { - disableStyle(style); - } }); diff --git a/src/plugins/gameActivityToggle/style.css b/src/plugins/gameActivityToggle/style.css index 3e6fd6b7..e13e2425 100644 --- a/src/plugins/gameActivityToggle/style.css +++ b/src/plugins/gameActivityToggle/style.css @@ -1,3 +1,3 @@ -[class*="panels"] [class*="avatarWrapper"] { +[class^="panels"] [class^="avatarWrapper"] { min-width: 88px; } diff --git a/src/plugins/hideAttachments/index.tsx b/src/plugins/hideAttachments/index.tsx index e122e3cb..fe9e6899 100644 --- a/src/plugins/hideAttachments/index.tsx +++ b/src/plugins/hideAttachments/index.tsx @@ -16,77 +16,92 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import "./styles.css"; + import { get, set } from "@api/DataStore"; +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: ["MessageUpdaterAPI"], + + patches: [{ + find: "this.renderAttachments(", + replacement: { + match: /(?<=\i=)this\.render(?:Attachments|Embeds|StickersAccessories)\((\i)\)/g, + replace: "$self.shouldHide($1?.id)?null:$&" + } + }], renderMessagePopoverButton(msg) { - if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null; + // @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 Attachments" : "Hide Attachments", + label: isHidden ? "Show Media" : "Hide Media", icon: isHidden ? ImageVisible : ImageInvisible, message: msg, channel: ChannelStore.getChannel(msg.channel_id), - onClick: () => this.toggleHide(msg.id) + onClick: () => this.toggleHide(msg.channel_id, msg.id) }; }, - async start() { - style = document.createElement("style"); - style.id = "VencordHideAttachments"; - document.head.appendChild(style); + 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() { await getHiddenMessages(); - await this.buildCss(); }, stop() { - style.remove(); hiddenMessages.clear(); }, - 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 00000000..4cbd10a6 --- /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 bb0b2053..e3041745 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 08e146ab..b0889186 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -12,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, @@ -73,8 +73,6 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent> 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); - - recalculateActivities(); } function recalculateActivities() { @@ -149,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, @@ -170,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)); @@ -245,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);` } ] @@ -264,14 +260,7 @@ export default definePlugin({ replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` } }, - // Discord has 2 different components for activities. Currently, the last is the one being used - { - find: ".activityTitleText,variant", - replacement: { - match: /\.activityTitleText.+?children:(\i)\.name.*?}\),/, - replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),` - }, - }, + // Activities from the apps launcher in the bottom right of the chat bar { find: ".promotedLabelWrapperNonBanner,children", replacement: { diff --git a/src/plugins/imageZoom/components/Magnifier.tsx b/src/plugins/imageZoom/components/Magnifier.tsx index 31fa7a11..009165ff 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 06e1dcd5..1a17b616 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,12 +159,41 @@ export default definePlugin({ authors: [Devs.Aria], tags: ["ImageUtilities"], + managedStyle, + patches: [ { find: ".contain,SCALE_DOWN:", replacement: { - match: /\.slide,\i\),/g, - replace: `$&id:"${ELEMENT_ID}",` + match: /imageClassName:/, + replace: `id:"${ELEMENT_ID}",$&` + } + }, + + { + find: ".dimensionlessImage,", + replacement: [ + { + match: /className:\i\.media,/, + replace: `id:"${ELEMENT_ID}",$&` + }, + { + // This patch needs to be above the next one as it uses the zoomed class as an anchor + match: /\.zoomed]:.+?,(?=children:)/, + replace: "$&onClick:()=>{}," + }, + { + match: /className:\i\(\)\(\i\.wrapper,.+?}\),/, + replace: "" + }, + ] + }, + // Make media viewer options not hide when zoomed in with the default Discord feature + { + find: '="FOCUS_SENSITIVE",', + replacement: { + match: /(?<=\.hidden]:)\i/, + replace: "false" } }, @@ -247,14 +280,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 63a51e29..10952177 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/README.md b/src/plugins/implicitRelationships/README.md index a76e298f..b560bf2c 100644 --- a/src/plugins/implicitRelationships/README.md +++ b/src/plugins/implicitRelationships/README.md @@ -2,6 +2,6 @@ Shows your implicit relationships in the Friends tab. -Implicit relationships on Discord are people with whom you've frecently interacted and share a mutual server; even though Discord thinks you should be friends with them, you haven't added them as friends. +Implicit relationships on Discord are people with whom you've frecently interacted and don't have a relationship with. Even though Discord thinks you should be friends with them, you haven't added them as friends! ![](https://camo.githubusercontent.com/6927161ee0c933f7ef6d61f243cca3e6ea4c8db9d1becd8cbf73c45e1bd0d127/68747470733a2f2f692e646f6c66692e65732f7055447859464662674d2e706e673f6b65793d736e3950343936416c32444c7072) diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index 1cde814e..13c12ed9 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findStoreLazy } from "@webpack"; -import { ChannelStore, Constants, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common"; +import { Constants, FluxDispatcher, GuildStore, RelationshipStore, RestAPI, SnowflakeUtils, UserStore } from "@webpack/common"; import { Settings } from "Vencord"; const UserAffinitiesStore = findStoreLazy("UserAffinitiesStore"); @@ -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" }, }, @@ -121,55 +121,61 @@ export default definePlugin({ : comparator(row); }, + async refreshUserAffinities() { + try { + await RestAPI.get({ url: "/users/@me/affinities/users", retries: 3 }).then(({ body }) => { + FluxDispatcher.dispatch({ + type: "LOAD_USER_AFFINITIES_SUCCESS", + affinities: body, + }); + }); + } catch (e) { + // Not a critical error if this fails for some reason + } + }, + async fetchImplicitRelationships() { // Implicit relationships are defined as users that you: // 1. Have an affinity for - // 2. Do not have a relationship with // TODO: Check how this works with pending/blocked relationships - // 3. Have a mutual guild with + // 2. Do not have a relationship with + await this.refreshUserAffinities(); const userAffinities: Set<string> = UserAffinitiesStore.getUserAffinitiesUserIds(); + const relationships = RelationshipStore.getRelationships(); const nonFriendAffinities = Array.from(userAffinities).filter( id => !RelationshipStore.getRelationshipType(id) ); + nonFriendAffinities.forEach(id => { + relationships[id] = 5; + }); + RelationshipStore.emitChange(); - // I would love to just check user cache here (falling back to the gateway of course) - // However, users in user cache may just be there because they share a DM or group DM with you - // So there's no guarantee that a user being in user cache means they have a mutual with you - // To get around this, we request users we have DMs with, and ignore them below if we don't get them back - const dmUserIds = new Set( - Object.values(ChannelStore.getSortedPrivateChannels()).flatMap(c => c.recipients) - ); - const toRequest = nonFriendAffinities.filter(id => !UserStore.getUser(id) || dmUserIds.has(id)); + const toRequest = nonFriendAffinities.filter(id => !UserStore.getUser(id)); const allGuildIds = Object.keys(GuildStore.getGuilds()); const sentNonce = SnowflakeUtils.fromTimestamp(Date.now()); let count = allGuildIds.length * Math.ceil(toRequest.length / 100); // OP 8 Request Guild Members allows 100 user IDs at a time - const ignore = new Set(toRequest); - const relationships = RelationshipStore.getRelationships(); + // Note: As we are using OP 8 here, implicit relationships who we do not share a guild + // with will not be fetched; so, if they're not otherwise cached, they will not be shown + // This should not be a big deal as these should be rare const callback = ({ chunks }) => { - for (const chunk of chunks) { - const { nonce, members } = chunk; - if (nonce !== sentNonce) return; - members.forEach(member => { - ignore.delete(member.user.id); - }); + const chunkCount = chunks.filter(chunk => chunk.nonce === sentNonce).length; + if (chunkCount === 0) return; - nonFriendAffinities.map(id => UserStore.getUser(id)).filter(user => user && !ignore.has(user.id)).forEach(user => relationships[user.id] = 5); - RelationshipStore.emitChange(); - if (--count === 0) { - // @ts-ignore - FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback); - } + count -= chunkCount; + RelationshipStore.emitChange(); + if (count <= 0) { + FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback); } }; - // @ts-ignore FluxDispatcher.subscribe("GUILD_MEMBERS_CHUNK_BATCH", callback); for (let i = 0; i < toRequest.length; i += 100) { FluxDispatcher.dispatch({ type: "GUILD_MEMBERS_REQUEST", guildIds: allGuildIds, userIds: toRequest.slice(i, i + 100), + presences: true, nonce: sentNonce, }); } diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 545169b1..4a268868 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -26,10 +26,12 @@ import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecor 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 { 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"; @@ -40,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; @@ -57,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; @@ -73,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()); @@ -141,14 +145,21 @@ for (const p of neededApiPlugins) { 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 (def.onChange != null) { - SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, def.onChange); + 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); } } } @@ -247,7 +258,7 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) { const { - name, commands, contextMenus, userProfileBadge, + name, commands, contextMenus, managedStyle, userProfileBadge, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton } = p; @@ -291,6 +302,8 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: } } + if (managedStyle) enableStyle(managedStyle); + if (userProfileBadge) addProfileBadge(userProfileBadge); if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit); @@ -308,7 +321,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) { const { - name, commands, contextMenus, userProfileBadge, + name, commands, contextMenus, managedStyle, userProfileBadge, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton } = p; @@ -350,6 +363,8 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu } } + if (managedStyle) disableStyle(managedStyle); + if (userProfileBadge) removeProfileBadge(userProfileBadge); if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit); diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index d6a39cba..f5e8cbb5 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -80,8 +80,8 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => { <svg aria-hidden role="img" - width="24" - height="24" + width="20" + height="20" viewBox={"0 0 64 64"} style={{ scale: "1.39", translate: "0 -1px" }} > @@ -149,6 +149,12 @@ export default definePlugin({ renderChatBarButton: ChatBarIcon, + colorCodeFromNumber(color: number): string { + return `#${[color >> 16, color >> 8, color] + .map(x => (x & 0xFF).toString(16)) + .join("")}`; + }, + // Gets the Embed of a Link async getEmbed(url: URL): Promise<Object | {}> { const { body } = await RestAPI.post({ @@ -157,6 +163,8 @@ export default definePlugin({ urls: [url] } }); + // The endpoint returns the color as a number, but Discord expects a string + body.embeds[0].color = this.colorCodeFromNumber(body.embeds[0].color); return await body.embeds[0]; }, @@ -166,7 +174,7 @@ export default definePlugin({ message.embeds.push({ type: "rich", rawTitle: "Decrypted Message", - color: "0x45f5f5", + color: "#45f5f5", rawDescription: revealed, footer: { text: "Made with ❤️ by c0dine and Sammy!", diff --git a/src/plugins/ircColors/README.md b/src/plugins/ircColors/README.md new file mode 100644 index 00000000..9d9c7634 --- /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 00000000..50630372 --- /dev/null +++ b/src/plugins/ircColors/index.ts @@ -0,0 +1,113 @@ +/* + * 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: /\i.gradientClassName]\),style:/, + replace: "$&{color:$self.calculateNameColorForMessageContext(arguments[0])},_style:" + } + }, + { + find: "#{intl::GUILD_OWNER}),children:", + replacement: { + match: /(typingIndicatorRef:.+?},)(\i=.+?)color:null!=.{0,50}?(?=,)/, + replace: (_, rest1, rest2) => `${rest1}ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest2}color:ircColor` + }, + predicate: () => settings.store.memberListColors + } + ], + + calculateNameColorForMessageContext(context: any) { + const userId: string | undefined = context?.message?.author?.id; + const colorString = context?.author?.colorString; + const color = calculateNameColorForUser(userId); + + // Color preview in role settings + if (context?.message?.channel_id === "1337" && userId === "313337") + return colorString; + + 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 02fd694f..77fa2784 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 67bbc4ce..ad7491cc 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 53ab93e3..c4a3adce 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/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index e4e5b877..460b95a8 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", @@ -63,11 +63,11 @@ export default definePlugin({ stringDelta(delta: number, showMillis: boolean) { const diff: Diff = { - days: Math.round(delta / (60 * 60 * 24 * 1000)), - hours: Math.round((delta / (60 * 60 * 1000)) % 24), - minutes: Math.round((delta / (60 * 1000)) % 60), - seconds: Math.round(delta / 1000 % 60), - milliseconds: Math.round(delta % 1000) + days: Math.floor(delta / (60 * 60 * 24 * 1000)), + hours: Math.floor((delta / (60 * 60 * 1000)) % 24), + minutes: Math.floor((delta / (60 * 1000)) % 60), + seconds: Math.floor(delta / 1000 % 60), + milliseconds: Math.floor(delta % 1000) }; const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null; @@ -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 c28e7801..c248167f 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -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> + ) } }); diff --git a/src/plugins/messageLogger/deleteStyleText.css b/src/plugins/messageLogger/deleteStyleText.css index a4e9a93c..a114b7de 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 333de9a4..4bce7f5e 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, @@ -400,7 +401,7 @@ export default definePlugin({ { // Updated message transformer(?) - find: "THREAD_STARTER_MESSAGE?null===", + find: "THREAD_STARTER_MESSAGE?null==", replacement: [ { // Pass through editHistory & deleted & original attachments to the "edited message" transformer @@ -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 2759129d..a76e9888 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 5a5d03fd..49e88c42 100644 --- a/src/plugins/messageTags/index.ts +++ b/src/plugins/messageTags/index.ts @@ -89,7 +89,7 @@ export default definePlugin({ settings, async start() { - // TODO: Remove DataStore tags migration once enough time has passed + // 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 diff --git a/src/plugins/moreUserTags/index.tsx b/src/plugins/moreUserTags/index.tsx deleted file mode 100644 index 8029b483..00000000 --- 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/newGuildSettings/index.tsx b/src/plugins/newGuildSettings/index.tsx index e613f7a0..f8a517fa 100644 --- a/src/plugins/newGuildSettings/index.tsx +++ b/src/plugins/newGuildSettings/index.tsx @@ -128,7 +128,7 @@ export default definePlugin({ { find: ",acceptInvite(", replacement: { - match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!==.+?;/, + match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!=.+?;/, replace: (m, guildId) => `${m}$self.applyDefaultSettings(${guildId});` } }, diff --git a/src/plugins/noBlockedMessages/index.ts b/src/plugins/noBlockedMessages/index.ts index 48ca63d1..efa4ed08 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,16 +32,35 @@ 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}", + find: ".__invalid_blocked,", replacement: [ { - match: /let\{[^}]*collapsedReason[^}]*\}/, + match: /let{expanded:\i,[^}]*?collapsedReason[^}]*}/, replace: "if($self.shouldHide(arguments[0]))return null;$&" } ] @@ -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/noDeepLinks.web/index.ts b/src/plugins/noDeepLinks.web/index.ts new file mode 100644 index 00000000..e217308a --- /dev/null +++ b/src/plugins/noDeepLinks.web/index.ts @@ -0,0 +1,25 @@ +/* + * 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 definePlugin from "@utils/types"; + +export default definePlugin({ + name: "DisableDeepLinks", + description: "Disables Discord's stupid deep linking feature which tries to force you to use their Desktop App", + authors: [Devs.Ven], + required: true, + + noop: () => { }, + + patches: [{ + find: /\.openNativeAppModal\(.{0,50}?\.DEEP_LINK/, + replacement: { + match: /\i\.\i\.openNativeAppModal/, + replace: "$self.noop", + } + }] +}); diff --git a/src/plugins/noScreensharePreview/index.ts b/src/plugins/noScreensharePreview/index.ts deleted file mode 100644 index d4bb9c1e..00000000 --- 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 bc3c7539..d320d76f 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/nsfwGateBypass/index.ts b/src/plugins/nsfwGateBypass/index.ts index b6f0f3e8..6d0cb702 100644 --- a/src/plugins/nsfwGateBypass/index.ts +++ b/src/plugins/nsfwGateBypass/index.ts @@ -1,6 +1,6 @@ /* * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors + * Copyright (c) 2025 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 @@ -26,10 +26,16 @@ export default definePlugin({ patches: [ { find: ".nsfwAllowed=null", - replacement: { - match: /(?<=\.nsfwAllowed=)null!==.+?(?=[,;])/, - replace: "!0", - }, - }, + replacement: [ + { + match: /(?<=\.nsfwAllowed=)null!=.+?(?=[,;])/, + replace: "true", + }, + { + match: /(?<=\.ageVerificationStatus=)null!=.+?(?=[,;])/, + replace: "3", // VERIFIED_ADULT + } + ], + } ], }); diff --git a/src/plugins/oneko/index.ts b/src/plugins/oneko/index.ts index 90a3901a..cabe4db9 100644 --- a/src/plugins/oneko/index.ts +++ b/src/plugins/oneko/index.ts @@ -26,7 +26,7 @@ export default definePlugin({ authors: [Devs.Ven, Devs.adryd], start() { - fetch("https://raw.githubusercontent.com/adryd325/oneko.js/8fa8a1864aa71cd7a794d58bc139e755e96a236c/oneko.js") + fetch("https://raw.githubusercontent.com/adryd325/oneko.js/c4ee66353b11a44e4a5b7e914a81f8d33111555e/oneko.js") .then(x => x.text()) .then(s => s.replace("./oneko.gif", "https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.gif") .replace("(isReducedMotion)", "(false)")) diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index 09fc2f3b..1c90b529 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/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index 23b188bc..b648f92e 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -20,11 +20,17 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin from "@utils/types"; -import { Constants, GuildStore, RestAPI } from "@webpack/common"; +import { Constants, GuildStore, PermissionStore, RestAPI } from "@webpack/common"; function showDisableInvites(guildId: string) { - // @ts-ignore - return !GuildStore.getGuild(guildId).hasFeature("INVITES_DISABLED"); + const guild = GuildStore.getGuild(guildId); + if (!guild) return false; + + return ( + // @ts-ignore + !guild.hasFeature("INVITES_DISABLED") && + PermissionStore.getGuildPermissionProps(guild).canManageRoles + ); } function disableInvites(guildId: string) { diff --git a/src/plugins/permissionFreeWill/index.ts b/src/plugins/permissionFreeWill/index.ts index c45cbff6..8a613514 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 341971ff..02662fe9 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 b7e42096..2ca61025 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 17f7dfdd..8c0fc659 100644 --- a/src/plugins/pinDms/components/CreateCategoryModal.tsx +++ b/src/plugins/pinDms/components/CreateCategoryModal.tsx @@ -6,7 +6,7 @@ import { classNameFactory } from "@api/Styles"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal"; -import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy } from "@webpack"; +import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; import { Button, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common"; import { DEFAULT_COLOR, SWATCHES } from "../constants"; @@ -30,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"/); diff --git a/src/plugins/pinDms/data.ts b/src/plugins/pinDms/data.ts index 2f4a1156..d689bd2a 100644 --- a/src/plugins/pinDms/data.ts +++ b/src/plugins/pinDms/data.ts @@ -155,7 +155,7 @@ export function moveChannel(channelId: string, direction: -1 | 1) { swapElementsInArray(category.channels, a, b); } -// TODO: Remove DataStore PinnedDms migration once enough time has passed +// 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; diff --git a/src/plugins/plainFolderIcon/index.ts b/src/plugins/plainFolderIcon/index.ts index 4c37e1e8..bb6876b5 100644 --- a/src/plugins/plainFolderIcon/index.ts +++ b/src/plugins/plainFolderIcon/index.ts @@ -25,9 +25,19 @@ export default definePlugin({ authors: [Devs.botato], patches: [{ find: ".expandedFolderIconWrapper", - replacement: [{ - match: /\(\w\|\|\w\)&&(\(.{0,40}\(.{1,3}\.animated)/, - replace: "$1", - }] + replacement: [ + // there are two elements, the first one is the plain folder icon + // the second is the four guild preview icons + // always show this one (the plain icons) + { + match: /\(\i\|\|\i\)&&(\(.{0,40}\(\i\.animated)/, + replace: "$1", + }, + // and never show this one (the guild preview icons) + { + match: /\(\i\|\|!\i\)&&(\(.{0,40}\(\i\.animated)/, + replace: "false&&$1", + } + ] }] }); diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index d2b722ef..7829295a 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -25,7 +25,7 @@ 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> @@ -188,7 +186,7 @@ const indicatorLocations = { description: "Inside messages", onEnable: () => addMessageDecoration("platform-indicator", props => <ErrorBoundary noop> - <PlatformIndicator user={props.message?.author} wantTopMargin={true} /> + <PlatformIndicator user={props.message?.author} /> </ErrorBoundary> ), onDisable: () => removeMessageDecoration("platform-indicator") diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx index 7b03e31d..ded408f9 100644 --- a/src/plugins/previewMessage/index.tsx +++ b/src/plugins/previewMessage/index.tsx @@ -105,8 +105,8 @@ const PreviewButton: ChatBarButtonFactory = ({ isMainChat, isEmpty, type: { atta <svg fill="currentColor" fillRule="evenodd" - width="24" - height="24" + width="20" + height="20" viewBox="0 0 24 24" style={{ scale: "1.096", translate: "0 -1px" }} > diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts index 4a7060c5..f6ca5b45 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 4cd81f2e..8d9789dd 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 43c81eb5..8b827174 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 71ac021f..1485022d 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 eeebd0aa..2b58d080 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 190b8f62..c62c300e 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 7b811943..ffa2b5a2 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 437df1a5..6bfac736 100644 --- a/src/plugins/sendTimestamps/index.tsx +++ b/src/plugins/sendTimestamps/index.tsx @@ -68,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)} @@ -86,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")}> @@ -141,8 +144,8 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => { <svg aria-hidden="true" role="img" - width="24" - height="24" + width="20" + height="20" viewBox="0 0 24 24" style={{ scale: "1.2" }} > diff --git a/src/plugins/sendTimestamps/styles.css b/src/plugins/sendTimestamps/styles.css index 033d5c9d..e7efbe59 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 be77ca1c..9f2d3008 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 8c88e4f4..274b7d13 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 8deca588..2794234d 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 dd140193..2d62af6e 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 8674147e..9d3a52c6 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: anywhere; width: 100%; } diff --git a/src/plugins/shikiCodeblocks.desktop/utils/misc.ts b/src/plugins/shikiCodeblocks.desktop/utils/misc.ts index e0c52634..1319a2f2 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 d748f326..0e64527e 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 46629c77..f99c0be9 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 cead5201..5bb16e0f 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 15cd17c4..bbe286af 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 92cd3b50..7a38bb12 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 @@ -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})` } }, { @@ -163,7 +173,7 @@ export default definePlugin({ replacement: [ // Make the channel appear as muted if it's hidden { - match: /{channel:(\i),name:\i,muted:(\i).+?;/, + match: /\.subtitle,.+?;(?=return\(0,\i\.jsxs?\))(?<={channel:(\i),name:\i,muted:(\i).+?;)/, replace: (m, channel, muted) => `${m}${muted}=$self.isHiddenChannel(${channel})?true:${muted};` }, // Add the hidden eye icon if the channel is hidden @@ -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};` } ] @@ -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 301ad75d..56e50a2f 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/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index bbafb58d..1f04f1f3 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -50,8 +50,8 @@ export default definePlugin({ { find: '?"@":""', replacement: { - match: /(?<=onContextMenu:\i,children:).*?\)}/, - replace: "$self.renderUsername(arguments[0])}" + match: /(?<=onContextMenu:\i,children:)\i\+\i/, + replace: "$self.renderUsername(arguments[0])" } }, ], diff --git a/src/plugins/showTimeoutDuration/index.tsx b/src/plugins/showTimeoutDuration/index.tsx index 1395cf80..2c7c8a85 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 00e60509..04ab8706 100644 --- a/src/plugins/silentMessageToggle/index.tsx +++ b/src/plugins/silentMessageToggle/index.tsx @@ -69,8 +69,8 @@ const SilentMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => { onClick={() => setEnabledValue(!enabled)} > <svg - width="24" - height="24" + width="20" + height="20" viewBox="0 0 24 24" style={{ scale: "1.2" }} > diff --git a/src/plugins/silentTyping/index.tsx b/src/plugins/silentTyping/index.tsx index 92bdbc49..e2d99ec0 100644 --- a/src/plugins/silentTyping/index.tsx +++ b/src/plugins/silentTyping/index.tsx @@ -54,7 +54,7 @@ const SilentTypingToggle: ChatBarButtonFactory = ({ isMainChat }) => { tooltip={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"} onClick={toggle} > - <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ scale: "1.2" }}> + <svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ scale: "1.2" }}> <path fill="currentColor" mask="url(#silent-typing-msg-mask)" d="M18.333 15.556H1.667a1.667 1.667 0 0 1 -1.667 -1.667v-10a1.667 1.667 0 0 1 1.667 -1.667h16.667a1.667 1.667 0 0 1 1.667 1.667v10a1.667 1.667 0 0 1 -1.667 1.667M4.444 6.25V4.861a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333V8.194a0.417 0.417 0 0 0 -0.417 -0.417H4.306a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417H7.639a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m10 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h8.056a0.417 0.417 0 0 0 0.417 -0.417m3.333 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417" transform="translate(2, 3)" /> {isEnabled && ( <> diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index d708c279..4184931f 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -17,8 +17,10 @@ */ import "./spotifyStyles.css"; +import "./visualRefreshSpotifyStyles.css"; // TODO: merge with spotifyStyles.css and remove when old UI is discontinued 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 +30,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 +42,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 +128,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 +145,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 +287,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")}> @@ -305,8 +308,8 @@ function Info({ track }: { track: Track; }) { {track.name} </Forms.FormText> {track.artists.some(a => a.name) && ( - <Forms.FormText variant="text-sm/normal" className={cl("ellipoverflow")}> - by  + <Forms.FormText variant="text-sm/normal" className={cl(["ellipoverflow", "secondary-song-info"])}> + <span className={cl("song-info-prefix")}>by </span> {track.artists.map((a, i) => ( <React.Fragment key={a.name}> <span @@ -323,8 +326,8 @@ function Info({ track }: { track: Track; }) { </Forms.FormText> )} {track.album.name && ( - <Forms.FormText variant="text-sm/normal" className={cl("ellipoverflow")}> - on  + <Forms.FormText variant="text-sm/normal" className={cl(["ellipoverflow", "secondary-song-info"])}> + <span className={cl("song-info-prefix")}>on </span> <span id={cl("album-title")} className={cl("album")} diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts index 5c0c5fe4..65ee2c15 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/index.tsx b/src/plugins/spotifyControls/index.tsx index b4f8e904..c7e8c91f 100644 --- a/src/plugins/spotifyControls/index.tsx +++ b/src/plugins/spotifyControls/index.tsx @@ -32,7 +32,7 @@ function toggleHoverControls(value: boolean) { export default definePlugin({ name: "SpotifyControls", description: "Adds a Spotify player above the account panel", - authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000], + authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000, Devs.nin0dev], options: { hoverControls: { description: "Show controls on hover", diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css index 893dc817..0b760377 100644 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ b/src/plugins/spotifyControls/spotifyStyles.css @@ -2,7 +2,9 @@ padding: 0.375rem 0.5rem; border-bottom: 1px solid var(--background-modifier-accent); - --vc-spotify-green: #1db954; /* so custom themes can easily change it */ + --vc-spotify-green: var(--spotify, #1db954); /* so custom themes can easily change it */ + --vc-spotify-green-90: color-mix(in hsl, var(--vc-spotify-green), transparent 90%); + --vc-spotify-green-80: color-mix(in hsl, var(--vc-spotify-green), transparent 80%); } .theme-light #vc-spotify-player { @@ -30,22 +32,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 +84,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 +141,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/spotifyControls/visualRefreshSpotifyStyles.css b/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css new file mode 100644 index 00000000..3a140a17 --- /dev/null +++ b/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css @@ -0,0 +1,77 @@ +/* TODO: merge with spotifyStyles.css and remove when old UI is discontinued */ +.visual-refresh { + #vc-spotify-player { + padding: 12px; + background: var(--bg-overlay-floating, var(--background-base-low, var(--background-secondary-alt))); + margin: 0; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + } + + .vc-spotify-song-info-prefix { + display: none; + } + + .vc-spotify-artist, .vc-spotify-album { + color: var(--header-primary); + } + + .vc-spotify-secondary-song-info { + font-size: 12px; + } + + #vc-spotify-progress-bar { + position: relative; + color: var(--text-normal); + width: 100%; + } + + #vc-spotify-progress-bar > [class^="slider"] { + flex-grow: 1; + width: 100%; + padding: 0 !important; + } + + #vc-spotify-progress-bar > [class^="slider"] [class^="bar"] { + height: 3px !important; + top: calc(12px - 4px / 2 + var(--bar-offset)); + } + + #vc-spotify-progress-bar > [class^="slider"] [class^="barFill"] { + background-color: var(--interactive-active); + } + + #vc-spotify-progress-bar > [class^="slider"]:hover [class^="barFill"] { + background-color: var(--vc-spotify-green); + } + + #vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] { + background-color: var(--interactive-active); + width: 16px !important; + height: 16px !important; + margin-top: calc(17px/-2 + var(--bar-offset)/2); + margin-left: -0.5px; + } + + .vc-spotify-progress-time { + margin-top: 8px; + font-family: var(--font-code); + } + + .vc-spotify-button-row { + margin-top: 14px; + } + + .vc-spotify-button { + margin: 0 2px; + border-radius: var(--radius-sm); + } + + .vc-spotify-repeat-context, .vc-spotify-repeat-track, .vc-spotify-shuffle-on { + background-color: var(--vc-spotify-green-90); + } + + .vc-spotify-repeat-context:hover, .vc-spotify-repeat-track:hover, .vc-spotify-shuffle-on:hover { + background-color: var(--vc-spotify-green-80); + } +} diff --git a/src/plugins/spotifyShareCommands/index.ts b/src/plugins/spotifyShareCommands/index.ts index 8c485666..ed793963 100644 --- a/src/plugins/spotifyShareCommands/index.ts +++ b/src/plugins/spotifyShareCommands/index.ts @@ -16,8 +16,9 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands"; +import { ApplicationCommandInputType, Command, findOption, OptionalMessageOption, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; +import { sendMessage } from "@utils/discord"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { FluxDispatcher, MessageActions } from "@webpack/common"; @@ -55,21 +56,36 @@ interface Track { const Spotify = findByPropsLazy("getPlayerState"); const PendingReplyStore = findByPropsLazy("getPendingReply"); -function sendMessage(channelId, message) { - message = { - // The following are required to prevent Discord from throwing an error - invalidEmojis: [], - tts: false, - validNonShortcutEmojis: [], - ...message - }; - const reply = PendingReplyStore.getPendingReply(channelId); - MessageActions.sendMessage(channelId, message, void 0, MessageActions.getSendMessageOptionsForReply(reply)) - .then(() => { - if (reply) { - FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId }); +function makeCommand(name: string, formatUrl: (track: Track) => string): Command { + return { + name, + description: `Share your current Spotify ${name} in chat`, + inputType: ApplicationCommandInputType.BUILT_IN, + options: [OptionalMessageOption], + execute(options, { channel }) { + const track: Track | null = Spotify.getTrack(); + if (!track) { + return sendBotMessage(channel.id, { + content: "You're not listening to any music." + }); } - }); + + const data = formatUrl(track); + const message = findOption(options, "message"); + + // Note: Due to how Discord handles commands, we need to manually create and send the message + + sendMessage( + channel.id, + { content: message ? `${message} ${data}` : data }, + false, + MessageActions.getSendMessageOptionsForReply(PendingReplyStore.getPendingReply(channel.id)) + ).then(() => { + FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId: channel.id }); + }); + + } + }; } export default definePlugin({ @@ -77,60 +93,8 @@ export default definePlugin({ description: "Share your current Spotify track, album or artist via slash command (/track, /album, /artist)", authors: [Devs.katlyn], commands: [ - { - name: "track", - description: "Send your current Spotify track to chat", - inputType: ApplicationCommandInputType.BUILT_IN, - options: [], - execute: (_, ctx) => { - const track: Track | null = Spotify.getTrack(); - if (track === null) { - sendBotMessage(ctx.channel.id, { - content: "You're not listening to any music." - }); - return; - } - // Note: Due to how Discord handles commands, we need to manually create and send the message - sendMessage(ctx.channel.id, { - content: `https://open.spotify.com/track/${track.id}` - }); - } - }, - { - name: "album", - description: "Send your current Spotify album to chat", - inputType: ApplicationCommandInputType.BUILT_IN, - options: [], - execute: (_, ctx) => { - const track: Track | null = Spotify.getTrack(); - if (track === null) { - sendBotMessage(ctx.channel.id, { - content: "You're not listening to any music." - }); - return; - } - sendMessage(ctx.channel.id, { - content: `https://open.spotify.com/album/${track.album.id}` - }); - } - }, - { - name: "artist", - description: "Send your current Spotify artist to chat", - inputType: ApplicationCommandInputType.BUILT_IN, - options: [], - execute: (_, ctx) => { - const track: Track | null = Spotify.getTrack(); - if (track === null) { - sendBotMessage(ctx.channel.id, { - content: "You're not listening to any music." - }); - return; - } - sendMessage(ctx.channel.id, { - content: track.artists[0].external_urls.spotify - }); - } - } + makeCommand("track", track => `https://open.spotify.com/track/${track.id}`), + makeCommand("album", track => `https://open.spotify.com/album/${track.album.id}`), + makeCommand("artist", track => track.artists[0].external_urls.spotify) ] }); diff --git a/src/plugins/startupTimings/index.tsx b/src/plugins/startupTimings/index.tsx index aabc786a..ac7f3f0c 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 3d1e891d..d5d6f4dc 100644 --- a/src/plugins/textReplace/index.tsx +++ b/src/plugins/textReplace/index.tsx @@ -45,7 +45,6 @@ const makeEmptyRuleArray = () => [makeEmptyRule()]; const settings = definePluginSettings({ replace: { type: OptionType.COMPONENT, - description: "", component: () => { const { stringRules, regexRules } = settings.use(["stringRules", "regexRules"]); @@ -245,7 +244,7 @@ export default definePlugin({ }, async start() { - // TODO: Remove DataStore rules migrations once enough time has passed + // 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; diff --git a/src/plugins/themeAttributes/index.ts b/src/plugins/themeAttributes/index.ts index 8e1e022b..7d904e7e 100644 --- a/src/plugins/themeAttributes/index.ts +++ b/src/plugins/themeAttributes/index.ts @@ -54,8 +54,8 @@ export default definePlugin({ } ], - getAvatarStyles(src: string) { - if (src.startsWith("data:")) return {}; + getAvatarStyles(src: string | null) { + if (!src || src.startsWith("data:")) return {}; return Object.fromEntries( [128, 256, 512, 1024, 2048, 4096].map(size => [ diff --git a/src/plugins/translate/TranslateIcon.tsx b/src/plugins/translate/TranslateIcon.tsx index 1b77fb94..bc0e335e 100644 --- a/src/plugins/translate/TranslateIcon.tsx +++ b/src/plugins/translate/TranslateIcon.tsx @@ -25,7 +25,7 @@ import { settings } from "./settings"; import { TranslateModal } from "./TranslateModal"; import { cl } from "./utils"; -export function TranslateIcon({ height = 24, width = 24, className }: { height?: number; width?: number; className?: string; }) { +export function TranslateIcon({ height = 20, width = 20, className }: { height?: number; width?: number; className?: string; }) { return ( <svg viewBox="0 96 960 960" diff --git a/src/plugins/translate/TranslateModal.tsx b/src/plugins/translate/TranslateModal.tsx index 7a32d1b7..786f8105 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 8e8f4c17..9b6393cc 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/styles.css b/src/plugins/translate/styles.css index 64b6c9b9..c07c9e36 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 e6a1b3b4..e6903bcd 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/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index ff68a486..22013992 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -25,6 +25,8 @@ import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/com import { User } from "discord-types/general"; import { PropsWithChildren } from "react"; +import managedStyle from "./style.css?managed"; + const settings = definePluginSettings({ showAvatars: { type: OptionType.BOOLEAN, @@ -60,24 +62,19 @@ interface Props { const TypingUser = ErrorBoundary.wrap(function ({ user, guildId }: Props) { return ( <strong + className="vc-typing-user" role="button" onClick={() => { openUserProfile(user.id); }} style={{ - display: "grid", - gridAutoFlow: "column", - gap: "4px", color: settings.store.showRoleColors ? GuildMemberStore.getMember(guildId, user.id)?.colorString : undefined, - cursor: "pointer" }} > {settings.store.showAvatars && ( - <div style={{ marginTop: "4px" }}> - <Avatar - size="SIZE_16" - src={user.getAvatarURL(guildId, 128)} /> - </div> + <Avatar + size="SIZE_16" + src={user.getAvatarURL(guildId, 128)} /> )} {GuildMemberStore.getNick(guildId!, user.id) || (!guildId && RelationshipStore.getNickname(user.id)) @@ -94,6 +91,8 @@ export default definePlugin({ authors: [Devs.zt], settings, + managedStyle, + patches: [ { find: "#{intl::THREE_USERS_TYPING}", @@ -101,7 +100,7 @@ export default definePlugin({ { // Style the indicator and add function call to modify the children before rendering match: /(?<=children:\[(\i)\.length>0.{0,200}?"aria-atomic":!0,children:)\i(?<=guildId:(\i).+?)/, - replace: "$self.renderTypingUsers({ users: $1, guildId: $2, children: $& }),style:$self.TYPING_TEXT_STYLE" + replace: "$self.renderTypingUsers({ users: $1, guildId: $2, children: $& })" }, { // Changes the indicator to keep the user object when creating the list of typing users @@ -118,12 +117,6 @@ export default definePlugin({ } ], - TYPING_TEXT_STYLE: { - display: "grid", - gridAutoFlow: "column", - gridGap: "0.25em" - }, - buildSeveralUsers, renderTypingUsers: ErrorBoundary.wrap(({ guildId, users, children }: PropsWithChildren<{ guildId: string, users: User[]; }>) => { diff --git a/src/plugins/typingTweaks/style.css b/src/plugins/typingTweaks/style.css new file mode 100644 index 00000000..28d0042a --- /dev/null +++ b/src/plugins/typingTweaks/style.css @@ -0,0 +1,5 @@ +.vc-typing-user [class^="wrapper"] { + display: inline-block; + margin-right: 0.25em; + vertical-align: -4px; +} diff --git a/src/plugins/unsuppressEmbeds/index.tsx b/src/plugins/unsuppressEmbeds/index.tsx index 16debf71..2df64b72 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 27b162b9..1699251e 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 e8924f82..9029cdc5 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 f3063f59..3d119c43 100644 --- a/src/plugins/userVoiceShow/index.tsx +++ b/src/plugins/userVoiceShow/index.tsx @@ -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 }, @@ -99,7 +99,7 @@ export default definePlugin({ addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />); } if (settings.store.showInMessages) { - addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />); + addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} />); } }, diff --git a/src/plugins/userVoiceShow/style.css b/src/plugins/userVoiceShow/style.css index d172975b..f9fd56fb 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 69c5b185..00000000 --- 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 788a79ae..df823102 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/vcNarrator/VoiceSetting.tsx b/src/plugins/vcNarrator/VoiceSetting.tsx new file mode 100644 index 00000000..fb5c739c --- /dev/null +++ b/src/plugins/vcNarrator/VoiceSetting.tsx @@ -0,0 +1,126 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Forms, SearchableSelect, useMemo, useState } from "@webpack/common"; + +import { getCurrentVoice, settings } from "./settings"; + +// TODO: replace by [Object.groupBy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy) once it has more maturity + +function groupBy<T extends object, K extends PropertyKey>(arr: T[], fn: (obj: T) => K) { + return arr.reduce((acc, obj) => { + const value = fn(obj); + acc[value] ??= []; + acc[value].push(obj); + return acc; + }, {} as Record<K, T[]>); +} + +interface PickerProps { + voice: string | undefined; + voices: SpeechSynthesisVoice[]; +} + +function SimplePicker({ voice, voices }: PickerProps) { + const options = voices.map(voice => ({ + label: voice.name, + value: voice.voiceURI, + default: voice.default, + })); + + return ( + <SearchableSelect + placeholder="Select a voice" + maxVisibleItems={5} + options={options} + value={options.find(o => o.value === voice)} + onChange={v => settings.store.voice = v} + closeOnSelect + /> + ); +} + +const languageNames = new Intl.DisplayNames(["en"], { type: "language" }); + +function ComplexPicker({ voice, voices }: PickerProps) { + const groupedVoices = useMemo(() => groupBy(voices, voice => voice.lang), [voices]); + + const languageNameMapping = useMemo(() => { + const list = [] as Record<"name" | "friendlyName", string>[]; + + for (const name in groupedVoices) { + try { + const friendlyName = languageNames.of(name); + if (friendlyName) { + list.push({ name, friendlyName }); + } + } catch { } + } + + return list; + }, [groupedVoices]); + + const [selectedLanguage, setSelectedLanguage] = useState(() => getCurrentVoice()?.lang ?? languageNameMapping[0].name); + + if (languageNameMapping.length === 1) { + return ( + <SimplePicker + voice={voice} + voices={groupedVoices[languageNameMapping[0].name]} + /> + ); + } + + const voicesForLanguage = groupedVoices[selectedLanguage]; + + const languageOptions = languageNameMapping.map(l => ({ + label: l.friendlyName, + value: l.name + })); + + return ( + <> + <Forms.FormTitle>Language</Forms.FormTitle> + <SearchableSelect + placeholder="Select a language" + options={languageOptions} + value={languageOptions.find(l => l.value === selectedLanguage)} + onChange={v => setSelectedLanguage(v)} + maxVisibleItems={5} + closeOnSelect + /> + <Forms.FormTitle>Voice</Forms.FormTitle> + <SimplePicker + voice={voice} + voices={voicesForLanguage} + /> + </> + ); +} + + +function VoiceSetting() { + const voices = useMemo(() => window.speechSynthesis?.getVoices() ?? [], []); + const { voice } = settings.use(["voice"]); + + if (!voices.length) + return <Forms.FormText>No voices found.</Forms.FormText>; + + // espeak on Linux has a ridiculous amount of voices (26k for me). + // If there are more than 20 voices, we split it up into two pickers, one for language, then one with only the voices for that language. + // This way, there are around 200-ish options per language + const Picker = voices.length > 20 ? ComplexPicker : SimplePicker; + return <Picker voice={voice} voices={voices} />; +} + +export function VoiceSettingSection() { + return ( + <Forms.FormSection> + <Forms.FormTitle>Voice</Forms.FormTitle> + <VoiceSetting /> + </Forms.FormSection> + ); +} diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx index 95be33be..02eb216c 100644 --- a/src/plugins/vcNarrator/index.tsx +++ b/src/plugins/vcNarrator/index.tsx @@ -16,17 +16,18 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Settings } from "@api/Settings"; import { ErrorCard } from "@components/ErrorCard"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { wordsToTitle } from "@utils/text"; -import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types"; +import definePlugin, { ReporterTestable } from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common"; import { ReactElement } from "react"; +import { getCurrentVoice, settings } from "./settings"; + interface VoiceState { userId: string; channelId?: string; @@ -43,25 +44,19 @@ const VoiceStateStore = findByPropsLazy("getVoiceStatesForChannel", "getCurrentC // Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would // not say the second mute, which would lead you to believe they're unmuted -function speak(text: string, settings: any = Settings.plugins.VcNarrator) { +function speak(text: string, { volume, rate } = settings.store) { if (!text) return; const speech = new SpeechSynthesisUtterance(text); - let voice = speechSynthesis.getVoices().find(v => v.voiceURI === settings.voice); - if (!voice) { - new Logger("VcNarrator").error(`Voice "${settings.voice}" not found. Resetting to default.`); - voice = speechSynthesis.getVoices().find(v => v.default); - settings.voice = voice?.voiceURI; - if (!voice) return; // This should never happen - } + const voice = getCurrentVoice(); speech.voice = voice!; - speech.volume = settings.volume; - speech.rate = settings.rate; + speech.volume = volume; + speech.rate = rate; speechSynthesis.speak(speech); } function clean(str: string) { - const replacer = Settings.plugins.VcNarrator.latinOnly + const replacer = settings.store.latinOnly ? /[^\p{Script=Latin}\p{Number}\p{Punctuation}\s]/gu : /[^\p{Letter}\p{Number}\p{Punctuation}\s]/gu; @@ -145,11 +140,11 @@ function updateStatuses(type: string, { deaf, mute, selfDeaf, selfMute, userId, */ function playSample(tempSettings: any, type: string) { - const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings); + const s = Object.assign({}, settings.plain, tempSettings); const currentUser = UserStore.getCurrentUser(); const myGuildId = SelectedGuildStore.getGuildId(); - speak(formatText(settings[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username, GuildMemberStore.getNick(myGuildId, currentUser.id) ?? currentUser.username), settings); + speak(formatText(s[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username, GuildMemberStore.getNick(myGuildId, currentUser.id) ?? currentUser.username), s); } export default definePlugin({ @@ -158,6 +153,8 @@ export default definePlugin({ authors: [Devs.Ven], reporterTestable: ReporterTestable.None, + settings, + flux: { VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) { const myGuildId = SelectedGuildStore.getGuildId(); @@ -177,8 +174,8 @@ export default definePlugin({ const [type, id] = getTypeAndChannelId(state, isMe); if (!type) continue; - const template = Settings.plugins.VcNarrator[type + "Message"]; - const user = isMe && !Settings.plugins.VcNarrator.sayOwnName ? "" : UserStore.getUser(userId).username; + const template = settings.store[type + "Message"]; + const user = isMe && !settings.store.sayOwnName ? "" : UserStore.getUser(userId).username; const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user); const nickname = user && (GuildMemberStore.getNick(myGuildId, userId) ?? user); const channel = ChannelStore.getChannel(id).name; @@ -195,7 +192,7 @@ export default definePlugin({ if (!s) return; const event = s.mute || s.selfMute ? "unmute" : "mute"; - speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", "")); + speak(formatText(settings.store[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", "")); }, AUDIO_TOGGLE_SELF_DEAF() { @@ -204,7 +201,7 @@ export default definePlugin({ if (!s) return; const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen"; - speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", "")); + speak(formatText(settings.store[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", "")); } }, @@ -218,81 +215,6 @@ export default definePlugin({ }, - optionsCache: null as Record<string, PluginOptionsItem> | null, - - get options() { - return this.optionsCache ??= { - voice: { - type: OptionType.SELECT, - description: "Narrator Voice", - options: window.speechSynthesis?.getVoices().map(v => ({ - label: v.name, - value: v.voiceURI, - default: v.default - })) ?? [] - }, - volume: { - type: OptionType.SLIDER, - description: "Narrator Volume", - default: 1, - markers: [0, 0.25, 0.5, 0.75, 1], - stickToMarkers: false - }, - rate: { - type: OptionType.SLIDER, - description: "Narrator Speed", - default: 1, - markers: [0.1, 0.5, 1, 2, 5, 10], - stickToMarkers: false - }, - sayOwnName: { - description: "Say own name", - type: OptionType.BOOLEAN, - default: false - }, - latinOnly: { - description: "Strip non latin characters from names before saying them", - type: OptionType.BOOLEAN, - default: false - }, - joinMessage: { - type: OptionType.STRING, - description: "Join Message", - default: "{{USER}} joined" - }, - leaveMessage: { - type: OptionType.STRING, - description: "Leave Message", - default: "{{USER}} left" - }, - moveMessage: { - type: OptionType.STRING, - description: "Move Message", - default: "{{USER}} moved to {{CHANNEL}}" - }, - muteMessage: { - type: OptionType.STRING, - description: "Mute Message (only self for now)", - default: "{{USER}} Muted" - }, - unmuteMessage: { - type: OptionType.STRING, - description: "Unmute Message (only self for now)", - default: "{{USER}} unmuted" - }, - deafenMessage: { - type: OptionType.STRING, - description: "Deafen Message (only self for now)", - default: "{{USER}} deafened" - }, - undeafenMessage: { - type: OptionType.STRING, - description: "Undeafen Message (only self for now)", - default: "{{USER}} undeafened" - } - } satisfies Record<string, PluginOptionsItem>; - }, - settingsAboutComponent({ tempSettings: s }) { const [hasVoices, hasEnglishVoices] = useMemo(() => { const voices = speechSynthesis.getVoices(); @@ -300,7 +222,7 @@ export default definePlugin({ }, []); const types = useMemo( - () => Object.keys(Vencord.Plugins.plugins.VcNarrator.options!).filter(k => k.endsWith("Message")).map(k => k.slice(0, -7)), + () => Object.keys(settings.def).filter(k => k.endsWith("Message")).map(k => k.slice(0, -7)), [], ); diff --git a/src/plugins/vcNarrator/settings.ts b/src/plugins/vcNarrator/settings.ts new file mode 100644 index 00000000..ee3aee0e --- /dev/null +++ b/src/plugins/vcNarrator/settings.ts @@ -0,0 +1,97 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Logger } from "@utils/Logger"; +import { OptionType } from "@utils/types"; + +import { VoiceSettingSection } from "./VoiceSetting"; + +export const getDefaultVoice = () => window.speechSynthesis?.getVoices().find(v => v.default); + +export function getCurrentVoice(voices = window.speechSynthesis?.getVoices()) { + if (!voices) return undefined; + + if (settings.store.voice) { + const voice = voices.find(v => v.voiceURI === settings.store.voice); + if (voice) return voice; + + new Logger("VcNarrator").error(`Voice "${settings.store.voice}" not found. Resetting to default.`); + } + + const voice = voices.find(v => v.default); + settings.store.voice = voice?.voiceURI; + return voice; +} + +export const settings = definePluginSettings({ + voice: { + type: OptionType.COMPONENT, + component: VoiceSettingSection, + get default() { + return getDefaultVoice()?.voiceURI; + } + }, + volume: { + type: OptionType.SLIDER, + description: "Narrator Volume", + default: 1, + markers: [0, 0.25, 0.5, 0.75, 1], + stickToMarkers: false + }, + rate: { + type: OptionType.SLIDER, + description: "Narrator Speed", + default: 1, + markers: [0.1, 0.5, 1, 2, 5, 10], + stickToMarkers: false + }, + sayOwnName: { + description: "Say own name", + type: OptionType.BOOLEAN, + default: false + }, + latinOnly: { + description: "Strip non latin characters from names before saying them", + type: OptionType.BOOLEAN, + default: false + }, + joinMessage: { + type: OptionType.STRING, + description: "Join Message", + default: "{{USER}} joined" + }, + leaveMessage: { + type: OptionType.STRING, + description: "Leave Message", + default: "{{USER}} left" + }, + moveMessage: { + type: OptionType.STRING, + description: "Move Message", + default: "{{USER}} moved to {{CHANNEL}}" + }, + muteMessage: { + type: OptionType.STRING, + description: "Mute Message (only self for now)", + default: "{{USER}} muted" + }, + unmuteMessage: { + type: OptionType.STRING, + description: "Unmute Message (only self for now)", + default: "{{USER}} unmuted" + }, + deafenMessage: { + type: OptionType.STRING, + description: "Deafen Message (only self for now)", + default: "{{USER}} deafened" + }, + undeafenMessage: { + type: OptionType.STRING, + description: "Undeafen Message (only self for now)", + default: "{{USER}} undeafened" + } +}); diff --git a/src/plugins/vencordToolbox/index.css b/src/plugins/vencordToolbox/index.css index a1c85ad5..642375b4 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 00805fbd..c59df00a 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 c53116b4..afd9d48c 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 b45919a2..ddcbd3b4 100644 --- a/src/plugins/viewRaw/index.tsx +++ b/src/plugins/viewRaw/index.tsx @@ -22,12 +22,12 @@ 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"; @@ -118,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; @@ -144,6 +144,23 @@ 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", @@ -152,10 +169,12 @@ export default definePlugin({ 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 }, renderMessagePopoverButton(msg) { diff --git a/src/plugins/voiceMessages/styles.css b/src/plugins/voiceMessages/styles.css index 1e2b1433..4f2e1d57 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/volumeBooster/index.ts b/src/plugins/volumeBooster/index.ts index c9a08bbc..62d2a2c1 100644 --- a/src/plugins/volumeBooster/index.ts +++ b/src/plugins/volumeBooster/index.ts @@ -136,7 +136,7 @@ export default definePlugin({ // @ts-expect-error if (data.sinkId != null && data.sinkId !== data.audioContext.sinkId && "setSinkId" in AudioContext.prototype) { // @ts-expect-error https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/setSinkId - data.audioContext.setSinkId(data.sinkId); + data.audioContext.setSinkId(data.sinkId === "default" ? "" : data.sinkId); } data.gainNode.gain.value = data._mute diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 66123805..07eb4a3e 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); @@ -39,13 +42,16 @@ const settings = definePluginSettings({ addBack: { type: OptionType.BOOLEAN, description: "Add back the Discord context menus for images, links and the chat input bar", + default: false, + restartNeeded: true, // Web slate menu has proper spellcheck suggestions and image context menu is also pretty good, - // so disable this by default. Vesktop just doesn't, so enable by default - default: IS_VESKTOP, - restartNeeded: true + // so disable this by default. Vesktop just doesn't, so we force enable it there + hidden: IS_VESKTOP, } }); +const shouldAddBackMenus = () => IS_VESKTOP || settings.store.addBack; + const MEDIA_PROXY_URL = "https://media.discordapp.net"; const CDN_URL = "cdn.discordapp.com"; @@ -78,7 +84,7 @@ export default definePlugin({ settings, start() { - if (settings.store.addBack) { + if (shouldAddBackMenus()) { window.removeEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackWeb); window.addEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackNative); this.changedListeners = true; @@ -141,7 +147,7 @@ export default definePlugin({ { find: 'navId:"image-context"', all: true, - predicate: () => settings.store.addBack, + predicate: shouldAddBackMenus, replacement: { // return IS_DESKTOP ? React.createElement(Menu, ...) match: /return \i\.\i(?=\?|&&)/, @@ -152,7 +158,7 @@ export default definePlugin({ // Add back link context menu { find: '"interactionUsernameProfile"', - predicate: () => settings.store.addBack, + predicate: shouldAddBackMenus, replacement: { match: /if\((?="A"===\i\.tagName&&""!==\i\.textContent)/, replace: "if(false&&" @@ -162,7 +168,7 @@ export default definePlugin({ // Add back slate / text input context menu { find: 'getElementById("slate-toolbar"', - predicate: () => settings.store.addBack, + predicate: shouldAddBackMenus, replacement: { match: /(?<=handleContextMenu\(\i\)\{.{0,200}isPlatformEmbedded)\)/, replace: "||true)" @@ -170,7 +176,7 @@ export default definePlugin({ }, { find: ".SLASH_COMMAND_SUGGESTIONS_TOGGLED,{", - predicate: () => settings.store.addBack, + predicate: shouldAddBackMenus, replacement: [ { // if (!IS_DESKTOP) return null; @@ -186,7 +192,7 @@ export default definePlugin({ }, { find: '"add-to-dictionary"', - predicate: () => settings.store.addBack, + predicate: shouldAddBackMenus, replacement: { match: /let\{text:\i=""/, replace: "return [null,null];$&" diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index 803cc715..aea57fef 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 8cc6a323..ae05d646 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 25dd05b1..0b6aa25b 100644 --- a/src/shared/SettingsStore.ts +++ b/src/shared/SettingsStore.ts @@ -167,6 +167,8 @@ export class SettingsStore<T extends object> { 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 22a38136..aec7292a 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 e7582591..65c73bd8 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -16,9 +16,15 @@ * 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 SUPPORT_CATEGORY_ID = "1108135649699180705"; +export const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920"; export interface Dev { name: string; @@ -579,6 +585,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/dependencies.ts b/src/utils/dependencies.ts index d8c361e8..6db180a4 100644 --- a/src/utils/dependencies.ts +++ b/src/utils/dependencies.ts @@ -69,8 +69,8 @@ export interface ApngFrameData { // The below code is only used on the Desktop (electron) build of Vencord. // Browser (extension) builds do not contain these remote imports. -export const shikiWorkerSrc = `https://unpkg.com/@vap/shiki-worker@0.0.8/dist/${IS_DEV ? "index.js" : "index.min.js"}`; -export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm"; +export const shikiWorkerSrc = `https://cdn.jsdelivr.net/npm/@vap/shiki-worker@0.0.8/dist/${IS_DEV ? "index.js" : "index.min.js"}`; +export const shikiOnigasmSrc = "https://cdn.jsdelivr.net/npm/@vap/shiki@0.10.3/dist/onig.wasm"; // @ts-expect-error -export const getStegCloak = /* #__PURE__*/ makeLazy(() => import("https://unpkg.com/stegcloak-dist@1.0.0/index.js")); +export const getStegCloak = /* #__PURE__*/ makeLazy(() => import("https://cdn.jsdelivr.net/npm/stegcloak-dist@1.0.0/index.js")); diff --git a/src/utils/discord.css b/src/utils/discord.css index 12d15694..746fb564 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 e46e44ad..526c5514 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 28c371c5..adca15d3 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 83b2f055..d06e5803 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 097c6456..b212e624 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/quickCss.ts b/src/utils/quickCss.ts index 6a18948d..c1e11759 100644 --- a/src/utils/quickCss.ts +++ b/src/utils/quickCss.ts @@ -97,7 +97,14 @@ document.addEventListener("DOMContentLoaded", () => { SettingsStore.addChangeListener("themeLinks", initThemes); SettingsStore.addChangeListener("enabledThemes", initThemes); - ThemeStore.addChangeListener(initThemes); + + let currentTheme = ThemeStore.theme; + ThemeStore.addChangeListener(() => { + if (currentTheme === ThemeStore.theme) return; + + currentTheme = ThemeStore.theme; + initThemes(); + }); if (!IS_WEB) VencordNative.quickCss.addThemeChangeListener(initThemes); diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts index f19928ac..6ec3e527 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 54de59e3..3ad63ea1 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -26,7 +26,7 @@ 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 { ReactNode } from "react"; import { Promisable } from "type-fest"; // exists to export default definePlugin({...}) @@ -41,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 { @@ -57,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 { @@ -150,6 +166,11 @@ export interface PluginDef { tags?: string[]; + /** + * Managed style to automatically enable and disable when the plugin is enabled or disabled + */ + managedStyle?: string; + userProfileBadge?: ProfileBadge; onMessageClick?: MessageClickListener; @@ -181,6 +202,10 @@ export const enum ReporterTestable { FluxEvents = 1 << 4 } +export function defineDefault<T = any>(value: T) { + return value; +} + export const enum OptionType { STRING, NUMBER, @@ -198,15 +223,16 @@ export type SettingsChecks<D extends SettingsDefinition> = { (IsDisabled<DefinedSettings<D>> & IsValid<PluginSettingType<D[K]>, DefinedSettings<D>>); }; -export type PluginSettingDef = (PluginSettingCustomDef & Pick<PluginSettingCommon, "onChange">) | (( - | 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; @@ -226,12 +252,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 @@ -310,7 +338,8 @@ export interface IPluginOptionComponentProps { export interface PluginSettingComponentDef { type: OptionType.COMPONENT; - component: (props: IPluginOptionComponentProps) => JSX.Element; + component: (props: IPluginOptionComponentProps) => ReactNode | Promise<ReactNode>; + default?: any; } /** Maps a `PluginSettingDef` to its value type */ @@ -320,7 +349,7 @@ 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; @@ -382,7 +411,7 @@ export type PluginOptionNumber = (PluginSettingNumberDef | PluginSettingBigIntDe 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>> = { diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index 11526c0f..a772c98d 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -16,76 +16,88 @@ * 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")); +export const Checkbox = waitForComponent<t.Checkbox>("Checkbox", filters.componentByCode(".checkboxWrapperDisabled:")); + +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('document.getElementById("app-mount"))', { + 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 d528390e..9896b3a2 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 8579f8b9..518f13e2 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 f6a6c4ad..2b8ee92a 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 { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; +import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, 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>; @@ -197,6 +197,36 @@ export type Switch = ComponentType<PropsWithChildren<{ tooltipNote?: ReactNode; }>>; +export type CheckboxAligns = { + CENTER: "center"; + TOP: "top"; +}; + +export type CheckboxTypes = { + DEFAULT: "default"; + INVERTED: "inverted"; + GHOST: "ghost"; + ROW: "row"; +}; + +export type Checkbox = ComponentType<PropsWithChildren<{ + value: boolean; + onChange(event: PointerEvent, value: boolean): void; + + align?: "center" | "top"; + disabled?: boolean; + displayOnly?: boolean; + readOnly?: boolean; + reverse?: boolean; + shape?: string; + size?: number; + type?: "default" | "inverted" | "ghost" | "row"; +}>> & { + Shapes: Record<"BOX" | "ROUND" | "SMALL_BOX", string>; + Aligns: CheckboxAligns; + Types: CheckboxTypes; +}; + export type Timestamp = ComponentType<PropsWithChildren<{ timestamp: Date; isEdited?: boolean; @@ -215,7 +245,8 @@ export type TextInput = ComponentType<PropsWithChildren<{ onChange?(value: string, name?: string): void; placeholder?: string; editable?: boolean; - maxLength?: number; + /** defaults to 999. Pass null to disable this default */ + maxLength?: number | null; error?: string; inputClassName?: string; @@ -227,13 +258,13 @@ export type TextInput = ComponentType<PropsWithChildren<{ /** TextInput.Sizes.DEFAULT */ size?: string; -} & Omit<HTMLProps<HTMLInputElement>, "onChange">>> & { +} & Omit<HTMLProps<HTMLInputElement>, "onChange" | "maxLength">>> & { Sizes: Record<"DEFAULT" | "MINI", string>; }; -export type TextArea = ComponentType<PropsWithRef<Omit<HTMLProps<HTMLTextAreaElement>, "onChange"> & { +export type TextArea = ComponentType<Omit<HTMLProps<HTMLTextAreaElement>, "onChange"> & { onChange(v: string): void; -}>>; +}>; interface SelectOption { disabled?: boolean; @@ -496,7 +527,7 @@ export type Avatar = ComponentType<PropsWithChildren<{ }>>; type FocusLock = ComponentType<PropsWithChildren<{ - containerRef: RefObject<HTMLElement>; + containerRef: Ref<HTMLElement>; }>>; export type Icon = ComponentType<JSX.IntrinsicElements["svg"] & { @@ -504,4 +535,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 f09b666b..00000000 --- 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 9ca7dfc9..67148303 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 de1ce182..cfea5d76 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 1bdf236a..f5535b19 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; } }; @@ -138,9 +142,12 @@ export const UploadHandler = { promptToUpload: findByCodeLazy("#{intl::ATTACHMENT_TOO_MANY_ERROR_TITLE}") as (files: File[], channel: Channel, draftType: Number) => void }; -export const ApplicationAssetUtils = findByPropsLazy("fetchAssetIds", "getAssetImage") as { - fetchAssetIds: (applicationId: string, e: string[]) => Promise<string[]>; -}; +export const ApplicationAssetUtils = mapMangledModuleLazy("getAssetImage: size must === [", { + fetchAssetIds: filters.byCode('.startsWith("http:")', ".dispatch({"), + getAssetFromImageURL: filters.byCode("].serialize(", ',":"'), + getAssetImage: filters.byCode("getAssetImage: size must === ["), + getAssets: filters.byCode(".assets") +}); export const Clipboard: t.Clipboard = mapMangledModuleLazy('queryCommandEnabled("copy")', { copy: filters.byCode(".copy("), diff --git a/src/webpack/index.ts b/src/webpack/index.ts index 036c2a3f..6f1fd25b 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 fb640cea..9b66a5b4 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 19519d64..0e3d641b 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -20,9 +20,11 @@ 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 { FluxStore } from "@webpack/types"; import { traceFunction } from "../debug/Tracer"; +import { Flux } from "./common"; +import { AnyModuleFactory, AnyWebpackRequire, ModuleExports, WebpackRequire } from "./wreq"; const logger = new Logger("Webpack"); @@ -33,8 +35,10 @@ 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 const fluxStores: Record<string, FluxStore> = {}; export type FilterFn = (mod: any) => boolean; @@ -56,40 +60,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 +192,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 +200,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 +222,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 +269,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 +286,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 +431,25 @@ export function findByCodeLazy(...code: CodeFilter) { * Find a store by its displayName */ export function findStore(name: StoreNameFilter) { - const res = find(filters.byStoreName(name), { isIndirect: true }); + let res = fluxStores[name] as any; + if (res == null) { + for (const store of Flux.Store.getAll?.() ?? []) { + const storeName = store.getName(); + + if (storeName === name) { + res = store; + } + + if (fluxStores[storeName] == null) { + fluxStores[storeName] = store; + } + } + + if (res == null) { + res = find(filters.byStoreName(name), { isIndirect: true }); + } + } + if (!res) handleModuleNotFound("findStore", name); return res; @@ -440,12 +517,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 +545,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 +553,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 +569,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 +587,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 +600,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 +613,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 +625,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 +648,7 @@ export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = D return false; } - wreq(Number(entryPointId)); + wreq(entryPoint); return true; } @@ -595,7 +685,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 +701,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 00000000..2b356f9d --- /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 db6d0918..d2a42bd5 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": [