revenge-plugins/plugins/key-inspector/src/lib/evalTool.ts
2026-08-01 21:35:08 -04:00

127 lines
5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { findByProps, findByName, findByTypeName, findByStoreName, findByPropsAll, findByNameAll, find } from "@vendetta/metro";
import { React, ReactNative, FluxDispatcher } from "@vendetta/metro/common";
import { instead, before, after } from "@vendetta/patcher";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { showToast } from "@vendetta/ui/toasts";
import { rawFind, rawFindByTypeName, rawFindByProps, rawFindByName, rawFindByStoreName } from "@shared/lib/rawFind";
declare const window: any;
function safeStringify(value: any): string {
if (value === undefined) return "undefined";
if (value === null) return "null";
if (typeof value === "function") return `[Function: ${value.name || "anonymous"}]`;
if (typeof value !== "object") return String(value);
const seen = new WeakSet();
try {
return JSON.stringify(
value,
(_key, val) => {
if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
if (typeof val === "symbol") return val.toString();
if (typeof val === "object" && val !== null) {
if (seen.has(val)) return "[Circular]";
seen.add(val);
}
return val;
},
2
);
} catch {
// Most live Discord/React objects (fibers, class instances, refs) have circular structure
// or getters that throw - falling back to a flat key listing still tells you something
// useful instead of just "[object Object]".
try {
return `[unserializable object, keys: ${Object.keys(value).join(", ")}]`;
} catch (e) {
return `[unserializable, and Object.keys threw: ${e}]`;
}
}
}
// Android/iOS keyboards commonly have "smart punctuation" that silently swaps straight quotes for
// curly ones as you type - that turns "foo" into “foo”, a different character to the JS parser.
// Harmless to keep normalizing even though it turned out not to be the actual cause of the errors
// seen while building this (see below) - cheap, and still a real footgun on its own.
function normalizeSmartPunctuation(code: string): string {
return code
.replace(/[‘’]/g, "'")
.replace(/[“”]/g, '"')
.replace(/[–—]/g, "-");
}
/**
* Runs arbitrary code typed into the Eval box, with this repo's own lookup helpers already in
* scope - built so an on-device assumption behind a fix (does this property exist, does this call
* behave the way decompiled source suggested) can be checked in seconds instead of a full
* build/push/update/test cycle.
*
* The dynamically-compiled function body is deliberately NOT `async`. Hermes (RN's JS engine)
* precompiles the whole app bundle to bytecode ahead of time, and its runtime `eval`/`new Function`
* path only supports a reduced subset of the language - confirmed on-device that it rejects `async`
* functions outright ("async functions are unsupported"), even though normal precompiled code
* (including this very function) uses async/await freely. So the user's code runs as a plain
* synchronous function; if it needs to do something timed/async, it returns a Promise itself
* (`return new Promise(resolve => setTimeout(() => resolve(x), 1000))` works fine - only the
* `async`/`await` *keywords* are the problem, not Promises as a runtime object) and this function
* awaits that result out here, in precompiled code where await is fully supported.
*/
export async function runEval(rawCode: string): Promise<string> {
const code = normalizeSmartPunctuation(rawCode);
try {
const fn = new Function(
"findByProps",
"findByName",
"findByTypeName",
"findByStoreName",
"findByPropsAll",
"findByNameAll",
"find",
"rawFind",
"rawFindByTypeName",
"rawFindByProps",
"rawFindByName",
"rawFindByStoreName",
"instead",
"before",
"after",
"getAssetIDByName",
"showToast",
"React",
"ReactNative",
"FluxDispatcher",
"window",
`return (function() {\n${code}\n})();`
);
const result = fn(
findByProps,
findByName,
findByTypeName,
findByStoreName,
findByPropsAll,
findByNameAll,
find,
rawFind,
rawFindByTypeName,
rawFindByProps,
rawFindByName,
rawFindByStoreName,
instead,
before,
after,
getAssetIDByName,
showToast,
React,
ReactNative,
FluxDispatcher,
window
);
const resolved = result && typeof result.then === "function" ? await result : result;
return safeStringify(resolved);
} catch (e) {
return `Error: ${e instanceof Error ? `${e.message}\n${e.stack ?? ""}` : String(e)}`;
}
}