mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-20 20:07:03 -04:00
newDevTools
This commit is contained in:
commit
7ce2c9187c
11 changed files with 713 additions and 221 deletions
|
@ -16,233 +16,40 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
|
||||
import { filters, findAll, search } from "@webpack";
|
||||
|
||||
const PORT = 8485;
|
||||
import { initWs, socket, stopWs } from "./initWs";
|
||||
console.log("imported");
|
||||
export const PORT = 8485;
|
||||
const NAV_ID = "dev-companion-reconnect";
|
||||
|
||||
const logger = new Logger("DevCompanion");
|
||||
export const logger = new Logger("DevCompanion");
|
||||
|
||||
let socket: WebSocket | undefined;
|
||||
|
||||
type Node = StringNode | RegexNode | FunctionNode;
|
||||
|
||||
interface StringNode {
|
||||
type: "string";
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface RegexNode {
|
||||
type: "regex";
|
||||
value: {
|
||||
pattern: string;
|
||||
flags: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface FunctionNode {
|
||||
type: "function";
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface PatchData {
|
||||
find: string;
|
||||
replacement: {
|
||||
match: StringNode | RegexNode;
|
||||
replace: StringNode | FunctionNode;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface FindData {
|
||||
type: string;
|
||||
args: Array<StringNode | FunctionNode>;
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
export const settings = definePluginSettings({
|
||||
notifyOnAutoConnect: {
|
||||
description: "Whether to notify when Dev Companion has automatically connected.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
},
|
||||
usePatchedModule: {
|
||||
description: "On extract requests, reply with the current patched module (if it is patched) instead of the original.",
|
||||
default: true,
|
||||
type: OptionType.BOOLEAN,
|
||||
},
|
||||
reloadAfterToggle: {
|
||||
description: "Reload after a disable/enable plugin command is recived.",
|
||||
default: true,
|
||||
type: OptionType.BOOLEAN
|
||||
}
|
||||
});
|
||||
|
||||
function parseNode(node: Node) {
|
||||
switch (node.type) {
|
||||
case "string":
|
||||
return node.value;
|
||||
case "regex":
|
||||
return new RegExp(node.value.pattern, node.value.flags);
|
||||
case "function":
|
||||
// We LOVE remote code execution
|
||||
// Safety: This comes from localhost only, which actually means we have less permissions than the source,
|
||||
// since we're running in the browser sandbox, whereas the sender has host access
|
||||
return (0, eval)(node.value);
|
||||
default:
|
||||
throw new Error("Unknown Node Type " + (node as any).type);
|
||||
}
|
||||
}
|
||||
|
||||
function initWs(isManual = false) {
|
||||
let wasConnected = isManual;
|
||||
let hasErrored = false;
|
||||
const ws = socket = new WebSocket(`ws://localhost:${PORT}`);
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
wasConnected = true;
|
||||
|
||||
logger.info("Connected to WebSocket");
|
||||
|
||||
(settings.store.notifyOnAutoConnect || isManual) && showNotification({
|
||||
title: "Dev Companion Connected",
|
||||
body: "Connected to WebSocket",
|
||||
noPersist: true
|
||||
});
|
||||
});
|
||||
|
||||
ws.addEventListener("error", e => {
|
||||
if (!wasConnected) return;
|
||||
|
||||
hasErrored = true;
|
||||
|
||||
logger.error("Dev Companion Error:", e);
|
||||
|
||||
showNotification({
|
||||
title: "Dev Companion Error",
|
||||
body: (e as ErrorEvent).message || "No Error Message",
|
||||
color: "var(--status-danger, red)",
|
||||
noPersist: true,
|
||||
});
|
||||
});
|
||||
|
||||
ws.addEventListener("close", e => {
|
||||
if (!wasConnected || hasErrored) return;
|
||||
|
||||
logger.info("Dev Companion Disconnected:", e.code, e.reason);
|
||||
|
||||
showNotification({
|
||||
title: "Dev Companion Disconnected",
|
||||
body: e.reason || "No Reason provided",
|
||||
color: "var(--status-danger, red)",
|
||||
noPersist: true,
|
||||
});
|
||||
});
|
||||
|
||||
ws.addEventListener("message", e => {
|
||||
try {
|
||||
var { nonce, type, data } = JSON.parse(e.data);
|
||||
} catch (err) {
|
||||
logger.error("Invalid JSON:", err, "\n" + e.data);
|
||||
return;
|
||||
}
|
||||
|
||||
function reply(error?: string) {
|
||||
const data = { nonce, ok: !error } as Record<string, unknown>;
|
||||
if (error) data.error = error;
|
||||
|
||||
ws.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
logger.info("Received Message:", type, "\n", data);
|
||||
|
||||
switch (type) {
|
||||
case "testPatch": {
|
||||
const { find, replacement } = data as PatchData;
|
||||
|
||||
const candidates = search(find);
|
||||
const keys = Object.keys(candidates);
|
||||
if (keys.length !== 1)
|
||||
return reply("Expected exactly one 'find' matches, found " + keys.length);
|
||||
|
||||
const mod = candidates[keys[0]];
|
||||
let src = String(mod.original ?? mod).replaceAll("\n", "");
|
||||
|
||||
if (src.startsWith("function(")) {
|
||||
src = "0," + src;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
|
||||
for (const { match, replace } of replacement) {
|
||||
i++;
|
||||
|
||||
try {
|
||||
const matcher = canonicalizeMatch(parseNode(match));
|
||||
const replacement = canonicalizeReplace(parseNode(replace), "PlaceHolderPluginName");
|
||||
|
||||
const newSource = src.replace(matcher, replacement as string);
|
||||
|
||||
if (src === newSource) throw "Had no effect";
|
||||
Function(newSource);
|
||||
|
||||
src = newSource;
|
||||
} catch (err) {
|
||||
return reply(`Replacement ${i} failed: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
reply();
|
||||
break;
|
||||
}
|
||||
case "testFind": {
|
||||
const { type, args } = data as FindData;
|
||||
try {
|
||||
var parsedArgs = args.map(parseNode);
|
||||
} catch (err) {
|
||||
return reply("Failed to parse args: " + err);
|
||||
}
|
||||
|
||||
try {
|
||||
let results: any[];
|
||||
switch (type.replace("find", "").replace("Lazy", "")) {
|
||||
case "":
|
||||
results = findAll(parsedArgs[0]);
|
||||
break;
|
||||
case "ByProps":
|
||||
results = findAll(filters.byProps(...parsedArgs));
|
||||
break;
|
||||
case "Store":
|
||||
results = findAll(filters.byStoreName(parsedArgs[0]));
|
||||
break;
|
||||
case "ByCode":
|
||||
results = findAll(filters.byCode(...parsedArgs));
|
||||
break;
|
||||
case "ModuleId":
|
||||
results = Object.keys(search(parsedArgs[0]));
|
||||
break;
|
||||
case "ComponentByCode":
|
||||
results = findAll(filters.componentByCode(...parsedArgs));
|
||||
break;
|
||||
default:
|
||||
return reply("Unknown Find Type " + type);
|
||||
}
|
||||
|
||||
const uniqueResultsCount = new Set(results).size;
|
||||
if (uniqueResultsCount === 0) throw "No results";
|
||||
if (uniqueResultsCount > 1) throw "Found more than one result! Make this filter more specific";
|
||||
} catch (err) {
|
||||
return reply("Failed to find: " + err);
|
||||
}
|
||||
|
||||
reply();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
reply("Unknown Type " + type);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "DevCompanion",
|
||||
description: "Dev Companion Plugin",
|
||||
authors: [Devs.Ven],
|
||||
authors: [Devs.Ven, Devs.sadan, Devs.Samwich],
|
||||
reporterTestable: ReporterTestable.None,
|
||||
settings,
|
||||
|
||||
|
@ -254,11 +61,11 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
start() {
|
||||
initWs();
|
||||
// if we're running the reporter, we need to initws in the reporter file to avoid a race condition
|
||||
if (!IS_COMPANION_TEST)
|
||||
initWs();
|
||||
},
|
||||
|
||||
stop() {
|
||||
socket?.close(1000, "Plugin Stopped");
|
||||
socket = void 0;
|
||||
}
|
||||
stop: stopWs,
|
||||
});
|
||||
|
||||
|
|
377
src/plugins/devCompanion/initWs.tsx
Normal file
377
src/plugins/devCompanion/initWs.tsx
Normal file
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||
import { filters, findAll, search, wreq } from "@webpack";
|
||||
import { Toasts } from "@webpack/common";
|
||||
import { reporterData } from "debug/reporterData";
|
||||
import { Settings } from "Vencord";
|
||||
|
||||
import { logger, PORT, settings } from ".";
|
||||
import { extractModule, extractOrThrow, FindData, findModuleId, FindType, mkRegexFind, parseNode, PatchData, SendData, toggleEnabled, } from "./util";
|
||||
|
||||
export function stopWs() {
|
||||
socket?.close(1000, "Plugin Stopped");
|
||||
socket = void 0;
|
||||
}
|
||||
|
||||
export let socket: WebSocket | undefined;
|
||||
|
||||
export function initWs(isManual = false) {
|
||||
let wasConnected = isManual;
|
||||
let hasErrored = false;
|
||||
const ws = socket = new WebSocket(`ws://localhost:${PORT}`);
|
||||
|
||||
function replyData<T extends SendData>(data: T) {
|
||||
ws.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
wasConnected = true;
|
||||
|
||||
logger.info("Connected to WebSocket");
|
||||
|
||||
// send module cache to vscode
|
||||
replyData({
|
||||
type: "moduleList",
|
||||
data: Object.keys(wreq.m),
|
||||
ok: true,
|
||||
});
|
||||
|
||||
if (IS_COMPANION_TEST) {
|
||||
const toSend = JSON.stringify(reporterData, (_k, v) => {
|
||||
if (v instanceof RegExp)
|
||||
return String(v);
|
||||
return v;
|
||||
});
|
||||
|
||||
socket?.send(JSON.stringify({
|
||||
type: "report",
|
||||
data: JSON.parse(toSend),
|
||||
ok: true
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
(settings.store.notifyOnAutoConnect || isManual) && Toasts.show({
|
||||
message: "Connected to WebSocket",
|
||||
id: Toasts.genId(),
|
||||
type: Toasts.Type.SUCCESS,
|
||||
options: {
|
||||
position: Toasts.Position.TOP
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ws.addEventListener("error", e => {
|
||||
if (!wasConnected) return;
|
||||
|
||||
hasErrored = true;
|
||||
|
||||
logger.error("Dev Companion Error:", e);
|
||||
|
||||
Toasts.show({
|
||||
message: "Dev Companion Error",
|
||||
id: Toasts.genId(),
|
||||
type: Toasts.Type.FAILURE,
|
||||
options: {
|
||||
position: Toasts.Position.TOP
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ws.addEventListener("close", e => {
|
||||
if (!wasConnected || hasErrored) return;
|
||||
|
||||
logger.info("Dev Companion Disconnected:", e.code, e.reason);
|
||||
|
||||
Toasts.show({
|
||||
message: "Dev Companion Disconnected",
|
||||
id: Toasts.genId(),
|
||||
type: Toasts.Type.FAILURE,
|
||||
options: {
|
||||
position: Toasts.Position.TOP
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ws.addEventListener("message", e => {
|
||||
try {
|
||||
var { nonce, type, data } = JSON.parse(e.data);
|
||||
} catch (err) {
|
||||
logger.error("Invalid JSON:", err, "\n" + e.data);
|
||||
return;
|
||||
}
|
||||
function reply(error?: string) {
|
||||
const data = { nonce, ok: !error } as Record<string, unknown>;
|
||||
if (error) data.error = error;
|
||||
|
||||
ws.send(JSON.stringify(data));
|
||||
}
|
||||
function replyData<T extends SendData>(data: T) {
|
||||
data.nonce = nonce;
|
||||
ws.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
logger.info("Received Message:", type, "\n", data);
|
||||
|
||||
switch (type) {
|
||||
case "disable": {
|
||||
const { enabled, pluginName } = data;
|
||||
const settings = Settings.plugins[pluginName];
|
||||
if (enabled !== settings.enabled)
|
||||
toggleEnabled(pluginName, reply);
|
||||
break;
|
||||
}
|
||||
case "rawId": {
|
||||
const { id } = data;
|
||||
replyData({
|
||||
ok: true,
|
||||
data: extractModule(id),
|
||||
type: "ret"
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "diff": {
|
||||
try {
|
||||
const { extractType, idOrSearch } = data;
|
||||
switch (extractType) {
|
||||
case "id": {
|
||||
if (typeof idOrSearch !== "number")
|
||||
throw new Error("Id is not a number, got :" + typeof idOrSearch);
|
||||
replyData({
|
||||
type: "diff",
|
||||
ok: true,
|
||||
data: {
|
||||
patched: extractOrThrow(idOrSearch),
|
||||
source: extractModule(idOrSearch, false)
|
||||
},
|
||||
moduleNumber: idOrSearch
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "search": {
|
||||
let moduleId;
|
||||
if (data.findType === FindType.STRING)
|
||||
moduleId = +findModuleId([idOrSearch.toString()]);
|
||||
|
||||
else
|
||||
moduleId = +findModuleId(mkRegexFind(idOrSearch));
|
||||
const p = extractOrThrow(moduleId);
|
||||
const p2 = extractModule(moduleId, false);
|
||||
console.log(p, p2, "done");
|
||||
replyData({
|
||||
type: "diff",
|
||||
ok: true,
|
||||
data: {
|
||||
patched: p,
|
||||
source: p2
|
||||
},
|
||||
moduleNumber: moduleId
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
reply(String(error));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "reload": {
|
||||
reply();
|
||||
window.location.reload();
|
||||
break;
|
||||
}
|
||||
case "extract": {
|
||||
try {
|
||||
const { extractType, idOrSearch } = data;
|
||||
switch (extractType) {
|
||||
case "id": {
|
||||
if (typeof idOrSearch !== "number")
|
||||
throw new Error("Id is not a number, got :" + typeof idOrSearch);
|
||||
|
||||
else
|
||||
replyData({
|
||||
type: "extract",
|
||||
ok: true,
|
||||
data: extractModule(idOrSearch),
|
||||
moduleNumber: idOrSearch
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "search": {
|
||||
let moduleId;
|
||||
if (data.findType === FindType.STRING)
|
||||
moduleId = +findModuleId([idOrSearch.toString()]);
|
||||
|
||||
else
|
||||
moduleId = +findModuleId(mkRegexFind(idOrSearch));
|
||||
replyData({
|
||||
type: "extract",
|
||||
ok: true,
|
||||
data: extractModule(moduleId),
|
||||
moduleNumber: moduleId
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "find": {
|
||||
const { findType, findArgs } = data;
|
||||
try {
|
||||
var parsedArgs = findArgs.map(parseNode);
|
||||
} catch (err) {
|
||||
return reply("Failed to parse args: " + err);
|
||||
}
|
||||
|
||||
try {
|
||||
let results: any[];
|
||||
switch (findType.replace("find", "").replace("Lazy", "")) {
|
||||
case "":
|
||||
case "Component":
|
||||
results = findAll(parsedArgs[0]);
|
||||
break;
|
||||
case "ByProps":
|
||||
results = findAll(filters.byProps(...parsedArgs));
|
||||
break;
|
||||
case "Store":
|
||||
results = findAll(filters.byStoreName(parsedArgs[0]));
|
||||
break;
|
||||
case "ByCode":
|
||||
results = findAll(filters.byCode(...parsedArgs));
|
||||
break;
|
||||
case "ModuleId":
|
||||
results = Object.keys(search(parsedArgs[0]));
|
||||
break;
|
||||
case "ComponentByCode":
|
||||
results = findAll(filters.componentByCode(...parsedArgs));
|
||||
break;
|
||||
default:
|
||||
return reply("Unknown Find Type " + findType);
|
||||
}
|
||||
|
||||
const uniqueResultsCount = new Set(results).size;
|
||||
if (uniqueResultsCount === 0) throw "No results";
|
||||
if (uniqueResultsCount > 1) throw "Found more than one result! Make this filter more specific";
|
||||
// best name ever
|
||||
const foundFind: string = [...results][0].toString();
|
||||
replyData({
|
||||
type: "extract",
|
||||
ok: true,
|
||||
find: true,
|
||||
data: foundFind,
|
||||
moduleNumber: +findModuleId([foundFind])
|
||||
});
|
||||
} catch (err) {
|
||||
return reply("Failed to find: " + err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
reply(`Unknown Extract type. Got: ${extractType}`);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
reply(String(error));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "testPatch": {
|
||||
const { find, replacement } = data as PatchData;
|
||||
|
||||
let candidates;
|
||||
if (data.findType === FindType.STRING)
|
||||
candidates = search(find.toString());
|
||||
|
||||
else
|
||||
candidates = search(...mkRegexFind(find));
|
||||
|
||||
// const candidates = search(find);
|
||||
const keys = Object.keys(candidates);
|
||||
if (keys.length !== 1)
|
||||
return reply("Expected exactly one 'find' matches, found " + keys.length);
|
||||
|
||||
const mod = candidates[keys[0]];
|
||||
let src = String(mod.original ?? mod).replaceAll("\n", "");
|
||||
|
||||
if (src.startsWith("function(")) {
|
||||
src = "0," + src;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
|
||||
for (const { match, replace } of replacement) {
|
||||
i++;
|
||||
|
||||
try {
|
||||
const matcher = canonicalizeMatch(parseNode(match));
|
||||
const replacement = canonicalizeReplace(parseNode(replace), "PlaceHolderPluginName");
|
||||
|
||||
const newSource = src.replace(matcher, replacement as string);
|
||||
|
||||
if (src === newSource) throw "Had no effect";
|
||||
Function(newSource);
|
||||
|
||||
src = newSource;
|
||||
} catch (err) {
|
||||
return reply(`Replacement ${i} failed: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
reply();
|
||||
break;
|
||||
}
|
||||
case "testFind": {
|
||||
const { type, args } = data as FindData;
|
||||
let parsedArgs;
|
||||
try {
|
||||
parsedArgs = args.map(parseNode);
|
||||
} catch (err) {
|
||||
return reply("Failed to parse args: " + err);
|
||||
}
|
||||
|
||||
try {
|
||||
let results: any[];
|
||||
switch (type.replace("find", "").replace("Lazy", "")) {
|
||||
case "":
|
||||
case "Component":
|
||||
results = findAll(parsedArgs[0]);
|
||||
break;
|
||||
case "ByProps":
|
||||
results = findAll(filters.byProps(...parsedArgs));
|
||||
break;
|
||||
case "Store":
|
||||
results = findAll(filters.byStoreName(parsedArgs[0]));
|
||||
break;
|
||||
case "ByCode":
|
||||
results = findAll(filters.byCode(...parsedArgs));
|
||||
break;
|
||||
case "ModuleId":
|
||||
results = Object.keys(search(parsedArgs[0]));
|
||||
break;
|
||||
case "ComponentByCode":
|
||||
results = findAll(filters.componentByCode(...parsedArgs));
|
||||
break;
|
||||
default:
|
||||
return reply("Unknown Find Type " + type);
|
||||
}
|
||||
|
||||
const uniqueResultsCount = new Set(results).size;
|
||||
if (uniqueResultsCount === 0) throw "No results";
|
||||
if (uniqueResultsCount > 1) throw "Found more than one result! Make this filter more specific";
|
||||
} catch (err) {
|
||||
return reply("Failed to find: " + err);
|
||||
}
|
||||
|
||||
reply();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
reply("Unknown Type " + type);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
199
src/plugins/devCompanion/util.tsx
Normal file
199
src/plugins/devCompanion/util.tsx
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { showNotice } from "@api/Notices";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { canonicalizeMatch } from "@utils/patches";
|
||||
import { CodeFilter, stringMatches, wreq } from "@webpack";
|
||||
import { Toasts } from "@webpack/common";
|
||||
|
||||
import { settings as companionSettings } from ".";
|
||||
|
||||
type Node = StringNode | RegexNode | FunctionNode;
|
||||
|
||||
|
||||
export interface StringNode {
|
||||
type: "string";
|
||||
value: string;
|
||||
}
|
||||
export interface RegexNode {
|
||||
type: "regex";
|
||||
value: {
|
||||
pattern: string;
|
||||
flags: string;
|
||||
};
|
||||
}
|
||||
export enum FindType {
|
||||
STRING,
|
||||
REGEX
|
||||
}
|
||||
export interface FunctionNode {
|
||||
type: "function";
|
||||
value: string;
|
||||
}
|
||||
export interface PatchData {
|
||||
find: string;
|
||||
replacement: {
|
||||
match: StringNode | RegexNode;
|
||||
replace: StringNode | FunctionNode;
|
||||
}[];
|
||||
}
|
||||
export interface FindData {
|
||||
type: string;
|
||||
args: Array<StringNode | FunctionNode>;
|
||||
}export interface SendData {
|
||||
type: string;
|
||||
data: any;
|
||||
ok: boolean;
|
||||
nonce?: number;
|
||||
}
|
||||
/**
|
||||
* extracts the patched module, if there is no patched module, throws an error
|
||||
* @param id module id
|
||||
*/
|
||||
export function extractOrThrow(id) {
|
||||
const module = wreq.m[id];
|
||||
if (!module?.$$vencordPatchedSource)
|
||||
throw new Error("No patched module found for module id " + id);
|
||||
return module.$$vencordPatchedSource;
|
||||
}
|
||||
/**
|
||||
* attempts to extract the module, throws if not found
|
||||
*
|
||||
*
|
||||
* if patched is true and no patched module is found fallsback to the non-patched module
|
||||
* @param id module id
|
||||
* @param patched return the patched module
|
||||
*/
|
||||
export function extractModule(id: number, patched = companionSettings.store.usePatchedModule): string {
|
||||
const module = wreq.m[id];
|
||||
if (!module)
|
||||
throw new Error("No module found for module id:" + id);
|
||||
return patched ? module.$$vencordPatchedSource ?? module.original.toString() : module.original.toString();
|
||||
}
|
||||
export function parseNode(node: Node) {
|
||||
switch (node.type) {
|
||||
case "string":
|
||||
return node.value;
|
||||
case "regex":
|
||||
return new RegExp(node.value.pattern, node.value.flags);
|
||||
case "function":
|
||||
// We LOVE remote code execution
|
||||
// Safety: This comes from localhost only, which actually means we have less permissions than the source,
|
||||
// since we're running in the browser sandbox, whereas the sender has host access
|
||||
return (0, eval)(node.value);
|
||||
default:
|
||||
throw new Error("Unknown Node Type " + (node as any).type);
|
||||
}
|
||||
}
|
||||
// we need to have our own because the one in webpack returns the first with no handling of more than one module
|
||||
export function findModuleId(find: CodeFilter) {
|
||||
const matches: string[] = [];
|
||||
for (const id in wreq.m) {
|
||||
if (stringMatches(wreq.m[id].toString(), find)) matches.push(id);
|
||||
}
|
||||
if (matches.length === 0) {
|
||||
throw new Error("No Matches Found");
|
||||
}
|
||||
if (matches.length !== 1) {
|
||||
throw new Error(`This filter matches ${matches.length} modules. Make it more specific!`);
|
||||
}
|
||||
return matches[0];
|
||||
}
|
||||
export function mkRegexFind(idOrSearch: string): RegExp[] {
|
||||
const regex = idOrSearch.substring(1, idOrSearch.lastIndexOf("/"));
|
||||
const flags = idOrSearch.substring(idOrSearch.lastIndexOf("/") + 1);
|
||||
return [canonicalizeMatch(RegExp(regex, flags))];
|
||||
}
|
||||
// the next two functions are copied from components/pluginSettings
|
||||
function showErrorToast(message: string) {
|
||||
Toasts.show({
|
||||
message,
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleEnabled(name: string, beforeReload: () => void) {
|
||||
let restartNeeded = false;
|
||||
function onRestartNeeded() {
|
||||
restartNeeded = true;
|
||||
}
|
||||
function beforeReturn() {
|
||||
if (restartNeeded) {
|
||||
if (companionSettings.store.reloadAfterToggle) {
|
||||
beforeReload();
|
||||
window.location.reload();
|
||||
}
|
||||
Toasts.show({
|
||||
id: Toasts.genId(),
|
||||
message: "Reload Needed",
|
||||
type: Toasts.Type.MESSAGE,
|
||||
options: {
|
||||
duration: 5000,
|
||||
position: Toasts.Position.TOP
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const plugin = Vencord.Plugins.plugins[name];
|
||||
|
||||
const settings = Settings.plugins[plugin.name];
|
||||
|
||||
const isEnabled = () => settings.enabled ?? false;
|
||||
|
||||
const wasEnabled = isEnabled();
|
||||
|
||||
// If we're enabling a plugin, make sure all deps are enabled recursively.
|
||||
if (!wasEnabled) {
|
||||
const { restartNeeded, failures } = Vencord.Plugins.startDependenciesRecursive(plugin);
|
||||
if (failures.length) {
|
||||
console.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(", ")}`);
|
||||
showNotice("Failed to start dependencies: " + failures.join(", "), "Close", () => null);
|
||||
beforeReturn();
|
||||
return;
|
||||
} else if (restartNeeded) {
|
||||
// If any dependencies have patches, don't start the plugin yet.
|
||||
settings.enabled = true;
|
||||
onRestartNeeded();
|
||||
beforeReturn();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes.
|
||||
if (plugin.patches?.length) {
|
||||
settings.enabled = !wasEnabled;
|
||||
onRestartNeeded();
|
||||
beforeReturn();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the plugin is enabled, but hasn't been started, then we can just toggle it off.
|
||||
if (wasEnabled && !plugin.started) {
|
||||
settings.enabled = !wasEnabled;
|
||||
beforeReturn();
|
||||
return;
|
||||
}
|
||||
|
||||
const result = wasEnabled ? Vencord.Plugins.stopPlugin(plugin) : Vencord.Plugins.startPlugin(plugin);
|
||||
|
||||
if (!result) {
|
||||
settings.enabled = false;
|
||||
|
||||
const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`;
|
||||
console.error(msg);
|
||||
showErrorToast(msg);
|
||||
beforeReturn();
|
||||
return;
|
||||
}
|
||||
|
||||
settings.enabled = !wasEnabled;
|
||||
beforeReturn();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue