This commit is contained in:
Ulysia 2025-02-19 04:11:22 +01:00
commit a8ed09f639
39 changed files with 1676 additions and 916 deletions

View file

@ -42,7 +42,7 @@ jobs:
- name: Clean up obsolete files - name: Clean up obsolete files
run: | 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 - name: Get some values needed for the release
id: release_values id: release_values

View file

@ -1,9 +1,22 @@
name: Test Patches name: Test Patches
on: on:
workflow_dispatch: workflow_dispatch:
schedule: inputs:
# Every day at midnight discord_branch:
- cron: 0 0 * * * 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: jobs:
TestPlugins: TestPlugins:
@ -40,28 +53,43 @@ jobs:
- name: Build Vencord Reporter Version - name: Build Vencord Reporter Version
run: pnpm buildReporter run: pnpm buildReporter
- name: Create Report - name: Run Reporter
timeout-minutes: 10 timeout-minutes: 10
run: | run: |
export PATH="$PWD/node_modules/.bin:$PATH" export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }} export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
esbuild scripts/generateReport.ts > dist/report.mjs 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) stable_output_file=$(mktemp)
timeout-minutes: 10 canary_output_file=$(mktemp)
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
esbuild scripts/generateReport.ts > dist/report.mjs pids=""
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
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: env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} WEBHOOK_URL: ${{ inputs.webhook_url || secrets.DISCORD_WEBHOOK }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}

1
.gitignore vendored
View file

@ -8,6 +8,7 @@ vencord_installer
.DS_Store .DS_Store
yarn.lock yarn.lock
bun.lock
package-lock.json package-lock.json
*.log *.log

View file

@ -36,7 +36,7 @@
"web_accessible_resources": [ "web_accessible_resources": [
{ {
"resources": ["dist/*", "third-party/*"], "resources": ["dist/*", "vendor/*"],
"matches": ["*://*.discord.com/*"] "matches": ["*://*.discord.com/*"]
} }
], ],

View file

