diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index 857b976e9..776c3d5f9 100644 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -22,7 +22,7 @@ import { Margins } from "@utils/margins"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; import { makeCodeblock } from "@utils/text"; import { Patch, ReplaceFn } from "@utils/types"; -import { search } from "@webpack"; +import { searchFactories } from "@webpack"; import { Button, Clipboard, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common"; import { SettingsTab, wrapTab } from "./shared"; @@ -33,7 +33,7 @@ if (IS_DEV) { } const findCandidates = debounce(function ({ find, setModule, setError }) { - const candidates = search(find); + const candidates = searchFactories(find); const keys = Object.keys(candidates); const len = keys.length; if (len === 0) diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 913d106fa..db4a7ee44 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -25,7 +25,7 @@ import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from import { SYM_PROXY_INNER_GET, SYM_PROXY_INNER_VALUE } from "@utils/proxyInner"; import definePlugin, { PluginNative, StartAt } from "@utils/types"; import * as Webpack from "@webpack"; -import { cacheFindAll, cacheFindModuleId, extract, filters, search } from "@webpack"; +import { _cacheFindAll, cacheFindAll, cacheFindModuleId, extract, filters, searchFactories } from "@webpack"; import * as Common from "@webpack/common"; import { loadLazyChunks } from "debug/loadLazyChunks"; import type { ComponentType } from "react"; @@ -34,85 +34,101 @@ const DESKTOP_ONLY = (f: string) => () => { throw new Error(`'${f}' is Discord Desktop only.`); }; -const define: typeof Object.defineProperty = - (obj, prop, desc) => { - if (Object.hasOwn(desc, "value")) - desc.writable = true; - return Object.defineProperty(obj, prop, { - configurable: true, - enumerable: true, - ...desc - }); - }; +type Define = typeof Object.defineProperty; +const define: Define = (target, p, attributes) => { + if (Object.hasOwn(attributes, "value")) { + attributes.writable = true; + } + + return Object.defineProperty(target, p, { + configurable: true, + enumerable: true, + ...attributes + }); +}; function makeShortcuts() { - function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn) { + function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn, shouldReturnFactory = false) { const cache = new Map(); return function (...filterProps: unknown[]) { const cacheKey = String(filterProps); if (cache.has(cacheKey)) return cache.get(cacheKey); - const matches = cacheFindAll(filterFactory(...filterProps)); + const matches = _cacheFindAll(filterFactory(...filterProps)); const result = (() => { switch (matches.length) { case 0: return null; - case 1: return matches[0]; + case 1: return shouldReturnFactory ? matches[0].factory : matches[0].result; default: - const uniqueMatches = [...new Set(matches)]; - if (uniqueMatches.length > 1) + const uniqueMatches = [...new Set(shouldReturnFactory ? matches.map(m => m.factory) : matches.map(m => m.result))]; + if (uniqueMatches.length > 1) { console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches); + } - return matches[0]; + return uniqueMatches[0]; } })(); + if (result && cacheKey) cache.set(cacheKey, result); return result; }; } let fakeRenderWin: WeakRef | undefined; + const find = newFindWrapper(f => f); const findByProps = newFindWrapper(filters.byProps); return { - ...Object.fromEntries(Object.keys(Common).map(key => [key, { getter: () => Common[key] }])), wp: Webpack, wpc: { getter: () => Webpack.cache }, wreq: { getter: () => Webpack.wreq }, + WebpackInstances: { getter: () => Vencord.WebpackPatcher.allWebpackInstances }, - wpsearch: search, - wpex: extract, - wpexs: (code: string) => extract(cacheFindModuleId(code)!), loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); }, + + wpsearch: searchFactories, + wpex: extract, + wpexs: (...code: Webpack.CodeFilter) => extract(cacheFindModuleId(...code)!), + filters, find, findAll: cacheFindAll, - findByProps, - findAllByProps: (...props: string[]) => cacheFindAll(filters.byProps(...props)), - findProp: (...props: string[]) => findByProps(...props)[props[0]], - findByCode: newFindWrapper(filters.byCode), - findAllByCode: (code: string) => cacheFindAll(filters.byCode(code)), + findComponent: find, + findAllComponents: cacheFindAll, + findExportedComponent: (...props: Webpack.PropsFilter) => findByProps(...props)[props[0]], findComponentByCode: newFindWrapper(filters.componentByCode), - findAllComponentsByCode: (...code: string[]) => cacheFindAll(filters.componentByCode(...code)), + findAllComponentsByCode: (...code: Webpack.PropsFilter) => cacheFindAll(filters.componentByCode(...code)), findComponentByFields: newFindWrapper(filters.componentByFields), - findAllComponentsByFields: (...fields: string[]) => cacheFindAll(filters.componentByFields(...fields)), - findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]], + findAllComponentsByFields: (...fields: Webpack.PropsFilter) => cacheFindAll(filters.componentByFields(...fields)), + findByProps, + findAllByProps: (...props: Webpack.PropsFilter) => cacheFindAll(filters.byProps(...props)), + findProp: (...props: Webpack.PropsFilter) => findByProps(...props)[props[0]], + findByCode: newFindWrapper(filters.byCode), + findAllByCode: (code: Webpack.CodeFilter) => cacheFindAll(filters.byCode(...code)), findStore: newFindWrapper(filters.byStoreName), findByFactoryCode: newFindWrapper(filters.byFactoryCode), - findAllByFactoryCode: (...code: string[]) => cacheFindAll(filters.byFactoryCode(...code)), - PluginsApi: { getter: () => Vencord.Plugins }, + findAllByFactoryCode: (...code: Webpack.CodeFilter) => cacheFindAll(filters.byFactoryCode(...code)), + findModuleFactory: newFindWrapper(filters.byFactoryCode, true), + findAllModuleFactories: (...code: Webpack.CodeFilter) => _cacheFindAll(filters.byFactoryCode(...code)).map(m => m.factory), + plugins: { getter: () => Vencord.Plugins.plugins }, + PluginsApi: { getter: () => Vencord.Plugins }, Settings: { getter: () => Vencord.Settings }, Api: { getter: () => Vencord.Api }, Util: { getter: () => Vencord.Util }, + reload: () => location.reload(), restart: IS_WEB ? DESKTOP_ONLY("restart") : relaunch, + canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement, + + preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true, fakeRender: (component: ComponentType, props: any) => { const prevWin = fakeRenderWin?.deref(); const win = prevWin?.closed === false @@ -142,8 +158,6 @@ function makeShortcuts() { Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div"))); }, - preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true, - channel: { getter: () => getCurrentChannel(), preload: false }, channelId: { getter: () => Common.SelectedChannelStore.getChannelId(), preload: false }, guild: { getter: () => getCurrentGuild(), preload: false }, @@ -152,6 +166,7 @@ function makeShortcuts() { meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false }, messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }, + ...Object.fromEntries(Object.keys(Common).map(key => [key, { getter: () => Common[key] }])), Stores: { getter: () => Object.fromEntries( Common.Flux.Store.getAll() @@ -213,8 +228,10 @@ export default definePlugin({ const shortcuts = makeShortcuts(); window.shortcutList = {}; - for (const [key, val] of Object.entries(shortcuts)) { - if ("getter" in val) { + for (const key in shortcuts) { + const val = shortcuts[key]; + + if (Object.hasOwn(val, "getter")) { define(window.shortcutList, key, { get: () => loadAndCacheShortcut(key, val, true) }); @@ -228,7 +245,7 @@ export default definePlugin({ } } - // unproxy loaded modules + // Unproxy loaded modules Webpack.onceDiscordLoaded.then(() => { setTimeout(() => this.eagerLoad(false), 1000); @@ -243,10 +260,10 @@ export default definePlugin({ await Webpack.onceDiscordLoaded; const shortcuts = makeShortcuts(); + for (const key in shortcuts) { + const val = shortcuts[key]; - for (const [key, val] of Object.entries(shortcuts)) { - if (!Object.hasOwn(val, "getter") || (val as any).preload === false) continue; - + if (!Object.hasOwn(val, "getter") || val.preload === false) continue; try { loadAndCacheShortcut(key, val, forceLoad); } catch { } // swallow not found errors in DEV diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx index 3781e5ca4..c6d485874 100644 --- a/src/plugins/devCompanion.dev/index.tsx +++ b/src/plugins/devCompanion.dev/index.tsx @@ -22,7 +22,7 @@ import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; import definePlugin, { OptionType, ReporterTestable } from "@utils/types"; -import { cacheFindAll, filters, search } from "@webpack"; +import { cacheFindAll, filters, searchFactories } from "@webpack"; const PORT = 8485; const NAV_ID = "dev-companion-reconnect"; @@ -154,7 +154,7 @@ function initWs(isManual = false) { case "testPatch": { const { find, replacement } = data as PatchData; - const candidates = search(find); + const candidates = searchFactories(find); const keys = Object.keys(candidates); if (keys.length !== 1) return reply("Expected exactly one 'find' matches, found " + keys.length); @@ -213,7 +213,7 @@ function initWs(isManual = false) { results = cacheFindAll(filters.byCode(...parsedArgs)); break; case "ModuleId": - results = Object.keys(search(parsedArgs[0])); + results = Object.keys(searchFactories(parsedArgs[0])); break; case "ComponentByCode": results = cacheFindAll(filters.componentByCode(...parsedArgs)); diff --git a/src/webpack/api.tsx b/src/webpack/api.tsx index 10194d3a4..8273c7afe 100644 --- a/src/webpack/api.tsx +++ b/src/webpack/api.tsx @@ -678,16 +678,18 @@ export const _cacheFind = traceFunction("cacheFind", function _cacheFind(filter: const mod = cache[key]; if (!mod?.loaded || mod?.exports == null) continue; + const factory = wreq.m[key] as AnyModuleFactory; + if (filter.$$vencordIsFactoryFilter) { if (filter(wreq.m[key])) { - return { result: mod.exports, id: key, exportKey: null, factory: wreq.m[key] as AnyModuleFactory }; + return { result: mod.exports, id: key, exportKey: null, factory }; } continue; } if (filter(mod.exports)) { - return { result: mod.exports, id: key, exportKey: null, factory: wreq.m[key] as AnyModuleFactory }; + return { result: mod.exports, id: key, exportKey: null, factory }; } if (typeof mod.exports !== "object") { @@ -695,14 +697,14 @@ export const _cacheFind = traceFunction("cacheFind", function _cacheFind(filter: } if (mod.exports.default != null && filter(mod.exports.default)) { - return { result: mod.exports.default, id: key, exportKey: "default ", factory: wreq.m[key] as AnyModuleFactory }; + return { result: mod.exports.default, id: key, exportKey: "default ", factory }; } for (const exportKey in mod.exports) if (exportKey.length <= 3) { const exportValue = mod.exports[exportKey]; if (exportValue != null && filter(exportValue)) { - return { result: exportValue, id: key, exportKey, factory: wreq.m[key] as AnyModuleFactory }; + return { result: exportValue, id: key, exportKey, factory }; } } } @@ -717,37 +719,37 @@ export const _cacheFind = traceFunction("cacheFind", function _cacheFind(filter: * @returns The found export or module exports, or undefined */ export function cacheFind(filter: FilterFn) { - const cacheFindResult = _cacheFind(filter); - - return cacheFindResult.result; + return _cacheFind(filter).result; } /** * Find the the export or module exports from an all the required modules that match the filter. * * @param filter A function that takes an export or module exports and returns a boolean - * @returns An array of all the found export or module exports */ -export function cacheFindAll(filter: FilterFn) { +export function _cacheFindAll(filter: FilterFn): Required[] { if (typeof filter !== "function") { throw new Error("Invalid filter. Expected a function got " + typeof filter); } - const ret: ModuleExports[] = []; + const results: Required[] = []; + for (const key in cache) { const mod = cache[key]; if (!mod?.loaded || mod?.exports == null) continue; + const factory = wreq.m[key] as AnyModuleFactory; + if (filter.$$vencordIsFactoryFilter) { if (filter(wreq.m[key])) { - ret.push(mod.exports); + results.push({ result: mod.exports, id: key, exportKey: null, factory }); } continue; } if (filter(mod.exports)) { - ret.push(mod.exports); + results.push({ result: mod.exports, id: key, exportKey: null, factory }); } if (typeof mod.exports !== "object") { @@ -755,57 +757,54 @@ export function cacheFindAll(filter: FilterFn) { } if (mod.exports.default != null && filter(mod.exports.default)) { - ret.push(mod.exports.default); + results.push({ result: mod.exports.default, id: key, exportKey: "default ", factory }); } for (const exportKey in mod.exports) if (exportKey.length <= 3) { const exportValue = mod.exports[exportKey]; if (exportValue != null && filter(exportValue)) { - ret.push(exportValue); + results.push({ result: exportValue, id: key, exportKey, factory }); break; } } } - return ret; + return results; +} + +/** + * Find the the export or module exports from an all the required modules that match the filter. + * + * @param filter A function that takes an export or module exports and returns a boolean + * @returns An array of found exports or module exports + */ +export function cacheFindAll(filter: FilterFn) { + return _cacheFindAll(filter).map(({ result }) => result); } /** * Find the id of the first already loaded module factory that includes all the given code. */ -export const cacheFindModuleId = traceFunction("cacheFindModuleId", function cacheFindModuleId(...code: CodeFilter) { - const parsedCode = code.map(canonicalizeMatch); - - for (const id in wreq.m) { - if (stringMatches(String(wreq.m[id]), parsedCode)) { - return id; - } - } -}); +export function cacheFindModuleId(...code: CodeFilter) { + return _cacheFind(filters.byFactoryCode(...code)).id; +} /** - * Search modules by keyword. This searches the factory methods, + * Search factories by keyword. This searches the source code of the module factories, * meaning you can search all sorts of things, methodName, strings somewhere in the code, etc. * * @param code One or more strings or regexes - * @returns Mapping of found modules + * @returns Mapping of found factories by their module id */ -export function search(...code: CodeFilter) { - code = code.map(canonicalizeMatch); +export function searchFactories(...code: CodeFilter) { + const filter = filters.byFactoryCode(...code); - const results: WebpackRequire["m"] = {}; - const factories = wreq.m; + return _cacheFindAll(filter).reduce((results, { factory, id }) => { + results[id] = factory; - for (const id in factories) { - const factory = factories[id]; - - if (stringMatches(String(factory), code)) { - results[id] = factory; - } - } - - return results; + return results; + }, {} as Record); } /** @@ -819,7 +818,7 @@ export function search(...code: CodeFilter) { */ export function extract(id: PropertyKey) { const factory = wreq.m[id]; - if (!factory) return null; + if (factory == null) return null; const code = ` // [EXTRACTED] WebpackModule${String(id)} @@ -833,6 +832,8 @@ export function extract(id: PropertyKey) { return extracted; } +/* ------------------------------- Deprecations ------------------------------- */ + /** * @deprecated Use separate finds instead * Same as {@link cacheFind} but in bulk. @@ -1040,7 +1041,7 @@ export const mapMangledModuleLazy = deprecatedRedirect("mapMangledModuleLazy", " export const findAll = deprecatedRedirect("findAll", "cacheFindAll", cacheFindAll); /** - * @deprecated Use {@link cacheFindBulk} instead + * @deprecated Use separate finds instead * * Same as {@link cacheFind} but in bulk * @@ -1057,3 +1058,14 @@ export const findBulk = deprecatedRedirect("findBulk", "cacheFindBulk", cacheFin * @returns string or null */ export const findModuleId = deprecatedRedirect("findModuleId", "cacheFindModuleId", cacheFindModuleId); + +/** + * @deprecated Use {@link searchFactories} instead + * + * Search modules by keyword. This searches the factory methods, + * meaning you can search all sorts of things, methodName, strings somewhere in the code, etc. + * + * @param code One or more strings or regexes + * @returns Mapping of found modules + */ +export const search = deprecatedRedirect("search", "searchFactories", searchFactories); diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 8089db3c3..226978597 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -481,7 +481,7 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory) { // inline require to avoid including it in !IS_DEV builds const diff = (require("diff") as typeof import("diff")).diffWordsWithSpace(context, patchedContext); let fmt = "%c %s "; - const elements = [] as string[]; + const elements: string[] = []; for (const d of diff) { const color = d.removed ? "red"