diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 0fde48637..cf4210779 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -241,17 +241,26 @@ page.on("console", async e => {
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 "A fatal error occurred:":
- process.exit(1);
case "Finished test":
await browser.close();
await printReport();
diff --git a/src/api/Notifications/NotificationComponent.tsx b/src/api/Notifications/NotificationComponent.tsx
index caa4b64ef..d07143c45 100644
--- a/src/api/Notifications/NotificationComponent.tsx
+++ b/src/api/Notifications/NotificationComponent.tsx
@@ -113,7 +113,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({
{timeout !== 0 && !permanent && (
)}
diff --git a/src/api/Settings.ts b/src/api/Settings.ts
index b94e6a3fd..70ba0bd4a 100644
--- a/src/api/Settings.ts
+++ b/src/api/Settings.ts
@@ -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
diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts
new file mode 100644
index 000000000..d8f84335c
--- /dev/null
+++ b/src/debug/loadLazyChunks.ts
@@ -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();
+ const invalidChunks = new Set();
+ const deferredRequires = new Set();
+
+ let chunksSearchingResolve: (value: void | PromiseLike) => void;
+ const chunksSearchingDone = new Promise(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);
+ }
+}
diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts
index 61c9f162b..6c7a2a03f 100644
--- a/src/debug/runReporter.ts
+++ b/src/debug/runReporter.ts
@@ -5,171 +5,22 @@
*/
import { Logger } from "@utils/Logger";
-import { canonicalizeMatch } from "@utils/patches";
import * as Webpack from "@webpack";
-import { wreq } from "@webpack";
import { patches } from "plugins";
+import { loadLazyChunks } from "./loadLazyChunks";
+
const ReporterLogger = new Logger("Reporter");
async function runReporter() {
- ReporterLogger.log("Starting test...");
-
try {
- const validChunks = new Set();
- const invalidChunks = new Set();
- const deferredRequires = new Set();
+ ReporterLogger.log("Starting test...");
- let chunksSearchingResolve: (value: void | PromiseLike) => void;
- const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
+ let loadLazyChunksResolve: (value: void | PromiseLike) => void;
+ const loadLazyChunksDone = new Promise(r => loadLazyChunksResolve = 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.beforeInitListeners.add(async () => {
- ReporterLogger.log("Loading all chunks...");
-
- 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 => (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);
- }
- }));
-
- ReporterLogger.log("Finished loading all chunks!");
+ Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve)));
+ await loadLazyChunksDone;
for (const patch of patches) {
if (!patch.all) {
diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts
index ee86b5fcf..0a1323e75 100644
--- a/src/plugins/consoleShortcuts/index.ts
+++ b/src/plugins/consoleShortcuts/index.ts
@@ -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,
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index 53ab7983a..32bfe7e97 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -44,7 +44,6 @@ const settings = Settings.plugins;
export function isPluginEnabled(p: string) {
return (
- IS_REPORTER ||
Plugins[p]?.required ||
Plugins[p]?.isDependency ||
settings[p]?.enabled
diff --git a/src/plugins/noPendingCount/index.ts b/src/plugins/noPendingCount/index.ts
index 29458df9d..57a65f52c 100644
--- a/src/plugins/noPendingCount/index.ts
+++ b/src/plugins/noPendingCount/index.ts
@@ -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,
diff --git a/src/plugins/partyMode/index.ts b/src/plugins/partyMode/index.ts
index 56c19c02c..c40f2e3c7 100644
--- a/src/plugins/partyMode/index.ts
+++ b/src/plugins/partyMode/index.ts
@@ -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() {
diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts
index 5296184d4..22a381360 100644
--- a/src/utils/Logger.ts
+++ b/src/utils/Logger.ts
@@ -32,7 +32,7 @@ export class Logger {
constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
- if (IS_REPORTER) {
+ if (IS_REPORTER && IS_WEB) {
console[level]("[Vencord]", this.name + ":", ...args);
return;
}