Reporter: Properly implement reporter build of Vencord; Test more plugins; Fix running in wrong pages

This commit is contained in:
Nuckyz 2024-05-30 04:44:13 -03:00
parent 892de53603
commit 9a9c1b0487
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
19 changed files with 184 additions and 145 deletions

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

View file

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

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

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

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

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

View file

@ -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 });
@ -477,7 +477,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) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
if (IS_REPORTER) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
return makeLazy(() => extractAndLoadChunks(code, matcher));
}
@ -487,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);