From 820a63cd370dc9d48cce4d3c18ad17e461c61193 Mon Sep 17 00:00:00 2001
From: John Shedletsky <113091876+ui0ppk@users.noreply.github.com>
Date: Mon, 1 Apr 2024 14:44:35 +0000
Subject: [PATCH] Delete src directory
---
src/def.d.ts | 544 ------------------
src/entry.ts | 26 -
src/index.ts | 47 --
src/lib/commands.ts | 33 --
src/lib/constants.ts | 8 -
src/lib/debug.ts | 135 -----
src/lib/emitter.ts | 33 --
src/lib/fixes.ts | 34 --
src/lib/logger.ts | 7 -
src/lib/metro/common.ts | 71 ---
src/lib/metro/filters.ts | 81 ---
src/lib/native.ts | 11 -
src/lib/patcher.ts | 4 -
src/lib/plugins.ts | 144 -----
src/lib/polyfills.ts | 5 -
src/lib/preinit.ts | 45 --
src/lib/settings.ts | 5 -
src/lib/storage/backends.ts | 77 ---
src/lib/storage/index.ts | 125 ----
src/lib/themes.ts | 236 --------
src/lib/utils/findInReactTree.ts | 6 -
src/lib/utils/findInTree.ts | 45 --
src/lib/utils/index.ts | 7 -
src/lib/utils/safeFetch.ts | 17 -
src/lib/utils/unfreeze.ts | 6 -
src/lib/utils/without.ts | 5 -
src/lib/windowObject.ts | 49 --
src/ui/alerts.ts | 27 -
src/ui/assets.ts | 26 -
src/ui/color.ts | 17 -
src/ui/components/Codeblock.tsx | 28 -
src/ui/components/ErrorBoundary.tsx | 23 -
src/ui/components/InputAlert.tsx | 49 --
src/ui/components/Search.tsx | 32 --
src/ui/components/Summary.tsx | 27 -
src/ui/components/TabulatedScreen.tsx | 30 -
src/ui/components/index.ts | 26 -
src/ui/quickInstall/forumPost.tsx | 58 --
src/ui/quickInstall/index.ts | 11 -
src/ui/quickInstall/url.tsx | 81 ---
src/ui/safeMode.tsx | 131 -----
src/ui/settings/components/AddonPage.tsx | 41 --
src/ui/settings/components/AssetDisplay.tsx | 22 -
src/ui/settings/components/Card.tsx | 116 ----
src/ui/settings/components/InstallButton.tsx | 36 --
src/ui/settings/components/PluginCard.tsx | 125 ----
.../settings/components/SettingsSection.tsx | 33 --
src/ui/settings/components/ThemeCard.tsx | 91 ---
src/ui/settings/components/Version.tsx | 26 -
src/ui/settings/data.tsx | 138 -----
src/ui/settings/index.ts | 11 -
src/ui/settings/pages/AssetBrowser.tsx | 28 -
src/ui/settings/pages/Developer.tsx | 166 ------
src/ui/settings/pages/General.tsx | 92 ---
src/ui/settings/pages/Plugins.tsx | 18 -
src/ui/settings/pages/Secret.tsx | 10 -
src/ui/settings/pages/Themes.tsx | 45 --
src/ui/settings/patches/panels.tsx | 39 --
src/ui/settings/patches/you.tsx | 106 ----
src/ui/shared.ts | 8 -
src/ui/toasts.ts | 13 -
61 files changed, 3536 deletions(-)
delete mode 100644 src/def.d.ts
delete mode 100644 src/entry.ts
delete mode 100644 src/index.ts
delete mode 100644 src/lib/commands.ts
delete mode 100644 src/lib/constants.ts
delete mode 100644 src/lib/debug.ts
delete mode 100644 src/lib/emitter.ts
delete mode 100644 src/lib/fixes.ts
delete mode 100644 src/lib/logger.ts
delete mode 100644 src/lib/metro/common.ts
delete mode 100644 src/lib/metro/filters.ts
delete mode 100644 src/lib/native.ts
delete mode 100644 src/lib/patcher.ts
delete mode 100644 src/lib/plugins.ts
delete mode 100644 src/lib/polyfills.ts
delete mode 100644 src/lib/preinit.ts
delete mode 100644 src/lib/settings.ts
delete mode 100644 src/lib/storage/backends.ts
delete mode 100644 src/lib/storage/index.ts
delete mode 100644 src/lib/themes.ts
delete mode 100644 src/lib/utils/findInReactTree.ts
delete mode 100644 src/lib/utils/findInTree.ts
delete mode 100644 src/lib/utils/index.ts
delete mode 100644 src/lib/utils/safeFetch.ts
delete mode 100644 src/lib/utils/unfreeze.ts
delete mode 100644 src/lib/utils/without.ts
delete mode 100644 src/lib/windowObject.ts
delete mode 100644 src/ui/alerts.ts
delete mode 100644 src/ui/assets.ts
delete mode 100644 src/ui/color.ts
delete mode 100644 src/ui/components/Codeblock.tsx
delete mode 100644 src/ui/components/ErrorBoundary.tsx
delete mode 100644 src/ui/components/InputAlert.tsx
delete mode 100644 src/ui/components/Search.tsx
delete mode 100644 src/ui/components/Summary.tsx
delete mode 100644 src/ui/components/TabulatedScreen.tsx
delete mode 100644 src/ui/components/index.ts
delete mode 100644 src/ui/quickInstall/forumPost.tsx
delete mode 100644 src/ui/quickInstall/index.ts
delete mode 100644 src/ui/quickInstall/url.tsx
delete mode 100644 src/ui/safeMode.tsx
delete mode 100644 src/ui/settings/components/AddonPage.tsx
delete mode 100644 src/ui/settings/components/AssetDisplay.tsx
delete mode 100644 src/ui/settings/components/Card.tsx
delete mode 100644 src/ui/settings/components/InstallButton.tsx
delete mode 100644 src/ui/settings/components/PluginCard.tsx
delete mode 100644 src/ui/settings/components/SettingsSection.tsx
delete mode 100644 src/ui/settings/components/ThemeCard.tsx
delete mode 100644 src/ui/settings/components/Version.tsx
delete mode 100644 src/ui/settings/data.tsx
delete mode 100644 src/ui/settings/index.ts
delete mode 100644 src/ui/settings/pages/AssetBrowser.tsx
delete mode 100644 src/ui/settings/pages/Developer.tsx
delete mode 100644 src/ui/settings/pages/General.tsx
delete mode 100644 src/ui/settings/pages/Plugins.tsx
delete mode 100644 src/ui/settings/pages/Secret.tsx
delete mode 100644 src/ui/settings/pages/Themes.tsx
delete mode 100644 src/ui/settings/patches/panels.tsx
delete mode 100644 src/ui/settings/patches/you.tsx
delete mode 100644 src/ui/shared.ts
delete mode 100644 src/ui/toasts.ts
diff --git a/src/def.d.ts b/src/def.d.ts
deleted file mode 100644
index 5fe8b6a..0000000
--- a/src/def.d.ts
+++ /dev/null
@@ -1,544 +0,0 @@
-import * as _spitroast from "spitroast";
-import _React from "react";
-import _RN from "react-native";
-import _Clipboard from "@react-native-clipboard/clipboard";
-import _moment from "moment";
-import _chroma from "chroma-js";
-import _lodash from "lodash";
-
-type MetroModules = { [id: number]: any };
-
-// Component types
-interface SummaryProps {
- label: string;
- icon?: string;
- noPadding?: boolean;
- noAnimation?: boolean;
- children: JSX.Element | JSX.Element[];
-}
-
-interface CodeblockProps {
- selectable?: boolean;
- style?: _RN.TextStyle;
- children?: string;
-}
-
-interface SearchProps {
- onChangeText?: (v: string) => void;
- placeholder?: string;
- style?: _RN.TextStyle;
-}
-
-interface ErrorBoundaryState {
- hasErr: boolean;
- errText?: string;
-}
-
-interface TabulatedScreenTab {
- id: string;
- title: string;
- render?: React.ComponentType;
- onPress?: (tab?: string) => void;
-}
-
-interface TabulatedScreenProps {
- tabs: TabulatedScreenTab[];
-}
-
-// Helper types for API functions
-type PropIntellisense
= Record
& Record;
-type PropsFinder = (...props: T[]) => PropIntellisense;
-type PropsFinderAll = (...props: T[]) => PropIntellisense[];
-
-type LoggerFunction = (...messages: any[]) => void;
-interface Logger {
- log: LoggerFunction;
- info: LoggerFunction;
- warn: LoggerFunction;
- error: LoggerFunction;
- time: LoggerFunction;
- trace: LoggerFunction;
- verbose: LoggerFunction;
-}
-
-type SearchTree = Record;
-type SearchFilter = (tree: SearchTree) => boolean;
-interface FindInTreeOptions {
- walkable?: string[];
- ignore?: string[];
- maxDepth?: number;
-}
-
-interface Asset {
- name: string;
- id: number;
-}
-
-export enum ButtonColors {
- BRAND = "brand",
- RED = "red",
- GREEN = "green",
- PRIMARY = "primary",
- TRANSPARENT = "transparent",
- GREY = "grey",
- LIGHTGREY = "lightgrey",
- WHITE = "white",
- LINK = "link"
-}
-
-interface ConfirmationAlertOptions {
- title?: string;
- content: string | JSX.Element | (string | JSX.Element)[];
- confirmText?: string;
- confirmColor?: ButtonColors;
- onConfirm: () => void;
- secondaryConfirmText?: string;
- onConfirmSecondary?: () => void;
- cancelText?: string;
- onCancel?: () => void;
- isDismissable?: boolean;
-}
-
-interface InputAlertProps {
- title?: string;
- confirmText?: string;
- confirmColor?: ButtonColors;
- onConfirm: (input: string) => (void | Promise);
- cancelText?: string;
- placeholder?: string;
- initialValue?: string;
- secureTextEntry?: boolean;
-}
-
-interface Author {
- name: string;
- id?: string;
-}
-
-// See https://github.com/vendetta-mod/polymanifest
-interface PluginManifest {
- name: string;
- description: string;
- authors: Author[];
- main: string;
- hash: string;
- // Vendor-specific field, contains our own data
- vendetta?: {
- icon?: string;
- };
-}
-
-interface Plugin {
- id: string;
- manifest: PluginManifest;
- enabled: boolean;
- update: boolean;
- js: string;
-}
-
-interface ThemeData {
- name: string;
- description?: string;
- authors?: Author[];
- spec: number;
- semanticColors?: Record;
- rawColors?: Record;
- background?: {
- url: string;
- blur?: number;
- /**
- * The alpha value of the background.
- * `CHAT_BACKGROUND` of semanticColors alpha value will be ignored when this is specified
- */
- alpha?: number;
- }
-}
-
-interface Theme {
- id: string;
- selected: boolean;
- data: ThemeData;
-}
-
-interface Settings extends StorageObject {
- debuggerUrl: string;
- developerSettings: boolean;
- debugBridgeEnabled: boolean;
- rdtEnabled: boolean;
- errorBoundaryEnabled: boolean;
- inspectionDepth: number;
- safeMode?: {
- enabled: boolean;
- currentThemeId?: string;
- };
-}
-
-interface ApplicationCommand {
- description: string;
- name: string;
- options: ApplicationCommandOption[];
- execute: (args: any[], ctx: CommandContext) => CommandResult | void | Promise | Promise;
- id?: string;
- applicationId: string;
- displayName: string;
- displayDescription: string;
- inputType: ApplicationCommandInputType;
- type: ApplicationCommandType;
-}
-
-export enum ApplicationCommandInputType {
- BUILT_IN,
- BUILT_IN_TEXT,
- BUILT_IN_INTEGRATION,
- BOT,
- PLACEHOLDER,
-}
-
-interface ApplicationCommandOption {
- name: string;
- description: string;
- required?: boolean;
- type: ApplicationCommandOptionType;
- displayName: string;
- displayDescription: string;
-}
-
-export enum ApplicationCommandOptionType {
- SUB_COMMAND = 1,
- SUB_COMMAND_GROUP,
- STRING,
- INTEGER,
- BOOLEAN,
- USER,
- CHANNEL,
- ROLE,
- MENTIONABLE,
- NUMBER,
- ATTACHMENT,
-}
-
-export enum ApplicationCommandType {
- CHAT = 1,
- USER,
- MESSAGE,
-}
-
-interface CommandContext {
- channel: any;
- guild: any;
-}
-
-interface CommandResult {
- content: string;
- tts?: boolean;
-}
-
-interface RNConstants extends _RN.PlatformConstants {
- // Android
- Version: number;
- Release: string;
- Serial: string;
- Fingerprint: string;
- Model: string;
- Brand: string;
- Manufacturer: string;
- ServerHost?: string;
-
- // iOS
- forceTouchAvailable: boolean;
- interfaceIdiom: string;
- osVersion: string;
- systemName: string;
-}
-
-/**
- * A key-value storage based upon `SharedPreferences` on Android.
- *
- * These types are based on Android though everything should be the same between
- * platforms.
- */
-interface MMKVManager {
- /**
- * Get the value for the given `key`, or null
- * @param key The key to fetch
- */
- getItem: (key: string) => Promise;
- /**
- * Deletes the value for the given `key`
- * @param key The key to delete
- */
- removeItem: (key: string) => void;
- /**
- * Sets the value of `key` to `value`
- */
- setItem: (key: string, value: string) => void;
- /**
- * Goes through every item in storage and returns it, excluding the
- * keys specified in `exclude`.
- * @param exclude A list of items to exclude from result
- */
- refresh: (exclude: string[]) => Promise>;
- /**
- * You will be murdered if you use this function.
- * Clears ALL of Discord's settings.
- */
- clear: () => void;
-}
-
-interface FileManager {
- /**
- * @param path **Full** path to file
- */
- fileExists: (path: string) => Promise;
- /**
- * Allowed URI schemes on Android: `file://`, `content://` ([See here](https://developer.android.com/reference/android/content/ContentResolver#accepts-the-following-uri-schemes:_3))
- */
- getSize: (uri: string) => Promise;
- /**
- * @param path **Full** path to file
- * @param encoding Set to `base64` in order to encode response
- */
- readFile(path: string, encoding: "base64" | "utf8"): Promise;
- saveFileToGallery?(uri: string, fileName: string, fileType: "PNG" | "JPEG"): Promise;
- /**
- * Beware! This function has differing functionality on iOS and Android.
- * @param storageDir Either `cache` or `documents`.
- * @param path Path in `storageDir`, parents are recursively created.
- * @param data The data to write to the file
- * @param encoding Set to `base64` if `data` is base64 encoded.
- * @returns Promise that resolves to path of the file once it got written
- */
- writeFile(storageDir: "cache" | "documents", path: string, data: string, encoding: "base64" | "utf8"): Promise;
- removeFile(storageDir: "cache" | "documents", path: string): Promise;
- getConstants: () => {
- /**
- * The path the `documents` storage dir (see {@link writeFile}) represents.
- */
- DocumentsDirPath: string;
- CacheDirPath: string;
- };
- /**
- * Will apparently cease to exist some time in the future so please use {@link getConstants} instead.
- * @deprecated
- */
- DocumentsDirPath: string;
-}
-
-type EmitterEvent = "SET" | "GET" | "DEL";
-
-interface EmitterListenerData {
- path: string[];
- value?: any;
-}
-
-type EmitterListener = (
- event: EmitterEvent,
- data: EmitterListenerData | any
-) => any;
-
-type EmitterListeners = Record>
-
-interface Emitter {
- listeners: EmitterListeners;
- on: (event: EmitterEvent, listener: EmitterListener) => void;
- off: (event: EmitterEvent, listener: EmitterListener) => void;
- once: (event: EmitterEvent, listener: EmitterListener) => void;
- emit: (event: EmitterEvent, data: EmitterListenerData) => void;
-}
-
-interface StorageObject> {
- [key: symbol]: keyof T | Emitter;
-}
-
-interface StorageBackend {
- get: () => unknown | Promise;
- set: (data: unknown) => void | Promise;
-}
-
-interface LoaderConfig extends StorageObject {
- customLoadUrl: {
- enabled: boolean;
- url: string;
- };
- loadReactDevTools: boolean;
-}
-
-interface LoaderIdentity {
- name: string;
- features: {
- loaderConfig?: boolean;
- devtools?: {
- prop: string;
- version: string;
- },
- themes?: {
- prop: string;
- }
- }
-}
-
-interface DiscordStyleSheet {
- [index: string]: any,
- createStyles: >(sheet: T | (() => T)) => () => T;
- createThemedStyleSheet: typeof import("react-native").StyleSheet.create;
-}
-
-interface VendettaObject {
- patcher: {
- after: typeof _spitroast.after;
- before: typeof _spitroast.before;
- instead: typeof _spitroast.instead;
- };
- metro: {
- find: (filter: (m: any) => boolean) => any;
- findAll: (filter: (m: any) => boolean) => any[];
- findByProps: PropsFinder;
- findByPropsAll: PropsFinderAll;
- findByName: (name: string, defaultExp?: boolean) => any;
- findByNameAll: (name: string, defaultExp?: boolean) => any[];
- findByDisplayName: (displayName: string, defaultExp?: boolean) => any;
- findByDisplayNameAll: (displayName: string, defaultExp?: boolean) => any[];
- findByTypeName: (typeName: string, defaultExp?: boolean) => any;
- findByTypeNameAll: (typeName: string, defaultExp?: boolean) => any[];
- findByStoreName: (name: string) => any;
- common: {
- constants: PropIntellisense<"Fonts" | "Permissions">;
- channels: PropIntellisense<"getVoiceChannelId">;
- i18n: PropIntellisense<"Messages">;
- url: PropIntellisense<"openURL">;
- toasts: PropIntellisense<"open" | "close">;
- stylesheet: DiscordStyleSheet;
- clipboard: typeof _Clipboard;
- assets: PropIntellisense<"registerAsset">;
- invites: PropIntellisense<"acceptInviteAndTransitionToInviteChannel">;
- commands: PropIntellisense<"getBuiltInCommands">;
- navigation: PropIntellisense<"pushLazy">;
- navigationStack: PropIntellisense<"createStackNavigator">;
- NavigationNative: PropIntellisense<"NavigationContainer">;
- // You may ask: "Why not just install Flux's types?"
- // Answer: Discord have a (presumably proprietary) fork. It's wildly different.
- Flux: PropIntellisense<"connectStores">;
- FluxDispatcher: PropIntellisense<"_currentDispatchActionType">;
- React: typeof _React;
- ReactNative: typeof _RN;
- moment: typeof _moment;
- chroma: typeof _chroma;
- lodash: typeof _lodash;
- util: PropIntellisense<"inspect" | "isNullOrUndefined">;
- };
- };
- constants: {
- DISCORD_SERVER: string;
- DISCORD_SERVER_ID: string;
- PLUGINS_CHANNEL_ID: string;
- THEMES_CHANNEL_ID: string;
- GITHUB: string;
- PROXY_PREFIX: string;
- HTTP_REGEX: RegExp;
- HTTP_REGEX_MULTI: RegExp;
- };
- utils: {
- findInReactTree: (tree: SearchTree, filter: SearchFilter) => any;
- findInTree: (tree: SearchTree, filter: SearchFilter, options: FindInTreeOptions) => any;
- safeFetch: (input: RequestInfo | URL, options?: RequestInit, timeout?: number) => Promise;
- unfreeze: (obj: object) => object;
- without: (object: O, ...keys: K) => Omit;
- };
- debug: {
- connectToDebugger: (url: string) => void;
- // TODO: Type output?
- getDebugInfo: () => void;
- }
- ui: {
- components: {
- // Discord
- Forms: PropIntellisense<"Form" | "FormSection">;
- General: PropIntellisense<"Button" | "Text" | "View">;
- Alert: _React.ComponentType;
- Button: _React.ComponentType & { Looks: any, Colors: ButtonColors, Sizes: any };
- HelpMessage: _React.ComponentType;
- SafeAreaView: typeof _RN.SafeAreaView;
- // Vendetta
- Summary: _React.ComponentType;
- ErrorBoundary: _React.ComponentType;
- Codeblock: _React.ComponentType;
- Search: _React.ComponentType;
- TabulatedScreen: _React.ComponentType
- }
- toasts: {
- showToast: (content: string, asset?: number) => void;
- };
- alerts: {
- showConfirmationAlert: (options: ConfirmationAlertOptions) => void;
- showCustomAlert: (component: _React.ComponentType, props: any) => void;
- showInputAlert: (options: InputAlertProps) => void;
- };
- assets: {
- all: Record;
- find: (filter: (a: any) => void) => Asset | null | undefined;
- getAssetByName: (name: string) => Asset;
- getAssetByID: (id: number) => Asset;
- getAssetIDByName: (name: string) => number;
- };
- // TODO: Make a vain attempt to type these
- semanticColors: Record;
- rawColors: Record;
- };
- plugins: {
- plugins: Record;
- fetchPlugin: (id: string) => Promise;
- installPlugin: (id: string, enabled?: boolean) => Promise;
- startPlugin: (id: string) => Promise;
- stopPlugin: (id: string, disable?: boolean) => void;
- removePlugin: (id: string) => void;
- getSettings: (id: string) => JSX.Element;
- };
- themes: {
- themes: Record;
- fetchTheme: (id: string, selected?: boolean) => Promise;
- installTheme: (id: string) => Promise;
- selectTheme: (id: string) => Promise;
- removeTheme: (id: string) => Promise;
- getCurrentTheme: () => Theme | null;
- updateThemes: () => Promise;
- };
- commands: {
- registerCommand: (command: ApplicationCommand) => () => void;
- };
- storage: {
- createProxy: (target: T) => { proxy: T, emitter: Emitter };
- useProxy: (storage: StorageObject) => T;
- createStorage: (backend: StorageBackend) => Promise>;
- wrapSync: >(store: T) => Awaited;
- awaitSyncWrapper: (store: any) => Promise;
- createMMKVBackend: (store: string) => StorageBackend;
- createFileBackend: (file: string) => StorageBackend;
- };
- settings: Settings;
- loader: {
- identity?: LoaderIdentity;
- config: LoaderConfig;
- };
- logger: Logger;
- version: string;
- unload: () => void;
-}
-
-interface VendettaPluginObject {
- id: string;
- manifest: PluginManifest;
- storage: Record;
-}
-
-declare global {
- type React = typeof _React;
- const __vendettaVersion: string;
-
- interface Window {
- [key: PropertyKey]: any;
- modules: MetroModules;
- vendetta: VendettaObject;
- React: typeof _React;
- __vendetta_loader?: LoaderIdentity;
- }
-}
diff --git a/src/entry.ts b/src/entry.ts
deleted file mode 100644
index 7f257e5..0000000
--- a/src/entry.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { ClientInfoManager } from "@lib/native";
-
-// This logs in the native logging implementation, e.g. logcat
-console.log("Binding your Discord app in chains...");
-
-// Make 'freeze' and 'seal' do nothing
-Object.freeze = Object;
-Object.seal = Object;
-
-// Prevent Discord from assigning the broken toString polyfill, so the app loads on 221.6+
-const origToString = Function.prototype.toString;
-Object.defineProperty(Function.prototype, "toString", {
- value: origToString,
- configurable: true,
- writable: false,
-});
-
-import(".").then((m) => m.default()).catch((e) => {
- console.log(e?.stack ?? e.toString());
- alert([
- "Failed to bind your Discord app!\n",
- `Build Number: ${ClientInfoManager.Build}`,
- `Bound: ${__vendettaVersion}`,
- e?.stack || e.toString(),
- ].join("\n"));
-});
diff --git a/src/index.ts b/src/index.ts
deleted file mode 100644
index 42489f7..0000000
--- a/src/index.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { ReactNative as RN } from "@metro/common";
-import { connectToDebugger, connectToRDT, patchLogHook } from "@lib/debug";
-import { awaitSyncWrapper } from "@lib/storage";
-import { patchCommands } from "@lib/commands";
-import { initPlugins } from "@lib/plugins";
-import { patchChatBackground } from "@lib/themes";
-import { patchAssets } from "@ui/assets";
-import initQuickInstall from "@ui/quickInstall";
-import initSafeMode from "@ui/safeMode";
-import initSettings from "@ui/settings";
-import initFixes from "@lib/fixes";
-import logger from "@lib/logger";
-import windowObject from "@lib/windowObject";
-import settings from "@lib/settings";
-
-export default async () => {
- // Load everything in parallel
- const unloads = await Promise.all([
- patchLogHook(),
- patchAssets(),
- patchCommands(),
- patchChatBackground(),
- initFixes(),
- initSafeMode(),
- initSettings(),
- initQuickInstall(),
- ]);
-
- // Wait for our settings proxy shit to be ready
- await awaitSyncWrapper(settings);
-
- // Assign window object
- window.vendetta = await windowObject(unloads);
-
- // Init developer tools
- if (settings.debugBridgeEnabled) connectToDebugger(settings.debuggerUrl);
- if (settings.rdtEnabled) connectToRDT();
-
- // Once done, load plugins
- unloads.push(await initPlugins());
-
- // Do the funny
- await RN.Image.prefetch("https://bound-mod.github.io/assets/images/fools.png");
-
- // We good :)
- logger.log("Your Discord app has been successfully bound in chains!");
-}
diff --git a/src/lib/commands.ts b/src/lib/commands.ts
deleted file mode 100644
index 4717d0b..0000000
--- a/src/lib/commands.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { ApplicationCommand, ApplicationCommandType } from "@types";
-import { commands as commandsModule } from "@metro/common";
-import { after } from "@lib/patcher";
-
-let commands: ApplicationCommand[] = [];
-
-export function patchCommands() {
- const unpatch = after("getBuiltInCommands", commandsModule, ([type], res: ApplicationCommand[]) => {
- if (type === ApplicationCommandType.CHAT) return res.concat(commands);
- });
-
- return () => {
- commands = [];
- unpatch();
- };
-}
-
-export function registerCommand(command: ApplicationCommand): () => void {
- // Get built in commands
- const builtInCommands = commandsModule.getBuiltInCommands(ApplicationCommandType.CHAT, true, false);
- builtInCommands.sort((a: ApplicationCommand, b: ApplicationCommand) => parseInt(b.id!) - parseInt(a.id!));
-
- const lastCommand = builtInCommands[builtInCommands.length - 1];
-
- // Override the new command's id to the last command id - 1
- command.id = (parseInt(lastCommand.id, 10) - 1).toString();
-
- // Add it to the commands array
- commands.push(command);
-
- // Return command id so it can be unregistered
- return () => (commands = commands.filter(({ id }) => id !== command.id));
-}
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
deleted file mode 100644
index e003a19..0000000
--- a/src/lib/constants.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export const DISCORD_SERVER = "https://discord.gg/n9QQ4XhhJP";
-export const DISCORD_SERVER_ID = "1015931589865246730";
-export const PLUGINS_CHANNEL_ID = "1091880384561684561";
-export const THEMES_CHANNEL_ID = "1091880434939482202";
-export const GITHUB = "https://github.com/bound-mod";
-export const PROXY_PREFIX = "https://vd-plugins.github.io/proxy";
-export const HTTP_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/;
-export const HTTP_REGEX_MULTI = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
diff --git a/src/lib/debug.ts b/src/lib/debug.ts
deleted file mode 100644
index 063e373..0000000
--- a/src/lib/debug.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import { RNConstants } from "@types";
-import { ReactNative as RN } from "@metro/common";
-import { after } from "@lib/patcher";
-import { getCurrentTheme, selectTheme } from "@lib/themes";
-import { ClientInfoManager, DeviceManager } from "@lib/native";
-import { getAssetIDByName } from "@ui/assets";
-import { showToast } from "@ui/toasts";
-import settings from "@lib/settings";
-import logger from "@lib/logger";
-export let socket: WebSocket;
-
-export function setSafeMode(state: boolean) {
- settings.safeMode = { ...settings.safeMode, enabled: state };
-
- if (window.__vendetta_loader?.features.themes) {
- if (getCurrentTheme()?.id) settings.safeMode!.currentThemeId = getCurrentTheme()!.id;
- if (settings.safeMode?.enabled) {
- selectTheme("default");
- } else if (settings.safeMode?.currentThemeId) {
- selectTheme(settings.safeMode?.currentThemeId);
- }
- }
-}
-
-export function connectToDebugger(url: string) {
- if (socket !== undefined && socket.readyState !== WebSocket.CLOSED) socket.close();
-
- if (!url) {
- showToast("Invalid debugger URL!", getAssetIDByName("Small"));
- return;
- }
-
- socket = new WebSocket(`ws://${url}`);
-
- socket.addEventListener("open", () => showToast("Connected to debugger.", getAssetIDByName("Check")));
- socket.addEventListener("message", (message: any) => {
- try {
- (0, eval)(message.data);
- } catch (e) {
- console.error(e);
- }
- });
-
- socket.addEventListener("error", (err: any) => {
- console.log(`Debugger error: ${err.message}`);
- showToast("An error occurred with the debugger connection!", getAssetIDByName("Small"));
- });
-}
-
-export const connectToRDT = () => window.__vendetta_rdc?.connectToDevTools({
- host: settings.debuggerUrl.split(":")?.[0],
- resolveRNStyle: RN.StyleSheet.flatten,
-});
-
-export function patchLogHook() {
- const unpatch = after("nativeLoggingHook", globalThis, (args) => {
- if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ message: args[0], level: args[1] }));
- logger.log(args[0]);
- });
-
- return () => {
- socket && socket.close();
- unpatch();
- }
-}
-
-export const versionHash: string = __vendettaVersion;
-
-export function getDebugInfo() {
- // Hermes
- const hermesProps = window.HermesInternal.getRuntimeProperties();
- const hermesVer = hermesProps["OSS Release Version"];
- const padding = "for RN ";
-
- // RN
- const PlatformConstants = RN.Platform.constants as RNConstants;
- const rnVer = PlatformConstants.reactNativeVersion;
-
- return {
- vendetta: {
- version: versionHash,
- loader: window.__vendetta_loader?.name.replaceAll("Vendetta", "Bound") /* <--- awful hack lmao */ ?? "Unknown",
- },
- discord: {
- version: ClientInfoManager.Version,
- build: ClientInfoManager.Build,
- },
- react: {
- version: React.version,
- nativeVersion: hermesVer.startsWith(padding) ? hermesVer.substring(padding.length) : `${rnVer.major}.${rnVer.minor}.${rnVer.patch}`,
- },
- hermes: {
- version: hermesVer,
- buildType: hermesProps["Build"],
- bytecodeVersion: hermesProps["Bytecode Version"],
- },
- ...RN.Platform.select(
- {
- android: {
- os: {
- name: "Android",
- version: PlatformConstants.Release,
- sdk: PlatformConstants.Version
- },
- },
- ios: {
- os: {
- name: PlatformConstants.systemName,
- version: PlatformConstants.osVersion
- },
- }
- }
- )!,
- ...RN.Platform.select(
- {
- android: {
- device: {
- manufacturer: PlatformConstants.Manufacturer,
- brand: PlatformConstants.Brand,
- model: PlatformConstants.Model,
- codename: DeviceManager.device
- }
- },
- ios: {
- device: {
- manufacturer: DeviceManager.deviceManufacturer,
- brand: DeviceManager.deviceBrand,
- model: DeviceManager.deviceModel,
- codename: DeviceManager.device
- }
- }
- }
- )!
- }
-}
diff --git a/src/lib/emitter.ts b/src/lib/emitter.ts
deleted file mode 100644
index 1bfbc08..0000000
--- a/src/lib/emitter.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Emitter, EmitterEvent, EmitterListener, EmitterListenerData, EmitterListeners } from "@types";
-
-export enum Events {
- GET = "GET",
- SET = "SET",
- DEL = "DEL",
-};
-
-export default function createEmitter(): Emitter {
- return {
- listeners: Object.values(Events).reduce((acc, val: string) => ((acc[val] = new Set()), acc), {}) as EmitterListeners,
-
- on(event: EmitterEvent, listener: EmitterListener) {
- if (!this.listeners[event].has(listener)) this.listeners[event].add(listener);
- },
-
- off(event: EmitterEvent, listener: EmitterListener) {
- this.listeners[event].delete(listener);
- },
-
- once(event: EmitterEvent, listener: EmitterListener) {
- const once = (event: EmitterEvent, data: EmitterListenerData) => {
- this.off(event, once);
- listener(event, data);
- };
- this.on(event, once);
- },
-
- emit(event: EmitterEvent, data: EmitterListenerData) {
- for (const listener of this.listeners[event]) listener(event, data);
- },
- };
-}
diff --git a/src/lib/fixes.ts b/src/lib/fixes.ts
deleted file mode 100644
index 135e1f0..0000000
--- a/src/lib/fixes.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { FluxDispatcher, moment } from "@metro/common";
-import { findByProps, findByStoreName } from "@metro/filters";
-import logger from "@lib/logger";
-
-const ThemeManager = findByProps("updateTheme", "overrideTheme");
-const AMOLEDThemeManager = findByProps("setAMOLEDThemeEnabled");
-const ThemeStore = findByStoreName("ThemeStore");
-const UnsyncedUserSettingsStore = findByStoreName("UnsyncedUserSettingsStore");
-
-function onDispatch({ locale }: { locale: string }) {
- // Theming
- // Based on https://github.com/Aliucord/AliucordRN/blob/main/src/ui/patchTheme.ts
- try {
- if (ThemeManager) {
- ThemeManager.overrideTheme(ThemeStore?.theme ?? "dark");
- if (AMOLEDThemeManager && UnsyncedUserSettingsStore.useAMOLEDTheme === 2) AMOLEDThemeManager.setAMOLEDThemeEnabled(true);
- }
- } catch(e) {
- logger.error("Failed to fix theme...", e);
- }
-
- // Timestamps
- try {
- // TODO: Test if this works with all locales
- moment.locale(locale.toLowerCase());
- } catch(e) {
- logger.error("Failed to fix timestamps...", e);
- }
-
- // We're done here!
- FluxDispatcher.unsubscribe("I18N_LOAD_SUCCESS", onDispatch);
-}
-
-export default () => FluxDispatcher.subscribe("I18N_LOAD_SUCCESS", onDispatch);
\ No newline at end of file
diff --git a/src/lib/logger.ts b/src/lib/logger.ts
deleted file mode 100644
index 4aa7607..0000000
--- a/src/lib/logger.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Logger } from "@types";
-import { findByProps } from "@metro/filters";
-
-export const logModule = findByProps("setLogFn").default;
-const logger: Logger = new logModule("Bound");
-
-export default logger;
diff --git a/src/lib/metro/common.ts b/src/lib/metro/common.ts
deleted file mode 100644
index ccec70c..0000000
--- a/src/lib/metro/common.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { find, findByProps, findByStoreName } from "@metro/filters";
-import { DiscordStyleSheet } from "@types";
-import { ReactNative as RN } from "@lib/preinit";
-import type { StyleSheet } from "react-native";
-
-const ThemeStore = findByStoreName("ThemeStore");
-const colorModule = findByProps("colors", "unsafe_rawColors");
-const colorResolver = colorModule?.internal ?? colorModule?.meta;
-
-// Reimplementation of Discord's createThemedStyleSheet, which was removed since 204201
-// Not exactly a 1:1 reimplementation, but sufficient to keep compatibility with existing plugins
-function createThemedStyleSheet>(sheet: T) {
- if (!colorModule) return;
- for (const key in sheet) {
- // @ts-ignore
- sheet[key] = new Proxy(RN.StyleSheet.flatten(sheet[key]), {
- get(target, prop, receiver) {
- const res = Reflect.get(target, prop, receiver);
- return colorResolver.isSemanticColor(res)
- ? colorResolver.resolveSemanticColor(ThemeStore.theme, res)
- : res
- }
- });
- }
-
- return sheet;
-}
-
-// 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 toasts = find(m => m.open && m.close && !m.startDrag && !m.init && !m.openReplay && !m.setAlwaysOnTop && !m.setAccountFlag);
-
-// Compatible with pre-204201 versions since createThemedStyleSheet is undefined.
-export const stylesheet = {
- ...find(m => m.createStyles && !m.ActionSheet),
- createThemedStyleSheet,
- ...findByProps("createThemedStyleSheet") as {},
-} as DiscordStyleSheet;
-
-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 navigationStack = findByProps("createStackNavigator");
-export const NavigationNative = findByProps("NavigationContainer");
-export const { TextStyleSheet } = findByProps("TextStyleSheet");
-
-// Flux
-export const Flux = findByProps("connectStores");
-export const FluxDispatcher = findByProps("_currentDispatchActionType");
-
-// React
-export const React = window.React as typeof import("react");
-export { ReactNative } from "@lib/preinit";
-
-// Moment
-export const moment = findByProps("isMoment") as typeof import("moment");
-
-// chroma.js
-export { chroma } from "@lib/preinit";
-
-// Lodash
-export const lodash = findByProps("forEachRight") as typeof import("lodash");
-
-// The node:util polyfill for RN
-// TODO: Find types for this
-export const util = findByProps("inspect", "isNullOrUndefined");
diff --git a/src/lib/metro/filters.ts b/src/lib/metro/filters.ts
deleted file mode 100644
index 1a78242..0000000
--- a/src/lib/metro/filters.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { MetroModules, PropsFinder, PropsFinderAll } from "@types";
-
-// Metro require
-declare const __r: (moduleId: number) => any;
-
-// Internal Metro error reporting logic
-const originalHandler = window.ErrorUtils.getGlobalHandler();
-
-// Function to blacklist a module, preventing it from being searched again
-const blacklist = (id: number) => Object.defineProperty(window.modules, id, {
- value: window.modules[id],
- enumerable: false,
- configurable: true,
- writable: true
-});
-
-// Blacklist any "bad-actor" modules, e.g. the dreaded null proxy, the window itself, or undefined modules
-for (const key in window.modules) {
- const id = Number(key);
- const module = window.modules[id]?.publicModule?.exports;
-
- if (!module || module === window || module["proxygone"] === null) {
- blacklist(id);
- continue;
- }
-}
-
-// Function to filter through modules
-const filterModules = (modules: MetroModules, single = false) => (filter: (m: any) => boolean) => {
- const found = [];
-
- for (const key in modules) {
- const id = Number(key);
- const module = modules[id]?.publicModule?.exports;
-
- // HACK: Override the function used to report fatal JavaScript errors (that crash the app) to prevent module-requiring side effects
- // Credit to @pylixonly (492949202121261067) for the initial version of this fix
- if (!modules[id].isInitialized) try {
- window.ErrorUtils.setGlobalHandler(() => {});
- __r(id);
- window.ErrorUtils.setGlobalHandler(originalHandler);
- } catch {}
-
- if (!module) {
- blacklist(id);
- continue;
- }
-
- if (module.default && module.__esModule && filter(module.default)) {
- if (single) return module.default;
- found.push(module.default);
- }
-
- if (filter(module)) {
- if (single) return module;
- else found.push(module);
- }
- }
-
- if (!single) return found;
-}
-
-export const modules = window.modules;
-export const find = filterModules(modules, true);
-export const findAll = filterModules(modules);
-
-const propsFilter = (props: (string | symbol)[]) => (m: any) => props.every((p) => m[p] !== undefined);
-const nameFilter = (name: string, defaultExp: boolean) => (defaultExp ? (m: any) => m?.name === name : (m: any) => m?.default?.name === name);
-const dNameFilter = (displayName: string, defaultExp: boolean) => (defaultExp ? (m: any) => m?.displayName === displayName : (m: any) => m?.default?.displayName === displayName);
-const tNameFilter = (typeName: string, defaultExp: boolean) => (defaultExp ? (m: any) => m?.type?.name === typeName : (m: any) => m?.default?.type?.name === typeName);
-const storeFilter = (name: string) => (m: any) => m.getName && m.getName.length === 0 && m.getName() === name;
-
-export const findByProps: PropsFinder = (...props) => find(propsFilter(props));
-export const findByPropsAll: PropsFinderAll = (...props) => findAll(propsFilter(props));
-export const findByName = (name: string, defaultExp = true) => find(nameFilter(name, defaultExp));
-export const findByNameAll = (name: string, defaultExp = true) => findAll(nameFilter(name, defaultExp));
-export const findByDisplayName = (displayName: string, defaultExp = true) => find(dNameFilter(displayName, defaultExp));
-export const findByDisplayNameAll = (displayName: string, defaultExp = true) => findAll(dNameFilter(displayName, defaultExp));
-export const findByTypeName = (typeName: string, defaultExp = true) => find(tNameFilter(typeName, defaultExp));
-export const findByTypeNameAll = (typeName: string, defaultExp = true) => findAll(tNameFilter(typeName, defaultExp));
-export const findByStoreName = (name: string) => find(storeFilter(name));
diff --git a/src/lib/native.ts b/src/lib/native.ts
deleted file mode 100644
index 2665bdf..0000000
--- a/src/lib/native.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { MMKVManager as _MMKVManager, FileManager as _FileManager } from "@types";
-const nmp = window.nativeModuleProxy;
-
-export const MMKVManager = nmp.MMKVManager as _MMKVManager;
-//! 173.10 renamed this to RTNFileManager.
-export const FileManager = (nmp.DCDFileManager ?? nmp.RTNFileManager) as _FileManager;
-//! 173.13 renamed this to RTNClientInfoManager.
-export const ClientInfoManager = nmp.InfoDictionaryManager ?? nmp.RTNClientInfoManager;
-//! 173.14 renamed this to RTNDeviceManager.
-export const DeviceManager = nmp.DCDDeviceManager ?? nmp.RTNDeviceManager;
-export const BundleUpdaterManager = nmp.BundleUpdaterManager;
\ No newline at end of file
diff --git a/src/lib/patcher.ts b/src/lib/patcher.ts
deleted file mode 100644
index fbff9d6..0000000
--- a/src/lib/patcher.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import * as _spitroast from "spitroast";
-
-export * from "spitroast";
-export default { ..._spitroast };
\ No newline at end of file
diff --git a/src/lib/plugins.ts b/src/lib/plugins.ts
deleted file mode 100644
index a73c20b..0000000
--- a/src/lib/plugins.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import { PluginManifest, Plugin } from "@types";
-import { safeFetch } from "@lib/utils";
-import { awaitSyncWrapper, createMMKVBackend, createStorage, purgeStorage, wrapSync } from "@lib/storage";
-import { allSettled } from "@lib/polyfills";
-import logger, { logModule } from "@lib/logger";
-import settings from "@lib/settings";
-
-type EvaledPlugin = {
- onLoad?(): void;
- onUnload(): void;
- settings: JSX.Element;
-};
-
-export const plugins = wrapSync(createStorage>(createMMKVBackend("VENDETTA_PLUGINS")));
-const loadedPlugins: Record = {};
-
-export async function fetchPlugin(id: string) {
- if (!id.endsWith("/")) id += "/";
- const existingPlugin = plugins[id];
-
- let pluginManifest: PluginManifest;
-
- try {
- pluginManifest = await (await safeFetch(id + "manifest.json", { cache: "no-store" })).json();
- } catch {
- throw new Error(`Failed to fetch manifest for ${id}`);
- }
-
- let pluginJs: string | undefined;
-
- if (existingPlugin?.manifest.hash !== pluginManifest.hash) {
- try {
- // by polymanifest spec, plugins should always specify their main file, but just in case
- pluginJs = await (await safeFetch(id + (pluginManifest.main || "index.js"), { cache: "no-store" })).text();
- } catch {} // Empty catch, checked below
- }
-
- if (!pluginJs && !existingPlugin) throw new Error(`Failed to fetch JS for ${id}`);
-
- plugins[id] = {
- id: id,
- manifest: pluginManifest,
- enabled: existingPlugin?.enabled ?? false,
- update: existingPlugin?.update ?? true,
- js: pluginJs ?? existingPlugin.js,
- };
-}
-
-export async function installPlugin(id: string, enabled = true) {
- if (!id.endsWith("/")) id += "/";
- if (typeof id !== "string" || id in plugins) throw new Error("Plugin already installed");
- await fetchPlugin(id);
- if (enabled) await startPlugin(id);
-}
-
-export async function evalPlugin(plugin: Plugin) {
- const vendettaForPlugins = {
- ...window.vendetta,
- plugin: {
- id: plugin.id,
- manifest: plugin.manifest,
- // Wrapping this with wrapSync is NOT an option.
- storage: await createStorage>(createMMKVBackend(plugin.id)),
- },
- logger: new logModule(`Vendetta » ${plugin.manifest.name}`),
- };
- const pluginString = `vendetta=>{return ${plugin.js}}\n//# sourceURL=${plugin.id}`;
-
- const raw = (0, eval)(pluginString)(vendettaForPlugins);
- const ret = typeof raw == "function" ? raw() : raw;
- return ret?.default ?? ret ?? {};
-}
-
-export async function startPlugin(id: string) {
- if (!id.endsWith("/")) id += "/";
- const plugin = plugins[id];
- if (!plugin) throw new Error("Attempted to start non-existent plugin");
-
- try {
- if (!settings.safeMode?.enabled) {
- const pluginRet: EvaledPlugin = await evalPlugin(plugin);
- loadedPlugins[id] = pluginRet;
- pluginRet.onLoad?.();
- }
- plugin.enabled = true;
- } catch (e) {
- logger.error(`Plugin ${plugin.id} errored whilst loading, and will be unloaded`, e);
-
- try {
- loadedPlugins[plugin.id]?.onUnload?.();
- } catch (e2) {
- logger.error(`Plugin ${plugin.id} errored whilst unloading`, e2);
- }
-
- delete loadedPlugins[id];
- plugin.enabled = false;
- }
-}
-
-export function stopPlugin(id: string, disable = true) {
- if (!id.endsWith("/")) id += "/";
- const plugin = plugins[id];
- const pluginRet = loadedPlugins[id];
- if (!plugin) throw new Error("Attempted to stop non-existent plugin");
-
- if (!settings.safeMode?.enabled) {
- try {
- pluginRet?.onUnload?.();
- } catch (e) {
- logger.error(`Plugin ${plugin.id} errored whilst unloading`, e);
- }
-
- delete loadedPlugins[id];
- }
-
- disable && (plugin.enabled = false);
-}
-
-export async function removePlugin(id: string) {
- if (!id.endsWith("/")) id += "/";
- const plugin = plugins[id];
- if (plugin.enabled) stopPlugin(id);
- delete plugins[id];
- await purgeStorage(id);
-}
-
-export async function initPlugins() {
- await awaitSyncWrapper(settings);
- await awaitSyncWrapper(plugins);
- const allIds = Object.keys(plugins);
-
- if (!settings.safeMode?.enabled) {
- // Loop over any plugin that is enabled, update it if allowed, then start it.
- await allSettled(allIds.filter(pl => plugins[pl].enabled).map(async (pl) => (plugins[pl].update && await fetchPlugin(pl).catch((e: Error) => logger.error(e.message)), await startPlugin(pl))));
- // Wait for the above to finish, then update all disabled plugins that are allowed to.
- allIds.filter(pl => !plugins[pl].enabled && plugins[pl].update).forEach(pl => fetchPlugin(pl));
- };
-
- return stopAllPlugins;
-}
-
-const stopAllPlugins = () => Object.keys(loadedPlugins).forEach(p => stopPlugin(p, false));
-
-export const getSettings = (id: string) => loadedPlugins[id]?.settings;
diff --git a/src/lib/polyfills.ts b/src/lib/polyfills.ts
deleted file mode 100644
index 7c745e6..0000000
--- a/src/lib/polyfills.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-//! Starting from 202.4, Promise.allSettled may be undefined due to conflicting then/promise versions, so we use our own.
-const allSettledFulfill = (value: T) => ({ status: "fulfilled", value });
-const allSettledReject = (reason: T) => ({ status: "rejected", reason });
-const mapAllSettled = (item: T) => Promise.resolve(item).then(allSettledFulfill, allSettledReject);
-export const allSettled = (iterator: T) => Promise.all(Array.from(iterator).map(mapAllSettled));
diff --git a/src/lib/preinit.ts b/src/lib/preinit.ts
deleted file mode 100644
index e4ea813..0000000
--- a/src/lib/preinit.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { initThemes } from "@lib/themes";
-import { instead } from "@lib/patcher";
-
-// Hoist required modules
-// This used to be in filters.ts, but things became convoluted
-
-const basicFind = (filter: (m: any) => any | string) => {
- for (const key in window.modules) {
- const exp = window.modules[key]?.publicModule.exports;
- if (exp && filter(exp)) return exp;
- }
-}
-
-const requireNativeComponent = basicFind(m => m?.default?.name === "requireNativeComponent");
-
-if (requireNativeComponent) {
- // > "Tried to register two views with the same name DCDVisualEffectView"
- // This serves as a workaround for the crashing You tab on Android starting from version 192.x
- // How? We simply ignore it.
- instead("default", requireNativeComponent, (args, orig) => {
- try {
- return orig(...args);
- } catch {
- return args[0];
- }
- })
-}
-
-// Hoist React on window
-window.React = basicFind(m => m.createElement) as typeof import("react");
-
-// Export ReactNative
-export const ReactNative = basicFind(m => m.AppRegistry) as typeof import("react-native");
-
-// Export chroma.js
-export const chroma = basicFind(m => m.brewer) as typeof import("chroma-js");
-
-// Themes
-if (window.__vendetta_loader?.features.themes) {
- try {
- initThemes();
- } catch (e) {
- console.error("[Bound] Failed to initialize themes...", e);
- }
-}
diff --git a/src/lib/settings.ts b/src/lib/settings.ts
deleted file mode 100644
index 4eea331..0000000
--- a/src/lib/settings.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { LoaderConfig, Settings } from "@types";
-import { createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@lib/storage";
-
-export default wrapSync(createStorage(createMMKVBackend("VENDETTA_SETTINGS")));
-export const loaderConfig = wrapSync(createStorage(createFileBackend("vendetta_loader.json")));
diff --git a/src/lib/storage/backends.ts b/src/lib/storage/backends.ts
deleted file mode 100644
index 8a544c1..0000000
--- a/src/lib/storage/backends.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { StorageBackend } from "@types";
-import { MMKVManager, FileManager } from "@lib/native";
-import { ReactNative as RN } from "@metro/common";
-
-const ILLEGAL_CHARS_REGEX = /[<>:"\/\\|?*]/g;
-
-const filePathFixer = (file: string): string => RN.Platform.select({
- default: file,
- ios: FileManager.saveFileToGallery ? file : `Documents/${file}`,
-});
-
-const getMMKVPath = (name: string): string => {
- if (ILLEGAL_CHARS_REGEX.test(name)) {
- // Replace forbidden characters with hyphens
- name = name.replace(ILLEGAL_CHARS_REGEX, '-').replace(/-+/g, '-');
- }
-
- return `vd_mmkv/${name}`;
-}
-
-export const purgeStorage = async (store: string) => {
- if (await MMKVManager.getItem(store)) {
- MMKVManager.removeItem(store);
- }
-
- const mmkvPath = getMMKVPath(store);
- if (await FileManager.fileExists(`${FileManager.getConstants().DocumentsDirPath}/${mmkvPath}`)) {
- await FileManager.removeFile?.("documents", mmkvPath);
- }
-}
-
-export const createMMKVBackend = (store: string) => {
- const mmkvPath = getMMKVPath(store);
- return createFileBackend(mmkvPath, (async () => {
- try {
- const path = `${FileManager.getConstants().DocumentsDirPath}/${mmkvPath}`;
- if (await FileManager.fileExists(path)) return;
-
- let oldData = await MMKVManager.getItem(store) ?? "{}";
-
- // From the testing on Android, it seems to return this if the data is too large
- if (oldData === "!!LARGE_VALUE!!") {
- const cachePath = `${FileManager.getConstants().CacheDirPath}/mmkv/${store}`;
- if (await FileManager.fileExists(cachePath)) {
- oldData = await FileManager.readFile(cachePath, "utf8")
- } else {
- console.log(`${store}: Experienced data loss :(`);
- oldData = "{}";
- }
- }
-
- await FileManager.writeFile("documents", filePathFixer(mmkvPath), oldData, "utf8");
- if (await MMKVManager.getItem(store) !== null) {
- MMKVManager.removeItem(store);
- console.log(`Successfully migrated ${store} store from MMKV storage to fs`);
- }
- } catch (err) {
- console.error("Failed to migrate to fs from MMKVManager ", err)
- }
- })());
-}
-
-export const createFileBackend = (file: string, migratePromise?: Promise): StorageBackend => {
- let created: boolean;
- return {
- get: async () => {
- await migratePromise;
- const path = `${FileManager.getConstants().DocumentsDirPath}/${file}`;
- if (!created && !(await FileManager.fileExists(path))) return (created = true), FileManager.writeFile("documents", filePathFixer(file), "{}", "utf8");
- return JSON.parse(await FileManager.readFile(path, "utf8"));
- },
- set: async (data) => {
- await migratePromise;
- await FileManager.writeFile("documents", filePathFixer(file), JSON.stringify(data), "utf8");
- }
- };
-};
diff --git a/src/lib/storage/index.ts b/src/lib/storage/index.ts
deleted file mode 100644
index 8df15f7..0000000
--- a/src/lib/storage/index.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { Emitter, StorageBackend, StorageObject } from "@types";
-import createEmitter from "@lib/emitter";
-
-const emitterSymbol = Symbol.for("vendetta.storage.emitter");
-const syncAwaitSymbol = Symbol.for("vendetta.storage.accessor");
-const storageErrorSymbol = Symbol.for("vendetta.storage.error");
-
-export function createProxy(target: any = {}): { proxy: any; emitter: Emitter } {
- const emitter = createEmitter();
-
- function createProxy(target: any, path: string[]): any {
- return new Proxy(target, {
- get(target, prop: string) {
- if ((prop as unknown) === emitterSymbol) return emitter;
-
- const newPath = [...path, prop];
- const value: any = target[prop];
-
- if (value !== undefined && value !== null) {
- emitter.emit("GET", {
- path: newPath,
- value,
- });
- if (typeof value === "object") {
- return createProxy(value, newPath);
- }
- return value;
- }
-
- return value;
- },
-
- set(target, prop: string, value) {
- target[prop] = value;
- emitter.emit("SET", {
- path: [...path, prop],
- value,
- });
- // we do not care about success, if this actually does fail we have other problems
- return true;
- },
-
- deleteProperty(target, prop: string) {
- const success = delete target[prop];
- if (success)
- emitter.emit("DEL", {
- path: [...path, prop],
- });
- return success;
- },
- });
- }
-
- return {
- proxy: createProxy(target, []),
- emitter,
- };
-}
-
-export function useProxy(storage: StorageObject): T {
- if (storage[storageErrorSymbol]) throw storage[storageErrorSymbol];
-
- const emitter = storage[emitterSymbol] as any as Emitter;
-
- if (!emitter) throw new Error("InvalidArgumentExcpetion - storage[emitterSymbol] is " + typeof emitter);
-
- const [, forceUpdate] = React.useReducer((n) => ~n, 0);
-
- React.useEffect(() => {
- const listener = () => forceUpdate();
-
- emitter.on("SET", listener);
- emitter.on("DEL", listener);
-
- return () => {
- emitter.off("SET", listener);
- emitter.off("DEL", listener);
- };
- }, []);
-
- return storage as T;
-}
-
-export async function createStorage(backend: StorageBackend): Promise> {
- const data = await backend.get();
- const { proxy, emitter } = createProxy(data);
-
- const handler = () => backend.set(proxy);
- emitter.on("SET", handler);
- emitter.on("DEL", handler);
-
- return proxy;
-}
-
-export function wrapSync>(store: T): Awaited {
- let awaited: any = undefined;
- let error: any = undefined;
-
- const awaitQueue: (() => void)[] = [];
- const awaitInit = (cb: () => void) => (awaited ? cb() : awaitQueue.push(cb));
-
- store.then((v) => {
- awaited = v;
- awaitQueue.forEach((cb) => cb());
- }).catch((e) => {
- error = e;
- });
-
- return new Proxy({} as Awaited, {
- ...Object.fromEntries(
- Object.getOwnPropertyNames(Reflect)
- // @ts-expect-error
- .map((k) => [k, (t: T, ...a: any[]) => Reflect[k](awaited ?? t, ...a)])
- ),
- get(target, prop, recv) {
- if (prop === storageErrorSymbol) return error;
- if (prop === syncAwaitSymbol) return awaitInit;
- return Reflect.get(awaited ?? target, prop, recv);
- },
- });
-}
-
-export const awaitSyncWrapper = (store: any) => new Promise((res) => store[syncAwaitSymbol](res));
-
-export * from "@lib/storage/backends";
diff --git a/src/lib/themes.ts b/src/lib/themes.ts
deleted file mode 100644
index 15d4003..0000000
--- a/src/lib/themes.ts
+++ /dev/null
@@ -1,236 +0,0 @@
-import { Theme, ThemeData } from "@types";
-import { ReactNative as RN, chroma } from "@metro/common";
-import { findInReactTree, safeFetch } from "@lib/utils";
-import { findByName, findByProps } from "@metro/filters";
-import { instead, after } from "@lib/patcher";
-import { createFileBackend, createMMKVBackend, createStorage, wrapSync, awaitSyncWrapper } from "@lib/storage";
-import logger from "./logger";
-
-//! As of 173.10, early-finding this does not work.
-// Somehow, this is late enough, though?
-export const color = findByProps("SemanticColor");
-
-export const themes = wrapSync(createStorage>(createMMKVBackend("VENDETTA_THEMES")));
-
-const semanticAlternativeMap: Record = {
- "BG_BACKDROP": "BACKGROUND_FLOATING",
- "BG_BASE_PRIMARY": "BACKGROUND_PRIMARY",
- "BG_BASE_SECONDARY": "BACKGROUND_SECONDARY",
- "BG_BASE_TERTIARY": "BACKGROUND_SECONDARY_ALT",
- "BG_MOD_FAINT": "BACKGROUND_MODIFIER_ACCENT",
- "BG_MOD_STRONG": "BACKGROUND_MODIFIER_ACCENT",
- "BG_MOD_SUBTLE": "BACKGROUND_MODIFIER_ACCENT",
- "BG_SURFACE_OVERLAY": "BACKGROUND_FLOATING",
- "BG_SURFACE_OVERLAY_TMP": "BACKGROUND_FLOATING",
- "BG_SURFACE_RAISED": "BACKGROUND_MOBILE_PRIMARY"
-}
-
-async function writeTheme(theme: Theme | {}) {
- if (typeof theme !== "object") throw new Error("Theme must be an object");
-
- // Save the current theme as vendetta_theme.json. When supported by loader,
- // this json will be written to window.__vendetta_theme and be used to theme the native side.
- await createFileBackend("vendetta_theme.json").set(theme);
-}
-
-export function patchChatBackground() {
- const currentBackground = getCurrentTheme()?.data?.background;
- if (!currentBackground) return;
-
- const MessagesWrapperConnected = findByName("MessagesWrapperConnected", false);
- if (!MessagesWrapperConnected) return;
- const { MessagesWrapper } = findByProps("MessagesWrapper");
- if (!MessagesWrapper) return;
-
- const patches = [
- after("default", MessagesWrapperConnected, (_, ret) => React.createElement(RN.ImageBackground, {
- style: { flex: 1, height: "100%" },
- source: { uri: currentBackground.url },
- blurRadius: typeof currentBackground.blur === "number" ? currentBackground.blur : 0,
- children: ret,
- })),
- after("render", MessagesWrapper.prototype, (_, ret) => {
- const Messages = findInReactTree(ret, (x) => "HACK_fixModalInteraction" in x?.props && x?.props?.style);
- if (Messages)
- Messages.props.style = Object.assign(
- RN.StyleSheet.flatten(Messages.props.style ?? {}),
- {
- backgroundColor: "#0000"
- }
- );
- else
- logger.error("Didn't find Messages when patching MessagesWrapper!");
- })
- ];
-
- return () => patches.forEach(x => x());
-}
-
-function normalizeToHex(colorString: string): string {
- if (chroma.valid(colorString)) return chroma(colorString).hex();
-
- const color = Number(RN.processColor(colorString));
-
- return chroma.rgb(
- color >> 16 & 0xff, // red
- color >> 8 & 0xff, // green
- color & 0xff, // blue
- color >> 24 & 0xff // alpha
- ).hex();
-}
-
-// Process data for some compatiblity with native side
-function processData(data: ThemeData) {
- if (data.semanticColors) {
- const semanticColors = data.semanticColors;
-
- for (const key in semanticColors) {
- for (const index in semanticColors[key]) {
- semanticColors[key][index] &&= normalizeToHex(semanticColors[key][index] as string);
- }
- }
- }
-
- if (data.rawColors) {
- const rawColors = data.rawColors;
-
- for (const key in rawColors) {
- data.rawColors[key] = normalizeToHex(rawColors[key]);
- }
-
- if (RN.Platform.OS === "android") applyAndroidAlphaKeys(rawColors);
- }
-
- return data;
-}
-
-function applyAndroidAlphaKeys(rawColors: Record) {
- // these are native Discord Android keys
- const alphaMap: Record = {
- "BLACK_ALPHA_60": ["BLACK", 0.6],
- "BRAND_NEW_360_ALPHA_20": ["BRAND_360", 0.2],
- "BRAND_NEW_360_ALPHA_25": ["BRAND_360", 0.25],
- "BRAND_NEW_500_ALPHA_20": ["BRAND_500", 0.2],
- "PRIMARY_DARK_500_ALPHA_20": ["PRIMARY_500", 0.2],
- "PRIMARY_DARK_700_ALPHA_60": ["PRIMARY_700", 0.6],
- "STATUS_GREEN_500_ALPHA_20": ["GREEN_500", 0.2],
- "STATUS_RED_500_ALPHA_20": ["RED_500", 0.2],
- };
-
- for (const key in alphaMap) {
- const [colorKey, alpha] = alphaMap[key];
- if (!rawColors[colorKey]) continue;
- rawColors[key] = chroma(rawColors[colorKey]).alpha(alpha).hex();
- }
-}
-
-export async function fetchTheme(id: string, selected = false) {
- let themeJSON: any;
-
- try {
- themeJSON = await (await safeFetch(id, { cache: "no-store" })).json();
- } catch {
- throw new Error(`Failed to fetch theme at ${id}`);
- }
-
- themes[id] = {
- id: id,
- selected: selected,
- data: processData(themeJSON),
- };
-
- // TODO: Should we prompt when the selected theme is updated?
- if (selected) writeTheme(themes[id]);
-}
-
-export async function installTheme(id: string) {
- if (typeof id !== "string" || id in themes) throw new Error("Theme already installed");
- await fetchTheme(id);
-}
-
-export async function selectTheme(id: string) {
- if (id === "default") return await writeTheme({});
- const selectedThemeId = Object.values(themes).find(i => i.selected)?.id;
-
- if (selectedThemeId) themes[selectedThemeId].selected = false;
- themes[id].selected = true;
- await writeTheme(themes[id]);
-}
-
-export async function removeTheme(id: string) {
- const theme = themes[id];
- if (theme.selected) await selectTheme("default");
- delete themes[id];
-
- return theme.selected;
-}
-
-export function getCurrentTheme(): Theme | null {
- const themeProp = window.__vendetta_loader?.features?.themes?.prop;
- if (!themeProp) return null;
- return window[themeProp] || null;
-}
-
-export async function updateThemes() {
- await awaitSyncWrapper(themes);
- const currentTheme = getCurrentTheme();
- await Promise.allSettled(Object.keys(themes).map(id => fetchTheme(id, currentTheme?.id === id)));
-}
-
-export async function initThemes() {
- //! Native code is required here!
- // Awaiting the sync wrapper is too slow, to the point where semanticColors are not correctly overwritten.
- // We need a workaround, and it will unfortunately have to be done on the native side.
- // await awaitSyncWrapper(themes);
-
- const selectedTheme = getCurrentTheme();
- if (!selectedTheme) return;
-
- const oldRaw = color.default.unsafe_rawColors;
-
- color.default.unsafe_rawColors = new Proxy(oldRaw, {
- get: (_, colorProp: string) => {
- if (!selectedTheme) return Reflect.get(oldRaw, colorProp);
-
- return selectedTheme.data?.rawColors?.[colorProp] ?? Reflect.get(oldRaw, colorProp);
- }
- });
-
- instead("resolveSemanticColor", color.default.meta ?? color.default.internal, (args, orig) => {
- if (!selectedTheme) return orig(...args);
-
- const [theme, propIndex] = args;
- const [name, colorDef] = extractInfo(theme, propIndex);
-
- const themeIndex = theme === "amoled" ? 2 : theme === "light" ? 1 : 0;
-
- //! As of 192.7, Tabs v2 uses BG_ semantic colors instead of BACKGROUND_ ones
- const alternativeName = semanticAlternativeMap[name] ?? name;
-
- const semanticColorVal = (selectedTheme.data?.semanticColors?.[name] ?? selectedTheme.data?.semanticColors?.[alternativeName])?.[themeIndex];
- if (name === "CHAT_BACKGROUND" && typeof selectedTheme.data?.background?.alpha === "number") {
- return chroma(semanticColorVal || "black").alpha(1 - selectedTheme.data.background.alpha).hex();
- }
-
- if (semanticColorVal) return semanticColorVal;
-
- const rawValue = selectedTheme.data?.rawColors?.[colorDef.raw];
- if (rawValue) {
- // Set opacity if needed
- return colorDef.opacity === 1 ? rawValue : chroma(rawValue).alpha(colorDef.opacity).hex();
- }
-
- // Fallback to default
- return orig(...args);
- });
-
- await updateThemes();
-}
-
-function extractInfo(themeMode: string, colorObj: any): [name: string, colorDef: any] {
- // @ts-ignore - assigning to extractInfo._sym
- const propName = colorObj[extractInfo._sym ??= Object.getOwnPropertySymbols(colorObj)[0]];
- const colorDef = color.SemanticColor[propName];
-
- return [propName, colorDef[themeMode.toLowerCase()]];
-}
\ No newline at end of file
diff --git a/src/lib/utils/findInReactTree.ts b/src/lib/utils/findInReactTree.ts
deleted file mode 100644
index 002f3e3..0000000
--- a/src/lib/utils/findInReactTree.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { SearchFilter } from "@types";
-import { findInTree } from "@lib/utils";
-
-export default (tree: { [key: string]: any }, filter: SearchFilter): any => findInTree(tree, filter, {
- walkable: ["props", "children", "child", "sibling"],
-});
\ No newline at end of file
diff --git a/src/lib/utils/findInTree.ts b/src/lib/utils/findInTree.ts
deleted file mode 100644
index f8a19cd..0000000
--- a/src/lib/utils/findInTree.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-// This has been completely reimplemented at this point, but the disclaimer at the end of disclaimers still counts.
-// https://github.com/Cordwood/Cordwood/blob/91c0b971bbf05e112927df75415df99fa105e1e7/src/lib/utils/findInTree.ts
-
-import { FindInTreeOptions, SearchTree, SearchFilter } from "@types";
-
-function treeSearch(tree: SearchTree, filter: SearchFilter, opts: Required, depth: number): any {
- if (depth > opts.maxDepth) return;
- if (!tree) return;
-
- try {
- if (filter(tree)) return tree;
- } catch {}
-
- if (Array.isArray(tree)) {
- for (const item of tree) {
- if (typeof item !== "object" || item === null) continue;
-
- try {
- const found = treeSearch(item, filter, opts, depth + 1);
- if (found) return found;
- } catch {}
- }
- } else if (typeof tree === "object") {
- for (const key of Object.keys(tree)) {
- if (typeof tree[key] !== "object" || tree[key] === null) continue;
- if (opts.walkable.length && !opts.walkable.includes(key)) continue;
- if (opts.ignore.includes(key)) continue;
-
- try {
- const found = treeSearch(tree[key], filter, opts, depth + 1);
- if (found) return found;
- } catch {}
- }
- }
-}
-
-export default (
- tree: SearchTree,
- filter: SearchFilter,
- {
- walkable = [],
- ignore = [],
- maxDepth = 100
- }: FindInTreeOptions = {},
-): any | undefined => treeSearch(tree, filter, { walkable, ignore, maxDepth }, 0);
diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts
deleted file mode 100644
index 8a3c295..0000000
--- a/src/lib/utils/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-// Makes mass-importing utils cleaner, chosen over moving utils to one file
-
-export { default as findInReactTree } from "@lib/utils/findInReactTree";
-export { default as findInTree } from "@lib/utils/findInTree";
-export { default as safeFetch } from "@lib/utils/safeFetch";
-export { default as unfreeze } from "@lib/utils/unfreeze";
-export { default as without } from "@lib/utils/without";
\ No newline at end of file
diff --git a/src/lib/utils/safeFetch.ts b/src/lib/utils/safeFetch.ts
deleted file mode 100644
index a4f7685..0000000
--- a/src/lib/utils/safeFetch.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// A really basic fetch wrapper which throws on non-ok response codes
-
-export default async function safeFetch(input: RequestInfo | URL, options?: RequestInit, timeout = 10000) {
- const req = await fetch(input, {
- signal: timeoutSignal(timeout),
- ...options
- });
-
- if (!req.ok) throw new Error("Request returned non-ok");
- return req;
-}
-
-function timeoutSignal(ms: number): AbortSignal {
- const controller = new AbortController();
- setTimeout(() => controller.abort(`Timed out after ${ms}ms`), ms);
- return controller.signal;
-}
diff --git a/src/lib/utils/unfreeze.ts b/src/lib/utils/unfreeze.ts
deleted file mode 100644
index c1a2e32..0000000
--- a/src/lib/utils/unfreeze.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-// https://stackoverflow.com/a/68339174
-
-export default function unfreeze(obj: object) {
- if (Object.isFrozen(obj)) return Object.assign({}, obj);
- return obj;
-}
\ No newline at end of file
diff --git a/src/lib/utils/without.ts b/src/lib/utils/without.ts
deleted file mode 100644
index dd7567e..0000000
--- a/src/lib/utils/without.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export default function without(object: O, ...keys: K): Omit {
- const cloned = { ...object };
- keys.forEach((k) => delete cloned[k]);
- return cloned;
-}
\ No newline at end of file
diff --git a/src/lib/windowObject.ts b/src/lib/windowObject.ts
deleted file mode 100644
index 9d3112f..0000000
--- a/src/lib/windowObject.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { VendettaObject } from "@types";
-import patcher from "@lib/patcher";
-import logger from "@lib/logger";
-import settings, { loaderConfig } from "@lib/settings";
-import * as constants from "@lib/constants";
-import * as debug from "@lib/debug";
-import * as plugins from "@lib/plugins";
-import * as themes from "@lib/themes";
-import * as commands from "@lib/commands";
-import * as storage from "@lib/storage";
-import * as metro from "@metro/filters";
-import * as common from "@metro/common";
-import * as components from "@ui/components";
-import * as toasts from "@ui/toasts";
-import * as alerts from "@ui/alerts";
-import * as assets from "@ui/assets";
-import * as color from "@ui/color";
-import * as utils from "@lib/utils";
-
-export default async (unloads: any[]): Promise => ({
- patcher: utils.without(patcher, "unpatchAll"),
- metro: { ...metro, common: { ...common } },
- constants,
- utils,
- debug: utils.without(debug, "versionHash", "patchLogHook", "setSafeMode"),
- ui: {
- components,
- toasts,
- alerts,
- assets,
- ...color,
- },
- plugins: utils.without(plugins, "initPlugins", "evalPlugin"),
- themes: utils.without(themes, "initThemes"),
- commands: utils.without(commands, "patchCommands"),
- storage,
- settings,
- loader: {
- identity: window.__vendetta_loader,
- config: loaderConfig,
- },
- logger,
- version: debug.versionHash,
- unload: () => {
- unloads.filter(i => typeof i === "function").forEach(p => p());
- // @ts-expect-error explode
- delete window.vendetta;
- },
-});
diff --git a/src/ui/alerts.ts b/src/ui/alerts.ts
deleted file mode 100644
index b35a3fe..0000000
--- a/src/ui/alerts.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { ConfirmationAlertOptions, InputAlertProps } from "@types";
-import { findByProps } from "@metro/filters";
-import InputAlert from "@ui/components/InputAlert";
-
-const Alerts = findByProps("openLazy", "close");
-
-interface InternalConfirmationAlertOptions extends Omit {
- content?: ConfirmationAlertOptions["content"];
- body?: ConfirmationAlertOptions["content"];
-};
-
-export function showConfirmationAlert(options: ConfirmationAlertOptions) {
- const internalOptions = options as InternalConfirmationAlertOptions;
-
- internalOptions.body = options.content;
- delete internalOptions.content;
-
- internalOptions.isDismissable ??= true;
-
- return Alerts.show(internalOptions);
-};
-
-export const showCustomAlert = (component: React.ComponentType, props: any) => Alerts.openLazy({
- importer: async () => () => React.createElement(component, props),
-});
-
-export const showInputAlert = (options: InputAlertProps) => showCustomAlert(InputAlert, options);
diff --git a/src/ui/assets.ts b/src/ui/assets.ts
deleted file mode 100644
index 842abe9..0000000
--- a/src/ui/assets.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Asset } from "@types";
-import { assets } from "@metro/common";
-import { after } from "@lib/patcher";
-
-export const all: Record = {};
-
-export function patchAssets() {
- const unpatch = after("registerAsset", assets, (args: Asset[], id: number) => {
- const asset = args[0];
- all[asset.name] = { ...asset, id: id };
- });
-
- for (let id = 1; ; id++) {
- const asset = assets.getAssetByID(id);
- if (!asset) break;
- if (all[asset.name]) continue;
- all[asset.name] = { ...asset, id: id };
- };
-
- return unpatch;
-}
-
-export const find = (filter: (a: any) => void): Asset | null | undefined => Object.values(all).find(filter);
-export const getAssetByName = (name: string): Asset => all[name];
-export const getAssetByID = (id: number): Asset => assets.getAssetByID(id);
-export const getAssetIDByName = (name: string) => all[name]?.id;
\ No newline at end of file
diff --git a/src/ui/color.ts b/src/ui/color.ts
deleted file mode 100644
index 84dd916..0000000
--- a/src/ui/color.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { constants } from "@metro/common";
-import { color } from "@lib/themes";
-
-//! This module is only found on 165.0+, under the assumption that iOS 165.0 is the same as Android 165.0.
-//* In 167.1, most if not all traces of the old color modules were removed.
-//* In 168.6, Discord restructured EVERYTHING again. SemanticColor on this module no longer works when passed to a stylesheet. We must now use what you see below.
-//* 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");
-
-//? SemanticColor and default.colors are effectively ThemeColorMap
-export const semanticColors = (color?.default?.colors ?? constants?.ThemeColorMap);
-
-//? RawColor and default.unsafe_rawColors are effectively Colors
-//* Note that constants.Colors does still appear to exist on newer versions despite Discord not internally using it - what the fuck?
-export const rawColors = (color?.default?.unsafe_rawColors ?? constants?.Colors);
\ No newline at end of file
diff --git a/src/ui/components/Codeblock.tsx b/src/ui/components/Codeblock.tsx
deleted file mode 100644
index 5c29921..0000000
--- a/src/ui/components/Codeblock.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { CodeblockProps } from "@types";
-import { ReactNative as RN, stylesheet, constants } from "@metro/common";
-import { semanticColors } from "@ui/color";
-import { cardStyle } from "@ui/shared";
-
-const styles = stylesheet.createThemedStyleSheet({
- codeBlock: {
- ...cardStyle,
- color: semanticColors.TEXT_NORMAL,
- fontFamily: constants.Fonts.CODE_SEMIBOLD,
- fontSize: 12,
- textAlignVertical: "center",
- paddingHorizontal: 12,
- },
-});
-
-// iOS doesn't support the selectable property on RN.Text...
-const InputBasedCodeblock = ({ style, children }: CodeblockProps) =>
-const TextBasedCodeblock = ({ selectable, style, children }: CodeblockProps) => {children}
-
-export default function Codeblock({ selectable, style, children }: CodeblockProps) {
- if (!selectable) return ;
-
- return RN.Platform.select({
- ios: ,
- default: ,
- });
-}
diff --git a/src/ui/components/ErrorBoundary.tsx b/src/ui/components/ErrorBoundary.tsx
deleted file mode 100644
index d1285fb..0000000
--- a/src/ui/components/ErrorBoundary.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { ErrorBoundaryState } from "@types";
-import { React, constants, TextStyleSheet } from "@metro/common";
-import { Tabs, Forms } from "@ui/components";
-
-export default class ErrorBoundary extends React.PureComponent {
- state: ErrorBoundaryState = { hasErr: false };
-
- static getDerivedStateFromError = (error: Error) => ({ hasErr: true, errText: error.message });
-
- render() {
- if (!this.state.hasErr) return this.props.children;
-
- return (
-
-
- Uh oh.
- {this.state.errText}
- this.setState({ hasErr: false, errText: undefined })} />
-
-
- )
- }
-}
diff --git a/src/ui/components/InputAlert.tsx b/src/ui/components/InputAlert.tsx
deleted file mode 100644
index 527c24d..0000000
--- a/src/ui/components/InputAlert.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { InputAlertProps } from "@types";
-import { findByProps } from "@metro/filters";
-import { Forms, Alert } from "@ui/components";
-
-const { FormInput } = Forms;
-const Alerts = findByProps("openLazy", "close");
-
-export default function InputAlert({ title, confirmText, confirmColor, onConfirm, cancelText, placeholder, initialValue = "", secureTextEntry }: InputAlertProps) {
- const [value, setValue] = React.useState(initialValue);
- const [error, setError] = React.useState("");
-
- function onConfirmWrapper() {
- const asyncOnConfirm = Promise.resolve(onConfirm(value))
-
- asyncOnConfirm.then(() => {
- Alerts.close();
- }).catch((e: Error) => {
- setError(e.message);
- });
- };
-
- return (
- Alerts.close()}
- >
- {
- setValue(typeof v === "string" ? v : v.text);
- if (error) setError("");
- }}
- returnKeyType="done"
- onSubmitEditing={onConfirmWrapper}
- error={error || undefined}
- secureTextEntry={secureTextEntry}
- autoFocus={true}
- showBorder={true}
- style={{ paddingVertical: 5, alignSelf: "stretch", paddingHorizontal: 0 }}
- />
-
- );
-};
\ No newline at end of file
diff --git a/src/ui/components/Search.tsx b/src/ui/components/Search.tsx
deleted file mode 100644
index a8a589c..0000000
--- a/src/ui/components/Search.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-// https://github.com/pyoncord/Pyoncord/blob/08c6b5ee1580991704640385b715d772859f34b7/src/lib/ui/components/Search.tsx
-
-import { SearchProps } from "@types";
-import { ReactNative as RN } from "@metro/common";
-import { getAssetIDByName } from "@ui/assets";
-import { Tabs } from "@ui/components";
-
-const SearchIcon = () => ;
-
-export default ({ onChangeText, placeholder, style }: SearchProps) => {
- const [query, setQuery] = React.useState("");
-
- const onChange = (value: string) => {
- setQuery(value);
- onChangeText?.(value);
- };
-
- return
-
-
-};
diff --git a/src/ui/components/Summary.tsx b/src/ui/components/Summary.tsx
deleted file mode 100644
index 60d3507..0000000
--- a/src/ui/components/Summary.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { SummaryProps } from "@types";
-import { ReactNative as RN } from "@metro/common";
-import { getAssetIDByName } from "@ui/assets";
-import { Forms } from "@ui/components";
-
-export default function Summary({ label, icon, noPadding = false, noAnimation = false, children }: SummaryProps) {
- const { FormRow, FormDivider } = Forms;
- const [hidden, setHidden] = React.useState(true);
-
- return (
- <>
- }
- trailing={}
- onPress={() => {
- setHidden(!hidden);
- if (!noAnimation) RN.LayoutAnimation.configureNext(RN.LayoutAnimation.Presets.easeInEaseOut);
- }}
- />
- {!hidden && <>
-
- {children}
- >}
- >
- )
-}
\ No newline at end of file
diff --git a/src/ui/components/TabulatedScreen.tsx b/src/ui/components/TabulatedScreen.tsx
deleted file mode 100644
index cb2506c..0000000
--- a/src/ui/components/TabulatedScreen.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-// https://github.com/maisymoe/strife/blob/54f4768ef41d66e682a0917b078129df5c34f0f8/plugins/Mockups/src/shared/TabulatedScreen.tsx
-
-import { TabulatedScreenProps, TabulatedScreenTab } from "@types";
-import { React, ReactNative as RN } from "@metro/common";
-import { findByProps } from "@metro/filters";
-
-const { BadgableTabBar } = findByProps("BadgableTabBar");
-
-export default ({ tabs }: TabulatedScreenProps) => {
- const [activeTab, setActiveTab] = React.useState(tabs[0]);
-
- return (
-
- {activeTab.render && }
-
- {
- const tab = tabs.find(t => t.id === id);
- if (!tab) return;
-
- tab.onPress?.(tab.id);
- tab.render && setActiveTab(tab);
- }}
- />
-
-
- )
-}
diff --git a/src/ui/components/index.ts b/src/ui/components/index.ts
deleted file mode 100644
index 786f9d1..0000000
--- a/src/ui/components/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { ReactNative as RN } from "@metro/common";
-import { findByDisplayName, findByName, findByProps, find } from "@metro/filters";
-
-// https://github.com/pyoncord/Pyoncord/blob/08c6b5ee1580991704640385b715d772859f34b7/src/lib/ui/components/discord/Redesign.ts#L4C1-L4C98
-const findSingular = (prop: string) => find(m => m[prop] && Object.keys(m).length === 1)?.[prop];
-
-// Discord
-export const Forms = findByProps("Form", "FormSection");
-export const Tabs = {
- ...findByProps("TableRow", "TableRowGroup"),
- RedesignSwitch: findSingular("FormSwitch"),
- RedesignCheckbox: findSingular("FormCheckbox"),
-} as Record;
-export const General = findByProps("Button", "Text", "View");
-export const Alert = findByDisplayName("FluxContainer(Alert)");
-export const Button = findByProps("Looks", "Colors", "Sizes") as React.ComponentType & { Looks: any, Colors: any, Sizes: any };
-export const HelpMessage = findByName("HelpMessage");
-// React Native's included SafeAreaView only adds padding on iOS.
-export const SafeAreaView = findByProps("useSafeAreaInsets").SafeAreaView as typeof RN.SafeAreaView;
-
-// Vendetta
-export { default as Summary } from "@ui/components/Summary";
-export { default as ErrorBoundary } from "@ui/components/ErrorBoundary";
-export { default as Codeblock } from "@ui/components/Codeblock";
-export { default as Search } from "@ui/components/Search";
-export { default as TabulatedScreen } from "@ui/components/TabulatedScreen";
diff --git a/src/ui/quickInstall/forumPost.tsx b/src/ui/quickInstall/forumPost.tsx
deleted file mode 100644
index 367789b..0000000
--- a/src/ui/quickInstall/forumPost.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { findByName, findByProps } from "@metro/filters";
-import { DISCORD_SERVER_ID, PLUGINS_CHANNEL_ID, THEMES_CHANNEL_ID, HTTP_REGEX_MULTI, PROXY_PREFIX } from "@lib/constants";
-import { after } from "@lib/patcher";
-import { installPlugin } from "@lib/plugins";
-import { installTheme } from "@lib/themes";
-import { findInReactTree } from "@lib/utils";
-import { getAssetIDByName } from "@ui/assets";
-import { showToast } from "@ui/toasts";
-import { Forms } from "@ui/components";
-
-const ForumPostLongPressActionSheet = findByName("ForumPostLongPressActionSheet", false);
-const { FormRow, FormIcon } = Forms;
-
-const { useFirstForumPostMessage } = findByProps("useFirstForumPostMessage");
-const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
-
-export default () => after("default", ForumPostLongPressActionSheet, ([{ thread }], res) => {
- if (thread.guild_id !== DISCORD_SERVER_ID) return;
-
- // Determine what type of addon this is.
- let postType: "Plugin" | "Theme";
- if (thread.parent_id === PLUGINS_CHANNEL_ID) {
- postType = "Plugin";
- } else if (thread.parent_id === THEMES_CHANNEL_ID && window.__vendetta_loader?.features.themes) {
- postType = "Theme";
- } else return;
-
- const { firstMessage } = useFirstForumPostMessage(thread);
-
- let urls = firstMessage?.content?.match(HTTP_REGEX_MULTI);
- if (!urls) return;
-
- if (postType === "Plugin") {
- urls = urls.filter((url: string) => url.startsWith(PROXY_PREFIX));
- } else {
- urls = urls.filter((url: string) => url.endsWith(".json"));
- };
-
- const url = urls[0];
- if (!url) return;
-
- const actions = findInReactTree(res, (t) => t?.[0]?.key);
- const ActionsSection = actions[0].type;
-
- actions.unshift(
- }
- label={`Install ${postType}`}
- onPress={() =>
- (postType === "Plugin" ? installPlugin : installTheme)(url).then(() => {
- showToast(`Successfully installed ${thread.name}`, getAssetIDByName("Check"));
- }).catch((e: Error) => {
- showToast(e.message, getAssetIDByName("Small"));
- }).finally(() => hideActionSheet())
- }
- />
- );
-});
diff --git a/src/ui/quickInstall/index.ts b/src/ui/quickInstall/index.ts
deleted file mode 100644
index 38e5f77..0000000
--- a/src/ui/quickInstall/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import patchForumPost from "@ui/quickInstall/forumPost";
-import patchUrl from "@ui/quickInstall/url";
-
-export default function initQuickInstall() {
- const patches = new Array;
-
- patches.push(patchForumPost());
- patches.push(patchUrl());
-
- return () => patches.forEach(p => p());
-};
diff --git a/src/ui/quickInstall/url.tsx b/src/ui/quickInstall/url.tsx
deleted file mode 100644
index 0eb4edc..0000000
--- a/src/ui/quickInstall/url.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { findByProps, find } from "@metro/filters";
-import { ReactNative as RN, channels, url } from "@metro/common";
-import { PROXY_PREFIX, THEMES_CHANNEL_ID } from "@lib/constants";
-import { after, instead } from "@lib/patcher";
-import { installPlugin } from "@lib/plugins";
-import { installTheme } from "@lib/themes";
-import { showConfirmationAlert } from "@ui/alerts";
-import { getAssetIDByName } from "@ui/assets";
-import { showToast } from "@ui/toasts";
-
-const showSimpleActionSheet = find((m) => m?.showSimpleActionSheet && !Object.getOwnPropertyDescriptor(m, "showSimpleActionSheet")?.get);
-const handleClick = findByProps("handleClick");
-const { openURL } = url;
-const { getChannelId } = channels;
-const { getChannel } = findByProps("getChannel");
-
-const { TextStyleSheet } = findByProps("TextStyleSheet");
-
-function typeFromUrl(url: string) {
- if (url.startsWith(PROXY_PREFIX)) {
- return "Plugin";
- } else if (url.endsWith(".json") && window.__vendetta_loader?.features.themes) {
- return "Theme";
- } else return;
-}
-
-function installWithToast(type: "Plugin" | "Theme", url: string) {
- (type === "Plugin" ? installPlugin : installTheme)(url)
- .then(() => {
- showToast("Successfully installed", getAssetIDByName("Check"));
- })
- .catch((e: Error) => {
- showToast(e.message, getAssetIDByName("Small"));
- });
-}
-
-export default () => {
- const patches = new Array();
-
- patches.push(
- after("showSimpleActionSheet", showSimpleActionSheet, (args) => {
- if (args[0].key !== "LongPressUrl") return;
- const {
- header: { title: url },
- options,
- } = args[0];
-
- const urlType = typeFromUrl(url);
- if (!urlType) return;
-
- options.push({
- label: `Install ${urlType}`,
- onPress: () => installWithToast(urlType, url),
- });
- })
- );
-
- patches.push(
- instead("handleClick", handleClick, async function (this: any, args, orig) {
- const { href: url } = args[0];
-
- const urlType = typeFromUrl(url);
- if (!urlType) return orig.apply(this, args);
-
- // Make clicking on theme links only work in #themes, should there be a theme proxy in the future, this can be removed.
- if (urlType === "Theme" && getChannel(getChannelId())?.parent_id !== THEMES_CHANNEL_ID) return orig.apply(this, args);
-
- showConfirmationAlert({
- title: "Hold Up",
- content: ["This link is a ", {urlType}, ", would you like to install it?"],
- onConfirm: () => installWithToast(urlType, url),
- confirmText: "Install",
- cancelText: "Cancel",
- secondaryConfirmText: "Open in Browser",
- onConfirmSecondary: () => openURL(url),
- });
- })
- );
-
- return () => patches.forEach((p) => p());
-};
diff --git a/src/ui/safeMode.tsx b/src/ui/safeMode.tsx
deleted file mode 100644
index bdb01db..0000000
--- a/src/ui/safeMode.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import { ReactNative as RN, TextStyleSheet, stylesheet } from "@metro/common";
-import { findByName, findByProps } from "@metro/filters";
-import { after } from "@lib/patcher";
-import { setSafeMode } from "@lib/debug";
-import { DeviceManager } from "@lib/native";
-import { semanticColors } from "@ui/color";
-import { cardStyle } from "@ui/shared";
-import { Tabs, Codeblock, ErrorBoundary as _ErrorBoundary, SafeAreaView } from "@ui/components";
-import settings from "@lib/settings";
-
-const ErrorBoundary = findByName("ErrorBoundary");
-
-// Let's just pray they have this.
-const { BadgableTabBar } = findByProps("BadgableTabBar");
-
-const styles = stylesheet.createThemedStyleSheet({
- container: {
- flex: 1,
- backgroundColor: semanticColors.BACKGROUND_PRIMARY,
- paddingHorizontal: 16,
- },
- header: {
- flex: 1,
- flexDirection: "row",
- justifyContent: "center",
- alignItems: "center",
- marginTop: 8,
- marginBottom: 16,
- ...cardStyle,
- },
- headerTitle: {
- ...TextStyleSheet["heading-lg/semibold"],
- color: semanticColors.HEADER_PRIMARY,
- marginBottom: 4,
- },
- headerDescription: {
- ...TextStyleSheet["text-sm/medium"],
- color: semanticColors.TEXT_MUTED,
- },
- body: {
- flex: 6,
- },
- footer: {
- flexDirection: DeviceManager.isTablet ? "row" : "column",
- justifyContent: "center",
- marginBottom: 16,
- },
-});
-
-interface Tab {
- id: string;
- title: string;
- trimWhitespace?: boolean;
-}
-
-interface Button {
- text: string;
- // TODO: Proper types for the below
- variant?: string;
- size?: string;
- onPress: () => void;
-}
-
-const tabs: Tab[] = [
- { id: "stack", title: "Stack Trace" },
- { id: "component", title: "Component", trimWhitespace: true },
-];
-
-export default () => after("render", ErrorBoundary.prototype, function (this: any, _, ret) {
- if (!(settings.errorBoundaryEnabled ?? true)) return;
- if (!this.state.error) return;
-
- // Not using setState here as we don't want to cause a re-render, we want this to be set in the initial render
- this.state.activeTab ??= "stack";
- const tabData = tabs.find(t => t.id === this.state.activeTab);
- const errorText: string = this.state.error[this.state.activeTab];
-
- // This is in the patch and not outside of it so that we can use `this`, e.g. for setting state
- const buttons: Button[] = [
- { text: "Restart Discord", onPress: this.handleReload },
- ...!settings.safeMode?.enabled ? [{ text: "Restart in Recovery Mode", onPress: setSafeMode }] : [],
- { variant: "destructive", text: "Retry Render", onPress: () => this.setState({ info: null, error: null }) },
- ]
-
- return (
- <_ErrorBoundary>
-
-
-
- {ret.props.title}
- {ret.props.body}
-
- {ret.props.Illustration && }
-
-
-
- {/*
- TODO: I tried to get this working as intended using regex and failed.
- When trimWhitespace is true, each line should have it's whitespace removed but with it's spaces kept.
- */}
- {tabData?.trimWhitespace ? errorText?.split("\n").filter(i => i.length !== 0).map(i => i.trim()).join("\n") : errorText}
-
-
- {/* Are errors caught by ErrorBoundary guaranteed to have the component stack? */}
- { this.setState({ activeTab: tab }) }}
- />
-
-
-
- {buttons.map(button => {
- const buttonIndex = buttons.indexOf(button) !== 0 ? 8 : 0;
-
- return
- })}
-
-
-
- )
-});
diff --git a/src/ui/settings/components/AddonPage.tsx b/src/ui/settings/components/AddonPage.tsx
deleted file mode 100644
index 601d6ee..0000000
--- a/src/ui/settings/components/AddonPage.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { ReactNative as RN } from "@metro/common";
-import { useProxy } from "@lib/storage";
-import { HelpMessage, ErrorBoundary, Search } from "@ui/components";
-import { CardWrapper } from "@ui/settings/components/Card";
-import settings from "@lib/settings";
-
-interface AddonPageProps {
- items: Record;
- safeModeMessage: string;
- safeModeExtras?: JSX.Element | JSX.Element[];
- card: React.ComponentType>;
-}
-
-export default function AddonPage({ items, safeModeMessage, safeModeExtras, card: CardComponent }: AddonPageProps) {
- useProxy(settings)
- useProxy(items);
- const [search, setSearch] = React.useState("");
-
- return (
-
- {/* TODO: Implement better searching than just by ID */}
-
- {settings.safeMode?.enabled &&
- {safeModeMessage}
- {safeModeExtras}
- }
- setSearch(v.toLowerCase())}
- placeholder="Search"
- />
- >}
- style={{ paddingHorizontal: 12, paddingTop: 12 }}
- contentContainerStyle={{ paddingBottom: 20 }}
- data={Object.values(items).filter(i => i.id?.toLowerCase().includes(search))}
- renderItem={({ item, index }) => }
- />
-
- )
-}
diff --git a/src/ui/settings/components/AssetDisplay.tsx b/src/ui/settings/components/AssetDisplay.tsx
deleted file mode 100644
index 581dd2f..0000000
--- a/src/ui/settings/components/AssetDisplay.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Asset } from "@types";
-import { ReactNative as RN, clipboard } from "@metro/common";
-import { showToast } from "@ui/toasts";
-import { getAssetIDByName } from "@ui/assets";
-import { Forms } from "@ui/components";
-
-interface AssetDisplayProps { asset: Asset }
-
-const { FormRow } = Forms;
-
-export default function AssetDisplay({ asset }: AssetDisplayProps) {
- return (
- }
- onPress={() => {
- clipboard.setString(asset.name);
- showToast("Copied asset name to clipboard.", getAssetIDByName("toast_copy_link"));
- }}
- />
- )
-}
\ No newline at end of file
diff --git a/src/ui/settings/components/Card.tsx b/src/ui/settings/components/Card.tsx
deleted file mode 100644
index f4f158d..0000000
--- a/src/ui/settings/components/Card.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { ReactNative as RN, stylesheet } from "@metro/common";
-import { findByProps } from "@metro/filters";
-import { getAssetIDByName } from "@ui/assets";
-import { semanticColors } from "@ui/color";
-import { Forms, Tabs } from "@ui/components";
-
-const { FormRow } = Forms;
-const { RedesignSwitch, RedesignCheckbox } = Tabs;
-const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
-const { showSimpleActionSheet } = findByProps("showSimpleActionSheet");
-
-// TODO: These styles work weirdly. iOS has cramped text, Android with low DPI probably does too. Fix?
-const styles = stylesheet.createThemedStyleSheet({
- card: {
- backgroundColor: semanticColors?.BACKGROUND_SECONDARY,
- borderRadius: 16,
- },
- header: {
- padding: 0,
- backgroundColor: semanticColors?.BACKGROUND_TERTIARY,
- borderTopLeftRadius: 16,
- borderTopRightRadius: 16,
- },
- actions: {
- flexDirection: "row-reverse",
- alignItems: "center",
- },
- icon: {
- width: 22,
- height: 22,
- marginLeft: 5,
- tintColor: semanticColors?.INTERACTIVE_NORMAL,
- },
-})
-
-interface Action {
- icon: string;
- onPress: () => void;
-}
-
-interface OverflowAction extends Action {
- label: string;
- isDestructive?: boolean;
-}
-
-export interface CardWrapper {
- item: T;
- index: number;
-}
-
-interface CardProps {
- index?: number;
- headerLabel: string | React.ComponentType;
- headerIcon?: string;
- toggleType?: "switch" | "radio";
- toggleValue?: boolean;
- onToggleChange?: (v: boolean) => void;
- descriptionLabel?: string | React.ComponentType;
- actions?: Action[];
- overflowTitle?: string;
- overflowActions?: OverflowAction[];
-}
-
-export default function Card(props: CardProps) {
- let pressableState = props.toggleValue ?? false;
-
- return (
-
- }
- trailing={props.toggleType && (props.toggleType === "switch" ?
- ()
- :
- ( {
- pressableState = !pressableState;
- props.onToggleChange?.(pressableState)
- }}>
-
- )
- )}
- />
-
- {props.overflowActions && showSimpleActionSheet({
- key: "CardOverflow",
- header: {
- title: props.overflowTitle,
- icon: props.headerIcon && ,
- onClose: () => hideActionSheet(),
- },
- options: props.overflowActions?.map(i => ({ ...i, icon: getAssetIDByName(i.icon) })),
- })}
- >
-
- }
- {props.actions?.map(({ icon, onPress }) => (
-
-
-
- ))}
-
- }
- />
-
- )
-}
diff --git a/src/ui/settings/components/InstallButton.tsx b/src/ui/settings/components/InstallButton.tsx
deleted file mode 100644
index 9bf55bb..0000000
--- a/src/ui/settings/components/InstallButton.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { ReactNative as RN, stylesheet, clipboard } from "@metro/common";
-import { HTTP_REGEX_MULTI } from "@lib/constants";
-import { showInputAlert } from "@ui/alerts";
-import { getAssetIDByName } from "@ui/assets";
-import { semanticColors } from "@ui/color";
-
-const styles = stylesheet.createThemedStyleSheet({
- icon: {
- marginRight: 10,
- tintColor: semanticColors.HEADER_PRIMARY,
- },
-});
-
-interface InstallButtonProps {
- alertTitle: string;
- installFunction: (id: string) => Promise;
-}
-
-export default function InstallButton({ alertTitle, installFunction: fetchFunction }: InstallButtonProps) {
- return (
-
- clipboard.getString().then((content) =>
- showInputAlert({
- title: alertTitle,
- initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",
- placeholder: "https://example.com/",
- onConfirm: (input: string) => fetchFunction(input),
- confirmText: "Install",
- cancelText: "Cancel",
- })
- )
- }>
-
-
- );
-}
diff --git a/src/ui/settings/components/PluginCard.tsx b/src/ui/settings/components/PluginCard.tsx
deleted file mode 100644
index e4ffdec..0000000
--- a/src/ui/settings/components/PluginCard.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { ButtonColors, Plugin } from "@types";
-import { NavigationNative, clipboard } from "@metro/common";
-import { removePlugin, startPlugin, stopPlugin, getSettings, fetchPlugin } from "@lib/plugins";
-import { MMKVManager } from "@lib/native";
-import { getAssetIDByName } from "@ui/assets";
-import { showToast } from "@ui/toasts";
-import { showConfirmationAlert } from "@ui/alerts";
-import Card, { CardWrapper } from "@ui/settings/components/Card";
-
-async function stopThenStart(plugin: Plugin, callback: Function) {
- if (plugin.enabled) stopPlugin(plugin.id, false);
- callback();
- if (plugin.enabled) await startPlugin(plugin.id);
-}
-
-export default function PluginCard({ item: plugin, index }: CardWrapper) {
- const settings = getSettings(plugin.id);
- const navigation = NavigationNative.useNavigation();
- const [removed, setRemoved] = React.useState(false);
-
- // This is needed because of React™
- if (removed) return null;
-
- return (
- i.name).join(", ")}`}
- headerIcon={plugin.manifest.vendetta?.icon || "ic_application_command_24px"}
- toggleType="switch"
- toggleValue={plugin.enabled}
- onToggleChange={(v: boolean) => {
- try {
- if (v) startPlugin(plugin.id); else stopPlugin(plugin.id);
- } catch (e) {
- showToast((e as Error).message, getAssetIDByName("Small"));
- }
- }}
- descriptionLabel={plugin.manifest.description}
- overflowTitle={plugin.manifest.name}
- overflowActions={[
- {
- icon: "ic_sync_24px",
- label: "Refetch",
- onPress: async () => {
- stopThenStart(plugin, () => {
- fetchPlugin(plugin.id).then(async () => {
- showToast("Successfully refetched plugin.", getAssetIDByName("toast_image_saved"));
- }).catch(() => {
- showToast("Failed to refetch plugin!", getAssetIDByName("Small"));
- })
- });
- },
- },
- {
- icon: "copy",
- label: "Copy URL",
- onPress: () => {
- clipboard.setString(plugin.id);
- showToast("Copied plugin URL to clipboard.", getAssetIDByName("toast_copy_link"));
- }
- },
- {
- icon: "ic_download_24px",
- label: plugin.update ? "Disable updates" : "Enable updates",
- onPress: () => {
- plugin.update = !plugin.update;
- showToast(`${plugin.update ? "Enabled" : "Disabled"} updates for ${plugin.manifest.name}.`, getAssetIDByName("toast_image_saved"));
- }
- },
- {
- icon: "ic_duplicate",
- label: "Clear data",
- isDestructive: true,
- onPress: () => showConfirmationAlert({
- title: "Wait!",
- content: `Are you sure you wish to clear the data of ${plugin.manifest.name}?`,
- confirmText: "Clear",
- cancelText: "Cancel",
- confirmColor: ButtonColors.RED,
- onConfirm: () => {
- stopThenStart(plugin, () => {
- try {
- MMKVManager.removeItem(plugin.id);
- showToast(`Cleared data for ${plugin.manifest.name}.`, getAssetIDByName("trash"));
- } catch {
- showToast(`Failed to clear data for ${plugin.manifest.name}!`, getAssetIDByName("Small"));
- }
- });
- }
- }),
- },
- {
- icon: "ic_message_delete",
- label: "Delete",
- isDestructive: true,
- onPress: () => showConfirmationAlert({
- title: "Wait!",
- content: `Are you sure you wish to delete ${plugin.manifest.name}? This will clear all of the plugin's data.`,
- confirmText: "Delete",
- cancelText: "Cancel",
- confirmColor: ButtonColors.RED,
- onConfirm: () => {
- try {
- removePlugin(plugin.id);
- setRemoved(true);
- } catch (e) {
- showToast((e as Error).message, getAssetIDByName("Small"));
- }
- }
- }),
- },
- ]}
- actions={[
- ...(settings ? [{
- icon: "settings",
- onPress: () => navigation.push("VendettaCustomPage", {
- title: plugin.manifest.name,
- render: settings,
- })
- }] : []),
- ]}
- />
- )
-}
diff --git a/src/ui/settings/components/SettingsSection.tsx b/src/ui/settings/components/SettingsSection.tsx
deleted file mode 100644
index 8e4ee04..0000000
--- a/src/ui/settings/components/SettingsSection.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { NavigationNative } from "@metro/common";
-import { useProxy } from "@lib/storage";
-import { getAssetIDByName } from "@ui/assets";
-import { getRenderableScreens } from "@ui/settings/data";
-import { ErrorBoundary, Forms } from "@ui/components";
-import settings from "@lib/settings";
-
-const { FormRow, FormSection, FormDivider } = Forms;
-
-export default function SettingsSection() {
- const navigation = NavigationNative.useNavigation();
- useProxy(settings);
-
- const screens = getRenderableScreens()
-
- return (
-
-
- {screens.map((s, i) => (
- <>
- }
- trailing={FormRow.Arrow}
- onPress={() => navigation.push(s.key)}
- />
- {i !== screens.length - 1 && }
- >
- ))}
-
-
- )
-}
diff --git a/src/ui/settings/components/ThemeCard.tsx b/src/ui/settings/components/ThemeCard.tsx
deleted file mode 100644
index f90763a..0000000
--- a/src/ui/settings/components/ThemeCard.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { ButtonColors, Theme } from "@types";
-import { clipboard } from "@metro/common";
-import { fetchTheme, removeTheme, selectTheme } from "@lib/themes";
-import { useProxy } from "@lib/storage";
-import { BundleUpdaterManager } from "@lib/native";
-import { getAssetIDByName } from "@ui/assets";
-import { showConfirmationAlert } from "@ui/alerts";
-import { showToast } from "@ui/toasts";
-import settings from "@lib/settings";
-import Card, { CardWrapper } from "@ui/settings/components/Card";
-
-async function selectAndReload(value: boolean, id: string) {
- await selectTheme(value ? id : "default");
- BundleUpdaterManager.reload();
-}
-
-export default function ThemeCard({ item: theme, index }: CardWrapper) {
- useProxy(settings);
- const [removed, setRemoved] = React.useState(false);
-
- // This is needed because of React™
- if (removed) return null;
-
- const authors = theme.data.authors;
-
- return (
- i.name).join(", ")}` : ""}`}
- descriptionLabel={theme.data.description ?? "No description."}
- toggleType={!settings.safeMode?.enabled ? "radio" : undefined}
- toggleValue={theme.selected}
- onToggleChange={(v: boolean) => {
- selectAndReload(v, theme.id);
- }}
- overflowTitle={theme.data.name}
- overflowActions={[
- {
- icon: "ic_sync_24px",
- label: "Refetch",
- onPress: () => {
- fetchTheme(theme.id, theme.selected).then(() => {
- if (theme.selected) {
- showConfirmationAlert({
- title: "Theme refetched",
- content: "A reload is required to see the changes. Do you want to reload now?",
- confirmText: "Reload",
- cancelText: "Cancel",
- confirmColor: ButtonColors.RED,
- onConfirm: () => BundleUpdaterManager.reload(),
- })
- } else {
- showToast("Successfully refetched theme.", getAssetIDByName("toast_image_saved"));
- }
- }).catch(() => {
- showToast("Failed to refetch theme!", getAssetIDByName("Small"));
- });
- },
- },
- {
- icon: "copy",
- label: "Copy URL",
- onPress: () => {
- clipboard.setString(theme.id);
- showToast("Copied theme URL to clipboard.", getAssetIDByName("toast_copy_link"));
- }
- },
- {
- icon: "ic_message_delete",
- label: "Delete",
- isDestructive: true,
- onPress: () => showConfirmationAlert({
- title: "Wait!",
- content: `Are you sure you wish to delete ${theme.data.name}?`,
- confirmText: "Delete",
- cancelText: "Cancel",
- confirmColor: ButtonColors.RED,
- onConfirm: () => {
- removeTheme(theme.id).then((wasSelected) => {
- setRemoved(true);
- if (wasSelected) selectAndReload(false, theme.id);
- }).catch((e: Error) => {
- showToast(e.message, getAssetIDByName("Small"));
- });
- }
- })
- },
- ]}
- />
- )
-}
diff --git a/src/ui/settings/components/Version.tsx b/src/ui/settings/components/Version.tsx
deleted file mode 100644
index 033accf..0000000
--- a/src/ui/settings/components/Version.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { clipboard } from "@metro/common";
-import { getAssetIDByName } from "@ui/assets";
-import { showToast } from "@ui/toasts";
-import { Forms } from "@ui/components";
-
-interface VersionProps {
- label: string;
- version: string;
- icon: string;
-}
-
-const { FormRow, FormText } = Forms;
-
-export default function Version({ label, version, icon }: VersionProps) {
- return (
- }
- trailing={{version}}
- onPress={() => {
- clipboard.setString(`${label} - ${version}`);
- showToast("Copied version to clipboard.", getAssetIDByName("toast_copy_link"));
- }}
- />
- )
-}
\ No newline at end of file
diff --git a/src/ui/settings/data.tsx b/src/ui/settings/data.tsx
deleted file mode 100644
index cecc30d..0000000
--- a/src/ui/settings/data.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import { ReactNative as RN, NavigationNative, stylesheet, lodash } from "@metro/common";
-import { installPlugin } from "@lib/plugins";
-import { installTheme } from "@lib/themes";
-import { showConfirmationAlert } from "@ui/alerts";
-import { semanticColors } from "@ui/color";
-import { showToast } from "@ui/toasts";
-import { without } from "@lib/utils";
-import { getAssetIDByName } from "@ui/assets";
-import settings from "@lib/settings";
-import ErrorBoundary from "@ui/components/ErrorBoundary";
-import InstallButton from "@ui/settings/components/InstallButton";
-import General from "@ui/settings/pages/General";
-import Plugins from "@ui/settings/pages/Plugins";
-import Themes from "@ui/settings/pages/Themes";
-import Secret from "@ui/settings/pages/Secret";
-import { PROXY_PREFIX } from "@/lib/constants";
-
-interface Screen {
- [index: string]: any;
- key: string;
- title: string;
- icon?: string;
- shouldRender?: () => boolean;
- options?: Record;
- render: React.ComponentType;
-}
-
-const styles = stylesheet.createThemedStyleSheet({ container: { flex: 1, backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY } });
-const formatKey = (key: string, youKeys: boolean) => youKeys ? lodash.snakeCase(key).toUpperCase() : key;
-// If a function is passed, it is called with the screen object, and the return value is mapped. If a string is passed, we map to the value of the property with that name on the screen. Else, just map to the given data.
-// Question: Isn't this overengineered?
-// Answer: Maybe.
-const keyMap = (screens: Screen[], data: string | ((s: Screen) => any) | null) => Object.fromEntries(screens.map(s => [s.key, typeof data === "function" ? data(s) : typeof data === "string" ? s[data] : data]));
-
-export const getScreens = (youKeys = false): Screen[] => [
- {
- key: formatKey("VendettaSettings", youKeys),
- title: "Settings",
- icon: "settings",
- render: General,
- },
- {
- key: formatKey("VendettaPlugins", youKeys),
- title: "Plugins",
- icon: "debug",
- options: {
- headerRight: () => (
- {
- if (!input.startsWith(PROXY_PREFIX) && !settings.developerSettings)
- setImmediate(() => showConfirmationAlert({
- title: "Unproxied Plugin",
- content: "The plugin you are trying to install has not been proxied/verified by Bound's staff. Are you sure you want to continue?",
- confirmText: "Install",
- onConfirm: () =>
- installPlugin(input)
- .then(() => showToast("Installed plugin", getAssetIDByName("Check")))
- .catch((x) => showToast(x?.message ?? `${x}`, getAssetIDByName("Small"))),
- cancelText: "Cancel",
- }));
- else return await installPlugin(input);
- }}
- />
- ),
- },
- render: Plugins,
- },
- {
- key: formatKey("VendettaThemes", youKeys),
- title: "Design",
- icon: "PencilSparkleIcon",
- // TODO: bad
- shouldRender: () => window.__vendetta_loader?.features.hasOwnProperty("themes") ?? false,
- options: {
- headerRight: () => !settings.safeMode?.enabled && ,
- },
- render: Themes,
- },
- {
- key: formatKey("BoundUpdater", youKeys),
- title: "Updater",
- icon: "ic_download_24px",
- render: Secret,
- },
- {
- key: formatKey("VendettaCustomPage", youKeys),
- title: "Bound Page",
- shouldRender: () => false,
- render: ({ render: PageView, noErrorBoundary, ...options }: { render: React.ComponentType; noErrorBoundary: boolean } & Record) => {
- const navigation = NavigationNative.useNavigation();
-
- navigation.addListener("focus", () => navigation.setOptions(without(options, "render", "noErrorBoundary")));
- return noErrorBoundary ? :
- },
- },
-];
-
-export const getRenderableScreens = (youKeys = false) => getScreens(youKeys).filter(s => s.shouldRender?.() ?? true);
-
-export const getPanelsScreens = () => keyMap(getScreens(), (s) => ({
- title: s.title,
- render: s.render,
- ...s.options,
-}));
-
-export const getYouData = () => {
- const screens = getScreens(true);
-
- return {
- getLayout: () => ({
- title: "Bound",
- label: "Bound",
- // We can't use our keyMap function here since `settings` is an array not an object
- settings: getRenderableScreens(true).map(s => s.key)
- }),
- titleConfig: keyMap(screens, "title"),
- relationships: keyMap(screens, null),
- rendererConfigs: keyMap(screens, (s) => {
- const WrappedComponent = React.memo(({ navigation, route }: any) => {
- navigation.addListener("focus", () => navigation.setOptions(s.options));
- return
- });
-
- return {
- type: "route",
- title: () => s.title,
- icon: s.icon ? getAssetIDByName(s.icon) : null,
- screen: {
- // TODO: This is bad, we should not re-convert the key casing
- // For some context, just using the key here would make the route key be VENDETTA_CUSTOM_PAGE in you tab, which breaks compat with panels UI navigation
- route: lodash.chain(s.key).camelCase().upperFirst().value(),
- getComponent: () => WrappedComponent,
- }
- }
- }),
- };
-};
diff --git a/src/ui/settings/index.ts b/src/ui/settings/index.ts
deleted file mode 100644
index 8fd59b0..0000000
--- a/src/ui/settings/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import patchPanels from "@ui/settings/patches/panels";
-import patchYou from "@ui/settings/patches/you";
-
-export default function initSettings() {
- const patches = [
- patchPanels(),
- patchYou(),
- ]
-
- return () => patches.forEach(p => p?.());
-}
diff --git a/src/ui/settings/pages/AssetBrowser.tsx b/src/ui/settings/pages/AssetBrowser.tsx
deleted file mode 100644
index 139c769..0000000
--- a/src/ui/settings/pages/AssetBrowser.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { ReactNative as RN } from "@metro/common";
-import { all } from "@ui/assets";
-import { Forms, Search, ErrorBoundary } from "@ui/components";
-import AssetDisplay from "@ui/settings/components/AssetDisplay";
-
-const { FormDivider } = Forms;
-
-export default function AssetBrowser() {
- const [search, setSearch] = React.useState("");
-
- return (
-
-
- setSearch(v)}
- placeholder="Search"
- />
- a.name.includes(search) || a.id.toString() === search)}
- renderItem={({ item }) => }
- ItemSeparatorComponent={FormDivider}
- keyExtractor={item => item.name}
- />
-
-
- )
-}
\ No newline at end of file
diff --git a/src/ui/settings/pages/Developer.tsx b/src/ui/settings/pages/Developer.tsx
deleted file mode 100644
index 880f6b0..0000000
--- a/src/ui/settings/pages/Developer.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import { ReactNative as RN, NavigationNative } from "@metro/common";
-import { findByProps } from "@metro/filters";
-import { connectToDebugger, connectToRDT, socket } from "@lib/debug";
-import { BundleUpdaterManager } from "@lib/native";
-import { useProxy } from "@lib/storage";
-import { showToast } from "@ui/toasts";
-import { getAssetIDByName } from "@ui/assets";
-import { Forms, Tabs, ErrorBoundary } from "@ui/components";
-import settings, { loaderConfig } from "@lib/settings";
-import AssetBrowser from "@ui/settings/pages/AssetBrowser";
-import Secret from "@ui/settings/pages/Secret";
-
-const { Stack, TableRow, TableRowIcon, TableSwitchRow, TableRowGroup, TextInput, Slider } = Tabs;
-const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
-const { showSimpleActionSheet } = findByProps("showSimpleActionSheet");
-
-export default function Developer() {
- const navigation = NavigationNative.useNavigation();
-
- useProxy(settings);
- useProxy(loaderConfig);
-
- return (
-
-
-
-
- {
- settings.debugBridgeEnabled = v;
- try {
- v ? connectToDebugger(settings.debuggerUrl) : socket.close();
- } catch {}
- }}
- />
-
- {
- settings.debuggerUrl = v;
- }}
- />
-
-
- {window.__vendetta_loader?.features.loaderConfig &&
- showToast("I was too lazy to edit the native side for this - maisy")}
- />
- {
- settings.rdtEnabled = v;
- if (v) connectToRDT();
- }}
- />
- showToast("Why is this even needed - maisy")}
- />
-
- {
- loaderConfig.customLoadUrl.url = v;
- }}
- />
-
- }
-
- {
- settings.errorBoundaryEnabled = v;
- }}
- />
- showSimpleActionSheet({
- key: "ErrorBoundaryTools",
- header: {
- title: "Which ErrorBoundary do you want to trip?",
- icon: ,
- onClose: () => hideActionSheet(),
- },
- options: [
- // @ts-expect-error
- // Of course, to trigger an error, we need to do something incorrectly. The below will do!
- { label: "Bound", onPress: () => navigation.push("VendettaCustomPage", { render: () => }) },
- { label: "Discord", isDestructive: true, onPress: () => navigation.push("VendettaCustomPage", { noErrorBoundary: true }) },
- ],
- })}
- arrow
- />
-
-
- }
- />
-
- {
- settings.inspectionDepth = v;
- }}
- minimumValue={1}
- maximumValue={6}
- step={1}
- />
-
- }
- onPress={() => navigation.push("VendettaCustomPage", {
- render: Secret,
- })}
- arrow
- />
-
-
- }
- onPress={() => BundleUpdaterManager.reload()}
- arrow
- />
- }
- onPress={() => window.gc?.()}
- arrow
- />
- }
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "Asset Browser",
- render: AssetBrowser,
- })}
- arrow
- />
-
-
-
-
- )
-}
diff --git a/src/ui/settings/pages/General.tsx b/src/ui/settings/pages/General.tsx
deleted file mode 100644
index c625186..0000000
--- a/src/ui/settings/pages/General.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { ReactNative as RN, NavigationNative, url } from "@metro/common";
-import { DISCORD_SERVER, GITHUB } from "@lib/constants";
-import { setSafeMode } from "@lib/debug";
-import { useProxy } from "@lib/storage";
-import { plugins } from "@lib/plugins";
-import { themes } from "@lib/themes";
-import { showToast } from "@ui/toasts";
-import { getAssetIDByName } from "@ui/assets";
-import { Tabs, ErrorBoundary } from "@ui/components";
-import settings from "@lib/settings";
-import Developer from "@ui/settings/pages/Developer";
-import Secret from "@ui/settings/pages/Secret";
-
-const { Stack, TableRow, TableRowIcon, TableSwitchRow, TableRowGroup } = Tabs;
-
-export default function General() {
- const navigation = NavigationNative.useNavigation();
-
- useProxy(settings);
- useProxy(plugins);
- useProxy(themes);
-
- return (
-
-
-
-
- }
- value={settings.safeMode?.enabled}
- onValueChange={(v: boolean) => {
- setSafeMode(v);
- // hack
- settings.safeMode!.enabled = v;
- }}
- />
-
-
- }
- onPress={() => navigation.push("VendettaCustomPage", {
- render: Secret,
- })}
- arrow
- />
- }
- onPress={() => navigation.push("VendettaCustomPage", { title: "Development Settings", render: Developer })}
- arrow
- />
-
-
- }
- trailing={}
- />
- }
- trailing={}
- />
-
-
- }
- onPress={() => url.openDeeplink(DISCORD_SERVER)}
- arrow
- />
- }
- onPress={() => url.openURL(GITHUB)}
- arrow
- />
- }
- onPress={() => showToast("nuh uh")}
- arrow
- />
-
-
-
-
- )
-}
diff --git a/src/ui/settings/pages/Plugins.tsx b/src/ui/settings/pages/Plugins.tsx
deleted file mode 100644
index 222cbb4..0000000
--- a/src/ui/settings/pages/Plugins.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Plugin } from "@types";
-import { useProxy } from "@lib/storage";
-import { plugins } from "@lib/plugins";
-import settings from "@lib/settings";
-import AddonPage from "@ui/settings/components/AddonPage";
-import PluginCard from "@ui/settings/components/PluginCard";
-
-export default function Plugins() {
- useProxy(settings)
-
- return (
-
- items={plugins}
- safeModeMessage="You are in Recovery Mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page."
- card={PluginCard}
- />
- )
-}
diff --git a/src/ui/settings/pages/Secret.tsx b/src/ui/settings/pages/Secret.tsx
deleted file mode 100644
index 754d7e6..0000000
--- a/src/ui/settings/pages/Secret.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { ReactNative as RN } from "@metro/common";
-import { ErrorBoundary } from "@ui/components";
-
-export default function General() {
- return (
-
-
-
- )
-}
diff --git a/src/ui/settings/pages/Themes.tsx b/src/ui/settings/pages/Themes.tsx
deleted file mode 100644
index 4aeedab..0000000
--- a/src/ui/settings/pages/Themes.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Theme, ButtonColors } from "@types";
-import { useProxy } from "@lib/storage";
-import { themes } from "@lib/themes";
-import { Button, TabulatedScreen } from "@ui/components";
-import settings from "@lib/settings";
-import AddonPage from "@ui/settings/components/AddonPage";
-import ThemeCard from "@ui/settings/components/ThemeCard";
-import Secret from "@ui/settings/pages/Secret";
-
-export default function Themes() {
- useProxy(settings);
-
- return (
-
- items={themes}
- safeModeMessage={`You are in Recovery Mode, meaning themes have been temporarily disabled.${settings.safeMode?.currentThemeId ? " If a theme appears to be causing the issue, you can press below to disable it persistently." : ""}`}
- safeModeExtras={settings.safeMode?.currentThemeId ?