@ -15,7 +15,7 @@ declare global {
const getTheme: () => string; const getTheme: () => string;
} }
const BASE = "/dist/monaco/vs"; const BASE = "/vendor/monaco/vs";
self.MonacoEnvironment = { self.MonacoEnvironment = {
getWorkerUrl(_moduleId: unknown, label: string) { getWorkerUrl(_moduleId: unknown, label: string) {

View file

@ -24,12 +24,12 @@
<script> <script>
const script = document.createElement("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"); const style = document.createElement("link");
style.type = "text/css"; style.type = "text/css";
style.rel = "stylesheet"; 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); document.body.append(style, script);
</script> </script>

View file

@ -134,7 +134,7 @@ export default tseslint.config(
"no-unsafe-optional-chaining": "error", "no-unsafe-optional-chaining": "error",
"no-useless-backreference": "error", "no-useless-backreference": "error",
"use-isnan": "error", "use-isnan": "error",
"prefer-const": "error", "prefer-const": ["error", { destructuring: "all" }],
"prefer-spread": "error", "prefer-spread": "error",
// Plugin Rules // Plugin Rules

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.11.4", "version": "1.11.5",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {
@ -30,13 +30,12 @@
"lint": "eslint", "lint": "eslint",
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins", "lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
"lint:fix": "pnpm lint --fix", "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", "testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
"testTsc": "tsc --noEmit" "testTsc": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@intrnl/xxhash64": "^0.1.2", "@intrnl/xxhash64": "^0.1.2",
"@sapphi-red/web-noise-suppressor": "0.3.5",
"@vap/core": "0.0.12", "@vap/core": "0.0.12",
"@vap/shiki": "0.10.5", "@vap/shiki": "0.10.5",
"fflate": "^0.8.2", "fflate": "^0.8.2",
@ -56,7 +55,7 @@
"@types/yazl": "^2.4.5", "@types/yazl": "^2.4.5",
"diff": "^7.0.0", "diff": "^7.0.0",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"esbuild": "^0.15.18", "esbuild": "^0.25.0",
"eslint": "^9.17.0", "eslint": "^9.17.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "2.1.0", "eslint-plugin-path-alias": "2.1.0",

View file

@ -19,9 +19,6 @@ importers:
'@intrnl/xxhash64': '@intrnl/xxhash64':
specifier: ^0.1.2 specifier: ^0.1.2
version: 0.1.2 version: 0.1.2
'@sapphi-red/web-noise-suppressor':
specifier: 0.3.5
version: 0.3.5
'@vap/core': '@vap/core':
specifier: 0.0.12 specifier: 0.0.12
version: 0.0.12 version: 0.0.12
@ -75,8 +72,8 @@ importers:
specifier: ^1.3.26 specifier: ^1.3.26
version: 1.3.26 version: 1.3.26
esbuild: esbuild:
specifier: ^0.15.18 specifier: ^0.25.0
version: 0.15.18 version: 0.25.0
eslint: eslint:
specifier: ^9.17.0 specifier: ^9.17.0
version: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) version: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
@ -229,6 +226,12 @@ packages:
cpu: [ppc64] cpu: [ppc64]
os: [aix] os: [aix]
'@esbuild/aix-ppc64@0.25.0':
resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.17.19': '@esbuild/android-arm64@0.17.19':
resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -241,10 +244,10 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@esbuild/android-arm@0.15.18': '@esbuild/android-arm64@0.25.0':
resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [arm] cpu: [arm64]
os: [android] os: [android]
'@esbuild/android-arm@0.17.19': '@esbuild/android-arm@0.17.19':
@ -259,6 +262,12 @@ packages:
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@esbuild/android-arm@0.25.0':
resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.17.19': '@esbuild/android-x64@0.17.19':
resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -271,6 +280,12 @@ packages:
cpu: [x64] cpu: [x64]
os: [android] os: [android]
'@esbuild/android-x64@0.25.0':
resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.17.19': '@esbuild/darwin-arm64@0.17.19':
resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -283,6 +298,12 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@esbuild/darwin-arm64@0.25.0':
resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.17.19': '@esbuild/darwin-x64@0.17.19':
resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -295,6 +316,12 @@ packages:
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@esbuild/darwin-x64@0.25.0':
resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.17.19': '@esbuild/freebsd-arm64@0.17.19':
resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -307,6 +334,12 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
'@esbuild/freebsd-arm64@0.25.0':
resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.17.19': '@esbuild/freebsd-x64@0.17.19':
resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -319,6 +352,12 @@ packages:
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@esbuild/freebsd-x64@0.25.0':
resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.17.19': '@esbuild/linux-arm64@0.17.19':
resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -331,6 +370,12 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@esbuild/linux-arm64@0.25.0':
resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.17.19': '@esbuild/linux-arm@0.17.19':
resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -343,6 +388,12 @@ packages:
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@esbuild/linux-arm@0.25.0':
resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.17.19': '@esbuild/linux-ia32@0.17.19':
resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -355,10 +406,10 @@ packages:
cpu: [ia32] cpu: [ia32]
os: [linux] os: [linux]
'@esbuild/linux-loong64@0.15.18': '@esbuild/linux-ia32@0.25.0':
resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==}
engines: {node: '>=12'} engines: {node: '>=18'}
cpu: [loong64] cpu: [ia32]
os: [linux] os: [linux]
'@esbuild/linux-loong64@0.17.19': '@esbuild/linux-loong64@0.17.19':
@ -373,6 +424,12 @@ packages:
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
'@esbuild/linux-loong64@0.25.0':
resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.17.19': '@esbuild/linux-mips64el@0.17.19':
resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -385,6 +442,12 @@ packages:
cpu: [mips64el] cpu: [mips64el]
os: [linux] os: [linux]
'@esbuild/linux-mips64el@0.25.0':
resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.17.19': '@esbuild/linux-ppc64@0.17.19':
resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -397,6 +460,12 @@ packages:
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
'@esbuild/linux-ppc64@0.25.0':
resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.17.19': '@esbuild/linux-riscv64@0.17.19':
resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -409,6 +478,12 @@ packages:
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
'@esbuild/linux-riscv64@0.25.0':
resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.17.19': '@esbuild/linux-s390x@0.17.19':
resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -421,6 +496,12 @@ packages:
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
'@esbuild/linux-s390x@0.25.0':
resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.17.19': '@esbuild/linux-x64@0.17.19':
resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -433,6 +514,18 @@ packages:
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@esbuild/linux-x64@0.25.0':
resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.25.0':
resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.17.19': '@esbuild/netbsd-x64@0.17.19':
resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -445,12 +538,24 @@ packages:
cpu: [x64] cpu: [x64]
os: [netbsd] os: [netbsd]
'@esbuild/netbsd-x64@0.25.0':
resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.23.1': '@esbuild/openbsd-arm64@0.23.1':
resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [openbsd] os: [openbsd]
'@esbuild/openbsd-arm64@0.25.0':
resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.17.19': '@esbuild/openbsd-x64@0.17.19':
resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -463,6 +568,12 @@ packages:
cpu: [x64] cpu: [x64]
os: [openbsd] os: [openbsd]
'@esbuild/openbsd-x64@0.25.0':
resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.17.19': '@esbuild/sunos-x64@0.17.19':
resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -475,6 +586,12 @@ packages:
cpu: [x64] cpu: [x64]
os: [sunos] os: [sunos]
'@esbuild/sunos-x64@0.25.0':
resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.17.19': '@esbuild/win32-arm64@0.17.19':
resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -487,6 +604,12 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@esbuild/win32-arm64@0.25.0':
resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.17.19': '@esbuild/win32-ia32@0.17.19':
resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -499,6 +622,12 @@ packages:
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@esbuild/win32-ia32@0.25.0':
resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.17.19': '@esbuild/win32-x64@0.17.19':
resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -511,6 +640,12 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@esbuild/win32-x64@0.25.0':
resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@eslint-community/eslint-utils@4.4.1': '@eslint-community/eslint-utils@4.4.1':
resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -609,9 +744,6 @@ packages:
'@rtsao/scc@1.1.0': '@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} 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': '@stylistic/eslint-plugin@2.12.1':
resolution: {integrity: sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ==} resolution: {integrity: sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -1205,131 +1337,6 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'} 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: esbuild@0.17.19:
resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -1340,6 +1347,11 @@ packages:
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
esbuild@0.25.0:
resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==}
engines: {node: '>=18'}
hasBin: true
escalade@3.2.0: escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2889,13 +2901,16 @@ snapshots:
'@esbuild/aix-ppc64@0.23.1': '@esbuild/aix-ppc64@0.23.1':
optional: true optional: true
'@esbuild/aix-ppc64@0.25.0':
optional: true
'@esbuild/android-arm64@0.17.19': '@esbuild/android-arm64@0.17.19':
optional: true optional: true
'@esbuild/android-arm64@0.23.1': '@esbuild/android-arm64@0.23.1':
optional: true optional: true
'@esbuild/android-arm@0.15.18': '@esbuild/android-arm64@0.25.0':
optional: true optional: true
'@esbuild/android-arm@0.17.19': '@esbuild/android-arm@0.17.19':
@ -2904,55 +2919,79 @@ snapshots:
'@esbuild/android-arm@0.23.1': '@esbuild/android-arm@0.23.1':
optional: true optional: true
'@esbuild/android-arm@0.25.0':
optional: true
'@esbuild/android-x64@0.17.19': '@esbuild/android-x64@0.17.19':
optional: true optional: true
'@esbuild/android-x64@0.23.1': '@esbuild/android-x64@0.23.1':
optional: true optional: true
'@esbuild/android-x64@0.25.0':
optional: true
'@esbuild/darwin-arm64@0.17.19': '@esbuild/darwin-arm64@0.17.19':
optional: true optional: true
'@esbuild/darwin-arm64@0.23.1': '@esbuild/darwin-arm64@0.23.1':
optional: true optional: true
'@esbuild/darwin-arm64@0.25.0':
optional: true
'@esbuild/darwin-x64@0.17.19': '@esbuild/darwin-x64@0.17.19':
optional: true optional: true
'@esbuild/darwin-x64@0.23.1': '@esbuild/darwin-x64@0.23.1':
optional: true optional: true
'@esbuild/darwin-x64@0.25.0':
optional: true
'@esbuild/freebsd-arm64@0.17.19': '@esbuild/freebsd-arm64@0.17.19':
optional: true optional: true
'@esbuild/freebsd-arm64@0.23.1': '@esbuild/freebsd-arm64@0.23.1':
optional: true optional: true
'@esbuild/freebsd-arm64@0.25.0':
optional: true
'@esbuild/freebsd-x64@0.17.19': '@esbuild/freebsd-x64@0.17.19':
optional: true optional: true
'@esbuild/freebsd-x64@0.23.1': '@esbuild/freebsd-x64@0.23.1':
optional: true optional: true
'@esbuild/freebsd-x64@0.25.0':
optional: true
'@esbuild/linux-arm64@0.17.19': '@esbuild/linux-arm64@0.17.19':
optional: true optional: true
'@esbuild/linux-arm64@0.23.1': '@esbuild/linux-arm64@0.23.1':
optional: true optional: true
'@esbuild/linux-arm64@0.25.0':
optional: true
'@esbuild/linux-arm@0.17.19': '@esbuild/linux-arm@0.17.19':
optional: true optional: true
'@esbuild/linux-arm@0.23.1': '@esbuild/linux-arm@0.23.1':
optional: true optional: true
'@esbuild/linux-arm@0.25.0':
optional: true
'@esbuild/linux-ia32@0.17.19': '@esbuild/linux-ia32@0.17.19':
optional: true optional: true
'@esbuild/linux-ia32@0.23.1': '@esbuild/linux-ia32@0.23.1':
optional: true optional: true
'@esbuild/linux-loong64@0.15.18': '@esbuild/linux-ia32@0.25.0':
optional: true optional: true
'@esbuild/linux-loong64@0.17.19': '@esbuild/linux-loong64@0.17.19':
@ -2961,75 +3000,117 @@ snapshots:
'@esbuild/linux-loong64@0.23.1': '@esbuild/linux-loong64@0.23.1':
optional: true optional: true
'@esbuild/linux-loong64@0.25.0':
optional: true
'@esbuild/linux-mips64el@0.17.19': '@esbuild/linux-mips64el@0.17.19':
optional: true optional: true
'@esbuild/linux-mips64el@0.23.1': '@esbuild/linux-mips64el@0.23.1':
optional: true optional: true
'@esbuild/linux-mips64el@0.25.0':
optional: true
'@esbuild/linux-ppc64@0.17.19': '@esbuild/linux-ppc64@0.17.19':
optional: true optional: true
'@esbuild/linux-ppc64@0.23.1': '@esbuild/linux-ppc64@0.23.1':
optional: true optional: true
'@esbuild/linux-ppc64@0.25.0':
optional: true
'@esbuild/linux-riscv64@0.17.19': '@esbuild/linux-riscv64@0.17.19':
optional: true optional: true
'@esbuild/linux-riscv64@0.23.1': '@esbuild/linux-riscv64@0.23.1':
optional: true optional: true
'@esbuild/linux-riscv64@0.25.0':
optional: true
'@esbuild/linux-s390x@0.17.19': '@esbuild/linux-s390x@0.17.19':
optional: true optional: true
'@esbuild/linux-s390x@0.23.1': '@esbuild/linux-s390x@0.23.1':
optional: true optional: true
'@esbuild/linux-s390x@0.25.0':
optional: true
'@esbuild/linux-x64@0.17.19': '@esbuild/linux-x64@0.17.19':
optional: true optional: true
'@esbuild/linux-x64@0.23.1': '@esbuild/linux-x64@0.23.1':
optional: true optional: true
'@esbuild/linux-x64@0.25.0':
optional: true
'@esbuild/netbsd-arm64@0.25.0':
optional: true
'@esbuild/netbsd-x64@0.17.19': '@esbuild/netbsd-x64@0.17.19':
optional: true optional: true
'@esbuild/netbsd-x64@0.23.1': '@esbuild/netbsd-x64@0.23.1':
optional: true optional: true
'@esbuild/netbsd-x64@0.25.0':
optional: true
'@esbuild/openbsd-arm64@0.23.1': '@esbuild/openbsd-arm64@0.23.1':
optional: true optional: true
'@esbuild/openbsd-arm64@0.25.0':
optional: true
'@esbuild/openbsd-x64@0.17.19': '@esbuild/openbsd-x64@0.17.19':
optional: true optional: true
'@esbuild/openbsd-x64@0.23.1': '@esbuild/openbsd-x64@0.23.1':
optional: true optional: true
'@esbuild/openbsd-x64@0.25.0':
optional: true
'@esbuild/sunos-x64@0.17.19': '@esbuild/sunos-x64@0.17.19':
optional: true optional: true
'@esbuild/sunos-x64@0.23.1': '@esbuild/sunos-x64@0.23.1':
optional: true optional: true
'@esbuild/sunos-x64@0.25.0':
optional: true
'@esbuild/win32-arm64@0.17.19': '@esbuild/win32-arm64@0.17.19':
optional: true optional: true
'@esbuild/win32-arm64@0.23.1': '@esbuild/win32-arm64@0.23.1':
optional: true optional: true
'@esbuild/win32-arm64@0.25.0':
optional: true
'@esbuild/win32-ia32@0.17.19': '@esbuild/win32-ia32@0.17.19':
optional: true optional: true
'@esbuild/win32-ia32@0.23.1': '@esbuild/win32-ia32@0.23.1':
optional: true optional: true
'@esbuild/win32-ia32@0.25.0':
optional: true
'@esbuild/win32-x64@0.17.19': '@esbuild/win32-x64@0.17.19':
optional: true optional: true
'@esbuild/win32-x64@0.23.1': '@esbuild/win32-x64@0.23.1':
optional: true optional: true
'@esbuild/win32-x64@0.25.0':
optional: true
'@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))': '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))':
dependencies: dependencies:
eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) eslint: 9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
@ -3135,8 +3216,6 @@ snapshots:
'@rtsao/scc@1.1.0': {} '@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@2.12.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2)':
dependencies: dependencies:
'@typescript-eslint/utils': 8.18.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2) '@typescript-eslint/utils': 8.18.1(eslint@9.17.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.7.2)
@ -3936,91 +4015,6 @@ snapshots:
is-date-object: 1.1.0 is-date-object: 1.1.0
is-symbol: 1.1.1 is-symbol: 1.1.1
esbuild-android-64@0.15.18:
optional: true
esbuild-android-arm64@0.15.18:
optional: true
esbuild-darwin-64@0.15.18:
optional: true
esbuild-darwin-arm64@0.15.18:
optional: true
esbuild-freebsd-64@0.15.18:
optional: true
esbuild-freebsd-arm64@0.15.18:
optional: true
esbuild-linux-32@0.15.18:
optional: true
esbuild-linux-64@0.15.18:
optional: true
esbuild-linux-arm64@0.15.18:
optional: true
esbuild-linux-arm@0.15.18:
optional: true
esbuild-linux-mips64le@0.15.18:
optional: true
esbuild-linux-ppc64le@0.15.18:
optional: true
esbuild-linux-riscv64@0.15.18:
optional: true
esbuild-linux-s390x@0.15.18:
optional: true
esbuild-netbsd-64@0.15.18:
optional: true
esbuild-openbsd-64@0.15.18:
optional: true
esbuild-sunos-64@0.15.18:
optional: true
esbuild-windows-32@0.15.18:
optional: true
esbuild-windows-64@0.15.18:
optional: true
esbuild-windows-arm64@0.15.18:
optional: true
esbuild@0.15.18:
optionalDependencies:
'@esbuild/android-arm': 0.15.18
'@esbuild/linux-loong64': 0.15.18
esbuild-android-64: 0.15.18
esbuild-android-arm64: 0.15.18
esbuild-darwin-64: 0.15.18
esbuild-darwin-arm64: 0.15.18
esbuild-freebsd-64: 0.15.18
esbuild-freebsd-arm64: 0.15.18
esbuild-linux-32: 0.15.18
esbuild-linux-64: 0.15.18
esbuild-linux-arm: 0.15.18
esbuild-linux-arm64: 0.15.18
esbuild-linux-mips64le: 0.15.18
esbuild-linux-ppc64le: 0.15.18
esbuild-linux-riscv64: 0.15.18
esbuild-linux-s390x: 0.15.18
esbuild-netbsd-64: 0.15.18
esbuild-openbsd-64: 0.15.18
esbuild-sunos-64: 0.15.18
esbuild-windows-32: 0.15.18
esbuild-windows-64: 0.15.18
esbuild-windows-arm64: 0.15.18
esbuild@0.17.19: esbuild@0.17.19:
optionalDependencies: optionalDependencies:
'@esbuild/android-arm': 0.17.19 '@esbuild/android-arm': 0.17.19
@ -4073,6 +4067,34 @@ snapshots:
'@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1 '@esbuild/win32-x64': 0.23.1
esbuild@0.25.0:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.0
'@esbuild/android-arm': 0.25.0
'@esbuild/android-arm64': 0.25.0
'@esbuild/android-x64': 0.25.0
'@esbuild/darwin-arm64': 0.25.0
'@esbuild/darwin-x64': 0.25.0
'@esbuild/freebsd-arm64': 0.25.0
'@esbuild/freebsd-x64': 0.25.0
'@esbuild/linux-arm': 0.25.0
'@esbuild/linux-arm64': 0.25.0
'@esbuild/linux-ia32': 0.25.0
'@esbuild/linux-loong64': 0.25.0
'@esbuild/linux-mips64el': 0.25.0
'@esbuild/linux-ppc64': 0.25.0
'@esbuild/linux-riscv64': 0.25.0
'@esbuild/linux-s390x': 0.25.0
'@esbuild/linux-x64': 0.25.0
'@esbuild/netbsd-arm64': 0.25.0
'@esbuild/netbsd-x64': 0.25.0
'@esbuild/openbsd-arm64': 0.25.0
'@esbuild/openbsd-x64': 0.25.0
'@esbuild/sunos-x64': 0.25.0
'@esbuild/win32-arm64': 0.25.0
'@esbuild/win32-ia32': 0.25.0
'@esbuild/win32-x64': 0.25.0
escalade@3.2.0: {} escalade@3.2.0: {}
escape-string-regexp@4.0.0: {} escape-string-regexp@4.0.0: {}

