From 074ebae3343170c19ca128ed76b0732375ea36bf Mon Sep 17 00:00:00 2001 From: V Date: Wed, 22 Nov 2023 01:57:00 +0100 Subject: [PATCH 01/26] ClientTheme fixes --- src/plugins/clientTheme/README.md | 7 +++++++ src/plugins/clientTheme/index.tsx | 21 ++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 src/plugins/clientTheme/README.md diff --git a/src/plugins/clientTheme/README.md b/src/plugins/clientTheme/README.md new file mode 100644 index 00000000..4b40148c --- /dev/null +++ b/src/plugins/clientTheme/README.md @@ -0,0 +1,7 @@ +# Classic Client Theme + +Revival of the old client theme experiment (The one that came before the sucky one that we actually got) + +![the ClientTheme theme colour picker](https://user-images.githubusercontent.com/37855219/230238053-e90b7098-373a-459a-bb8c-c24e82f69270.png) + +https://github.com/Vendicated/Vencord/assets/45497981/6c1bcb3b-e0c7-4a02-b0b8-c4c5cd954f38 diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 3e07b15f..d0026c75 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -11,11 +11,12 @@ import { Devs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; +import { LazyComponent } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy } from "@webpack"; +import { findByCode } from "@webpack"; import { Button, Forms } from "@webpack/common"; -const ColorPicker = findByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR"); +const ColorPicker = LazyComponent(() => findByCode(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR")); const colorPresets = [ "#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D", @@ -24,11 +25,7 @@ const colorPresets = [ ]; function onPickColor(color: number) { - let hexColor = color.toString(16); - - while (hexColor.length < 6) { - hexColor = "0" + hexColor; - } + const hexColor = color.toString(16).padStart(6, "0"); settings.store.color = hexColor; updateColorVars(hexColor); @@ -59,7 +56,8 @@ function ThemeSettings() { {lightnessWarning && Selected color is very light} {lightModeWarning && Light mode isn't supported} - : null} + : null + } ); } @@ -85,7 +83,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "ClientTheme", - authors: [Devs.F53], + authors: [Devs.F53, Devs.Nuckyz], description: "Recreation of the old client theme experiment. Add a color to your Discord client theme", settings, @@ -113,8 +111,9 @@ export default definePlugin({ } }); +const variableRegex = /(--primary-[5-9]\d{2}-hsl):.*?(\S*)%;/g; + async function generateColorOffsets() { - const variableRegex = /(--primary-[5-9]\d{2}-hsl):.*?(\S*)%;/g; const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]'); const variableLightness = {} as Record; @@ -213,7 +212,7 @@ function hexToHSL(hexCode: string) { } // Minimized math just for lightness, lowers lag when changing colors -function hexToLightness(hexCode) { +function hexToLightness(hexCode: string) { // Hex => RGB normalized to 0-1 const r = parseInt(hexCode.substring(0, 2), 16) / 255; const g = parseInt(hexCode.substring(2, 4), 16) / 255; From 371b5b0be8a6a6fab64ecb2d03c760f85820a063 Mon Sep 17 00:00:00 2001 From: V Date: Wed, 22 Nov 2023 06:14:16 +0100 Subject: [PATCH 02/26] allow plugins to specify how soon their start() method is called --- src/Vencord.ts | 16 +++++++++++----- src/plugins/clientTheme/index.tsx | 22 +++++----------------- src/plugins/index.ts | 10 ++++++++-- src/utils/types.ts | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/Vencord.ts b/src/Vencord.ts index 83c69e73..a106a0b7 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -27,6 +27,8 @@ export { PlainSettings, Settings }; import "./utils/quickCss"; import "./webpack/patchWebpack"; +import { StartAt } from "@utils/types"; + import { get as dsGet } from "./api/DataStore"; import { showNotification } from "./api/Notifications"; import { PlainSettings, Settings } from "./api/Settings"; @@ -79,7 +81,7 @@ async function syncSettings() { async function init() { await onceReady; - startAllPlugins(); + startAllPlugins(StartAt.WebpackReady); syncSettings(); @@ -130,13 +132,17 @@ async function init() { } } +startAllPlugins(StartAt.Init); init(); -if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) { - document.addEventListener("DOMContentLoaded", () => { +document.addEventListener("DOMContentLoaded", () => { + startAllPlugins(StartAt.DOMContentLoaded); + + if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) { document.head.append(Object.assign(document.createElement("style"), { id: "vencord-native-titlebar-style", textContent: "[class*=titleBar]{display: none!important}" })); - }, { once: true }); -} + } +}, { once: true }); + diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index d0026c75..7cda33e2 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -12,7 +12,7 @@ import { getTheme, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { LazyComponent } from "@utils/react"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { OptionType, StartAt } from "@utils/types"; import { findByCode } from "@webpack"; import { Button, Forms } from "@webpack/common"; @@ -87,25 +87,13 @@ export default definePlugin({ description: "Recreation of the old client theme experiment. Add a color to your Discord client theme", settings, - patches: [ - { - find: "Could not find app-mount", - replacement: { - match: /(?<=Could not find app-mount"\))/, - replace: ",$self.addThemeInitializer()" - } - } - ], - - addThemeInitializer() { - document.addEventListener("DOMContentLoaded", this.themeInitializer = () => { - updateColorVars(settings.store.color); - generateColorOffsets(); - }); + startAt: StartAt.DOMContentLoaded, + start() { + updateColorVars(settings.store.color); + generateColorOffsets(); }, stop() { - document.removeEventListener("DOMContentLoaded", this.themeInitializer); document.getElementById("clientThemeVars")?.remove(); document.getElementById("clientThemeOffsets")?.remove(); } diff --git a/src/plugins/index.ts b/src/plugins/index.ts index f6d57726..23483860 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -19,7 +19,7 @@ import { registerCommand, unregisterCommand } from "@api/Commands"; import { Settings } from "@api/Settings"; import { Logger } from "@utils/Logger"; -import { Patch, Plugin } from "@utils/types"; +import { Patch, Plugin, StartAt } from "@utils/types"; import { FluxDispatcher } from "@webpack/common"; import { FluxEvents } from "@webpack/types"; @@ -85,9 +85,15 @@ for (const p of pluginsValues) { } } -export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins() { +export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) { + logger.info(`Starting plugins (stage ${target})`); for (const name in Plugins) if (isPluginEnabled(name)) { + const p = Plugins[name]; + + const startAt = p.startAt ?? StartAt.WebpackReady; + if (startAt !== target) continue; + startPlugin(Plugins[name]); } }); diff --git a/src/utils/types.ts b/src/utils/types.ts index b32b127b..7305cd01 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -80,6 +80,11 @@ export interface PluginDef { * Whether this plugin should be enabled by default, but can be disabled */ enabledByDefault?: boolean; + /** + * When to call the start() method + * @default StartAt.WebpackReady + */ + startAt?: StartAt, /** * Optionally provide settings that the user can configure in the Plugins tab of settings. * @deprecated Use `settings` instead @@ -117,6 +122,15 @@ export interface PluginDef { tags?: string[]; } +export const enum StartAt { + /** Right away, as soon as Vencord initialised */ + Init = "Init", + /** On the DOMContentLoaded event, so once the document is ready */ + DOMContentLoaded = "DOMContentLoaded", + /** Once Discord's core webpack modules have finished loading, so as soon as things like react and flux are available */ + WebpackReady = "WebpackReady" +} + export const enum OptionType { STRING, NUMBER, From ffe6512693c7828c1d3c3bb36db69d69a93abe3f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 22 Nov 2023 02:49:08 -0300 Subject: [PATCH 03/26] Improve component finding api and migrate plugins to use them --- src/components/PluginSettings/PluginModal.tsx | 6 +- src/plugins/betterFolders/FolderSideBar.tsx | 6 +- src/plugins/clientTheme/index.tsx | 5 +- src/plugins/customRPC/index.tsx | 6 +- src/plugins/gameActivityToggle/index.tsx | 4 +- src/plugins/messageLinkEmbeds/index.tsx | 10 +-- .../serverProfile/GuildProfileModal.tsx | 6 +- src/plugins/showConnections/VerifiedIcon.tsx | 6 +- src/plugins/showConnections/index.tsx | 6 +- .../components/HiddenChannelLockScreen.tsx | 10 +-- src/plugins/voiceMessages/VoicePreview.tsx | 5 +- src/plugins/whoReacted/index.tsx | 6 +- src/utils/react.tsx | 68 ++++++++++++++----- src/webpack/webpack.ts | 2 +- 14 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 03789ac6..7d5750df 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -24,9 +24,9 @@ import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import { OptionType, Plugin } from "@utils/types"; -import { findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { User } from "discord-types/general"; import { Constructor } from "type-fest"; @@ -42,7 +42,7 @@ import { } from "./components"; import { openContributorModal } from "./ContributorModal"; -const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); +const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 97959873..13dd9ac8 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -17,8 +17,8 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { LazyComponent } from "@utils/react"; -import { find, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@utils/react"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; import { useStateFromStores } from "@webpack/common"; import type { CSSProperties } from "react"; @@ -26,7 +26,7 @@ import { ExpandedGuildFolderStore, settings } from "."; const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); const Animations = findByPropsLazy("a", "animated", "useTransition"); -const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")'))); +const GuildsBar = findComponentByCodeLazy('("guildsnav")'); export default ErrorBoundary.wrap(guildsBarProps => { const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders()); diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 7cda33e2..e5de67e4 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -11,12 +11,11 @@ import { Devs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType, StartAt } from "@utils/types"; -import { findByCode } from "@webpack"; import { Button, Forms } from "@webpack/common"; -const ColorPicker = LazyComponent(() => findByCode(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR")); +const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR"); const colorPresets = [ "#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D", diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index feed52fd..64bfd460 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -20,12 +20,12 @@ import { definePluginSettings, Settings } from "@api/Settings"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import { isTruthy } from "@utils/guards"; -import { useAwaiter } from "@utils/react"; +import { findComponentByCodeLazy, useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; -const ActivityComponent = findByCodeLazy("onOpenGameProfile"); +const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); const ActivityClassName = findByPropsLazy("activity", "buttonColor"); const Colors = findByPropsLazy("profileColors"); diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index 735f124c..cc68d55b 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -19,13 +19,13 @@ import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin from "@utils/types"; -import { findByCodeLazy } from "@webpack"; import { StatusSettingsStores } from "@webpack/common"; import style from "./style.css?managed"; -const Button = findByCodeLazy("Button.Sizes.NONE,disabled:"); +const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:"); function makeIcon(showCurrentGame?: boolean) { return function () { diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index e600d737..d8c3c99f 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -22,9 +22,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants.js"; import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { find, findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { Button, ChannelStore, @@ -45,9 +45,9 @@ const messageCache = new Map(); -const Embed = LazyComponent(() => findByCode(".inlineMediaEmbed")); -const AutoModEmbed = LazyComponent(() => findByCode(".withFooter]:", "childrenMessageContent:")); -const ChannelMessage = LazyComponent(() => find(m => m.type?.toString()?.includes("renderSimpleAccessories)"))); +const Embed = findComponentByCodeLazy(".inlineMediaEmbed"); +const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageContent:"); +const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)"); const SearchResultClasses = findByPropsLazy("message", "searchResult"); diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index 80f0842b..9def7bf9 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -10,14 +10,14 @@ import { classNameFactory } from "@api/Styles"; import { openImageModal, openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { LazyComponent, useAwaiter } from "@utils/react"; -import { findByProps, findByPropsLazy } from "@webpack"; +import { findExportedComponentLazy, useAwaiter } from "@utils/react"; +import { findByPropsLazy } from "@webpack"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { Guild, User } from "discord-types/general"; const IconUtils = findByPropsLazy("getGuildBannerURL"); const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); -const FriendRow = LazyComponent(() => findByProps("FriendRow").FriendRow); +const FriendRow = findExportedComponentLazy("FriendRow"); const cl = classNameFactory("vc-gp-"); diff --git a/src/plugins/showConnections/VerifiedIcon.tsx b/src/plugins/showConnections/VerifiedIcon.tsx index 79e27c45..20005069 100644 --- a/src/plugins/showConnections/VerifiedIcon.tsx +++ b/src/plugins/showConnections/VerifiedIcon.tsx @@ -16,12 +16,12 @@ * along with this program. If not, see . */ -import { LazyComponent } from "@utils/react"; -import { findByCode, findLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@utils/react"; +import { findLazy } from "@webpack"; import { i18n, useToken } from "@webpack/common"; const ColorMap = findLazy(m => m.colors?.INTERACTIVE_MUTED?.css); -const VerifiedIconComponent = LazyComponent(() => findByCode(".CONNECTIONS_ROLE_OFFICIAL_ICON_TOOLTIP")); +const VerifiedIconComponent = findComponentByCodeLazy(".CONNECTIONS_ROLE_OFFICIAL_ICON_TOOLTIP"); export function VerifiedIcon() { const color = useToken(ColorMap.colors.INTERACTIVE_MUTED).hex(); diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 948bdb83..2d2122d6 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -24,15 +24,15 @@ import { Flex } from "@components/Flex"; import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { copyWithToast } from "@utils/misc"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCode, findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack"; import { Text, Tooltip, UserProfileStore } from "@webpack/common"; import { User } from "discord-types/general"; import { VerifiedIcon } from "./VerifiedIcon"; -const Section = LazyComponent(() => findByCode(".lastSection]:")); +const Section = findComponentByCodeLazy(".lastSection]: "); const ThemeStore = findStoreLazy("ThemeStore"); const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl"); const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"'); diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 26efce1d..5231fe8e 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -18,9 +18,9 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy, findComponentLazy } from "@utils/react"; import { formatDuration } from "@utils/text"; -import { find, findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import type { Channel } from "discord-types/general"; @@ -81,14 +81,14 @@ const enum ChannelFlags { const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase"); const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent"); -const ChannelBeginHeader = LazyComponent(() => findByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE")); -const TagComponent = LazyComponent(() => find(m => { +const ChannelBeginHeader = findComponentByCodeLazy(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE"); +const TagComponent = findComponentLazy(m => { if (typeof m !== "function") return false; const code = Function.prototype.toString.call(m); // Get the component which doesn't include increasedActivity return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill"); -})); +}); const EmojiParser = findByPropsLazy("convertSurrogateToName"); const EmojiUtils = findByPropsLazy("getURL", "buildEmojiReactionColorsPlatformed"); diff --git a/src/plugins/voiceMessages/VoicePreview.tsx b/src/plugins/voiceMessages/VoicePreview.tsx index 912c55ae..d2c62336 100644 --- a/src/plugins/voiceMessages/VoicePreview.tsx +++ b/src/plugins/voiceMessages/VoicePreview.tsx @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -import { LazyComponent, useTimer } from "@utils/react"; -import { find } from "@webpack"; +import { findComponentByCodeLazy, useTimer } from "@utils/react"; import { cl } from "./utils"; @@ -25,7 +24,7 @@ interface VoiceMessageProps { src: string; waveform: string; } -const VoiceMessage = LazyComponent(() => find(m => m.type?.toString().includes("waveform:"))); +const VoiceMessage = findComponentByCodeLazy("waveform:"); export type VoicePreviewOptions = { src?: string; diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index fb84c155..15395a1f 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -20,14 +20,14 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { LazyComponent, useForceUpdater } from "@utils/react"; +import { findComponentByCodeLazy, useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; -import { findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; import { CustomEmoji } from "@webpack/types"; import { Message, ReactionEmoji, User } from "discord-types/general"; -const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); +const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const queue = new Queue(); diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 0181c95b..2e3352bc 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { FilterFn, filters, find, findByProps } from "@webpack"; import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; import { makeLazy } from "./lazy"; @@ -77,7 +78,6 @@ interface AwaiterOpts { * @param fallbackValue The fallback value that will be used until the promise resolved * @returns [value, error, isPending] */ - export function useAwaiter(factory: () => Promise): AwaiterRes; export function useAwaiter(factory: () => Promise, providedOpts: AwaiterOpts): AwaiterRes; export function useAwaiter(factory: () => Promise, providedOpts?: AwaiterOpts): AwaiterRes { @@ -113,31 +113,16 @@ export function useAwaiter(factory: () => Promise, providedOpts?: AwaiterO return [state.value, state.error, state.pending]; } + /** * Returns a function that can be used to force rerender react components */ - export function useForceUpdater(): () => void; export function useForceUpdater(withDep: true): [unknown, () => void]; export function useForceUpdater(withDep?: true) { const r = useReducer(x => x + 1, 0); return withDep ? r : r[1]; } -/** - * A lazy component. The factory method is called on first render. For example useful - * for const Component = LazyComponent(() => findByDisplayName("...").default) - * @param factory Function returning a Component - * @param attempts How many times to try to get the component before giving up - * @returns Result of factory function - */ - -export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { - const get = makeLazy(factory, attempts); - return (props: T) => { - const Component = get() ?? NoopComponent; - return ; - }; -} interface TimerOpts { interval?: number; @@ -159,3 +144,52 @@ export function useTimer({ interval = 1000, deps = [] }: TimerOpts) { return time; } + +/** + * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs + */ +export function findComponentByCode(...code: string[]) { + const filter = filters.byCode(...code); + return find(m => { + if (filter(m)) return true; + if (!m.$$typeof) return false; + if (m.type) return filter(m.type); // memos + if (m.render) return filter(m.render); // forwardRefs + return false; + }) ?? NoopComponent; +} + +/** + * Finds the first component that matches the filter, lazily. + */ +export function findComponentLazy(filter: FilterFn) { + return LazyComponent(() => find(filter)); +} + +/** + * Finds the first component that includes all the given code, lazily + */ +export function findComponentByCodeLazy(...code: string[]) { + return LazyComponent(() => findComponentByCode(...code)); +} + +/** + * Finds the first component that is exported by the first prop name, lazily + */ +export function findExportedComponentLazy(...props: string[]) { + return LazyComponent(() => findByProps(...props)?.[props[0]]); +} + +/** + * A lazy component. The factory method is called on first render. + * @param factory Function returning a Component + * @param attempts How many times to try to get the component before giving up + * @returns Result of factory function + */ +export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { + const get = makeLazy(factory, attempts); + return (props: T) => { + const Component = get() ?? NoopComponent; + return ; + }; +} diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 3ef5ac80..4fb94056 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -386,7 +386,7 @@ export function findStore(name: string) { } /** - * findByDisplayName but lazy + * findStore but lazy */ export function findStoreLazy(name: string) { return proxyLazy(() => findStore(name)); From b21b6d7e5dae67537864274bd9e9f58c1f827b8f Mon Sep 17 00:00:00 2001 From: V Date: Wed, 22 Nov 2023 06:48:59 +0100 Subject: [PATCH 04/26] Make it possible to destructure lazy webpack finds --- src/plugins/betterFolders/index.tsx | 5 ++--- src/plugins/greetStickerPicker/index.tsx | 5 ++--- src/utils/lazy.ts | 25 +++++++++++++++++++++--- src/webpack/common/stores.ts | 19 +++++++++--------- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 8f40d90f..68c50bcf 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -18,9 +18,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; -import { proxyLazy } from "@utils/lazy"; import definePlugin, { OptionType } from "@utils/types"; -import { findByProps, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; import { FluxDispatcher, i18n } from "@webpack/common"; import FolderSideBar from "./FolderSideBar"; @@ -31,7 +30,7 @@ enum FolderIconDisplay { MoreThanOneFolderExpanded } -const GuildsTree = proxyLazy(() => findByProps("GuildsTree").GuildsTree); +const { GuildsTree } = findByPropsLazy("GuildsTree"); const SortedGuildStore = findStoreLazy("SortedGuildStore"); export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx index 2c8f3379..845c7a1b 100644 --- a/src/plugins/greetStickerPicker/index.tsx +++ b/src/plugins/greetStickerPicker/index.tsx @@ -18,9 +18,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; -import { proxyLazy } from "@utils/lazy"; import definePlugin, { OptionType } from "@utils/types"; -import { findByProps, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ContextMenu, FluxDispatcher, Menu } from "@webpack/common"; import { Channel, Message } from "discord-types/general"; @@ -51,7 +50,7 @@ const settings = definePluginSettings({ }>(); const MessageActions = findByPropsLazy("sendGreetMessage"); -const WELCOME_STICKERS = proxyLazy(() => findByProps("WELCOME_STICKERS")?.WELCOME_STICKERS); +const { WELCOME_STICKERS } = findByPropsLazy("WELCOME_STICKERS"); function greet(channel: Channel, message: Message, stickers: string[]) { const options = MessageActions.getSendMessageOptionsForReply({ diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts index 4bac45bc..1c89d511 100644 --- a/src/utils/lazy.ts +++ b/src/utils/lazy.ts @@ -43,7 +43,6 @@ for (const method of [ "construct", "defineProperty", "deleteProperty", - "get", "getOwnPropertyDescriptor", "getPrototypeOf", "has", @@ -86,7 +85,11 @@ handler.getOwnPropertyDescriptor = (target, p) => { * Note that the example below exists already as an api, see {@link findByPropsLazy} * @example const mod = proxyLazy(() => findByProps("blah")); console.log(mod.blah); */ -export function proxyLazy(factory: () => T, attempts = 5): T { +export function proxyLazy(factory: () => T, attempts = 5, isChild = false): T { + let isSameTick = true; + if (!isChild) + setTimeout(() => isSameTick = false, 0); + let tries = 0; const proxyDummy = Object.assign(function () { }, { [kCACHE]: void 0 as T | undefined, @@ -100,5 +103,21 @@ export function proxyLazy(factory: () => T, attempts = 5): T { } }); - return new Proxy(proxyDummy, handler) as any; + return new Proxy(proxyDummy, { + ...handler, + get(target, p, receiver) { + // if we're still in the same tick, it means the lazy was immediately used. + // thus, we lazy proxy the get access to make things like destructuring work as expected + // meow here will also be a lazy + // `const { meow } = findByPropsLazy("meow");` + if (!isChild && isSameTick) + return proxyLazy( + () => Reflect.get(target[kGET](), p, receiver), + attempts, + true + ); + + return Reflect.get(target[kGET](), p, receiver); + } + }) as any; } diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index d10b1866..1b00180f 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -16,11 +16,10 @@ * along with this program. If not, see . */ -import { proxyLazy } from "@utils/lazy"; import type * as Stores from "discord-types/stores"; // eslint-disable-next-line path-alias/no-relative -import { filters, findByProps, findByPropsLazy, mapMangledModuleLazy } from "../webpack"; +import { filters, findByPropsLazy, mapMangledModuleLazy } from "../webpack"; import { waitForStore } from "./internal"; import * as t from "./types/stores"; @@ -78,13 +77,15 @@ export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { * * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); */ -export const useStateFromStores: ( - stores: t.FluxStore[], - mapper: () => T, - idk?: any, - isEqual?: (old: T, newer: T) => boolean -) => T - = proxyLazy(() => findByProps("useStateFromStores").useStateFromStores); +export const { useStateFromStores }: { + useStateFromStores: ( + stores: t.FluxStore[], + mapper: () => T, + idk?: any, + isEqual?: (old: T, newer: T) => boolean + ) => T; +} + = findByPropsLazy("useStateFromStores"); waitForStore("DraftStore", s => DraftStore = s); waitForStore("UserStore", s => UserStore = s); From 7b24c8ac69b194783caaeb79ea04668554c4bec4 Mon Sep 17 00:00:00 2001 From: V Date: Wed, 22 Nov 2023 07:04:17 +0100 Subject: [PATCH 05/26] move new webpack methods to more appropriate file --- src/components/PluginSettings/PluginModal.tsx | 3 +- src/plugins/betterFolders/FolderSideBar.tsx | 3 +- src/plugins/clientTheme/index.tsx | 2 +- src/plugins/customRPC/index.tsx | 4 +-- src/plugins/gameActivityToggle/index.tsx | 2 +- src/plugins/messageLinkEmbeds/index.tsx | 3 +- .../serverProfile/GuildProfileModal.tsx | 4 +-- src/plugins/showConnections/VerifiedIcon.tsx | 3 +- src/plugins/showConnections/index.tsx | 3 +- .../components/HiddenChannelLockScreen.tsx | 3 +- src/plugins/voiceMessages/VoicePreview.tsx | 3 +- src/plugins/whoReacted/index.tsx | 4 +-- src/utils/react.tsx | 36 ------------------- src/webpack/webpack.ts | 36 +++++++++++++++++++ 14 files changed, 52 insertions(+), 57 deletions(-) diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 7d5750df..34de43c2 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -24,9 +24,8 @@ import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal"; -import { findComponentByCodeLazy } from "@utils/react"; import { OptionType, Plugin } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { User } from "discord-types/general"; import { Constructor } from "type-fest"; diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 13dd9ac8..53d24ed9 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -17,8 +17,7 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { findComponentByCodeLazy } from "@utils/react"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { useStateFromStores } from "@webpack/common"; import type { CSSProperties } from "react"; diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index e5de67e4..7b30863e 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -11,8 +11,8 @@ import { Devs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType, StartAt } from "@utils/types"; +import { findComponentByCodeLazy } from "@webpack"; import { Button, Forms } from "@webpack/common"; const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR"); diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index 64bfd460..3653a077 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -20,9 +20,9 @@ import { definePluginSettings, Settings } from "@api/Settings"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import { isTruthy } from "@utils/guards"; -import { findComponentByCodeLazy, useAwaiter } from "@utils/react"; +import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index cc68d55b..2b84d26f 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -19,8 +19,8 @@ import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { findComponentByCodeLazy } from "@utils/react"; import definePlugin from "@utils/types"; +import { findComponentByCodeLazy } from "@webpack"; import { StatusSettingsStores } from "@webpack/common"; import style from "./style.css?managed"; diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index d8c3c99f..76282999 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -22,9 +22,8 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants.js"; import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, ChannelStore, diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index 9def7bf9..97b40b76 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -10,8 +10,8 @@ import { classNameFactory } from "@api/Styles"; import { openImageModal, openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { findExportedComponentLazy, useAwaiter } from "@utils/react"; -import { findByPropsLazy } from "@webpack"; +import { useAwaiter } from "@utils/react"; +import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { Guild, User } from "discord-types/general"; diff --git a/src/plugins/showConnections/VerifiedIcon.tsx b/src/plugins/showConnections/VerifiedIcon.tsx index 20005069..ffdf21e6 100644 --- a/src/plugins/showConnections/VerifiedIcon.tsx +++ b/src/plugins/showConnections/VerifiedIcon.tsx @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -import { findComponentByCodeLazy } from "@utils/react"; -import { findLazy } from "@webpack"; +import { findComponentByCodeLazy, findLazy } from "@webpack"; import { i18n, useToken } from "@webpack/common"; const ColorMap = findLazy(m => m.colors?.INTERACTIVE_MUTED?.css); diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 2d2122d6..67fcc968 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -24,9 +24,8 @@ import { Flex } from "@components/Flex"; import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { copyWithToast } from "@utils/misc"; -import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { Text, Tooltip, UserProfileStore } from "@webpack/common"; import { User } from "discord-types/general"; diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 5231fe8e..649e87aa 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -18,9 +18,8 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; -import { findComponentByCodeLazy, findComponentLazy } from "@utils/react"; import { formatDuration } from "@utils/text"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import type { Channel } from "discord-types/general"; diff --git a/src/plugins/voiceMessages/VoicePreview.tsx b/src/plugins/voiceMessages/VoicePreview.tsx index d2c62336..0976f794 100644 --- a/src/plugins/voiceMessages/VoicePreview.tsx +++ b/src/plugins/voiceMessages/VoicePreview.tsx @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import { findComponentByCodeLazy, useTimer } from "@utils/react"; +import { useTimer } from "@utils/react"; +import { findComponentByCodeLazy } from "@webpack"; import { cl } from "./utils"; diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index 15395a1f..4a2bdeed 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -20,9 +20,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { findComponentByCodeLazy, useForceUpdater } from "@utils/react"; +import { useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; import { CustomEmoji } from "@webpack/types"; import { Message, ReactionEmoji, User } from "discord-types/general"; diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 2e3352bc..2d2bac39 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import { FilterFn, filters, find, findByProps } from "@webpack"; import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; import { makeLazy } from "./lazy"; @@ -145,41 +144,6 @@ export function useTimer({ interval = 1000, deps = [] }: TimerOpts) { return time; } -/** - * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs - */ -export function findComponentByCode(...code: string[]) { - const filter = filters.byCode(...code); - return find(m => { - if (filter(m)) return true; - if (!m.$$typeof) return false; - if (m.type) return filter(m.type); // memos - if (m.render) return filter(m.render); // forwardRefs - return false; - }) ?? NoopComponent; -} - -/** - * Finds the first component that matches the filter, lazily. - */ -export function findComponentLazy(filter: FilterFn) { - return LazyComponent(() => find(filter)); -} - -/** - * Finds the first component that includes all the given code, lazily - */ -export function findComponentByCodeLazy(...code: string[]) { - return LazyComponent(() => findComponentByCode(...code)); -} - -/** - * Finds the first component that is exported by the first prop name, lazily - */ -export function findExportedComponentLazy(...props: string[]) { - return LazyComponent(() => findByProps(...props)?.[props[0]]); -} - /** * A lazy component. The factory method is called on first render. * @param factory Function returning a Component diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 4fb94056..9e3a0aab 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -18,6 +18,7 @@ import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; +import { LazyComponent, NoopComponent } from "@utils/react"; import type { WebpackInstance } from "discord-types/other"; import { traceFunction } from "../debug/Tracer"; @@ -392,6 +393,41 @@ export function findStoreLazy(name: string) { return proxyLazy(() => findStore(name)); } +/** + * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs + */ +export function findComponentByCode(...code: string[]) { + const filter = filters.byCode(...code); + return find(m => { + if (filter(m)) return true; + if (!m.$$typeof) return false; + if (m.type) return filter(m.type); // memos + if (m.render) return filter(m.render); // forwardRefs + return false; + }) ?? NoopComponent; +} + +/** + * Finds the first component that matches the filter, lazily. + */ +export function findComponentLazy(filter: FilterFn) { + return LazyComponent(() => find(filter)); +} + +/** + * Finds the first component that includes all the given code, lazily + */ +export function findComponentByCodeLazy(...code: string[]) { + return LazyComponent(() => findComponentByCode(...code)); +} + +/** + * Finds the first component that is exported by the first prop name, lazily + */ +export function findExportedComponentLazy(...props: string[]) { + return LazyComponent(() => findByProps(...props)?.[props[0]]); +} + /** * Wait for a module that matches the provided filter to be registered, * then call the callback with the module as the first argument From 93a95b6d565d7d6bb501b2776d641f6e25cac542 Mon Sep 17 00:00:00 2001 From: Jack <30497388+FieryFlames@users.noreply.github.com> Date: Wed, 22 Nov 2023 01:23:21 -0500 Subject: [PATCH 06/26] feat(patcher): Grouped replacements (#2009) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Co-authored-by: V --- src/utils/types.ts | 2 ++ src/webpack/patchWebpack.ts | 33 +++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/utils/types.ts b/src/utils/types.ts index 7305cd01..16867a43 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -41,6 +41,8 @@ export interface Patch { all?: boolean; /** Do not warn if this patch did no changes */ noWarn?: boolean; + /** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */ + group?: boolean; predicate?(): boolean; } diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index b81415e6..82648f62 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -204,6 +204,9 @@ function patchFactories(factories: Record()).add(patch.plugin); - logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); - if (IS_DEV) { - logger.debug("Function Source:\n", code); + if (newCode === code) { + if (!patch.noWarn) { + (window.explosivePlugins ??= new Set()).add(patch.plugin); + logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); + if (IS_DEV) { + logger.debug("Function Source:\n", code); + } + } + + if (patch.group) { + logger.warn(`Undoing patch ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`); + code = previousCode; + mod = previousMod; + patchedBy.delete(patch.plugin); + break; } } else { code = newCode; @@ -259,9 +272,17 @@ function patchFactories(factories: Record Date: Thu, 23 Nov 2023 02:20:02 +0100 Subject: [PATCH 07/26] fix showConnections & better webpack errors --- src/plugins/showConnections/index.tsx | 2 +- src/webpack/webpack.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 67fcc968..d4d59465 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -31,7 +31,7 @@ import { User } from "discord-types/general"; import { VerifiedIcon } from "./VerifiedIcon"; -const Section = findComponentByCodeLazy(".lastSection]: "); +const Section = findComponentByCodeLazy(".lastSection", "children:"); const ThemeStore = findStoreLazy("ThemeStore"); const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl"); const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"'); diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 9e3a0aab..d8743e30 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -18,7 +18,7 @@ import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; -import { LazyComponent, NoopComponent } from "@utils/react"; +import { LazyComponent } from "@utils/react"; import type { WebpackInstance } from "discord-types/other"; import { traceFunction } from "../debug/Tracer"; @@ -398,13 +398,18 @@ export function findStoreLazy(name: string) { */ export function findComponentByCode(...code: string[]) { const filter = filters.byCode(...code); - return find(m => { + const res = find(m => { if (filter(m)) return true; if (!m.$$typeof) return false; if (m.type) return filter(m.type); // memos if (m.render) return filter(m.render); // forwardRefs return false; - }) ?? NoopComponent; + }, { isIndirect: true }); + + if (!res) + handleModuleNotFound("findComponentByCode", ...code); + + return res; } /** From 6869705673de48d1f1463b851308c975c010a1f5 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 02:44:04 +0100 Subject: [PATCH 08/26] beef up ConsoleShortcuts --- src/plugins/consoleShortcuts/index.ts | 30 +++++++++++++++++++++++---- src/webpack/webpack.ts | 24 +++++++++++---------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 1c23d60e..10853f25 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -62,23 +62,27 @@ export default definePlugin({ } let fakeRenderWin: WeakRef | undefined; + const find = newFindWrapper(f => f); return { + ...Vencord.Webpack.Common, wp: Vencord.Webpack, wpc: Webpack.wreq.c, wreq: Webpack.wreq, wpsearch: search, wpex: extract, - wpexs: (code: string) => Vencord.Webpack.extract(Vencord.Webpack.findModuleId(code)!), - find: newFindWrapper(f => f), + wpexs: (code: string) => extract(Webpack.findModuleId(code)!), + find, findAll, findByProps: newFindWrapper(filters.byProps), findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)), findByCode: newFindWrapper(filters.byCode), findAllByCode: (code: string) => findAll(filters.byCode(code)), + findComponentByCode: newFindWrapper(filters.componentByCode), + findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)), + findExportedComponent: (...props: string[]) => find(...props)[props[0]], findStore: newFindWrapper(filters.byStoreName), PluginsApi: Vencord.Plugins, plugins: Vencord.Plugins.plugins, - React, Settings: Vencord.Settings, Api: Vencord.Api, reload: () => location.reload(), @@ -92,7 +96,25 @@ export default definePlugin({ fakeRenderWin = new WeakRef(win); win.focus(); - ReactDOM.render(React.createElement(component, props), win.document.body); + const doc = win.document; + doc.body.style.margin = "1em"; + + if (!win.prepared) { + win.prepared = true; + + [...document.querySelectorAll("style"), ...document.querySelectorAll("link[rel=stylesheet]")].forEach(s => { + const n = s.cloneNode(true) as HTMLStyleElement | HTMLLinkElement; + + if (s.parentElement?.tagName === "HEAD") + doc.head.append(n); + else if (n.id?.startsWith("vencord-") || n.id?.startsWith("vcd-")) + doc.documentElement.append(n); + else + doc.body.append(n); + }); + } + + ReactDOM.render(React.createElement(component, props), doc.body.appendChild(document.createElement("div"))); } }; }, diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index d8743e30..c7be62da 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -52,7 +52,18 @@ export const filters = { return true; }, byStoreName: (name: string): FilterFn => m => - m.constructor?.displayName === name + m.constructor?.displayName === name, + + componentByCode: (...code: string[]): FilterFn => { + const filter = filters.byCode(...code); + return m => { + if (filter(m)) return true; + if (!m.$$typeof) return false; + if (m.type) return filter(m.type); // memos + if (m.render) return filter(m.render); // forwardRefs + return false; + }; + } }; export const subscriptions = new Map(); @@ -397,18 +408,9 @@ export function findStoreLazy(name: string) { * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs */ export function findComponentByCode(...code: string[]) { - const filter = filters.byCode(...code); - const res = find(m => { - if (filter(m)) return true; - if (!m.$$typeof) return false; - if (m.type) return filter(m.type); // memos - if (m.render) return filter(m.render); // forwardRefs - return false; - }, { isIndirect: true }); - + const res = find(filters.componentByCode(...code), { isIndirect: true }); if (!res) handleModuleNotFound("findComponentByCode", ...code); - return res; } From 63451bad25c9f246e6d458f0472e2b57d696a581 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 03:11:17 +0100 Subject: [PATCH 09/26] Remove obsolete mapMangledModule ~ modules are no longer mangled --- src/plugins/gifPaste/index.ts | 8 ++-- src/plugins/greetStickerPicker/index.tsx | 4 +- src/plugins/imageZoom/index.tsx | 6 +-- .../components/RolesAndUsersPermissions.tsx | 6 +-- .../spotifyControls/PlayerComponent.tsx | 6 +-- src/utils/modal.tsx | 10 +---- src/webpack/common/menu.ts | 8 +--- src/webpack/common/stores.ts | 6 +-- src/webpack/common/types/menu.d.ts | 6 +-- src/webpack/common/types/utils.d.ts | 21 ++++++++++ src/webpack/common/utils.ts | 14 ++----- src/webpack/webpack.ts | 41 ------------------- 12 files changed, 46 insertions(+), 90 deletions(-) diff --git a/src/plugins/gifPaste/index.ts b/src/plugins/gifPaste/index.ts index 9ce88f9d..3e864b31 100644 --- a/src/plugins/gifPaste/index.ts +++ b/src/plugins/gifPaste/index.ts @@ -19,11 +19,9 @@ import { Devs } from "@utils/constants"; import { insertTextIntoChatInputBox } from "@utils/discord"; import definePlugin from "@utils/types"; -import { filters, mapMangledModuleLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; -const ExpressionPickerState = mapMangledModuleLazy('name:"expression-picker-last-active-view"', { - close: filters.byCode("activeView:null", "setState") -}); +const { closeExpressionPicker } = findByPropsLazy("closeExpressionPicker"); export default definePlugin({ name: "GifPaste", @@ -41,7 +39,7 @@ export default definePlugin({ handleSelect(gif?: { url: string; }) { if (gif) { insertTextIntoChatInputBox(gif.url + " "); - ExpressionPickerState.close(); + closeExpressionPicker(); } } }); diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx index 845c7a1b..9623d422 100644 --- a/src/plugins/greetStickerPicker/index.tsx +++ b/src/plugins/greetStickerPicker/index.tsx @@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { ContextMenu, FluxDispatcher, Menu } from "@webpack/common"; +import { ContextMenuApi, FluxDispatcher, Menu } from "@webpack/common"; import { Channel, Message } from "discord-types/general"; interface Sticker { @@ -183,6 +183,6 @@ export default definePlugin({ } ) { if (!(props.message as any).deleted) - ContextMenu.open(event, () => ); + ContextMenuApi.openContextMenu(event, () => ); } }); diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 75c944eb..c14754c8 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -23,7 +23,7 @@ import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; import { debounce } from "@utils/debounce"; import definePlugin, { OptionType } from "@utils/types"; -import { ContextMenu, Menu, React, ReactDOM } from "@webpack/common"; +import { ContextMenuApi, Menu, React, ReactDOM } from "@webpack/common"; import type { Root } from "react-dom/client"; import { Magnifier, MagnifierProps } from "./components/Magnifier"; @@ -89,7 +89,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => { checked={settings.store.square} action={() => { settings.store.square = !settings.store.square; - ContextMenu.close(); + ContextMenuApi.closeContextMenu(); }} /> () => { checked={settings.store.nearestNeighbour} action={() => { settings.store.nearestNeighbour = !settings.store.nearestNeighbour; - ContextMenu.close(); + ContextMenuApi.closeContextMenu(); }} /> { if ((settings.store as any).unsafeViewAsRole && permission.type === PermissionType.Role) - ContextMenu.open(e, () => ( + ContextMenuApi.openContextMenu(e, () => ( ) => - ContextMenu.open(e, () => ); + ContextMenuApi.openContextMenu(e, () => ); } function Controls() { @@ -277,7 +277,7 @@ function Info({ track }: { track: Track; }) { alt="Album Image" onClick={() => setCoverExpanded(!coverExpanded)} onContextMenu={e => { - ContextMenu.open(e, () => ); + ContextMenuApi.openContextMenu(e, () => ); }} /> )} diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 1efb9cdd..6758a1a1 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { filters, findByProps, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; +import { findByProps, findByPropsLazy } from "@webpack"; import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react"; import { LazyComponent } from "./react"; @@ -49,13 +49,7 @@ export interface ModalOptions { type RenderFunction = (props: ModalProps) => ReactNode; -export const Modals = mapMangledModuleLazy(".closeWithCircleBackground", { - ModalRoot: filters.byCode(".root"), - ModalHeader: filters.byCode(".header"), - ModalContent: filters.byCode(".content"), - ModalFooter: filters.byCode(".footerSeparator"), - ModalCloseButton: filters.byCode(".closeWithCircleBackground"), -}) as { +export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as { ModalRoot: ComponentType Menu = m); -export const ContextMenu: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', { - open: filters.byCode("stopPropagation"), - openLazy: m => m.toString().length < 50, - close: filters.byCode("CONTEXT_MENU_CLOSE") -}); +export const ContextMenuApi: t.ContextMenuApi = findByPropsLazy("closeContextMenu", "openContextMenu"); diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 1b00180f..0c470d6a 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -19,7 +19,7 @@ import type * as Stores from "discord-types/stores"; // eslint-disable-next-line path-alias/no-relative -import { filters, findByPropsLazy, mapMangledModuleLazy } from "../webpack"; +import { findByPropsLazy } from "../webpack"; import { waitForStore } from "./internal"; import * as t from "./types/stores"; @@ -62,10 +62,6 @@ export let EmojiStore: t.EmojiStore; export let WindowStore: t.WindowStore; export let DraftStore: t.DraftStore; -export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { - openUntrustedLink: filters.byCode(".apply(this,arguments)") -}); - /** * React hook that returns stateful data for one or more stores * You might need a custom comparator (4th argument) if your store data is an object diff --git a/src/webpack/common/types/menu.d.ts b/src/webpack/common/types/menu.d.ts index 29f3ffae..0b8ab5c6 100644 --- a/src/webpack/common/types/menu.d.ts +++ b/src/webpack/common/types/menu.d.ts @@ -75,14 +75,14 @@ export interface Menu { } export interface ContextMenuApi { - close(): void; - open( + closeContextMenu(): void; + openContextMenu( event: UIEvent, render?: Menu["Menu"], options?: { enableSpellCheck?: boolean; }, renderLazy?: () => Promise ): void; - openLazy( + openContextMenuLazy( event: UIEvent, renderLazy?: () => Promise, options?: { enableSpellCheck?: boolean; } diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index 64a68f2c..24665914 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -161,3 +161,24 @@ export interface i18n { Messages: Record; } + +export interface Clipboard { + copy(text: string): void; + SUPPORTS_COPY: boolean; +} + +export interface NavigationRouter { + back(): void; + forward(): void; + hasNavigated(): boolean; + getHistory(): { + action: string; + length: 50; + [key: string]: any; + }; + transitionTo(path: string, ...args: unknown[]): void; + transitionToGuild(guildId: string, ...args: unknown[]): void; + replaceWith(...args: unknown[]): void; + getLastRouteChangeSource(): any; + getLastRouteChangeSourceLocationStack(): any; +} diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 8376925e..2a3d4e67 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -20,7 +20,7 @@ import { proxyLazy } from "@utils/lazy"; import type { Channel, User } from "discord-types/general"; // eslint-disable-next-line path-alias/no-relative -import { _resolveReady, filters, find, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "../webpack"; +import { _resolveReady, find, findByPropsLazy, findLazy, waitFor } from "../webpack"; import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; @@ -102,17 +102,9 @@ export const ApplicationAssetUtils = findByPropsLazy("fetchAssetIds", "getAssetI fetchAssetIds: (applicationId: string, e: string[]) => Promise; }; -export const Clipboard = mapMangledModuleLazy('document.queryCommandEnabled("copy")||document.queryCommandSupported("copy")', { - copy: filters.byCode(".copy("), - SUPPORTS_COPY: x => typeof x === "boolean", -}); +export const Clipboard: t.Clipboard = findByPropsLazy("SUPPORTS_COPY", "copy"); -export const NavigationRouter = mapMangledModuleLazy("transitionToGuild - ", { - transitionTo: filters.byCode("transitionTo -"), - transitionToGuild: filters.byCode("transitionToGuild -"), - goBack: filters.byCode("goBack()"), - goForward: filters.byCode("goForward()"), -}); +export const NavigationRouter: t.NavigationRouter = findByPropsLazy("transitionTo", "replaceWith", "transitionToGuild"); waitFor(["dispatch", "subscribe"], m => { FluxDispatcher = m; diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index c7be62da..b9c67e8f 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -312,47 +312,6 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId( 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 const mapMangledModule = traceFunction("mapMangledModule", 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 - */ -export function mapMangledModuleLazy(code: string, mappers: Record): Record { - return proxyLazy(() => mapMangledModule(code, mappers)); -} - /** * Find the first module that has the specified properties */ From 9efc0ff5797cfdd346d0a1951db4d3480137f72d Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 03:21:58 +0100 Subject: [PATCH 10/26] Remove obsolete nested webpack search --- src/webpack/patchWebpack.ts | 15 ++------ src/webpack/webpack.ts | 68 ------------------------------------- 2 files changed, 3 insertions(+), 80 deletions(-) diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 82648f62..0311e171 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -170,18 +170,9 @@ function patchFactories(factories: Record Date: Thu, 23 Nov 2023 03:41:09 +0100 Subject: [PATCH 11/26] Remove obsolete webpack hacks --- src/webpack/patchWebpack.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 0311e171..b992cfde 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -119,12 +119,9 @@ function patchFactories(factories: Record mod.toString(); - factory.original = originalMod; - } catch { } + factory.toString = () => mod.toString(); + factory.original = originalMod; for (let i = 0; i < patches.length; i++) { const patch = patches[i]; @@ -210,7 +205,6 @@ function patchFactories(factories: Record()).add(patch.plugin); logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); if (IS_DEV) { logger.debug("Function Source:\n", code); From 4832a9433fe6575d4a15cd733246cbc1498666c4 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 06:18:34 +0100 Subject: [PATCH 12/26] fix broken webpack finds --- src/plugins/friendInvites/index.ts | 4 ++-- src/plugins/webContextMenus.web/index.ts | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/plugins/friendInvites/index.ts b/src/plugins/friendInvites/index.ts index 92209986..e5ff447e 100644 --- a/src/plugins/friendInvites/index.ts +++ b/src/plugins/friendInvites/index.ts @@ -23,7 +23,7 @@ import { findByPropsLazy } from "@webpack"; import { RestAPI, UserStore } from "@webpack/common"; const FriendInvites = findByPropsLazy("createFriendInvite"); -const uuid = findByPropsLazy("v4", "v1"); +const { uuid4 } = findByPropsLazy("uuid4"); export default definePlugin({ name: "FriendInvites", @@ -56,7 +56,7 @@ export default definePlugin({ let invite: any; if (uses === 1) { - const random = uuid.v4(); + const random = uuid4(); const { body: { invite_suggestions } } = await RestAPI.post({ url: "/friend-finder/find-friends", body: { diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index bbfaa674..9c4dd67c 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -20,8 +20,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { saveFile } from "@utils/web"; -import { findByProps, findLazy } from "@webpack"; -import { Clipboard } from "@webpack/common"; +import { findByProps } from "@webpack"; +import { Clipboard, ComponentDispatch } from "@webpack/common"; async function fetchImage(url: string) { const res = await fetch(url); @@ -30,7 +30,6 @@ async function fetchImage(url: string) { return await res.blob(); } -const MiniDispatcher = findLazy(m => m.emitter?._events?.INSERT_TEXT); const settings = definePluginSettings({ // This needs to be all in one setting because to enable any of these, we need to make Discord use their desktop context @@ -213,7 +212,7 @@ export default definePlugin({ cut() { this.copy(); - MiniDispatcher.dispatch("INSERT_TEXT", { rawText: "" }); + ComponentDispatch.dispatch("INSERT_TEXT", { rawText: "" }); }, async paste() { From 0f74817e255727ad7b1fa6a6d963f42115e29070 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 23 Nov 2023 02:21:59 -0300 Subject: [PATCH 13/26] Fix broken SHC find --- .../showHiddenChannels/components/HiddenChannelLockScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 649e87aa..87946110 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -90,7 +90,7 @@ const TagComponent = findComponentLazy(m => { }); const EmojiParser = findByPropsLazy("convertSurrogateToName"); -const EmojiUtils = findByPropsLazy("getURL", "buildEmojiReactionColorsPlatformed"); +const EmojiUtils = findByPropsLazy("getURL", "getEmojiColors"); const ChannelTypesToChannelNames = { [ChannelTypes.GUILD_TEXT]: "text", From f39f16d34bd12a19c4c06b2ac4d7ba4010c1dcef Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 06:43:22 +0100 Subject: [PATCH 14/26] fix circular import bricking browser version --- src/utils/lazyReact.tsx | 23 +++++++++++++++++++++++ src/utils/react.tsx | 17 ++--------------- src/webpack/webpack.ts | 5 +---- 3 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 src/utils/lazyReact.tsx diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx new file mode 100644 index 00000000..abd300a9 --- /dev/null +++ b/src/utils/lazyReact.tsx @@ -0,0 +1,23 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { makeLazy } from "./lazy"; + +const NoopComponent = () => null; + +/** + * A lazy component. The factory method is called on first render. + * @param factory Function returning a Component + * @param attempts How many times to try to get the component before giving up + * @returns Result of factory function + */ +export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { + const get = makeLazy(factory, attempts); + return (props: T) => { + const Component = get() ?? NoopComponent; + return ; + }; +} diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 2d2bac39..f31549f1 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -18,9 +18,10 @@ import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; -import { makeLazy } from "./lazy"; import { checkIntersecting } from "./misc"; +export * from "./lazyReact"; + export const NoopComponent = () => null; /** @@ -143,17 +144,3 @@ export function useTimer({ interval = 1000, deps = [] }: TimerOpts) { return time; } - -/** - * A lazy component. The factory method is called on first render. - * @param factory Function returning a Component - * @param attempts How many times to try to get the component before giving up - * @returns Result of factory function - */ -export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { - const get = makeLazy(factory, attempts); - return (props: T) => { - const Component = get() ?? NoopComponent; - return ; - }; -} diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 2c55fd36..b4fdb4e1 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -17,8 +17,8 @@ */ import { proxyLazy } from "@utils/lazy"; +import { LazyComponent } from "@utils/lazyReact"; import { Logger } from "@utils/Logger"; -import { LazyComponent } from "@utils/react"; import type { WebpackInstance } from "discord-types/other"; import { traceFunction } from "../debug/Tracer"; @@ -338,9 +338,6 @@ export function waitFor(filter: string | string[] | FilterFn, callback: Callback else if (typeof filter !== "function") throw new Error("filter must be a string, string[] or function, got " + typeof filter); - const [existing, id] = find(filter!, { isIndirect: true, isWaitFor: true }); - if (existing) return void callback(existing, id); - subscriptions.set(filter, callback); } From 81fb7c6322f4fd5f56ea95b268597f5365af6f39 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 06:45:01 +0100 Subject: [PATCH 15/26] add back code that got lost --- src/webpack/webpack.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index b4fdb4e1..980288e9 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -338,6 +338,9 @@ export function waitFor(filter: string | string[] | FilterFn, callback: Callback else if (typeof filter !== "function") throw new Error("filter must be a string, string[] or function, got " + typeof filter); + const [existing, id] = find(filter!, { isIndirect: true, isWaitFor: true }); + if (existing) return void callback(existing, id); + subscriptions.set(filter, callback); } From e14fba28a51981e76f23dad9b9145b49421dac07 Mon Sep 17 00:00:00 2001 From: cat <101748822+capillarys@users.noreply.github.com> Date: Thu, 23 Nov 2023 17:31:25 +0000 Subject: [PATCH 16/26] ClearURLS for x.com links (#2005) --- src/plugins/clearURLs/defaultRules.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/clearURLs/defaultRules.ts b/src/plugins/clearURLs/defaultRules.ts index d6914b90..e7c3ecbe 100644 --- a/src/plugins/clearURLs/defaultRules.ts +++ b/src/plugins/clearURLs/defaultRules.ts @@ -121,6 +121,9 @@ export const defaultRules = [ "t@*.twitter.com", "s@*.twitter.com", "ref_*@*.twitter.com", + "t@*.x.com", + "s@*.x.com", + "ref_*@*.x.com", "tt_medium", "tt_content", "lr@yandex.*", From fdddfdb05b339a7c677044756b75cdff43f45e16 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 24 Nov 2023 16:32:30 -0300 Subject: [PATCH 17/26] feat(plugins): FixImagesQuality --- src/plugins/fixImagesQuality/index.ts | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/plugins/fixImagesQuality/index.ts diff --git a/src/plugins/fixImagesQuality/index.ts b/src/plugins/fixImagesQuality/index.ts new file mode 100644 index 00000000..e0dfcb6d --- /dev/null +++ b/src/plugins/fixImagesQuality/index.ts @@ -0,0 +1,35 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "FixImagesQuality", + description: "Fixes the quality of images in the chat being horrible.", + authors: [Devs.Nuckyz], + patches: [ + { + find: "handleImageLoad=", + replacement: { + match: /(?<=getSrc\(\i\){.+?format:)\i/, + replace: "null" + } + } + ] +}); From 3e8e106be7f010a87871a44f75446053aae3b6be Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 24 Nov 2023 16:49:19 -0300 Subject: [PATCH 18/26] Fix broken patches --- src/plugins/fixImagesQuality/index.ts | 20 +++------------ src/plugins/sortFriendRequests/index.tsx | 31 ++++++++++++------------ src/plugins/webContextMenus.web/index.ts | 6 ++--- 3 files changed, 22 insertions(+), 35 deletions(-) diff --git a/src/plugins/fixImagesQuality/index.ts b/src/plugins/fixImagesQuality/index.ts index e0dfcb6d..d94c8e3b 100644 --- a/src/plugins/fixImagesQuality/index.ts +++ b/src/plugins/fixImagesQuality/index.ts @@ -1,20 +1,8 @@ /* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; diff --git a/src/plugins/sortFriendRequests/index.tsx b/src/plugins/sortFriendRequests/index.tsx index 3698379c..c40a1814 100644 --- a/src/plugins/sortFriendRequests/index.tsx +++ b/src/plugins/sortFriendRequests/index.tsx @@ -16,17 +16,27 @@ * along with this program. If not, see . */ +import { definePluginSettings } from "@api/Settings"; import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { RelationshipStore } from "@webpack/common"; import { User } from "discord-types/general"; -import { Settings } from "Vencord"; + +const settings = definePluginSettings({ + showDates: { + type: OptionType.BOOLEAN, + description: "Show dates on friend requests", + default: false, + restartNeeded: true + } +}); export default definePlugin({ name: "SortFriendRequests", authors: [Devs.Megu], description: "Sorts friend requests by date of receipt", + settings, patches: [{ find: "getRelationshipCounts(){", @@ -35,13 +45,11 @@ export default definePlugin({ replace: ".sortBy((row) => $self.sortList(row))" } }, { - find: "RelationshipTypes.PENDING_INCOMING?", + find: ".Messages.FRIEND_REQUEST_CANCEL", replacement: { - predicate: () => Settings.plugins.SortFriendRequests.showDates, - match: /(user:(\i),.{10,50}),subText:(\i),(className:\i\.userInfo}\))/, - replace: (_, pre, user, subtext, post) => `${pre}, - subText: $self.makeSubtext(${subtext}, ${user}), - ${post}` + predicate: () => settings.store.showDates, + match: /subText:(\i)(?=,className:\i\.userInfo}\))(?<=user:(\i).+?)/, + replace: (_, subtext, user) => `subText:$self.makeSubtext(${subtext},${user})` } }], @@ -63,14 +71,5 @@ export default definePlugin({ {!isNaN(since.getTime()) && Received — {since.toDateString()}} ); - }, - - options: { - showDates: { - type: OptionType.BOOLEAN, - description: "Show dates on friend requests", - default: false, - restartNeeded: true - } } }); diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 9c4dd67c..aa575bfd 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -120,9 +120,9 @@ export default definePlugin({ find: 'navId:"image-context"', predicate: () => settings.store.addBack, replacement: { - // return IS_DESKTOP ? React.createElement(Menu, ...) - match: /return \i\.\i\?/, - replace: "return true?" + // return IS_DESKTOP && null != ... ? React.createElement(Menu, ...) + match: /return \i\.\i(?=&&null)/, + replace: "return true" } }, From 534565db256e9aaf47f3b3092cbe6f4e0bc982be Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 01:32:21 +0100 Subject: [PATCH 19/26] Add webpack find testing (#2016) Co-authored-by: V Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- .github/workflows/reportBrokenPlugins.yml | 2 +- scripts/build/build.mjs | 4 +- scripts/build/buildWeb.mjs | 4 +- scripts/build/common.mjs | 1 + scripts/generateReport.ts | 212 +++++++++++++++--- src/plugins/fakeNitro/index.ts | 9 +- .../components/UserPermissions.tsx | 5 +- src/plugins/spotifyControls/SpotifyStore.ts | 9 +- src/plugins/typingIndicator/index.tsx | 5 +- src/plugins/vencordToolbox/index.tsx | 7 +- src/utils/lazy.ts | 2 +- src/utils/lazyReact.tsx | 6 +- src/webpack/common/internal.tsx | 10 +- src/webpack/common/utils.ts | 44 ++-- src/webpack/webpack.ts | 66 +++++- 15 files changed, 289 insertions(+), 97 deletions(-) diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml index 8bc93618..13202200 100644 --- a/.github/workflows/reportBrokenPlugins.yml +++ b/.github/workflows/reportBrokenPlugins.yml @@ -29,7 +29,7 @@ jobs: sudo apt-get install -y chromium-browser - name: Build web - run: pnpm buildWeb --standalone + run: pnpm buildWeb --standalone --dev - name: Create Report timeout-minutes: 10 diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 89cca7e4..a2e0e002 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -21,11 +21,11 @@ import esbuild from "esbuild"; import { readdir } from "fs/promises"; import { join } from "path"; -import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isDev, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs"; const defines = { IS_STANDALONE: isStandalone, - IS_DEV: JSON.stringify(watch), + IS_DEV: JSON.stringify(isDev), IS_UPDATER_DISABLED: updaterDisabled, IS_WEB: false, IS_EXTENSION: false, diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index 353f4e06..b4c72606 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises import { join } from "path"; import Zip from "zip-local"; -import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, globPlugins, isDev, VERSION } from "./common.mjs"; /** * @type {esbuild.BuildOptions} @@ -43,7 +43,7 @@ const commonOptions = { IS_WEB: "true", IS_EXTENSION: "false", IS_STANDALONE: "true", - IS_DEV: JSON.stringify(watch), + IS_DEV: JSON.stringify(isDev), IS_DISCORD_DESKTOP: "false", IS_VESKTOP: "false", IS_UPDATER_DISABLED: "true", diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 5488b1b3..5c34ad03 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -33,6 +33,7 @@ export const VERSION = PackageJSON.version; // https://reproducible-builds.org/docs/source-date-epoch/ export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now(); export const watch = process.argv.includes("--watch"); +export const isDev = watch || process.argv.includes("--dev"); export const isStandalone = JSON.stringify(process.argv.includes("--standalone")); export const updaterDisabled = JSON.stringify(process.argv.includes("--disable-updater")); export const gitHash = process.env.VENCORD_HASH || execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 4e044c94..cadf0c2a 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -34,7 +34,7 @@ for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) { const CANARY = process.env.USE_CANARY === "true"; const browser = await pup.launch({ - headless: true, + headless: "new", executablePath: process.env.CHROMIUM_BIN }); @@ -58,14 +58,16 @@ const report = { plugin: string; error: string; }[], - otherErrors: [] as string[] + otherErrors: [] as string[], + badWebpackFinds: [] as string[] }; const IGNORED_DISCORD_ERRORS = [ "KeybindStore: Looking for callback action", "Unable to process domain list delta: Client revision number is null", "Downloading the full bad domains file", - /\[GatewaySocket\].{0,110}Cannot access '/ + /\[GatewaySocket\].{0,110}Cannot access '/, + "search for 'name' in undefined" ] as Array; function toCodeBlock(s: string) { @@ -74,7 +76,10 @@ function toCodeBlock(s: string) { } async function printReport() { + console.log(); + console.log("# Vencord Report" + (CANARY ? " (Canary)" : "")); + console.log(); console.log("## Bad Patches"); @@ -87,12 +92,19 @@ async function printReport() { console.log(); + console.log("## Bad Webpack Finds"); + report.badWebpackFinds.forEach(p => console.log("- " + p)); + + console.log(); + console.log("## Bad Starts"); report.badStarts.forEach(p => { console.log(`- ${p.plugin}`); console.log(` - Error: ${toCodeBlock(p.error)}`); }); + console.log(); + report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex))); console.log("## Discord Errors"); @@ -100,8 +112,9 @@ async function printReport() { console.log(`- ${toCodeBlock(e)}`); }); + console.log(); + if (process.env.DISCORD_WEBHOOK) { - // this code was written almost entirely by Copilot xD await fetch(process.env.DISCORD_WEBHOOK, { method: "POST", headers: { @@ -110,7 +123,7 @@ async function printReport() { body: JSON.stringify({ description: "Here's the latest Vencord Report!", username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""), - avatar_url: "https://cdn.discordapp.com/icons/1015060230222131221/f0204a918c6c9c9a43195997e97d8adf.webp", + avatar_url: "https://cdn.discordapp.com/icons/1015060230222131221/6101cff21e241cebb60c4a01563d0c01.webp?size=512", embeds: [ { title: "Bad Patches", @@ -125,6 +138,11 @@ async function printReport() { }).join("\n\n") || "None", color: report.badPatches.length ? 0xff0000 : 0x00ff00 }, + { + title: "Bad Webpack Finds", + description: report.badWebpackFinds.map(toCodeBlock).join("\n") || "None", + color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00 + }, { title: "Bad Starts", description: report.badStarts.map(p => { @@ -153,29 +171,35 @@ async function printReport() { page.on("console", async e => { const level = e.type(); - const args = e.args(); + const rawArgs = e.args(); - const firstArg = (await args[0]?.jsonValue()); - if (firstArg === "PUPPETEER_TEST_DONE_SIGNAL") { + const firstArg = await rawArgs[0]?.jsonValue(); + if (firstArg === "[PUPPETEER_TEST_DONE_SIGNAL]") { await browser.close(); await printReport(); process.exit(); } - const isVencord = (await args[0]?.jsonValue()) === "[Vencord]"; - const isDebug = (await args[0]?.jsonValue()) === "[PUP_DEBUG]"; + const isVencord = firstArg === "[Vencord]"; + const isDebug = firstArg === "[PUP_DEBUG]"; + const isWebpackFindFail = firstArg === "[PUP_WEBPACK_FIND_FAIL]"; + + if (isWebpackFindFail) { + process.exitCode = 1; + report.badWebpackFinds.push(await rawArgs[1].jsonValue() as string); + } if (isVencord) { - // make ci fail - process.exitCode = 1; + const args = await Promise.all(e.args().map(a => a.jsonValue())); - const jsonArgs = await Promise.all(args.map(a => a.jsonValue())); - const [, tag, message] = jsonArgs; - const cause = await maybeGetError(args[3]); + const [, tag, message] = args as Array; + const cause = await maybeGetError(e.args()[3]); switch (tag) { case "WebpackInterceptor:": - const [, plugin, type, id, regex] = (message as string).match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!; + process.exitCode = 1; + + const [, plugin, type, id, regex] = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!; report.badPatches.push({ plugin, type, @@ -183,17 +207,26 @@ page.on("console", async e => { match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"), error: cause }); + break; case "PluginManager:": - const [, name] = (message as string).match(/Failed to start (.+)/)!; + const failedToStartMatch = message.match(/Failed to start (.+)/); + if (!failedToStartMatch) break; + + process.exitCode = 1; + + const [, name] = failedToStartMatch; report.badStarts.push({ plugin: name, error: cause }); + break; } - } else if (isDebug) { - console.error(e.text()); + } + + if (isDebug) { + console.log(e.text()); } else if (level === "error") { const text = await Promise.all( e.args().map(async a => { @@ -206,8 +239,8 @@ page.on("console", async e => { ).then(a => a.join(" ").trim()); - if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of")) { - console.error("Got unexpected error", text); + if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("found no module Filter:")) { + console.error("[Unexpected Error]", text); report.otherErrors.push(text); } } @@ -219,17 +252,16 @@ page.on("pageerror", e => console.error("[Page Error]", e)); await page.setBypassCSP(true); function runTime(token: string) { - console.error("[PUP_DEBUG]", "Starting test..."); + console.log("[PUP_DEBUG]", "Starting test..."); try { - // spoof languages to not be suspicious + // Spoof languages to not be suspicious Object.defineProperty(navigator, "languages", { get: function () { return ["en-US", "en"]; }, }); - // Monkey patch Logger to not log with custom css // @ts-ignore Vencord.Util.Logger.prototype._log = function (level, levelColor, args) { @@ -237,7 +269,7 @@ function runTime(token: string) { console[level]("[Vencord]", this.name + ":", ...args); }; - // force enable all plugins and patches + // Force enable all plugins and patches Vencord.Plugins.patches.length = 0; Object.values(Vencord.Plugins.plugins).forEach(p => { // Needs native server to run @@ -247,8 +279,14 @@ function runTime(token: string) { p.patches?.forEach(patch => { patch.plugin = p.name; delete patch.predicate; + if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement]; + + patch.replacement.forEach(r => { + delete r.predicate; + }); + Vencord.Plugins.patches.push(patch); }); }); @@ -256,41 +294,141 @@ function runTime(token: string) { Vencord.Webpack.waitFor( "loginToken", m => { - console.error("[PUP_DEBUG]", "Logging in with token..."); + console.log("[PUP_DEBUG]", "Logging in with token..."); m.loginToken(token); } ); - // force load all chunks + // Force load all chunks Vencord.Webpack.onceReady.then(() => setTimeout(async () => { - console.error("[PUP_DEBUG]", "Webpack is ready!"); + console.log("[PUP_DEBUG]", "Webpack is ready!"); const { wreq } = Vencord.Webpack; - console.error("[PUP_DEBUG]", "Loading all chunks..."); - const ids = Function("return" + wreq.u.toString().match(/(?<=\()\{.+?\}/s)![0])(); - for (const id in ids) { + console.log("[PUP_DEBUG]", "Loading all chunks..."); + + let chunks = null as Record | null; + const sym = Symbol("Vencord.chunksExtract"); + + Object.defineProperty(Object.prototype, sym, { + get() { + chunks = this; + }, + set() { }, + configurable: true, + }); + + await (wreq as any).el(sym); + delete Object.prototype[sym]; + + const validChunksEntryPoints = [] as string[]; + const validChunks = [] as string[]; + const invalidChunks = [] as string[]; + + if (!chunks) throw new Error("Failed to get chunks"); + + chunksLoop: + for (const entryPoint in chunks) { + const chunkIds = chunks[entryPoint]; + + for (const id of chunkIds) { + if (!wreq.u(id)) continue; + + const isWasm = await fetch(wreq.p + wreq.u(id)) + .then(r => r.text()) + .then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); + + await new Promise(r => setTimeout(r, 150)); + + if (isWasm) { + invalidChunks.push(id); + continue chunksLoop; + } + + validChunks.push(id); + } + + validChunksEntryPoints.push(entryPoint); + } + + for (const entryPoint of validChunksEntryPoints) { + try { + // Loads all chunks required for an entry point + await (wreq as any).el(entryPoint); + } catch (err) { } + } + + const allChunks = Function("return " + (wreq.u.toString().match(/(?<=\()\{.+?\}/s)?.[0] ?? "null"))() as Record | null; + if (!allChunks) throw new Error("Failed to get all chunks"); + const chunksLeft = Object.keys(allChunks).filter(id => { + return !(validChunks.includes(id) || invalidChunks.includes(id)); + }); + + for (const id of chunksLeft) { const isWasm = await fetch(wreq.p + wreq.u(id)) .then(r => r.text()) .then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); - if (!isWasm) - await wreq.e(id as any); + // Loads a chunk + if (!isWasm) await wreq.e(id as any); await new Promise(r => setTimeout(r, 150)); } - console.error("[PUP_DEBUG]", "Finished loading chunks!"); + + // Make sure every chunk has finished loading + await new Promise(r => setTimeout(r, 1000)); + + for (const entryPoint of validChunksEntryPoints) { + try { + if (wreq.m[entryPoint]) wreq(entryPoint as any); + } catch (err) { + console.error(err); + } + } + + console.log("[PUP_DEBUG]", "Finished loading all chunks!"); for (const patch of Vencord.Plugins.patches) { if (!patch.all) { new Vencord.Util.Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`); } } - setTimeout(() => console.log("PUPPETEER_TEST_DONE_SIGNAL"), 1000); + + for (const [searchType, args] of Vencord.Webpack.lazyWebpackSearchHistory) { + let method = searchType; + + if (searchType === "findComponent") method = "find"; + if (searchType === "findExportedComponent") method = "findByProps"; + if (searchType === "waitFor" || searchType === "waitForComponent" || searchType === "waitForStore") { + if (typeof args[0] === "string") method = "findByProps"; + else method = "find"; + } + + try { + let result: any; + + if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") { + const [factory] = args; + result = factory(); + } else { + // @ts-ignore + result = Vencord.Webpack[method](...args); + } + + if (result == null || ("$$get" in result && result.$$get() == null)) throw "a rock at ben shapiro"; + } catch (e) { + let logMessage = searchType; + if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`; + else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`; + + console.log("[PUP_WEBPACK_FIND_FAIL]", logMessage); + } + } + + setTimeout(() => console.log("[PUPPETEER_TEST_DONE_SIGNAL]"), 1000); }, 1000)); } catch (e) { - console.error("[PUP_DEBUG]", "A fatal error occurred"); - console.error("[PUP_DEBUG]", e); + console.log("[PUP_DEBUG]", "A fatal error occurred:", e); process.exit(1); } } diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index 4d6b7957..3ac75556 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -21,10 +21,9 @@ import { definePluginSettings, Settings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; import { getCurrentGuild } from "@utils/discord"; -import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import type { Message } from "discord-types/general"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; @@ -48,9 +47,9 @@ function searchProtoClassField(localName: string, protoClass: any) { return fieldGetter?.(); } -const PreloadedUserSettingsActionCreators = proxyLazy(() => UserSettingsActionCreators.PreloadedUserSettingsActionCreators); -const AppearanceSettingsActionCreators = proxyLazy(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass)); -const ClientThemeSettingsActionsCreators = proxyLazy(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators)); +const PreloadedUserSettingsActionCreators = proxyLazyWebpack(() => UserSettingsActionCreators.PreloadedUserSettingsActionCreators); +const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass)); +const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators)); const USE_EXTERNAL_EMOJIS = 1n << 18n; const USE_EXTERNAL_STICKERS = 1n << 37n; diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index aeb97664..b75bafdc 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -18,9 +18,8 @@ import ErrorBoundary from "@components/ErrorBoundary"; import ExpandableHeader from "@components/ExpandableHeader"; -import { proxyLazy } from "@utils/lazy"; import { classes } from "@utils/misc"; -import { filters, findBulk } from "@webpack"; +import { filters, findBulk, proxyLazyWebpack } from "@webpack"; import { i18n, PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common"; import type { Guild, GuildMember } from "discord-types/general"; @@ -36,7 +35,7 @@ interface UserPermission { type UserPermissions = Array; -const Classes = proxyLazy(() => { +const Classes = proxyLazyWebpack(() => { const modules = findBulk( filters.byProps("roles", "rolePill", "rolePillBorder"), filters.byProps("roleCircle", "dotBorderBase", "dotBorderColor"), diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts index f940d8d5..b3cd0b28 100644 --- a/src/plugins/spotifyControls/SpotifyStore.ts +++ b/src/plugins/spotifyControls/SpotifyStore.ts @@ -17,8 +17,7 @@ */ import { Settings } from "@api/Settings"; -import { proxyLazy } from "@utils/lazy"; -import { findByPropsLazy } from "@webpack"; +import { findByProps, proxyLazyWebpack } from "@webpack"; import { Flux, FluxDispatcher } from "@webpack/common"; export interface Track { @@ -66,12 +65,12 @@ interface Device { type Repeat = "off" | "track" | "context"; // Don't wanna run before Flux and Dispatcher are ready! -export const SpotifyStore = proxyLazy(() => { +export const SpotifyStore = proxyLazyWebpack(() => { // For some reason ts hates extends Flux.Store const { Store } = Flux; - const SpotifySocket = findByPropsLazy("getActiveSocketAndDevice"); - const SpotifyUtils = findByPropsLazy("SpotifyAPI"); + const SpotifySocket = findByProps("getActiveSocketAndDevice"); + const SpotifyUtils = findByProps("SpotifyAPI"); const API_BASE = "https://api.spotify.com/v1/me/player"; diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index 86bfbb4f..e35eb9b6 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -19,14 +19,13 @@ import { definePluginSettings, Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { LazyComponent } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { find, findStoreLazy } from "@webpack"; +import { find, findStoreLazy, LazyComponentWebpack } from "@webpack"; import { ChannelStore, GuildMemberStore, i18n, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { buildSeveralUsers } from "../typingTweaks"; -const ThreeDots = LazyComponent(() => { +const ThreeDots = LazyComponentWebpack(() => { // This doesn't really need to explicitly find Dots' own module, but it's fine const res = find(m => m.Dots && !m.Menu); diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index cd266c6f..bb63a86b 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -22,15 +22,14 @@ import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { LazyComponent } from "@utils/react"; import definePlugin from "@utils/types"; -import { filters, find } from "@webpack"; +import { filters, find, LazyComponentWebpack } from "@webpack"; import { Menu, Popout, useState } from "@webpack/common"; import type { ReactNode } from "react"; -const HeaderBarIcon = LazyComponent(() => { +const HeaderBarIcon = LazyComponentWebpack(() => { const filter = filters.byCode(".HEADER_BAR_BADGE"); - return find(m => m.Icon && filter(m.Icon)).Icon; + return find(m => m.Icon && filter(m.Icon))?.Icon; }); function VencordPopout(onClose: () => void) { diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts index 1c89d511..32336fb4 100644 --- a/src/utils/lazy.ts +++ b/src/utils/lazy.ts @@ -76,7 +76,7 @@ handler.getOwnPropertyDescriptor = (target, p) => { }; /** - * Wraps the result of {@see makeLazy} in a Proxy you can consume as if it wasn't lazy. + * Wraps the result of {@link makeLazy} in a Proxy you can consume as if it wasn't lazy. * On first property access, the lazy is evaluated * @param factory lazy factory * @param attempts how many times to try to evaluate the lazy before giving up diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx index abd300a9..e45ca079 100644 --- a/src/utils/lazyReact.tsx +++ b/src/utils/lazyReact.tsx @@ -16,8 +16,12 @@ const NoopComponent = () => null; */ export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { const get = makeLazy(factory, attempts); - return (props: T) => { + const LazyComponent = (props: T) => { const Component = get() ?? NoopComponent; return ; }; + + LazyComponent.$$get = get; + + return LazyComponent; } diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx index 66c52de0..9a89af36 100644 --- a/src/webpack/common/internal.tsx +++ b/src/webpack/common/internal.tsx @@ -19,9 +19,11 @@ import { LazyComponent } from "@utils/react"; // eslint-disable-next-line path-alias/no-relative -import { FilterFn, filters, waitFor } from "../webpack"; +import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "../webpack"; export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]): T { + if (IS_DEV) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]); + let myValue: T = function () { throw new Error(`Vencord could not find the ${name} Component`); } as any; @@ -30,11 +32,13 @@ export function waitForComponent = React.Comp waitFor(filter, (v: any) => { myValue = v; Object.assign(lazyComponent, v); - }); + }, { isIndirect: true }); return lazyComponent; } export function waitForStore(name: string, cb: (v: any) => void) { - waitFor(filters.byStoreName(name), cb); + if (IS_DEV) lazyWebpackSearchHistory.push(["waitForStore", [name]]); + + waitFor(filters.byStoreName(name), cb, { isIndirect: true }); } diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 2a3d4e67..cef4d51d 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -16,14 +16,23 @@ * along with this program. If not, see . */ -import { proxyLazy } from "@utils/lazy"; import type { Channel, User } from "discord-types/general"; // eslint-disable-next-line path-alias/no-relative -import { _resolveReady, find, findByPropsLazy, findLazy, waitFor } from "../webpack"; +import { _resolveReady, findByPropsLazy, findLazy, waitFor } from "../webpack"; import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; + +waitFor(["dispatch", "subscribe"], m => { + FluxDispatcher = m; + const cb = () => { + m.unsubscribe("CONNECTION_OPEN", cb); + _resolveReady(); + }; + m.subscribe("CONNECTION_OPEN", cb); +}); + export let ComponentDispatch; waitFor(["ComponentDispatch", "ComponentDispatcher"], m => ComponentDispatch = m.ComponentDispatch); @@ -41,7 +50,9 @@ export let SnowflakeUtils: t.SnowflakeUtils; waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m); export let Parser: t.Parser; +waitFor("parseTopic", m => Parser = m); export let Alerts: t.Alerts; +waitFor(["show", "close"], m => Alerts = m); const ToastType = { MESSAGE: 0, @@ -82,6 +93,13 @@ export const Toasts = { } }; +// This is the same module but this is easier +waitFor("showToast", m => { + Toasts.show = m.showToast; + Toasts.pop = m.popToast; +}); + + /** * Show a simple toast. If you need more options, use Toasts.show manually */ @@ -106,26 +124,8 @@ export const Clipboard: t.Clipboard = findByPropsLazy("SUPPORTS_COPY", "copy"); export const NavigationRouter: t.NavigationRouter = findByPropsLazy("transitionTo", "replaceWith", "transitionToGuild"); -waitFor(["dispatch", "subscribe"], m => { - FluxDispatcher = m; - const cb = () => { - m.unsubscribe("CONNECTION_OPEN", cb); - _resolveReady(); - }; - m.subscribe("CONNECTION_OPEN", cb); -}); - - -// This is the same module but this is easier -waitFor("showToast", m => { - Toasts.show = m.showToast; - Toasts.pop = m.popToast; -}); - -waitFor(["show", "close"], m => Alerts = m); -waitFor("parseTopic", m => Parser = m); - export let SettingsRouter: any; waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m); -export const PermissionsBits: t.PermissionsBits = proxyLazy(() => find(m => typeof m.Permissions?.ADMINISTRATOR === "bigint").Permissions); +const { Permissions } = findLazy(m => typeof m.Permissions?.ADMINISTRATOR === "bigint") as { Permissions: t.PermissionsBits; }; +export { Permissions as PermissionsBits }; diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 980288e9..4bdec707 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -127,13 +127,6 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn return isWaitFor ? [null, null] : null; }); -/** - * find but lazy - */ -export function findLazy(filter: FilterFn) { - return proxyLazy(() => find(filter)); -} - export function findAll(filter: FilterFn) { if (typeof filter !== "function") throw new Error("Invalid filter. Expected a function got " + typeof filter); @@ -244,6 +237,49 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId( return null; }); +export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "findByCode" | "findStore" | "findComponent" | "findComponentByCode" | "findExportedComponent" | "waitFor" | "waitForComponent" | "waitForStore" | "proxyLazyWebpack" | "LazyComponentWebpack", any[]]>; + +/** + * This is just a wrapper around {@link proxyLazy} to make our reporter test for your webpack finds. + * + * Wraps the result of {@link makeLazy} in a Proxy you can consume as if it wasn't lazy. + * On first property access, the lazy is evaluated + * @param factory lazy factory + * @param attempts how many times to try to evaluate the lazy before giving up + * @returns Proxy + * + * Note that the example below exists already as an api, see {@link findByPropsLazy} + * @example const mod = proxyLazy(() => findByProps("blah")); console.log(mod.blah); + */ +export function proxyLazyWebpack(factory: () => any, attempts?: number) { + if (IS_DEV) lazyWebpackSearchHistory.push(["proxyLazyWebpack", [factory]]); + + return proxyLazy(factory, attempts); +} + +/** + * This is just a wrapper around {@link LazyComponent} to make our reporter test for your webpack finds. + * + * A lazy component. The factory method is called on first render. + * @param factory Function returning a Component + * @param attempts How many times to try to get the component before giving up + * @returns Result of factory function + */ +export function LazyComponentWebpack(factory: () => any, attempts?: number) { + if (IS_DEV) lazyWebpackSearchHistory.push(["LazyComponentWebpack", [factory]]); + + return LazyComponent(factory, attempts); +} + +/** + * find but lazy + */ +export function findLazy(filter: FilterFn) { + if (IS_DEV) lazyWebpackSearchHistory.push(["find", [filter]]); + + return proxyLazy(() => find(filter)); +} + /** * Find the first module that has the specified properties */ @@ -258,6 +294,8 @@ export function findByProps(...props: string[]) { * findByProps but lazy */ export function findByPropsLazy(...props: string[]) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findByProps", props]); + return proxyLazy(() => findByProps(...props)); } @@ -275,6 +313,8 @@ export function findByCode(...code: string[]) { * findByCode but lazy */ export function findByCodeLazy(...code: string[]) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findByCode", code]); + return proxyLazy(() => findByCode(...code)); } @@ -292,6 +332,8 @@ export function findStore(name: string) { * findStore but lazy */ export function findStoreLazy(name: string) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findStore", [name]]); + return proxyLazy(() => findStore(name)); } @@ -309,6 +351,8 @@ export function findComponentByCode(...code: string[]) { * Finds the first component that matches the filter, lazily. */ export function findComponentLazy(filter: FilterFn) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findComponent", [filter]]); + return LazyComponent(() => find(filter)); } @@ -316,6 +360,8 @@ export function findComponentLazy(filter: FilterFn) { * Finds the first component that includes all the given code, lazily */ export function findComponentByCodeLazy(...code: string[]) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findComponentByCode", code]); + return LazyComponent(() => findComponentByCode(...code)); } @@ -323,6 +369,8 @@ export function findComponentByCodeLazy(...code: string[ * Finds the first component that is exported by the first prop name, lazily */ export function findExportedComponentLazy(...props: string[]) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findExportedComponent", props]); + return LazyComponent(() => findByProps(...props)?.[props[0]]); } @@ -330,7 +378,9 @@ export function findExportedComponentLazy(...props: stri * Wait for a module that matches the provided filter to be registered, * then call the callback with the module as the first argument */ -export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn) { +export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn, { isIndirect = false }: { isIndirect?: boolean; } = {}) { + if (IS_DEV && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]); + if (typeof filter === "string") filter = filters.byProps(filter); else if (Array.isArray(filter)) From 598ffe6368bf454b65c522d4dfbca8a1afc9a05c Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 01:51:50 +0100 Subject: [PATCH 20/26] reporter: remove sleeps --- scripts/generateReport.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index cadf0c2a..3cbab55e 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -338,8 +338,6 @@ function runTime(token: string) { .then(r => r.text()) .then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); - await new Promise(r => setTimeout(r, 150)); - if (isWasm) { invalidChunks.push(id); continue chunksLoop; @@ -371,8 +369,6 @@ function runTime(token: string) { // Loads a chunk if (!isWasm) await wreq.e(id as any); - - await new Promise(r => setTimeout(r, 150)); } // Make sure every chunk has finished loading From 68fca7854179222ba29cb7428f34874f3576fe05 Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 01:56:29 +0100 Subject: [PATCH 21/26] reporter: test dev instead of main --- .github/workflows/reportBrokenPlugins.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml index 13202200..4b09463e 100644 --- a/.github/workflows/reportBrokenPlugins.yml +++ b/.github/workflows/reportBrokenPlugins.yml @@ -12,6 +12,12 @@ jobs: steps: - uses: actions/checkout@v3 + if: ${{ github.event_name == 'schedule' }} + with: + ref: dev + + - uses: actions/checkout@v3 + if: ${{ github.event_name == 'workflow_dispatch' }} - uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json From 7c3b247d84c01882830e754e0e2995c36e4a2a33 Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 02:43:29 +0100 Subject: [PATCH 22/26] fix WebContextMenus --- src/plugins/webContextMenus.web/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index aa575bfd..cf50bb86 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -118,10 +118,11 @@ export default definePlugin({ // Add back image context menu { find: 'navId:"image-context"', + all: true, predicate: () => settings.store.addBack, replacement: { - // return IS_DESKTOP && null != ... ? React.createElement(Menu, ...) - match: /return \i\.\i(?=&&null)/, + // return IS_DESKTOP ? React.createElement(Menu, ...) + match: /return \i\.\i(?=\?|&&)/, replace: "return true" } }, From 604cf002116e4172058b2f1c8a6df4a187cf61ab Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 02:51:19 +0100 Subject: [PATCH 23/26] reporter: fix markdown output --- scripts/generateReport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 3cbab55e..54097671 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -226,7 +226,7 @@ page.on("console", async e => { } if (isDebug) { - console.log(e.text()); + console.error(e.text()); } else if (level === "error") { const text = await Promise.all( e.args().map(async a => { From 6bbf562ab6b92ad7cc267cfa53aec6a88b7c006d Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 02:51:58 +0100 Subject: [PATCH 24/26] bump to v1.6.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23d9dd27..4d0dd262 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.6.3", + "version": "1.6.4", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From ec16fd874189411479890e00d93770001f9d09ab Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 02:55:59 +0100 Subject: [PATCH 25/26] fix ci --- .github/workflows/test.yml | 2 ++ src/utils/lazyReact.tsx | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 46d56418..a756681c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,9 +3,11 @@ on: push: branches: - main + - dev pull_request: branches: - main + - dev jobs: test: runs-on: ubuntu-latest diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx index e45ca079..da36d4e7 100644 --- a/src/utils/lazyReact.tsx +++ b/src/utils/lazyReact.tsx @@ -4,6 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { ComponentType } from "react"; + import { makeLazy } from "./lazy"; const NoopComponent = () => null; @@ -23,5 +25,5 @@ export function LazyComponent(factory: () => React.Compo LazyComponent.$$get = get; - return LazyComponent; + return LazyComponent as ComponentType; } From 867730a478d0b7a87b46ecc9f8080d66dde2cd68 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 24 Nov 2023 23:14:18 -0300 Subject: [PATCH 26/26] Simplify some components finds; Make undo of patch groups more clear --- src/plugins/typingIndicator/index.tsx | 9 ++------- src/plugins/vencordToolbox/index.tsx | 7 ++----- src/webpack/patchWebpack.ts | 4 ++-- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index e35eb9b6..c5cf5a9d 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -20,17 +20,12 @@ import { definePluginSettings, Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { find, findStoreLazy, LazyComponentWebpack } from "@webpack"; +import { findExportedComponentLazy, findStoreLazy } from "@webpack"; import { ChannelStore, GuildMemberStore, i18n, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { buildSeveralUsers } from "../typingTweaks"; -const ThreeDots = LazyComponentWebpack(() => { - // This doesn't really need to explicitly find Dots' own module, but it's fine - const res = find(m => m.Dots && !m.Menu); - - return res?.Dots; -}); +const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots"); const TypingStore = findStoreLazy("TypingStore"); const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore"); diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index bb63a86b..2b0ce14e 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -23,14 +23,11 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { filters, find, LazyComponentWebpack } from "@webpack"; +import { findExportedComponentLazy } from "@webpack"; import { Menu, Popout, useState } from "@webpack/common"; import type { ReactNode } from "react"; -const HeaderBarIcon = LazyComponentWebpack(() => { - const filter = filters.byCode(".HEADER_BAR_BADGE"); - return find(m => m.Icon && filter(m.Icon))?.Icon; -}); +const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider"); function VencordPopout(onClose: () => void) { const pluginEntries = [] as ReactNode[]; diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index b992cfde..76a9fb0f 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -212,7 +212,7 @@ function patchFactories(factories: Record