diff --git a/src/components/PatchHelper.tsx b/src/components/PatchHelper.tsx index 853febc5..391be9d0 100644 --- a/src/components/PatchHelper.tsx +++ b/src/components/PatchHelper.tsx @@ -80,7 +80,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError const fullMatch = matchResult[0] ? makeCodeblock(matchResult[0], "js") : ""; const groups = matchResult.length > 1 - ? makeCodeblock(matchResult.slice(1).map((g, i) => `Group ${i}: ${g}`).join("\n"), "yml") + ? makeCodeblock(matchResult.slice(1).map((g, i) => `Group ${i + 1}: ${g}`).join("\n"), "yml") : ""; return ( @@ -172,6 +172,22 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) { onChange={onChange} error={error ?? replacementError} /> + {!isFunc && ( + <> + Cheat Sheet + {Object.entries({ + "$$": "Insert a $", + "$&": "Insert the entire match", + "$\\`": "Insert the substring before the match", + "$'": "Insert the substring after the match", + "$n": "Insert the nth capturing group ($1, $2...)" + }).map(([placeholder, desc]) => ( + + {Parser.parse("`" + placeholder + "`")}: {desc} + + ))} + + )} = Promise.resolve(); - add(func: () => Promisable) { - this.promise = this.promise.then(func); + add(func: (lastValue: unknown) => Promisable): Promise { + return (this.promise = this.promise.then(func)); } } diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 3db4dd2d..ba2d5591 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -21,6 +21,8 @@ import type { WebpackInstance } from "discord-types/other"; import Logger from "../utils/logger"; import { proxyLazy } from "../utils/proxyLazy"; +const logger = new Logger("Webpack"); + export let _resolveReady: () => void; /** * Fired once a gateway connection to Discord has been established. @@ -51,7 +53,6 @@ export const filters = { }, }; -const logger = new Logger("Webpack"); export const subscriptions = new Map(); export const listeners = new Set(); @@ -126,51 +127,143 @@ export function findAll(filter: FilterFn, getDefault = true) { } /** - * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) - * then maps it into an easily usable module via the specified mappers - * @param code Code snippet - * @param mappers Mappers to create the non mangled exports - * @returns Unmangled exports as specified in mappers - * - * @example mapMangledModule("headerIdIsManaged:", { - * openModal: filters.byCode("headerIdIsManaged:"), - * closeModal: filters.byCode("key==") - * }) - */ -export function mapMangledModule(code: string, mappers: Record): Record { - const exports = {} as Record; + * Same as {@link find} but in bulk + * @param filterFns Arry of filters. Please note that this array will be modified in place, so if you still + * need it afterwards, pass a copy. + * @returns Array of results in the same order as the passed filters + */ +export function bulk(...filterFns: FilterFn[]) { + if (!Array.isArray(filterFns)) + throw new Error("Invalid filters. Expected function[] got " + typeof filterFns); - // search every factory function - for (const id in wreq.m) { - const src = wreq.m[id].toString() as string; - if (src.includes(code)) { - const mod = wreq(id as any as number); - outer: - for (const key in mod) { - const member = mod[key]; - for (const newName in mappers) { - // if the current mapper matches this module - if (mappers[newName](member)) { - exports[newName] = member; + const { length } = filterFns; + + if (length === 0) + throw new Error("Expected at least two filters."); + + if (length === 1) { + if (IS_DEV) { + throw new Error("bulk called with only one filter. Use find"); + } + return find(filterFns[0]); + } + + const filters = filterFns as Array; + + let found = 0; + const results = Array(length); + + outer: + for (const key in cache) { + const mod = cache[key]; + if (!mod?.exports) continue; + + for (let j = 0; j < length; j++) { + const filter = filters[j]; + // Already done + if (filter === undefined) continue; + + if (filter(mod.exports)) { + results[j] = mod.exports; + filters[j] = undefined; + if (++found === length) break outer; + break; + } + + if (typeof mod.exports !== "object") + continue; + + if (mod.exports.default && filter(mod.exports.default)) { + results[j] = mod.exports.default; + filters[j] = undefined; + if (++found === length) break outer; + break; + } + + for (const nestedMod in mod.exports) + if (nestedMod.length <= 3) { + const nested = mod.exports[nestedMod]; + if (nested && filter(nested)) { + results[j] = nested; + filters[j] = undefined; + if (++found === length) break outer; continue outer; } } - } - return exports; } } - const err = new Error("Didn't find module matching this code:\n" + code); - if (IS_DEV) - throw err; + if (found !== length) { + const err = new Error(`Got ${length} filters, but only found ${found} modules!`); + if (IS_DEV) { + // Strict behaviour in DevBuilds to fail early and make sure the issue is found + throw err; + } + logger.warn(err); + } + + return results; +} + +/** + * Find the id of a module by its code + * @param code Code + * @returns number or null + */ +export function findModuleId(code: string) { + for (const id in wreq.m) { + if (wreq.m[id].toString().includes(code)) { + return Number(id); + } + } + const err = new Error("Didn't find module with code:\n" + code); + if (IS_DEV) { + // Strict behaviour in DevBuilds to fail early and make sure the issue is found + throw err; + } logger.warn(err); + + return null; +} + +/** + * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) + * then maps it into an easily usable module via the specified mappers + * @param code Code snippet + * @param mappers Mappers to create the non mangled exports + * @returns Unmangled exports as specified in mappers + * + * @example mapMangledModule("headerIdIsManaged:", { + * openModal: filters.byCode("headerIdIsManaged:"), + * closeModal: filters.byCode("key==") + * }) + */ +export function mapMangledModule(code: string, mappers: Record): Record { + const exports = {} as Record; + + const id = findModuleId(code); + if (id === null) + return exports; + + const mod = wreq(id); + outer: + for (const key in mod) { + const member = mod[key]; + for (const newName in mappers) { + // if the current mapper matches this module + if (mappers[newName](member)) { + exports[newName] = member; + continue outer; + } + } + } return exports; } /** - * Same as {@link mapMangledModule} but lazy - */ + * Same as {@link mapMangledModule} but lazy + */ export function mapMangledModuleLazy(code: string, mappers: Record): Record { return proxyLazy(() => mapMangledModule(code, mappers)); } @@ -210,11 +303,11 @@ export function removeListener(callback: CallbackFn) { } /** - * Search modules by keyword. This searches the factory methods, - * meaning you can search all sorts of things, displayName, methodName, strings somewhere in the code, etc - * @param filters One or more strings or regexes - * @returns Mapping of found modules - */ + * Search modules by keyword. This searches the factory methods, + * meaning you can search all sorts of things, displayName, methodName, strings somewhere in the code, etc + * @param filters One or more strings or regexes + * @returns Mapping of found modules + */ export function search(...filters: Array) { const results = {} as Record; const factories = wreq.m; @@ -233,13 +326,13 @@ export function search(...filters: Array) { } /** - * Extract a specific module by id into its own Source File. This has no effect on - * the code, it is only useful to be able to look at a specific module without having - * to view a massive file. extract then returns the extracted module so you can jump to it. - * As mentioned above, note that this extracted module is not actually used, - * so putting breakpoints or similar will have no effect. - * @param id The id of the module to extract - */ + * Extract a specific module by id into its own Source File. This has no effect on + * the code, it is only useful to be able to look at a specific module without having + * to view a massive file. extract then returns the extracted module so you can jump to it. + * As mentioned above, note that this extracted module is not actually used, + * so putting breakpoints or similar will have no effect. + * @param id The id of the module to extract + */ export function extract(id: number) { const mod = wreq.m[id] as Function; if (!mod) return null;