View file

@ -17,38 +17,41 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import esbuild from "esbuild"; // @ts-check
import { readdir } from "fs/promises"; import { readdir } from "fs/promises";
import { join } from "path"; 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_STANDALONE,
IS_DEV, IS_DEV,
IS_REPORTER, IS_REPORTER,
IS_UPDATER_DISABLED, IS_UPDATER_DISABLED,
IS_WEB: false, IS_WEB: false,
IS_EXTENSION: false, IS_EXTENSION: false,
VERSION: JSON.stringify(VERSION), VERSION,
BUILD_TIMESTAMP BUILD_TIMESTAMP
}; });
if (defines.IS_STANDALONE === false) if (defines.IS_STANDALONE === "false") {
// If this is a local build (not standalone), optimize // If this is a local build (not standalone), optimize
// for the specific platform we're on // for the specific platform we're on
defines["process.platform"] = JSON.stringify(process.platform); defines["process.platform"] = JSON.stringify(process.platform);
}
/** /**
* @type {esbuild.BuildOptions} * @type {import("esbuild").BuildOptions}
*/ */
const nodeCommonOpts = { const nodeCommonOpts = {
...commonOpts, ...commonOpts,
define: defines,
format: "cjs", format: "cjs",
platform: "node", platform: "node",
target: ["esnext"], target: ["esnext"],
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external], // @ts-ignore this is never undefined
define: defines external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external]
}; };
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`; 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 // Discord Desktop main & renderer & preload
esbuild.build({ {
...nodeCommonOpts, ...nodeCommonOpts,
entryPoints: ["src/main/index.ts"], entryPoints: ["src/main/index.ts"],
outfile: "dist/patcher.js", outfile: "dist/patcher.js",
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") }, footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
sourcemap, sourcemap,
define: {
...defines,
IS_DISCORD_DESKTOP: true,
IS_VESKTOP: false
},
plugins: [ plugins: [
// @ts-ignore this is never undefined
...nodeCommonOpts.plugins, ...nodeCommonOpts.plugins,
globNativesPlugin globNativesPlugin
] ],
}), define: {
esbuild.build({ ...defines,
IS_DISCORD_DESKTOP: "true",
IS_VESKTOP: "false"
}
},
{
...commonOpts, ...commonOpts,
entryPoints: ["src/Vencord.ts"], entryPoints: ["src/Vencord.ts"],
outfile: "dist/renderer.js", outfile: "dist/renderer.js",
@ -135,11 +140,11 @@ await Promise.all([
], ],
define: { define: {
...defines, ...defines,
IS_DISCORD_DESKTOP: true, IS_DISCORD_DESKTOP: "true",
IS_VESKTOP: false IS_VESKTOP: "false"
} }
}), },
esbuild.build({ {
...nodeCommonOpts, ...nodeCommonOpts,
entryPoints: ["src/preload.ts"], entryPoints: ["src/preload.ts"],
outfile: "dist/preload.js", outfile: "dist/preload.js",
@ -147,29 +152,29 @@ await Promise.all([
sourcemap, sourcemap,
define: { define: {
...defines, ...defines,
IS_DISCORD_DESKTOP: true, IS_DISCORD_DESKTOP: "true",
IS_VESKTOP: false IS_VESKTOP: "false"
} }
}), },
// Vencord Desktop main & renderer & preload // Vencord Desktop main & renderer & preload
esbuild.build({ {
...nodeCommonOpts, ...nodeCommonOpts,
entryPoints: ["src/main/index.ts"], entryPoints: ["src/main/index.ts"],
outfile: "dist/vencordDesktopMain.js", outfile: "dist/vencordDesktopMain.js",
footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") }, footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") },
sourcemap, sourcemap,
define: {
...defines,
IS_DISCORD_DESKTOP: false,
IS_VESKTOP: true
},
plugins: [ plugins: [
...nodeCommonOpts.plugins, ...nodeCommonOpts.plugins,
globNativesPlugin globNativesPlugin
] ],
}), define: {
esbuild.build({ ...defines,
IS_DISCORD_DESKTOP: "false",
IS_VESKTOP: "true"
}
},
{
...commonOpts, ...commonOpts,
entryPoints: ["src/Vencord.ts"], entryPoints: ["src/Vencord.ts"],
outfile: "dist/vencordDesktopRenderer.js", outfile: "dist/vencordDesktopRenderer.js",
@ -184,11 +189,11 @@ await Promise.all([
], ],
define: { define: {
...defines, ...defines,
IS_DISCORD_DESKTOP: false, IS_DISCORD_DESKTOP: "false",
IS_VESKTOP: true IS_VESKTOP: "true"
} }
}), },
esbuild.build({ {
...nodeCommonOpts, ...nodeCommonOpts,
entryPoints: ["src/preload.ts"], entryPoints: ["src/preload.ts"],
outfile: "dist/vencordDesktopPreload.js", outfile: "dist/vencordDesktopPreload.js",
@ -196,14 +201,10 @@ await Promise.all([
sourcemap, sourcemap,
define: { define: {
...defines, ...defines,
IS_DISCORD_DESKTOP: false, IS_DISCORD_DESKTOP: "false",
IS_VESKTOP: true IS_VESKTOP: "true"
} }
}), }
]).catch(err => { ]);
console.error("Build failed");
console.error(err.message); await buildOrWatchAll(buildConfigs);
// make ci fail
if (!commonOpts.watch)
process.exitCode = 1;
});

View file

@ -17,29 +17,30 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import esbuild from "esbuild"; // @ts-check
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises"; import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
import { join } from "path"; import { join } from "path";
import Zip from "zip-local"; 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 = { const commonOptions = {
...commonOpts, ...commonOpts,
entryPoints: ["browser/Vencord.ts"], entryPoints: ["browser/Vencord.ts"],
globalName: "Vencord",
format: "iife", format: "iife",
globalName: "Vencord",
external: ["~plugins", "~git-hash", "/assets/*"], external: ["~plugins", "~git-hash", "/assets/*"],
target: ["esnext"],
plugins: [ plugins: [
globPlugins("web"), globPlugins("web"),
...commonRendererPlugins ...commonRendererPlugins
], ],
target: ["esnext"], define: stringifyValues({
define: {
IS_WEB: true, IS_WEB: true,
IS_EXTENSION: false, IS_EXTENSION: false,
IS_STANDALONE: true, IS_STANDALONE: true,
@ -48,9 +49,9 @@ const commonOptions = {
IS_DISCORD_DESKTOP: false, IS_DISCORD_DESKTOP: false,
IS_VESKTOP: false, IS_VESKTOP: false,
IS_UPDATER_DISABLED: true, IS_UPDATER_DISABLED: true,
VERSION: JSON.stringify(VERSION), VERSION,
BUILD_TIMESTAMP BUILD_TIMESTAMP
} })
}; };
const MonacoWorkerEntryPoints = [ const MonacoWorkerEntryPoints = [
@ -58,52 +59,45 @@ const MonacoWorkerEntryPoints = [
"vs/editor/editor.worker.js" "vs/editor/editor.worker.js"
]; ];
const RnNoiseFiles = [ /** @type {import("esbuild").BuildOptions[]} */
"dist/rnnoise.wasm", const buildConfigs = [
"dist/rnnoise_simd.wasm", {
"dist/rnnoise/workletProcessor.js",
"LICENSE"
];
await Promise.all(
[
esbuild.build({
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`), entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
bundle: true, bundle: true,
minify: true, minify: true,
format: "iife", format: "iife",
outbase: "node_modules/monaco-editor/esm/", outbase: "node_modules/monaco-editor/esm/",
outdir: "dist/monaco" outdir: "dist/vendor/monaco"
}), },
esbuild.build({ {
entryPoints: ["browser/monaco.ts"], entryPoints: ["browser/monaco.ts"],
bundle: true, bundle: true,
minify: true, minify: true,
format: "iife", format: "iife",
outfile: "dist/monaco/index.js", outfile: "dist/vendor/monaco/index.js",
loader: { loader: {
".ttf": "file" ".ttf": "file"
} }
}), },
esbuild.build({ {
...commonOptions, ...commonOptions,
outfile: "dist/browser.js", outfile: "dist/browser.js",
footer: { js: "//# sourceURL=VencordWeb" } footer: { js: "//# sourceURL=VencordWeb" }
}), },
esbuild.build({ {
...commonOptions, ...commonOptions,
outfile: "dist/extension.js", outfile: "dist/extension.js",
define: { define: {
...commonOptions?.define, ...commonOptions.define,
IS_EXTENSION: true, IS_EXTENSION: "true"
}, },
footer: { js: "//# sourceURL=VencordWeb" } footer: { js: "//# sourceURL=VencordWeb" }
}), },
esbuild.build({ {
...commonOptions, ...commonOptions,
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
define: { define: {
...(commonOptions?.define), ...commonOptions.define,
window: "unsafeWindow", window: "unsafeWindow",
}, },
outfile: "dist/Vencord.user.js", outfile: "dist/Vencord.user.js",
@ -114,14 +108,10 @@ await Promise.all(
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local // UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});" js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
} }
}) }
] ];
).catch(err => {
console.error("Build failed"); await buildOrWatchAll(buildConfigs);
console.error(err.message);
if (!commonOpts.watch)
process.exit(1);
});;
/** /**
* @type {(dir: string) => Promise<string[]>} * @type {(dir: string) => Promise<string[]>}
@ -155,16 +145,13 @@ async function buildExtension(target, files) {
const entries = { const entries = {
"dist/Vencord.js": await readFile("dist/extension.js"), "dist/Vencord.js": await readFile("dist/extension.js"),
"dist/Vencord.css": await readFile("dist/extension.css"), "dist/Vencord.css": await readFile("dist/extension.css"),
...await loadDir("dist/monaco"), ...await loadDir("dist/vendor/monaco", "dist/"),
...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}`)]
))),
...Object.fromEntries(await Promise.all(files.map(async f => { ...Object.fromEntries(await Promise.all(files.map(async f => {
let content = await readFile(join("browser", f)); let content = await readFile(join("browser", f));
if (f.startsWith("manifest")) { if (f.startsWith("manifest")) {
const json = JSON.parse(content.toString("utf-8")); const json = JSON.parse(content.toString("utf-8"));
json.version = VERSION; json.version = VERSION;
content = new TextEncoder().encode(JSON.stringify(json)); content = Buffer.from(new TextEncoder().encode(JSON.stringify(json)));
} }
return [ return [
@ -210,7 +197,6 @@ if (!process.argv.includes("--skip-extension")) {
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip"); Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
console.info("Packed Firefox Extension written to dist/extension-firefox.zip"); console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
} else { } else {
await appendCssRuntime; await appendCssRuntime;
} }

View file

@ -16,11 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @ts-check
import "../suppressExperimentalWarnings.js"; import "../suppressExperimentalWarnings.js";
import "../checkNodeVersion.js"; import "../checkNodeVersion.js";
import { exec, execSync } from "child_process"; import { exec, execSync } from "child_process";
import esbuild from "esbuild"; import esbuild, { build, context } from "esbuild";
import { constants as FsConstants, readFileSync } from "fs"; import { constants as FsConstants, readFileSync } from "fs";
import { access, readdir, readFile } from "fs/promises"; import { access, readdir, readFile } from "fs/promises";
import { minify as minifyHtml } from "html-minifier-terser"; import { minify as minifyHtml } from "html-minifier-terser";
@ -31,7 +33,7 @@ import { getPluginTarget } from "../utils.mjs";
import { builtinModules } from "module"; import { builtinModules } from "module";
/** @type {import("../../package.json")} */ /** @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; export const VERSION = PackageJSON.version;
// https://reproducible-builds.org/docs/source-date-epoch/ // https://reproducible-builds.org/docs/source-date-epoch/
@ -54,6 +56,34 @@ export const banner = {
`.trim() `.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/; const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
/** /**
* @param {string} base * @param {string} base
@ -311,18 +341,16 @@ export const banImportPlugin = (filter, message) => ({
export const commonOpts = { export const commonOpts = {
logLevel: "info", logLevel: "info",
bundle: true, bundle: true,
watch, minify: !watch && !IS_REPORTER,
minify: !watch, sourcemap: watch ? "inline" : "external",
sourcemap: watch ? "inline" : "",
legalComments: "linked", legalComments: "linked",
banner, banner,
plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin], plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"], external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
inject: ["./scripts/build/inject/react.mjs"], inject: ["./scripts/build/inject/react.mjs"],
jsx: "transform",
jsxFactory: "VencordCreateElement", jsxFactory: "VencordCreateElement",
jsxFragment: "VencordFragment", jsxFragment: "VencordFragment"
// Work around https://github.com/evanw/esbuild/issues/2460
tsconfig: "./scripts/build/tsconfig.esbuild.json"
}; };
const escapedBuiltinModules = builtinModules 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(/^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(/^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"), 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 ...commonOpts.plugins
]; ];

View file

@ -1,7 +0,0 @@
// Work around https://github.com/evanw/esbuild/issues/2460
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react"
}
}

View file

@ -16,24 +16,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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" /> /// <reference types="../src/globals" />
// eslint-disable-next-line spaced-comment
/// <reference types="../src/modules" /> /// <reference types="../src/modules" />
import { createHmac } from "crypto";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import pup, { JSHandle } from "puppeteer-core"; 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]) { if (!process.env[variable]) {
console.error(`Missing environment variable ${variable}`); logStderr(`Missing environment variable ${variable}`);
process.exit(1); process.exit(1);
} }
} }
const CANARY = process.env.USE_CANARY === "true"; const CANARY = process.env.USE_CANARY === "true";
let metaData = {
buildNumber: "Unknown Build Number",
buildHash: "Unknown Build Hash"
};
const browser = await pup.launch({ const browser = await pup.launch({
headless: true, headless: true,
@ -51,14 +54,17 @@ async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
.catch(() => undefined); .catch(() => undefined);
} }
const report = { interface PatchInfo {
badPatches: [] as {
plugin: string; plugin: string;
type: string; type: string;
id: string; id: string;
match: string; match: string;
error?: string; error?: string;
}[], };
const report = {
badPatches: [] as PatchInfo[],
slowPatches: [] as PatchInfo[],
badStarts: [] as { badStarts: [] as {
plugin: string; plugin: string;
error: string; error: string;
@ -128,35 +134,39 @@ async function printReport() {
console.log(); console.log();
if (process.env.DISCORD_WEBHOOK) { if (process.env.WEBHOOK_URL) {
await fetch(process.env.DISCORD_WEBHOOK, { const patchesToEmbed = (title: string, patches: PatchInfo[], color: number) => ({
method: "POST", title,
headers: { color,
"Content-Type": "application/json" description: patches.map(p => {
},
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 = [ const lines = [
`**__${p.plugin} (${p.type}):__**`, `**__${p.plugin} (${p.type}):__**`,
`ID: \`${p.id}\``, `ID: \`${p.id}\``,
`Match: ${toCodeBlock(p.match, "Match: ".length, true)}` `Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
]; ];
if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`); if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
return lines.join("\n"); return lines.join("\n");
}).join("\n\n") || "None", }).join("\n\n"),
color: report.badPatches.length ? 0xff0000 : 0x00ff00 });
},
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
},
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", title: "Bad Webpack Finds",
description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None", description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00 color: 0xff0000
}, },
{ report.badStarts.length > 0 && {
title: "Bad Starts", title: "Bad Starts",
description: report.badStarts.map(p => { description: report.badStarts.map(p => {
const lines = [ const lines = [
@ -166,18 +176,46 @@ async function printReport() {
return lines.join("\n"); return lines.join("\n");
} }
).join("\n\n") || "None", ).join("\n\n") || "None",
color: report.badStarts.length ? 0xff0000 : 0x00ff00 color: 0xff0000
}, },
{ report.otherErrors.length > 0 && {
title: "Discord Errors", title: "Discord Errors",
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None", description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
color: report.otherErrors.length ? 0xff0000 : 0x00ff00 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 => { }).then(res => {
if (!res.ok) console.error(`Webhook failed with status ${res.status}`); if (!res.ok) logStderr(`Webhook failed with status ${res.status}`);
else console.error("Posted to Discord Webhook successfully"); else logStderr("Posted to Webhook successfully");
}); });
} }
} }
@ -186,10 +224,13 @@ page.on("console", async e => {
const level = e.type(); const level = e.type();
const rawArgs = e.args(); const rawArgs = e.args();
async function getText() { async function getText(skipFirst = true) {
let args = e.args();
if (skipFirst) args = args.slice(1);
try { try {
return await Promise.all( return await Promise.all(
e.args().map(async a => { args.map(async a => {
return await maybeGetError(a) || await a.jsonValue(); return await maybeGetError(a) || await a.jsonValue();
}) })
).then(a => a.join(" ").trim()); ).then(a => a.join(" ").trim());
@ -202,6 +243,12 @@ page.on("console", async e => {
const isVencord = firstArg === "[Vencord]"; const isVencord = firstArg === "[Vencord]";
const isDebug = firstArg === "[PUP_DEBUG]"; const isDebug = firstArg === "[PUP_DEBUG]";
const isReporterMeta = firstArg === "[REPORTER_META]";
if (isReporterMeta) {
metaData = await rawArgs[1].jsonValue() as any;
return;
}
outer: outer:
if (isVencord) { if (isVencord) {
@ -215,18 +262,21 @@ page.on("console", async e => {
switch (tag) { switch (tag) {
case "WebpackInterceptor:": case "WebpackInterceptor:":
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!; const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/);
if (!patchFailMatch) break; 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; process.exitCode = 1;
const [, plugin, type, id, regex] = patchFailMatch; const [, plugin, type, id, regex] = match;
report.badPatches.push({ const list = patchFailMatch ? report.badPatches : report.slowPatches;
list.push({
plugin, plugin,
type, type,
id, id,
match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"), match: regex,
error: await maybeGetError(e.args()[3]) error: await maybeGetError(e.args()[3])
}); });
@ -235,7 +285,7 @@ page.on("console", async e => {
const failedToStartMatch = message.match(/Failed to start (.+)/); const failedToStartMatch = message.match(/Failed to start (.+)/);
if (!failedToStartMatch) break; if (!failedToStartMatch) break;
console.error(await getText()); logStderr(await getText());
process.exitCode = 1; process.exitCode = 1;
const [, name] = failedToStartMatch; const [, name] = failedToStartMatch;
@ -246,7 +296,7 @@ page.on("console", async e => {
break; break;
case "LazyChunkLoader:": case "LazyChunkLoader:":
console.error(await getText()); logStderr(await getText());
switch (message) { switch (message) {
case "A fatal error occurred:": case "A fatal error occurred:":
@ -255,7 +305,7 @@ page.on("console", async e => {
break; break;
case "Reporter:": case "Reporter:":
console.error(await getText()); logStderr(await getText());
switch (message) { switch (message) {
case "A fatal error occurred:": case "A fatal error occurred:":
@ -273,47 +323,36 @@ page.on("console", async e => {
} }
if (isDebug) { if (isDebug) {
console.error(await getText()); logStderr(await getText());
} else if (level === "error") { } 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 (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))) { if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) {
report.ignoredErrors.push(text); report.ignoredErrors.push(text);
} else { } else {
console.error("[Unexpected Error]", text); logStderr("[Unexpected Error]", text);
report.otherErrors.push(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 => { page.on("pageerror", e => {
if (e.message.includes("Sentry successfully disabled")) return; if (e.message.includes("Sentry successfully disabled")) return;
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) { if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) {
console.error("[Page Error]", e.message); logStderr("[Page Error]", e.message);
report.otherErrors.push(e.message); report.otherErrors.push(e.message);
} else { } else {
report.ignoredErrors.push(e.message); 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(` await page.evaluateOnNewDocument(`
if (location.host.endsWith("discord.com")) { if (location.host.endsWith("discord.com")) {
${readFileSync("./dist/browser.js", "utf-8")}; ${readFileSync("./dist/browser.js", "utf-8")};
(${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
} }
`); `);

View file

@ -23,6 +23,7 @@ export * as Util from "./utils";
export * as QuickCss from "./utils/quickCss"; export * as QuickCss from "./utils/quickCss";
export * as Updater from "./utils/updater"; export * as Updater from "./utils/updater";
export * as Webpack from "./webpack"; export * as Webpack from "./webpack";
export * as WebpackPatcher from "./webpack/patchWebpack";
export { PlainSettings, Settings }; export { PlainSettings, Settings };
import "./utils/quickCss"; import "./utils/quickCss";

View file

@ -32,9 +32,10 @@ export interface Settings {
autoUpdate: boolean; autoUpdate: boolean;
autoUpdateNotification: boolean, autoUpdateNotification: boolean,
useQuickCss: boolean; useQuickCss: boolean;
eagerPatches: boolean;
enabledThemes: string[];
enableReactDevtools: boolean; enableReactDevtools: boolean;
themeLinks: string[]; themeLinks: string[];
enabledThemes: string[];
frameless: boolean; frameless: boolean;
transparent: boolean; transparent: boolean;
winCtrlQ: boolean; winCtrlQ: boolean;
@ -81,6 +82,7 @@ const DefaultSettings: Settings = {
autoUpdateNotification: true, autoUpdateNotification: true,
useQuickCss: true, useQuickCss: true,
themeLinks: [], themeLinks: [],
eagerPatches: IS_REPORTER,
enabledThemes: [], enabledThemes: [],
enableReactDevtools: false, enableReactDevtools: false,
frameless: false, frameless: false,

View file

@ -65,7 +65,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
} }
const canonicalMatch = canonicalizeMatch(new RegExp(match)); const canonicalMatch = canonicalizeMatch(new RegExp(match));
try { try {
const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin"); const canonicalReplace = canonicalizeReplace(replacement, 'Vencord.Plugins.plugins["YourPlugin"]');
var patched = src.replace(canonicalMatch, canonicalReplace as string); var patched = src.replace(canonicalMatch, canonicalReplace as string);
setReplacementError(void 0); setReplacementError(void 0);
} catch (e) { } catch (e) {

View file

@ -23,35 +23,61 @@ if (IS_DEV || IS_REPORTER) {
var logger = new Logger("Tracer", "#FFD166"); var logger = new Logger("Tracer", "#FFD166");
} }
const noop = function () { }; export const beginTrace = !(IS_DEV || IS_REPORTER) ? () => { } :
export const beginTrace = !(IS_DEV || IS_REPORTER) ? noop :
function beginTrace(name: string, ...args: any[]) { function beginTrace(name: string, ...args: any[]) {
if (name in traces) if (name in traces) {
throw new Error(`Trace ${name} already exists!`); throw new Error(`Trace ${name} already exists!`);
}
traces[name] = [performance.now(), args]; traces[name] = [performance.now(), args];
}; };
export const finishTrace = !(IS_DEV || IS_REPORTER) ? noop : function finishTrace(name: string) { export const finishTrace = !(IS_DEV || IS_REPORTER) ? () => 0 :
function finishTrace(name: string) {
const end = performance.now(); const end = performance.now();
const [start, args] = traces[name]; const [start, args] = traces[name];
delete 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 Func = (...args: any[]) => any;
type TraceNameMapper<F extends Func> = (...args: Parameters<F>) => string; type TraceNameMapper<F extends Func> = (...args: Parameters<F>) => string;
const noopTracer = function noopTracerWithResults<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) {
<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) => 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) export const traceFunction = !(IS_DEV || IS_REPORTER)
? noopTracer ? noopTracer
: function traceFunction<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): F { : 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; const traceName = mapper?.(...args) ?? name;
beginTrace(traceName, ...arguments); beginTrace(traceName, ...arguments);

View file

@ -8,23 +8,26 @@ import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches"; import { canonicalizeMatch } from "@utils/patches";
import * as Webpack from "@webpack"; import * as Webpack from "@webpack";
import { wreq } from "@webpack"; import { wreq } from "@webpack";
import { AnyModuleFactory, ModuleFactory } from "@webpack/wreq.d";
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
export async function loadLazyChunks() { export async function loadLazyChunks() {
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
try { try {
LazyChunkLoaderLogger.log("Loading all chunks..."); LazyChunkLoaderLogger.log("Loading all chunks...");
const validChunks = new Set<number>(); const validChunks = new Set<PropertyKey>();
const invalidChunks = new Set<number>(); const invalidChunks = new Set<PropertyKey>();
const deferredRequires = new Set<number>(); const deferredRequires = new Set<PropertyKey>();
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void; const { promise: chunksSearchingDone, resolve: chunksSearchingResolve } = Promise.withResolvers<void>();
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
// True if resolved, false otherwise // True if resolved, false otherwise
const chunksSearchPromises = [] as Array<() => boolean>; 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); const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
let foundCssDebuggingLoad = false; let foundCssDebuggingLoad = false;
@ -34,12 +37,15 @@ export async function loadLazyChunks() {
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&")); const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
const lazyChunks = factoryCode.matchAll(LazyChunkRegex); const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>(); const validChunkGroups = new Set<[chunkIds: PropertyKey[], entryPoint: PropertyKey]>();
const shouldForceDefer = false; const shouldForceDefer = false;
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { 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) { if (chunkIds.length === 0) {
return; return;
@ -74,7 +80,8 @@ export async function loadLazyChunks() {
} }
if (!invalidChunkGroup) { 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( await Promise.all(
Array.from(validChunkGroups) Array.from(validChunkGroups)
.map(([chunkIds]) => .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; continue;
} }
if (wreq.m[entryPoint]) wreq(entryPoint as any); if (wreq.m[entryPoint]) wreq(entryPoint);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
@ -122,41 +129,44 @@ export async function loadLazyChunks() {
}, 0); }, 0);
} }
Webpack.factoryListeners.add(factory => { function factoryListener(factory: AnyModuleFactory | ModuleFactory) {
let isResolved = false; let isResolved = false;
searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true); searchAndLoadLazyChunks(String(factory))
.then(() => isResolved = true)
.catch(() => isResolved = true);
chunksSearchPromises.push(() => isResolved); chunksSearchPromises.push(() => isResolved);
}); }
for (const factoryId in wreq.m) { Webpack.factoryListeners.add(factoryListener);
let isResolved = false; for (const moduleId in wreq.m) {
searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true); factoryListener(wreq.m[moduleId]);
chunksSearchPromises.push(() => isResolved);
} }
await chunksSearchingDone; await chunksSearchingDone;
Webpack.factoryListeners.delete(factoryListener);
// Require deferred entry points // Require deferred entry points
for (const deferredRequire of deferredRequires) { 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 // 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: // 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]; const id = currentMatch[1] ?? currentMatch[2];
if (id == null) continue; 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"); 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 => { const chunksLeft = allChunks.filter(id => {
return !(validChunks.has(id) || invalidChunks.has(id)); return !(validChunks.has(id) || invalidChunks.has(id));
}); });
@ -166,12 +176,9 @@ export async function loadLazyChunks() {
.then(r => r.text()) .then(r => r.text())
.then(t => t.includes("importScripts(")); .then(t => t.includes("importScripts("));
// Loads and requires a chunk // Loads the chunk. Currently this only happens with the language packs which are loaded differently
if (!isWorkerAsset) { if (!isWorkerAsset) {
await wreq.e(id as any); await wreq.e(id);
// 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);
} }
})); }));

View file

@ -6,28 +6,56 @@
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import * as Webpack from "@webpack"; import * as Webpack from "@webpack";
import { patches } from "plugins"; import { getBuildNumber, patchTimings } from "@webpack/patcher";
import { addPatch, patches } from "../plugins";
import { loadLazyChunks } from "./loadLazyChunks"; import { loadLazyChunks } from "./loadLazyChunks";
async function runReporter() {
const ReporterLogger = new Logger("Reporter"); const ReporterLogger = new Logger("Reporter");
async function runReporter() {
try { try {
ReporterLogger.log("Starting test..."); ReporterLogger.log("Starting test...");
let loadLazyChunksResolve: (value: void | PromiseLike<void>) => void; const { promise: loadLazyChunksDone, resolve: loadLazyChunksResolve } = Promise.withResolvers<void>();
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
// 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; 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) { for (const patch of patches) {
if (!patch.all) { if (!patch.all) {
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`); 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) { for (const [searchType, args] of Webpack.lazyWebpackSearchHistory) {
let method = searchType; let method = searchType;
@ -50,9 +78,9 @@ async function runReporter() {
result = await Webpack.extractAndLoadChunks(code, matcher); result = await Webpack.extractAndLoadChunks(code, matcher);
if (result === false) result = null; if (result === false) result = null;
} else if (method === "mapMangledModule") { } 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"); if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
} else { } else {
// @ts-ignore // @ts-ignore
@ -88,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);

7
src/globals.d.ts vendored
View file

@ -64,13 +64,8 @@ declare global {
export var Vesktop: any; export var Vesktop: any;
export var VesktopNative: any; export var VesktopNative: any;
interface Window { interface Window extends Record<PropertyKey, any> {
webpackChunkdiscord_app: {
push(chunk: any): any;
pop(): any;
};
_: LoDashStatic; _: LoDashStatic;
[k: string]: any;
} }
} }

View file

@ -20,6 +20,7 @@ import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, StartAt } from "@utils/types"; import definePlugin, { OptionType, StartAt } from "@utils/types";
import { WebpackRequire } from "@webpack/wreq.d";
const settings = definePluginSettings({ const settings = definePluginSettings({
disableAnalytics: { disableAnalytics: {
@ -81,9 +82,9 @@ export default definePlugin({
Object.defineProperty(Function.prototype, "g", { Object.defineProperty(Function.prototype, "g", {
configurable: true, configurable: true,
set(v: any) { set(this: WebpackRequire, globalObj: WebpackRequire["g"]) {
Object.defineProperty(this, "g", { Object.defineProperty(this, "g", {
value: v, value: globalObj,
configurable: true, configurable: true,
enumerable: true, enumerable: true,
writable: true writable: true
@ -92,11 +93,11 @@ export default definePlugin({
// Ensure this is most likely the Sentry WebpackInstance. // 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 // 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(); 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; return;
} }
const assetPath = stack?.match(/\/assets\/.+?\.js/)?.[0]; const assetPath = stack.match(/http.+?(?=:\d+?:\d+?$)/m)?.[0];
if (!assetPath) { if (!assetPath) {
return; return;
} }
@ -106,7 +107,8 @@ export default definePlugin({
srcRequest.send(); srcRequest.send();
// Final condition to see if this is the Sentry WebpackInstance // 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; return;
} }

View file

@ -158,6 +158,9 @@ export default definePlugin({
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS") aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
}; };
if (!names[settingsLocation] || names[settingsLocation].endsWith("_SETTINGS"))
return firstChild === "PREMIUM";
return header === names[settingsLocation]; return header === names[settingsLocation];
} catch { } catch {
return firstChild === "PREMIUM"; return firstChild === "PREMIUM";

View file

@ -82,6 +82,8 @@ function makeShortcuts() {
wp: Webpack, wp: Webpack,
wpc: { getter: () => Webpack.cache }, wpc: { getter: () => Webpack.cache },
wreq: { getter: () => Webpack.wreq }, wreq: { getter: () => Webpack.wreq },
wpPatcher: { getter: () => Vencord.WebpackPatcher },
wpInstances: { getter: () => Vencord.WebpackPatcher.allWebpackInstances },
wpsearch: search, wpsearch: search,
wpex: extract, wpex: extract,
wpexs: (code: string) => extract(findModuleId(code)!), wpexs: (code: string) => extract(findModuleId(code)!),

View file

@ -160,7 +160,7 @@ function initWs(isManual = false) {
return reply("Expected exactly one 'find' matches, found " + keys.length); return reply("Expected exactly one 'find' matches, found " + keys.length);
const mod = candidates[keys[0]]; const mod = candidates[keys[0]];
let src = String(mod.original ?? mod).replaceAll("\n", ""); let src = String(mod).replaceAll("\n", "");
if (src.startsWith("function(")) { if (src.startsWith("function(")) {
src = "0," + src; src = "0," + src;
@ -173,7 +173,7 @@ function initWs(isManual = false) {
try { try {
const matcher = canonicalizeMatch(parseNode(match)); 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); const newSource = src.replace(matcher, replacement as string);

View file

@ -31,7 +31,7 @@ export default definePlugin({
{ {
name: "create friend invite", name: "create friend invite",
description: "Generates a friend invite link.", description: "Generates a friend invite link.",
inputType: ApplicationCommandInputType.BOT, inputType: ApplicationCommandInputType.BUILT_IN,
execute: async (args, ctx) => { execute: async (args, ctx) => {
const invite = await FriendInvites.createFriendInvite(); const invite = await FriendInvites.createFriendInvite();
@ -48,7 +48,7 @@ export default definePlugin({
{ {
name: "view friend invites", name: "view friend invites",
description: "View a list of all generated friend invites.", description: "View a list of all generated friend invites.",
inputType: ApplicationCommandInputType.BOT, inputType: ApplicationCommandInputType.BUILT_IN,
execute: async (_, ctx) => { execute: async (_, ctx) => {
const invites = await FriendInvites.getAllFriendInvites(); const invites = await FriendInvites.getAllFriendInvites();
const friendInviteList = invites.map(i => const friendInviteList = invites.map(i =>
@ -67,7 +67,7 @@ export default definePlugin({
{ {
name: "revoke friend invites", name: "revoke friend invites",
description: "Revokes all generated friend invites.", description: "Revokes all generated friend invites.",
inputType: ApplicationCommandInputType.BOT, inputType: ApplicationCommandInputType.BUILT_IN,
execute: async (_, ctx) => { execute: async (_, ctx) => {
await FriendInvites.revokeFriendInvites(); await FriendInvites.revokeFriendInvites();

View file

@ -28,9 +28,10 @@ import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/Messag
import { Settings, SettingsStore } from "@api/Settings"; import { Settings, SettingsStore } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import { Logger } from "@utils/Logger"; 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 { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
import { FluxDispatcher } from "@webpack/common"; import { FluxDispatcher } from "@webpack/common";
import { patches } from "@webpack/patcher";
import { FluxEvents } from "@webpack/types"; import { FluxEvents } from "@webpack/types";
import Plugins from "~plugins"; import Plugins from "~plugins";
@ -41,7 +42,7 @@ const logger = new Logger("PluginManager", "#a6d189");
export const PMLogger = logger; export const PMLogger = logger;
export const plugins = Plugins; 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 */ /** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */
let enabledPluginsSubscribedFlux = false; let enabledPluginsSubscribedFlux = false;
@ -58,7 +59,7 @@ export function isPluginEnabled(p: string) {
) ?? false; ) ?? 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; const patch = newPatch as Patch;
patch.plugin = pluginName; patch.plugin = pluginName;
@ -74,10 +75,12 @@ export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string) {
patch.replacement = [patch.replacement]; patch.replacement = [patch.replacement];
} }
for (const replacement of patch.replacement) {
canonicalizeReplacement(replacement, pluginPath);
if (IS_REPORTER) { if (IS_REPORTER) {
patch.replacement.forEach(r => { delete replacement.predicate;
delete r.predicate; }
});
} }
patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate()); patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate());

