mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-23 15:05:11 +00:00
Merge branch 'dev' into managed-styles-rewrite
This commit is contained in:
commit
0d4e6795e0
58 changed files with 1702 additions and 1959 deletions
42
.github/ISSUE_TEMPLATE/blank.yml
vendored
42
.github/ISSUE_TEMPLATE/blank.yml
vendored
|
@ -2,30 +2,24 @@ name: Blank Issue
|
|||
description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# READ THIS BEFORE OPENING AN ISSUE
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||

|
||||
|
||||
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
||||
|
||||
DO NOT USE THIS FORM, unless
|
||||
- you are a vencord contributor
|
||||
- you were given explicit permission to use this form by a moderator in our support server
|
||||
|
||||
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
|
||||
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer.
|
||||
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Content
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: agreement-check
|
||||
attributes:
|
||||
label: Request Agreement
|
||||
options:
|
||||
- label: I have read the requirements for opening an issue above
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Content
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: agreement-check
|
||||
attributes:
|
||||
label: Request Agreement
|
||||
options:
|
||||
- label: I have read the requirements for opening an issue above
|
||||
required: true
|
||||
|
|
127
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
127
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -4,78 +4,63 @@ labels: [bug]
|
|||
title: "[Bug] <title>"
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# READ THIS BEFORE OPENING AN ISSUE
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||

