add type, formatting and refactor

This commit is contained in:
sadan 2024-08-19 23:13:21 -04:00
parent 6c93b4d7ec
commit 4250424040
No known key found for this signature in database
2 changed files with 206 additions and 146 deletions

View file

@ -22,7 +22,9 @@ import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { CodeFilter, filters, findAll, search, stringMatches, wreq } from "@webpack";
import { filters, findAll, search, wreq } from "@webpack";
import { extractModule, extractOrThrow, FindData, findModuleId, parseNode, PatchData, SendData } from "./util";
const PORT = 8485;
const NAV_ID = "dev-companion-reconnect";
@ -31,82 +33,19 @@ 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,
}
});
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
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("More than one match");
}
return matches[0];
}
interface SendData {
type: string,
data: any,
ok: boolean;
nonce?: number;
}
function initWs(isManual = false) {
let wasConnected = isManual;
let hasErrored = false;
@ -191,97 +130,122 @@ function initWs(isManual = false) {
logger.info("Received Message:", type, "\n", data);
switch (type) {
case "extract": {
case "diff": {
const { extractType, idOrSearch } = data;
switch (extractType) {
case "id": {
console.log("ID!");
let data;
if (typeof idOrSearch === "number")
data = wreq.m[idOrSearch]?.toString() || null;
else {
return reply(`the provided moduleID is not a number. Got: ${typeof idOrSearch}`);
}
if (!data)
return reply(`Module(${idOrSearch}) not found`);
else
replyData({
type: "extract",
ok: true,
data,
moduleNumber: idOrSearch
});
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": {
try {
const moduleId = findModuleId([idOrSearch.toString()]);
const data = wreq.m[moduleId].toString();
const moduleId = +findModuleId([idOrSearch.toString()]);
replyData({
type: "diff",
ok: true,
data: {
patched: extractOrThrow(moduleId),
source: extractModule(moduleId, false)
},
moduleNumber: moduleId
});
break;
}
}
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": {
const moduleId = +findModuleId([idOrSearch.toString()]);
replyData({
type: "extract",
ok: true,
data,
moduleNumber: +moduleId
data: extractModule(moduleId),
moduleNumber: moduleId
});
} catch (e) {
reply("Error: " + String(e));
break;
}
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 "":
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);
case "find": {
const { findType, findArgs } = data;
try {
var parsedArgs = findArgs.map(parseNode);
} catch (err) {
return reply("Failed to parse args: " + err);
}
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);
try {
let results: any[];
switch (findType.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 " + 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;
}
break;
default:
reply(`Unknown Extract type. Got: ${extractType}`);
break;
}
default:
reply(`Unknown Extract type. Got: ${extractType}`);
break;
} catch (error) {
reply(String(error));
}
break;
}

View file

@ -0,0 +1,96 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { CodeFilter, stringMatches, wreq } from "@webpack";
import { settings } from ".";
type Node = StringNode | RegexNode | FunctionNode;
export interface StringNode {
type: "string";
value: string;
}
export interface RegexNode {
type: "regex";
value: {
pattern: string;
flags: string;
};
}
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 = settings.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 : module.original;
} 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("More than one match");
}
return matches[0];
}