mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-01-11 02:16:23 +00:00
Merge branch 'dev' into Warning
This commit is contained in:
commit
ba68c6b0e7
47 changed files with 709 additions and 519 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
|||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build web
|
||||
run: pnpm buildWeb --standalone
|
||||
run: pnpm buildWebStandalone
|
||||
|
||||
- name: Build
|
||||
run: pnpm build --standalone
|
||||
|
|
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
|||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build web
|
||||
run: pnpm buildWeb --standalone
|
||||
run: pnpm buildWebStandalone
|
||||
|
||||
- name: Publish extension
|
||||
run: |
|
||||
|
|
4
.github/workflows/reportBrokenPlugins.yml
vendored
4
.github/workflows/reportBrokenPlugins.yml
vendored
|
@ -37,8 +37,8 @@ jobs:
|
|||
with:
|
||||
chrome-version: stable
|
||||
|
||||
- name: Build web
|
||||
run: pnpm buildWeb --standalone --dev
|
||||
- name: Build Vencord Reporter Version
|
||||
run: pnpm buildReporter
|
||||
|
||||
- name: Create Report
|
||||
timeout-minutes: 10
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.8.6",
|
||||
"version": "1.8.8",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
@ -20,7 +20,11 @@
|
|||
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
|
||||
"buildStandalone": "pnpm build --standalone",
|
||||
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
|
||||
"buildWebStandalone": "pnpm buildWeb --standalone",
|
||||
"buildReporter": "pnpm buildWebStandalone --reporter --skip-extension",
|
||||
"buildReporterDesktop": "pnpm build --reporter",
|
||||
"watch": "pnpm build --watch",
|
||||
"watchWeb": "pnpm buildWeb --watch",
|
||||
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
||||
"inject": "node scripts/runInstaller.mjs",
|
||||
|
@ -103,6 +107,6 @@
|
|||
},
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8"
|
||||
"pnpm": ">=9"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,19 +21,21 @@ import esbuild from "esbuild";
|
|||
import { readdir } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isDev, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
|
||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, VERSION, watch } from "./common.mjs";
|
||||
|
||||
const defines = {
|
||||
IS_STANDALONE: isStandalone,
|
||||
IS_DEV: JSON.stringify(isDev),
|
||||
IS_UPDATER_DISABLED: updaterDisabled,
|
||||
IS_STANDALONE,
|
||||
IS_DEV,
|
||||
IS_REPORTER,
|
||||
IS_UPDATER_DISABLED,
|
||||
IS_WEB: false,
|
||||
IS_EXTENSION: false,
|
||||
VERSION: JSON.stringify(VERSION),
|
||||
BUILD_TIMESTAMP,
|
||||
BUILD_TIMESTAMP
|
||||
};
|
||||
if (defines.IS_STANDALONE === "false")
|
||||
// If this is a local build (not standalone), optimise
|
||||
|
||||
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);
|
||||
|
||||
|
@ -46,7 +48,7 @@ const nodeCommonOpts = {
|
|||
platform: "node",
|
||||
target: ["esnext"],
|
||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
|
||||
define: defines,
|
||||
define: defines
|
||||
};
|
||||
|
||||
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
||||
|
@ -73,13 +75,13 @@ const globNativesPlugin = {
|
|||
let i = 0;
|
||||
for (const dir of pluginDirs) {
|
||||
const dirPath = join("src", dir);
|
||||
if (!await existsAsync(dirPath)) continue;
|
||||
if (!await exists(dirPath)) continue;
|
||||
const plugins = await readdir(dirPath);
|
||||
for (const p of plugins) {
|
||||
const nativePath = join(dirPath, p, "native.ts");
|
||||
const indexNativePath = join(dirPath, p, "native/index.ts");
|
||||
|
||||
if (!(await existsAsync(nativePath)) && !(await existsAsync(indexNativePath)))
|
||||
if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
|
||||
continue;
|
||||
|
||||
const nameParts = p.split(".");
|
||||
|
|
|
@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises
|
|||
import { join } from "path";
|
||||
import Zip from "zip-local";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, isDev, VERSION } from "./common.mjs";
|
||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION } from "./common.mjs";
|
||||
|
||||
/**
|
||||
* @type {esbuild.BuildOptions}
|
||||
|
@ -33,22 +33,23 @@ const commonOptions = {
|
|||
entryPoints: ["browser/Vencord.ts"],
|
||||
globalName: "Vencord",
|
||||
format: "iife",
|
||||
external: ["plugins", "git-hash", "/assets/*"],
|
||||
external: ["~plugins", "~git-hash", "/assets/*"],
|
||||
plugins: [
|
||||
globPlugins("web"),
|
||||
...commonOpts.plugins,
|
||||
],
|
||||
target: ["esnext"],
|
||||
define: {
|
||||
IS_WEB: "true",
|
||||
IS_EXTENSION: "false",
|
||||
IS_STANDALONE: "true",
|
||||
IS_DEV: JSON.stringify(isDev),
|
||||
IS_DISCORD_DESKTOP: "false",
|
||||
IS_VESKTOP: "false",
|
||||
IS_UPDATER_DISABLED: "true",
|
||||
IS_WEB: true,
|
||||
IS_EXTENSION: false,
|
||||
IS_STANDALONE: true,
|
||||
IS_DEV,
|
||||
IS_REPORTER,
|
||||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: false,
|
||||
IS_UPDATER_DISABLED: true,
|
||||
VERSION: JSON.stringify(VERSION),
|
||||
BUILD_TIMESTAMP,
|
||||
BUILD_TIMESTAMP
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -87,16 +88,16 @@ await Promise.all(
|
|||
esbuild.build({
|
||||
...commonOptions,
|
||||
outfile: "dist/browser.js",
|
||||
footer: { js: "//# sourceURL=VencordWeb" },
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
outfile: "dist/extension.js",
|
||||
define: {
|
||||
...commonOptions?.define,
|
||||
IS_EXTENSION: "true",
|
||||
IS_EXTENSION: true,
|
||||
},
|
||||
footer: { js: "//# sourceURL=VencordWeb" },
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
|
@ -112,7 +113,7 @@ await Promise.all(
|
|||
footer: {
|
||||
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
||||
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
|
||||
},
|
||||
}
|
||||
})
|
||||
]
|
||||
);
|
||||
|
@ -165,7 +166,7 @@ async function buildExtension(target, files) {
|
|||
f.startsWith("manifest") ? "manifest.json" : f,
|
||||
content
|
||||
];
|
||||
}))),
|
||||
})))
|
||||
};
|
||||
|
||||
await rm(target, { recursive: true, force: true });
|
||||
|
@ -192,14 +193,19 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
|
|||
return appendFile("dist/Vencord.user.js", cssRuntime);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
if (!process.argv.includes("--skip-extension")) {
|
||||
await Promise.all([
|
||||
appendCssRuntime,
|
||||
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
|
||||
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
|
||||
]);
|
||||
]);
|
||||
|
||||
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
|
||||
console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
|
||||
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
|
||||
console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
|
||||
|
||||
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
|
||||
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
|
||||
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||
|
||||
} else {
|
||||
await appendCssRuntime;
|
||||
}
|
||||
|
|
|
@ -35,24 +35,26 @@ const PackageJSON = JSON.parse(readFileSync("package.json"));
|
|||
export const VERSION = PackageJSON.version;
|
||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||
export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now();
|
||||
|
||||
export const watch = process.argv.includes("--watch");
|
||||
export const isDev = watch || process.argv.includes("--dev");
|
||||
export const isStandalone = JSON.stringify(process.argv.includes("--standalone"));
|
||||
export const updaterDisabled = JSON.stringify(process.argv.includes("--disable-updater"));
|
||||
export const IS_DEV = watch || process.argv.includes("--dev");
|
||||
export const IS_REPORTER = process.argv.includes("--reporter");
|
||||
export const IS_STANDALONE = process.argv.includes("--standalone");
|
||||
|
||||
export const IS_UPDATER_DISABLED = process.argv.includes("--disable-updater");
|
||||
export const gitHash = process.env.VENCORD_HASH || execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
|
||||
|
||||
export const banner = {
|
||||
js: `
|
||||
// Vencord ${gitHash}
|
||||
// Standalone: ${isStandalone}
|
||||
// Platform: ${isStandalone === "false" ? process.platform : "Universal"}
|
||||
// Updater disabled: ${updaterDisabled}
|
||||
// Standalone: ${IS_STANDALONE}
|
||||
// Platform: ${IS_STANDALONE === false ? process.platform : "Universal"}
|
||||
// Updater Disabled: ${IS_UPDATER_DISABLED}
|
||||
`.trim()
|
||||
};
|
||||
|
||||
const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs"));
|
||||
|
||||
export function existsAsync(path) {
|
||||
return access(path, FsConstants.F_OK)
|
||||
export async function exists(path) {
|
||||
return await access(path, FsConstants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
@ -66,7 +68,7 @@ export const makeAllPackagesExternalPlugin = {
|
|||
setup(build) {
|
||||
const filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../"
|
||||
build.onResolve({ filter }, args => ({ path: args.path, external: true }));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -89,14 +91,14 @@ export const globPlugins = kind => ({
|
|||
let plugins = "\n";
|
||||
let i = 0;
|
||||
for (const dir of pluginDirs) {
|
||||
if (!await existsAsync(`./src/${dir}`)) continue;
|
||||
if (!await exists(`./src/${dir}`)) continue;
|
||||
const files = await readdir(`./src/${dir}`);
|
||||
for (const file of files) {
|
||||
if (file.startsWith("_") || file.startsWith(".")) continue;
|
||||
if (file === "index.ts") continue;
|
||||
|
||||
const target = getPluginTarget(file);
|
||||
if (target) {
|
||||
if (target && !IS_REPORTER) {
|
||||
if (target === "dev" && !watch) continue;
|
||||
if (target === "web" && kind === "discordDesktop") continue;
|
||||
if (target === "desktop" && kind === "web") continue;
|
||||
|
@ -178,7 +180,7 @@ export const fileUrlPlugin = {
|
|||
build.onLoad({ filter, namespace: "file-uri" }, async ({ pluginData: { path, uri } }) => {
|
||||
const { searchParams } = new URL(uri);
|
||||
const base64 = searchParams.has("base64");
|
||||
const minify = isStandalone === "true" && searchParams.has("minify");
|
||||
const minify = IS_STANDALONE === true && searchParams.has("minify");
|
||||
const noTrim = searchParams.get("trim") === "false";
|
||||
|
||||
const encoding = base64 ? "base64" : "utf-8";
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
* 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
|
||||
|
@ -40,10 +42,11 @@ const browser = await pup.launch({
|
|||
|
||||
const page = await browser.newPage();
|
||||
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
|
||||
await page.setBypassCSP(true);
|
||||
|
||||
function maybeGetError(handle: JSHandle) {
|
||||
return (handle as JSHandle<Error>)?.getProperty("message")
|
||||
.then(m => m.jsonValue());
|
||||
async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
|
||||
return await (handle as JSHandle<Error>)?.getProperty("message")
|
||||
.then(m => m?.jsonValue());
|
||||
}
|
||||
|
||||
const report = {
|
||||
|
@ -59,6 +62,7 @@ const report = {
|
|||
error: string;
|
||||
}[],
|
||||
otherErrors: [] as string[],
|
||||
ignoredErrors: [] as string[],
|
||||
badWebpackFinds: [] as string[]
|
||||
};
|
||||
|
||||
|
@ -106,15 +110,6 @@ async function printReport() {
|
|||
|
||||
console.log();
|
||||
|
||||
const ignoredErrors = [] as string[];
|
||||
report.otherErrors = report.otherErrors.filter(e => {
|
||||
if (IGNORED_DISCORD_ERRORS.some(regex => e.match(regex))) {
|
||||
ignoredErrors.push(e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
console.log("## Discord Errors");
|
||||
report.otherErrors.forEach(e => {
|
||||
console.log(`- ${toCodeBlock(e)}`);
|
||||
|
@ -123,7 +118,7 @@ async function printReport() {
|
|||
console.log();
|
||||
|
||||
console.log("## Ignored Discord Errors");
|
||||
ignoredErrors.forEach(e => {
|
||||
report.ignoredErrors.forEach(e => {
|
||||
console.log(`- ${toCodeBlock(e)}`);
|
||||
});
|
||||
|
||||
|
@ -188,61 +183,6 @@ page.on("console", async e => {
|
|||
const level = e.type();
|
||||
const rawArgs = e.args();
|
||||
|
||||
const firstArg = await rawArgs[0]?.jsonValue();
|
||||
if (firstArg === "[PUPPETEER_TEST_DONE_SIGNAL]") {
|
||||
await browser.close();
|
||||
await printReport();
|
||||
process.exit();
|
||||
}
|
||||
|
||||
const isVencord = firstArg === "[Vencord]";
|
||||
const isDebug = firstArg === "[PUP_DEBUG]";
|
||||
const isWebpackFindFail = firstArg === "[PUP_WEBPACK_FIND_FAIL]";
|
||||
|
||||
if (isWebpackFindFail) {
|
||||
process.exitCode = 1;
|
||||
report.badWebpackFinds.push(await rawArgs[1].jsonValue() as string);
|
||||
}
|
||||
|
||||
if (isVencord) {
|
||||
const args = await Promise.all(e.args().map(a => a.jsonValue()));
|
||||
|
||||
const [, tag, message] = args as Array<string>;
|
||||
const cause = await maybeGetError(e.args()[3]);
|
||||
|
||||
switch (tag) {
|
||||
case "WebpackInterceptor:":
|
||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!;
|
||||
if (!patchFailMatch) break;
|
||||
|
||||
process.exitCode = 1;
|
||||
|
||||
const [, plugin, type, id, regex] = patchFailMatch;
|
||||
report.badPatches.push({
|
||||
plugin,
|
||||
type,
|
||||
id,
|
||||
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
|
||||
error: cause
|
||||
});
|
||||
|
||||
break;
|
||||
case "PluginManager:":
|
||||
const failedToStartMatch = message.match(/Failed to start (.+)/);
|
||||
if (!failedToStartMatch) break;
|
||||
|
||||
process.exitCode = 1;
|
||||
|
||||
const [, name] = failedToStartMatch;
|
||||
report.badStarts.push({
|
||||
plugin: name,
|
||||
error: cause
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function getText() {
|
||||
try {
|
||||
return await Promise.all(
|
||||
|
@ -255,174 +195,100 @@ page.on("console", async e => {
|
|||
}
|
||||
}
|
||||
|
||||
if (isDebug) {
|
||||
const text = await getText();
|
||||
const firstArg = await rawArgs[0]?.jsonValue();
|
||||
|
||||
console.error(text);
|
||||
if (text.includes("A fatal error occurred:")) {
|
||||
const isVencord = firstArg === "[Vencord]";
|
||||
const isDebug = firstArg === "[PUP_DEBUG]";
|
||||
|
||||
outer:
|
||||
if (isVencord) {
|
||||
try {
|
||||
var args = await Promise.all(e.args().map(a => a.jsonValue()));
|
||||
} catch {
|
||||
break outer;
|
||||
}
|
||||
|
||||
const [, tag, message, otherMessage] = args as Array<string>;
|
||||
|
||||
switch (tag) {
|
||||
case "WebpackInterceptor:":
|
||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!;
|
||||
if (!patchFailMatch) break;
|
||||
|
||||
console.error(await getText());
|
||||
process.exitCode = 1;
|
||||
|
||||
const [, plugin, type, id, regex] = patchFailMatch;
|
||||
report.badPatches.push({
|
||||
plugin,
|
||||
type,
|
||||
id,
|
||||
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
|
||||
error: await maybeGetError(e.args()[3])
|
||||
});
|
||||
|
||||
break;
|
||||
case "PluginManager:":
|
||||
const failedToStartMatch = message.match(/Failed to start (.+)/);
|
||||
if (!failedToStartMatch) break;
|
||||
|
||||
console.error(await getText());
|
||||
process.exitCode = 1;
|
||||
|
||||
const [, name] = failedToStartMatch;
|
||||
report.badStarts.push({
|
||||
plugin: name,
|
||||
error: await maybeGetError(e.args()[3]) ?? "Unknown error"
|
||||
});
|
||||
|
||||
break;
|
||||
case "LazyChunkLoader:":
|
||||
console.error(await getText());
|
||||
|
||||
switch (message) {
|
||||
case "A fatal error occurred:":
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
break;
|
||||
case "Reporter:":
|
||||
console.error(await getText());
|
||||
|
||||
switch (message) {
|
||||
case "A fatal error occurred:":
|
||||
process.exit(1);
|
||||
case "Webpack Find Fail:":
|
||||
process.exitCode = 1;
|
||||
report.badWebpackFinds.push(otherMessage);
|
||||
break;
|
||||
case "Finished test":
|
||||
await browser.close();
|
||||
await printReport();
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isDebug) {
|
||||
console.error(await getText());
|
||||
} else if (level === "error") {
|
||||
const text = await getText();
|
||||
|
||||
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) {
|
||||
if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) {
|
||||
report.ignoredErrors.push(text);
|
||||
} else {
|
||||
console.error("[Unexpected Error]", text);
|
||||
report.otherErrors.push(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
page.on("error", e => console.error("[Error]", e));
|
||||
page.on("pageerror", e => console.error("[Page Error]", e));
|
||||
|
||||
await page.setBypassCSP(true);
|
||||
|
||||
async function runtime(token: string) {
|
||||
console.log("[PUP_DEBUG]", "Starting test...");
|
||||
|
||||
try {
|
||||
// Spoof languages to not be suspicious
|
||||
Object.defineProperty(navigator, "languages", {
|
||||
get: function () {
|
||||
return ["en-US", "en"];
|
||||
},
|
||||
});
|
||||
|
||||
// Monkey patch Logger to not log with custom css
|
||||
// @ts-ignore
|
||||
const originalLog = Vencord.Util.Logger.prototype._log;
|
||||
// @ts-ignore
|
||||
Vencord.Util.Logger.prototype._log = function (level, levelColor, args) {
|
||||
if (level === "warn" || level === "error")
|
||||
return console[level]("[Vencord]", this.name + ":", ...args);
|
||||
|
||||
return originalLog.call(this, level, levelColor, args);
|
||||
};
|
||||
|
||||
// Force enable all plugins and patches
|
||||
Vencord.Plugins.patches.length = 0;
|
||||
Object.values(Vencord.Plugins.plugins).forEach(p => {
|
||||
// Needs native server to run
|
||||
if (p.name === "WebRichPresence (arRPC)") return;
|
||||
|
||||
Vencord.Settings.plugins[p.name].enabled = true;
|
||||
p.patches?.forEach(patch => {
|
||||
patch.plugin = p.name;
|
||||
delete patch.predicate;
|
||||
delete patch.group;
|
||||
|
||||
Vencord.Util.canonicalizeFind(patch);
|
||||
if (!Array.isArray(patch.replacement)) {
|
||||
patch.replacement = [patch.replacement];
|
||||
}
|
||||
|
||||
patch.replacement.forEach(r => {
|
||||
delete r.predicate;
|
||||
});
|
||||
|
||||
Vencord.Plugins.patches.push(patch);
|
||||
});
|
||||
});
|
||||
|
||||
let wreq: typeof Vencord.Webpack.wreq;
|
||||
|
||||
const { canonicalizeMatch, Logger } = Vencord.Util;
|
||||
|
||||
const validChunks = new Set<string>();
|
||||
const invalidChunks = new Set<string>();
|
||||
const deferredRequires = new Set<string>();
|
||||
|
||||
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
||||
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
||||
|
||||
// True if resolved, false otherwise
|
||||
const chunksSearchPromises = [] as Array<() => boolean>;
|
||||
|
||||
const LazyChunkRegex = canonicalizeMatch(/(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\)))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
|
||||
|
||||
async function searchAndLoadLazyChunks(factoryCode: string) {
|
||||
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
||||
const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
|
||||
|
||||
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
|
||||
// the chunk containing the component
|
||||
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
|
||||
|
||||
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIdsArray, rawChunkIdsSingle, entryPoint]) => {
|
||||
const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle;
|
||||
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Vencord.Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
|
||||
|
||||
if (chunkIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let invalidChunkGroup = false;
|
||||
|
||||
for (const id of chunkIds) {
|
||||
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
||||
|
||||
const isWasm = await fetch(wreq.p + wreq.u(id))
|
||||
.then(r => r.text())
|
||||
.then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
|
||||
|
||||
if (isWasm) {
|
||||
invalidChunks.add(id);
|
||||
invalidChunkGroup = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
validChunks.add(id);
|
||||
}
|
||||
|
||||
if (!invalidChunkGroup) {
|
||||
validChunkGroups.add([chunkIds, entryPoint]);
|
||||
}
|
||||
}));
|
||||
|
||||
// Loads all found valid chunk groups
|
||||
await Promise.all(
|
||||
Array.from(validChunkGroups)
|
||||
.map(([chunkIds]) =>
|
||||
Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
|
||||
)
|
||||
);
|
||||
|
||||
// Requires the entry points for all valid chunk groups
|
||||
for (const [, entryPoint] of validChunkGroups) {
|
||||
try {
|
||||
if (shouldForceDefer) {
|
||||
deferredRequires.add(entryPoint);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wreq.m[entryPoint]) wreq(entryPoint as any);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// setImmediate to only check if all chunks were loaded after this function resolves
|
||||
// We check if all chunks were loaded every time a factory is loaded
|
||||
// If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
|
||||
// But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
|
||||
setTimeout(() => {
|
||||
let allResolved = true;
|
||||
|
||||
for (let i = 0; i < chunksSearchPromises.length; i++) {
|
||||
const isResolved = chunksSearchPromises[i]();
|
||||
|
||||
if (isResolved) {
|
||||
// Remove finished promises to avoid having to iterate through a huge array everytime
|
||||
chunksSearchPromises.splice(i--, 1);
|
||||
} else {
|
||||
allResolved = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allResolved) chunksSearchingResolve();
|
||||
}, 0);
|
||||
}
|
||||
page.on("error", e => console.error("[Error]", e.message));
|
||||
page.on("pageerror", e => console.error("[Page Error]", e.message));
|
||||
|
||||
async function reporterRuntime(token: string) {
|
||||
Vencord.Webpack.waitFor(
|
||||
"loginToken",
|
||||
m => {
|
||||
|
@ -430,124 +296,13 @@ async function runtime(token: string) {
|
|||
m.loginToken(token);
|
||||
}
|
||||
);
|
||||
|
||||
Vencord.Webpack.beforeInitListeners.add(async webpackRequire => {
|
||||
console.log("[PUP_DEBUG]", "Loading all chunks...");
|
||||
|
||||
wreq = webpackRequire;
|
||||
|
||||
Vencord.Webpack.factoryListeners.add(factory => {
|
||||
let isResolved = false;
|
||||
searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
|
||||
|
||||
chunksSearchPromises.push(() => isResolved);
|
||||
});
|
||||
|
||||
// setImmediate to only search the initial factories after Discord initialized the app
|
||||
// our beforeInitListeners are called before Discord initializes the app
|
||||
setTimeout(() => {
|
||||
for (const factoryId in wreq.m) {
|
||||
let isResolved = false;
|
||||
searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
|
||||
|
||||
chunksSearchPromises.push(() => isResolved);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
await chunksSearchingDone;
|
||||
|
||||
// Require deferred entry points
|
||||
for (const deferredRequire of deferredRequires) {
|
||||
wreq!(deferredRequire as any);
|
||||
}
|
||||
|
||||
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
||||
const allChunks = [] as string[];
|
||||
|
||||
// Matches "id" or id:
|
||||
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
|
||||
const id = currentMatch[1] ?? currentMatch[2];
|
||||
if (id == null) continue;
|
||||
|
||||
allChunks.push(id);
|
||||
}
|
||||
|
||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
||||
|
||||
// Chunks that are not loaded (not used) by Discord code anymore
|
||||
const chunksLeft = allChunks.filter(id => {
|
||||
return !(validChunks.has(id) || invalidChunks.has(id));
|
||||
});
|
||||
|
||||
await Promise.all(chunksLeft.map(async id => {
|
||||
const isWasm = await fetch(wreq.p + wreq.u(id))
|
||||
.then(r => r.text())
|
||||
.then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
|
||||
|
||||
// Loads and requires a chunk
|
||||
if (!isWasm) {
|
||||
await wreq.e(id as any);
|
||||
if (wreq.m[id]) wreq(id as any);
|
||||
}
|
||||
}));
|
||||
|
||||
console.log("[PUP_DEBUG]", "Finished loading all chunks!");
|
||||
|
||||
for (const patch of Vencord.Plugins.patches) {
|
||||
if (!patch.all) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [searchType, args] of Vencord.Webpack.lazyWebpackSearchHistory) {
|
||||
let method = searchType;
|
||||
|
||||
if (searchType === "findComponent") method = "find";
|
||||
if (searchType === "findExportedComponent") method = "findByProps";
|
||||
if (searchType === "waitFor" || searchType === "waitForComponent") {
|
||||
if (typeof args[0] === "string") method = "findByProps";
|
||||
else method = "find";
|
||||
}
|
||||
if (searchType === "waitForStore") method = "findStore";
|
||||
|
||||
try {
|
||||
let result: any;
|
||||
|
||||
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||
const [factory] = args;
|
||||
result = factory();
|
||||
} else if (method === "extractAndLoadChunks") {
|
||||
const [code, matcher] = args;
|
||||
|
||||
const module = Vencord.Webpack.findModuleFactory(...code);
|
||||
if (module) result = module.toString().match(canonicalizeMatch(matcher));
|
||||
} else {
|
||||
// @ts-ignore
|
||||
result = Vencord.Webpack[method](...args);
|
||||
}
|
||||
|
||||
if (result == null || ("$$vencordInternal" in result && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
|
||||
} catch (e) {
|
||||
let logMessage = searchType;
|
||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
||||
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
||||
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||
|
||||
console.log("[PUP_WEBPACK_FIND_FAIL]", logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => console.log("[PUPPETEER_TEST_DONE_SIGNAL]"), 1000);
|
||||
} catch (e) {
|
||||
console.log("[PUP_DEBUG]", "A fatal error occurred:", e);
|
||||
}
|
||||
}
|
||||
|
||||
await page.evaluateOnNewDocument(`
|
||||
${readFileSync("./dist/browser.js", "utf-8")}
|
||||
|
||||
;(${runtime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
|
||||
if (location.host.endsWith("discord.com")) {
|
||||
${readFileSync("./dist/browser.js", "utf-8")};
|
||||
(${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
|
||||
}
|
||||
`);
|
||||
|
||||
await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login");
|
||||
|
|
|
@ -42,6 +42,10 @@ import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
|
|||
import { onceReady } from "./webpack";
|
||||
import { SettingsRouter } from "./webpack/common";
|
||||
|
||||
if (IS_REPORTER) {
|
||||
require("./debug/runReporter");
|
||||
}
|
||||
|
||||
async function syncSettings() {
|
||||
// pre-check for local shared settings
|
||||
if (
|
||||
|
|
|
@ -49,7 +49,7 @@ let defaultGetStoreFunc: UseStore | undefined;
|
|||
|
||||
function defaultGetStore() {
|
||||
if (!defaultGetStoreFunc) {
|
||||
defaultGetStoreFunc = createStore("VencordData", "VencordStore");
|
||||
defaultGetStoreFunc = createStore(!IS_REPORTER ? "VencordData" : "VencordDataReporter", "VencordStore");
|
||||
}
|
||||
return defaultGetStoreFunc;
|
||||
}
|
||||
|
|
29
src/api/MessageUpdater.ts
Normal file
29
src/api/MessageUpdater.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { MessageCache, MessageStore } from "@webpack/common";
|
||||
import { FluxStore } from "@webpack/types";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
/**
|
||||
* Update and re-render a message
|
||||
* @param channelId The channel id of the message
|
||||
* @param messageId The message id
|
||||
* @param fields The fields of the message to change. Leave empty if you just want to re-render
|
||||
*/
|
||||
export function updateMessage(channelId: string, messageId: string, fields?: Partial<Message>) {
|
||||
const channelMessageCache = MessageCache.getOrCreate(channelId);
|
||||
if (!channelMessageCache.has(messageId)) return;
|
||||
|
||||
// To cause a message to re-render, we basically need to create a new instance of the message and obtain a new reference
|
||||
// If we have fields to modify we can use the merge method of the class, otherwise we just create a new instance with the old fields
|
||||
const newChannelMessageCache = channelMessageCache.update(messageId, (oldMessage: any) => {
|
||||
return fields ? oldMessage.merge(fields) : new oldMessage.constructor(oldMessage);
|
||||
});
|
||||
|
||||
MessageCache.commit(newChannelMessageCache);
|
||||
(MessageStore as unknown as FluxStore).emitChange();
|
||||
}
|
|
@ -113,7 +113,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
|||
{timeout !== 0 && !permanent && (
|
||||
<div
|
||||
className="vc-notification-progressbar"
|
||||
style={{ width: `${(1 - timeoutProgress) * 100}%`, backgroundColor: color || "var(--brand-experiment)" }}
|
||||
style={{ width: `${(1 - timeoutProgress) * 100}%`, backgroundColor: color || "var(--brand-500)" }}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
|
|
@ -106,7 +106,7 @@ const DefaultSettings: Settings = {
|
|||
}
|
||||
};
|
||||
|
||||
const settings = VencordNative.settings.get();
|
||||
const settings = !IS_REPORTER ? VencordNative.settings.get() : {} as Settings;
|
||||
mergeDefaults(settings, DefaultSettings);
|
||||
|
||||
const saveSettingsOnFrequentAction = debounce(async () => {
|
||||
|
@ -129,7 +129,7 @@ export const SettingsStore = new SettingsStoreClass(settings, {
|
|||
|
||||
if (path === "plugins" && key in plugins)
|
||||
return target[key] = {
|
||||
enabled: plugins[key].required ?? plugins[key].enabledByDefault ?? false
|
||||
enabled: IS_REPORTER ?? plugins[key].required ?? plugins[key].enabledByDefault ?? false
|
||||
};
|
||||
|
||||
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
||||
|
@ -156,12 +156,14 @@ export const SettingsStore = new SettingsStoreClass(settings, {
|
|||
}
|
||||
});
|
||||
|
||||
SettingsStore.addGlobalChangeListener((_, path) => {
|
||||
if (!IS_REPORTER) {
|
||||
SettingsStore.addGlobalChangeListener((_, path) => {
|
||||
SettingsStore.plain.cloud.settingsSyncVersion = Date.now();
|
||||
localStorage.Vencord_settingsDirty = true;
|
||||
saveSettingsOnFrequentAction();
|
||||
VencordNative.settings.set(SettingsStore.plain, path);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link Settings} but unproxied. You should treat this as readonly,
|
||||
|
|
|
@ -26,6 +26,7 @@ import * as $MessageAccessories from "./MessageAccessories";
|
|||
import * as $MessageDecorations from "./MessageDecorations";
|
||||
import * as $MessageEventsAPI from "./MessageEvents";
|
||||
import * as $MessagePopover from "./MessagePopover";
|
||||
import * as $MessageUpdater from "./MessageUpdater";
|
||||
import * as $Notices from "./Notices";
|
||||
import * as $Notifications from "./Notifications";
|
||||
import * as $ServerList from "./ServerList";
|
||||
|
@ -110,3 +111,8 @@ export const ContextMenu = $ContextMenu;
|
|||
* An API allowing you to add buttons to the chat input
|
||||
*/
|
||||
export const ChatButtons = $ChatButtons;
|
||||
|
||||
/**
|
||||
* An API allowing you to update and re-render messages
|
||||
*/
|
||||
export const MessageUpdater = $MessageUpdater;
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
|
||||
import { Logger } from "@utils/Logger";
|
||||
|
||||
if (IS_DEV) {
|
||||
if (IS_DEV || IS_REPORTER) {
|
||||
var traces = {} as Record<string, [number, any[]]>;
|
||||
var logger = new Logger("Tracer", "#FFD166");
|
||||
}
|
||||
|
||||
const noop = function () { };
|
||||
|
||||
export const beginTrace = !IS_DEV ? noop :
|
||||
export const beginTrace = !(IS_DEV || IS_REPORTER) ? noop :
|
||||
function beginTrace(name: string, ...args: any[]) {
|
||||
if (name in traces)
|
||||
throw new Error(`Trace ${name} already exists!`);
|
||||
|
@ -33,7 +33,7 @@ export const beginTrace = !IS_DEV ? noop :
|
|||
traces[name] = [performance.now(), args];
|
||||
};
|
||||
|
||||
export const finishTrace = !IS_DEV ? noop : function finishTrace(name: string) {
|
||||
export const finishTrace = !(IS_DEV || IS_REPORTER) ? noop : function finishTrace(name: string) {
|
||||
const end = performance.now();
|
||||
|
||||
const [start, args] = traces[name];
|
||||
|
@ -48,7 +48,7 @@ type TraceNameMapper<F extends Func> = (...args: Parameters<F>) => string;
|
|||
const noopTracer =
|
||||
<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) => f;
|
||||
|
||||
export const traceFunction = !IS_DEV
|
||||
export const traceFunction = !(IS_DEV || IS_REPORTER)
|
||||
? noopTracer
|
||||
: function traceFunction<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): F {
|
||||
return function (this: any, ...args: Parameters<F>) {
|
||||
|
|
167
src/debug/loadLazyChunks.ts
Normal file
167
src/debug/loadLazyChunks.ts
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeMatch } from "@utils/patches";
|
||||
import * as Webpack from "@webpack";
|
||||
import { wreq } from "@webpack";
|
||||
|
||||
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||
|
||||
export async function loadLazyChunks() {
|
||||
try {
|
||||
LazyChunkLoaderLogger.log("Loading all chunks...");
|
||||
|
||||
const validChunks = new Set<string>();
|
||||
const invalidChunks = new Set<string>();
|
||||
const deferredRequires = new Set<string>();
|
||||
|
||||
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
||||
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
||||
|
||||
// True if resolved, false otherwise
|
||||
const chunksSearchPromises = [] as Array<() => boolean>;
|
||||
|
||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
|
||||
|
||||
async function searchAndLoadLazyChunks(factoryCode: string) {
|
||||
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
||||
const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
|
||||
|
||||
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
|
||||
// the chunk containing the component
|
||||
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
|
||||
|
||||
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
||||
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
|
||||
|
||||
if (chunkIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let invalidChunkGroup = false;
|
||||
|
||||
for (const id of chunkIds) {
|
||||
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
||||
|
||||
const isWasm = await fetch(wreq.p + wreq.u(id))
|
||||
.then(r => r.text())
|
||||
.then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
|
||||
|
||||
if (isWasm && IS_WEB) {
|
||||
invalidChunks.add(id);
|
||||
invalidChunkGroup = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
validChunks.add(id);
|
||||
}
|
||||
|
||||
if (!invalidChunkGroup) {
|
||||
validChunkGroups.add([chunkIds, entryPoint]);
|
||||
}
|
||||
}));
|
||||
|
||||
// Loads all found valid chunk groups
|
||||
await Promise.all(
|
||||
Array.from(validChunkGroups)
|
||||
.map(([chunkIds]) =>
|
||||
Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
|
||||
)
|
||||
);
|
||||
|
||||
// Requires the entry points for all valid chunk groups
|
||||
for (const [, entryPoint] of validChunkGroups) {
|
||||
try {
|
||||
if (shouldForceDefer) {
|
||||
deferredRequires.add(entryPoint);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wreq.m[entryPoint]) wreq(entryPoint as any);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// setImmediate to only check if all chunks were loaded after this function resolves
|
||||
// We check if all chunks were loaded every time a factory is loaded
|
||||
// If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
|
||||
// But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
|
||||
setTimeout(() => {
|
||||
let allResolved = true;
|
||||
|
||||
for (let i = 0; i < chunksSearchPromises.length; i++) {
|
||||
const isResolved = chunksSearchPromises[i]();
|
||||
|
||||
if (isResolved) {
|
||||
// Remove finished promises to avoid having to iterate through a huge array everytime
|
||||
chunksSearchPromises.splice(i--, 1);
|
||||
} else {
|
||||
allResolved = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allResolved) chunksSearchingResolve();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
Webpack.factoryListeners.add(factory => {
|
||||
let isResolved = false;
|
||||
searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
|
||||
|
||||
chunksSearchPromises.push(() => isResolved);
|
||||
});
|
||||
|
||||
for (const factoryId in wreq.m) {
|
||||
let isResolved = false;
|
||||
searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
|
||||
|
||||
chunksSearchPromises.push(() => isResolved);
|
||||
}
|
||||
|
||||
await chunksSearchingDone;
|
||||
|
||||
// Require deferred entry points
|
||||
for (const deferredRequire of deferredRequires) {
|
||||
wreq!(deferredRequire as any);
|
||||
}
|
||||
|
||||
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
||||
const allChunks = [] as string[];
|
||||
|
||||
// Matches "id" or id:
|
||||
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
|
||||
const id = currentMatch[1] ?? currentMatch[2];
|
||||
if (id == null) continue;
|
||||
|
||||
allChunks.push(id);
|
||||
}
|
||||
|
||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
||||
|
||||
// Chunks that are not loaded (not used) by Discord code anymore
|
||||
const chunksLeft = allChunks.filter(id => {
|
||||
return !(validChunks.has(id) || invalidChunks.has(id));
|
||||
});
|
||||
|
||||
await Promise.all(chunksLeft.map(async id => {
|
||||
const isWasm = await fetch(wreq.p + wreq.u(id))
|
||||
.then(r => r.text())
|
||||
.then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
|
||||
|
||||
// Loads and requires a chunk
|
||||
if (!isWasm) {
|
||||
await wreq.e(id as any);
|
||||
if (wreq.m[id]) wreq(id as any);
|
||||
}
|
||||
}));
|
||||
|
||||
LazyChunkLoaderLogger.log("Finished loading all chunks!");
|
||||
} catch (e) {
|
||||
LazyChunkLoaderLogger.log("A fatal error occurred:", e);
|
||||
}
|
||||
}
|
75
src/debug/runReporter.ts
Normal file
75
src/debug/runReporter.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Logger } from "@utils/Logger";
|
||||
import * as Webpack from "@webpack";
|
||||
import { patches } from "plugins";
|
||||
|
||||
import { loadLazyChunks } from "./loadLazyChunks";
|
||||
|
||||
const ReporterLogger = new Logger("Reporter");
|
||||
|
||||
async function runReporter() {
|
||||
try {
|
||||
ReporterLogger.log("Starting test...");
|
||||
|
||||
let loadLazyChunksResolve: (value: void | PromiseLike<void>) => void;
|
||||
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
|
||||
|
||||
Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve)));
|
||||
await loadLazyChunksDone;
|
||||
|
||||
for (const patch of patches) {
|
||||
if (!patch.all) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [searchType, args] of Webpack.lazyWebpackSearchHistory) {
|
||||
let method = searchType;
|
||||
|
||||
if (searchType === "findComponent") method = "find";
|
||||
if (searchType === "findExportedComponent") method = "findByProps";
|
||||
if (searchType === "waitFor" || searchType === "waitForComponent") {
|
||||
if (typeof args[0] === "string") method = "findByProps";
|
||||
else method = "find";
|
||||
}
|
||||
if (searchType === "waitForStore") method = "findStore";
|
||||
|
||||
try {
|
||||
let result: any;
|
||||
|
||||
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||
const [factory] = args;
|
||||
result = factory();
|
||||
} else if (method === "extractAndLoadChunks") {
|
||||
const [code, matcher] = args;
|
||||
|
||||
result = await Webpack.extractAndLoadChunks(code, matcher);
|
||||
if (result === false) result = null;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
result = Webpack[method](...args);
|
||||
}
|
||||
|
||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
|
||||
} catch (e) {
|
||||
let logMessage = searchType;
|
||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
||||
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
||||
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||
|
||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
ReporterLogger.log("Finished test");
|
||||
} catch (e) {
|
||||
ReporterLogger.log("A fatal error occurred:", e);
|
||||
}
|
||||
}
|
||||
|
||||
runReporter();
|
3
src/globals.d.ts
vendored
3
src/globals.d.ts
vendored
|
@ -34,9 +34,10 @@ declare global {
|
|||
*/
|
||||
export var IS_WEB: boolean;
|
||||
export var IS_EXTENSION: boolean;
|
||||
export var IS_DEV: boolean;
|
||||
export var IS_STANDALONE: boolean;
|
||||
export var IS_UPDATER_DISABLED: boolean;
|
||||
export var IS_DEV: boolean;
|
||||
export var IS_REPORTER: boolean;
|
||||
export var IS_DISCORD_DESKTOP: boolean;
|
||||
export var IS_VESKTOP: boolean;
|
||||
export var VERSION: string;
|
||||
|
|
|
@ -140,8 +140,14 @@ if (!IS_VANILLA) {
|
|||
return originalAppend.apply(this, args);
|
||||
};
|
||||
|
||||
// disable renderer backgrounding to prevent the app from unloading when in the background
|
||||
// https://github.com/electron/electron/issues/2822
|
||||
// https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
|
||||
// Work around discord unloading when in background
|
||||
// Discord also recently started adding these flags but only on windows for some reason dunno why, it happens on Linux too
|
||||
app.commandLine.appendSwitch("disable-renderer-backgrounding");
|
||||
app.commandLine.appendSwitch("disable-background-timer-throttling");
|
||||
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
|
||||
} else {
|
||||
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
||||
}
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
*/
|
||||
|
||||
if (!IS_UPDATER_DISABLED)
|
||||
import(IS_STANDALONE ? "./http" : "./git");
|
||||
require(IS_STANDALONE ? "./http" : "./git");
|
||||
|
|
37
src/plugins/_api/messageUpdater.ts
Normal file
37
src/plugins/_api/messageUpdater.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "MessageUpdaterAPI",
|
||||
description: "API for updating and re-rendering messages.",
|
||||
authors: [Devs.Nuckyz],
|
||||
|
||||
patches: [
|
||||
{
|
||||
// Message accessories have a custom logic to decide if they should render again, so we need to make it not ignore changed message reference
|
||||
find: "}renderEmbeds(",
|
||||
replacement: {
|
||||
match: /(?<=this.props,\i,\[)"message",/,
|
||||
replace: ""
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
|
@ -65,7 +65,7 @@ export default definePlugin({
|
|||
commands: [{
|
||||
name: "vencord-debug",
|
||||
description: "Send Vencord Debug info",
|
||||
predicate: ctx => AllowedChannelIds.includes(ctx.channel.id),
|
||||
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
||||
async execute() {
|
||||
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import { popNotice, showNotice } from "@api/Notices";
|
||||
import { Link } from "@components/Link";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import definePlugin, { ReporterTestable } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
||||
|
||||
|
@ -41,6 +41,7 @@ export default definePlugin({
|
|||
name: "WebRichPresence (arRPC)",
|
||||
description: "Client plugin for arRPC to enable RPC on Discord Web (experimental)",
|
||||
authors: [Devs.Ducko],
|
||||
reporterTestable: ReporterTestable.None,
|
||||
|
||||
settingsAboutComponent: () => (
|
||||
<>
|
||||
|
|
|
@ -25,6 +25,7 @@ import definePlugin, { PluginNative, StartAt } from "@utils/types";
|
|||
import * as Webpack from "@webpack";
|
||||
import { extract, filters, findAll, findModuleId, search } from "@webpack";
|
||||
import * as Common from "@webpack/common";
|
||||
import { loadLazyChunks } from "debug/loadLazyChunks";
|
||||
import type { ComponentType } from "react";
|
||||
|
||||
const DESKTOP_ONLY = (f: string) => () => {
|
||||
|
@ -82,6 +83,7 @@ function makeShortcuts() {
|
|||
wpsearch: search,
|
||||
wpex: extract,
|
||||
wpexs: (code: string) => extract(findModuleId(code)!),
|
||||
loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); },
|
||||
find,
|
||||
findAll: findAll,
|
||||
findByProps,
|
||||
|
|
|
@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
|
||||
import { filters, findAll, search } from "@webpack";
|
||||
|
||||
const PORT = 8485;
|
||||
|
@ -243,6 +243,7 @@ export default definePlugin({
|
|||
name: "DevCompanion",
|
||||
description: "Dev Companion Plugin",
|
||||
authors: [Devs.Ven],
|
||||
reporterTestable: ReporterTestable.None,
|
||||
settings,
|
||||
|
||||
toolboxActions: {
|
||||
|
|
|
@ -333,7 +333,7 @@ export default definePlugin({
|
|||
]
|
||||
},
|
||||
{
|
||||
find: "renderEmbeds(",
|
||||
find: "}renderEmbeds(",
|
||||
replacement: [
|
||||
{
|
||||
// Call our function to decide whether the embed should be ignored or not
|
||||
|
|
|
@ -36,7 +36,7 @@ export default definePlugin({
|
|||
{
|
||||
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
|
||||
replacement: {
|
||||
match: /\i.default,\{userId:(\i)}\)/,
|
||||
match: /\i.default,\{userId:([^,]+?)}\)/,
|
||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -21,7 +21,7 @@ import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
|||
import { Settings } from "@api/Settings";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeFind } from "@utils/patches";
|
||||
import { Patch, Plugin, StartAt } from "@utils/types";
|
||||
import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
import { FluxEvents } from "@webpack/types";
|
||||
|
||||
|
@ -39,6 +39,7 @@ export const patches = [] as Patch[];
|
|||
let enabledPluginsSubscribedFlux = false;
|
||||
const subscribedFluxEventsPlugins = new Set<string>();
|
||||
|
||||
const pluginsValues = Object.values(Plugins);
|
||||
const settings = Settings.plugins;
|
||||
|
||||
export function isPluginEnabled(p: string) {
|
||||
|
@ -49,25 +50,56 @@ export function isPluginEnabled(p: string) {
|
|||
) ?? false;
|
||||
}
|
||||
|
||||
const pluginsValues = Object.values(Plugins);
|
||||
export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string) {
|
||||
const patch = newPatch as Patch;
|
||||
patch.plugin = pluginName;
|
||||
|
||||
// First roundtrip to mark and force enable dependencies (only for enabled plugins)
|
||||
if (IS_REPORTER) {
|
||||
delete patch.predicate;
|
||||
delete patch.group;
|
||||
}
|
||||
|
||||
canonicalizeFind(patch);
|
||||
if (!Array.isArray(patch.replacement)) {
|
||||
patch.replacement = [patch.replacement];
|
||||
}
|
||||
|
||||
if (IS_REPORTER) {
|
||||
patch.replacement.forEach(r => {
|
||||
delete r.predicate;
|
||||
});
|
||||
}
|
||||
|
||||
patches.push(patch);
|
||||
}
|
||||
|
||||
function isReporterTestable(p: Plugin, part: ReporterTestable) {
|
||||
return p.reporterTestable == null
|
||||
? true
|
||||
: (p.reporterTestable & part) === part;
|
||||
}
|
||||
|
||||
// First round-trip to mark and force enable dependencies
|
||||
//
|
||||
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
|
||||
// goes for the top level and their children, but for now this works okay with the current API plugins
|
||||
for (const p of pluginsValues) if (settings[p.name]?.enabled) {
|
||||
for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
|
||||
p.dependencies?.forEach(d => {
|
||||
const dep = Plugins[d];
|
||||
if (dep) {
|
||||
|
||||
if (!dep) {
|
||||
const error = new Error(`Plugin ${p.name} has unresolved dependency ${d}`);
|
||||
|
||||
if (IS_DEV) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.warn(error);
|
||||
return;
|
||||
}
|
||||
|
||||
settings[d].enabled = true;
|
||||
dep.isDependency = true;
|
||||
}
|
||||
else {
|
||||
const error = new Error(`Plugin ${p.name} has unresolved dependency ${d}`);
|
||||
if (IS_DEV)
|
||||
throw error;
|
||||
logger.warn(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -82,23 +114,18 @@ for (const p of pluginsValues) {
|
|||
}
|
||||
|
||||
if (p.patches && isPluginEnabled(p.name)) {
|
||||
if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
|
||||
for (const patch of p.patches) {
|
||||
patch.plugin = p.name;
|
||||
|
||||
canonicalizeFind(patch);
|
||||
if (!Array.isArray(patch.replacement)) {
|
||||
patch.replacement = [patch.replacement];
|
||||
addPatch(patch, p.name);
|
||||
}
|
||||
|
||||
patches.push(patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) {
|
||||
logger.info(`Starting plugins (stage ${target})`);
|
||||
for (const name in Plugins)
|
||||
if (isPluginEnabled(name)) {
|
||||
for (const name in Plugins) {
|
||||
if (isPluginEnabled(name) && (!IS_REPORTER || isReporterTestable(Plugins[name], ReporterTestable.Start))) {
|
||||
const p = Plugins[name];
|
||||
|
||||
const startAt = p.startAt ?? StartAt.WebpackReady;
|
||||
|
@ -106,30 +133,38 @@ export const startAllPlugins = traceFunction("startAllPlugins", function startAl
|
|||
|
||||
startPlugin(Plugins[name]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export function startDependenciesRecursive(p: Plugin) {
|
||||
let restartNeeded = false;
|
||||
const failures: string[] = [];
|
||||
p.dependencies?.forEach(dep => {
|
||||
if (!Settings.plugins[dep].enabled) {
|
||||
startDependenciesRecursive(Plugins[dep]);
|
||||
|
||||
p.dependencies?.forEach(d => {
|
||||
if (!settings[d].enabled) {
|
||||
const dep = Plugins[d];
|
||||
startDependenciesRecursive(dep);
|
||||
|
||||
// If the plugin has patches, don't start the plugin, just enable it.
|
||||
Settings.plugins[dep].enabled = true;
|
||||
if (Plugins[dep].patches) {
|
||||
logger.warn(`Enabling dependency ${dep} requires restart.`);
|
||||
settings[d].enabled = true;
|
||||
dep.isDependency = true;
|
||||
|
||||
if (dep.patches) {
|
||||
logger.warn(`Enabling dependency ${d} requires restart.`);
|
||||
restartNeeded = true;
|
||||
return;
|
||||
}
|
||||
const result = startPlugin(Plugins[dep]);
|
||||
if (!result) failures.push(dep);
|
||||
|
||||
const result = startPlugin(dep);
|
||||
if (!result) failures.push(d);
|
||||
}
|
||||
});
|
||||
|
||||
return { restartNeeded, failures };
|
||||
}
|
||||
|
||||
export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof FluxDispatcher) {
|
||||
if (p.flux && !subscribedFluxEventsPlugins.has(p.name)) {
|
||||
if (p.flux && !subscribedFluxEventsPlugins.has(p.name) && (!IS_REPORTER || isReporterTestable(p, ReporterTestable.FluxEvents))) {
|
||||
subscribedFluxEventsPlugins.add(p.name);
|
||||
|
||||
logger.debug("Subscribing to flux events of plugin", p.name);
|
||||
|
|
|
@ -18,12 +18,13 @@
|
|||
|
||||
import { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { updateMessage } from "@api/MessageUpdater";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getStegCloak } from "@utils/dependencies";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ChannelStore, Constants, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
|
||||
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
|
||||
import { ChannelStore, Constants, RestAPI, Tooltip } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
import { buildDecModal } from "./components/DecryptionModal";
|
||||
|
@ -103,7 +104,10 @@ export default definePlugin({
|
|||
name: "InvisibleChat",
|
||||
description: "Encrypt your Messages in a non-suspicious way!",
|
||||
authors: [Devs.SammCheese],
|
||||
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"],
|
||||
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI", "MessageUpdaterAPI"],
|
||||
reporterTestable: ReporterTestable.Patches,
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
// Indicator
|
||||
|
@ -120,7 +124,6 @@ export default definePlugin({
|
|||
URL_REGEX: new RegExp(
|
||||
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
|
||||
),
|
||||
settings,
|
||||
async start() {
|
||||
addButton("InvisibleChat", message => {
|
||||
return this.INV_REGEX.test(message?.content)
|
||||
|
@ -180,14 +183,7 @@ export default definePlugin({
|
|||
message.embeds.push(embed);
|
||||
}
|
||||
|
||||
this.updateMessage(message);
|
||||
},
|
||||
|
||||
updateMessage: (message: any) => {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_UPDATE",
|
||||
message,
|
||||
});
|
||||
updateMessage(message.channel_id, message.id, { embeds: message.embeds });
|
||||
},
|
||||
|
||||
popOverIcon: () => <PopOverIcon />,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { updateMessage } from "@api/MessageUpdater";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants.js";
|
||||
|
@ -28,7 +29,6 @@ import {
|
|||
Button,
|
||||
ChannelStore,
|
||||
Constants,
|
||||
FluxDispatcher,
|
||||
GuildStore,
|
||||
IconUtils,
|
||||
MessageStore,
|
||||
|
@ -250,15 +250,9 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
|||
if (linkedMessage) {
|
||||
messageCache.set(messageID, { message: linkedMessage, fetched: true });
|
||||
} else {
|
||||
const msg = { ...message } as any;
|
||||
delete msg.embeds;
|
||||
delete msg.interaction;
|
||||
|
||||
messageFetchQueue.unshift(() => fetchMessage(channelID, messageID)
|
||||
.then(m => m && FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_UPDATE",
|
||||
message: msg
|
||||
}))
|
||||
.then(m => m && updateMessage(message.channel_id, message.id))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
@ -367,7 +361,7 @@ export default definePlugin({
|
|||
name: "MessageLinkEmbeds",
|
||||
description: "Adds a preview to messages that link another message",
|
||||
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
|
||||
dependencies: ["MessageAccessoriesAPI"],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
|
|
|
@ -62,6 +62,16 @@ export default definePlugin({
|
|||
replace: "return 0;"
|
||||
}
|
||||
},
|
||||
// New message requests hook
|
||||
{
|
||||
find: "useNewMessageRequestsCount:",
|
||||
predicate: () => settings.store.hideMessageRequestsCount,
|
||||
replacement: {
|
||||
match: /getNonChannelAckId\(\i\.\i\.MESSAGE_REQUESTS\).+?return /,
|
||||
replace: "$&0;"
|
||||
}
|
||||
},
|
||||
// Old message requests hook
|
||||
{
|
||||
find: "getMessageRequestsCount(){",
|
||||
predicate: () => settings.store.hideMessageRequestsCount,
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
|
||||
const enum Intensity {
|
||||
|
@ -46,6 +46,7 @@ export default definePlugin({
|
|||
name: "PartyMode",
|
||||
description: "Allows you to use party mode cause the party never ends ✨",
|
||||
authors: [Devs.UwUDev],
|
||||
reporterTestable: ReporterTestable.None,
|
||||
settings,
|
||||
|
||||
start() {
|
||||
|
|
|
@ -16,9 +16,8 @@ export default definePlugin({
|
|||
{
|
||||
find: '"call_ringing_beat"',
|
||||
replacement: {
|
||||
// FIXME Remove === alternative when it hits stable
|
||||
match: /500(!==|===)\i\(\)\.random\(1,1e3\)/,
|
||||
replace: (_, predicate) => predicate === "!==" ? "false" : "true",
|
||||
match: /500!==\i\(\)\.random\(1,1e3\)/,
|
||||
replace: "false",
|
||||
}
|
||||
},
|
||||
],
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
Enables Discord's experimental Summaries feature on every server, displaying AI generated summaries of conversations.
|
||||
|
||||
Read more about summaries in the [official Discord help article](https://support.discord.com/hc/en-us/articles/12926016807575-In-Channel-Conversation-Summaries)!
|
||||
|
||||
Note that this plugin can't fetch old summaries, it can only display ones created while your Discord is running with the plugin enabled.
|
||||
|
||||
![](https://github.com/Vendicated/Vencord/assets/45497981/bd931b0c-2e85-4c10-9f7c-8ba01eb55745)
|
||||
|
|
|
@ -20,7 +20,7 @@ import "./shiki.css";
|
|||
|
||||
import { enableStyle } from "@api/Styles";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import definePlugin, { ReporterTestable } from "@utils/types";
|
||||
import previewExampleText from "file://previewExample.tsx";
|
||||
|
||||
import { shiki } from "./api/shiki";
|
||||
|
@ -34,6 +34,9 @@ export default definePlugin({
|
|||
name: "ShikiCodeblocks",
|
||||
description: "Brings vscode-style codeblocks into Discord, powered by Shiki",
|
||||
authors: [Devs.Vap],
|
||||
reporterTestable: ReporterTestable.Patches,
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "codeBlock:{react(",
|
||||
|
@ -66,7 +69,6 @@ export default definePlugin({
|
|||
isPreview: true,
|
||||
tempSettings,
|
||||
}),
|
||||
settings,
|
||||
|
||||
// exports
|
||||
shiki,
|
||||
|
|
|
@ -182,7 +182,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: "\"Profile Panel: user cannot be undefined\"",
|
||||
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
|
||||
replacement: {
|
||||
// createElement(Divider, {}), createElement(NoteComponent)
|
||||
match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:(?=.+?channelId:(\i).id)/,
|
||||
|
|
|
@ -73,8 +73,9 @@ export default definePlugin({
|
|||
find: '"placeholder-channel-id"',
|
||||
replacement: [
|
||||
// Remove the special logic for channels we don't have access to
|
||||
// FIXME Remove variable matcher from threadsIds when it hits stable
|
||||
{
|
||||
match: /if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{if\(this\.id===\i\).+?threadIds:\i}}/,
|
||||
match: /if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{if\(this\.id===\i\).+?threadIds:(?:\[\]|\i)}}/,
|
||||
replace: ""
|
||||
},
|
||||
// Do not check for unreads when selecting the render level if the channel is hidden
|
||||
|
|
|
@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { wordsToTitle } from "@utils/text";
|
||||
import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
|
||||
import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
|
||||
|
||||
|
@ -155,6 +155,7 @@ export default definePlugin({
|
|||
name: "VcNarrator",
|
||||
description: "Announces when users join, leave, or move voice channels via narrator",
|
||||
authors: [Devs.Ven],
|
||||
reporterTestable: ReporterTestable.None,
|
||||
|
||||
flux: {
|
||||
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { makeRange } from "@components/PluginSettings/components";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType, PluginNative } from "@utils/types";
|
||||
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
|
||||
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
|
||||
|
@ -143,7 +143,9 @@ export default definePlugin({
|
|||
description: "Forwards discord notifications to XSOverlay, for easy viewing in VR",
|
||||
authors: [Devs.Nyako],
|
||||
tags: ["vr", "notify"],
|
||||
reporterTestable: ReporterTestable.None,
|
||||
settings,
|
||||
|
||||
flux: {
|
||||
CALL_UPDATE({ call }: { call: Call; }) {
|
||||
if (call?.ringing?.includes(UserStore.getCurrentUser().id) && settings.store.callNotifications) {
|
||||
|
|
|
@ -32,6 +32,11 @@ export class Logger {
|
|||
constructor(public name: string, public color: string = "white") { }
|
||||
|
||||
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
|
||||
if (IS_REPORTER && IS_WEB) {
|
||||
console[level]("[Vencord]", this.name + ":", ...args);
|
||||
return;
|
||||
}
|
||||
|
||||
console[level](
|
||||
`%c Vencord %c %c ${this.name} ${customFmt}`,
|
||||
`background: ${levelColor}; color: black; font-weight: bold; border-radius: 5px;`,
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
*/
|
||||
|
||||
import { makeLazy } from "./lazy";
|
||||
import { EXTENSION_BASE_URL } from "./web-metadata";
|
||||
|
||||
/*
|
||||
Add dynamically loaded dependencies for plugins here.
|
||||
|
@ -67,15 +66,6 @@ export interface ApngFrameData {
|
|||
playTime: number;
|
||||
}
|
||||
|
||||
// On web (extensions), use extension uri as basepath (load files from extension)
|
||||
// On desktop (electron), load from cdn
|
||||
export const rnnoiseDist = IS_EXTENSION
|
||||
? new URL("/third-party/rnnoise", EXTENSION_BASE_URL).toString()
|
||||
: "https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.3/dist";
|
||||
export const rnnoiseWasmSrc = (simd = false) => `${rnnoiseDist}/rnnoise${simd ? "_simd" : ""}.wasm`;
|
||||
export const rnnoiseWorkletSrc = `${rnnoiseDist}/rnnoise/workletProcessor.js`;
|
||||
|
||||
|
||||
// The below code is only used on the Desktop (electron) build of Vencord.
|
||||
// Browser (extension) builds do not contain these remote imports.
|
||||
|
||||
|
|
|
@ -94,6 +94,10 @@ export interface PluginDef {
|
|||
* @default StartAt.WebpackReady
|
||||
*/
|
||||
startAt?: StartAt,
|
||||
/**
|
||||
* Which parts of the plugin can be tested by the reporter. Defaults to all parts
|
||||
*/
|
||||
reporterTestable?: number;
|
||||
/**
|
||||
* Optionally provide settings that the user can configure in the Plugins tab of settings.
|
||||
* @deprecated Use `settings` instead
|
||||
|
@ -144,6 +148,13 @@ export const enum StartAt {
|
|||
WebpackReady = "WebpackReady"
|
||||
}
|
||||
|
||||
export const enum ReporterTestable {
|
||||
None = 1 << 1,
|
||||
Start = 1 << 2,
|
||||
Patches = 1 << 3,
|
||||
FluxEvents = 1 << 4
|
||||
}
|
||||
|
||||
export const enum OptionType {
|
||||
STRING,
|
||||
NUMBER,
|
||||
|
|
|
@ -22,7 +22,7 @@ import { LazyComponent } from "@utils/react";
|
|||
import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "../webpack";
|
||||
|
||||
export function waitForComponent<T extends React.ComponentType<any> = React.ComponentType<any> & Record<string, any>>(name: string, filter: FilterFn | string | string[]): T {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]);
|
||||
|
||||
let myValue: T = function () {
|
||||
throw new Error(`Vencord could not find the ${name} Component`);
|
||||
|
@ -38,7 +38,7 @@ export function waitForComponent<T extends React.ComponentType<any> = React.Comp
|
|||
}
|
||||
|
||||
export function waitForStore(name: string, cb: (v: any) => void) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["waitForStore", [name]]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForStore", [name]]);
|
||||
|
||||
waitFor(filters.byStoreName(name), cb, { isIndirect: true });
|
||||
}
|
||||
|
|
25
src/webpack/common/types/stores.d.ts
vendored
25
src/webpack/common/types/stores.d.ts
vendored
|
@ -41,8 +41,33 @@ export class FluxStore {
|
|||
__getLocalVars(): Record<string, any>;
|
||||
}
|
||||
|
||||
export class FluxEmitter {
|
||||
constructor();
|
||||
|
||||
changeSentinel: number;
|
||||
changedStores: Set<FluxStore>;
|
||||
isBatchEmitting: boolean;
|
||||
isDispatching: boolean;
|
||||
isPaused: boolean;
|
||||
pauseTimer: NodeJS.Timeout | null;
|
||||
reactChangedStores: Set<FluxStore>;
|
||||
|
||||
batched(batch: (...args: any[]) => void): void;
|
||||
destroy(): void;
|
||||
emit(): void;
|
||||
emitNonReactOnce(): void;
|
||||
emitReactOnce(): void;
|
||||
getChangeSentinel(): number;
|
||||
getIsPaused(): boolean;
|
||||
injectBatchEmitChanges(batch: (...args: any[]) => void): void;
|
||||
markChanged(store: FluxStore): void;
|
||||
pause(): void;
|
||||
resume(): void;
|
||||
}
|
||||
|
||||
export interface Flux {
|
||||
Store: typeof FluxStore;
|
||||
Emitter: FluxEmitter;
|
||||
}
|
||||
|
||||
export class WindowStore extends FluxStore {
|
||||
|
|
|
@ -144,6 +144,7 @@ const persistFilter = filters.byCode("[zustand persist middleware]");
|
|||
export const { persist: zustandPersist } = findLazy(m => m.persist && persistFilter(m.persist));
|
||||
|
||||
export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
||||
export const MessageCache = findByPropsLazy("clearCache", "_channelMessages");
|
||||
export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal");
|
||||
export const InviteActions = findByPropsLazy("resolveInvite");
|
||||
|
||||
|
|
|
@ -209,7 +209,7 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
|
|||
|
||||
// There are (at the time of writing) 11 modules exporting the window
|
||||
// Make these non enumerable to improve webpack search performance
|
||||
if (exports === window && require.c) {
|
||||
if (require.c && (exports === window || exports?.default === window)) {
|
||||
Object.defineProperty(require.c, id, {
|
||||
value: require.c[id],
|
||||
enumerable: false,
|
||||
|
@ -229,7 +229,7 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
|
|||
|
||||
for (const [filter, callback] of subscriptions) {
|
||||
try {
|
||||
if (filter(exports)) {
|
||||
if (exports && filter(exports)) {
|
||||
subscriptions.delete(filter);
|
||||
callback(exports, id);
|
||||
} else if (exports.default && filter(exports.default)) {
|
||||
|
|
|
@ -106,13 +106,13 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn
|
|||
|
||||
for (const key in cache) {
|
||||
const mod = cache[key];
|
||||
if (!mod?.exports || mod.exports === window) continue;
|
||||
if (!mod?.exports) continue;
|
||||
|
||||
if (filter(mod.exports)) {
|
||||
return isWaitFor ? [mod.exports, key] : mod.exports;
|
||||
}
|
||||
|
||||
if (mod.exports.default && mod.exports.default !== window && filter(mod.exports.default)) {
|
||||
if (mod.exports.default && filter(mod.exports.default)) {
|
||||
const found = mod.exports.default;
|
||||
return isWaitFor ? [found, key] : found;
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "f
|
|||
* @example const mod = proxyLazy(() => findByProps("blah")); console.log(mod.blah);
|
||||
*/
|
||||
export function proxyLazyWebpack<T = any>(factory: () => any, attempts?: number) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["proxyLazyWebpack", [factory]]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["proxyLazyWebpack", [factory]]);
|
||||
|
||||
return proxyLazy<T>(factory, attempts);
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ export function proxyLazyWebpack<T = any>(factory: () => any, attempts?: number)
|
|||
* @returns Result of factory function
|
||||
*/
|
||||
export function LazyComponentWebpack<T extends object = any>(factory: () => any, attempts?: number) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["LazyComponentWebpack", [factory]]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["LazyComponentWebpack", [factory]]);
|
||||
|
||||
return LazyComponent<T>(factory, attempts);
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ export function LazyComponentWebpack<T extends object = any>(factory: () => any,
|
|||
* Find the first module that matches the filter, lazily
|
||||
*/
|
||||
export function findLazy(filter: FilterFn) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["find", [filter]]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["find", [filter]]);
|
||||
|
||||
return proxyLazy(() => find(filter));
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ export function findByProps(...props: string[]) {
|
|||
* Find the first module that has the specified properties, lazily
|
||||
*/
|
||||
export function findByPropsLazy(...props: string[]) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["findByProps", props]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByProps", props]);
|
||||
|
||||
return proxyLazy(() => findByProps(...props));
|
||||
}
|
||||
|
@ -325,7 +325,7 @@ export function findByCode(...code: string[]) {
|
|||
* Find the first function that includes all the given code, lazily
|
||||
*/
|
||||
export function findByCodeLazy(...code: string[]) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["findByCode", code]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByCode", code]);
|
||||
|
||||
return proxyLazy(() => findByCode(...code));
|
||||
}
|
||||
|
@ -344,7 +344,7 @@ export function findStore(name: string) {
|
|||
* Find a store by its displayName, lazily
|
||||
*/
|
||||
export function findStoreLazy(name: string) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["findStore", [name]]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findStore", [name]]);
|
||||
|
||||
return proxyLazy(() => findStore(name));
|
||||
}
|
||||
|
@ -363,7 +363,7 @@ export function findComponentByCode(...code: string[]) {
|
|||
* Finds the first component that matches the filter, lazily.
|
||||
*/
|
||||
export function findComponentLazy<T extends object = any>(filter: FilterFn) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["findComponent", [filter]]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findComponent", [filter]]);
|
||||
|
||||
|
||||
return LazyComponent<T>(() => {
|
||||
|
@ -378,7 +378,7 @@ export function findComponentLazy<T extends object = any>(filter: FilterFn) {
|
|||
* Finds the first component that includes all the given code, lazily
|
||||
*/
|
||||
export function findComponentByCodeLazy<T extends object = any>(...code: string[]) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["findComponentByCode", code]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findComponentByCode", code]);
|
||||
|
||||
return LazyComponent<T>(() => {
|
||||
const res = find(filters.componentByCode(...code), { isIndirect: true });
|
||||
|
@ -392,7 +392,7 @@ export function findComponentByCodeLazy<T extends object = any>(...code: string[
|
|||
* Finds the first component that is exported by the first prop name, lazily
|
||||
*/
|
||||
export function findExportedComponentLazy<T extends object = any>(...props: string[]) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["findExportedComponent", props]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findExportedComponent", props]);
|
||||
|
||||
return LazyComponent<T>(() => {
|
||||
const res = find(filters.byProps(...props), { isIndirect: true });
|
||||
|
@ -402,14 +402,14 @@ export function findExportedComponentLazy<T extends object = any>(...props: stri
|
|||
});
|
||||
}
|
||||
|
||||
export const DefaultExtractAndLoadChunksRegex = /(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\))|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/;
|
||||
export const ChunkIdsRegex = /\("(.+?)"\)/g;
|
||||
export const DefaultExtractAndLoadChunksRegex = /(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/;
|
||||
export const ChunkIdsRegex = /\("([^"]+?)"\)/g;
|
||||
|
||||
/**
|
||||
* Extract and load chunks using their entry point
|
||||
* @param code An array of all the code the module factory containing the lazy chunk loading must include
|
||||
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory
|
||||
* @returns A promise that resolves when the chunks were loaded
|
||||
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory
|
||||
* @returns A promise that resolves with a boolean whether the chunks were loaded
|
||||
*/
|
||||
export async function extractAndLoadChunks(code: string[], matcher: RegExp = DefaultExtractAndLoadChunksRegex) {
|
||||
const module = findModuleFactory(...code);
|
||||
|
@ -417,7 +417,11 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
|
|||
const err = new Error("extractAndLoadChunks: Couldn't find module factory");
|
||||
logger.warn(err, "Code:", code, "Matcher:", matcher);
|
||||
|
||||
return;
|
||||
// Strict behaviour in DevBuilds to fail early and make sure the issue is found
|
||||
if (IS_DEV && !devToolsOpen)
|
||||
throw err;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = module.toString().match(canonicalizeMatch(matcher));
|
||||
|
@ -429,10 +433,10 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
|
|||
if (IS_DEV && !devToolsOpen)
|
||||
throw err;
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const [, rawChunkIdsArray, rawChunkIdsSingle, entryPointId] = match;
|
||||
const [, rawChunkIds, entryPointId] = match;
|
||||
if (Number.isNaN(Number(entryPointId))) {
|
||||
const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the chunk ids array, or the entry point id returned as the second group wasn't a number");
|
||||
logger.warn(err, "Code:", code, "Matcher:", matcher);
|
||||
|
@ -441,16 +445,27 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
|
|||
if (IS_DEV && !devToolsOpen)
|
||||
throw err;
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle;
|
||||
if (rawChunkIds) {
|
||||
const chunkIds = Array.from(rawChunkIds.matchAll(ChunkIdsRegex)).map((m: any) => m[1]);
|
||||
await Promise.all(chunkIds.map(id => wreq.e(id)));
|
||||
}
|
||||
|
||||
if (wreq.m[entryPointId] == null) {
|
||||
const err = new Error("extractAndLoadChunks: Entry point is not loaded in the module factories, perhaps one of the chunks failed to load");
|
||||
logger.warn(err, "Code:", code, "Matcher:", matcher);
|
||||
|
||||
// Strict behaviour in DevBuilds to fail early and make sure the issue is found
|
||||
if (IS_DEV && !devToolsOpen)
|
||||
throw err;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
wreq(entryPointId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -458,11 +473,11 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
|
|||
*
|
||||
* Extract and load chunks using their entry point
|
||||
* @param code An array of all the code the module factory containing the lazy chunk loading must include
|
||||
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory
|
||||
* @returns A function that returns a promise that resolves when the chunks were loaded, on first call
|
||||
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory
|
||||
* @returns A function that returns a promise that resolves with a boolean whether the chunks were loaded, on first call
|
||||
*/
|
||||
export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtractAndLoadChunksRegex) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
|
||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
|
||||
|
||||
return makeLazy(() => extractAndLoadChunks(code, matcher));
|
||||
}
|
||||
|
@ -472,7 +487,7 @@ export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtrac
|
|||
* then call the callback with the module as the first argument
|
||||
*/
|
||||
export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn, { isIndirect = false }: { isIndirect?: boolean; } = {}) {
|
||||
if (IS_DEV && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]);
|
||||
if (IS_REPORTER && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]);
|
||||
|
||||
if (typeof filter === "string")
|
||||
filter = filters.byProps(filter);
|
||||
|
|
Loading…
Reference in a new issue