diff --git a/src/plugins/_api/dynamicImageModalApi.ts b/src/plugins/_api/dynamicImageModalApi.ts index 2ce51400d..4d74cff24 100644 --- a/src/plugins/_api/dynamicImageModalApi.ts +++ b/src/plugins/_api/dynamicImageModalApi.ts @@ -16,8 +16,8 @@ export default definePlugin({ { find: "SCALE_DOWN:", replacement: { - match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/, - replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))` + match: /(?<="IMAGE"===\i\?)\i(?=\?)/, + replace: "true" } } ] diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index a2fed5d79..c347cc982 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -85,7 +85,7 @@ export default definePlugin({ replace: "$&onRequestClose:$self.onPopoutClose," }, { - match: /(?<=\.avatarWrapper,)/, + match: /(?<=#{intl::SET_STATUS}\),)/, replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu," } ] diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts index c08d43282..29827a5e9 100644 --- a/src/plugins/betterUploadButton/index.ts +++ b/src/plugins/betterUploadButton/index.ts @@ -26,10 +26,18 @@ export default definePlugin({ patches: [ { find: '"ChannelAttachButton"', - replacement: { - match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/, - replace: "$&onClick:$1,onContextMenu:$2.onClick,", - }, + replacement: [ + { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/, + replace: "$&onClick:$1,onContextMenu:$2.onClick,", + noWarn: true + }, + { + match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/, + replace: "$&,onClick:$1,onContextMenu:$2.onClick,", + }, + ] }, ], }); diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 69a7a1a59..4801cf753 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -50,12 +50,24 @@ export default definePlugin({ find: ".decorationGridItem,", replacement: [ { - match: /(?<==)\i=>{let{children.{20,100}decorationGridItem/, - replace: "$self.DecorationGridItem=$&" + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /(?<==)\i=>{let{children.{20,200}decorationGridItem/, + replace: "$self.DecorationGridItem=$&", + noWarn: true }, { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert match: /(?<==)\i=>{let{user:\i,avatarDecoration/, - replace: "$self.DecorationGridDecoration=$&" + replace: "$self.DecorationGridDecoration=$&", + noWarn: true + }, + { + match: /(?<==)\i=>{var{children.{20,200}decorationGridItem/, + replace: "$self.DecorationGridItem=$&", + }, + { + match: /(?<==)\i=>{var{user:\i,avatarDecoration/, + replace: "$self.DecorationGridDecoration=$&", }, // Remove NEW label from decor avatar decorations { diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index cfc3818dd..f0be9ee54 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -34,7 +34,7 @@ export default definePlugin({ { find: "#{intl::FRIENDS_ALL_HEADER}", replacement: { - match: /toString\(\)\}\);case (\i\.\i)\.BLOCKED/, + match: /toString\(\)\}\);case (\i\.\i)\.PENDING/, replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED' }, }, @@ -50,7 +50,7 @@ export default definePlugin({ { find: "#{intl::FRIENDS_SECTION_ONLINE}", replacement: { - match: /,{id:(\i\.\i)\.BLOCKED,show:.+?className:(\i\.item)/, + match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/, replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}` } }, @@ -58,7 +58,7 @@ export default definePlugin({ { find: '"FriendsStore"', replacement: { - match: /(?<=case (\i\.\i)\.BLOCKED:return (\i)\.type===\i\.\i\.BLOCKED)/, + match: /(?<=case (\i\.\i)\.SUGGESTIONS:return \d+===(\i)\.type)/, replace: ";case $1.IMPLICIT:return $2.type===5" }, }, diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index 67bbc4ce8..ad7491cc3 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -65,10 +65,18 @@ export default definePlugin({ patches: [ { find: "{isSidebarVisible:", - replacement: { - match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, - replace: ":[$1?.startsWith('members')?$self.render():null,$2" - }, + replacement: [ + { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, + replace: ":[$1?.startsWith('members')?$self.render():null,$2", + noWarn: true + }, + { + match: /(?<=var\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, + replace: ":[$1?.startsWith('members')?$self.render():null,$2", + }, + ], predicate: () => settings.store.memberList }, { diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 7b7bcc44d..ffa2b5a25 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -84,8 +84,14 @@ export default definePlugin({ find: ".USER_MENTION)", replacement: [ { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/, - replace: "$&,color:$self.getColorInt($1?.id,$2?.id)" + replace: "$&,color:$self.getColorInt($1?.id,$2?.id)", + noWarn: true + }, + { + match: /(?<=onContextMenu:\i,color:)\i(?=\},\i\),\{children)(?<=user:(\i),channel:(\i).{0,500}?)/, + replace: "$self.getColorInt($1?.id,$2?.id)", } ], predicate: () => settings.store.chatMentions diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index ccaff2ad8..7a38bb123 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -173,7 +173,7 @@ export default definePlugin({ replacement: [ // Make the channel appear as muted if it's hidden { - match: /{channel:(\i),name:\i,muted:(\i).+?;/, + match: /\.subtitle,.+?;(?=return\(0,\i\.jsxs?\))(?<={channel:(\i),name:\i,muted:(\i).+?;)/, replace: (m, channel, muted) => `${m}${muted}=$self.isHiddenChannel(${channel})?true:${muted};` }, // Add the hidden eye icon if the channel is hidden @@ -204,7 +204,7 @@ export default definePlugin({ { // Hide unreads predicate: () => settings.store.hideUnreads === true, - match: /{channel:(\i),name:\i,.+?unread:(\i).+?;/, + match: /\.subtitle,.+?;(?=return\(0,\i\.jsxs?\))(?<={channel:(\i),name:\i,.+?unread:(\i).+?)/, replace: (m, channel, unread) => `${m}${unread}=$self.isHiddenChannel(${channel})?false:${unread};` } ] @@ -485,7 +485,7 @@ export default definePlugin({ } }, { - find: '="NowPlayingViewStore",', + find: '"NowPlayingViewStore"', replacement: { // Make active now voice states on hidden channels match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/, diff --git a/src/plugins/startupTimings/index.tsx b/src/plugins/startupTimings/index.tsx index aabc786a4..ac7f3f0c8 100644 --- a/src/plugins/startupTimings/index.tsx +++ b/src/plugins/startupTimings/index.tsx @@ -27,12 +27,22 @@ export default definePlugin({ authors: [Devs.Megu], patches: [{ find: "#{intl::ACTIVITY_SETTINGS}", - replacement: { - match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/, - replace: (_, commaOrSemi, settings, elements) => "" + - `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` + - `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})` - } + replacement: [ + { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/, + replace: (_, commaOrSemi, settings, elements) => "" + + `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` + + `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`, + noWarn: true + }, + { + match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+\)\)\}\))(?=\)\})/, + replace: (_, commaOrSemi, settings, elements) => "" + + `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` + + `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`, + }, + ] }], StartupTimingPage }); diff --git a/src/plugins/userMessagesPronouns/index.ts b/src/plugins/userMessagesPronouns/index.ts index 27b162b90..1699251ee 100644 --- a/src/plugins/userMessagesPronouns/index.ts +++ b/src/plugins/userMessagesPronouns/index.ts @@ -41,11 +41,20 @@ export default definePlugin({ }, { find: '="SYSTEM_TAG"', - replacement: { - // Add next to username (compact mode) - match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g, - replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0])," - } + replacement: [ + { + // Add next to username (compact mode) + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g, + replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),", + noWarn: true + }, + { + // Add next to username (compact mode) + match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\)\),(?=\i)/g, + replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),", + }, + ] } ], diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index 12ee18e14..afd9d48c5 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -193,10 +193,18 @@ export default definePlugin({ // Avatar component used in User DMs "User Profile" popup in the right and Profiles Modal pfp { find: ".overlay:void 0,status:", - replacement: { - match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/, - replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)}," - }, + replacement: [ + { + // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert + match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/, + replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},", + noWarn: true + }, + { + match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",.{0,100}className:\i,/, + replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},", + } + ], all: true }, // Banners diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts index e46e44ad7..526c5514b 100644 --- a/src/utils/lazy.ts +++ b/src/utils/lazy.ts @@ -20,9 +20,9 @@ export function makeLazy(factory: () => T, attempts = 5): () => T { let tries = 0; let cache: T; return () => { - if (!cache && attempts > tries++) { + if (cache === undefined && attempts > tries++) { cache = factory(); - if (!cache && attempts === tries) + if (cache === undefined && attempts === tries) console.error("Lazy factory failed:", factory); } return cache; diff --git a/src/utils/types.ts b/src/utils/types.ts index 4ff30b78c..6a791c86e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -41,7 +41,12 @@ export interface PatchReplacement { match: string | RegExp; /** The replacement string or function which returns the string for the patch replacement */ replace: string | ReplaceFn; - /** A function which returns whether this patch replacement should be applied */ + /** Do not warn if this replacement did no changes */ + noWarn?: boolean; + /** + * A function which returns whether this patch replacement should be applied. + * This is ran before patches are registered, so if this returns false, the patch will never be registered. + */ predicate?(): boolean; /** The minimum build number for this patch to be applied */ fromBuild?: number; @@ -61,7 +66,10 @@ export interface Patch { noWarn?: boolean; /** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */ group?: boolean; - /** A function which returns whether this patch should be applied */ + /** + * A function which returns whether this patch replacement should be applied. + * This is ran before patches are registered, so if this returns false, the patch will never be registered. + */ predicate?(): boolean; /** The minimum build number for this patch to be applied */ fromBuild?: number; diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index e31e167e8..2681d51d3 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -41,7 +41,7 @@ export const Switch = waitForComponent("Switch", filters.componentByCo const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", { Tooltip: filters.componentByCode("this.renderTooltip()]"), - TooltipContainer: filters.componentByCode('="div",') + TooltipContainer: filters.componentByCode('="div"') }) as { Tooltip: t.Tooltip, TooltipContainer: t.TooltipContainer; diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 4b9106238..9b66a5b4e 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -12,8 +12,8 @@ import { canonicalizeReplacement } from "@utils/patches"; import { Patch, PatchReplacement } from "@utils/types"; import { traceFunctionWithResults } from "../debug/Tracer"; -import { _blacklistBadModules, _initWebpack, factoryListeners, findModuleId, moduleListeners, waitForSubscriptions, wreq } from "./webpack"; -import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, ModuleExports, PatchedModuleFactory, WebpackRequire } from "./wreq.d"; +import { _blacklistBadModules, _initWebpack, factoryListeners, findModuleFactory, moduleListeners, waitForSubscriptions, wreq } from "./webpack"; +import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, PatchedModuleFactory, WebpackRequire } from "./wreq.d"; export const patches = [] as Patch[]; @@ -27,28 +27,26 @@ export const patchTimings = [] as Array<[plugin: string, moduleId: PropertyKey, export const getBuildNumber = makeLazy(() => { try { - try { - if (wreq.m[128014]?.toString().includes("Trying to open a changelog for an invalid build number")) { - const hardcodedGetBuildNumber = wreq(128014).b as () => number; - - if (typeof hardcodedGetBuildNumber === "function" && typeof hardcodedGetBuildNumber() === "number") { - return hardcodedGetBuildNumber(); - } + function matchBuildNumber(factoryStr: string) { + const buildNumberMatch = factoryStr.match(/.concat\("(\d+?)"\)/); + if (buildNumberMatch == null) { + return -1; } - } catch { } - const moduleId = findModuleId("Trying to open a changelog for an invalid build number"); - if (moduleId == null) { - return -1; + return Number(buildNumberMatch[1]); } - const exports = Object.values(wreq(moduleId)); - if (exports.length !== 1 || typeof exports[0] !== "function") { - return -1; + const hardcodedFactoryStr = String(wreq.m[128014]); + if (hardcodedFactoryStr.includes("Trying to open a changelog for an invalid build number")) { + const hardcodedBuildNumber = matchBuildNumber(hardcodedFactoryStr); + + if (hardcodedBuildNumber !== -1) { + return hardcodedBuildNumber; + } } - const buildNumber = exports[0](); - return typeof buildNumber === "number" ? buildNumber : -1; + const moduleFactory = findModuleFactory("Trying to open a changelog for an invalid build number"); + return matchBuildNumber(String(moduleFactory)); } catch { return -1; } @@ -122,12 +120,12 @@ define(Function.prototype, "m", { return; } - patchThisInstance(); - if (wreq == null && this.c != null) { logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire"); _initWebpack(this as WebpackRequire); } + + patchThisInstance(); } }); @@ -478,23 +476,23 @@ function patchFactory(moduleId: PropertyKey, originalFactory: AnyModuleFactory): for (let i = 0; i < patches.length; i++) { const patch = patches[i]; - const moduleMatches = typeof patch.find === "string" - ? code.includes(patch.find) - : (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code)); - - if (!moduleMatches) { - continue; - } - - // Eager patches cannot retrieve the build number because this code runs before the module for it is loaded - const buildNumber = Settings.eagerPatches ? -1 : getBuildNumber(); - const shouldCheckBuildNumber = !Settings.eagerPatches && buildNumber !== -1; + const buildNumber = getBuildNumber(); + const shouldCheckBuildNumber = buildNumber !== -1; if ( shouldCheckBuildNumber && (patch.fromBuild != null && buildNumber < patch.fromBuild) || (patch.toBuild != null && buildNumber > patch.toBuild) ) { + patches.splice(i--, 1); + continue; + } + + const moduleMatches = typeof patch.find === "string" + ? code.includes(patch.find) + : (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code)); + + if (!moduleMatches) { continue; } @@ -536,7 +534,7 @@ function patchFactory(moduleId: PropertyKey, originalFactory: AnyModuleFactory): } if (newCode === code) { - if (!patch.noWarn) { + if (!(patch.noWarn || replacement.noWarn)) { logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(moduleId)}): ${replacement.match}`); if (IS_DEV) { logger.debug("Function Source:\n", code);