Merge branch 'dev' into managed-styles-rewrite

This commit is contained in:
Sqaaakoi 2025-02-23 15:07:56 +13:00
commit 0d4e6795e0
No known key found for this signature in database
58 changed files with 1702 additions and 1959 deletions

View file

@ -5,15 +5,9 @@ body:
- type: markdown
attributes:
value: |
# READ THIS BEFORE OPENING AN ISSUE
![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true)
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
DO NOT USE THIS FORM, unless
- you are a vencord contributor
- you were given explicit permission to use this form by a moderator in our support server
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer.
- type: textarea
id: content

View file

@ -7,24 +7,9 @@ body:
- type: markdown
attributes:
value: |
# READ THIS BEFORE OPENING AN ISSUE
![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true)
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
DO NOT USE THIS FORM, unless
- you are a vencord contributor
- you were given explicit permission to use this form by a moderator in our support server
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
- type: input
id: discord
attributes:
label: Discord Account
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
placeholder: username#0000
validations:
required: false
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer.
- type: textarea
id: bug-description
@ -77,5 +62,5 @@ body:
options:
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
required: true
- label: I have read the requirements for opening an issue above
- label: I am a Vencord Developer
required: true

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -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

View file

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

View file

@ -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) {

View file

@ -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>

View file

@ -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

View file

@ -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": "*"
}
"urix": "*",
"q": "*"
},
"webExt": {
"artifactsDir": "./dist",
"build": {
"overwriteDest": true
},
"sourceDir": "./dist/firefox-unpacked"
"onlyBuiltDependencies": [
"esbuild"
]
},
"engines": {
"node": ">=18",
"pnpm": ">=9"
"node": ">=18"
}
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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);

View file