|
||||
|
||||
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
||||
|
||||
DO NOT USE THIS FORM, unless
|
||||
- you are a vencord contributor
|
||||
- you were given explicit permission to use this form by a moderator in our support server
|
||||
|
||||
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
|
||||
|
||||
- type: input
|
||||
id: discord
|
||||
attributes:
|
||||
label: Discord Account
|
||||
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
|
||||
placeholder: username#0000
|
||||
validations:
|
||||
required: false
|
||||
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer.
|
||||
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: What happens when the bug or crash occurs?
|
||||
description: Where does this bug or crash occur, when does it occur, etc.
|
||||
placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behaviour
|
||||
attributes:
|
||||
label: What is the expected behaviour?
|
||||
description: Simply detail what the expected behaviour is.
|
||||
placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-take
|
||||
attributes:
|
||||
label: How do you recreate this bug or crash?
|
||||
description: Give us a list of steps in order to recreate the bug or crash.
|
||||
placeholder: |
|
||||
1. Do ...
|
||||
2. Then ...
|
||||
3. Do this ..., ... and then ...
|
||||
4. Observe "the bug" or "the crash"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: crash-log
|
||||
attributes:
|
||||
label: Errors
|
||||
description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```".
|
||||
value: |
|
||||
```
|
||||
Replace this text with your crash-log.
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: agreement-check
|
||||
attributes:
|
||||
label: Request Agreement
|
||||
description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable
|
||||
options:
|
||||
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: What happens when the bug or crash occurs?
|
||||
description: Where does this bug or crash occur, when does it occur, etc.
|
||||
placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ...
|
||||
validations:
|
||||
required: true
|
||||
- label: I have read the requirements for opening an issue above
|
||||
|
||||
- type: textarea
|
||||
id: expected-behaviour
|
||||
attributes:
|
||||
label: What is the expected behaviour?
|
||||
description: Simply detail what the expected behaviour is.
|
||||
placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-take
|
||||
attributes:
|
||||
label: How do you recreate this bug or crash?
|
||||
description: Give us a list of steps in order to recreate the bug or crash.
|
||||
placeholder: |
|
||||
1. Do ...
|
||||
2. Then ...
|
||||
3. Do this ..., ... and then ...
|
||||
4. Observe "the bug" or "the crash"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: crash-log
|
||||
attributes:
|
||||
label: Errors
|
||||
description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```".
|
||||
value: |
|
||||
```
|
||||
Replace this text with your crash-log.
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: agreement-check
|
||||
attributes:
|
||||
label: Request Agreement
|
||||
description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable
|
||||
options:
|
||||
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
|
||||
required: true
|
||||
- label: I am a Vencord Developer
|
||||
required: true
|
||||
|
|
BIN
.github/ISSUE_TEMPLATE/developer-banner.png
vendored
Normal file
BIN
.github/ISSUE_TEMPLATE/developer-banner.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -42,7 +42,7 @@ jobs:
|
|||
|
||||
- name: Clean up obsolete files
|
||||
run: |
|
||||
rm -rf dist/*-unpacked dist/monaco Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
||||
rm -rf dist/*-unpacked dist/vendor Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
||||
|
||||
- name: Get some values needed for the release
|
||||
id: release_values
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": ["dist/*", "third-party/*"],
|
||||
"resources": ["dist/*", "vendor/*"],
|
||||
"matches": ["*://*.discord.com/*"]
|
||||
}
|
||||
],
|
||||
|
|
|
@ -15,7 +15,7 @@ declare global {
|
|||
const getTheme: () => string;
|
||||
}
|
||||
|
||||
const BASE = "/dist/monaco/vs";
|
||||
const BASE = "/vendor/monaco/vs";
|
||||
|
||||
self.MonacoEnvironment = {
|
||||
getWorkerUrl(_moduleId: unknown, label: string) {
|
||||
|
|
|
@ -24,12 +24,12 @@
|
|||
|
||||
<script>
|
||||
const script = document.createElement("script");
|
||||
script.src = new URL("/dist/monaco/index.js", baseUrl);
|
||||
script.src = new URL("/vendor/monaco/index.js", baseUrl);
|
||||
|
||||
const style = document.createElement("link");
|
||||
style.type = "text/css";
|
||||
style.rel = "stylesheet";
|
||||
style.href = new URL("/dist/monaco/index.css", baseUrl);
|
||||
style.href = new URL("/vendor/monaco/index.css", baseUrl);
|
||||
|
||||
document.body.append(style, script);
|
||||
</script>
|
||||
|
|
|
@ -134,7 +134,7 @@ export default tseslint.config(
|
|||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"use-isnan": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-const": ["error", { destructuring: "all" }],
|
||||
"prefer-spread": "error",
|
||||
|
||||
// Plugin Rules
|
||||
|
|
49
package.json
49
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.11.4",
|
||||
"version": "1.11.5",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
@ -24,19 +24,18 @@
|
|||
"dev": "pnpm watch",
|
||||
"watchWeb": "pnpm buildWeb --watch",
|
||||
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false",
|
||||
"inject": "node scripts/runInstaller.mjs",
|
||||
"uninject": "node scripts/runInstaller.mjs",
|
||||
"lint": "eslint",
|
||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
||||
"test": "pnpm buildStandalone && pnpm testTsc && pnpm lint && pnpm lint-styles && pnpm generatePluginJson",
|
||||
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
|
||||
"testTsc": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@intrnl/xxhash64": "^0.1.2",
|
||||
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
||||
"@vap/core": "0.0.12",
|
||||
"@vap/shiki": "0.10.5",
|
||||
"fflate": "^0.8.2",
|
||||
|
@ -46,31 +45,31 @@
|
|||
"virtual-merge": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/eslint-plugin": "^2.12.1",
|
||||
"@types/chrome": "^0.0.287",
|
||||
"@types/diff": "^6.0.0",
|
||||
"@stylistic/eslint-plugin": "^4.0.0",
|
||||
"@types/chrome": "^0.0.304",
|
||||
"@types/diff": "^7.0.1",
|
||||
"@types/lodash": "^4.17.14",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/yazl": "^2.4.5",
|
||||
"diff": "^7.0.0",
|
||||
"discord-types": "^1.3.26",
|
||||
"esbuild": "^0.15.18",
|
||||
"eslint": "^9.17.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^9.20.1",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-path-alias": "2.1.0",
|
||||
"eslint-plugin-react": "^7.37.3",
|
||||
"eslint-plugin-simple-header": "^1.2.1",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"highlight.js": "11.7.0",
|
||||
"highlight.js": "11.11.1",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"moment": "^2.22.2",
|
||||
"puppeteer-core": "^23.11.1",
|
||||
"standalone-electron-types": "^1.0.0",
|
||||
"puppeteer-core": "^24.2.1",
|
||||
"standalone-electron-types": "^34.2.0",
|
||||
"stylelint": "^16.12.0",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
"stylelint-config-standard": "^37.0.0",
|
||||
"ts-patch": "^3.3.0",
|
||||
"ts-pattern": "^5.6.0",
|
||||
"tsx": "^4.19.2",
|
||||
|
@ -80,10 +79,10 @@
|
|||
"typescript-transform-paths": "^3.5.3",
|
||||
"zip-local": "^0.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@9.1.0",
|
||||
"packageManager": "pnpm@10.4.1",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"eslint@9.17.0": "patches/eslint@9.17.0.patch",
|
||||
"eslint@9.20.1": "patches/eslint@9.20.1.patch",
|
||||
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
||||
},
|
||||
"peerDependencyRules": {
|
||||
|
@ -96,18 +95,14 @@
|
|||
"source-map-resolve": "*",
|
||||
"resolve-url": "*",
|
||||
"source-map-url": "*",
|
||||
"urix": "*"
|
||||
}
|
||||
},
|
||||
"webExt": {
|
||||
"artifactsDir": "./dist",
|
||||
"build": {
|
||||
"overwriteDest": true
|
||||
"urix": "*",
|
||||
"q": "*"
|
||||
},
|
||||
"sourceDir": "./dist/firefox-unpacked"
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=9"
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@vencord/types",
|
||||
"private": false,
|
||||
"version": "0.1.3",
|
||||
"version": "1.11.5",
|
||||
"description": "",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
|
@ -13,16 +13,16 @@
|
|||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"fs-extra": "^11.2.0",
|
||||
"tsx": "^3.12.6"
|
||||
"fs-extra": "^11.3.0",
|
||||
"tsx": "^4.19.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/lodash": "4.17.15",
|
||||
"@types/node": "^22.13.4",
|
||||
"@types/react": "18.3.1",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"discord-types": "^1.3.26",
|
||||
"standalone-electron-types": "^1.0.0",
|
||||
"type-fest": "^3.5.3"
|
||||
"standalone-electron-types": "^34.2.0",
|
||||
"type-fest": "^4.35.0"
|
||||
}
|
||||
}
|
||||
|
|
1922
pnpm-lock.yaml
generated
1922
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -17,38 +17,41 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import esbuild from "esbuild";
|
||||
// @ts-check
|
||||
|
||||
import { readdir } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch } from "./common.mjs";
|
||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch, buildOrWatchAll, stringifyValues } from "./common.mjs";
|
||||
|
||||
const defines = {
|
||||
const defines = stringifyValues({
|
||||
IS_STANDALONE,
|
||||
IS_DEV,
|
||||
IS_REPORTER,
|
||||
IS_UPDATER_DISABLED,
|
||||
IS_WEB: false,
|
||||
IS_EXTENSION: false,
|
||||
VERSION: JSON.stringify(VERSION),
|
||||
VERSION,
|
||||
BUILD_TIMESTAMP
|
||||
};
|
||||
});
|
||||
|
||||
if (defines.IS_STANDALONE === false)
|
||||
if (defines.IS_STANDALONE === "false") {
|
||||
// If this is a local build (not standalone), optimize
|
||||
// for the specific platform we're on
|
||||
defines["process.platform"] = JSON.stringify(process.platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {esbuild.BuildOptions}
|
||||
* @type {import("esbuild").BuildOptions}
|
||||
*/
|
||||
const nodeCommonOpts = {
|
||||
...commonOpts,
|
||||
define: defines,
|
||||
format: "cjs",
|
||||
platform: "node",
|
||||
target: ["esnext"],
|
||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
|
||||
define: defines
|
||||
// @ts-ignore this is never undefined
|
||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external]
|
||||
};
|
||||
|
||||
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
||||
|
@ -102,25 +105,27 @@ const globNativesPlugin = {
|
|||
}
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
/** @type {import("esbuild").BuildOptions[]} */
|
||||
const buildConfigs = ([
|
||||
// Discord Desktop main & renderer & preload
|
||||
esbuild.build({
|
||||
{
|
||||
...nodeCommonOpts,
|
||||
entryPoints: ["src/main/index.ts"],
|
||||
outfile: "dist/patcher.js",
|
||||
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
|
||||
sourcemap,
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: true,
|
||||
IS_VESKTOP: false
|
||||
},
|
||||
plugins: [
|
||||
// @ts-ignore this is never undefined
|
||||
...nodeCommonOpts.plugins,
|
||||
globNativesPlugin
|
||||
]
|
||||
}),
|
||||
esbuild.build({
|
||||
],
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: "true",
|
||||
IS_VESKTOP: "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
...commonOpts,
|
||||
entryPoints: ["src/Vencord.ts"],
|
||||
outfile: "dist/renderer.js",
|
||||
|
@ -135,11 +140,11 @@ await Promise.all([
|
|||
],
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: true,
|
||||
IS_VESKTOP: false
|
||||
IS_DISCORD_DESKTOP: "true",
|
||||
IS_VESKTOP: "false"
|
||||
}
|
||||
}),
|
||||
esbuild.build({
|
||||
},
|
||||
{
|
||||
...nodeCommonOpts,
|
||||
entryPoints: ["src/preload.ts"],
|
||||
outfile: "dist/preload.js",
|
||||
|
@ -147,29 +152,29 @@ await Promise.all([
|
|||
sourcemap,
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: true,
|
||||
IS_VESKTOP: false
|
||||
IS_DISCORD_DESKTOP: "true",
|
||||
IS_VESKTOP: "false"
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
||||
// Vencord Desktop main & renderer & preload
|
||||
esbuild.build({
|
||||
{
|
||||
...nodeCommonOpts,
|
||||
entryPoints: ["src/main/index.ts"],
|
||||
outfile: "dist/vencordDesktopMain.js",
|
||||
footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") },
|
||||
sourcemap,
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: true
|
||||
},
|
||||
plugins: [
|
||||
...nodeCommonOpts.plugins,
|
||||
globNativesPlugin
|
||||
]
|
||||
}),
|
||||
esbuild.build({
|
||||
],
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: "false",
|
||||
IS_VESKTOP: "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
...commonOpts,
|
||||
entryPoints: ["src/Vencord.ts"],
|
||||
outfile: "dist/vencordDesktopRenderer.js",
|
||||
|
@ -184,11 +189,11 @@ await Promise.all([
|
|||
],
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: true
|
||||
IS_DISCORD_DESKTOP: "false",
|
||||
IS_VESKTOP: "true"
|
||||
}
|
||||
}),
|
||||
esbuild.build({
|
||||
},
|
||||
{
|
||||
...nodeCommonOpts,
|
||||
entryPoints: ["src/preload.ts"],
|
||||
outfile: "dist/vencordDesktopPreload.js",
|
||||
|
@ -196,14 +201,10 @@ await Promise.all([
|
|||
sourcemap,
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: true
|
||||
IS_DISCORD_DESKTOP: "false",
|
||||
IS_VESKTOP: "true"
|
||||
}
|
||||
}),
|
||||
]).catch(err => {
|
||||
console.error("Build failed");
|
||||
console.error(err.message);
|
||||
// make ci fail
|
||||
if (!commonOpts.watch)
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
await buildOrWatchAll(buildConfigs);
|
||||
|
|
|
@ -17,29 +17,30 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import esbuild from "esbuild";
|
||||
// @ts-check
|
||||
|
||||
import { readFileSync } from "fs";
|
||||
import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import Zip from "zip-local";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs";
|
||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins, buildOrWatchAll, stringifyValues } from "./common.mjs";
|
||||
|
||||
/**
|
||||
* @type {esbuild.BuildOptions}
|
||||
* @type {import("esbuild").BuildOptions}
|
||||
*/
|
||||
const commonOptions = {
|
||||
...commonOpts,
|
||||
entryPoints: ["browser/Vencord.ts"],
|
||||
globalName: "Vencord",
|
||||
format: "iife",
|
||||
globalName: "Vencord",
|
||||
external: ["~plugins", "~git-hash", "/assets/*"],
|
||||
target: ["esnext"],
|
||||
plugins: [
|
||||
globPlugins("web"),
|
||||
...commonRendererPlugins
|
||||
],
|
||||
target: ["esnext"],
|
||||
define: {
|
||||
define: stringifyValues({
|
||||
IS_WEB: true,
|
||||
IS_EXTENSION: false,
|
||||
IS_STANDALONE: true,
|
||||
|
@ -48,9 +49,9 @@ const commonOptions = {
|
|||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: false,
|
||||
IS_UPDATER_DISABLED: true,
|
||||
VERSION: JSON.stringify(VERSION),
|
||||
VERSION,
|
||||
BUILD_TIMESTAMP
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const MonacoWorkerEntryPoints = [
|
||||
|
@ -58,70 +59,59 @@ const MonacoWorkerEntryPoints = [
|
|||
"vs/editor/editor.worker.js"
|
||||
];
|
||||
|
||||
const RnNoiseFiles = [
|
||||
"dist/rnnoise.wasm",
|
||||
"dist/rnnoise_simd.wasm",
|
||||
"dist/rnnoise/workletProcessor.js",
|
||||
"LICENSE"
|
||||
/** @type {import("esbuild").BuildOptions[]} */
|
||||
const buildConfigs = [
|
||||
{
|
||||
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
|
||||
bundle: true,
|
||||
minify: true,
|
||||
format: "iife",
|
||||
outbase: "node_modules/monaco-editor/esm/",
|
||||
outdir: "dist/vendor/monaco"
|
||||
},
|
||||
{
|
||||
entryPoints: ["browser/monaco.ts"],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
format: "iife",
|
||||
outfile: "dist/vendor/monaco/index.js",
|
||||
loader: {
|
||||
".ttf": "file"
|
||||
}
|
||||
},
|
||||
{
|
||||
...commonOptions,
|
||||
outfile: "dist/browser.js",
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
},
|
||||
{
|
||||
...commonOptions,
|
||||
outfile: "dist/extension.js",
|
||||
define: {
|
||||
...commonOptions.define,
|
||||
IS_EXTENSION: "true"
|
||||
},
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
},
|
||||
{
|
||||
...commonOptions,
|
||||
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
||||
define: {
|
||||
...commonOptions.define,
|
||||
window: "unsafeWindow",
|
||||
},
|
||||
outfile: "dist/Vencord.user.js",
|
||||
banner: {
|
||||
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${VERSION}.${new Date().getTime()}`)
|
||||
},
|
||||
footer: {
|
||||
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
||||
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
await Promise.all(
|
||||
[
|
||||
esbuild.build({
|
||||
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
|
||||
bundle: true,
|
||||
minify: true,
|
||||
format: "iife",
|
||||
outbase: "node_modules/monaco-editor/esm/",
|
||||
outdir: "dist/monaco"
|
||||
}),
|
||||
esbuild.build({
|
||||
entryPoints: ["browser/monaco.ts"],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
format: "iife",
|
||||
outfile: "dist/monaco/index.js",
|
||||
loader: {
|
||||
".ttf": "file"
|
||||
}
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
outfile: "dist/browser.js",
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
outfile: "dist/extension.js",
|
||||
define: {
|
||||
...commonOptions?.define,
|
||||
IS_EXTENSION: true,
|
||||
},
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
||||
define: {
|
||||
...(commonOptions?.define),
|
||||
window: "unsafeWindow",
|
||||
},
|
||||
outfile: "dist/Vencord.user.js",
|
||||
banner: {
|
||||
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${VERSION}.${new Date().getTime()}`)
|
||||
},
|
||||
footer: {
|
||||
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
||||
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
|
||||
}
|
||||
})
|
||||
]
|
||||
).catch(err => {
|
||||
console.error("Build failed");
|
||||
console.error(err.message);
|
||||
if (!commonOpts.watch)
|
||||
process.exit(1);
|
||||
});;
|
||||
await buildOrWatchAll(buildConfigs);
|
||||
|
||||
/**
|
||||
* @type {(dir: string) => Promise<string[]>}
|
||||
|
@ -155,16 +145,13 @@ async function buildExtension(target, files) {
|
|||
const entries = {
|
||||
"dist/Vencord.js": await readFile("dist/extension.js"),
|
||||
"dist/Vencord.css": await readFile("dist/extension.css"),
|
||||
...await loadDir("dist/monaco"),
|
||||
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
|
||||
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
|
||||
))),
|
||||
...await loadDir("dist/vendor/monaco", "dist/"),
|
||||
...Object.fromEntries(await Promise.all(files.map(async f => {
|
||||
let content = await readFile(join("browser", f));
|
||||
if (f.startsWith("manifest")) {
|
||||
const json = JSON.parse(content.toString("utf-8"));
|
||||
json.version = VERSION;
|
||||
content = new TextEncoder().encode(JSON.stringify(json));
|
||||
content = Buffer.from(new TextEncoder().encode(JSON.stringify(json)));
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -208,7 +195,6 @@ if (!process.argv.includes("--skip-extension")) {
|
|||
|
||||
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
|
||||
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||
|
||||
} else {
|
||||
await appendCssRuntime;
|
||||
}
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
import "../suppressExperimentalWarnings.js";
|
||||
import "../checkNodeVersion.js";
|
||||
|
||||
import { exec, execSync } from "child_process";
|
||||
import esbuild from "esbuild";
|
||||
import esbuild, { build, context } from "esbuild";
|
||||
import { constants as FsConstants, readFileSync } from "fs";
|
||||
import { access, readdir, readFile } from "fs/promises";
|
||||
import { minify as minifyHtml } from "html-minifier-terser";
|
||||
|
@ -31,7 +33,7 @@ import { getPluginTarget } from "../utils.mjs";
|
|||
import { builtinModules } from "module";
|
||||
|
||||
/** @type {import("../../package.json")} */
|
||||
const PackageJSON = JSON.parse(readFileSync("package.json"));
|
||||
const PackageJSON = JSON.parse(readFileSync("package.json", "utf-8"));
|
||||
|
||||
export const VERSION = PackageJSON.version;
|
||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||
|
@ -54,6 +56,34 @@ export const banner = {
|
|||
`.trim()
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON.stringify all values in an object
|
||||
* @type {(obj: Record<string, any>) => Record<string, string>}
|
||||
*/
|
||||
export function stringifyValues(obj) {
|
||||
for (const key in obj) {
|
||||
obj[key] = JSON.stringify(obj[key]);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("esbuild").BuildOptions[]} buildConfigs
|
||||
*/
|
||||
export async function buildOrWatchAll(buildConfigs) {
|
||||
if (watch) {
|
||||
await Promise.all(buildConfigs.map(cfg =>
|
||||
context(cfg).then(ctx => ctx.watch())
|
||||
));
|
||||
} else {
|
||||
await Promise.all(buildConfigs.map(cfg => build(cfg)))
|
||||
.catch(error => {
|
||||
console.error(error.message);
|
||||
process.exit(1); // exit immediately to skip the rest of the builds
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
|
||||
/**
|
||||
* @param {string} base
|
||||
|
@ -311,18 +341,16 @@ export const banImportPlugin = (filter, message) => ({
|
|||
export const commonOpts = {
|
||||
logLevel: "info",
|
||||
bundle: true,
|
||||
watch,
|
||||
minify: !watch && !IS_REPORTER,
|
||||
sourcemap: watch ? "inline" : "",
|
||||
sourcemap: watch ? "inline" : "external",
|
||||
legalComments: "linked",
|
||||
banner,
|
||||
plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
|
||||
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
|
||||
inject: ["./scripts/build/inject/react.mjs"],
|
||||
jsx: "transform",
|
||||
jsxFactory: "VencordCreateElement",
|
||||
jsxFragment: "VencordFragment",
|
||||
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||
tsconfig: "./scripts/build/tsconfig.esbuild.json"
|
||||
jsxFragment: "VencordFragment"
|
||||
};
|
||||
|
||||
const escapedBuiltinModules = builtinModules
|
||||
|
@ -335,5 +363,6 @@ export const commonRendererPlugins = [
|
|||
banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"),
|
||||
banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"),
|
||||
banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"),
|
||||
// @ts-ignore this is never undefined
|
||||
...commonOpts.plugins
|
||||
];
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
|
@ -16,11 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-fallthrough */
|
||||
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="../src/globals" />
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="../src/modules" />
|
||||
|
||||
import { createHmac } from "crypto";
|
||||
|
@ -58,14 +54,17 @@ async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
|
|||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
interface PatchInfo {
|
||||
plugin: string;
|
||||
type: string;
|
||||
id: string;
|
||||
match: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
const report = {
|
||||
badPatches: [] as {
|
||||
plugin: string;
|
||||
type: string;
|
||||
id: string;
|
||||
match: string;
|
||||
error?: string;
|
||||
}[],
|
||||
badPatches: [] as PatchInfo[],
|
||||
slowPatches: [] as PatchInfo[],
|
||||
badStarts: [] as {
|
||||
plugin: string;
|
||||
error: string;
|
||||
|
@ -136,53 +135,67 @@ async function printReport() {
|
|||
console.log();
|
||||
|
||||
if (process.env.WEBHOOK_URL) {
|
||||
const patchesToEmbed = (title: string, patches: PatchInfo[], color: number) => ({
|
||||
title,
|
||||
color,
|
||||
description: patches.map(p => {
|
||||
const lines = [
|
||||
`**__${p.plugin} (${p.type}):__**`,
|
||||
`ID: \`${p.id}\``,
|
||||
`Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
|
||||
];
|
||||
if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
|
||||
|
||||
return lines.join("\n");
|
||||
}).join("\n\n"),
|
||||
});
|
||||
|
||||
const embeds = [
|
||||
{
|
||||
author: {
|
||||
name: `Discord ${CANARY ? "Canary" : "Stable"} (${metaData.buildNumber})`,
|
||||
url: `https://nelly.tools/builds/app/${metaData.buildHash}`,
|
||||
icon_url: CANARY ? "https://cdn.discordapp.com/emojis/1252721945699549327.png?size=128" : "https://cdn.discordapp.com/emojis/1252721943463985272.png?size=128"
|
||||
},
|
||||
color: CANARY ? 0xfbb642 : 0x5865f2
|
||||
},
|
||||
report.badPatches.length > 0 && patchesToEmbed("Bad Patches", report.badPatches, 0xff0000),
|
||||
report.slowPatches.length > 0 && patchesToEmbed("Slow Patches", report.slowPatches, 0xf0b232),
|
||||
report.badWebpackFinds.length > 0 && {
|
||||
title: "Bad Webpack Finds",
|
||||
description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
|
||||
color: 0xff0000
|
||||
},
|
||||
report.badStarts.length > 0 && {
|
||||
title: "Bad Starts",
|
||||
description: report.badStarts.map(p => {
|
||||
const lines = [
|
||||
`**__${p.plugin}:__**`,
|
||||
toCodeBlock(p.error, 0, true)
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
).join("\n\n") || "None",
|
||||
color: 0xff0000
|
||||
},
|
||||
report.otherErrors.length > 0 && {
|
||||
title: "Discord Errors",
|
||||
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
|
||||
color: 0xff0000
|
||||
}
|
||||
].filter(Boolean);
|
||||
|
||||
if (embeds.length === 1) {
|
||||
embeds.push({
|
||||
title: "No issues found",
|
||||
description: "Seems like everything is working fine (for now) <:shipit:1330992641466433556>",
|
||||
color: 0x00ff00
|
||||
});
|
||||
}
|
||||
|
||||
const body = JSON.stringify({
|
||||
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
|
||||
embeds: [
|
||||
{
|
||||
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
|
||||
},
|
||||
{
|
||||
title: "Bad Patches",
|
||||
description: report.badPatches.map(p => {
|
||||
const lines = [
|
||||
`**__${p.plugin} (${p.type}):__**`,
|
||||
`ID: \`${p.id}\``,
|
||||
`Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
|
||||
];
|
||||
if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
|
||||
return lines.join("\n");
|
||||
}).join("\n\n") || "None",
|
||||
color: report.badPatches.length ? 0xff0000 : 0x00ff00
|
||||
},
|
||||
{
|
||||
title: "Bad Webpack Finds",
|
||||
description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
|
||||
color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00
|
||||
},
|
||||
{
|
||||
title: "Bad Starts",
|
||||
description: report.badStarts.map(p => {
|
||||
const lines = [
|
||||
`**__${p.plugin}:__**`,
|
||||
toCodeBlock(p.error, 0, true)
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
).join("\n\n") || "None",
|
||||
color: report.badStarts.length ? 0xff0000 : 0x00ff00
|
||||
},
|
||||
{
|
||||
title: "Discord Errors",
|
||||
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
|
||||
color: report.otherErrors.length ? 0xff0000 : 0x00ff00
|
||||
}
|
||||
]
|
||||
embeds
|
||||
});
|
||||
|
||||
const headers = {
|
||||
|
@ -249,14 +262,17 @@ page.on("console", async e => {
|
|||
|
||||
switch (tag) {
|
||||
case "WebpackInterceptor:":
|
||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module|took [\d.]+?ms) \(Module id is (.+?)\): (.+)/)!;
|
||||
if (!patchFailMatch) break;
|
||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/);
|
||||
const patchSlowMatch = message.match(/Patch by (.+?) (took [\d.]+?ms) \(Module id is (.+?)\): (.+)/);
|
||||
const match = patchFailMatch ?? patchSlowMatch;
|
||||
if (!match) break;
|
||||
|
||||
logStderr(await getText());
|
||||
process.exitCode = 1;
|
||||
|
||||
const [, plugin, type, id, regex] = patchFailMatch;
|
||||
report.badPatches.push({
|
||||
const [, plugin, type, id, regex] = match;
|
||||
const list = patchFailMatch ? report.badPatches : report.slowPatches;
|
||||
list.push({
|
||||
plugin,
|
||||
type,
|
||||
id,
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Settings } from "@api/Settings";
|
||||
import { PluginIpcMappings } from "@main/ipcPlugins";
|
||||
import type { UserThemeHeader } from "@main/themes";
|
||||
import { IpcEvents } from "@shared/IpcEvents";
|
||||
import { IpcRes } from "@utils/types";
|
||||
import type { Settings } from "api/Settings";
|
||||
import { ipcRenderer } from "electron";
|
||||
|
||||
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
|
|
|
@ -65,7 +65,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
|||
}
|
||||
const canonicalMatch = canonicalizeMatch(new RegExp(match));
|
||||
try {
|
||||
const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin");
|
||||
const canonicalReplace = canonicalizeReplace(replacement, 'Vencord.Plugins.plugins["YourPlugin"]');
|
||||
var patched = src.replace(canonicalMatch, canonicalReplace as string);
|
||||
setReplacementError(void 0);
|
||||
} catch (e) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Logger } from "@utils/Logger";
|
|||
import { canonicalizeMatch } from "@utils/patches";
|
||||
import * as Webpack from "@webpack";
|
||||
import { wreq } from "@webpack";
|
||||
import { AnyModuleFactory, ModuleFactory } from "webpack";
|
||||
import { AnyModuleFactory, ModuleFactory } from "@webpack/wreq.d";
|
||||
|
||||
export async function loadLazyChunks() {
|
||||
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||
|
@ -20,8 +20,7 @@ export async function loadLazyChunks() {
|
|||
const invalidChunks = new Set<PropertyKey>();
|
||||
const deferredRequires = new Set<PropertyKey>();
|
||||
|
||||
let chunksSearchingResolve: (value: void) => void;
|
||||
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
||||
const { promise: chunksSearchingDone, resolve: chunksSearchingResolve } = Promise.withResolvers<void>();
|
||||
|
||||
// True if resolved, false otherwise
|
||||
const chunksSearchPromises = [] as Array<() => boolean>;
|
||||
|
@ -69,7 +68,7 @@ export async function loadLazyChunks() {
|
|||
|
||||
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||
.then(r => r.text())
|
||||
.then(t => t.includes("importScripts("));
|
||||
.then(t => /importScripts\(|self\.postMessage/.test(t));
|
||||
|
||||
if (isWorkerAsset) {
|
||||
invalidChunks.add(id);
|
||||
|
@ -140,8 +139,8 @@ export async function loadLazyChunks() {
|
|||
}
|
||||
|
||||
Webpack.factoryListeners.add(factoryListener);
|
||||
for (const factoryId in wreq.m) {
|
||||
factoryListener(wreq.m[factoryId]);
|
||||
for (const moduleId in wreq.m) {
|
||||
factoryListener(wreq.m[moduleId]);
|
||||
}
|
||||
|
||||
await chunksSearchingDone;
|
||||
|
@ -175,7 +174,7 @@ export async function loadLazyChunks() {
|
|||
await Promise.all(chunksLeft.map(async id => {
|
||||
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||
.then(r => r.text())
|
||||
.then(t => t.includes("importScripts("));
|
||||
.then(t => /importScripts\(|self\.postMessage/.test(t));
|
||||
|
||||
// Loads the chunk. Currently this only happens with the language packs which are loaded differently
|
||||
if (!isWorkerAsset) {
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
import { Logger } from "@utils/Logger";
|
||||
import * as Webpack from "@webpack";
|
||||
import { addPatch, patches } from "plugins";
|
||||
import { getBuildNumber } from "webpack/patchWebpack";
|
||||
import { getBuildNumber, patchTimings } from "@webpack/patcher";
|
||||
|
||||
import { addPatch, patches } from "../plugins";
|
||||
import { loadLazyChunks } from "./loadLazyChunks";
|
||||
|
||||
async function runReporter() {
|
||||
|
@ -17,8 +17,7 @@ async function runReporter() {
|
|||
try {
|
||||
ReporterLogger.log("Starting test...");
|
||||
|
||||
let loadLazyChunksResolve: (value: void) => void;
|
||||
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
|
||||
const { promise: loadLazyChunksDone, resolve: loadLazyChunksResolve } = Promise.withResolvers<void>();
|
||||
|
||||
// The main patch for starting the reporter chunk loading
|
||||
addPatch({
|
||||
|
@ -51,7 +50,7 @@ async function runReporter() {
|
|||
}
|
||||
}
|
||||
|
||||
for (const [plugin, moduleId, match, totalTime] of Vencord.WebpackPatcher.patchTimings) {
|
||||
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}`);
|
||||
}
|
||||
|
@ -79,9 +78,9 @@ async function runReporter() {
|
|||
result = await Webpack.extractAndLoadChunks(code, matcher);
|
||||
if (result === false) result = null;
|
||||
} else if (method === "mapMangledModule") {
|
||||
const [code, mapper] = args;
|
||||
const [code, mapper, includeBlacklistedExports] = args;
|
||||
|
||||
result = Webpack.mapMangledModule(code, mapper);
|
||||
result = Webpack.mapMangledModule(code, mapper, includeBlacklistedExports);
|
||||
if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
|
||||
} else {
|
||||
// @ts-ignore
|
||||
|
|
|
@ -16,8 +16,8 @@ export default definePlugin({
|
|||
{
|
||||
find: "SCALE_DOWN:",
|
||||
replacement: {
|
||||
match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
|
||||
replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
|
||||
match: /(?<="IMAGE"===\i\?)\i(?=\?)/,
|
||||
replace: "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { WebpackRequire } from "webpack";
|
||||
import { WebpackRequire } from "@webpack/wreq.d";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
disableAnalytics: {
|
||||
|
|
|
@ -158,6 +158,9 @@ export default definePlugin({
|
|||
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
|
||||
};
|
||||
|
||||
if (!names[settingsLocation] || names[settingsLocation].endsWith("_SETTINGS"))
|
||||
return firstChild === "PREMIUM";
|
||||
|
||||
return header === names[settingsLocation];
|
||||
} catch {
|
||||
return firstChild === "PREMIUM";
|
||||
|
|
|
@ -85,7 +85,7 @@ export default definePlugin({
|
|||
replace: "$&onRequestClose:$self.onPopoutClose,"
|
||||
},
|
||||
{
|
||||
match: /(?<=\.avatarWrapper,)/,
|
||||
match: /(?<=#{intl::SET_STATUS}\),)/,
|
||||
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -185,8 +185,8 @@ export default definePlugin({
|
|||
{
|
||||
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
||||
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||
match: /(?<=\.isExpanded\),children:\[)/,
|
||||
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
|
||||
match: /\.isExpanded\),.{0,30}children:\[/,
|
||||
replace: "$&$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
|
||||
},
|
||||
{
|
||||
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
|
||||
|
|
|
@ -26,10 +26,18 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
find: '"ChannelAttachButton"',
|
||||
replacement: {
|
||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
|
||||
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
||||
},
|
||||
replacement: [
|
||||
{
|
||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
|
||||
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
||||
noWarn: true
|
||||
},
|
||||
{
|
||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/,
|
||||
replace: "$&,onClick:$1,onContextMenu:$2.onClick,",
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -50,12 +50,24 @@ export default definePlugin({
|
|||
find: ".decorationGridItem,",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<==)\i=>{let{children.{20,100}decorationGridItem/,
|
||||
replace: "$self.DecorationGridItem=$&"
|
||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||
match: /(?<==)\i=>{let{children.{20,200}decorationGridItem/,
|
||||
replace: "$self.DecorationGridItem=$&",
|
||||
noWarn: true
|
||||
},
|
||||
{
|
||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||
match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
|
||||
replace: "$self.DecorationGridDecoration=$&"
|
||||
replace: "$self.DecorationGridDecoration=$&",
|
||||
noWarn: true
|
||||
},
|
||||
{
|
||||
match: /(?<==)\i=>{var{children.{20,200}decorationGridItem/,
|
||||
replace: "$self.DecorationGridItem=$&",
|
||||
},
|
||||
{
|
||||
match: /(?<==)\i=>{var{user:\i,avatarDecoration/,
|
||||
replace: "$self.DecorationGridDecoration=$&",
|
||||
},
|
||||
// Remove NEW label from decor avatar decorations
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { CopyIcon, DeleteIcon } from "@components/Icons";
|
||||
import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "webpack/common";
|
||||
import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "@webpack/common";
|
||||
|
||||
import { Decoration } from "../../lib/api";
|
||||
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
|
||||
|
|
|
@ -173,7 +173,7 @@ function initWs(isManual = false) {
|
|||
|
||||
try {
|
||||
const matcher = canonicalizeMatch(parseNode(match));
|
||||
const replacement = canonicalizeReplace(parseNode(replace), "PlaceHolderPluginName");
|
||||
const replacement = canonicalizeReplace(parseNode(replace), 'Vencord.Plugins.plugins["PlaceHolderPluginName"]');
|
||||
|
||||
const newSource = src.replace(matcher, replacement as string);
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { app } from "electron";
|
|||
|
||||
app.on("browser-window-created", (_, win) => {
|
||||
win.webContents.on("frame-created", (_, { frame }) => {
|
||||
frame.once("dom-ready", () => {
|
||||
frame?.once("dom-ready", () => {
|
||||
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
|
||||
const settings = RendererSettings.store.plugins?.FixSpotifyEmbeds;
|
||||
if (!settings?.enabled) return;
|
||||
|
|
|
@ -9,7 +9,7 @@ import { app } from "electron";
|
|||
|
||||
app.on("browser-window-created", (_, win) => {
|
||||
win.webContents.on("frame-created", (_, { frame }) => {
|
||||
frame.once("dom-ready", () => {
|
||||
frame?.once("dom-ready", () => {
|
||||
if (frame.url.startsWith("https://www.youtube.com/")) {
|
||||
const settings = RendererSettings.store.plugins?.FixYoutubeEmbeds;
|
||||
if (!settings?.enabled) return;
|
||||
|
|
|
@ -31,7 +31,7 @@ export default definePlugin({
|
|||
{
|
||||
name: "create friend invite",
|
||||
description: "Generates a friend invite link.",
|
||||
inputType: ApplicationCommandInputType.BOT,
|
||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
||||
|
||||
execute: async (args, ctx) => {
|
||||
const invite = await FriendInvites.createFriendInvite();
|
||||
|
@ -48,7 +48,7 @@ export default definePlugin({
|
|||
{
|
||||
name: "view friend invites",
|
||||
description: "View a list of all generated friend invites.",
|
||||
inputType: ApplicationCommandInputType.BOT,
|
||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
||||
execute: async (_, ctx) => {
|
||||
const invites = await FriendInvites.getAllFriendInvites();
|
||||
const friendInviteList = invites.map(i =>
|
||||
|
@ -67,7 +67,7 @@ export default definePlugin({
|
|||
{
|
||||
name: "revoke friend invites",
|
||||
description: "Revokes all generated friend invites.",
|
||||
inputType: ApplicationCommandInputType.BOT,
|
||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
||||
execute: async (_, ctx) => {
|
||||
await FriendInvites.revokeFriendInvites();
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Devs } from "@utils/constants";
|
|||
import { Margins } from "@utils/margins";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common";
|
||||
import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
|
||||
const enum ActivitiesTypes {
|
||||
Game,
|
||||
|
|
|
@ -34,7 +34,7 @@ export default definePlugin({
|
|||
{
|
||||
find: "#{intl::FRIENDS_ALL_HEADER}",
|
||||
replacement: {
|
||||
match: /toString\(\)\}\);case (\i\.\i)\.BLOCKED/,
|
||||
match: /toString\(\)\}\);case (\i\.\i)\.PENDING/,
|
||||
replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED'
|
||||
},
|
||||
},
|
||||
|
@ -50,7 +50,7 @@ export default definePlugin({
|
|||
{
|
||||
find: "#{intl::FRIENDS_SECTION_ONLINE}",
|
||||
replacement: {
|
||||
match: /,{id:(\i\.\i)\.BLOCKED,show:.+?className:(\i\.item)/,
|
||||
match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/,
|
||||
replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}`
|
||||
}
|
||||
},
|
||||
|
@ -58,7 +58,7 @@ export default definePlugin({
|
|||
{
|
||||
find: '"FriendsStore"',
|
||||
replacement: {
|
||||
match: /(?<=case (\i\.\i)\.BLOCKED:return (\i)\.type===\i\.\i\.BLOCKED)/,
|
||||
match: /(?<=case (\i\.\i)\.SUGGESTIONS:return \d+===(\i)\.type)/,
|
||||
replace: ";case $1.IMPLICIT:return $2.type===5"
|
||||
},
|
||||
},
|
||||
|
|
|
@ -31,6 +31,7 @@ import { Logger } from "@utils/Logger";
|
|||
import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches";
|
||||
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
import { patches } from "@webpack/patcher";
|
||||
import { FluxEvents } from "@webpack/types";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
@ -41,7 +42,7 @@ const logger = new Logger("PluginManager", "#a6d189");
|
|||
|
||||
export const PMLogger = logger;
|
||||
export const plugins = Plugins;
|
||||
export const patches = [] as Patch[];
|
||||
export { patches };
|
||||
|
||||
/** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */
|
||||
let enabledPluginsSubscribedFlux = false;
|
||||
|
|
|
@ -65,10 +65,18 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
find: "{isSidebarVisible:",
|
||||
replacement: {
|
||||
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
||||
replace: ":[$1?.startsWith('members')?$self.render():null,$2"
|
||||
},
|
||||
replacement: [
|
||||
{
|
||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
||||
replace: ":[$1?.startsWith('members')?$self.render():null,$2",
|
||||
noWarn: true
|
||||
},
|
||||
{
|
||||
match: /(?<=var\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
||||
replace: ":[$1?.startsWith('members')?$self.render():null,$2",
|
||||
},
|
||||
],
|
||||
predicate: () => settings.store.memberList
|
||||
},
|
||||
{
|
||||
|
|
|
@ -84,8 +84,14 @@ export default definePlugin({
|
|||
find: ".USER_MENTION)",
|
||||
replacement: [
|
||||
{
|
||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||
match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/,
|
||||
replace: "$&,color:$self.getColorInt($1?.id,$2?.id)"
|
||||
replace: "$&,color:$self.getColorInt($1?.id,$2?.id)",
|
||||
noWarn: true
|
||||
},
|
||||
{
|
||||
match: /(?<=onContextMenu:\i,color:)\i(?=\},\i\),\{children)(?<=user:(\i),channel:(\i).{0,500}?)/,
|
||||
replace: "$self.getColorInt($1?.id,$2?.id)",
|
||||
}
|
||||
],
|
||||
predicate: () => settings.store.chatMentions
|
||||
|
|
|
@ -96,6 +96,6 @@
|
|||
|
||||
.vc-shiki-root .vc-shiki-table-cell:last-child {
|
||||
padding-left: 8px;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ export default definePlugin({
|
|||
replacement: [
|
||||
// Make the channel appear as muted if it's hidden
|
||||
{
|
||||
match: /{channel:(\i),name:\i,muted:(\i).+?;/,
|
||||
match: /\.subtitle,.+?;(?=return\(0,\i\.jsxs?\))(?<={channel:(\i),name:\i,muted:(\i).+?;)/,
|
||||
replace: (m, channel, muted) => `${m}${muted}=$self.isHiddenChannel(${channel})?true:${muted};`
|
||||
},
|
||||
// Add the hidden eye icon if the channel is hidden
|
||||
|
@ -204,7 +204,7 @@ export default definePlugin({
|
|||
{
|
||||
// Hide unreads
|
||||
predicate: () => settings.store.hideUnreads === true,
|
||||
match: /{channel:(\i),name:\i,.+?unread:(\i).+?;/,
|
||||
match: /\.subtitle,.+?;(?=return\(0,\i\.jsxs?\))(?<={channel:(\i),name:\i,.+?unread:(\i).+?)/,
|
||||
replace: (m, channel, unread) => `${m}${unread}=$self.isHiddenChannel(${channel})?false:${unread};`
|
||||
}
|
||||
]
|
||||
|
@ -485,7 +485,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: '="NowPlayingViewStore",',
|
||||
find: '"NowPlayingViewStore"',
|
||||
replacement: {
|
||||
// Make active now voice states on hidden channels
|
||||
match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/,
|
||||
|
|
|
@ -77,7 +77,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
|
|||
|
||||
class SpotifyStore extends Store {
|
||||
public mPosition = 0;
|
||||
private start = 0;
|
||||
public _start = 0;
|
||||
|
||||
public track: Track | null = null;
|
||||
public device: Device | null = null;
|
||||
|
@ -100,26 +100,26 @@ export const SpotifyStore = proxyLazyWebpack(() => {
|
|||
public get position(): number {
|
||||
let pos = this.mPosition;
|
||||
if (this.isPlaying) {
|
||||
pos += Date.now() - this.start;
|
||||
pos += Date.now() - this._start;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
public set position(p: number) {
|
||||
this.mPosition = p;
|
||||
this.start = Date.now();
|
||||
this._start = Date.now();
|
||||
}
|
||||
|
||||
prev() {
|
||||
this.req("post", "/previous");
|
||||
this._req("post", "/previous");
|
||||
}
|
||||
|
||||
next() {
|
||||
this.req("post", "/next");
|
||||
this._req("post", "/next");
|
||||
}
|
||||
|
||||
setVolume(percent: number) {
|
||||
this.req("put", "/volume", {
|
||||
this._req("put", "/volume", {
|
||||
query: {
|
||||
volume_percent: Math.round(percent)
|
||||
}
|
||||
|
@ -131,17 +131,17 @@ export const SpotifyStore = proxyLazyWebpack(() => {
|
|||
}
|
||||
|
||||
setPlaying(playing: boolean) {
|
||||
this.req("put", playing ? "/play" : "/pause");
|
||||
this._req("put", playing ? "/play" : "/pause");
|
||||
}
|
||||
|
||||
setRepeat(state: Repeat) {
|
||||
this.req("put", "/repeat", {
|
||||
this._req("put", "/repeat", {
|
||||
query: { state }
|
||||
});
|
||||
}
|
||||
|
||||
setShuffle(state: boolean) {
|
||||
this.req("put", "/shuffle", {
|
||||
this._req("put", "/shuffle", {
|
||||
query: { state }
|
||||
}).then(() => {
|
||||
this.shuffle = state;
|
||||
|
@ -154,7 +154,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
|
|||
|
||||
this.isSettingPosition = true;
|
||||
|
||||
return this.req("put", "/seek", {
|
||||
return this._req("put", "/seek", {
|
||||
query: {
|
||||
position_ms: Math.round(ms)
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
|
|||
});
|
||||
}
|
||||
|
||||
private req(method: "post" | "get" | "put", route: string, data: any = {}) {
|
||||
_req(method: "post" | "get" | "put", route: string, data: any = {}) {
|
||||
if (this.device?.is_active)
|
||||
(data.query ??= {}).device_id = this.device.id;
|
||||
|
||||
|
|
|
@ -27,12 +27,22 @@ export default definePlugin({
|
|||
authors: [Devs.Megu],
|
||||
patches: [{
|
||||
find: "#{intl::ACTIVITY_SETTINGS}",
|
||||
replacement: {
|
||||
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
|
||||
replace: (_, commaOrSemi, settings, elements) => "" +
|
||||
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
|
||||
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`
|
||||
}
|
||||
replacement: [
|
||||
{
|
||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
|
||||
replace: (_, commaOrSemi, settings, elements) => "" +
|
||||
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
|
||||
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`,
|
||||
noWarn: true
|
||||
},
|
||||
{
|
||||
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+\)\)\}\))(?=\)\})/,
|
||||
replace: (_, commaOrSemi, settings, elements) => "" +
|
||||
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
|
||||
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`,
|
||||
},
|
||||
]
|
||||
}],
|
||||
StartupTimingPage
|
||||
});
|
||||
|
|
|
@ -41,11 +41,20 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
find: '="SYSTEM_TAG"',
|
||||
replacement: {
|
||||
// Add next to username (compact mode)
|
||||
match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g,
|
||||
replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),"
|
||||
}
|
||||
replacement: [
|
||||
{
|
||||
// Add next to username (compact mode)
|
||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||
match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g,
|
||||
replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),",
|
||||
noWarn: true
|
||||
},
|
||||
{
|
||||
// Add next to username (compact mode)
|
||||
match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\)\),(?=\i)/g,
|
||||
replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),",
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
|
|||
);
|
||||
}
|
||||
|
||||
interface VoiceChannelIndicatorProps {
|
||||
export interface VoiceChannelIndicatorProps {
|
||||
userId: string;
|
||||
isActionButton?: boolean;
|
||||
shouldHighlight?: boolean;
|
||||
|
|
|
@ -193,10 +193,18 @@ export default definePlugin({
|
|||
// Avatar component used in User DMs "User Profile" popup in the right and Profiles Modal pfp
|
||||
{
|
||||
find: ".overlay:void 0,status:",
|
||||
replacement: {
|
||||
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/,
|
||||
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},"
|
||||
},
|
||||
replacement: [
|
||||
{
|
||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/,
|
||||
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
|
||||
noWarn: true
|
||||
},
|
||||
{
|
||||
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",.{0,100}className:\i,/,
|
||||
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
|
||||
}
|
||||
],
|
||||
all: true
|
||||
},
|
||||
// Banners
|
||||
|
|
|
@ -10,7 +10,7 @@ import adguard from "file://adguard.js?minify";
|
|||
|
||||
app.on("browser-window-created", (_, win) => {
|
||||
win.webContents.on("frame-created", (_, { frame }) => {
|
||||
frame.once("dom-ready", () => {
|
||||
frame?.once("dom-ready", () => {
|
||||
if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return;
|
||||
|
||||
if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) {
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
@media(width <= 485px) {
|
||||
.vc-image-modal {
|
||||
display: relative;
|
||||
overflow: visible;
|
||||
overflow: initial;
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ export function makeLazy<T>(factory: () => T, attempts = 5): () => T {
|
|||
let tries = 0;
|
||||
let cache: T;
|
||||
return () => {
|
||||
if (!cache && attempts > tries++) {
|
||||
if (cache === undefined && attempts > tries++) {
|
||||
cache = factory();
|
||||
if (!cache && attempts === tries)
|
||||
if (cache === undefined && attempts === tries)
|
||||
console.error("Lazy factory failed:", factory);
|
||||
}
|
||||
return cache;
|
||||
|
|
|
@ -42,7 +42,12 @@ export interface PatchReplacement {
|
|||
match: string | RegExp;
|
||||
/** The replacement string or function which returns the string for the patch replacement */
|
||||
replace: string | ReplaceFn;
|
||||
/** A function which returns whether this patch replacement should be applied */
|
||||
/** Do not warn if this replacement did no changes */
|
||||
noWarn?: boolean;
|
||||
/**
|
||||
* A function which returns whether this patch replacement should be applied.
|
||||
* This is ran before patches are registered, so if this returns false, the patch will never be registered.
|
||||
*/
|
||||
predicate?(): boolean;
|
||||
/** The minimum build number for this patch to be applied */
|
||||
fromBuild?: number;
|
||||
|
@ -62,7 +67,10 @@ export interface Patch {
|
|||
noWarn?: boolean;
|
||||
/** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */
|
||||
group?: boolean;
|
||||
/** A function which returns whether this patch should be applied */
|
||||
/**
|
||||
* A function which returns whether this patch replacement should be applied.
|
||||
* This is ran before patches are registered, so if this returns false, the patch will never be registered.
|
||||
*/
|
||||
predicate?(): boolean;
|
||||
/** The minimum build number for this patch to be applied */
|
||||
fromBuild?: number;
|
||||
|
|
|
@ -41,7 +41,7 @@ export const Switch = waitForComponent<t.Switch>("Switch", filters.componentByCo
|
|||
|
||||
const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", {
|
||||
Tooltip: filters.componentByCode("this.renderTooltip()]"),
|
||||
TooltipContainer: filters.componentByCode('="div",')
|
||||
TooltipContainer: filters.componentByCode('="div"')
|
||||
}) as {
|
||||
Tooltip: t.Tooltip,
|
||||
TooltipContainer: t.TooltipContainer;
|
||||
|
|
|
@ -29,7 +29,7 @@ export type GenericStore = t.FluxStore & Record<string, any>;
|
|||
|
||||
export const DraftType = findByPropsLazy("ChannelMessage", "SlashCommand");
|
||||
|
||||
export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & {
|
||||
export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & GenericStore & {
|
||||
getMessages(chanId: string): any;
|
||||
};
|
||||
|
||||
|
|
2
src/webpack/common/types/components.d.ts
vendored
2
src/webpack/common/types/components.d.ts
vendored
|
@ -496,7 +496,7 @@ export type Avatar = ComponentType<PropsWithChildren<{
|
|||
}>>;
|
||||
|
||||
type FocusLock = ComponentType<PropsWithChildren<{
|
||||
containerRef: RefObject<HTMLElement>;
|
||||
containerRef: Ref<HTMLElement>;
|
||||
}>>;
|
||||
|
||||
export type Icon = ComponentType<JSX.IntrinsicElements["svg"] & {
|
||||
|
|
3
src/webpack/common/types/stores.d.ts
vendored
3
src/webpack/common/types/stores.d.ts
vendored
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { DraftType } from "@webpack/common";
|
||||
import { Channel, Guild, Role } from "discord-types/general";
|
||||
|
||||
import { FluxDispatcher, FluxEvents } from "./utils";
|
||||
|
@ -234,7 +233,7 @@ export class PopoutWindowStore extends FluxStore {
|
|||
}
|
||||
|
||||
export type useStateFromStores = <T>(
|
||||
stores: t.FluxStore[],
|
||||
stores: any[],
|
||||
mapper: () => T,
|
||||
dependencies?: any,
|
||||
isEqual?: (old: T, newer: T) => boolean
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { runtimeHashMessageKey } from "@utils/intlHash";
|
||||
import type { Channel } from "discord-types/general";
|
||||
|
||||
// eslint-disable-next-line path-alias/no-relative
|
||||
|
@ -58,9 +57,9 @@ export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = ma
|
|||
export const lodash: typeof import("lodash") = findByPropsLazy("debounce", "cloneDeep");
|
||||
|
||||
export const i18n = mapMangledModuleLazy('defaultLocale:"en-US"', {
|
||||
intl: filters.byProps("string", "format"),
|
||||
t: filters.byProps(runtimeHashMessageKey("DISCORD"))
|
||||
});
|
||||
t: m => m?.[Symbol.toStringTag] === "IntlMessagesProxy",
|
||||
intl: m => m != null && Object.getPrototypeOf(m)?.withFormatters != null
|
||||
}, true);
|
||||
|
||||
export let SnowflakeUtils: t.SnowflakeUtils;
|
||||
waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m);
|
||||
|
|
|
@ -9,63 +9,63 @@ import { makeLazy } from "@utils/lazy";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { interpolateIfDefined } from "@utils/misc";
|
||||
import { canonicalizeReplacement } from "@utils/patches";
|
||||
import { PatchReplacement } from "@utils/types";
|
||||
import { Patch, PatchReplacement } from "@utils/types";
|
||||
|
||||
import { traceFunctionWithResults } from "../debug/Tracer";
|
||||
import { patches } from "../plugins";
|
||||
import { _initWebpack, _shouldIgnoreModule, AnyModuleFactory, AnyWebpackRequire, factoryListeners, findModuleId, MaybeWrappedModuleFactory, ModuleExports, moduleListeners, waitForSubscriptions, WebpackRequire, WrappedModuleFactory, wreq } from ".";
|
||||
import { _blacklistBadModules, _initWebpack, factoryListeners, findModuleFactory, moduleListeners, waitForSubscriptions, wreq } from "./webpack";
|
||||
import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, PatchedModuleFactory, WebpackRequire } from "./wreq.d";
|
||||
|
||||
export const patches = [] as Patch[];
|
||||
|
||||
export const SYM_IS_PROXIED_FACTORY = Symbol("WebpackPatcher.isProxiedFactory");
|
||||
export const SYM_ORIGINAL_FACTORY = Symbol("WebpackPatcher.originalFactory");
|
||||
export const SYM_PATCHED_SOURCE = Symbol("WebpackPatcher.patchedSource");
|
||||
export const SYM_PATCHED_BY = Symbol("WebpackPatcher.patchedBy");
|
||||
/** A set with all the Webpack instances */
|
||||
export const allWebpackInstances = new Set<AnyWebpackRequire>();
|
||||
export const patchTimings = [] as Array<[plugin: string, moduleId: PropertyKey, match: string | RegExp, totalTime: number]>;
|
||||
|
||||
const logger = new Logger("WebpackInterceptor", "#8caaee");
|
||||
/** Whether we tried to fallback to factory WebpackRequire, or disabled patches */
|
||||
let wreqFallbackApplied = false;
|
||||
/** Whether we should be patching factories.
|
||||
*
|
||||
* This should be disabled if we start searching for the module to get the build number, and then resumed once it's done.
|
||||
* */
|
||||
let shouldPatchFactories = true;
|
||||
export const patchTimings = [] as Array<[plugin: string, moduleId: PropertyKey, match: PatchReplacement["match"], totalTime: number]>;
|
||||
|
||||
export const getBuildNumber = makeLazy(() => {
|
||||
try {
|
||||
shouldPatchFactories = false;
|
||||
|
||||
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();
|
||||
}
|
||||
function matchBuildNumber(factoryStr: string) {
|
||||
const buildNumberMatch = factoryStr.match(/.concat\("(\d+?)"\)/);
|
||||
if (buildNumberMatch == null) {
|
||||
return -1;
|
||||
}
|
||||
} catch { }
|
||||
|
||||
const moduleId = findModuleId("Trying to open a changelog for an invalid build number");
|
||||
if (moduleId == null) {
|
||||
return -1;
|
||||
return Number(buildNumberMatch[1]);
|
||||
}
|
||||
|
||||
const exports = Object.values<ModuleExports>(wreq(moduleId));
|
||||
if (exports.length !== 1 || typeof exports[0] !== "function") {
|
||||
return -1;
|
||||
const hardcodedFactoryStr = String(wreq.m[128014]);
|
||||
if (hardcodedFactoryStr.includes("Trying to open a changelog for an invalid build number")) {
|
||||
const hardcodedBuildNumber = matchBuildNumber(hardcodedFactoryStr);
|
||||
|
||||
if (hardcodedBuildNumber !== -1) {
|
||||
return hardcodedBuildNumber;
|
||||
}
|
||||
}
|
||||
|
||||
const buildNumber = exports[0]();
|
||||
return typeof buildNumber === "number" ? buildNumber : -1;
|
||||
const moduleFactory = findModuleFactory("Trying to open a changelog for an invalid build number");
|
||||
return matchBuildNumber(String(moduleFactory));
|
||||
} catch {
|
||||
return -1;
|
||||
} finally {
|
||||
shouldPatchFactories = true;
|
||||
}
|
||||
});
|
||||
|
||||
type Define = typeof Reflect.defineProperty;
|
||||
const define: Define = (target, p, attributes) => {
|
||||
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");
|
||||
|
||||
/** Whether we tried to fallback to the WebpackRequire of the factory, or disabled patches */
|
||||
let wreqFallbackApplied = false;
|
||||
|
||||
const define: typeof Reflect.defineProperty = (target, p, attributes) => {
|
||||
if (Object.hasOwn(attributes, "value")) {
|
||||
attributes.writable = true;
|
||||
}
|
||||
|
@ -77,22 +77,18 @@ const define: Define = (target, p, attributes) => {
|
|||
});
|
||||
};
|
||||
|
||||
export function getOriginalFactory(id: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) {
|
||||
const moduleFactory = webpackRequire.m[id];
|
||||
return (moduleFactory?.[SYM_ORIGINAL_FACTORY] ?? moduleFactory) as AnyModuleFactory | undefined;
|
||||
}
|
||||
// wreq.m is the Webpack object containing module factories. It is pre-populated with factories, and is also populated via webpackGlobal.push
|
||||
// We use this setter to intercept when wreq.m is defined and setup our setters which decide whether we should patch these module factories
|
||||
// and the Webpack instance where they are being defined.
|
||||
|
||||
export function getFactoryPatchedSource(id: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) {
|
||||
return webpackRequire.m[id]?.[SYM_PATCHED_SOURCE];
|
||||
}
|
||||
// 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.
|
||||
|
||||
export function getFactoryPatchedBy(id: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) {
|
||||
return webpackRequire.m[id]?.[SYM_PATCHED_BY];
|
||||
}
|
||||
// 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 the Webpack object containing module factories. It is pre-populated with module factories, and is also populated via webpackGlobal.push
|
||||
// We use this setter to intercept when wreq.m is defined and apply the patching in its module factories.
|
||||
// We wrap wreq.m with our proxy, which is responsible for patching the module factories when they are set, or defining getters for the patched versions.
|
||||
// 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", {
|
||||
|
@ -101,7 +97,7 @@ define(Function.prototype, "m", {
|
|||
set(this: AnyWebpackRequire, originalModules: AnyWebpackRequire["m"]) {
|
||||
define(this, "m", { value: originalModules });
|
||||
|
||||
// Ensure this is one of Discord main Webpack instances.
|
||||
// Ensure this is likely one of Discord main Webpack instances.
|
||||
// We may catch Discord bundled libs, React Devtools or other extensions Webpack instances here.
|
||||
const { stack } = new Error();
|
||||
if (!stack?.includes("http") || stack.match(/at \d+? \(/) || !String(this).includes("exports:{}")) {
|
||||
|
@ -109,53 +105,108 @@ define(Function.prototype, "m", {
|
|||
}
|
||||
|
||||
const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1];
|
||||
logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
|
||||
|
||||
allWebpackInstances.add(this);
|
||||
|
||||
// Define a setter for the ensureChunk property of WebpackRequire. Only the main Webpack (which is the only that includes chunk loading) has this property.
|
||||
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
|
||||
define(this, "e", {
|
||||
// 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,
|
||||
|
||||
set(this: WebpackRequire, ensureChunk: WebpackRequire["e"]) {
|
||||
define(this, "e", { value: ensureChunk });
|
||||
clearTimeout(setterTimeout);
|
||||
set(this: AnyWebpackRequire, bundlePath: NonNullable<AnyWebpackRequire["p"]>) {
|
||||
define(this, "p", { value: bundlePath });
|
||||
clearTimeout(bundlePathTimeout);
|
||||
|
||||
logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
|
||||
_initWebpack(this);
|
||||
if (bundlePath !== "/assets/") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wreq == null && this.c != null) {
|
||||
logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
|
||||
_initWebpack(this as WebpackRequire);
|
||||
}
|
||||
|
||||
patchThisInstance();
|
||||
}
|
||||
});
|
||||
// setImmediate to clear this property setter if this is not the main Webpack.
|
||||
// If this is the main Webpack, wreq.e will always be set before the timeout runs.
|
||||
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "e"), 0);
|
||||
|
||||
// Patch the pre-populated factories
|
||||
for (const id in originalModules) {
|
||||
if (updateExistingFactory(originalModules, id, originalModules[id], true)) {
|
||||
continue;
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
notifyFactoryListeners(originalModules[id]);
|
||||
defineModulesFactoryGetter(id, Settings.eagerPatches && shouldPatchFactories ? wrapAndPatchFactory(id, originalModules[id]) : originalModules[id]);
|
||||
}
|
||||
|
||||
define(originalModules, Symbol.toStringTag, {
|
||||
value: "ModuleFactories",
|
||||
enumerable: false
|
||||
});
|
||||
|
||||
// The proxy responsible for patching the module factories when they are set, or defining getters for the patched versions
|
||||
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));
|
||||
*/
|
||||
// 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);
|
||||
|
||||
define(this, "m", { value: proxiedModuleFactories });
|
||||
/**
|
||||
* Patch the current Webpack instance assigned to `this` context.
|
||||
* This should only be called if this instance was later found to be one we need to patch.
|
||||
*/
|
||||
const patchThisInstance = () => {
|
||||
logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
|
||||
allWebpackInstances.add(this);
|
||||
|
||||
// Proxy (and maybe patch) pre-populated factories
|
||||
for (const moduleId in originalModules) {
|
||||
updateExistingOrProxyFactory(originalModules, moduleId, originalModules[moduleId], originalModules, true);
|
||||
}
|
||||
|
||||
define(originalModules, Symbol.toStringTag, {
|
||||
value: "ModuleFactories",
|
||||
enumerable: false
|
||||
});
|
||||
|
||||
const proxiedModuleFactories = new Proxy(originalModules, moduleFactoriesHandler);
|
||||
/*
|
||||
If Webpack ever decides to set module factories using the variable of the modules object directly, instead of wreq.m, switch the proxy to the prototype
|
||||
Reflect.setPrototypeOf(originalModules, new Proxy(originalModules, moduleFactoriesHandler));
|
||||
*/
|
||||
|
||||
define(this, "m", { value: proxiedModuleFactories });
|
||||
|
||||
// Overwrite Webpack's defineExports function to define the export descriptors configurable.
|
||||
// This is needed so we can later blacklist specific exports from Webpack search by making them non-enumerable
|
||||
this.d = function (exports, definition) {
|
||||
for (const key in definition) {
|
||||
if (Object.hasOwn(definition, key) && !Object.hasOwn(exports, key)) {
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: definition[key],
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
|
@ -172,71 +223,127 @@ const moduleFactoriesHandler: ProxyHandler<AnyWebpackRequire["m"]> = {
|
|||
},
|
||||
*/
|
||||
|
||||
// The set trap for patching or defining getters for the module factories when new module factories are loaded
|
||||
set(target, p, newValue, receiver) {
|
||||
if (updateExistingFactory(target, p, newValue)) {
|
||||
set: updateExistingOrProxyFactory
|
||||
};
|
||||
|
||||
// The proxy for patching lazily and/or running factories with our wrapper.
|
||||
const moduleFactoryHandler: ProxyHandler<MaybePatchedModuleFactory> = {
|
||||
apply(target, thisArg: unknown, argArray: Parameters<AnyModuleFactory>) {
|
||||
// SYM_ORIGINAL_FACTORY means the factory has already been patched
|
||||
if (target[SYM_ORIGINAL_FACTORY] != null) {
|
||||
return runFactoryWithWrap(target as PatchedModuleFactory, thisArg, argArray);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
notifyFactoryListeners(newValue);
|
||||
defineModulesFactoryGetter(p, Settings.eagerPatches && shouldPatchFactories ? wrapAndPatchFactory(p, newValue) : newValue);
|
||||
const originalFactory: AnyModuleFactory = target[SYM_ORIGINAL_FACTORY] ?? target;
|
||||
|
||||
return true;
|
||||
// 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 factory that exists in any Webpack instance with a new original factory.
|
||||
* Update a duplicated factory that exists in any of the Webpack instances we track with a new original factory.
|
||||
*
|
||||
* @target The module factories where this new original factory is being set
|
||||
* @param id The id of the module
|
||||
* @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 ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactoriesTarget
|
||||
* @returns Whether the original factory was updated, or false if it doesn't exist in any Webpack instance
|
||||
* @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(moduleFactoriesTarget: AnyWebpackRequire["m"], id: PropertyKey, newFactory: AnyModuleFactory, ignoreExistingInTarget: boolean = false) {
|
||||
let existingFactory: TypedPropertyDescriptor<AnyModuleFactory> | undefined;
|
||||
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 === moduleFactoriesTarget) continue;
|
||||
if (ignoreExistingInTarget && wreq.m === moduleFactories) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Object.hasOwn(wreq.m, id)) {
|
||||
existingFactory = Reflect.getOwnPropertyDescriptor(wreq.m, id);
|
||||
if (Object.hasOwn(wreq.m, moduleId)) {
|
||||
existingFactory = wreq.m[moduleId];
|
||||
moduleFactoriesWithFactory = wreq.m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingFactory != null) {
|
||||
// If existingFactory exists in any Webpack instance, it's either wrapped in defineModuleFactoryGetter, or it has already been required.
|
||||
// So define the descriptor of it on this current Webpack instance (if it doesn't exist already), call Reflect.set with the new original,
|
||||
// and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
|
||||
|
||||
if (moduleFactoriesWithFactory !== moduleFactoriesTarget) {
|
||||
Reflect.defineProperty(moduleFactoriesTarget, id, existingFactory);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Persist patched source and patched by in the new original factory, if the patched one has already been required
|
||||
if (IS_DEV && existingFactory.value != null) {
|
||||
newFactory[SYM_PATCHED_SOURCE] = existingFactory.value[SYM_PATCHED_SOURCE];
|
||||
newFactory[SYM_PATCHED_BY] = existingFactory.value[SYM_PATCHED_BY];
|
||||
// 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;
|
||||
}
|
||||
|
||||
return Reflect.set(moduleFactoriesTarget, id, newFactory, moduleFactoriesTarget);
|
||||
// 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(factory: AnyModuleFactory) {
|
||||
function notifyFactoryListeners(moduleId: PropertyKey, factory: AnyModuleFactory) {
|
||||
for (const factoryListener of factoryListeners) {
|
||||
try {
|
||||
factoryListener(factory);
|
||||
factoryListener(factory, moduleId);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack factory listener:\n", err, factoryListener);
|
||||
}
|
||||
|
@ -244,213 +351,148 @@ function notifyFactoryListeners(factory: AnyModuleFactory) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Define the getter for returning the patched version of the module factory.
|
||||
* Run a (possibly) patched module factory with a wrapper which notifies our listeners.
|
||||
*
|
||||
* If eagerPatches is enabled, the factory argument should already be the patched version, else it will be the original
|
||||
* and only be patched when accessed for the first time.
|
||||
*
|
||||
* @param id The id of the module
|
||||
* @param factory The original or patched module factory
|
||||
* @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 defineModulesFactoryGetter(id: PropertyKey, factory: MaybeWrappedModuleFactory) {
|
||||
const descriptor: PropertyDescriptor = {
|
||||
get() {
|
||||
// SYM_ORIGINAL_FACTORY means the factory is already patched
|
||||
if (!shouldPatchFactories || factory[SYM_ORIGINAL_FACTORY] != null) {
|
||||
return factory;
|
||||
}
|
||||
function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unknown, argArray: Parameters<MaybePatchedModuleFactory>) {
|
||||
const originalFactory = patchedFactory[SYM_ORIGINAL_FACTORY];
|
||||
|
||||
return (factory = wrapAndPatchFactory(id, factory));
|
||||
},
|
||||
set(newFactory: MaybeWrappedModuleFactory) {
|
||||
if (IS_DEV) {
|
||||
newFactory[SYM_PATCHED_SOURCE] = factory[SYM_PATCHED_SOURCE];
|
||||
newFactory[SYM_PATCHED_BY] = factory[SYM_PATCHED_BY];
|
||||
}
|
||||
|
||||
if (factory[SYM_ORIGINAL_FACTORY] != null) {
|
||||
factory.toString = newFactory.toString.bind(newFactory);
|
||||
factory[SYM_ORIGINAL_FACTORY] = newFactory;
|
||||
} else {
|
||||
factory = newFactory;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Define the getter in all the module factories objects. Patches are only executed once, so make sure all module factories object
|
||||
// have the patched version
|
||||
for (const wreq of allWebpackInstances) {
|
||||
define(wreq.m, id, descriptor);
|
||||
if (patchedFactory === originalFactory) {
|
||||
// @ts-expect-error Clear up ORIGINAL_FACTORY if the factory did not have any patch applied
|
||||
delete patchedFactory[SYM_ORIGINAL_FACTORY];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps and patches a module factory.
|
||||
*
|
||||
* @param id The id of the module
|
||||
* @param factory The original or patched module factory
|
||||
* @returns The wrapper for the patched module factory
|
||||
*/
|
||||
function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory) {
|
||||
const [patchedFactory, patchedSource, patchedBy] = patchFactory(id, originalFactory);
|
||||
let [module, exports, require] = argArray;
|
||||
|
||||
const wrappedFactory: WrappedModuleFactory = function (...args) {
|
||||
// Restore the original factory in all the module factories objects. We want to make sure the original factory is restored properly, no matter what is the Webpack instance
|
||||
for (const wreq of allWebpackInstances) {
|
||||
define(wreq.m, id, { value: wrappedFactory[SYM_ORIGINAL_FACTORY] });
|
||||
}
|
||||
// Restore the original factory in all the module factories objects, discarding our proxy and allowing it to be garbage collected
|
||||
defineInWebpackInstances(module.id, originalFactory);
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [module, exports, require] = args;
|
||||
if (wreq == null) {
|
||||
if (!wreqFallbackApplied) {
|
||||
wreqFallbackApplied = true;
|
||||
|
||||
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];
|
||||
|
||||
// Make sure the require argument is actually the WebpackRequire function
|
||||
if (typeof require === "function" && require.m != 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}` +
|
||||
")"
|
||||
);
|
||||
|
||||
logger.warn(
|
||||
"WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
|
||||
`id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
|
||||
")"
|
||||
);
|
||||
|
||||
_initWebpack(require as WebpackRequire);
|
||||
} else if (IS_DEV) {
|
||||
logger.error("WebpackRequire was not initialized, running modules without patches instead.");
|
||||
return wrappedFactory[SYM_ORIGINAL_FACTORY].apply(this, args);
|
||||
}
|
||||
// Could technically be wrong, but it's better than nothing
|
||||
_initWebpack(require as WebpackRequire);
|
||||
} else if (IS_DEV) {
|
||||
return wrappedFactory[SYM_ORIGINAL_FACTORY].apply(this, args);
|
||||
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;
|
||||
}
|
||||
|
||||
let factoryReturn: unknown;
|
||||
try {
|
||||
// Call the patched factory
|
||||
factoryReturn = patchedFactory.apply(this, args);
|
||||
} catch (err) {
|
||||
// Just re-throw Discord errors
|
||||
if (patchedFactory === wrappedFactory[SYM_ORIGINAL_FACTORY]) {
|
||||
throw err;
|
||||
}
|
||||
logger.error("Error in patched module factory:\n", err);
|
||||
return originalFactory.apply(thisArg, argArray);
|
||||
}
|
||||
|
||||
logger.error("Error in patched module factory:\n", err);
|
||||
return wrappedFactory[SYM_ORIGINAL_FACTORY].apply(this, args);
|
||||
}
|
||||
exports = module.exports;
|
||||
|
||||
exports = module.exports;
|
||||
if (exports == null) {
|
||||
if (typeof require === "function" && require.c) {
|
||||
if (_blacklistBadModules(require.c, exports, module.id)) {
|
||||
return factoryReturn;
|
||||
}
|
||||
|
||||
if (typeof require === "function") {
|
||||
const shouldIgnoreModule = _shouldIgnoreModule(exports);
|
||||
|
||||
if (shouldIgnoreModule) {
|
||||
if (require.c != null) {
|
||||
Object.defineProperty(require.c, id, {
|
||||
value: require.c[id],
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
}
|
||||
|
||||
return factoryReturn;
|
||||
}
|
||||
}
|
||||
|
||||
for (const callback of moduleListeners) {
|
||||
try {
|
||||
callback(exports, id);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack module listener:\n", err, callback);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [filter, callback] of waitForSubscriptions) {
|
||||
try {
|
||||
if (filter(exports)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exports, id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof exports !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const exportKey in exports) {
|
||||
const exportValue = exports[exportKey];
|
||||
|
||||
if (exportValue != null && filter(exportValue)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exportValue, id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("Error while firing callback for Webpack waitFor subscription:\n", err, filter, callback);
|
||||
}
|
||||
}
|
||||
|
||||
return factoryReturn;
|
||||
};
|
||||
|
||||
wrappedFactory.toString = originalFactory.toString.bind(originalFactory);
|
||||
wrappedFactory[SYM_ORIGINAL_FACTORY] = originalFactory;
|
||||
|
||||
if (IS_DEV && patchedFactory !== originalFactory) {
|
||||
wrappedFactory[SYM_PATCHED_SOURCE] = patchedSource;
|
||||
wrappedFactory[SYM_PATCHED_BY] = patchedBy;
|
||||
originalFactory[SYM_PATCHED_SOURCE] = patchedSource;
|
||||
originalFactory[SYM_PATCHED_BY] = patchedBy;
|
||||
}
|
||||
|
||||
// @ts-expect-error Allow GC to get into action, if possible
|
||||
originalFactory = undefined;
|
||||
return wrappedFactory;
|
||||
if (exports == null) {
|
||||
return factoryReturn;
|
||||
}
|
||||
|
||||
for (const callback of moduleListeners) {
|
||||
try {
|
||||
callback(exports, module.id);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack module listener:\n", err, callback);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [filter, callback] of waitForSubscriptions) {
|
||||
try {
|
||||
if (filter(exports)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exports, module.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof exports !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const exportKey in exports) {
|
||||
const exportValue = exports[exportKey];
|
||||
|
||||
if (exportValue != null && filter(exportValue)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exportValue, module.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("Error while firing callback for Webpack waitFor subscription:\n", err, filter, callback);
|
||||
}
|
||||
}
|
||||
|
||||
return factoryReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches a module factory.
|
||||
*
|
||||
* @param id The id of the module
|
||||
* @param factory The original module factory
|
||||
* @returns The patched module factory, the patched source of it, and the plugins that patched it
|
||||
* @param moduleId The id of the module
|
||||
* @param originalFactory The original module factory
|
||||
* @returns The patched module factory
|
||||
*/
|
||||
function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFactory: AnyModuleFactory, patchedSource: string, patchedBy: Set<string>] {
|
||||
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(factory);
|
||||
let code: string = "0," + String(originalFactory);
|
||||
let patchedSource = code;
|
||||
let patchedFactory = factory;
|
||||
let patchedFactory = originalFactory;
|
||||
|
||||
const patchedBy = new Set<string>();
|
||||
|
||||
for (let i = 0; i < patches.length; i++) {
|
||||
const patch = patches[i];
|
||||
|
||||
const moduleMatches = typeof patch.find === "string"
|
||||
? code.includes(patch.find)
|
||||
: (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code));
|
||||
|
||||
if (!moduleMatches) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reporter eagerly patches and cannot retrieve the build number because this code runs before the module for it is loaded
|
||||
const buildNumber = IS_REPORTER ? -1 : getBuildNumber();
|
||||
const shouldCheckBuildNumber = !Settings.eagerPatches && buildNumber !== -1;
|
||||
const buildNumber = getBuildNumber();
|
||||
const shouldCheckBuildNumber = buildNumber !== -1;
|
||||
|
||||
if (
|
||||
shouldCheckBuildNumber &&
|
||||
(patch.fromBuild != null && buildNumber < patch.fromBuild) ||
|
||||
(patch.toBuild != null && buildNumber > patch.toBuild)
|
||||
) {
|
||||
patches.splice(i--, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const moduleMatches = typeof patch.find === "string"
|
||||
? code.includes(patch.find)
|
||||
: (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code));
|
||||
|
||||
if (!moduleMatches) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -463,7 +505,7 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
});
|
||||
|
||||
const previousCode = code;
|
||||
const previousFactory = factory;
|
||||
const previousFactory = originalFactory;
|
||||
let markedAsPatched = false;
|
||||
|
||||
// We change all patch.replacement to array in plugins/index
|
||||
|
@ -482,18 +524,18 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
}
|
||||
|
||||
const lastCode = code;
|
||||
const lastFactory = factory;
|
||||
const lastFactory = originalFactory;
|
||||
|
||||
try {
|
||||
const [newCode, totalTime] = executePatch(replacement.match, replacement.replace as string);
|
||||
|
||||
if (IS_REPORTER) {
|
||||
patchTimings.push([patch.plugin, id, replacement.match, totalTime]);
|
||||
patchTimings.push([patch.plugin, moduleId, replacement.match, totalTime]);
|
||||
}
|
||||
|
||||
if (newCode === code) {
|
||||
if (!patch.noWarn) {
|
||||
logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(id)}): ${replacement.match}`);
|
||||
if (!(patch.noWarn || replacement.noWarn)) {
|
||||
logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(moduleId)}): ${replacement.match}`);
|
||||
if (IS_DEV) {
|
||||
logger.debug("Function Source:\n", code);
|
||||
}
|
||||
|
@ -514,8 +556,13 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
continue;
|
||||
}
|
||||
|
||||
const pluginsList = [...patchedBy];
|
||||
if (!patchedBy.has(patch.plugin)) {
|
||||
pluginsList.push(patch.plugin);
|
||||
}
|
||||
|
||||
code = newCode;
|
||||
patchedSource = `// Webpack Module ${String(id)} - Patched by ${[...patchedBy, patch.plugin].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(id)}`;
|
||||
patchedSource = `// Webpack Module ${String(moduleId)} - Patched by ${pluginsList.join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(moduleId)}`;
|
||||
patchedFactory = (0, eval)(patchedSource);
|
||||
|
||||
if (!patchedBy.has(patch.plugin)) {
|
||||
|
@ -523,7 +570,7 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
markedAsPatched = true;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Patch by ${patch.plugin} errored (Module id is ${String(id)}): ${replacement.match}\n`, err);
|
||||
logger.error(`Patch by ${patch.plugin} errored (Module id is ${String(moduleId)}): ${replacement.match}\n`, err);
|
||||
|
||||
if (IS_DEV) {
|
||||
diffErroredPatch(code, lastCode, lastCode.match(replacement.match)!);
|
||||
|
@ -550,7 +597,14 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
}
|
||||
}
|
||||
|
||||
return [patchedFactory, patchedSource, patchedBy];
|
||||
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) {
|
||||
|
|
|
@ -22,7 +22,8 @@ import { Logger } from "@utils/Logger";
|
|||
import { canonicalizeMatch } from "@utils/patches";
|
||||
|
||||
import { traceFunction } from "../debug/Tracer";
|
||||
import { AnyModuleFactory, ModuleExports, WebpackRequire } from "./wreq";
|
||||
import { Flux } from "./common";
|
||||
import { AnyModuleFactory, AnyWebpackRequire, ModuleExports, WebpackRequire } from "./wreq";
|
||||
|
||||
const logger = new Logger("Webpack");
|
||||
|
||||
|
@ -90,17 +91,13 @@ export const filters = {
|
|||
};
|
||||
|
||||
export type CallbackFn = (module: ModuleExports, id: PropertyKey) => void;
|
||||
export type FactoryListernFn = (factory: AnyModuleFactory) => void;
|
||||
export type FactoryListernFn = (factory: AnyModuleFactory, moduleId: PropertyKey) => void;
|
||||
|
||||
export const waitForSubscriptions = new Map<FilterFn, CallbackFn>();
|
||||
export const moduleListeners = new Set<CallbackFn>();
|
||||
export const factoryListeners = new Set<FactoryListernFn>();
|
||||
|
||||
export function _initWebpack(webpackRequire: WebpackRequire) {
|
||||
if (webpackRequire.c == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
wreq = webpackRequire;
|
||||
cache = webpackRequire.c;
|
||||
|
||||
|
@ -115,18 +112,38 @@ export function _initWebpack(webpackRequire: WebpackRequire) {
|
|||
// Credits to Zerebos for implementing this in BD, thus giving the idea for us to implement it too
|
||||
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 === window) 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;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function _shouldIgnoreModule(exports: any) {
|
||||
if (_shouldIgnoreValue(exports)) {
|
||||
function makePropertyNonEnumerable(target: Record<PropertyKey, any>, key: PropertyKey) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
||||
if (descriptor == null) return;
|
||||
|
||||
Reflect.defineProperty(target, key, {
|
||||
...descriptor,
|
||||
enumerable: false
|
||||
});
|
||||
}
|
||||
|
||||
export function _blacklistBadModules(requireCache: NonNullable<AnyWebpackRequire["c"]>, exports: ModuleExports, moduleId: PropertyKey) {
|
||||
if (shouldIgnoreValue(exports)) {
|
||||
makePropertyNonEnumerable(requireCache, moduleId);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -134,14 +151,16 @@ export function _shouldIgnoreModule(exports: any) {
|
|||
return false;
|
||||
}
|
||||
|
||||
let allNonEnumerable = true;
|
||||
let hasOnlyBadProperties = true;
|
||||
for (const exportKey in exports) {
|
||||
if (!_shouldIgnoreValue(exports[exportKey])) {
|
||||
allNonEnumerable = false;
|
||||
if (shouldIgnoreValue(exports[exportKey])) {
|
||||
makePropertyNonEnumerable(exports, exportKey);
|
||||
} else {
|
||||
hasOnlyBadProperties = false;
|
||||
}
|
||||
}
|
||||
|
||||
return allNonEnumerable;
|
||||
return hasOnlyBadProperties;
|
||||
}
|
||||
|
||||
let devToolsOpen = false;
|
||||
|
@ -409,7 +428,10 @@ export function findByCodeLazy(...code: CodeFilter) {
|
|||
* Find a store by its displayName
|
||||
*/
|
||||
export function findStore(name: StoreNameFilter) {
|
||||
const res = find(filters.byStoreName(name), { isIndirect: true });
|
||||
const res = Flux.Store.getAll
|
||||
? Flux.Store.getAll().find(filters.byStoreName(name))
|
||||
: find(filters.byStoreName(name), { isIndirect: true });
|
||||
|
||||
if (!res)
|
||||
handleModuleNotFound("findStore", name);
|
||||
return res;
|
||||
|
@ -477,12 +499,27 @@ export function findExportedComponentLazy<T extends object = any>(...props: Prop
|
|||
});
|
||||
}
|
||||
|
||||
function getAllPropertyNames(object: Record<PropertyKey, any>, includeNonEnumerable: boolean) {
|
||||
const names = new Set<PropertyKey>();
|
||||
|
||||
const getKeys = includeNonEnumerable ? Object.getOwnPropertyNames : Object.keys;
|
||||
do {
|
||||
getKeys(object).forEach(name => name !== "__esModule" && names.add(name));
|
||||
object = Object.getPrototypeOf(object);
|
||||
} while (object != null);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module)
|
||||
* then maps it into an easily usable module via the specified mappers.
|
||||
*
|
||||
* @param code The code to look for
|
||||
* @param mappers Mappers to create the non mangled exports
|
||||
* @param includeBlacklistedExports Whether to include blacklisted exports in the search.
|
||||
* These exports are dangerous. Accessing properties on them may throw errors
|
||||
* or always return values (so a byProps filter will always return true)
|
||||
* @returns Unmangled exports as specified in mappers
|
||||
*
|
||||
* @example mapMangledModule("headerIdIsManaged:", {
|
||||
|
@ -490,7 +527,7 @@ export function findExportedComponentLazy<T extends object = any>(...props: Prop
|
|||
* closeModal: filters.byCode("key==")
|
||||
* })
|
||||
*/
|
||||
export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>): Record<S, any> {
|
||||
export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>, includeBlacklistedExports = false): Record<S, any> {
|
||||
const exports = {} as Record<S, any>;
|
||||
|
||||
const id = findModuleId(...Array.isArray(code) ? code : [code]);
|
||||
|
@ -498,8 +535,9 @@ export const mapMangledModule = traceFunction("mapMangledModule", function mapMa
|
|||
return exports;
|
||||
|
||||
const mod = wreq(id as any);
|
||||
const keys = getAllPropertyNames(mod, includeBlacklistedExports);
|
||||
outer:
|
||||
for (const key in mod) {
|
||||
for (const key of keys) {
|
||||
const member = mod[key];
|
||||
for (const newName in mappers) {
|
||||
// if the current mapper matches this module
|
||||
|
@ -513,24 +551,13 @@ export const mapMangledModule = traceFunction("mapMangledModule", function mapMa
|
|||
});
|
||||
|
||||
/**
|
||||
* {@link mapMangledModule}, lazy.
|
||||
|
||||
* Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module)
|
||||
* then maps it into an easily usable module via the specified mappers.
|
||||
*
|
||||
* @param code The code to look for
|
||||
* @param mappers Mappers to create the non mangled exports
|
||||
* @returns Unmangled exports as specified in mappers
|
||||
*
|
||||
* @example mapMangledModule("headerIdIsManaged:", {
|
||||
* openModal: filters.byCode("headerIdIsManaged:"),
|
||||
* closeModal: filters.byCode("key==")
|
||||
* })
|
||||
* lazy mapMangledModule
|
||||
* @see {@link mapMangledModule}
|
||||
*/
|
||||
export function mapMangledModuleLazy<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>): Record<S, any> {
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["mapMangledModule", [code, mappers]]);
|
||||
export function mapMangledModuleLazy<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>, includeBlacklistedExports = false): Record<S, any> {
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["mapMangledModule", [code, mappers, includeBlacklistedExports]]);
|
||||
|
||||
return proxyLazy(() => mapMangledModule(code, mappers));
|
||||
return proxyLazy(() => mapMangledModule(code, mappers, includeBlacklistedExports));
|
||||
}
|
||||
|
||||
export const DefaultExtractAndLoadChunksRegex = /(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/;
|
||||
|
|
93
src/webpack/wreq.d.ts
vendored
93
src/webpack/wreq.d.ts
vendored
|
@ -17,14 +17,11 @@ export type Module = {
|
|||
/** 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__";
|
||||
|
||||
/** Keys here can be symbols too, but we can't properly type them */
|
||||
export type AsyncModulePromise = Promise<ModuleExports> & {
|
||||
[WebpackQueues]: (fnQueue: ((queue: any[]) => any)) => any;
|
||||
[WebpackExports]: ModuleExports;
|
||||
[WebpackError]?: any;
|
||||
"__webpack_queues__": (fnQueue: ((queue: any[]) => any)) => any;
|
||||
"__webpack_exports__": ModuleExports;
|
||||
"__webpack_error__"?: any;
|
||||
};
|
||||
|
||||
export type AsyncModuleBody = (
|
||||
|
@ -33,27 +30,45 @@ export type AsyncModuleBody = (
|
|||
asyncResult: (error?: any) => void
|
||||
) => Promise<void>;
|
||||
|
||||
export type ChunkHandlers = {
|
||||
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: ChunkHandlers, chunkId: PropertyKey, promises: Promise<void[]>) => void,
|
||||
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: ChunkHandlers, chunkId: PropertyKey, promises: Promise<void[]>) => void,
|
||||
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 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) */
|
||||
|
@ -134,14 +149,21 @@ export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & {
|
|||
* }
|
||||
* // exports is now { exportName: someExportedValue } (but each value is actually a getter)
|
||||
*/
|
||||
d: (this: WebpackRequire, exports: AnyRecord, definiton: AnyRecord) => void;
|
||||
/** The chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */
|
||||
f: ChunkHandlers;
|
||||
d: (this: WebpackRequire, exports: Record<PropertyKey, any>, definiton: Record<PropertyKey, () => ModuleExports>) => void;
|
||||
/** The ensure chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */
|
||||
f: EnsureChunkHandlers;
|
||||
/**
|
||||
* The ensure chunk function, it ensures a chunk is loaded, or loads if needed.
|
||||
* Internally it uses the handlers in {@link WebpackRequire.f} to load/ensure the chunk is loaded.
|
||||
*/
|
||||
e: (this: WebpackRequire, chunkId: PropertyKey) => Promise<void[]>;
|
||||
/** The prefetch chunk handlers, which are used to prefetch the files of the chunks */
|
||||
F: PrefetchChunkHandlers;
|
||||
/**
|
||||
* The prefetch chunk function.
|
||||
* Internally it uses the handlers in {@link WebpackRequire.F} to prefetch a chunk.
|
||||
*/
|
||||
E: (this: WebpackRequire, chunkId: PropertyKey) => void;
|
||||
/** Get the filename for the css part of a chunk */
|
||||
k: (this: WebpackRequire, chunkId: PropertyKey) => string;
|
||||
/** Get the filename for the js part of a chunk */
|
||||
|
@ -162,18 +184,18 @@ export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & {
|
|||
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;
|
||||
/**
|
||||
* 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
|
||||
|
@ -185,6 +207,13 @@ export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & {
|
|||
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
|
||||
|
@ -200,12 +229,10 @@ export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: M
|
|||
[SYM_PATCHED_BY]?: Set<string>;
|
||||
};
|
||||
|
||||
export type WrappedModuleFactory = AnyModuleFactory & {
|
||||
export type PatchedModuleFactory = AnyModuleFactory & {
|
||||
[SYM_ORIGINAL_FACTORY]: AnyModuleFactory;
|
||||
[SYM_PATCHED_SOURCE]?: string;
|
||||
[SYM_PATCHED_BY]?: Set<string>;
|
||||
};
|
||||
|
||||
export type MaybeWrappedModuleFactory = AnyModuleFactory | WrappedModuleFactory;
|
||||
|
||||
export type WrappedModuleFactories = Record<PropertyKey, WrappedModuleFactory>;
|
||||
export type MaybePatchedModuleFactory = PatchedModuleFactory | AnyModuleFactory;
|
||||
|
|
|
@ -29,7 +29,9 @@
|
|||
"@shared/*": ["./shared/*"],
|
||||
"@webpack/types": ["./webpack/common/types"],
|
||||
"@webpack/common": ["./webpack/common"],
|
||||
"@webpack": ["./webpack/webpack"]
|
||||
"@webpack": ["./webpack/webpack"],
|
||||
"@webpack/patcher": ["./webpack/patchWebpack"],
|
||||
"@webpack/wreq.d": ["./webpack/wreq.d"],
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
|
|
Loading…
Add table
Reference in a new issue