View file

@ -32,7 +32,7 @@ export class Logger {
constructor(public name: string, public color: string = "white") { } constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") { 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); console[level]("[Vencord]", this.name + ":", ...args);
return; return;
} }

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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 REACT_GLOBAL = "Vencord.Webpack.Common.React";
export const VENBOT_USER_ID = "1017176847865352332"; export const VENBOT_USER_ID = "1017176847865352332";
export const VENCORD_GUILD_ID = "1015060230222131221"; export const VENCORD_GUILD_ID = "1015060230222131221";

View file

@ -100,6 +100,11 @@ export function pluralise(amount: number, singular: string, plural = singular +
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`; 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 { export function tryOrElse<T>(func: () => T, fallback: T): T {
try { try {
const res = func(); const res = func();

View file

@ -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$]*)`); 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 { export function canonicalizeReplace<T extends string | ReplaceFn>(replace: T, pluginPath: string): T {
const self = `Vencord.Plugins.plugins[${JSON.stringify(pluginName)}]`;
if (typeof replace !== "function") 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) { export function canonicalizeDescriptor<T>(descriptor: TypedPropertyDescriptor<T>, canonicalize: (value: T) => T) {
@ -65,12 +66,12 @@ export function canonicalizeDescriptor<T>(descriptor: TypedPropertyDescriptor<T>
return descriptor; 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); const descriptors = Object.getOwnPropertyDescriptors(replacement);
descriptors.match = canonicalizeDescriptor(descriptors.match, canonicalizeMatch); descriptors.match = canonicalizeDescriptor(descriptors.match, canonicalizeMatch);
descriptors.replace = canonicalizeDescriptor( descriptors.replace = canonicalizeDescriptor(
descriptors.replace, descriptors.replace,
replace => canonicalizeReplace(replace, plugin), replace => canonicalizeReplace(replace, pluginPath),
); );
Object.defineProperties(replacement, descriptors); Object.defineProperties(replacement, descriptors);
} }

View file

@ -43,6 +43,10 @@ export interface PatchReplacement {
replace: string | ReplaceFn; replace: string | ReplaceFn;
/** A function which returns whether this patch replacement should be applied */ /** A function which returns whether this patch replacement should be applied */
predicate?(): boolean; 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 { export interface Patch {
@ -59,6 +63,10 @@ export interface Patch {
group?: boolean; group?: boolean;
/** A function which returns whether this patch should be applied */ /** A function which returns whether this patch should be applied */
predicate?(): boolean; 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 { export interface PluginAuthor {

View file

@ -25,7 +25,7 @@ export const Menu = {} as t.Menu;
// Relies on .name properties added by the MenuItemDemanglerAPI // Relies on .name properties added by the MenuItemDemanglerAPI
waitFor(m => m.name === "MenuCheckboxItem", (_, id) => { 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 // 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 as any); const module = wreq(id);
for (const e of Object.values(module)) { for (const e of Object.values(module)) {
if (typeof e === "function" && e.name.startsWith("Menu")) { if (typeof e === "function" && e.name.startsWith("Menu")) {

View file

@ -58,9 +58,9 @@ export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = ma
export const lodash: typeof import("lodash") = findByPropsLazy("debounce", "cloneDeep"); export const lodash: typeof import("lodash") = findByPropsLazy("debounce", "cloneDeep");
export const i18n = mapMangledModuleLazy('defaultLocale:"en-US"', { export const i18n = mapMangledModuleLazy('defaultLocale:"en-US"', {
t: filters.byProps(runtimeHashMessageKey("DISCORD")),
intl: filters.byProps("string", "format"), intl: filters.byProps("string", "format"),
t: filters.byProps(runtimeHashMessageKey("DISCORD")) }, true);
});
export let SnowflakeUtils: t.SnowflakeUtils; export let SnowflakeUtils: t.SnowflakeUtils;
waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m); waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m);

View file

@ -18,3 +18,4 @@
export * as Common from "./common"; export * as Common from "./common";
export * from "./webpack"; export * from "./webpack";
export * from "./wreq.d";

View file

@ -1,205 +1,446 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a Discord client mod
* Copyright (c) 2022 Vendicated and contributors * Copyright (c) 2024 Vendicated, Nuckyz, and contributors
* * SPDX-License-Identifier: GPL-3.0-or-later
* 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 { WEBPACK_CHUNK } from "@utils/constants"; import { Settings } from "@api/Settings";
import { makeLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { interpolateIfDefined } from "@utils/misc";
import { canonicalizeReplacement } from "@utils/patches"; import { canonicalizeReplacement } from "@utils/patches";
import { PatchReplacement } from "@utils/types"; import { Patch, PatchReplacement } from "@utils/types";
import { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer"; import { traceFunctionWithResults } from "../debug/Tracer";
import { patches } from "../plugins"; import { _blacklistBadModules, _initWebpack, factoryListeners, findModuleId, moduleListeners, waitForSubscriptions, wreq } from "./webpack";
import { _initWebpack, _shouldIgnoreModule, beforeInitListeners, factoryListeners, moduleListeners, subscriptions, wreq } from "."; import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, ModuleExports, 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 {
try {
if (wreq.m[128014]?.toString().includes("Trying to open a changelog for an invalid build number")) {
const hardcodedGetBuildNumber = wreq(128014).b as () => number;
if (typeof hardcodedGetBuildNumber === "function" && typeof hardcodedGetBuildNumber() === "number") {
return hardcodedGetBuildNumber();
}
}
} catch { }
const moduleId = findModuleId("Trying to open a changelog for an invalid build number");
if (moduleId == null) {
return -1;
}
const exports = Object.values<ModuleExports>(wreq(moduleId));
if (exports.length !== 1 || typeof exports[0] !== "function") {
return -1;
}
const buildNumber = exports[0]();
return typeof buildNumber === "number" ? buildNumber : -1;
} 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"); 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 const define: typeof Reflect.defineProperty = (target, p, attributes) => {
// This way we can patch the factory of everything being pushed to the modules array if (Object.hasOwn(attributes, "value")) {
Object.defineProperty(window, WEBPACK_CHUNK, { attributes.writable = true;
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; return Reflect.defineProperty(target, p, {
}
});
// 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,
set(v: any) {
Object.defineProperty(this, "m", {
value: v,
configurable: true, configurable: true,
enumerable: true, enumerable: true,
writable: true ...attributes
}); });
};
// When using react devtools or other extensions, we may also catch their webpack here. // wreq.m is the Webpack object containing module factories. It is pre-populated with factories, and is also populated via webpackGlobal.push
// This ensures we actually got the right one // 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.
// 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(); 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; return;
} }
const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? ""; const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1];
logger.info("Found Webpack module factory", fileName);
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. set(this: AnyWebpackRequire, bundlePath: NonNullable<AnyWebpackRequire["p"]>) {
// So if the setter is called, this means we can initialize the internal references to WebpackRequire. define(this, "p", { value: bundlePath });
Object.defineProperty(this, "p", { clearTimeout(bundlePathTimeout);
configurable: true,
set(this: WebpackInstance, bundlePath: string) { if (bundlePath !== "/assets/") {
Object.defineProperty(this, "p", { return;
value: bundlePath, }
configurable: true,
patchThisInstance();
if (wreq == null && this.c != null) {
logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
_initWebpack(this as WebpackRequire);
}
}
});
// 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: object, getters: object) {
for (const key in getters) {
if (Object.hasOwn(getters, key) && !Object.hasOwn(exports, key)) {
Object.defineProperty(exports, key, {
enumerable: 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);
}
}
});
// 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);
}
});
function patchPush(webpackGlobal: any) {
function handlePush(chunk: any) {
try {
patchFactories(chunk[1]);
} catch (err) {
logger.error("Error in handlePush", err);
}
return handlePush.$$vencordOriginal.call(webpackGlobal, chunk);
}
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);
Object.defineProperty(webpackGlobal, "push", {
configurable: true, configurable: true,
get: getters[key],
get: () => handlePush,
set(v) {
handlePush.$$vencordOriginal = v;
}
}); });
} }
}
};
};
}
});
let webpackNotInitializedLogged = false; // 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
function patchFactories(factories: Record<string, (module: any, exports: any, require: WebpackInstance) => void>) { // 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,
for (const id in factories) { // to avoid Reflect.get having no effect and causing a stack overflow
let mod = factories[id]; get(target, p, receiver) {
return undefined;
},
// Same thing as get
has(target, p) {
return false;
},
*/
const originalMod = mod; set: updateExistingOrProxyFactory
const patchedBy = new Set(); };
const factory = factories[id] = function (module: any, exports: any, require: WebpackInstance) { // The proxy for patching lazily and/or running factories with our wrapper.
if (wreq == null && IS_DEV) { const moduleFactoryHandler: ProxyHandler<MaybePatchedModuleFactory> = {
if (!webpackNotInitializedLogged) { apply(target, thisArg: unknown, argArray: Parameters<AnyModuleFactory>) {
webpackNotInitializedLogged = true; // SYM_ORIGINAL_FACTORY means the factory has already been patched
logger.error("WebpackRequire was not initialized, running modules without patches instead."); if (target[SYM_ORIGINAL_FACTORY] != null) {
return runFactoryWithWrap(target as PatchedModuleFactory, thisArg, argArray);
} }
return void originalMod(module, exports, require); // 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;
}
notifyFactoryListeners(moduleId, newFactory);
const proxiedFactory = new Proxy(Settings.eagerPatches ? patchFactory(moduleId, newFactory) : newFactory, moduleFactoryHandler);
return Reflect.set(moduleFactories, moduleId, proxiedFactory, receiver);
}
/**
* 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;
}
if (Object.hasOwn(wreq.m, moduleId)) {
existingFactory = wreq.m[moduleId];
moduleFactoriesWithFactory = wreq.m;
break;
}
}
if (existingFactory != null) {
// Sanity check to make sure these factories are equal
if (String(newFactory) !== String(existingFactory)) {
return false;
}
// 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);
}
// 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;
}
// 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];
}
return true;
}
return false;
}
/**
* 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 });
}
}
/**
* 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 { try {
mod(module, exports, require); factoryListener(factory, moduleId);
} catch (err) { } catch (err) {
// Just rethrow discord errors logger.error("Error in Webpack factory listener:\n", err, factoryListener);
if (mod === originalMod) throw err; }
}
}
logger.error("Error in patched module", err); /**
return void originalMod(module, exports, require); * 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; exports = module.exports;
if (exports == null) {
return factoryReturn;
}
if (!exports) return; if (typeof require === "function" && require.c) {
if (_blacklistBadModules(require.c, exports, module.id)) {
if (require.c) { return factoryReturn;
const shouldIgnoreModule = _shouldIgnoreModule(exports);
if (shouldIgnoreModule) {
Object.defineProperty(require.c, id, {
value: require.c[id],
enumerable: false,
configurable: true,
writable: true
});
return;
} }
} }
for (const callback of moduleListeners) { for (const callback of moduleListeners) {
try { try {
callback(exports, id); callback(exports, module.id);
} catch (err) { } catch (err) {
logger.error("Error in Webpack module listener:\n", err, callback); logger.error("Error in Webpack module listener:\n", err, callback);
} }
} }
for (const [filter, callback] of subscriptions) { for (const [filter, callback] of waitForSubscriptions) {
try { try {
if (exports && filter(exports)) { if (filter(exports)) {
subscriptions.delete(filter); waitForSubscriptions.delete(filter);
callback(exports, id); callback(exports, module.id);
continue;
} }
if (typeof exports !== "object") { if (typeof exports !== "object") {
@ -207,66 +448,100 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
} }
for (const exportKey in exports) { for (const exportKey in exports) {
if (exports[exportKey] && filter(exports[exportKey])) { const exportValue = exports[exportKey];
subscriptions.delete(filter);
callback(exports[exportKey], id); if (exportValue != null && filter(exportValue)) {
waitForSubscriptions.delete(filter);
callback(exportValue, module.id);
break;
} }
} }
} catch (err) { } catch (err) {
logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback); logger.error("Error while firing callback for Webpack waitFor 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);
} }
} }
// Discords Webpack chunks for some ungodly reason contain random return factoryReturn;
// 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 * Patches a module factory.
// ever targets newer browsers, the minifier could potentially use this trick and *
// cause issues. * @param moduleId The id of the module
// * @param originalFactory The original module factory
// 0, prefix is to turn it into an expression: 0,function(){} would be invalid syntax without the 0, * @returns The patched module factory
let code: string = "0," + mod.toString().replaceAll("\n", ""); */
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++) { for (let i = 0; i < patches.length; i++) {
const patch = patches[i]; const patch = patches[i];
const moduleMatches = typeof patch.find === "string" const moduleMatches = typeof patch.find === "string"
? code.includes(patch.find) ? code.includes(patch.find)
: patch.find.test(code); : (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code));
if (!moduleMatches) continue; if (!moduleMatches) {
continue;
}
patchedBy.add(patch.plugin); // Eager patches cannot retrieve the build number because this code runs before the module for it is loaded
const buildNumber = Settings.eagerPatches ? -1 : getBuildNumber();
const shouldCheckBuildNumber = !Settings.eagerPatches && buildNumber !== -1;
if (
shouldCheckBuildNumber &&
(patch.fromBuild != null && buildNumber < patch.fromBuild) ||
(patch.toBuild != null && buildNumber > patch.toBuild)
) {
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 executePatch = traceFunction(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => code.replace(match, replace));
const previousMod = mod;
const previousCode = code; const previousCode = code;
const previousFactory = originalFactory;
let markedAsPatched = false;
// We change all patch.replacement to array in plugins/index // We change all patch.replacement to array in plugins/index
for (const replacement of patch.replacement as PatchReplacement[]) { for (const replacement of patch.replacement as PatchReplacement[]) {
const lastMod = mod; if (
const lastCode = code; shouldCheckBuildNumber &&
(replacement.fromBuild != null && buildNumber < replacement.fromBuild) ||
(replacement.toBuild != null && buildNumber > replacement.toBuild)
) {
continue;
}
canonicalizeReplacement(replacement, patch.plugin); // TODO: remove once Vesktop has been updated to use addPatch
if (patch.plugin === "Vesktop") {
canonicalizeReplacement(replacement, "VCDP");
}
const lastCode = code;
const lastFactory = originalFactory;
try { try {
const newCode = executePatch(replacement.match, replacement.replace as string); 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 (newCode === code) {
if (!patch.noWarn) { if (!patch.noWarn) {
logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(moduleId)}): ${replacement.match}`);
if (IS_DEV) { if (IS_DEV) {
logger.debug("Function Source:\n", code); logger.debug("Function Source:\n", code);
} }
@ -274,9 +549,13 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
if (patch.group) { if (patch.group) {
logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`); logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`);
mod = previousMod;
code = previousCode; code = previousCode;
patchedFactory = previousFactory;
if (markedAsPatched) {
patchedBy.delete(patch.plugin); patchedBy.delete(patch.plugin);
}
break; break;
} }
@ -284,13 +563,53 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
} }
code = newCode; code = newCode;
mod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`); patchedSource = `// Webpack Module ${String(moduleId)} - Patched by ${[...patchedBy, patch.plugin].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) { } catch (err) {
logger.error(`Patch by ${patch.plugin} errored (Module id is ${id}): ${replacement.match}\n`, err); logger.error(`Patch by ${patch.plugin} errored (Module id is ${String(moduleId)}): ${replacement.match}\n`, err);
if (IS_DEV) { 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 (!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; const changeSize = code.length - lastCode.length;
const match = lastCode.match(replacement.match)!;
// Use 200 surrounding characters of context // Use 200 surrounding characters of context
const start = Math.max(0, match.index! - 200); const start = Math.max(0, match.index! - 200);
@ -301,10 +620,10 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
const context = lastCode.slice(start, end); const context = lastCode.slice(start, end);
const patchedContext = code.slice(start, endPatched); const patchedContext = code.slice(start, endPatched);
// inline require to avoid including it in !IS_DEV builds // Inline require to avoid including it in !IS_DEV builds
const diff = (require("diff") as typeof import("diff")).diffWordsWithSpace(context, patchedContext); const diff = (require("diff") as typeof import("diff")).diffWordsWithSpace(context, patchedContext);
let fmt = "%c %s "; let fmt = "%c %s ";
const elements = [] as string[]; const elements: string[] = [];
for (const d of diff) { for (const d of diff) {
const color = d.removed const color = d.removed
? "red" ? "red"
@ -320,34 +639,3 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff"); const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff");
logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements); 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;
code = previousCode;
break;
}
mod = lastMod;
code = lastCode;
}
}
if (!patch.all) patches.splice(i--, 1);
}
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;
}
}
}
}
}

View file

@ -20,9 +20,10 @@ import { makeLazy, proxyLazy } from "@utils/lazy";
import { LazyComponent } from "@utils/lazyReact"; import { LazyComponent } from "@utils/lazyReact";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches"; import { canonicalizeMatch } from "@utils/patches";
import type { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer"; import { traceFunction } from "../debug/Tracer";
import { Flux } from "./common";
import { AnyModuleFactory, AnyWebpackRequire, ModuleExports, WebpackRequire } from "./wreq";
const logger = new Logger("Webpack"); const logger = new Logger("Webpack");
@ -33,8 +34,8 @@ export let _resolveReady: () => void;
*/ */
export const onceReady = new Promise<void>(r => _resolveReady = r); export const onceReady = new Promise<void>(r => _resolveReady = r);
export let wreq: WebpackInstance; export let wreq: WebpackRequire;
export let cache: WebpackInstance["c"]; export let cache: WebpackRequire["c"];
export type FilterFn = (mod: any) => boolean; export type FilterFn = (mod: any) => boolean;
@ -89,33 +90,60 @@ export const filters = {
} }
}; };
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 moduleListeners = new Set<CallbackFn>();
export const factoryListeners = new Set<(factory: (module: any, exports: any, require: WebpackInstance) => void) => void>(); export const factoryListeners = new Set<FactoryListernFn>();
export const beforeInitListeners = new Set<(wreq: WebpackInstance) => void>();
export function _initWebpack(webpackRequire: WebpackInstance) { export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire; wreq = webpackRequire;
cache = webpackRequire.c; 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 // Credits to Zerebos for implementing this in BD, thus giving the idea for us to implement it too
const TypedArray = Object.getPrototypeOf(Int8Array); const TypedArray = Object.getPrototypeOf(Int8Array);
function _shouldIgnoreValue(value: any) { const PROXY_CHECK = "is this a proxy that returns values for any key?";
function shouldIgnoreValue(value: any) {
if (value == null) return true; if (value == null) return true;
if (value === window) return true; if (value === window) return true;
if (value === document || value === document.documentElement) return true; if (value === document || value === document.documentElement) return true;
if (value[Symbol.toStringTag] === "DOMTokenList") 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; if (value instanceof TypedArray) return true;
return false; return false;
} }
export function _shouldIgnoreModule(exports: any) { function makePropertyNonEnumerable(target: Object, key: PropertyKey) {
if (_shouldIgnoreValue(exports)) { 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; return true;
} }
@ -123,14 +151,16 @@ export function _shouldIgnoreModule(exports: any) {
return false; return false;
} }
let allNonEnumerable = true; let hasOnlyBadProperties = true;
for (const exportKey in exports) { for (const exportKey in exports) {
if (!_shouldIgnoreValue(exports[exportKey])) { if (shouldIgnoreValue(exports[exportKey])) {
allNonEnumerable = false; makePropertyNonEnumerable(exports, exportKey);
} else {
hasOnlyBadProperties = false;
} }
} }
return allNonEnumerable; return hasOnlyBadProperties;
} }
let devToolsOpen = false; let devToolsOpen = false;
@ -398,7 +428,10 @@ export function findByCodeLazy(...code: CodeFilter) {
* Find a store by its displayName * Find a store by its displayName
*/ */
export function findStore(name: StoreNameFilter) { export function findStore(name: StoreNameFilter) {
const res = find(filters.byStoreName(name), { isIndirect: true }); const res = Flux.Store.getAll
? Flux.Store.getAll().find(filters.byStoreName(name))
: find(filters.byStoreName(name), { isIndirect: true });
if (!res) if (!res)
handleModuleNotFound("findStore", name); handleModuleNotFound("findStore", name);
return res; return res;
@ -466,12 +499,27 @@ export function findExportedComponentLazy<T extends object = any>(...props: Prop
}); });
} }
function getAllPropertyNames(object: Object, includeNonEnumerable: boolean) {
const names = new Set<PropertyKey>();
const getKeys = includeNonEnumerable ? Object.getOwnPropertyNames : Object.keys;
do {
getKeys(object).forEach(name => 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) * 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. * then maps it into an easily usable module via the specified mappers.
* *
* @param code The code to look for * @param code The code to look for
* @param mappers Mappers to create the non mangled exports * @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 * @returns Unmangled exports as specified in mappers
* *
* @example mapMangledModule("headerIdIsManaged:", { * @example mapMangledModule("headerIdIsManaged:", {
@ -479,7 +527,7 @@ export function findExportedComponentLazy<T extends object = any>(...props: Prop
* closeModal: filters.byCode("key==") * 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 exports = {} as Record<S, any>;
const id = findModuleId(...Array.isArray(code) ? code : [code]); const id = findModuleId(...Array.isArray(code) ? code : [code]);
@ -487,8 +535,9 @@ export const mapMangledModule = traceFunction("mapMangledModule", function mapMa
return exports; return exports;
const mod = wreq(id as any); const mod = wreq(id as any);
const keys = getAllPropertyNames(mod, includeBlacklistedExports);
outer: outer:
for (const key in mod) { for (const key of keys) {
const member = mod[key]; const member = mod[key];
for (const newName in mappers) { for (const newName in mappers) {
// if the current mapper matches this module // if the current mapper matches this module
@ -502,24 +551,13 @@ export const mapMangledModule = traceFunction("mapMangledModule", function mapMa
}); });
/** /**
* {@link mapMangledModule}, lazy. * lazy mapMangledModule
* @see {@link mapMangledModule}
* 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==")
* })
*/ */
export function mapMangledModuleLazy<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>): Record<S, any> { 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]]); 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,"?([^)]+?)"?\)\)/; export const DefaultExtractAndLoadChunksRegex = /(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/;
@ -531,7 +569,7 @@ export const ChunkIdsRegex = /\("([^"]+?)"\)/g;
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory * @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 * @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); const module = findModuleFactory(...code);
if (!module) { if (!module) {
const err = new Error("extractAndLoadChunks: Couldn't find module factory"); const err = new Error("extractAndLoadChunks: Couldn't find module factory");
@ -544,7 +582,7 @@ export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = D
return false; return false;
} }
const match = module.toString().match(canonicalizeMatch(matcher)); const match = String(module).match(canonicalizeMatch(matcher));
if (!match) { if (!match) {
const err = new Error("extractAndLoadChunks: Couldn't find chunk loading in module factory code"); const err = new Error("extractAndLoadChunks: Couldn't find chunk loading in module factory code");
logger.warn(err, "Code:", code, "Matcher:", matcher); logger.warn(err, "Code:", code, "Matcher:", matcher);
@ -557,8 +595,9 @@ export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = D
} }
const [, rawChunkIds, entryPointId] = match; 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); logger.warn(err, "Code:", code, "Matcher:", matcher);
// Strict behaviour in DevBuilds to fail early and make sure the issue is found // Strict behaviour in DevBuilds to fail early and make sure the issue is found
@ -568,12 +607,19 @@ export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = D
return false; return false;
} }
const numEntryPoint = Number(entryPointId);
const entryPoint = Number.isNaN(numEntryPoint) ? entryPointId : numEntryPoint;
if (rawChunkIds) { 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))); 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"); 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); logger.warn(err, "Code:", code, "Matcher:", matcher);
@ -584,7 +630,7 @@ export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = D
return false; return false;
} }
wreq(Number(entryPointId)); wreq(entryPoint);
return true; return true;
} }
@ -621,7 +667,7 @@ export function waitFor(filter: string | PropsFilter | FilterFn, callback: Callb
if (existing) return void callback(existing, id); if (existing) return void callback(existing, id);
} }
subscriptions.set(filter, callback); waitForSubscriptions.set(filter, callback);
} }
/** /**
@ -637,7 +683,7 @@ export function search(...code: CodeFilter) {
const factories = wreq.m; const factories = wreq.m;
for (const id in factories) { for (const id in factories) {
const factory = factories[id].original ?? factories[id]; const factory = factories[id];
if (stringMatches(factory.toString(), code)) if (stringMatches(factory.toString(), code))
results[id] = factory; results[id] = factory;

241
src/webpack/wreq.d.ts vendored Normal file
View file

@ -0,0 +1,241 @@
/*
* 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;
export type WebpackQueues = unique symbol | "__webpack_queues__";
export type WebpackExports = unique symbol | "__webpack_exports__";
export type WebpackError = unique symbol | "__webpack_error__";
export type AsyncModulePromise = Promise<ModuleExports> & {
[WebpackQueues]: (fnQueue: ((queue: any[]) => any)) => any;
[WebpackExports]: ModuleExports;
[WebpackError]?: 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: AnyRecord, definiton: AnyRecord) => 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;

View file

@ -29,7 +29,9 @@
"@shared/*": ["./shared/*"], "@shared/*": ["./shared/*"],
"@webpack/types": ["./webpack/common/types"], "@webpack/types": ["./webpack/common/types"],
"@webpack/common": ["./webpack/common"], "@webpack/common": ["./webpack/common"],
"@webpack": ["./webpack/webpack"] "@webpack": ["./webpack/webpack"],
"@webpack/patcher": ["./webpack/patchWebpack"],
"@webpack/wreq.d": ["./webpack/wreq.d"],
}, },
"plugins": [ "plugins": [