@ -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,52 +59,45 @@ const MonacoWorkerEntryPoints = [
"vs/editor/editor.worker.js"
];
const RnNoiseFiles = [
"dist/rnnoise.wasm",
"dist/rnnoise_simd.wasm",
"dist/rnnoise/workletProcessor.js",
"LICENSE"
];
await Promise.all(
[
esbuild.build({
/** @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/monaco"
}),
esbuild.build({
outdir: "dist/vendor/monaco"
},
{
entryPoints: ["browser/monaco.ts"],
bundle: true,
minify: true,
format: "iife",
outfile: "dist/monaco/index.js",
outfile: "dist/vendor/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,
...commonOptions.define,
IS_EXTENSION: "true"
},
footer: { js: "//# sourceURL=VencordWeb" }
}),
esbuild.build({
},
{
...commonOptions,
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
define: {
...(commonOptions?.define),
...commonOptions.define,
window: "unsafeWindow",
},
outfile: "dist/Vencord.user.js",
@ -114,14 +108,10 @@ await Promise.all(
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
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;
}

View file

@ -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
];

View file

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

View file

@ -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);
}
const report = {
badPatches: [] as {
interface PatchInfo {
plugin: string;
type: string;
id: string;
match: string;
error?: string;
}[],
};
const report = {
badPatches: [] as PatchInfo[],
slowPatches: [] as PatchInfo[],
badStarts: [] as {
plugin: string;
error: string;
@ -136,9 +135,22 @@ async function printReport() {
console.log();
if (process.env.WEBHOOK_URL) {
const body = JSON.stringify({
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
embeds: [
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})`,
@ -147,25 +159,14 @@ async function printReport() {
},
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
},
{
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: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00
color: 0xff0000
},
{
report.badStarts.length > 0 && {
title: "Bad Starts",
description: report.badStarts.map(p => {
const lines = [
@ -175,14 +176,26 @@ async function printReport() {
return lines.join("\n");
}
).join("\n\n") || "None",
color: report.badStarts.length ? 0xff0000 : 0x00ff00
color: 0xff0000
},
{
report.otherErrors.length > 0 && {
title: "Discord Errors",
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
color: report.otherErrors.length ? 0xff0000 : 0x00ff00
color: 0xff0000
}
]
].filter(Boolean);
if (embeds.length === 1) {
embeds.push({
title: "No issues found",
description: "Seems like everything is working fine (for now) <:shipit:1330992641466433556>",
color: 0x00ff00
});
}
const body = JSON.stringify({
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
embeds
});
const headers = {
@ -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,

View file

@ -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[]) {

View file

@ -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) {

View file

@ -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) {

View file

@ -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

View file

@ -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"
}
}
]

View file

@ -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: {

View file

@ -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";

View file

@ -85,7 +85,7 @@ export default definePlugin({
replace: "$&onRequestClose:$self.onPopoutClose,"
},
{
match: /(?<=\.avatarWrapper,)/,
match: /(?<=#{intl::SET_STATUS}\),)/,
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
}
]

View file

@ -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

View file

@ -26,10 +26,18 @@ export default definePlugin({
patches: [
{
find: '"ChannelAttachButton"',
replacement: {
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,",
},
]
},
],
});

View file

@ -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
{

View file

@ -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";

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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,

View file

@ -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"
},
},

View file

@ -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;

View file

@ -65,10 +65,18 @@ export default definePlugin({
patches: [
{
find: "{isSidebarVisible:",
replacement: {
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"
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
},
{

View file

@ -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

View file

@ -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%;
}

View file

@ -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.+?}\)(?=\?)/,

View file

@ -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;

View file

@ -27,12 +27,22 @@ export default definePlugin({
authors: [Devs.Megu],
patches: [{
find: "#{intl::ACTIVITY_SETTINGS}",
replacement: {
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})`
}
`&&${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
});

View file

@ -41,11 +41,20 @@ export default definePlugin({
},
{
find: '="SYSTEM_TAG"',
replacement: {
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]),"
}
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]),",
},
]
}
],

View file

@ -128,7 +128,7 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
);
}
interface VoiceChannelIndicatorProps {
export interface VoiceChannelIndicatorProps {
userId: string;
isActionButton?: boolean;
shouldHighlight?: boolean;

View file

@ -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: {
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)},"
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

View file

@ -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"))) {

View file

@ -17,7 +17,6 @@
@media(width <= 485px) {
.vc-image-modal {
display: relative;
overflow: visible;
overflow: initial;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
};

View file

@ -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"] & {

View file

@ -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

View file

@ -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);

View file

@ -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();
}
}
} catch { }
const moduleId = findModuleId("Trying to open a changelog for an invalid build number");
if (moduleId == null) {
function matchBuildNumber(factoryStr: string) {
const buildNumberMatch = factoryStr.match(/.concat\("(\d+?)"\)/);
if (buildNumberMatch == null) {
return -1;
}
const exports = Object.values<ModuleExports>(wreq(moduleId));
if (exports.length !== 1 || typeof exports[0] !== "function") {
return -1;
return Number(buildNumberMatch[1]);
}
const buildNumber = exports[0]();
return typeof buildNumber === "number" ? buildNumber : -1;
const hardcodedFactoryStr = String(wreq.m[128014]);
if (hardcodedFactoryStr.includes("Trying to open a changelog for an invalid build number")) {
const hardcodedBuildNumber = matchBuildNumber(hardcodedFactoryStr);
if (hardcodedBuildNumber !== -1) {
return hardcodedBuildNumber;
}
}
const moduleFactory = findModuleFactory("Trying to open a changelog for an invalid build number");
return matchBuildNumber(String(moduleFactory));
} catch {
return -1;
} 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,35 +105,75 @@ 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);
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);
_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]);
// If neither of these properties setters were triggered, delete them as they are not needed anymore.
const bundlePathTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
const onChunksLoadedTimeout = setTimeout(() => Reflect.deleteProperty(this, "O"), 0);
/**
* Patch the current Webpack instance assigned to `this` context.
* This should only be called if this instance was later found to be one we need to patch.
*/
const patchThisInstance = () => {
logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
allWebpackInstances.add(this);
// Proxy (and maybe patch) pre-populated factories
for (const moduleId in originalModules) {
updateExistingOrProxyFactory(originalModules, moduleId, originalModules[moduleId], originalModules, true);
}
define(originalModules, Symbol.toStringTag, {
@ -145,7 +181,6 @@ define(Function.prototype, "m", {
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
@ -153,9 +188,25 @@ define(Function.prototype, "m", {
*/
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,129 +351,79 @@ 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];
if (patchedFactory === originalFactory) {
// @ts-expect-error Clear up ORIGINAL_FACTORY if the factory did not have any patch applied
delete 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];
}
let [module, exports, require] = argArray;
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);
}
}
/**
* 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);
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] });
}
// eslint-disable-next-line prefer-const
let [module, exports, require] = args;
// Restore the original factory in all the module factories objects, discarding our proxy and allowing it to be garbage collected
defineInWebpackInstances(module.id, originalFactory);
if (wreq == null) {
if (!wreqFallbackApplied) {
wreqFallbackApplied = true;
// Make sure the require argument is actually the WebpackRequire function
if (typeof require === "function" && require.m != null) {
if (typeof require === "function" && require.m != null && require.c != null) {
const { stack } = new Error();
const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
logger.warn(
"WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
`id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
"WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called wrapped module factory (" +
`id: ${String(module.id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
")"
);
// Could technically be wrong, but it's better than nothing
_initWebpack(require as WebpackRequire);
} else if (IS_DEV) {
logger.error("WebpackRequire was not initialized, running modules without patches instead.");
return wrappedFactory[SYM_ORIGINAL_FACTORY].apply(this, args);
return originalFactory.apply(thisArg, argArray);
}
} else if (IS_DEV) {
return wrappedFactory[SYM_ORIGINAL_FACTORY].apply(this, args);
return originalFactory.apply(thisArg, argArray);
}
}
let factoryReturn: unknown;
try {
// Call the patched factory
factoryReturn = patchedFactory.apply(this, args);
factoryReturn = patchedFactory.apply(thisArg, argArray);
} catch (err) {
// Just re-throw Discord errors
if (patchedFactory === wrappedFactory[SYM_ORIGINAL_FACTORY]) {
if (patchedFactory === originalFactory) {
throw err;
}
logger.error("Error in patched module factory:\n", err);
return wrappedFactory[SYM_ORIGINAL_FACTORY].apply(this, args);
return originalFactory.apply(thisArg, argArray);
}
exports = module.exports;
if (typeof require === "function" && require.c) {
if (_blacklistBadModules(require.c, exports, module.id)) {
return factoryReturn;
}
}
if (exports == null) {
return factoryReturn;
}
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);
callback(exports, module.id);
} catch (err) {
logger.error("Error in Webpack module listener:\n", err, callback);
}
@ -376,7 +433,7 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
try {
if (filter(exports)) {
waitForSubscriptions.delete(filter);
callback(exports, id);
callback(exports, module.id);
continue;
}
@ -389,7 +446,7 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
if (exportValue != null && filter(exportValue)) {
waitForSubscriptions.delete(filter);
callback(exportValue, id);
callback(exportValue, module.id);
break;
}
}
@ -399,58 +456,43 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
}
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;
}
/**
* 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) {

View file

@ -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
View file

@ -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;

View file

@ -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": [