Merge branch 'dev' into immediate-finds

This commit is contained in:
Nuckyz 2024-05-30 05:13:13 -03:00
commit c0984db612
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
18 changed files with 218 additions and 182 deletions

View file

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

View file

@ -20,7 +20,9 @@
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
"buildStandalone": "pnpm build --standalone",
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
"buildReporter": "pnpm buildWeb --standalone --reporter --skip-extension",
"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",

View file

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

View file

@ -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}
@ -40,15 +40,16 @@ const commonOptions = {
],
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([
appendCssRuntime,
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
]);
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;
}

View file

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

View file

@ -282,7 +282,7 @@ page.on("pageerror", e => console.error("[Page Error]", e));
await page.setBypassCSP(true);
async function runtime(token: string) {
async function reporterRuntime(token: string) {
console.log("[PUP_DEBUG]", "Starting test...");
try {
@ -290,41 +290,7 @@ async function runtime(token: string) {
Object.defineProperty(navigator, "languages", {
get: function () {
return ["en-US", "en"];
},
});
// Monkey patch Logger to not log with custom css
const originalLog = Vencord.Util.Logger.prototype["_log"];
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;
@ -599,9 +565,10 @@ async function runtime(token: string) {
}
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");

View file

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

3
src/globals.d.ts vendored
View file

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

View file

@ -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 { findByProps } 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: () => (
<>

View file

@ -21,8 +21,8 @@ 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 { cacheFind, filters, search } from "@webpack";
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { cacheFindAll, filters, search } from "@webpack";
const PORT = 8485;
const NAV_ID = "dev-companion-reconnect";
@ -201,22 +201,22 @@ function initWs(isManual = false) {
let results: any[];
switch (type.replace("find", "").replace("Lazy", "")) {
case "":
results = cacheFind(parsedArgs[0]);
results = cacheFindAll(parsedArgs[0]);
break;
case "ByProps":
results = cacheFind(filters.byProps(...parsedArgs));
results = cacheFindAll(filters.byProps(...parsedArgs));
break;
case "Store":
results = cacheFind(filters.byStoreName(parsedArgs[0]));
results = cacheFindAll(filters.byStoreName(parsedArgs[0]));
break;
case "ByCode":
results = cacheFind(filters.byCode(...parsedArgs));
results = cacheFindAll(filters.byCode(...parsedArgs));
break;
case "ModuleId":
results = Object.keys(search(parsedArgs[0]));
break;
case "ComponentByCode":
results = cacheFind(filters.componentByCode(...parsedArgs));
results = cacheFindAll(filters.componentByCode(...parsedArgs));
break;
default:
return reply("Unknown Find Type " + type);
@ -243,6 +243,7 @@ export default definePlugin({
name: "DevCompanion",
description: "Dev Companion Plugin",
authors: [Devs.Ven],
reporterTestable: ReporterTestable.None,
settings,
toolboxActions: {

View file

@ -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,35 +39,68 @@ 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) {
return (
IS_REPORTER ||
Plugins[p]?.required ||
Plugins[p]?.isDependency ||
settings[p]?.enabled
) ?? 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) {
settings[d].enabled = true;
dep.isDependency = true;
}
else {
if (!dep) {
const error = new Error(`Plugin ${p.name} has unresolved dependency ${d}`);
if (IS_DEV)
if (IS_DEV) {
throw error;
}
logger.warn(error);
return;
}
settings[d].enabled = true;
dep.isDependency = true;
});
}
@ -82,23 +115,18 @@ for (const p of pluginsValues) {
}
if (p.patches && isPluginEnabled(p.name)) {
for (const patch of p.patches) {
patch.plugin = p.name;
canonicalizeFind(patch);
if (!Array.isArray(patch.replacement)) {
patch.replacement = [patch.replacement];
if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
for (const patch of p.patches) {
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 +134,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);

View file

@ -23,7 +23,7 @@ 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 definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { ChannelStore, Constants, RestAPI, Tooltip } from "@webpack/common";
import { Message } from "discord-types/general";
@ -105,6 +105,9 @@ export default definePlugin({
description: "Encrypt your Messages in a non-suspicious way!",
authors: [Devs.SammCheese],
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI", "MessageUpdaterAPI"],
reporterTestable: ReporterTestable.Patches,
settings,
patches: [
{
// Indicator
@ -121,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)

View file

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

View file

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

View file

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

View file

@ -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 && (level === "warn" || level === "error")) {
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;`,

View file

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

View file

@ -132,7 +132,7 @@ export function waitFor(filter: FilterFn, callback: ModCallbackFn, { isIndirect
if (typeof callback !== "function")
throw new Error("Invalid callback. Expected a function got " + typeof callback);
if (IS_DEV && !isIndirect) {
if (IS_REPORTER && !isIndirect) {
const originalCallback = callback;
let callbackCalled = false;
@ -180,7 +180,7 @@ export function find<T = AnyObject>(filter: FilterFn, callback: (mod: any) => an
const [proxy, setInnerValue] = proxyInner<T>(`Webpack find matched no module. Filter: ${printFilter(filter)}`, "Webpack find with proxy called on a primitive value. This can happen if you try to destructure a primitive in the top level definition of the find.");
waitFor(filter, mod => setInnerValue(callback(mod)), { isIndirect: true });
if (IS_DEV && !isIndirect) {
if (IS_REPORTER && !isIndirect) {
webpackSearchHistory.push(["find", [proxy, filter]]);
}
@ -220,7 +220,7 @@ export function findComponent<T extends object = any>(filter: FilterFn, parse: (
Object.assign(WrapperComponent, parsedComponent);
}, { isIndirect: true });
if (IS_DEV) {
if (IS_REPORTER) {
WrapperComponent.$$vencordInner = () => InnerComponent;
if (!isIndirect) {
@ -268,7 +268,7 @@ export function findExportedComponent<T extends object = any>(...props: string[]
Object.assign(WrapperComponent, parsedComponent);
}, { isIndirect: true });
if (IS_DEV) {
if (IS_REPORTER) {
WrapperComponent.$$vencordInner = () => InnerComponent;
webpackSearchHistory.push(["findExportedComponent", [WrapperComponent, ...newProps]]);
}
@ -294,7 +294,7 @@ export function findComponentByCode<T extends object = any>(...code: string[] |
const ComponentResult = findComponent<T>(filters.componentByCode(...newCode), parse, { isIndirect: true });
if (IS_DEV) {
if (IS_REPORTER) {
webpackSearchHistory.push(["findComponentByCode", [ComponentResult, ...newCode]]);
}
@ -309,7 +309,7 @@ export function findComponentByCode<T extends object = any>(...code: string[] |
export function findByProps<T = AnyObject>(...props: string[]) {
const result = find<T>(filters.byProps(...props), m => m, { isIndirect: true });
if (IS_DEV) {
if (IS_REPORTER) {
webpackSearchHistory.push(["findByProps", [result, ...props]]);
}
@ -324,7 +324,7 @@ export function findByProps<T = AnyObject>(...props: string[]) {
export function findByCode<T = AnyObject>(...code: string[]) {
const result = find<T>(filters.byCode(...code), m => m, { isIndirect: true });
if (IS_DEV) {
if (IS_REPORTER) {
webpackSearchHistory.push(["findByCode", [result, ...code]]);
}
@ -339,7 +339,7 @@ export function findByCode<T = AnyObject>(...code: string[]) {
export function findStore<T = GenericStore>(name: string) {
const result = find<T>(filters.byStoreName(name), m => m, { isIndirect: true });
if (IS_DEV) {
if (IS_REPORTER) {
webpackSearchHistory.push(["findStore", [result, name]]);
}
@ -448,12 +448,10 @@ export const cacheFindBulk = traceFunction("cacheFindBulk", function cacheFindBu
if (found !== length) {
const err = new Error(`Got ${length} filters, but only found ${found} modules!`);
if (!IS_DEV) {
if (!IS_DEV || devToolsOpen) {
logger.warn(err);
return results;
}
if (!devToolsOpen) {
return null;
} else {
throw err; // Strict behaviour in DevBuilds to fail early and make sure the issue is found
}
}
@ -476,16 +474,13 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId(
}
const err = new Error("Didn't find module with code(s):\n" + code.join("\n"));
if (!IS_DEV) {
if (!IS_DEV || devToolsOpen) {
logger.warn(err);
return null;
}
if (!devToolsOpen) {
} else {
throw err; // Strict behaviour in DevBuilds to fail early and make sure the issue is found
}
return null;
});
/**
@ -509,7 +504,7 @@ export function findModuleFactory(...code: string[]) {
* @returns Result of factory function
*/
export function webpackDependantLazy<T = AnyObject>(factory: () => T, attempts?: number) {
if (IS_DEV) webpackSearchHistory.push(["webpackDependantLazy", [factory]]);
if (IS_REPORTER) webpackSearchHistory.push(["webpackDependantLazy", [factory]]);
return proxyLazy<T>(factory, attempts);
}
@ -524,7 +519,7 @@ export function webpackDependantLazy<T = AnyObject>(factory: () => T, attempts?:
* @returns Result of factory function
*/
export function webpackDependantLazyComponent<T extends object = any>(factory: () => any, attempts?: number) {
if (IS_DEV) webpackSearchHistory.push(["webpackDependantLazyComponent", [factory]]);
if (IS_REPORTER) webpackSearchHistory.push(["webpackDependantLazyComponent", [factory]]);
return LazyComponent<T>(factory, attempts);
}
@ -643,37 +638,37 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
const module = findModuleFactory(...code);
if (!module) {
const err = new Error("extractAndLoadChunks: Couldn't find module factory");
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;
if (!IS_DEV || devToolsOpen) {
logger.warn(err, "Code:", code, "Matcher:", matcher);
return false;
} else {
throw err; // Strict behaviour in DevBuilds to fail early and make sure the issue is found
}
}
const match = module.toString().match(canonicalizeMatch(matcher));
if (!match) {
const err = new Error("extractAndLoadChunks: Couldn't find chunk loading in module factory code");
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;
if (!IS_DEV || devToolsOpen) {
logger.warn(err, "Code:", code, "Matcher:", matcher);
return false;
} else {
throw err; // Strict behaviour in DevBuilds to fail early and make sure the issue is found
}
}
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);
// Strict behaviour in DevBuilds to fail early and make sure the issue is found
if (IS_DEV && !devToolsOpen)
throw err;
return false;
if (!IS_DEV || devToolsOpen) {
logger.warn(err, "Code:", code, "Matcher:", matcher);
return false;
} else {
throw err; // Strict behaviour in DevBuilds to fail early and make sure the issue is found
}
}
if (rawChunkIds) {
@ -683,13 +678,13 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
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;
if (!IS_DEV || devToolsOpen) {
logger.warn(err, "Code:", code, "Matcher:", matcher);
return false;
} else {
throw err; // Strict behaviour in DevBuilds to fail early and make sure the issue is found
}
}
wreq(entryPointId);
@ -706,7 +701,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
* @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) webpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
if (IS_REPORTER) webpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
return makeLazy(() => extractAndLoadChunks(code, matcher));
}