diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx index 47c2c0ec6..57b3d5ebf 100644 --- a/src/components/settings/tabs/plugins/PluginModal.tsx +++ b/src/components/settings/tabs/plugins/PluginModal.tsx @@ -34,7 +34,7 @@ import { classes, isObjectEmpty } from "@utils/misc"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; import { User } from "@vencord/discord-types"; -import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; +import { findComponentByCodeLazy, findCssClassesLazy } from "@webpack"; import { Clickable, FluxDispatcher, React, Toasts, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; import { Constructor } from "type-fest"; @@ -46,7 +46,7 @@ import { GithubButton, WebsiteButton } from "./LinkIconButton"; const cl = classNameFactory("vc-plugin-modal-"); -const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); +const AvatarStyles = findCssClassesLazy("moreUsers", "avatar", "clickableAvatar"); const CloseButton = findComponentByCodeLazy("CLOSE_BUTTON_LABEL"); const ConfirmModal = findComponentByCodeLazy('parentComponent:"ConfirmModal"'); const WarningIcon = findComponentByCodeLazy("3.15H3.29c-1.74"); diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 17bf3987c..7396d151f 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -104,9 +104,9 @@ interface Modals { export const Modals: Modals = mapMangledModuleLazy(':"thin")', { ModalRoot: filters.componentByCode('.MODAL,"aria-labelledby":'), ModalHeader: filters.componentByCode(",id:"), - ModalContent: filters.componentByCode(".content,"), - ModalFooter: filters.componentByCode(".footer,"), - ModalCloseButton: filters.componentByCode(".close]:") + ModalContent: filters.componentByCode("scrollbarType:"), + ModalFooter: filters.componentByCode(".HORIZONTAL_REVERSE,"), + ModalCloseButton: filters.componentByCode(".withCircleBackground") }); export const ModalRoot = LazyComponent(() => Modals.ModalRoot); diff --git a/src/utils/text.ts b/src/utils/text.ts index 2d84fecd0..76f7f1eca 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -179,3 +179,8 @@ export const ZWSP = "\u200b"; export function toInlineCode(s: string) { return "``" + ZWSP + s.replaceAll("`", ZWSP + "`" + ZWSP) + ZWSP + "``"; } + +// @ts-expect-error Missing RegExp.escape +export const escapeRegExp: (s: string) => string = RegExp.escape ?? function (s: string) { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +}; diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index dbc6e84b1..8306e69aa 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -58,8 +58,8 @@ export const TooltipContainer = TooltipContainerComponent as never; export const TextInput = waitForComponent("TextInput", filters.componentByCode("#{intl::MAXIMUM_LENGTH_ERROR}", '"input"')); export const TextArea = waitForComponent("TextArea", filters.componentByCode("this.getPaddingRight()},id:")); -export const Select = waitForComponent("Select", filters.componentByCode('"renderLeading","renderTrailing"])')); -export const SearchableSelect = waitForComponent("SearchableSelect", filters.componentByCode('"SearchableSelect"),{id:')); +export const Select = waitForComponent("Select", filters.componentByCode('"Select"')); +export const SearchableSelect = waitForComponent("SearchableSelect", filters.componentByCode('"SearchableSelect"')); export const Slider = waitForComponent("Slider", filters.componentByCode('"markDash".concat(')); export const Popout = waitForComponent("Popout", filters.componentByCode("ref:this.ref,", "renderPopout:this.renderPopout,")); export const Dialog = waitForComponent("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1')); diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 3ab7d1f8c..c41b53843 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -20,6 +20,7 @@ import { makeLazy, proxyLazy } from "@utils/lazy"; import { LazyComponent } from "@utils/lazyReact"; import { Logger } from "@utils/Logger"; import { canonicalizeMatch } from "@utils/patches"; +import { escapeRegExp } from "@utils/text"; import type { FluxStore } from "@vencord/discord-types"; import type { ModuleExports, ModuleFactory, WebpackRequire } from "@vencord/discord-types/webpack"; @@ -53,6 +54,10 @@ export const stringMatches = (s: string, filter: CodeFilter) => : (f.global && (f.lastIndex = 0), f.test(s)) ); +export function makeClassNameRegex(className: string) { + return new RegExp(`(?:\\b|_)${escapeRegExp(className)}(?:\\b|_)`); +} + export const filters = { byProps: (...props: PropsFilter): FilterFn => props.length === 1 @@ -90,6 +95,17 @@ export const filters = { filter.$$vencordProps = [...code]; return filter; + }, + + byClassNames: (...classes: string[]): FilterFn => { + const regexes = classes.map(makeClassNameRegex); + + return (m: any) => { + if (typeof m !== "object") return false; + + const values = Object.values(m); + return regexes.every(cls => values.some(v => typeof v === "string" && cls.test(v))); + }; } }; @@ -209,7 +225,7 @@ export function handleModuleNotFound(method: string, ...filter: unknown[]) { /** * Find the first module that matches the filter */ -export const find = traceFunction("find", function find(filter: FilterFn, { isIndirect = false, isWaitFor = false }: { isIndirect?: boolean; isWaitFor?: boolean; } = {}) { +export const find = traceFunction("find", function find(filter: FilterFn, { isIndirect = false, isWaitFor = false, topLevelOnly = false }: { isIndirect?: boolean; isWaitFor?: boolean; topLevelOnly?: boolean; } = {}) { if (IS_ANTI_CRASH_TEST) return null; if (typeof filter !== "function") @@ -223,7 +239,7 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn return isWaitFor ? [mod.exports, key] : mod.exports; } - if (typeof mod.exports !== "object") continue; + if (typeof mod.exports !== "object" || topLevelOnly) continue; for (const nestedMod in mod.exports) { const nested = mod.exports[nestedMod]; @@ -563,6 +579,29 @@ export function findExportedComponentLazy(...props: Prop }); } +export function findCssClasses(...classes: S[]): Record { + const res = find(filters.byClassNames(...classes), { isIndirect: true, topLevelOnly: true }); + + if (!res) + handleModuleNotFound("findCssClasses", ...classes); + + const values = Object.values(res); + const mapped = {} as Record; + + for (const cls of classes) { + const re = makeClassNameRegex(cls); + mapped[cls] = values.find(v => typeof v === "string" && re.test(v)) as string; + } + + return mapped; +} + +export function findCssClassesLazy(...classes: S[]) { + if (IS_REPORTER) lazyWebpackSearchHistory.push(["findCssClasses", classes]); + + return proxyLazy(() => findCssClasses(...classes)); +} + function getAllPropertyNames(object: Record, includeNonEnumerable: boolean) { const names = new Set();