feat(metro): lazy patching!
This commit is contained in:
parent
3667a4361e
commit
b489aea48a
32 changed files with 258 additions and 128 deletions
|
|
@ -2,9 +2,9 @@ import { Strings } from "@core/i18n";
|
|||
import { ApplicationCommand, ApplicationCommandOptionType } from "@lib/api/commands/types";
|
||||
import { settings } from "@lib/settings";
|
||||
import { messageUtil } from "@metro/common";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
|
||||
const util = findByProps("inspect");
|
||||
const util = findByPropsProxy("inspect");
|
||||
const AsyncFunction = (async () => void 0).constructor;
|
||||
|
||||
const ZERO_WIDTH_SPACE_CHARACTER = "\u200B";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { FluxDispatcher } from "@metro/common";
|
||||
import { findByName } from "@metro/utils";
|
||||
import { findByNameProxy } from "@metro/utils";
|
||||
import { PrimitiveType } from "intl-messageformat";
|
||||
|
||||
import langDefault from "./default.json";
|
||||
|
||||
const IntlMessageFormat = findByName("MessageFormat") as typeof import("intl-messageformat").default;
|
||||
const IntlMessageFormat = findByNameProxy("MessageFormat") as typeof import("intl-messageformat").default;
|
||||
|
||||
type I18nKey = keyof typeof langDefault;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,17 +6,17 @@ import { useProxy } from "@lib/api/storage";
|
|||
import { installPlugin, plugins, removePlugin } from "@lib/managers/plugins";
|
||||
import { installTheme, removeTheme, themes } from "@lib/managers/themes";
|
||||
import { DISCORD_SERVER_ID, HTTP_REGEX_MULTI, PLUGINS_CHANNEL_ID, PROXY_PREFIX, THEMES_CHANNEL_ID } from "@lib/utils/constants";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { ErrorBoundary } from "@ui/components";
|
||||
import { Button } from "@ui/components/discord/Redesign";
|
||||
import { showToast } from "@ui/toasts";
|
||||
|
||||
type PostType = "Plugin" | "Theme";
|
||||
|
||||
// const ForumPostLongPressActionSheet = findByName("ForumPostLongPressActionSheet", false);
|
||||
// const { ActionSheetRow } = findByProps("ActionSheetRow");
|
||||
const { useFirstForumPostMessage } = findByProps("useFirstForumPostMessage");
|
||||
const forumReactions = findByProps("MostCommonForumPostReaction");
|
||||
// const ForumPostLongPressActionSheet = findByNameProxy("ForumPostLongPressActionSheet", false);
|
||||
// const { ActionSheetRow } = findByPropsProxy("ActionSheetRow");
|
||||
const { useFirstForumPostMessage } = findByPropsProxy("useFirstForumPostMessage");
|
||||
const forumReactions = findByPropsProxy("MostCommonForumPostReaction");
|
||||
|
||||
const postMap = {
|
||||
Plugin: {
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ import { PROXY_PREFIX, THEMES_CHANNEL_ID } from "@lib/utils/constants";
|
|||
import { channels, url } from "@metro/common";
|
||||
import { byMutableProp } from "@metro/filters";
|
||||
import { findExports } from "@metro/finders";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { showConfirmationAlert } from "@ui/alerts";
|
||||
import { showToast } from "@ui/toasts";
|
||||
|
||||
const showSimpleActionSheet = findExports(byMutableProp("showSimpleActionSheet"));
|
||||
const handleClick = findByProps("handleClick");
|
||||
const handleClick = findByPropsProxy("handleClick");
|
||||
const { openURL } = url;
|
||||
const { getChannelId } = channels;
|
||||
const { getChannel } = findByProps("getChannel");
|
||||
const { getChannel } = findByPropsProxy("getChannel");
|
||||
|
||||
function typeFromUrl(url: string) {
|
||||
if (url.startsWith(PROXY_PREFIX)) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useProxy } from "@lib/api/storage";
|
|||
import { settings } from "@lib/settings";
|
||||
import { HTTP_REGEX_MULTI } from "@lib/utils/constants";
|
||||
import { clipboard } from "@metro/common";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { showInputAlert } from "@ui/alerts";
|
||||
import { ErrorBoundary, Search } from "@ui/components";
|
||||
import { HelpMessage } from "@ui/components/discord";
|
||||
|
|
@ -43,8 +43,8 @@ function getItemsByQuery<T extends AddonPageProps<unknown>["items"][string]>(ite
|
|||
}).map(r => r.obj);
|
||||
}
|
||||
|
||||
const reanimated = findByProps("useSharedValue");
|
||||
const { FloatingActionButton } = findByProps("FloatingActionButton");
|
||||
const reanimated = findByPropsProxy("useSharedValue");
|
||||
const { FloatingActionButton } = findByPropsProxy("FloatingActionButton");
|
||||
|
||||
export default function AddonPage<T>({ floatingButtonText, fetchFunction, items, safeModeMessage, safeModeExtras, card: CardComponent, isRemoveMode, headerComponent, onFABPress }: AddonPageProps<T>) {
|
||||
useProxy(items);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { getAssetIDByName } from "@lib/api/assets";
|
||||
import { ReactNative as RN } from "@metro/common";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { semanticColors } from "@ui/color";
|
||||
import { FormRow } from "@ui/components/discord/Forms";
|
||||
import { FormCheckbox, FormSwitch, IconButton } from "@ui/components/discord/Redesign";
|
||||
import { createStyles, TextStyleSheet } from "@ui/styles";
|
||||
import { ImageBackground, View } from "react-native";
|
||||
|
||||
const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
|
||||
const { showSimpleActionSheet } = findByProps("showSimpleActionSheet");
|
||||
const { hideActionSheet } = findByPropsProxy("openLazy", "hideActionSheet");
|
||||
const { showSimpleActionSheet } = findByPropsProxy("showSimpleActionSheet");
|
||||
|
||||
// TODO: These styles work weirdly. iOS has cramped text, Android with low DPI probably does too. Fix?
|
||||
const useStyles = createStyles({
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { useProxy } from "@lib/api/storage";
|
|||
import { connectToDebugger } from "@lib/debug";
|
||||
import { loaderConfig, settings } from "@lib/settings";
|
||||
import { NavigationNative } from "@metro/common";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { semanticColors } from "@ui/color";
|
||||
import { ErrorBoundary } from "@ui/components";
|
||||
import { FormText } from "@ui/components/discord/Forms";
|
||||
|
|
@ -15,8 +15,8 @@ import { Button, Stack, TableRow, TableRowGroup, TableSwitchRow, TextInput } fro
|
|||
import { createStyles, TextStyleSheet } from "@ui/styles";
|
||||
import { ScrollView, StyleSheet } from "react-native";
|
||||
|
||||
const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
|
||||
const { showSimpleActionSheet } = findByProps("showSimpleActionSheet");
|
||||
const { hideActionSheet } = findByPropsProxy("openLazy", "hideActionSheet");
|
||||
const { showSimpleActionSheet } = findByPropsProxy("showSimpleActionSheet");
|
||||
|
||||
const RDT_EMBED_LINK = "https://raw.githubusercontent.com/amsyarasyiq/rdt-embedder/main/dist.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { createProxy, useProxy } from "@lib/api/storage";
|
|||
import { FontDefinition, saveFont, validateFont } from "@lib/managers/fonts";
|
||||
import { getCurrentTheme } from "@lib/managers/themes";
|
||||
import { safeFetch } from "@lib/utils";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { ErrorBoundary } from "@ui/components";
|
||||
import { ActionSheet, BottomSheetTitleHeader, Button, IconButton, Stack, TableRow, TableRowGroup, Text, TextInput, useNavigation } from "@ui/components/discord/Redesign";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
|
||||
const actionSheet = findByProps("hideActionSheet");
|
||||
const actionSheet = findByPropsProxy("hideActionSheet");
|
||||
|
||||
function guessFontName(urls: string[]) {
|
||||
const fileNames = urls.map(url => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,26 @@
|
|||
export {
|
||||
after,
|
||||
before,
|
||||
instead
|
||||
import { getFindProxyInfo } from "@metro/proxy";
|
||||
import {
|
||||
after as _after,
|
||||
before as _before,
|
||||
instead as _instead
|
||||
} from "spitroast";
|
||||
|
||||
function shim<T extends (...args: any) => any>(fn: T) {
|
||||
return function shimmed(this: any, ...args: Parameters<T>) {
|
||||
const proxyInfo = getFindProxyInfo(args[1]);
|
||||
if (proxyInfo && !proxyInfo.cache) {
|
||||
return void proxyInfo.subscribe(exp => {
|
||||
args[1] = exp;
|
||||
fn.apply(this, args);
|
||||
});
|
||||
}
|
||||
return fn.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
export const after = shim(_after);
|
||||
export const before = shim(_before);
|
||||
export const instead = shim(_instead);
|
||||
|
||||
/** @internal */
|
||||
export * as default from "spitroast";
|
||||
export default { after, before, instead };
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { Author } from "@lib/utils/types";
|
|||
import { chroma } from "@metro/common";
|
||||
import { byMutableProp } from "@metro/filters";
|
||||
import { findExports } from "@metro/finders";
|
||||
import { findByName, findByProps, findByStoreName } from "@metro/utils";
|
||||
import { findByNameProxy, findByPropsProxy, findByStoreName } from "@metro/utils";
|
||||
import { ImageBackground, Platform, processColor } from "react-native";
|
||||
|
||||
export interface ThemeData {
|
||||
|
|
@ -41,12 +41,12 @@ export interface Theme {
|
|||
|
||||
//! As of 173.10, early-finding this does not work.
|
||||
// Somehow, this is late enough, though?
|
||||
export const color = findByProps("SemanticColor");
|
||||
export const color = findByPropsProxy("SemanticColor");
|
||||
|
||||
const appearanceManager = findByProps("updateTheme");
|
||||
const mmkvStorage = findByProps("storage")?.parseResolve ? findByProps("storage") : findByProps("impl").impl;
|
||||
const appearanceManager = findByPropsProxy("updateTheme");
|
||||
const mmkvStorage = findByPropsProxy("storage")?.parseResolve ? findByPropsProxy("storage") : findByPropsProxy("impl").impl;
|
||||
const ThemeStore = findByStoreName("ThemeStore");
|
||||
const formDividerModule = findByProps("DIVIDER_COLORS");
|
||||
const formDividerModule = findByPropsProxy("DIVIDER_COLORS");
|
||||
|
||||
export const themes = wrapSync(createStorage<Record<string, Theme>>(createMMKVBackend("VENDETTA_THEMES")));
|
||||
|
||||
|
|
@ -75,9 +75,9 @@ async function writeTheme(theme: Theme | {}) {
|
|||
* @internal
|
||||
*/
|
||||
export function patchChatBackground() {
|
||||
const MessagesWrapperConnected = findByName("MessagesWrapperConnected", false);
|
||||
const MessagesWrapperConnected = findByNameProxy("MessagesWrapperConnected", false);
|
||||
if (!MessagesWrapperConnected) return;
|
||||
const { MessagesWrapper } = findByProps("MessagesWrapper");
|
||||
const { MessagesWrapper } = findByPropsProxy("MessagesWrapper");
|
||||
if (!MessagesWrapper) return;
|
||||
|
||||
const patches = [
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { ButtonColors } from "@lib/utils/types";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import InputAlert, { InputAlertProps } from "@ui/components/InputAlert";
|
||||
|
||||
const Alerts = findByProps("openLazy", "close");
|
||||
const Alerts = findByPropsProxy("openLazy", "close");
|
||||
|
||||
interface InternalConfirmationAlertOptions extends Omit<ConfirmationAlertOptions, "content"> {
|
||||
content?: ConfirmationAlertOptions["content"];
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { findByStoreName } from "@metro/utils";
|
|||
//* In 173.10, Discord restructured a lot of the app. These changes included making the color module impossible to early-find.
|
||||
// ? To stop duplication, it is now exported in our theming code.
|
||||
// ? These comments are preserved for historical purposes.
|
||||
// const colorModule = findByProps("colors", "meta");
|
||||
// const colorModule = findByPropsProxy("colors", "meta");
|
||||
|
||||
// ? SemanticColor and default.colors are effectively ThemeColorMap
|
||||
export const semanticColors = (color?.default?.colors ?? constants?.ThemeColorMap) as Record<string, any>;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { instead } from "@lib/api/patcher";
|
||||
import { OptionalKeys } from "@lib/utils/types";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { AccessibilityInfo, Platform, View } from "react-native";
|
||||
|
||||
const { ContextMenu: _ContextMenu } = findByProps("ContextMenu");
|
||||
const { ContextMenu: _ContextMenu } = findByPropsProxy("ContextMenu");
|
||||
|
||||
type ContextMenuItem = {
|
||||
label: string,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ButtonColors } from "@lib/utils/types";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { Alert } from "@ui/components/discord";
|
||||
import { FormInput } from "@ui/components/discord/Forms";
|
||||
|
||||
const Alerts = findByProps("openLazy", "close");
|
||||
const Alerts = findByPropsProxy("openLazy", "close");
|
||||
|
||||
export interface InputAlertProps {
|
||||
title?: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
|
||||
export const Forms = findByProps("Form", "FormSection");
|
||||
export const Forms = findByPropsProxy("Form", "FormSection");
|
||||
|
||||
export const {
|
||||
Form,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { findExports } from "@metro/finders";
|
||||
import { createFilterDefinition, findByProps } from "@metro/utils";
|
||||
import { createFilterDefinition, findByPropsProxy } from "@metro/utils";
|
||||
import { RedesignModuleKeys } from "@ui/types";
|
||||
import { useEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
|
|
@ -11,9 +11,9 @@ const bySingularProp = createFilterDefinition<[string]>(
|
|||
|
||||
const findSingular = (prop: string) => findExports(bySingularProp(prop))?.[prop];
|
||||
|
||||
export const Redesign = findByProps("TableRow") as Record<RedesignModuleKeys, any>;
|
||||
export const Redesign = findByPropsProxy("TableRow") as Record<RedesignModuleKeys, any>;
|
||||
|
||||
export const CompatfulRedesign = findByProps("ActionSheetRow") as unknown as {
|
||||
export const CompatfulRedesign = findByPropsProxy("ActionSheetRow") as unknown as {
|
||||
TextStyleSheet: typeof import("@ui/styles").TextStyleSheet;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { findByDisplayName, findByName, findByProps } from "@metro/utils";
|
||||
import { findByDisplayName, findByNameProxy, findByPropsProxy } from "@metro/utils";
|
||||
|
||||
export * as Forms from "./Forms";
|
||||
export * as Redesign from "./Redesign";
|
||||
|
||||
// Discord
|
||||
export const Alert = findByDisplayName("FluxContainer(Alert)");
|
||||
export const Button = findByProps("Looks", "Colors", "Sizes") as React.ComponentType<any> & { Looks: any, Colors: any, Sizes: any; };
|
||||
export const HelpMessage = findByName("HelpMessage");
|
||||
export const Button = findByPropsProxy("Looks", "Colors", "Sizes") as React.ComponentType<any> & { Looks: any, Colors: any, Sizes: any; };
|
||||
export const HelpMessage = findByNameProxy("HelpMessage");
|
||||
// React Native's included SafeAreaView only adds padding on iOS.
|
||||
export const SafeAreaView = findByProps("useSafeAreaInsets").SafeAreaView as any;
|
||||
export const SafeAreaView = findByPropsProxy("useSafeAreaInsets").SafeAreaView as any;
|
||||
|
|
|
|||
|
|
@ -4,17 +4,17 @@ import { after } from "@lib/api/patcher";
|
|||
import { toggleSafeMode } from "@lib/debug";
|
||||
import { settings } from "@lib/settings";
|
||||
import { ButtonColors } from "@lib/utils/types";
|
||||
import { findByName, findByProps } from "@metro/utils";
|
||||
import { findByNameProxy, findByPropsProxy } from "@metro/utils";
|
||||
import { semanticColors } from "@ui/color";
|
||||
import { Codeblock, ErrorBoundary as _ErrorBoundary } from "@ui/components";
|
||||
import { Button, SafeAreaView } from "@ui/components/discord";
|
||||
import { createThemedStyleSheet, TextStyleSheet } from "@ui/styles";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
const ErrorBoundary = findByName("ErrorBoundary");
|
||||
const ErrorBoundary = findByNameProxy("ErrorBoundary");
|
||||
|
||||
// Let's just pray they have this.
|
||||
const { BadgableTabBar } = findByProps("BadgableTabBar");
|
||||
const { BadgableTabBar } = findByPropsProxy("BadgableTabBar");
|
||||
|
||||
const styles = createThemedStyleSheet({
|
||||
container: {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { after } from "@lib/api/patcher";
|
||||
import { findInReactTree } from "@lib/utils";
|
||||
import { i18n, NavigationNative } from "@metro/common";
|
||||
import { findByName } from "@metro/utils";
|
||||
import { findByNameProxy } from "@metro/utils";
|
||||
import { FormIcon, FormRow, FormSection } from "@ui/components/discord/Forms";
|
||||
import { registeredSections } from "@ui/settings";
|
||||
import { after } from "spitroast";
|
||||
|
||||
import { CustomPageRenderer, wrapOnPress } from "./shared";
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ function SettingsSection() {
|
|||
}
|
||||
|
||||
export function patchPanelUI(unpatches: (() => void | boolean)[]) {
|
||||
unpatches.push(after("default", findByName("getScreens", false), (_a, screens) => ({
|
||||
unpatches.push(after("default", findByNameProxy("getScreens", false), (_a, screens) => ({
|
||||
...screens,
|
||||
VendettaCustomPage: {
|
||||
title: "Bnuuy",
|
||||
|
|
@ -36,7 +36,7 @@ export function patchPanelUI(unpatches: (() => void | boolean)[]) {
|
|||
}
|
||||
})));
|
||||
|
||||
const unpatch = after("default", findByName("UserSettingsOverviewWrapper", false), (_a, ret) => {
|
||||
const unpatch = after("default", findByNameProxy("UserSettingsOverviewWrapper", false), (_a, ret) => {
|
||||
const UserSettingsOverview = findInReactTree(ret.props.children, n => n.type?.name === "UserSettingsOverview");
|
||||
|
||||
unpatches.push(after("renderSupportAndAcknowledgements", UserSettingsOverview.type.prototype, (_args, { props: { children } }) => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { NavigationNative } from "@metro/common";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { ErrorBoundary } from "@ui/components";
|
||||
import { RowConfig } from "@ui/settings";
|
||||
|
||||
const tabsNavigationRef = findByProps("getRootNavigationRef");
|
||||
const tabsNavigationRef = findByPropsProxy("getRootNavigationRef");
|
||||
|
||||
export const CustomPageRenderer = React.memo(() => {
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { after } from "@lib/api/patcher";
|
||||
import { findInReactTree } from "@lib/utils";
|
||||
import { i18n } from "@metro/common";
|
||||
import { findByName, findByProps } from "@metro/utils";
|
||||
import { findByNameProxy, findByPropsProxy } from "@metro/utils";
|
||||
import { registeredSections } from "@ui/settings";
|
||||
import { after } from "spitroast";
|
||||
|
||||
import { CustomPageRenderer, wrapOnPress } from "./shared";
|
||||
|
||||
const settingConstants = findByProps("SETTING_RENDERER_CONFIG");
|
||||
const SettingsOverviewScreen = findByName("SettingsOverviewScreen", false);
|
||||
const settingConstants = findByPropsProxy("SETTING_RENDERER_CONFIG");
|
||||
const SettingsOverviewScreen = findByNameProxy("SettingsOverviewScreen", false);
|
||||
|
||||
function useIsFirstRender() {
|
||||
let firstRender = false;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { isSemanticColor, resolveSemanticColor } from "@ui/color";
|
||||
import { CompatfulRedesign } from "@ui/components/discord/Redesign";
|
||||
import { DiscordTextStyles } from "@ui/types";
|
||||
|
|
@ -6,7 +6,7 @@ import { ImageStyle, StyleSheet, TextStyle, ViewStyle } from "react-native";
|
|||
|
||||
type NamedStyles<T> = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };
|
||||
|
||||
export const { TextStyleSheet } = findByProps("TextStyleSheet") as unknown as {
|
||||
export const { TextStyleSheet } = findByPropsProxy("TextStyleSheet") as unknown as {
|
||||
TextStyleSheet: { [key in DiscordTextStyles]: TextStyle; };
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { Strings } from "@core/i18n";
|
||||
import { getAssetIDByName } from "@lib/api/assets";
|
||||
import { toasts } from "@metro/common";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { findByPropsProxy } from "@metro/utils";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
const { uuid4 } = findByProps("uuid4");
|
||||
const { uuid4 } = findByPropsProxy("uuid4");
|
||||
|
||||
export const showToast = (content: string, asset?: number) => toasts.open({
|
||||
// ? In build 182205/44707, Discord changed their toasts, source is no longer used, rather icon, and a key is needed.
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ Object.getOwnPropertyNames(Reflect).forEach(fnName => {
|
|||
* @returns A proxy that will call the factory function only when needed
|
||||
* @example const ChannelStore = proxyLazy(() => findByProps("getChannelId"));
|
||||
*/
|
||||
export function proxyLazy<T>(factory: () => T, fallback?: any): T {
|
||||
const dummy = function () { } as any;
|
||||
export function proxyLazy<T>(factory: () => T, asFunction = true): T {
|
||||
const dummy = asFunction ? function () { } as any : {};
|
||||
dummy[factorySymbol] = () => {
|
||||
return (dummy[cacheSymbol] ??= factory()) ?? fallback;
|
||||
return dummy[cacheSymbol] ??= factory();
|
||||
};
|
||||
|
||||
return new Proxy(dummy, lazyHandler) as any;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { findByName } from "@metro/utils";
|
||||
import { findByNameProxy } from "@metro/utils";
|
||||
|
||||
export type LoggerFunction = (...messages: any[]) => void;
|
||||
export interface Logger {
|
||||
|
|
@ -11,5 +11,5 @@ export interface Logger {
|
|||
verbose: LoggerFunction;
|
||||
}
|
||||
|
||||
export const logModule = findByName("Logger");
|
||||
export const logModule = findByNameProxy("Logger");
|
||||
export const logger: Logger = new logModule("Bunny");
|
||||
|
|
|
|||
|
|
@ -10,11 +10,20 @@ export function throttle<T extends (...args: any[]) => any>(
|
|||
time: number = 200
|
||||
): T {
|
||||
let active = false;
|
||||
let calledWhileActive = false;
|
||||
return function (this: ReturnType<T>, ...args: Parameters<T>) {
|
||||
if (!active) {
|
||||
active = true;
|
||||
funct.apply(this, args);
|
||||
setTimeout(() => { active = false; }, time);
|
||||
setTimeout(() => {
|
||||
active = false;
|
||||
if (calledWhileActive) {
|
||||
calledWhileActive = false;
|
||||
funct.apply(this, args);
|
||||
}
|
||||
}, time);
|
||||
} else {
|
||||
calledWhileActive = true;
|
||||
}
|
||||
} as T;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ClientInfoManager, MMKVManager } from "@lib/api/native/modules";
|
||||
import { throttle } from "@lib/utils/throttle";
|
||||
|
||||
const CACHE_VERSION = 12;
|
||||
const CACHE_VERSION = 18;
|
||||
const BUNNY_METRO_CACHE_KEY = `__bunny_metro_cache_key_v${CACHE_VERSION}__`;
|
||||
|
||||
export enum ExportsFlags {
|
||||
|
|
@ -17,27 +17,18 @@ type ModulesMap = {
|
|||
[id: number]: number;
|
||||
};
|
||||
|
||||
interface MetroCacheStore {
|
||||
_v: typeof CACHE_VERSION;
|
||||
_buildNumber: number;
|
||||
exportsIndex: Record<string, [number, number]>;
|
||||
findIndex: Record<string, ModulesMap | undefined>;
|
||||
polyfillIndex: Record<string, ModulesMap | undefined>;
|
||||
assetsIndex: Record<string, number>;
|
||||
}
|
||||
|
||||
let _metroCache = null as unknown as MetroCacheStore;
|
||||
let _metroCache = null as unknown as ReturnType<typeof buildInitCache>;
|
||||
|
||||
export const getMetroCache = window.__getMetroCache = () => _metroCache;
|
||||
|
||||
function buildInitCache() {
|
||||
_metroCache = {
|
||||
const cache = {
|
||||
_v: CACHE_VERSION,
|
||||
_buildNumber: ClientInfoManager.Build,
|
||||
exportsIndex: {},
|
||||
findIndex: {},
|
||||
polyfillIndex: {},
|
||||
assetsIndex: {}
|
||||
_buildNumber: ClientInfoManager.Build as number,
|
||||
exportsIndex: {} as Record<string, [number, number]>,
|
||||
findIndex: {} as Record<string, ModulesMap | undefined>,
|
||||
polyfillIndex: {} as Record<string, ModulesMap | undefined>,
|
||||
assetsIndex: {} as Record<string, number>
|
||||
} as const;
|
||||
|
||||
// Make sure all assets are cached. Delay by a second
|
||||
|
|
@ -47,6 +38,9 @@ function buildInitCache() {
|
|||
require("@metro/modules").requireModule(id);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
_metroCache = cache;
|
||||
return cache;
|
||||
}
|
||||
|
||||
export async function initMetroCache() {
|
||||
|
|
@ -64,7 +58,9 @@ export async function initMetroCache() {
|
|||
}
|
||||
}
|
||||
|
||||
const saveCache = throttle(() => MMKVManager.setItem(BUNNY_METRO_CACHE_KEY, JSON.stringify(_metroCache)));
|
||||
const saveCache = throttle(() => {
|
||||
MMKVManager.setItem(BUNNY_METRO_CACHE_KEY, JSON.stringify(_metroCache));
|
||||
});
|
||||
|
||||
function extractExportsFlags(moduleExports: any) {
|
||||
if (!moduleExports) return 0;
|
||||
|
|
@ -80,13 +76,17 @@ function extractExportsFlags(moduleExports: any) {
|
|||
}
|
||||
|
||||
export function indexExportsFlags(moduleId: number, moduleExports: any) {
|
||||
_metroCache.exportsIndex[moduleId] ??= moduleExports.default && moduleExports.__esModule
|
||||
const flags: [number, number] = moduleExports.default && moduleExports.__esModule
|
||||
? [extractExportsFlags(moduleExports), extractExportsFlags(moduleExports.default)]
|
||||
: [extractExportsFlags(moduleExports), 0];
|
||||
|
||||
if (flags[0] !== ExportsFlags.EXISTS && flags[1] !== ExportsFlags.EXISTS) {
|
||||
_metroCache.exportsIndex[moduleId] = flags;
|
||||
}
|
||||
}
|
||||
|
||||
export function indexBlacklistFlag(id: number) {
|
||||
_metroCache.exportsIndex[id] ??= [ExportsFlags.BLACKLISTED, ExportsFlags.BLACKLISTED];
|
||||
_metroCache.exportsIndex[id] ??= [ExportsFlags.BLACKLISTED, 0];
|
||||
}
|
||||
|
||||
export function getCacherForUniq(uniq: string, allFind: boolean) {
|
||||
|
|
@ -95,6 +95,7 @@ export function getCacherForUniq(uniq: string, allFind: boolean) {
|
|||
return {
|
||||
cacheId(moduleId: number, exports: any) {
|
||||
indexObject ??= _metroCache.findIndex[uniq] ??= {};
|
||||
// TODO: A log here perhaps
|
||||
indexObject[moduleId] ??= extractExportsFlags(exports);
|
||||
|
||||
saveCache();
|
||||
|
|
|
|||
|
|
@ -1,38 +1,38 @@
|
|||
import { Dispatcher } from "@metro/types";
|
||||
import { findByFilePath, findByProps } from "@metro/utils";
|
||||
import { findByFilePath, findByPropsProxy } from "@metro/utils";
|
||||
|
||||
// Discord
|
||||
export const constants = findByProps("Fonts", "Permissions");
|
||||
export const channels = findByProps("getVoiceChannelId");
|
||||
export const i18n = findByProps("Messages");
|
||||
export const url = findByProps("openURL", "openDeeplink");
|
||||
export const clipboard = findByProps("setString", "getString", "hasString") as typeof import("@react-native-clipboard/clipboard").default;
|
||||
export const assets = findByProps("registerAsset");
|
||||
export const invites = findByProps("acceptInviteAndTransitionToInviteChannel");
|
||||
export const commands = findByProps("getBuiltInCommands");
|
||||
export const navigation = findByProps("pushLazy");
|
||||
export const constants = findByPropsProxy("Fonts", "Permissions");
|
||||
export const channels = findByPropsProxy("getVoiceChannelId");
|
||||
export const i18n = findByPropsProxy("Messages");
|
||||
export const url = findByPropsProxy("openURL", "openDeeplink");
|
||||
export const clipboard = findByPropsProxy("setString", "getString", "hasString") as typeof import("@react-native-clipboard/clipboard").default;
|
||||
export const assets = findByPropsProxy("registerAsset");
|
||||
export const invites = findByPropsProxy("acceptInviteAndTransitionToInviteChannel");
|
||||
export const commands = findByPropsProxy("getBuiltInCommands");
|
||||
export const navigation = findByPropsProxy("pushLazy");
|
||||
export const toasts = findByFilePath("modules/toast/native/ToastActionCreators.tsx").default;
|
||||
export const messageUtil = findByProps("sendBotMessage");
|
||||
export const navigationStack = findByProps("createStackNavigator");
|
||||
export const NavigationNative = findByProps("NavigationContainer");
|
||||
export const messageUtil = findByPropsProxy("sendBotMessage");
|
||||
export const navigationStack = findByPropsProxy("createStackNavigator");
|
||||
export const NavigationNative = findByPropsProxy("NavigationContainer");
|
||||
|
||||
// Flux
|
||||
export const Flux = findByProps("connectStores");
|
||||
export const FluxDispatcher = findByProps("_interceptors") as Dispatcher;
|
||||
export const Flux = findByPropsProxy("connectStores");
|
||||
export const FluxDispatcher = findByPropsProxy("_interceptors") as Dispatcher;
|
||||
|
||||
// React
|
||||
export const React = window.React = findByProps("createElement") as typeof import("react");
|
||||
export const ReactNative = window.ReactNative = findByProps("AppRegistry") as typeof import("react-native");
|
||||
export const React = window.React = findByPropsProxy("createElement") as typeof import("react");
|
||||
export const ReactNative = window.ReactNative = findByPropsProxy("AppRegistry") as typeof import("react-native");
|
||||
|
||||
// Moment
|
||||
export const moment = findByProps("isMoment") as typeof import("moment");
|
||||
export const moment = findByPropsProxy("isMoment") as typeof import("moment");
|
||||
|
||||
// chroma.js
|
||||
export const chroma = findByProps("brewer") as typeof import("chroma-js");
|
||||
export const chroma = findByPropsProxy("brewer") as typeof import("chroma-js");
|
||||
|
||||
// Lodash
|
||||
export const lodash = findByProps("forEachRight") as typeof import("lodash");
|
||||
export const lodash = findByPropsProxy("forEachRight") as typeof import("lodash");
|
||||
|
||||
// The node:util polyfill for RN
|
||||
// TODO: Find types for this
|
||||
export const util = findByProps("inspect", "isNullOrUndefined");
|
||||
export const util = findByPropsProxy("inspect", "isNullOrUndefined");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import { getCacherForUniq } from "./caches";
|
|||
import { getModules, requireModule } from "./modules";
|
||||
import { FilterFn } from "./types";
|
||||
|
||||
function testExports<A extends unknown[]>(moduleExports: any, moduleId: number, filter: FilterFn<A>) {
|
||||
export function filterExports<A extends unknown[]>(
|
||||
moduleExports: any,
|
||||
moduleId: number, filter:
|
||||
FilterFn<A>
|
||||
): [any, boolean] | [void, false] {
|
||||
if (moduleExports.default && moduleExports.__esModule && filter(moduleExports.default, moduleId, true)) {
|
||||
return [filter.raw ? moduleExports : moduleExports.default, !filter.raw];
|
||||
}
|
||||
|
|
@ -11,7 +15,7 @@ function testExports<A extends unknown[]>(moduleExports: any, moduleId: number,
|
|||
return [moduleExports, false];
|
||||
}
|
||||
|
||||
return [];
|
||||
return [undefined, false];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -22,7 +26,7 @@ export function findModule<A extends unknown[]>(filter: FilterFn<A>) {
|
|||
const { cacheId, finish } = getCacherForUniq(filter.uniq, false);
|
||||
|
||||
for (const [id, moduleExports] of getModules(filter.uniq, false)) {
|
||||
const [testedExports, defaultExp] = testExports(moduleExports, id, filter);
|
||||
const [testedExports, defaultExp] = filterExports(moduleExports, id, filter);
|
||||
if (testedExports !== undefined) {
|
||||
cacheId(id, testedExports);
|
||||
return [id, defaultExp];
|
||||
|
|
@ -60,7 +64,7 @@ export function findAllModule<A extends unknown[]>(filter: FilterFn<A>) {
|
|||
const foundExports: [id: number, defaultExp: boolean][] = [];
|
||||
|
||||
for (const [id, moduleExports] of getModules(filter.uniq, true)) {
|
||||
const [testedExports, defaultExp] = testExports(moduleExports, id, filter);
|
||||
const [testedExports, defaultExp] = filterExports(moduleExports, id, filter);
|
||||
if (testedExports !== undefined) {
|
||||
foundExports.push([id, defaultExp]);
|
||||
cacheId(id, testedExports);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { before, instead } from "spitroast";
|
|||
export const metroModules: Metro.ModuleList = window.modules;
|
||||
const metroRequire: Metro.Require = window.__r;
|
||||
|
||||
// eslint-disable-next-line func-call-spacing
|
||||
const moduleSubscriptions = new Map<number, Set<() => void>>();
|
||||
const blacklistedIds = new Set<String>();
|
||||
const noopHandler = () => undefined;
|
||||
const functionToString = Function.prototype.toString;
|
||||
|
|
@ -61,7 +63,10 @@ function blacklistModule(id: string) {
|
|||
}
|
||||
|
||||
function isBadExports(exports: any) {
|
||||
return !exports || exports === window || exports["<!@ pylix was here :fuyusquish: !@>"] === null;
|
||||
return !exports
|
||||
|| exports === window
|
||||
|| exports["<!@ pylix was here :fuyusquish: !@>"] === null
|
||||
|| (exports.__proto__ === Object.prototype && Reflect.ownKeys(exports).length === 0);
|
||||
}
|
||||
|
||||
function onModuleRequire(moduleExports: any, id: Metro.ModuleID) {
|
||||
|
|
@ -135,12 +140,37 @@ function onModuleRequire(moduleExports: any, id: Metro.ModuleID) {
|
|||
moduleExports.locale(origLocale);
|
||||
});
|
||||
}
|
||||
|
||||
moduleSubscriptions.get(Number(id))?.forEach(s => s());
|
||||
}
|
||||
|
||||
export function getImportingModuleId() {
|
||||
return _importingModuleId;
|
||||
}
|
||||
|
||||
export function subscribeModule(id: number, cb: () => void): () => void {
|
||||
const subs = moduleSubscriptions.get(id) ?? new Set();
|
||||
|
||||
const deleteIfEmpty = () => {
|
||||
subs.delete(cb);
|
||||
if (moduleSubscriptions.get(id)?.size === 0) {
|
||||
moduleSubscriptions.delete(id);
|
||||
}
|
||||
};
|
||||
const wrappedCb = () => {
|
||||
cb();
|
||||
deleteIfEmpty();
|
||||
};
|
||||
|
||||
subs.add(wrappedCb);
|
||||
moduleSubscriptions.set(id, subs);
|
||||
|
||||
return () => {
|
||||
subs.delete(wrappedCb);
|
||||
deleteIfEmpty();
|
||||
};
|
||||
}
|
||||
|
||||
export function requireModule(id: Metro.ModuleID) {
|
||||
if (!metroModules[0]?.isInitialized) metroRequire(0);
|
||||
if (blacklistedIds.has(String(id))) return undefined;
|
||||
|
|
|
|||
69
src/metro/proxy.ts
Normal file
69
src/metro/proxy.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { proxyLazy } from "@lib/utils/lazy";
|
||||
|
||||
import { getMetroCache } from "./caches";
|
||||
import { filterExports, findExports } from "./finders";
|
||||
import { requireModule, subscribeModule } from "./modules";
|
||||
import type { FilterFn } from "./types";
|
||||
|
||||
// TODO: "Find info" sounds too meh
|
||||
const findInfoSym = Symbol.for("bunny.metro.findInfo");
|
||||
|
||||
const proxyInfoMap = new WeakMap<{}, FindProxyInfo<any[]>>();
|
||||
|
||||
interface FindProxyInfo<A extends unknown[]> {
|
||||
filter: FilterFn<A>;
|
||||
indexed: boolean;
|
||||
moduleId: number | undefined;
|
||||
subscribe(cb: (exports: any) => void): () => void;
|
||||
unproxy(): any;
|
||||
get cache(): any;
|
||||
}
|
||||
|
||||
function getIndexedSingleFind<A extends unknown[]>(filter: FilterFn<A>) {
|
||||
const id = Object.keys(getMetroCache().findIndex[filter.uniq]!).filter(k => k !== "_")[0];
|
||||
return id ? Number(id) : void 0;
|
||||
}
|
||||
|
||||
function subscribeModuleForFind(proxy: any, callback: (exports: any) => void) {
|
||||
const info = getFindProxyInfo(proxy);
|
||||
if (!info) throw new Error("Subscribing a module for non-proxy-find");
|
||||
if (!info.indexed) throw new Error("Attempting to subscribe to a non-indexed find");
|
||||
|
||||
return subscribeModule(info.moduleId!, () => {
|
||||
const [exp] = filterExports(
|
||||
requireModule(info.moduleId!),
|
||||
info.moduleId!,
|
||||
info.filter
|
||||
);
|
||||
callback(exp);
|
||||
});
|
||||
}
|
||||
|
||||
export function getFindProxyInfo<A extends unknown[]>(proxy: any): FindProxyInfo<A> | void {
|
||||
return proxyInfoMap.get(proxy) as unknown as FindProxyInfo<A>;
|
||||
}
|
||||
|
||||
export function createFindProxy<A extends unknown[]>(filter: FilterFn<A>) {
|
||||
let cache: any = undefined;
|
||||
|
||||
const moduleId = getIndexedSingleFind(filter);
|
||||
const info: FindProxyInfo<A> = {
|
||||
filter,
|
||||
indexed: !!moduleId,
|
||||
moduleId,
|
||||
subscribe(cb: (exports: any) => void) {
|
||||
return subscribeModuleForFind(proxy, cb);
|
||||
},
|
||||
get cache() {
|
||||
return cache;
|
||||
},
|
||||
unproxy() {
|
||||
return cache ??= findExports(filter);
|
||||
}
|
||||
};
|
||||
|
||||
const proxy = proxyLazy(() => cache ??= findExports(filter));
|
||||
proxyInfoMap.set(proxy, info as FindProxyInfo<any>);
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { proxyLazy } from "@lib/utils/lazy";
|
||||
|
||||
import { byDisplayName, byFilePath, byName, byProps, byStoreName, byTypeName } from "./filters";
|
||||
import { findAllExports, findExports } from "./finders";
|
||||
import { createFindProxy } from "./proxy";
|
||||
import { FilterCheckDef, FilterDefinition, ModuleExports } from "./types";
|
||||
|
||||
export function createFilterDefinition<A extends unknown[]>(
|
||||
|
|
@ -43,25 +43,24 @@ export function createSimpleFilter(
|
|||
}
|
||||
|
||||
export const findByProps = (...props: string[]) => findExports(byProps(...props));
|
||||
export const findByPropsLazy = (...props: string[]) => proxyLazy(() => findByProps(...props));
|
||||
export const findByPropsProxy = (...props: string[]) => createFindProxy(byProps(...props));
|
||||
export const findByPropsAll = (...props: string[]) => findAllExports(byProps(...props));
|
||||
|
||||
export const findByName = (name: string, expDefault = true) => findExports(expDefault ? byName(name) : byName.byRaw(name));
|
||||
export const findByNameLazy = (name: string, expDefault = true) => proxyLazy(() => findByName(name, expDefault));
|
||||
export const findByNameProxy = (name: string, expDefault = true) => createFindProxy(expDefault ? byName(name) : byName.byRaw(name));
|
||||
export const findByNameAll = (name: string, expDefault = true) => findAllExports(expDefault ? byName(name) : byName.byRaw(name));
|
||||
|
||||
export const findByDisplayName = (name: string, expDefault = true) => findExports(expDefault ? byDisplayName(name) : byDisplayName.byRaw(name));
|
||||
export const findByDisplayNameLazy = (name: string, expDefault = true) => proxyLazy(() => findByDisplayName(name, expDefault));
|
||||
export const findByDisplayNameProxy = (name: string, expDefault = true) => createFindProxy(expDefault ? byDisplayName(name) : byDisplayName.byRaw(name));
|
||||
export const findByDisplayNameAll = (name: string, expDefault = true) => findAllExports(expDefault ? byDisplayName(name) : byDisplayName.byRaw(name));
|
||||
|
||||
export const findByTypeName = (name: string, expDefault = true) => findExports(expDefault ? byTypeName(name) : byTypeName.byRaw(name));
|
||||
export const findByTypeNameLazy = (name: string, expDefault = true) => proxyLazy(() => findByTypeName(name, expDefault));
|
||||
export const findByTypeNameProxy = (name: string, expDefault = true) => createFindProxy(expDefault ? byTypeName(name) : byTypeName.byRaw(name));
|
||||
export const findByTypeNameAll = (name: string, expDefault = true) => findAllExports(expDefault ? byTypeName(name) : byTypeName.byRaw(name));
|
||||
|
||||
export const findByStoreName = (name: string) => findExports(byStoreName(name));
|
||||
export const findByStoreNameLazy = (name: string) => proxyLazy(() => findByStoreName(name));
|
||||
export const findByStoreNameProxy = (name: string) => createFindProxy(byStoreName(name));
|
||||
|
||||
export const findByFilePath = (path: string) => findExports(byFilePath(path));
|
||||
export const findByFilePathLazy = (path: string) => proxyLazy(() => findByFilePath(path));
|
||||
|
||||
export const findByFilePathProxy = (path: string) => createFindProxy(byFilePath(path));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue