ESLint hops in

This commit is contained in:
amsyarasyiq 2024-03-06 01:57:59 +08:00
parent 77b78071d7
commit 3692cc41c8
79 changed files with 2926 additions and 472 deletions

77
.eslintrc.json Normal file
View file

@ -0,0 +1,77 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"ignorePatterns": [ "dist", "browser" ],
"plugins": [
"@typescript-eslint",
"simple-import-sort",
"path-alias",
"unused-imports"
],
"settings": {
"import/resolver": {
"alias": {
"map": [
[ "@lib/*", "./src/lib/*" ],
[ "@core/*", "./src/core/*" ],
[ "@metro/*", "src/lib/metro/*"],
[ "@ui/*", "src/lib/ui/*"],
[ "@types", "src/lib/utils/types.ts"]
],
"extensions": [".ts", ".js", ".jsx", ".tsx", ".json"]
}
}
},
"rules": {
"quotes": [ "error", "double", { "avoidEscape": true } ],
"jsx-quotes": [ "error", "prefer-double" ],
"no-mixed-spaces-and-tabs": "error",
"indent": [ "error", 4, { "SwitchCase": 1 } ],
"arrow-parens": [ "error", "as-needed" ],
"eol-last": [ "error", "always" ],
"func-call-spacing": [ "error", "never" ],
"no-multi-spaces": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
"semi": [ "error", "always" ],
"semi-style": [ "error", "last" ],
"space-in-parens": [ "error", "never" ],
"block-spacing": [ "error", "always" ],
"object-curly-spacing": [ "error", "always" ],
"eqeqeq": [ "error", "always", { "null": "ignore" } ],
"spaced-comment": [ "error", "always", { "markers": [ "!" ] } ],
"yoda": "error",
"prefer-destructuring": [ "error", { "object": true, "array": false } ],
"operator-assignment": [ "error", "always" ],
"no-useless-computed-key": "error",
"no-unneeded-ternary": [ "error", { "defaultAssignment": false } ],
"no-invalid-regexp": "error",
"no-constant-condition": [ "error", { "checkLoops": false } ],
"no-duplicate-imports": "error",
"no-extra-semi": "error",
"dot-notation": "error",
"no-useless-escape": [ "error" ],
"no-fallthrough": "error",
"for-direction": "error",
"no-async-promise-executor": "error",
"no-cond-assign": "error",
"no-dupe-else-if": "error",
"no-duplicate-case": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-prototype-builtins": "error",
"no-regex-spaces": "error",
"no-shadow-restricted-names": "error",
"no-unexpected-multiline": "error",
"no-unsafe-optional-chaining": "error",
"no-useless-backreference": "error",
"use-isnan": "error",
"prefer-const": "error",
"prefer-spread": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"path-alias/no-relative": "error",
"unused-imports/no-unused-imports": "error"
}
}

View file

@ -1,8 +0,0 @@
{
"printWidth": 240,
"tabWidth": 4,
"singleQuote": false,
"jsxSingleQuote": false,
"bracketSpacing": true,
"useTabs": false
}

16
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[typescriptreact]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"javascript.format.semicolons": "insert",
"typescript.format.semicolons": "insert",
"typescript.preferences.quoteStyle": "double",
"javascript.preferences.quoteStyle": "double"
}

View file

@ -1 +0,0 @@
github: [maisymoe, wingio]

View file

@ -2,8 +2,8 @@
import swc from "@swc/core"; import swc from "@swc/core";
import { execSync } from "child_process"; import { execSync } from "child_process";
import esbuild from "esbuild"; import esbuild from "esbuild";
import { copyFile, copyFileSync } from "fs"; import { copyFileSync } from "fs";
import { readFile, writeFile } from "fs/promises"; import { readFile } from "fs/promises";
import { createServer } from "http"; import { createServer } from "http";
import path from "path"; import path from "path";
import { argv } from "process"; import { argv } from "process";
@ -36,8 +36,11 @@ try {
loader: { ".png": "dataurl" }, loader: { ".png": "dataurl" },
legalComments: "none", legalComments: "none",
alias: { alias: {
"@*": "src*", "@lib/*": "src/lib/*",
"@types": "src/utils/types.ts" "@core/*": "src/core/*",
"@metro/*": "src/lib/metro/*",
"@ui/*": "src/lib/ui/*",
"@types": "src/utils/types.ts",
}, },
plugins: [ plugins: [
{ {
@ -45,12 +48,12 @@ try {
setup: async build => { setup: async build => {
build.onStart(() => { build.onStart(() => {
console.clear(); console.clear();
console.log(`Building with commit hash "${commitHash}", isRelease="${isRelease}"`) console.log(`Building with commit hash "${commitHash}", isRelease="${isRelease}"`);
}); });
build.onEnd(result => { build.onEnd(result => {
const { outfile } = build.initialOptions; const { outfile } = build.initialOptions;
copyFileSync(outfile, path.resolve(outfile, "..", "pyondetta.js")) copyFileSync(outfile, path.resolve(outfile, "..", "pyondetta.js"));
console.log(`Built with ${result.errors?.length} errors!`); console.log(`Built with ${result.errors?.length} errors!`);
}); });
} }
@ -59,12 +62,12 @@ try {
name: "swc", name: "swc",
setup(build) { setup(build) {
let timeString = Number(new Date).toString(36); let timeString = Number(new Date).toString(36);
build.onStart(() => { build.onStart(() => {
timeString = Number(new Date).toString(36); timeString = Number(new Date).toString(36);
console.log(`swc plugin: time-string="${timeString}"`); console.log(`swc plugin: time-string="${timeString}"`);
}) });
build.onLoad({ filter: /\.[jt]sx?$/ }, async args => { build.onLoad({ filter: /\.[jt]sx?$/ }, async args => {
const result = await swc.transformFile(args.path, { const result = await swc.transformFile(args.path, {
jsc: { jsc: {

View file

@ -6,7 +6,8 @@
"dev": "node build.mjs", "dev": "node build.mjs",
"build": "node build.mjs --release", "build": "node build.mjs --release",
"serve": "node build.mjs --serve", "serve": "node build.mjs --serve",
"watch": "node build.mjs --watch" "watch": "node build.mjs --watch",
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx"
}, },
"keywords": [ "keywords": [
"discord", "discord",
@ -26,13 +27,22 @@
"@types/node": "^20.11.21", "@types/node": "^20.11.21",
"@types/react": "18.2.60", "@types/react": "18.2.60",
"@types/react-native": "0.72.3", "@types/react-native": "0.72.3",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"@typescript-eslint/typescript-estree": "^7.1.1",
"esbuild": "^0.17.19", "esbuild": "^0.17.19",
"eslint": "^8.57.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-unused-imports": "^3.1.0",
"moment": "2.22.2", "moment": "2.22.2",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"typescript-transform-paths": "^3.4.7" "typescript-transform-paths": "^3.4.7"
}, },
"dependencies": { "dependencies": {
"@swc/helpers": "0.5.0", "@swc/helpers": "0.5.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"fuzzysort": "^2.0.4", "fuzzysort": "^2.0.4",
"intl-messageformat": "^10.5.11", "intl-messageformat": "^10.5.11",
"spitroast": "^1.4.3" "spitroast": "^1.4.3"
@ -45,4 +55,4 @@
] ]
} }
} }
} }

2355
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
import { Strings } from "@/core/i18n"; import { Strings } from "@core/i18n";
import { ApplicationCommand, ApplicationCommandOptionType } from "@lib/api/commands/types";
import { getDebugInfo } from "@lib/debug"; import { getDebugInfo } from "@lib/debug";
import { messageUtil } from "@metro/common"; import { messageUtil } from "@metro/common";
import { ApplicationCommand, ApplicationCommandOptionType } from "@/lib/api/commands/types";
export default () => <ApplicationCommand>{ export default () => <ApplicationCommand>{
name: "debug", name: "debug",
@ -31,4 +31,4 @@ export default () => <ApplicationCommand>{
messageUtil.sendMessage(ctx.channel.id, { content }); messageUtil.sendMessage(ctx.channel.id, { content });
} }
} }
} };

View file

@ -1,7 +1,7 @@
import { findByProps } from "@metro/filters"; import { Strings } from "@core/i18n";
import { ApplicationCommand, ApplicationCommandOptionType } from "@lib/api/commands/types";
import { messageUtil } from "@metro/common"; import { messageUtil } from "@metro/common";
import { Strings } from "@/core/i18n"; import { findByProps } from "@metro/filters";
import { ApplicationCommand, ApplicationCommandOptionType } from "@/lib/api/commands/types";
const util = findByProps("inspect"); const util = findByProps("inspect");
const AsyncFunction = (async () => void 0).constructor; const AsyncFunction = (async () => void 0).constructor;
@ -38,4 +38,4 @@ export default () => <ApplicationCommand>{
messageUtil.sendBotMessage(ctx.channel.id, wrapInJSCodeblock(err?.stack ?? err)); messageUtil.sendBotMessage(ctx.channel.id, wrapInJSCodeblock(err?.stack ?? err));
} }
} }
} };

View file

@ -1,7 +1,7 @@
import { messageUtil } from "@metro/common"; import { Strings } from "@core/i18n";
import { ApplicationCommand, ApplicationCommandOptionType } from "@lib/api/commands/types";
import { plugins as pluginStorage } from "@lib/managers/plugins"; import { plugins as pluginStorage } from "@lib/managers/plugins";
import { Strings } from "@/core/i18n"; import { messageUtil } from "@metro/common";
import { ApplicationCommand, ApplicationCommandOptionType } from "@/lib/api/commands/types";
export default () => <ApplicationCommand>{ export default () => <ApplicationCommand>{
name: "plugins", name: "plugins",
@ -38,4 +38,4 @@ export default () => <ApplicationCommand>{
messageUtil.sendMessage(ctx.channel.id, { content }); messageUtil.sendMessage(ctx.channel.id, { content });
} }
} }
} };

View file

@ -6,10 +6,10 @@
* - @pylixonly * - @pylixonly
*/ */
import { FluxDispatcher, moment } from "@metro/common";
import logger from "@lib/utils/logger"; import logger from "@lib/utils/logger";
import { FluxDispatcher, moment } from "@metro/common";
function onDispatch({ locale }: { locale: string }) { function onDispatch({ locale }: { locale: string; }) {
// Timestamps // Timestamps
try { try {
moment.locale(locale.toLowerCase()); moment.locale(locale.toLowerCase());
@ -22,5 +22,5 @@ function onDispatch({ locale }: { locale: string }) {
} }
export default () => { export default () => {
FluxDispatcher.subscribe("I18N_LOAD_SUCCESS", onDispatch) FluxDispatcher.subscribe("I18N_LOAD_SUCCESS", onDispatch);
}; };

View file

@ -1,4 +1,5 @@
import { findByName } from "@/lib/metro/filters"; import { findByName } from "@lib/metro/filters";
import langDefault from "./default.json"; import langDefault from "./default.json";
const IntlMessageFormat = findByName("MessageFormat") as typeof import("intl-messageformat").default; const IntlMessageFormat = findByName("MessageFormat") as typeof import("intl-messageformat").default;
@ -11,7 +12,10 @@ export const Strings = new Proxy({}, {
} }
}) as Record<I18nKey, string>; }) as Record<I18nKey, string>;
export function formatString<T = void>(key: I18nKey, val: Record<string, T>) { type FRet<T> = T extends string ? string : string | T | (string | T)[];
export function formatString<T = void>(key: I18nKey, val: Record<string, T>): FRet<T> {
const str = Strings[key]; const str = Strings[key];
// @ts-ignore
return new IntlMessageFormat(str).format(val); return new IntlMessageFormat(str).format(val);
} }

View file

@ -7,4 +7,4 @@ export function initCorePlugins() {
}); });
return () => unloads.forEach(m => typeof m === "function" && m()); return () => unloads.forEach(m => typeof m === "function" && m());
} }

View file

@ -1,14 +1,14 @@
import { findByName, findByProps } from "@metro/filters"; import { formatString, Strings } from "@core/i18n";
import { DISCORD_SERVER_ID, PLUGINS_CHANNEL_ID, THEMES_CHANNEL_ID, HTTP_REGEX_MULTI, PROXY_PREFIX } from "@lib/utils/constants"; import { getAssetIDByName } from "@lib/api/assets";
import { isThemeSupported } from "@lib/api/native/loader";
import { after } from "@lib/api/patcher"; import { after } from "@lib/api/patcher";
import { useProxy } from "@lib/api/storage";
import { installPlugin, plugins, removePlugin } from "@lib/managers/plugins"; import { installPlugin, plugins, removePlugin } from "@lib/managers/plugins";
import { installTheme, removeTheme, themes } from "@lib/managers/themes"; import { installTheme, removeTheme, themes } from "@lib/managers/themes";
import { findInReactTree } from "@lib/utils"; import { findInReactTree } from "@lib/utils";
import { getAssetIDByName } from "@/lib/api/assets"; import { DISCORD_SERVER_ID, HTTP_REGEX_MULTI, PLUGINS_CHANNEL_ID, PROXY_PREFIX, THEMES_CHANNEL_ID } from "@lib/utils/constants";
import { findByName, findByProps } from "@metro/filters";
import { showToast } from "@ui/toasts"; import { showToast } from "@ui/toasts";
import { useProxy } from "@/lib/api/storage";
import { isThemeSupported } from "@/lib/api/native/loader";
import { Strings, formatString } from "@/core/i18n";
type PostType = "Plugin" | "Theme"; type PostType = "Plugin" | "Theme";
@ -35,7 +35,7 @@ const postMap = {
return isInstalled ? removeTheme(url) : installTheme(url); return isInstalled ? removeTheme(url) : installTheme(url);
}, },
} }
} };
function useExtractThreadContent(thread: any, _firstMessage = null, actionSheet = false): ([PostType, string]) | void { function useExtractThreadContent(thread: any, _firstMessage = null, actionSheet = false): ([PostType, string]) | void {
if (thread.guild_id !== DISCORD_SERVER_ID) return; if (thread.guild_id !== DISCORD_SERVER_ID) return;
@ -50,7 +50,7 @@ function useExtractThreadContent(thread: any, _firstMessage = null, actionSheet
const { firstMessage } = actionSheet ? useFirstForumPostMessage(thread) : { firstMessage: _firstMessage }; const { firstMessage } = actionSheet ? useFirstForumPostMessage(thread) : { firstMessage: _firstMessage };
let urls = firstMessage?.content?.match(HTTP_REGEX_MULTI)?.filter(postMap[postType].urlsFilter); const urls = firstMessage?.content?.match(HTTP_REGEX_MULTI)?.filter(postMap[postType].urlsFilter);
if (!urls || !urls[0]) return; if (!urls || !urls[0]) return;
// Sync with lib/managers/plugins // Sync with lib/managers/plugins
@ -82,14 +82,14 @@ function useInstaller(thread: any, firstMessage = null, actionSheet = false): [t
} }
}; };
return [false, postType, isInstalled, isInstalling, installOrRemove] return [false, postType, isInstalled, isInstalling, installOrRemove];
} }
const actionSheetPatch = () => after("default", ForumPostLongPressActionSheet, ([{ thread }], res) => { const actionSheetPatch = () => after("default", ForumPostLongPressActionSheet, ([{ thread }], res) => {
const [shouldReturn, postType, installed, loading, installOrRemove] = useInstaller(thread); const [shouldReturn, postType, installed, loading, installOrRemove] = useInstaller(thread);
if (shouldReturn) return; if (shouldReturn) return;
const actions = findInReactTree(res, (t) => t?.[0]?.key); const actions = findInReactTree(res, t => t?.[0]?.key);
const ActionsSection = actions[0].type; const ActionsSection = actions[0].type;
actions.unshift(<ActionsSection key="install"> actions.unshift(<ActionsSection key="install">
@ -118,14 +118,14 @@ const installButtonPatch = () => after("MostCommonForumPostReaction", forumReact
icon={getAssetIDByName(installed ? "ic_message_delete" : "DownloadIcon")} icon={getAssetIDByName(installed ? "ic_message_delete" : "DownloadIcon")}
style={{ marginLeft: 8 }} style={{ marginLeft: 8 }}
/> />
</> </>;
}); });
export default () => { export default () => {
const patches = [ const patches = [
actionSheetPatch(), actionSheetPatch(),
installButtonPatch() installButtonPatch()
] ];
return () => patches.map(p => p()); return () => patches.map(p => p());
} };

View file

@ -8,4 +8,4 @@ export default function onLoad() {
patches.push(patchUrl()); patches.push(patchUrl());
return () => patches.forEach(p => p()); return () => patches.forEach(p => p());
}; }

View file

@ -1,16 +1,16 @@
import { findByProps, find } from "@metro/filters"; import { formatString, Strings } from "@core/i18n";
import { ReactNative as RN, channels, url } from "@metro/common"; import { getAssetIDByName } from "@lib/api/assets";
import { PROXY_PREFIX, THEMES_CHANNEL_ID } from "@lib/utils/constants"; import { isThemeSupported } from "@lib/api/native/loader";
import { after, instead } from "@lib/api/patcher"; import { after, instead } from "@lib/api/patcher";
import { installPlugin } from "@lib/managers/plugins"; import { installPlugin } from "@lib/managers/plugins";
import { installTheme } from "@lib/managers/themes"; import { installTheme } from "@lib/managers/themes";
import { PROXY_PREFIX, THEMES_CHANNEL_ID } from "@lib/utils/constants";
import { channels, url } from "@metro/common";
import { find, findByProps } from "@metro/filters";
import { showConfirmationAlert } from "@ui/alerts"; import { showConfirmationAlert } from "@ui/alerts";
import { getAssetIDByName } from "@/lib/api/assets";
import { showToast } from "@ui/toasts"; import { showToast } from "@ui/toasts";
import { isThemeSupported } from "@/lib/api/native/loader";
import { Strings, formatString } from "@/core/i18n";
const showSimpleActionSheet = find((m) => m?.showSimpleActionSheet && !Object.getOwnPropertyDescriptor(m, "showSimpleActionSheet")?.get); const showSimpleActionSheet = find(m => m?.showSimpleActionSheet && !Object.getOwnPropertyDescriptor(m, "showSimpleActionSheet")?.get);
const handleClick = findByProps("handleClick"); const handleClick = findByProps("handleClick");
const { openURL } = url; const { openURL } = url;
const { getChannelId } = channels; const { getChannelId } = channels;
@ -38,7 +38,7 @@ export default () => {
const patches = new Array<Function>(); const patches = new Array<Function>();
patches.push( patches.push(
after("showSimpleActionSheet", showSimpleActionSheet, (args) => { after("showSimpleActionSheet", showSimpleActionSheet, args => {
if (args[0].key !== "LongPressUrl") return; if (args[0].key !== "LongPressUrl") return;
const { const {
header: { title: url }, header: { title: url },
@ -77,5 +77,5 @@ export default () => {
}) })
); );
return () => patches.forEach((p) => p()); return () => patches.forEach(p => p());
}; };

View file

@ -5,4 +5,4 @@ const mapAllSettled = <T>(item: T) => Promise.resolve(item).then(allSettledFulfi
export const allSettled = <T extends unknown[]>(iterator: T) => Promise.all(Array.from(iterator).map(mapAllSettled)); export const allSettled = <T extends unknown[]>(iterator: T) => Promise.all(Array.from(iterator).map(mapAllSettled));
// @ts-ignore // @ts-ignore
Promise.allSettled ??= allSettled; Promise.allSettled ??= allSettled;

View file

@ -1,34 +1,36 @@
import { NavigationNative, ReactNative as RN, clipboard } from "@metro/common"; import { formatString, Strings } from "@core/i18n";
import { useProxy } from "@/lib/api/storage"; import { CardWrapper } from "@core/ui/components/Card";
import { HelpMessage, ErrorBoundary, Search } from "@ui/components"; import { getAssetIDByName } from "@lib/api/assets";
import { CardWrapper } from "@/core/ui/components/Card"; import { useProxy } from "@lib/api/storage";
import { findByProps } from "@lib/metro/filters";
import { settings } from "@lib/settings"; import { settings } from "@lib/settings";
import { findByProps } from "@/lib/metro/filters"; import { HTTP_REGEX_MULTI } from "@lib/utils/constants";
import { HTTP_REGEX_MULTI } from "@/lib/utils/constants"; import { clipboard, ReactNative as RN } from "@metro/common";
import { showInputAlert } from "@ui/alerts"; import { showInputAlert } from "@ui/alerts";
import { getAssetIDByName } from "@/lib/api/assets"; import { ErrorBoundary, HelpMessage, Search } from "@ui/components";
import { Strings, formatString } from "@/core/i18n";
import fuzzysort from "fuzzysort"; import fuzzysort from "fuzzysort";
interface AddonPageProps<T> { interface AddonPageProps<T> {
title: string; title: string;
fetchFunction: (url: string) => Promise<void>; fetchFunction: (url: string) => Promise<void>;
items: Record<string, T & { id: string }>; items: Record<string, T & { id: string; }>;
safeModeMessage: string; safeModeMessage: string;
safeModeExtras?: JSX.Element | JSX.Element[]; safeModeExtras?: JSX.Element | JSX.Element[];
card: React.ComponentType<CardWrapper<T>>; card: React.ComponentType<CardWrapper<T>>;
} }
function getItemsByQuery<T extends { id?: string }>(items: T[], query: string): T[] { function getItemsByQuery<T extends { id?: string; }>(items: T[], query: string): T[] {
if (!query) return items; if (!query) return items;
return fuzzysort.go(query, items, { keys: [ return fuzzysort.go(query, items, {
"id", keys: [
"manifest.name", "id",
"manifest.description", "manifest.name",
"manifest.authors.0.name", "manifest.description",
"manifest.authors.1.name" "manifest.authors.0.name",
]}).map(r => r.obj); "manifest.authors.1.name"
]
}).map(r => r.obj);
} }
const reanimated = findByProps("useSharedValue"); const reanimated = findByProps("useSharedValue");
@ -68,13 +70,13 @@ export default function AddonPage<T>({ title, fetchFunction, items, safeModeMess
data={getItemsByQuery(Object.values(items), search)} data={getItemsByQuery(Object.values(items), search)}
renderItem={({ item, index }) => <CardComponent item={item} index={index} />} renderItem={({ item, index }) => <CardComponent item={item} index={index} />}
/> />
<FloatingActionButton <FloatingActionButton
text={formatString("INSTALL_TITLE", { title })} text={formatString("INSTALL_TITLE", { title })}
icon={getAssetIDByName("DownloadIcon")} icon={getAssetIDByName("DownloadIcon")}
state={{ collapseText }} state={{ collapseText }}
onPress={() => { onPress={() => {
// from ./InstallButton.tsx // from ./InstallButton.tsx
clipboard.getString().then((content) => clipboard.getString().then(content =>
showInputAlert({ showInputAlert({
title: formatString("INSTALL_TITLE", { title }), title: formatString("INSTALL_TITLE", { title }),
initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "", initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",
@ -83,9 +85,9 @@ export default function AddonPage<T>({ title, fetchFunction, items, safeModeMess
confirmText: Strings.INSTALL, confirmText: Strings.INSTALL,
cancelText: Strings.CANCEL, cancelText: Strings.CANCEL,
}) })
) );
}} }}
/> />
</ErrorBoundary> </ErrorBoundary>
) );
} }

View file

@ -1,9 +1,9 @@
import { ReactNative as RN, clipboard } from "@metro/common"; import { Asset } from "@lib/api/assets";
import { showToast } from "@ui/toasts"; import { clipboard, ReactNative as RN } from "@metro/common";
import { Forms } from "@ui/components"; import { Forms } from "@ui/components";
import { Asset } from "@/lib/api/assets"; import { showToast } from "@ui/toasts";
interface AssetDisplayProps { asset: Asset } interface AssetDisplayProps { asset: Asset; }
const { FormRow } = Forms; const { FormRow } = Forms;
@ -17,5 +17,5 @@ export default function AssetDisplay({ asset }: AssetDisplayProps) {
showToast.showCopyToClipboard(); showToast.showCopyToClipboard();
}} }}
/> />
) );
} }

View file

@ -1,8 +1,8 @@
import ContextMenu from "@ui/components/ContextMenu"; import { getAssetIDByName } from "@lib/api/assets";
import { ReactNative as RN, stylesheet } from "@metro/common"; import { ReactNative as RN, stylesheet } from "@metro/common";
import { findByProps } from "@metro/filters"; import { findByProps } from "@metro/filters";
import { getAssetIDByName } from "@/lib/api/assets";
import { semanticColors } from "@ui/color"; import { semanticColors } from "@ui/color";
import ContextMenu from "@ui/components/ContextMenu";
const { TableRow, TableRowIcon, TableSwitchRow, TableCheckboxRow, TableRowGroup } = findByProps("TableRow"); const { TableRow, TableRowIcon, TableSwitchRow, TableCheckboxRow, TableRowGroup } = findByProps("TableRow");
@ -33,7 +33,7 @@ const styles = stylesheet.createThemedStyleSheet({
marginLeft: 5, marginLeft: 5,
tintColor: semanticColors?.INTERACTIVE_NORMAL, tintColor: semanticColors?.INTERACTIVE_NORMAL,
} }
}) });
interface Action { interface Action {
icon: string; icon: string;
@ -77,43 +77,43 @@ export default function Card(props: CardProps) {
return ( return (
<RN.View style={[styles.card, { marginTop: props.index !== 0 ? 10 : 0 }]}> <RN.View style={[styles.card, { marginTop: props.index !== 0 ? 10 : 0 }]}>
<TableRowGroup> <TableRowGroup>
{props.toggleType && (props.toggleType === "switch" ? {props.toggleType && (props.toggleType === "switch" ?
<TableSwitchRow <TableSwitchRow
{...headerProps} {...headerProps}
value={props.toggleValue} value={props.toggleValue}
onValueChange={props.onToggleChange} onValueChange={props.onToggleChange}
/> : props.toggleType === "radio" ? /> : props.toggleType === "radio" ?
<TableCheckboxRow <TableCheckboxRow
{...headerProps} {...headerProps}
onPress={() => { onPress={() => {
pressableState = !pressableState; pressableState = !pressableState;
props.onToggleChange?.(pressableState) props.onToggleChange?.(pressableState);
}} }}
checked={props.toggleValue} checked={props.toggleValue}
/> : undefined) || <TableRow {...headerProps} />} /> : undefined) || <TableRow {...headerProps} />}
<TableRow <TableRow
label={props.descriptionLabel} label={props.descriptionLabel}
trailing={ trailing={
<RN.View style={styles.actions}> <RN.View style={styles.actions}>
{props.overflowActions && {props.overflowActions &&
<ContextMenu <ContextMenu
triggerOnLongPress={false} triggerOnLongPress={false}
items={props.overflowActions.map(i => ({ items={props.overflowActions.map(i => ({
label: i.label, label: i.label,
iconSource: getAssetIDByName(i.icon), iconSource: getAssetIDByName(i.icon),
action: i.onPress action: i.onPress
}))} }))}
align="auto" align="auto"
title={props.overflowTitle!!} title={props.overflowTitle!!}
children={(props) => <RN.TouchableOpacity {...props}> children={props => <RN.TouchableOpacity {...props}>
<RN.Image style={styles.icon} source={getAssetIDByName("ic_more_24px")} /> <RN.Image style={styles.icon} source={getAssetIDByName("ic_more_24px")} />
</RN.TouchableOpacity>} </RN.TouchableOpacity>}
/> />
} }
</RN.View> </RN.View>
} }
/> />
</TableRowGroup> </TableRowGroup>
</RN.View> </RN.View>
) );
} }

View file

@ -1,9 +1,9 @@
import { ReactNative as RN, stylesheet, clipboard } from "@metro/common"; import { Strings } from "@core/i18n";
import { getAssetIDByName } from "@lib/api/assets";
import { HTTP_REGEX_MULTI } from "@lib/utils/constants"; import { HTTP_REGEX_MULTI } from "@lib/utils/constants";
import { clipboard, ReactNative as RN, stylesheet } from "@metro/common";
import { showInputAlert } from "@ui/alerts"; import { showInputAlert } from "@ui/alerts";
import { getAssetIDByName } from "@/lib/api/assets";
import { semanticColors } from "@ui/color"; import { semanticColors } from "@ui/color";
import { Strings } from "@/core/i18n";
const styles = stylesheet.createThemedStyleSheet({ const styles = stylesheet.createThemedStyleSheet({
icon: { icon: {
@ -20,7 +20,7 @@ interface InstallButtonProps {
export default function InstallButton({ alertTitle, installFunction: fetchFunction }: InstallButtonProps) { export default function InstallButton({ alertTitle, installFunction: fetchFunction }: InstallButtonProps) {
return ( return (
<RN.TouchableOpacity onPress={() => <RN.TouchableOpacity onPress={() =>
clipboard.getString().then((content) => clipboard.getString().then(content =>
showInputAlert({ showInputAlert({
title: alertTitle, title: alertTitle,
initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "", initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",

View file

@ -1,13 +1,13 @@
import { NavigationNative, clipboard } from "@metro/common"; import { formatString, Strings } from "@core/i18n";
import { removePlugin, startPlugin, stopPlugin, getSettings, fetchPlugin, BunnyPlugin } from "@lib/managers/plugins"; import Card, { CardWrapper } from "@core/ui/components/Card";
import { getAssetIDByName } from "@lib/api/assets";
import { MMKVManager } from "@lib/api/native/modules"; import { MMKVManager } from "@lib/api/native/modules";
import { getAssetIDByName } from "@/lib/api/assets"; import { useProxy } from "@lib/api/storage";
import { showToast } from "@ui/toasts"; import { BunnyPlugin, fetchPlugin, getSettings, removePlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
import { ButtonColors } from "@lib/utils/types";
import { clipboard, NavigationNative } from "@metro/common";
import { showConfirmationAlert } from "@ui/alerts"; import { showConfirmationAlert } from "@ui/alerts";
import Card, { CardWrapper } from "@/core/ui/components/Card"; import { showToast } from "@ui/toasts";
import { useProxy } from "@/lib/api/storage";
import { ButtonColors } from "@/lib/utils/types";
import { Strings, formatString } from "@/core/i18n";
async function stopThenStart(plugin: BunnyPlugin, callback: Function) { async function stopThenStart(plugin: BunnyPlugin, callback: Function) {
if (plugin.enabled) stopPlugin(plugin.id, false); if (plugin.enabled) stopPlugin(plugin.id, false);
@ -60,7 +60,7 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
showToast("Successfully refetched plugin.", getAssetIDByName("toast_image_saved")); showToast("Successfully refetched plugin.", getAssetIDByName("toast_image_saved"));
}).catch(() => { }).catch(() => {
showToast("Failed to refetch plugin!", getAssetIDByName("Small")); showToast("Failed to refetch plugin!", getAssetIDByName("Small"));
}) });
}); });
}, },
}, },
@ -72,7 +72,7 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
showToast.showCopyToClipboard(); showToast.showCopyToClipboard();
} }
}, },
{ {
icon: "ic_download_24px", icon: "ic_download_24px",
label: plugin.update ? Strings.DISABLE_UPDATES : Strings.ENABLE_UPDATES, label: plugin.update ? Strings.DISABLE_UPDATES : Strings.ENABLE_UPDATES,
onPress: () => { onPress: () => {
@ -108,7 +108,7 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
isDestructive: true, isDestructive: true,
onPress: () => showConfirmationAlert({ onPress: () => showConfirmationAlert({
title: Strings.HOLD_UP, title: Strings.HOLD_UP,
content: formatString("ARE_YOU_SURE_TO_DELETE_PLUGIN", {name: plugin.manifest.name}), content: formatString("ARE_YOU_SURE_TO_DELETE_PLUGIN", { name: plugin.manifest.name }),
confirmText: Strings.DELETE, confirmText: Strings.DELETE,
cancelText: Strings.CANCEL, cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED, confirmColor: ButtonColors.RED,
@ -124,5 +124,5 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
} }
]} ]}
/> />
) );
} }

View file

@ -1,10 +1,10 @@
import { NavigationNative } from "@metro/common"; import { Strings } from "@core/i18n";
import { useProxy } from "@/lib/api/storage"; import { getScreens } from "@core/ui/settings/data";
import { getAssetIDByName } from "@/lib/api/assets"; import { getAssetIDByName } from "@lib/api/assets";
import { getScreens } from "@/core/ui/settings/data"; import { useProxy } from "@lib/api/storage";
import { ErrorBoundary, Forms } from "@ui/components";
import { settings } from "@lib/settings"; import { settings } from "@lib/settings";
import { Strings } from "@/core/i18n"; import { NavigationNative } from "@metro/common";
import { ErrorBoundary, Forms } from "@ui/components";
const { FormRow, FormSection, FormDivider } = Forms; const { FormRow, FormSection, FormDivider } = Forms;
@ -12,7 +12,7 @@ export default function SettingsSection() {
const navigation = NavigationNative.useNavigation(); const navigation = NavigationNative.useNavigation();
useProxy(settings); useProxy(settings);
const screens = getScreens() const screens = getScreens();
return ( return (
<ErrorBoundary> <ErrorBoundary>
@ -30,5 +30,5 @@ export default function SettingsSection() {
))} ))}
</FormSection> </FormSection>
</ErrorBoundary> </ErrorBoundary>
) );
} }

View file

@ -1,14 +1,14 @@
import { clipboard } from "@metro/common"; import { formatString, Strings } from "@core/i18n";
import { Theme, applyTheme, fetchTheme, removeTheme, selectTheme, themes } from "@lib/managers/themes"; import Card, { CardWrapper } from "@core/ui/components/Card";
import { useProxy } from "@/lib/api/storage"; import { getAssetIDByName } from "@lib/api/assets";
import { BundleUpdaterManager } from "@lib/api/native/modules"; import { BundleUpdaterManager } from "@lib/api/native/modules";
import { getAssetIDByName } from "@/lib/api/assets"; import { useProxy } from "@lib/api/storage";
import { applyTheme, fetchTheme, removeTheme, selectTheme, Theme, themes } from "@lib/managers/themes";
import { settings } from "@lib/settings";
import { ButtonColors } from "@lib/utils/types";
import { clipboard } from "@metro/common";
import { showConfirmationAlert } from "@ui/alerts"; import { showConfirmationAlert } from "@ui/alerts";
import { showToast } from "@ui/toasts"; import { showToast } from "@ui/toasts";
import { settings } from "@lib/settings";
import Card, { CardWrapper } from "@/core/ui/components/Card";
import { ButtonColors } from "@/lib/utils/types";
import { Strings, formatString } from "@/core/i18n";
async function selectAndApply(value: boolean, id: string) { async function selectAndApply(value: boolean, id: string) {
try { try {
@ -18,8 +18,8 @@ async function selectAndApply(value: boolean, id: string) {
// TODO: Implement native side reload-less & check if it's applied by 100% // TODO: Implement native side reload-less & check if it's applied by 100%
showToast(Strings.THEMES_RELOAD_FOR_CHANGES, getAssetIDByName("yellow-alert")); showToast(Strings.THEMES_RELOAD_FOR_CHANGES, getAssetIDByName("yellow-alert"));
} catch (e: any) { } catch (e: any) {
console.error("Error while selectAndApply,", e) console.error("Error while selectAndApply,", e);
} }
} }
export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) { export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
@ -30,7 +30,7 @@ export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
// This is needed because of React™ // This is needed because of React™
if (removed) return null; if (removed) return null;
const authors = theme.data.authors; const { authors } = theme.data;
return ( return (
<Card <Card
@ -57,7 +57,7 @@ export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
cancelText: Strings.CANCEL, cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED, confirmColor: ButtonColors.RED,
onConfirm: () => BundleUpdaterManager.reload(), onConfirm: () => BundleUpdaterManager.reload(),
}) });
} else { } else {
showToast(Strings.THEME_REFETCH_SUCCESSFUL, getAssetIDByName("toast_image_saved")); showToast(Strings.THEME_REFETCH_SUCCESSFUL, getAssetIDByName("toast_image_saved"));
} }
@ -85,7 +85,7 @@ export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
cancelText: Strings.CANCEL, cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED, confirmColor: ButtonColors.RED,
onConfirm: () => { onConfirm: () => {
removeTheme(theme.id).then((wasSelected) => { removeTheme(theme.id).then(wasSelected => {
setRemoved(true); setRemoved(true);
if (wasSelected) selectAndApply(false, theme.id); if (wasSelected) selectAndApply(false, theme.id);
}).catch((e: Error) => { }).catch((e: Error) => {
@ -96,5 +96,5 @@ export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
}, },
]} ]}
/> />
) );
} }

View file

@ -1,8 +1,8 @@
import { getAssetIDByName } from "@lib/api/assets";
import { findByProps } from "@lib/metro/filters";
import { clipboard } from "@metro/common"; import { clipboard } from "@metro/common";
import { getAssetIDByName } from "@/lib/api/assets";
import { showToast } from "@ui/toasts";
import { Forms } from "@ui/components"; import { Forms } from "@ui/components";
import { findByProps } from "@/lib/metro/filters"; import { showToast } from "@ui/toasts";
interface VersionProps { interface VersionProps {
label: string; label: string;
@ -15,7 +15,7 @@ const { TableRow } = findByProps("TableRow");
const { FormText } = Forms; const { FormText } = Forms;
export default function Version({ label, version, icon, padding }: VersionProps) { export default function Version({ label, version, icon, padding }: VersionProps) {
return ( return (
<TableRow <TableRow
style={padding && { paddingHorizontal: 15, paddingVertical: -15 }} style={padding && { paddingHorizontal: 15, paddingVertical: -15 }}
label={label} label={label}
@ -26,5 +26,5 @@ export default function Version({ label, version, icon, padding }: VersionProps)
showToast.showCopyToClipboard(); showToast.showCopyToClipboard();
}} }}
/> />
) );
} }

View file

@ -1,23 +1,23 @@
import { ReactNative as RN, NavigationNative, stylesheet, lodash } from "@metro/common"; import { Strings } from "@core/i18n";
import InstallButton from "@core/ui/components/InstallButton";
import Developer from "@core/ui/settings/pages/Developer";
import General from "@core/ui/settings/pages/General";
import Plugins from "@core/ui/settings/pages/Plugins";
import Themes from "@core/ui/settings/pages/Themes";
import { getAssetIDByName } from "@lib/api/assets";
import { isThemeSupported } from "@lib/api/native/loader";
import { useProxy } from "@lib/api/storage";
import { installPlugin } from "@lib/managers/plugins"; import { installPlugin } from "@lib/managers/plugins";
import { installTheme } from "@lib/managers/themes"; import { installTheme } from "@lib/managers/themes";
import { findByProps } from "@lib/metro/filters";
import { settings } from "@lib/settings";
import { without } from "@lib/utils";
import { PROXY_PREFIX } from "@lib/utils/constants";
import { NavigationNative, ReactNative as RN, stylesheet } from "@metro/common";
import { showConfirmationAlert } from "@ui/alerts"; import { showConfirmationAlert } from "@ui/alerts";
import { semanticColors } from "@ui/color"; import { semanticColors } from "@ui/color";
import { showToast } from "@ui/toasts";
import { without } from "@lib/utils";
import { getAssetIDByName } from "@/lib/api/assets";
import { settings } from "@lib/settings";
import ErrorBoundary from "@ui/components/ErrorBoundary"; import ErrorBoundary from "@ui/components/ErrorBoundary";
import InstallButton from "@/core/ui/components/InstallButton"; import { showToast } from "@ui/toasts";
import General from "@/core/ui/settings/pages/General";
import Plugins from "@/core/ui/settings/pages/Plugins";
import Themes from "@/core/ui/settings/pages/Themes";
import Developer from "@/core/ui/settings/pages/Developer";
import { PROXY_PREFIX } from "@/lib/utils/constants";
import { findByProps } from "@/lib/metro/filters";
import { isThemeSupported } from "@/lib/api/native/loader";
import { Strings } from "@/core/i18n";
import { useProxy } from "@/lib/api/storage";
const { useSafeAreaInsets } = findByProps("useSafeAreaInsets"); const { useSafeAreaInsets } = findByProps("useSafeAreaInsets");
@ -31,10 +31,10 @@ interface Screen {
render: React.ComponentType<any>; render: React.ComponentType<any>;
} }
const useStyles = stylesheet.createStyles({ const useStyles = stylesheet.createStyles({
container: { container: {
flex: 1, flex: 1,
backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY
} }
}); });
@ -58,7 +58,7 @@ export const getScreens = (): Screen[] => [
headerRight: () => ( headerRight: () => (
<InstallButton <InstallButton
alertTitle={Strings.INSTALL_PLUGIN} alertTitle={Strings.INSTALL_PLUGIN}
installFunction={async (input) => { installFunction={async input => {
if (!input.startsWith(PROXY_PREFIX) && !settings.developerSettings) if (!input.startsWith(PROXY_PREFIX) && !settings.developerSettings)
setImmediate(() => showConfirmationAlert({ setImmediate(() => showConfirmationAlert({
title: Strings.MODAL_UNPROXIED_PLUGIN_HEADER, title: Strings.MODAL_UNPROXIED_PLUGIN_HEADER,
@ -99,16 +99,16 @@ export const getScreens = (): Screen[] => [
key: "VendettaCustomPage", key: "VendettaCustomPage",
title: "Vendetta Page", title: "Vendetta Page",
shouldRender: () => false, shouldRender: () => false,
render: ({ render: PageView, noErrorBoundary, ...options }: { render: React.ComponentType; noErrorBoundary: boolean } & Record<string, object>) => { render: ({ render: PageView, noErrorBoundary, ...options }: { render: React.ComponentType; noErrorBoundary: boolean; } & Record<string, object>) => {
const navigation = NavigationNative.useNavigation(); const navigation = NavigationNative.useNavigation();
navigation.addListener("focus", () => navigation.setOptions(without(options, "render", "noErrorBoundary"))); navigation.addListener("focus", () => navigation.setOptions(without(options, "render", "noErrorBoundary")));
return noErrorBoundary ? <PageView /> : <ErrorBoundary><PageView /></ErrorBoundary> return noErrorBoundary ? <PageView /> : <ErrorBoundary><PageView /></ErrorBoundary>;
}, },
}, },
]; ];
export const getPanelsScreens = () => keyMap(getScreens(), (s) => ({ export const getPanelsScreens = () => keyMap(getScreens(), s => ({
title: s.title, title: s.title,
render: s.render, render: s.render,
...s.options, ...s.options,
@ -128,12 +128,12 @@ export const getYouData = () => {
}), }),
titleConfig: keyMap(screens, "title"), titleConfig: keyMap(screens, "title"),
relationships: keyMap(screens, null), relationships: keyMap(screens, null),
rendererConfigs: keyMap(screens, (s) => { rendererConfigs: keyMap(screens, s => {
const WrappedComponent = React.memo(({ navigation, route }: any) => { const WrappedComponent = React.memo(({ navigation, route }: any) => {
const styles = useStyles(); const styles = useStyles();
const { bottom: paddingBottom } = useSafeAreaInsets(); const { bottom: paddingBottom } = useSafeAreaInsets();
navigation.addListener("focus", () => navigation.setOptions(s.options)); navigation.addListener("focus", () => navigation.setOptions(s.options));
return <RN.View style={[{ paddingBottom }, styles.container]}><s.render {...route.params} /></RN.View> return <RN.View style={[{ paddingBottom }, styles.container]}><s.render {...route.params} /></RN.View>;
}); });
return { return {
@ -145,7 +145,7 @@ export const getYouData = () => {
route: s.key, route: s.key,
getComponent: () => WrappedComponent, getComponent: () => WrappedComponent,
} }
} };
}), }),
}; };
}; };

View file

@ -1,12 +1,12 @@
// TODO: make settings patches part of the api // TODO: make settings patches part of the api
import patchPanels from "@/core/ui/settings/patches/panels"; import patchPanels from "@core/ui/settings/patches/panels";
import patchYou from "@/core/ui/settings/patches/you"; import patchYou from "@core/ui/settings/patches/you";
export default function initSettings() { export default function initSettings() {
const patches = [ const patches = [
patchPanels(), patchPanels(),
patchYou(), patchYou(),
] ];
return () => patches.forEach(p => p?.()); return () => patches.forEach(p => p?.());
} }

View file

@ -1,7 +1,7 @@
import AssetDisplay from "@core/ui/components/AssetDisplay";
import { all } from "@lib/api/assets";
import { ReactNative as RN } from "@metro/common"; import { ReactNative as RN } from "@metro/common";
import { all } from "@/lib/api/assets"; import { ErrorBoundary, Forms, Search } from "@ui/components";
import { Forms, Search, ErrorBoundary } from "@ui/components";
import AssetDisplay from "@/core/ui/components/AssetDisplay";
const { FormDivider } = Forms; const { FormDivider } = Forms;
@ -24,5 +24,5 @@ export default function AssetBrowser() {
/> />
</RN.View> </RN.View>
</ErrorBoundary> </ErrorBoundary>
) );
} }

View file

@ -1,14 +1,14 @@
import { ReactNative as RN, NavigationNative, stylesheet } from "@metro/common"; import { Strings } from "@core/i18n";
import { findByProps } from "@metro/filters"; import AssetBrowser from "@core/ui/settings/pages/AssetBrowser";
import { getAssetIDByName } from "@lib/api/assets";
import { getReactDevToolsProp, getReactDevToolsVersion, isLoaderConfigSupported, isReactDevToolsPreloaded } from "@lib/api/native/loader";
import { useProxy } from "@lib/api/storage";
import { connectToDebugger } from "@lib/debug"; import { connectToDebugger } from "@lib/debug";
import { useProxy } from "@/lib/api/storage"; import { loaderConfig, settings } from "@lib/settings";
import { getAssetIDByName } from "@/lib/api/assets"; import { NavigationNative, ReactNative as RN, stylesheet } from "@metro/common";
import { ErrorBoundary, Forms } from "@ui/components"; import { findByProps } from "@metro/filters";
import { settings, loaderConfig } from "@lib/settings";
import AssetBrowser from "@/core/ui/settings/pages/AssetBrowser";
import { semanticColors } from "@ui/color"; import { semanticColors } from "@ui/color";
import { getReactDevToolsProp, getReactDevToolsVersion, isLoaderConfigSupported, isReactDevToolsPreloaded } from "@/lib/api/native/loader"; import { ErrorBoundary, Forms } from "@ui/components";
import { Strings } from "@/core/i18n";
const { Stack, TableRow, TableSwitchRow, TableRowGroup, TextInput } = findByProps("TableRow"); const { Stack, TableRow, TableSwitchRow, TableRowGroup, TextInput } = findByProps("TableRow");
const { FormText } = Forms; const { FormText } = Forms;
@ -99,7 +99,7 @@ export default function Developer() {
render: AssetBrowser, render: AssetBrowser,
})} })}
/> />
<TableRow <TableRow
arrow arrow
label="ErrorBoundary Tools" label="ErrorBoundary Tools"
icon={<TableRow.Icon source={getAssetIDByName("ic_warning_24px")} />} icon={<TableRow.Icon source={getAssetIDByName("ic_warning_24px")} />}
@ -111,7 +111,7 @@ export default function Developer() {
onClose: () => hideActionSheet(), onClose: () => hideActionSheet(),
}, },
options: [ options: [
// @ts-expect-error // @ts-expect-error
// Of course, to trigger an error, we need to do something incorrectly. The below will do! // Of course, to trigger an error, we need to do something incorrectly. The below will do!
{ label: Strings.BUNNY, onPress: () => navigation.push("VendettaCustomPage", { render: () => <undefined /> }) }, { label: Strings.BUNNY, onPress: () => navigation.push("VendettaCustomPage", { render: () => <undefined /> }) },
{ label: "Discord", isDestructive: true, onPress: () => navigation.push("VendettaCustomPage", { noErrorBoundary: true }) }, { label: "Discord", isDestructive: true, onPress: () => navigation.push("VendettaCustomPage", { noErrorBoundary: true }) },
@ -122,5 +122,5 @@ export default function Developer() {
</Stack> </Stack>
</RN.ScrollView> </RN.ScrollView>
</ErrorBoundary> </ErrorBoundary>
) );
} }

View file

@ -1,14 +1,14 @@
import { ReactNative as RN, stylesheet, url } from "@metro/common"; import { Strings } from "@core/i18n";
import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants"; import Version from "@core/ui/components/Version";
import { getAssetIDByName } from "@lib/api/assets";
import { useProxy } from "@lib/api/storage";
import { getDebugInfo, toggleSafeMode } from "@lib/debug"; import { getDebugInfo, toggleSafeMode } from "@lib/debug";
import { useProxy } from "@/lib/api/storage";
import { getAssetIDByName } from "@/lib/api/assets";
import { Summary } from "@ui/components";
import { settings } from "@lib/settings";
import Version from "@/core/ui/components/Version";
import { findByProps } from "@lib/metro/filters"; import { findByProps } from "@lib/metro/filters";
import { settings } from "@lib/settings";
import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants";
import { ReactNative as RN, stylesheet, url } from "@metro/common";
import { semanticColors } from "@ui/color"; import { semanticColors } from "@ui/color";
import { Strings } from "@/core/i18n"; import { Summary } from "@ui/components";
const { Stack, TableRow, TableSwitchRow, TableRowGroup } = findByProps("TableRow"); const { Stack, TableRow, TableSwitchRow, TableRowGroup } = findByProps("TableRow");
const debugInfo = getDebugInfo(); const debugInfo = getDebugInfo();
@ -18,7 +18,7 @@ const styles = stylesheet.createThemedStyleSheet({
flex: 1, flex: 1,
backgroundColor: semanticColors.BACKGROUND_MOBILE_SECONDARY, backgroundColor: semanticColors.BACKGROUND_MOBILE_SECONDARY,
} }
}) });
export default function General() { export default function General() {
useProxy(settings); useProxy(settings);
@ -137,5 +137,5 @@ export default function General() {
</TableRowGroup> </TableRowGroup>
</Stack> </Stack>
</RN.ScrollView> </RN.ScrollView>
) );
} }

View file

@ -1,12 +1,12 @@
import { useProxy } from "@/lib/api/storage"; import { Strings } from "@core/i18n";
import AddonPage from "@core/ui/components/AddonPage";
import PluginCard from "@core/ui/components/PluginCard";
import { useProxy } from "@lib/api/storage";
import { BunnyPlugin, installPlugin, plugins } from "@lib/managers/plugins"; import { BunnyPlugin, installPlugin, plugins } from "@lib/managers/plugins";
import { settings } from "@lib/settings"; import { settings } from "@lib/settings";
import AddonPage from "@/core/ui/components/AddonPage";
import PluginCard from "@/core/ui/components/PluginCard";
import { Strings } from "@/core/i18n";
export default function Plugins() { export default function Plugins() {
useProxy(settings) useProxy(settings);
return ( return (
<AddonPage<BunnyPlugin> <AddonPage<BunnyPlugin>
@ -16,5 +16,5 @@ export default function Plugins() {
safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS} safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS}
card={PluginCard} card={PluginCard}
/> />
) );
} }

View file

@ -1,11 +1,11 @@
import { useProxy } from "@/lib/api/storage"; import { Strings } from "@core/i18n";
import { Theme, installTheme, themes } from "@lib/managers/themes"; import AddonPage from "@core/ui/components/AddonPage";
import { Button } from "@ui/components"; import ThemeCard from "@core/ui/components/ThemeCard";
import { useProxy } from "@lib/api/storage";
import { installTheme, Theme, themes } from "@lib/managers/themes";
import { settings } from "@lib/settings"; import { settings } from "@lib/settings";
import AddonPage from "@/core/ui/components/AddonPage"; import { ButtonColors } from "@lib/utils/types";
import ThemeCard from "@/core/ui/components/ThemeCard"; import { Button } from "@ui/components";
import { ButtonColors } from "@/lib/utils/types";
import { Strings } from "@/core/i18n";
export default function Themes() { export default function Themes() {
useProxy(settings); useProxy(settings);
@ -27,5 +27,5 @@ export default function Themes() {
/> : undefined} /> : undefined}
card={ThemeCard} card={ThemeCard}
/> />
) );
} }

View file

@ -1,9 +1,9 @@
import { i18n } from "@metro/common"; import SettingsSection from "@core/ui/components/SettingsSection";
import { findByName } from "@metro/filters"; import { getPanelsScreens } from "@core/ui/settings/data";
import { after } from "@lib/api/patcher"; import { after } from "@lib/api/patcher";
import { findInReactTree } from "@lib/utils"; import { findInReactTree } from "@lib/utils";
import { getPanelsScreens } from "@/core/ui/settings/data"; import { i18n } from "@metro/common";
import SettingsSection from "@/core/ui/components/SettingsSection"; import { findByName } from "@metro/filters";
const screensModule = findByName("getScreens", false); const screensModule = findByName("getScreens", false);
const settingsModule = findByName("UserSettingsOverviewWrapper", false); const settingsModule = findByName("UserSettingsOverviewWrapper", false);
@ -27,9 +27,9 @@ export default function patchPanels() {
// TODO: Rewrite this whole patch, the index hasn't been properly found for months now // TODO: Rewrite this whole patch, the index hasn't been properly found for months now
patches.push(after("render", Overview.type.prototype, (_, { props: { children } }) => { patches.push(after("render", Overview.type.prototype, (_, { props: { children } }) => {
const titles = [i18n.Messages["BILLING_SETTINGS"], i18n.Messages["PREMIUM_SETTINGS"]]; const titles = [i18n.Messages.BILLING_SETTINGS, i18n.Messages.PREMIUM_SETTINGS];
//! Fix for Android 174201 and iOS 42188 //! Fix for Android 174201 and iOS 42188
children = findInReactTree(children, i => i.children?.[1].type?.name === "FormSection").children; ({ children } = findInReactTree(children, i => i.children?.[1].type?.name === "FormSection"));
const index = children.findIndex((c: any) => titles.includes(c?.props.label)); const index = children.findIndex((c: any) => titles.includes(c?.props.label));
children.splice(index === -1 ? 4 : index, 0, <SettingsSection />); children.splice(index === -1 ? 4 : index, 0, <SettingsSection />);
})); }));

View file

@ -1,8 +1,8 @@
import { findByProps } from "@metro/filters"; import { Strings } from "@core/i18n";
import { getScreens, getYouData } from "@core/ui/settings/data";
import { after, before } from "@lib/api/patcher"; import { after, before } from "@lib/api/patcher";
import { getScreens, getYouData } from "@/core/ui/settings/data";
import { i18n } from "@lib/metro/common"; import { i18n } from "@lib/metro/common";
import { Strings } from "@/core/i18n"; import { findByProps } from "@metro/filters";
export default function patchYou() { export default function patchYou() {
const patches = new Array<Function>; const patches = new Array<Function>;
@ -53,7 +53,7 @@ function oldYouPatch(patches: Function[]) {
const oldRelationships = miscModule.SETTING_RELATIONSHIPS; const oldRelationships = miscModule.SETTING_RELATIONSHIPS;
const oldRendererConfigs = miscModule.SETTING_RENDERER_CONFIGS; const oldRendererConfigs = miscModule.SETTING_RENDERER_CONFIGS;
miscModule.SETTING_RELATIONSHIPS = { ...oldRelationships, ...data.relationships }; miscModule.SETTING_RELATIONSHIPS = { ...oldRelationships, ...data.relationships };
miscModule.SETTING_RENDERER_CONFIGS = { ...oldRendererConfigs, ...data.rendererConfigs }; miscModule.SETTING_RENDERER_CONFIGS = { ...oldRendererConfigs, ...data.rendererConfigs };
@ -78,7 +78,7 @@ function newYouPatch(patches: Function[]) {
patches.push(before("type", settingsListComponents.SearchableSettingsList, ([{ sections }]) => manipulateSections(sections, data.getLayout()))); patches.push(before("type", settingsListComponents.SearchableSettingsList, ([{ sections }]) => manipulateSections(sections, data.getLayout())));
patches.push(after("getSettingListSearchResultItems", gettersModule, (_, ret) => { patches.push(after("getSettingListSearchResultItems", gettersModule, (_, ret) => {
ret.forEach((s: any) => screens.some(b => b.key === s.setting) && (s.breadcrumbs = [Strings.BUNNY])) ret.forEach((s: any) => screens.some(b => b.key === s.setting) && (s.breadcrumbs = [Strings.BUNNY]));
})); }));
const oldRendererConfig = settingConstantsModule.SETTING_RENDERER_CONFIG; const oldRendererConfig = settingConstantsModule.SETTING_RENDERER_CONFIG;
@ -102,5 +102,5 @@ function manipulateSections(sections: any[], layout: any) {
// Upload Logs button be gone // Upload Logs button be gone
const supportCategory = sections.find((i: any) => isLabel(i, i18n.Messages.SUPPORT)); const supportCategory = sections.find((i: any) => isLabel(i, i18n.Messages.SUPPORT));
if (supportCategory) supportCategory.settings = supportCategory.settings.filter((s: string) => s !== "UPLOAD_DEBUG_LOGS") if (supportCategory) supportCategory.settings = supportCategory.settings.filter((s: string) => s !== "UPLOAD_DEBUG_LOGS");
} }

View file

@ -1,19 +1,19 @@
import patcher from "@lib/api/patcher"; import * as assets from "@lib/api/assets";
import * as commands from "@lib/api/commands";
import { getVendettaLoaderIdentity } from "@lib/api/native/loader"; import { getVendettaLoaderIdentity } from "@lib/api/native/loader";
import { settings, loaderConfig } from "@lib/settings"; import patcher from "@lib/api/patcher";
import * as storage from "@lib/api/storage";
import * as debug from "@lib/debug"; import * as debug from "@lib/debug";
import * as plugins from "@lib/managers/plugins"; import * as plugins from "@lib/managers/plugins";
import * as themes from "@lib/managers/themes"; import * as themes from "@lib/managers/themes";
import * as commands from "@/lib/api/commands"; import { loaderConfig, settings } from "@lib/settings";
import * as storage from "@/lib/api/storage"; import * as utils from "@lib/utils";
import * as metro from "@metro/filters";
import * as common from "@metro/common"; import * as common from "@metro/common";
import * as metro from "@metro/filters";
import * as alerts from "@ui/alerts";
import * as color from "@ui/color";
import * as components from "@ui/components"; import * as components from "@ui/components";
import * as toasts from "@ui/toasts"; import * as toasts from "@ui/toasts";
import * as alerts from "@ui/alerts";
import * as assets from "@/lib/api/assets";
import * as color from "@ui/color";
import * as utils from "@lib/utils";
export const createVendettaObject = (unloads: any[]): any => ({ export const createVendettaObject = (unloads: any[]): any => ({
patcher: { patcher: {
@ -21,7 +21,7 @@ export const createVendettaObject = (unloads: any[]): any => ({
after: patcher.after, after: patcher.after,
instead: patcher.instead instead: patcher.instead
}, },
metro: { metro: {
find: (filter: (m: any) => boolean) => metro.find(filter), find: (filter: (m: any) => boolean) => metro.find(filter),
findAll: (filter: (m: any) => boolean) => metro.findAll(filter), findAll: (filter: (m: any) => boolean) => metro.findAll(filter),
findByProps: (...props: any) => metro.findByProps(...props), findByProps: (...props: any) => metro.findByProps(...props),
@ -60,9 +60,9 @@ export const createVendettaObject = (unloads: any[]): any => ({
constants: { constants: {
DISCORD_SERVER: "https://discord.gg/n9QQ4XhhJP", DISCORD_SERVER: "https://discord.gg/n9QQ4XhhJP",
GITHUB: "https://github.com/vendetta-mod", GITHUB: "https://github.com/vendetta-mod",
PROXY_PREFIX: "https://vd-plugins.github.io/proxy", PROXY_PREFIX: "https://vd-plugins.github.io/proxy",
HTTP_REGEX: /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/, HTTP_REGEX: /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$/,
HTTP_REGEX_MULTI: /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g, HTTP_REGEX_MULTI: /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&//=]*)/g,
DISCORD_SERVER_ID: "1015931589865246730", DISCORD_SERVER_ID: "1015931589865246730",
PLUGINS_CHANNEL_ID: "1091880384561684561", PLUGINS_CHANNEL_ID: "1091880384561684561",
THEMES_CHANNEL_ID: "1091880434939482202", THEMES_CHANNEL_ID: "1091880434939482202",

View file

@ -1,5 +1,5 @@
import { instead } from "spitroast";
import { version } from "bunny-build"; import { version } from "bunny-build";
import { instead } from "spitroast";
// @ts-ignore - shut up fr // @ts-ignore - shut up fr
globalThis.window = globalThis; globalThis.window = globalThis;
@ -8,11 +8,11 @@ const init = (async () => {
try { try {
// This logs in the native logging implementation, e.g. logcat // This logs in the native logging implementation, e.g. logcat
console.log("Hello from Bunny! Pyon!"); console.log("Hello from Bunny! Pyon!");
// Make 'freeze' and 'seal' do nothing // Make 'freeze' and 'seal' do nothing
Object.freeze = Object.seal = Object; Object.freeze = Object.seal = Object;
await import(".").then((m) => m.default()); await import(".").then(m => m.default());
} catch (e) { } catch (e) {
const { ClientInfoManager } = require("@lib/api/native/modules"); const { ClientInfoManager } = require("@lib/api/native/modules");
const stack = e instanceof Error ? e.stack : undefined; const stack = e instanceof Error ? e.stack : undefined;
@ -50,14 +50,14 @@ if (typeof globalThis.__r !== "undefined") {
unpatch(); unpatch();
originalRequire(0); originalRequire(0);
callQueue.forEach(arg => callQueue.forEach(arg =>
batchedBridge.getCallableModule(arg[0]) batchedBridge.getCallableModule(arg[0])
&& batchedBridge.__callFunction(...arg)); && batchedBridge.__callFunction(...arg));
}; };
startDiscord(); startDiscord();
} }
var _requireFunc: any; // We can't set properties to 'this' during __r set for some reason var _requireFunc: any; // We can't set properties to 'this' during __r set for some reason
Object.defineProperties(globalThis, { Object.defineProperties(globalThis, {
@ -71,7 +71,7 @@ if (typeof globalThis.__r !== "undefined") {
onceIndexRequired(v); onceIndexRequired(v);
_requireFunc = v; _requireFunc = v;
} else return v(a); } else return v(a);
} };
} }
}, },
__d: { __d: {
@ -83,7 +83,7 @@ if (typeof globalThis.__r !== "undefined") {
} }
return this.value; return this.value;
}, },
set(v) { this.value = v } set(v) { this.value = v; }
} }
}); });
} }

View file

@ -1,15 +1,15 @@
import initFixes from "@core/fixes";
import { initCorePlugins } from "@core/plugins";
import initSettings from "@core/ui/settings";
import { patchAssets } from "@lib/api/assets";
import { patchCommands } from "@lib/api/commands";
import { isThemeSupported } from "@lib/api/native/loader";
import { patchLogHook } from "@lib/debug"; import { patchLogHook } from "@lib/debug";
import { patchCommands } from "@/lib/api/commands";
import { initPlugins } from "@lib/managers/plugins"; import { initPlugins } from "@lib/managers/plugins";
import { initThemes, patchChatBackground } from "@lib/managers/themes"; import { initThemes, patchChatBackground } from "@lib/managers/themes";
import { patchAssets } from "@/lib/api/assets";
import initSafeMode from "@ui/safeMode";
import initSettings from "@/core/ui/settings";
import initFixes from "@/core/fixes";
import logger from "@lib/utils/logger"; import logger from "@lib/utils/logger";
import initWindowObject from "@lib/windowObject"; import initWindowObject from "@lib/windowObject";
import { initCorePlugins } from "./core/plugins"; import initSafeMode from "@ui/safeMode";
import { isThemeSupported } from "./lib/api/native/loader";
export default async () => { export default async () => {
// Themes // Themes
@ -41,4 +41,4 @@ export default async () => {
// We good :) // We good :)
logger.log("Bunny is ready!"); logger.log("Bunny is ready!");
} };

View file

@ -1,5 +1,5 @@
import { assets } from "@metro/common";
import { after } from "@lib/api/patcher"; import { after } from "@lib/api/patcher";
import { assets } from "@metro/common";
export const all: Record<string, Asset> = {}; export const all: Record<string, Asset> = {};
@ -19,7 +19,7 @@ export function patchAssets() {
if (!asset) break; if (!asset) break;
if (all[asset.name]) continue; if (all[asset.name]) continue;
all[asset.name] = { ...asset, id: id }; all[asset.name] = { ...asset, id: id };
}; }
return unpatch; return unpatch;
} }
@ -27,4 +27,4 @@ export function patchAssets() {
export const find = (filter: (a: any) => void): Asset | null | undefined => Object.values(all).find(filter); 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 getAssetByName = (name: string): Asset => all[name];
export const getAssetByID = (id: number): Asset => assets.getAssetByID(id); export const getAssetByID = (id: number): Asset => assets.getAssetByID(id);
export const getAssetIDByName = (name: string) => all[name]?.id; export const getAssetIDByName = (name: string) => all[name]?.id;

View file

@ -1,6 +1,7 @@
import { commands as commandsModule } from "@metro/common";
import { after } from "@lib/api/patcher"; import { after } from "@lib/api/patcher";
import { ApplicationCommand, ApplicationCommandType, ApplicationCommandInputType } from "./types"; import { commands as commandsModule } from "@metro/common";
import { ApplicationCommand, ApplicationCommandInputType, ApplicationCommandType } from "./types";
let commands: ApplicationCommand[] = []; let commands: ApplicationCommand[] = [];
@ -11,9 +12,9 @@ export function patchCommands() {
// Register core commands // Register core commands
[ [
// require("@/core/commands/eval"), // require("@core/commands/eval"),
require("@/core/commands/debug"), require("@core/commands/debug"),
require("@/core/commands/plugins") require("@core/commands/plugins")
].forEach(r => registerCommand(r.default())); ].forEach(r => registerCommand(r.default()));
return () => { return () => {

View file

@ -65,4 +65,4 @@ export interface CommandContext {
export interface CommandResult { export interface CommandResult {
content: string; content: string;
tts?: boolean; tts?: boolean;
} }

View file

@ -1,5 +1,5 @@
export * as assets from "./assets";
export * as commands from "./commands"; export * as commands from "./commands";
export * as native from "./native"; export * as native from "./native";
export * as patcher from "./patcher";
export * as storage from "./storage"; export * as storage from "./storage";
export * as assets from "./assets";
export * as patcher from "./patcher";

View file

@ -70,4 +70,4 @@ export async function downloadFile(url: string, path: string, prefix = "pyoncord
// const data = Buffer.from(arrayBuffer).toString("base64"); // const data = Buffer.from(arrayBuffer).toString("base64");
// return void await FileManager.writeFile("documents", `${prefix}${path}`, data, "base64"); // return void await FileManager.writeFile("documents", `${prefix}${path}`, data, "base64");
// } // }

View file

@ -1,3 +1,3 @@
export * as fs from "./fs"; export * as fs from "./fs";
export * as modules from "./modules";
export * as loader from "./loader"; export * as loader from "./loader";
export * as modules from "./modules";

View file

@ -1,4 +1,4 @@
import { Theme } from "../../managers/themes"; import { Theme } from "@lib/managers/themes";
// @ts-ignore // @ts-ignore
const pyonLoaderIdentity = globalThis.__PYON_LOADER__; const pyonLoaderIdentity = globalThis.__PYON_LOADER__;
@ -15,8 +15,8 @@ export interface VendettaLoaderIdentity {
}, },
themes?: { themes?: {
prop: string; prop: string;
} };
} };
} }
export function isVendettaLoader() { export function isVendettaLoader() {
@ -33,13 +33,13 @@ function polyfillVendettaLoaderIdentity() {
const loader = { const loader = {
name: pyonLoaderIdentity.loaderName, name: pyonLoaderIdentity.loaderName,
features: {} as Record<string, any> features: {} as Record<string, any>
} };
if (isLoaderConfigSupported()) loader.features.loaderConfig = true; if (isLoaderConfigSupported()) loader.features.loaderConfig = true;
if (isSysColorsSupported()) { if (isSysColorsSupported()) {
loader.features.syscolors = { loader.features.syscolors = {
prop: "__vendetta_syscolors" prop: "__vendetta_syscolors"
} };
Object.defineProperty(globalThis, "__vendetta_syscolors", { Object.defineProperty(globalThis, "__vendetta_syscolors", {
get: () => getSysColors(), get: () => getSysColors(),
@ -49,7 +49,7 @@ function polyfillVendettaLoaderIdentity() {
if (isThemeSupported()) { if (isThemeSupported()) {
loader.features.themes = { loader.features.themes = {
prop: "__vendetta_theme" prop: "__vendetta_theme"
} };
Object.defineProperty(globalThis, "__vendetta_theme", { Object.defineProperty(globalThis, "__vendetta_theme", {
get: () => getStoredTheme(), get: () => getStoredTheme(),
@ -126,7 +126,7 @@ export function getStoredTheme(): Theme | null {
export function getThemeFilePath() { export function getThemeFilePath() {
if (isPyonLoader()) { if (isPyonLoader()) {
return "pyoncord/current-theme.json" return "pyoncord/current-theme.json";
} else if (isVendettaLoader()) { } else if (isVendettaLoader()) {
return "vendetta_theme.json"; return "vendetta_theme.json";
} }
@ -157,7 +157,7 @@ export function getReactDevToolsVersion() {
if (!isReactDevToolsPreloaded()) return null; if (!isReactDevToolsPreloaded()) return null;
if (isVendettaLoader()) { if (isVendettaLoader()) {
return vendettaLoaderIdentity!!.features.devtools!!.version return vendettaLoaderIdentity!!.features.devtools!!.version;
} }
return null; return null;

View file

@ -80,5 +80,5 @@ export const FileManager = (nmp.DCDFileManager ?? nmp.RTNFileManager) as FileMan
export const ClientInfoManager = nmp.InfoDictionaryManager ?? nmp.RTNClientInfoManager; export const ClientInfoManager = nmp.InfoDictionaryManager ?? nmp.RTNClientInfoManager;
//! 173.14 renamed this to RTNDeviceManager. //! 173.14 renamed this to RTNDeviceManager.
export const DeviceManager = nmp.DCDDeviceManager ?? nmp.RTNDeviceManager; export const DeviceManager = nmp.DCDDeviceManager ?? nmp.RTNDeviceManager;
export const BundleUpdaterManager = nmp.BundleUpdaterManager; export const { BundleUpdaterManager } = nmp;
export const ThemeManager = nmp.RTNThemeManager ?? nmp.DCDTheme; export const ThemeManager = nmp.RTNThemeManager ?? nmp.DCDTheme;

View file

@ -1,4 +1,4 @@
import * as _spitroast from "spitroast"; import * as _spitroast from "spitroast";
export * from "spitroast"; export * from "spitroast";
export default { ..._spitroast }; export default { ..._spitroast };

View file

@ -1,12 +1,12 @@
import { FileManager, MMKVManager } from "@lib/api/native/modules";
import { ReactNative as RN } from "@metro/common"; import { ReactNative as RN } from "@metro/common";
import { FileManager, MMKVManager } from "../native/modules";
export interface StorageBackend { export interface StorageBackend {
get: () => unknown | Promise<unknown>; get: () => unknown | Promise<unknown>;
set: (data: unknown) => void | Promise<void>; set: (data: unknown) => void | Promise<void>;
} }
const ILLEGAL_CHARS_REGEX = /[<>:"\/\\|?*]/g; const ILLEGAL_CHARS_REGEX = /[<>:"/\\|?*]/g;
const filePathFixer = (file: string): string => RN.Platform.select({ const filePathFixer = (file: string): string => RN.Platform.select({
default: file, default: file,
@ -16,11 +16,11 @@ const filePathFixer = (file: string): string => RN.Platform.select({
const getMMKVPath = (name: string): string => { const getMMKVPath = (name: string): string => {
if (ILLEGAL_CHARS_REGEX.test(name)) { if (ILLEGAL_CHARS_REGEX.test(name)) {
// Replace forbidden characters with hyphens // Replace forbidden characters with hyphens
name = name.replace(ILLEGAL_CHARS_REGEX, '-').replace(/-+/g, '-'); name = name.replace(ILLEGAL_CHARS_REGEX, "-").replace(/-+/g, "-");
} }
return `vd_mmkv/${name}`; return `vd_mmkv/${name}`;
} };
export const purgeStorage = async (store: string) => { export const purgeStorage = async (store: string) => {
if (await MMKVManager.getItem(store)) { if (await MMKVManager.getItem(store)) {
@ -31,7 +31,7 @@ export const purgeStorage = async (store: string) => {
if (await FileManager.fileExists(`${FileManager.getConstants().DocumentsDirPath}/${mmkvPath}`)) { if (await FileManager.fileExists(`${FileManager.getConstants().DocumentsDirPath}/${mmkvPath}`)) {
await FileManager.removeFile?.("documents", mmkvPath); await FileManager.removeFile?.("documents", mmkvPath);
} }
} };
export const createMMKVBackend = (store: string, defaultData = {}) => { export const createMMKVBackend = (store: string, defaultData = {}) => {
const mmkvPath = getMMKVPath(store); const mmkvPath = getMMKVPath(store);
@ -47,7 +47,7 @@ export const createMMKVBackend = (store: string, defaultData = {}) => {
if (oldData === "!!LARGE_VALUE!!") { if (oldData === "!!LARGE_VALUE!!") {
const cachePath = `${FileManager.getConstants().CacheDirPath}/mmkv/${store}`; const cachePath = `${FileManager.getConstants().CacheDirPath}/mmkv/${store}`;
if (await FileManager.fileExists(cachePath)) { if (await FileManager.fileExists(cachePath)) {
oldData = await FileManager.readFile(cachePath, "utf8") oldData = await FileManager.readFile(cachePath, "utf8");
} else { } else {
console.log(`${store}: Experienced data loss :(`); console.log(`${store}: Experienced data loss :(`);
oldData = defaultStr; oldData = defaultStr;
@ -67,7 +67,7 @@ export const createMMKVBackend = (store: string, defaultData = {}) => {
console.log(`Successfully migrated ${store} store from MMKV storage to fs`); console.log(`Successfully migrated ${store} store from MMKV storage to fs`);
} }
})()); })());
} };
export const createFileBackend = (file: string, defaultData = {}, migratePromise?: Promise<void>): StorageBackend => { export const createFileBackend = (file: string, defaultData = {}, migratePromise?: Promise<void>): StorageBackend => {
return { return {
@ -86,7 +86,7 @@ export const createFileBackend = (file: string, defaultData = {}, migratePromise
await FileManager.writeFile("documents", filePathFixer(file), JSON.stringify(defaultData), "utf8"); await FileManager.writeFile("documents", filePathFixer(file), JSON.stringify(defaultData), "utf8");
return JSON.parse(await FileManager.readFile(path, "utf8")); return JSON.parse(await FileManager.readFile(path, "utf8"));
}, },
set: async (data) => { set: async data => {
await migratePromise; await migratePromise;
await FileManager.writeFile("documents", filePathFixer(file), JSON.stringify(data), "utf8"); await FileManager.writeFile("documents", filePathFixer(file), JSON.stringify(data), "utf8");
} }

View file

@ -1,5 +1,5 @@
import { createEmitter, Emitter } from "@lib/utils/emitter";
import { StorageBackend } from "@lib/api/storage/backends"; import { StorageBackend } from "@lib/api/storage/backends";
import { createEmitter, Emitter } from "@lib/utils/emitter";
const emitterSymbol = Symbol.for("vendetta.storage.emitter"); const emitterSymbol = Symbol.for("vendetta.storage.emitter");
const syncAwaitSymbol = Symbol.for("vendetta.storage.accessor"); const syncAwaitSymbol = Symbol.for("vendetta.storage.accessor");
@ -59,7 +59,7 @@ export function createProxy(target: any = {}): { proxy: any; emitter: Emitter }
export function useProxy<T>(storage: T): T { export function useProxy<T>(storage: T): T {
const emitter = (storage as any)[emitterSymbol] as Emitter; const emitter = (storage as any)[emitterSymbol] as Emitter;
const [, forceUpdate] = React.useReducer((n) => ~n, 0); const [, forceUpdate] = React.useReducer(n => ~n, 0);
React.useEffect(() => { React.useEffect(() => {
const listener = () => forceUpdate(); const listener = () => forceUpdate();
@ -93,16 +93,16 @@ export function wrapSync<T extends Promise<any>>(store: T): Awaited<T> {
const awaitQueue: (() => void)[] = []; const awaitQueue: (() => void)[] = [];
const awaitInit = (cb: () => void) => (awaited ? cb() : awaitQueue.push(cb)); const awaitInit = (cb: () => void) => (awaited ? cb() : awaitQueue.push(cb));
store.then((v) => { store.then(v => {
awaited = v; awaited = v;
awaitQueue.forEach((cb) => cb()); awaitQueue.forEach(cb => cb());
}); });
return new Proxy({} as Awaited<T>, { return new Proxy({} as Awaited<T>, {
...Object.fromEntries( ...Object.fromEntries(
Object.getOwnPropertyNames(Reflect) Object.getOwnPropertyNames(Reflect)
// @ts-expect-error // @ts-expect-error
.map((k) => [k, (t: T, ...a: any[]) => Reflect[k](awaited ?? t, ...a)]) .map(k => [k, (t: T, ...a: any[]) => Reflect[k](awaited ?? t, ...a)])
), ),
get(target, prop, recv) { get(target, prop, recv) {
if (prop === syncAwaitSymbol) return awaitInit; if (prop === syncAwaitSymbol) return awaitInit;
@ -111,6 +111,6 @@ export function wrapSync<T extends Promise<any>>(store: T): Awaited<T> {
}); });
} }
export const awaitSyncWrapper = (store: any) => new Promise<void>((res) => store[syncAwaitSymbol](res)); export const awaitSyncWrapper = (store: any) => new Promise<void>(res => store[syncAwaitSymbol](res));
export * from "@lib/api/storage/backends"; export * from "@lib/api/storage/backends";

View file

@ -1,14 +1,14 @@
import { ReactNative as RN } from "@metro/common"; import { getAssetIDByName } from "@lib/api/assets";
import { getLoaderName, isThemeSupported } from "@lib/api/native/loader";
import { BundleUpdaterManager, ClientInfoManager, DeviceManager } from "@lib/api/native/modules";
import { after } from "@lib/api/patcher"; import { after } from "@lib/api/patcher";
import { getThemeFromLoader, selectTheme } from "@lib/managers/themes"; import { getThemeFromLoader, selectTheme } from "@lib/managers/themes";
import { ClientInfoManager, DeviceManager, BundleUpdaterManager } from "@lib/api/native/modules";
import { getAssetIDByName } from "@/lib/api/assets";
import { showToast } from "@ui/toasts";
import { settings } from "@lib/settings"; import { settings } from "@lib/settings";
import logger from "@lib/utils/logger"; import logger from "@lib/utils/logger";
import { getLoaderName, isThemeSupported } from "./api/native/loader"; import { ReactNative as RN } from "@metro/common";
import type { PlatformConstants } from "react-native"; import { showToast } from "@ui/toasts";
import { version } from "bunny-build"; import { version } from "bunny-build";
import type { PlatformConstants } from "react-native";
export let socket: WebSocket; export let socket: WebSocket;
export interface RNConstants extends PlatformConstants { export interface RNConstants extends PlatformConstants {
@ -30,7 +30,7 @@ export interface RNConstants extends PlatformConstants {
} }
export async function toggleSafeMode() { export async function toggleSafeMode() {
settings.safeMode = { ...settings.safeMode, enabled: !settings.safeMode?.enabled } settings.safeMode = { ...settings.safeMode, enabled: !settings.safeMode?.enabled };
if (isThemeSupported()) { if (isThemeSupported()) {
if (getThemeFromLoader()?.id) settings.safeMode!.currentThemeId = getThemeFromLoader()!.id; if (getThemeFromLoader()?.id) settings.safeMode!.currentThemeId = getThemeFromLoader()!.id;
if (settings.safeMode?.enabled) { if (settings.safeMode?.enabled) {
@ -68,7 +68,7 @@ export function connectToDebugger(url: string) {
} }
export function patchLogHook() { export function patchLogHook() {
const unpatch = after("nativeLoggingHook", globalThis, (args) => { const unpatch = after("nativeLoggingHook", globalThis, args => {
if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ message: args[0], level: args[1] })); if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ message: args[0], level: args[1] }));
logger.log(args[0]); logger.log(args[0]);
}); });
@ -76,7 +76,7 @@ export function patchLogHook() {
return () => { return () => {
socket && socket.close(); socket && socket.close();
unpatch(); unpatch();
} };
} }
export const versionHash = version; export const versionHash = version;
@ -111,7 +111,7 @@ export function getDebugInfo() {
}, },
hermes: { hermes: {
version: hermesVer, version: hermesVer,
buildType: hermesProps["Build"], buildType: hermesProps.Build,
bytecodeVersion: hermesProps["Bytecode Version"], bytecodeVersion: hermesProps["Bytecode Version"],
}, },
...RN.Platform.select( ...RN.Platform.select(
@ -151,5 +151,5 @@ export function getDebugInfo() {
} }
} }
)! )!
} };
} }

View file

@ -1,9 +1,9 @@
import { safeFetch } from "@lib/utils"; import { allSettled } from "@core/polyfills";
import { awaitSyncWrapper, createMMKVBackend, createStorage, purgeStorage, wrapSync } from "@/lib/api/storage"; import { awaitSyncWrapper, createMMKVBackend, createStorage, purgeStorage, wrapSync } from "@lib/api/storage";
import logger, { logModule } from "@lib/utils/logger";
import { settings } from "@lib/settings"; import { settings } from "@lib/settings";
import { Author } from "../utils/types"; import { safeFetch } from "@lib/utils";
import { allSettled } from "@/core/polyfills"; import logger, { logModule } from "@lib/utils/logger";
import { Author } from "@lib/utils/types";
type EvaledPlugin = { type EvaledPlugin = {
onLoad?(): void; onLoad?(): void;
@ -53,7 +53,7 @@ export async function fetchPlugin(id: string) {
try { try {
// by polymanifest spec, plugins should always specify their main file, but just in case // 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(); pluginJs = await (await safeFetch(id + (pluginManifest.main || "index.js"), { cache: "no-store" })).text();
} catch {} // Empty catch, checked below } catch { } // Empty catch, checked below
} }
if (!pluginJs && !existingPlugin) throw new Error(`Failed to fetch JS for ${id}`); if (!pluginJs && !existingPlugin) throw new Error(`Failed to fetch JS for ${id}`);
@ -88,7 +88,7 @@ export async function evalPlugin(plugin: BunnyPlugin) {
const pluginString = `vendetta=>{return ${plugin.js}}\n//# sourceURL=${plugin.id}`; const pluginString = `vendetta=>{return ${plugin.js}}\n//# sourceURL=${plugin.id}`;
const raw = (0, eval)(pluginString)(vendettaForPlugins); const raw = (0, eval)(pluginString)(vendettaForPlugins);
const ret = typeof raw == "function" ? raw() : raw; const ret = typeof raw === "function" ? raw() : raw;
return ret?.default ?? ret ?? {}; return ret?.default ?? ret ?? {};
} }
@ -152,10 +152,10 @@ export async function initPlugins() {
if (!settings.safeMode?.enabled) { if (!settings.safeMode?.enabled) {
// Loop over any plugin that is enabled, update it if allowed, then start it. // 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)))); 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. // 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)); allIds.filter(pl => !plugins[pl].enabled && plugins[pl].update).forEach(pl => fetchPlugin(pl));
}; }
return stopAllPlugins; return stopAllPlugins;
} }

View file

@ -1,13 +1,13 @@
// TODO: rewrite most of this // TODO: rewrite most of this
import { ReactNative as RN, chroma } from "@metro/common"; import { getStoredTheme, getThemeFilePath } from "@lib/api/native/loader";
import { findInReactTree, safeFetch } from "@lib/utils";
import { find, findByName, findByProps, findByStoreName } from "@metro/filters";
import { after, before, instead } from "@lib/api/patcher";
import { createFileBackend, createMMKVBackend, createStorage, wrapSync, awaitSyncWrapper } from "@/lib/api/storage";
import logger from "@lib/utils/logger";
import { ThemeManager } from "@lib/api/native/modules"; import { ThemeManager } from "@lib/api/native/modules";
import { getStoredTheme, getThemeFilePath } from "../api/native/loader"; import { after, before, instead } from "@lib/api/patcher";
import { Author } from "../utils/types"; import { awaitSyncWrapper, createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@lib/api/storage";
import { findInReactTree, safeFetch } from "@lib/utils";
import logger from "@lib/utils/logger";
import { Author } from "@lib/utils/types";
import { chroma, ReactNative as RN } from "@metro/common";
import { find, findByName, findByProps, findByStoreName } from "@metro/filters";
export interface ThemeData { export interface ThemeData {
name: string; name: string;
@ -24,7 +24,7 @@ export interface ThemeData {
* `CHAT_BACKGROUND` of semanticColors alpha value will be ignored when this is specified * `CHAT_BACKGROUND` of semanticColors alpha value will be ignored when this is specified
*/ */
alpha?: number; alpha?: number;
} };
} }
export interface Theme { export interface Theme {
@ -54,7 +54,7 @@ const semanticAlternativeMap: Record<string, string> = {
"BG_SURFACE_OVERLAY": "BACKGROUND_FLOATING", "BG_SURFACE_OVERLAY": "BACKGROUND_FLOATING",
"BG_SURFACE_OVERLAY_TMP": "BACKGROUND_FLOATING", "BG_SURFACE_OVERLAY_TMP": "BACKGROUND_FLOATING",
"BG_SURFACE_RAISED": "BACKGROUND_MOBILE_PRIMARY" "BG_SURFACE_RAISED": "BACKGROUND_MOBILE_PRIMARY"
} };
async function writeTheme(theme: Theme | {}) { async function writeTheme(theme: Theme | {}) {
if (typeof theme !== "object") throw new Error("Theme must be an object"); if (typeof theme !== "object") throw new Error("Theme must be an object");
@ -78,7 +78,7 @@ export function patchChatBackground() {
children: ret, children: ret,
})), })),
after("render", MessagesWrapper.prototype, (_, ret) => { after("render", MessagesWrapper.prototype, (_, ret) => {
const Messages = findInReactTree(ret, (x) => "HACK_fixModalInteraction" in x?.props && x?.props?.style); const Messages = findInReactTree(ret, x => x && "HACK_fixModalInteraction" in x.props && x?.props?.style);
if (Messages) if (Messages)
Messages.props.style = Object.assign( Messages.props.style = Object.assign(
RN.StyleSheet.flatten(Messages.props.style ?? {}), RN.StyleSheet.flatten(Messages.props.style ?? {}),
@ -100,7 +100,7 @@ function normalizeToHex(colorString: string): string {
const color = Number(RN.processColor(colorString)); const color = Number(RN.processColor(colorString));
return chroma.rgb( return chroma.rgb(
color >> 16 & 0xff, // red color >> 16 & 0xff, // red
color >> 8 & 0xff, // green color >> 8 & 0xff, // green
color & 0xff, // blue color & 0xff, // blue
color >> 24 & 0xff // alpha color >> 24 & 0xff // alpha
@ -110,7 +110,7 @@ function normalizeToHex(colorString: string): string {
// Process data for some compatiblity with native side // Process data for some compatiblity with native side
function processData(data: ThemeData) { function processData(data: ThemeData) {
if (data.semanticColors) { if (data.semanticColors) {
const semanticColors = data.semanticColors; const { semanticColors } = data;
for (const key in semanticColors) { for (const key in semanticColors) {
for (const index in semanticColors[key]) { for (const index in semanticColors[key]) {
@ -120,7 +120,7 @@ function processData(data: ThemeData) {
} }
if (data.rawColors) { if (data.rawColors) {
const rawColors = data.rawColors; const { rawColors } = data;
for (const key in rawColors) { for (const key in rawColors) {
data.rawColors[key] = normalizeToHex(rawColors[key]); data.rawColors[key] = normalizeToHex(rawColors[key]);
@ -227,7 +227,7 @@ function patchColor() {
configurable: true, configurable: true,
enumerable: true, enumerable: true,
get: () => { get: () => {
return enabled ? currentTheme?.data?.rawColors?.[k] ?? origRawColor[k] : origRawColor[k] return enabled ? currentTheme?.data?.rawColors?.[k] ?? origRawColor[k] : origRawColor[k];
} }
}); });
}); });
@ -258,7 +258,7 @@ function patchColor() {
}); });
// Prevent setting to real Discord settings // Prevent setting to real Discord settings
before("set", mmkvStorage, (args) => { before("set", mmkvStorage, args => {
if (!args[1]) return; if (!args[1]) return;
const key = args[0]; const key = args[0];
@ -267,11 +267,11 @@ function patchColor() {
const interceptors: Record<string, () => void> = ({ const interceptors: Record<string, () => void> = ({
SelectivelySyncedUserSettingsStore: () => { SelectivelySyncedUserSettingsStore: () => {
if (value._state?.appearance?.settings?.theme) { if (value._state?.appearance?.settings?.theme) {
const { theme } = value._state?.appearance?.settings; const { theme } = value._state?.appearance?.settings ?? {};
if (isDiscordTheme(theme)) { if (isDiscordTheme(theme)) {
vdThemeFallback = theme; vdThemeFallback = theme;
} else { } else {
value._state.appearance.settings.theme = vdThemeFallback; value._state.appearance.settings.theme = vdThemeFallback;
} }
} }
}, },
@ -282,8 +282,8 @@ function patchColor() {
selectTheme("default"); selectTheme("default");
vdThemeFallback = theme; vdThemeFallback = theme;
} else { } else {
value._state.theme = vdThemeFallback; value._state.theme = vdThemeFallback;
} }
} }
} }
}); });
@ -299,9 +299,9 @@ function patchColor() {
if (args[1] !== vdKey) return orig(...args); if (args[1] !== vdKey) return orig(...args);
const [name, colorDef] = extractInfo(vdThemeFallback, args[1]); const [name, colorDef] = extractInfo(vdThemeFallback, args[1]);
const themeIndex = vdThemeFallback === "midnight" ? 2 : vdThemeFallback === "light" ? 1 : 0; const themeIndex = vdThemeFallback === "midnight" ? 2 : vdThemeFallback === "light" ? 1 : 0;
//! As of 192.7, Tabs v2 uses BG_ semantic colors instead of BACKGROUND_ ones //! As of 192.7, Tabs v2 uses BG_ semantic colors instead of BACKGROUND_ ones
const alternativeName = semanticAlternativeMap[name] ?? name; const alternativeName = semanticAlternativeMap[name] ?? name;
@ -345,7 +345,7 @@ export function applyTheme(appliedTheme: Theme | null, fallbackTheme?: string, u
Object.keys(color.Shadow).forEach(k => color.Shadow[k][vdKey] = color.Shadow[k][fallbackTheme!!]); Object.keys(color.Shadow).forEach(k => color.Shadow[k][vdKey] = color.Shadow[k][fallbackTheme!!]);
Object.keys(color.SemanticColor).forEach(k => { Object.keys(color.SemanticColor).forEach(k => {
color.SemanticColor[k][vdKey] = { color.SemanticColor[k][vdKey] = {
...color.SemanticColor[k][fallbackTheme!!], ...color.SemanticColor[k][fallbackTheme!!],
override: appliedTheme?.data?.semanticColors?.[k]?.[0] override: appliedTheme?.data?.semanticColors?.[k]?.[0]
}; };
@ -374,4 +374,4 @@ function extractInfo(themeName: string, colorObj: any): [name: string, colorDef:
const colorDef = color.SemanticColor[propName]; const colorDef = color.SemanticColor[propName];
return [propName, colorDef[themeName]]; return [propName, colorDef[themeName]];
} }

View file

@ -18,11 +18,11 @@ function createThemedStyleSheet<T extends StyleSheet.NamedStyles<T>>(sheet: T) {
for (const key in sheet) { for (const key in sheet) {
// @ts-ignore // @ts-ignore
sheet[key] = new Proxy(ReactNative.StyleSheet.flatten(sheet[key]), { sheet[key] = new Proxy(ReactNative.StyleSheet.flatten(sheet[key]), {
get(target, prop, receiver) { get(target, prop, receiver) {
const res = Reflect.get(target, prop, receiver); const res = Reflect.get(target, prop, receiver);
return colorResolver.isSemanticColor(res) return colorResolver.isSemanticColor(res)
? colorResolver.resolveSemanticColor(ThemeStore.theme, res) ? colorResolver.resolveSemanticColor(ThemeStore.theme, res)
: res : res;
} }
}); });
} }

View file

@ -26,7 +26,7 @@ for (const key in window.modules) {
const id = key as MetroModulesIndex; const id = key as MetroModulesIndex;
const module = window.modules[id]?.publicModule?.exports; const module = window.modules[id]?.publicModule?.exports;
if (!module || module === window || module["proxygone"] === null) { if (!module || module === window || module.proxygone === null) {
blacklist(id); blacklist(id);
continue; continue;
} }
@ -44,9 +44,9 @@ const onModuleCheck = (exports: any) => {
} catch { } catch {
return args[0]; return args[0];
} }
}) });
} }
} };
// Function to filter through modules // Function to filter through modules
const filterModules = (modules: MetroModules, single = false) => (filter: (m: any) => boolean) => { const filterModules = (modules: MetroModules, single = false) => (filter: (m: any) => boolean) => {
@ -81,13 +81,13 @@ const filterModules = (modules: MetroModules, single = false) => (filter: (m: an
} }
if (!single) return found; if (!single) return found;
} };
export const modules = window.modules; export const { modules } = window;
export const find = filterModules(modules, true); export const find = filterModules(modules, true);
export const findAll = filterModules(modules); export const findAll = filterModules(modules);
const propsFilter = (props: (string | symbol)[]) => (m: any) => props.every((p) => m[p] !== undefined); 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 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 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 tNameFilter = (typeName: string, defaultExp: boolean) => (defaultExp ? (m: any) => m?.type?.name === typeName : (m: any) => m?.default?.type?.name === typeName);

View file

@ -1,3 +1,3 @@
export * as common from "./common";
export * from "./filters"; export * from "./filters";
export * as filters from "./filters"; export * as filters from "./filters";
export * as common from "./common";

View file

@ -1,4 +1,5 @@
import { createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@/lib/api/storage"; import { createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@lib/api/storage";
import { getLoaderConfigPath } from "./api/native/loader"; import { getLoaderConfigPath } from "./api/native/loader";
export interface Settings { export interface Settings {

View file

@ -1,13 +1,13 @@
import { ButtonColors } from "@lib/utils/types";
import { findByProps } from "@metro/filters"; import { findByProps } from "@metro/filters";
import InputAlert, { InputAlertProps } from "@ui/components/InputAlert"; import InputAlert, { InputAlertProps } from "@ui/components/InputAlert";
import { ButtonColors } from "@/lib/utils/types";
const Alerts = findByProps("openLazy", "close"); const Alerts = findByProps("openLazy", "close");
interface InternalConfirmationAlertOptions extends Omit<ConfirmationAlertOptions, "content"> { interface InternalConfirmationAlertOptions extends Omit<ConfirmationAlertOptions, "content"> {
content?: ConfirmationAlertOptions["content"]; content?: ConfirmationAlertOptions["content"];
body?: ConfirmationAlertOptions["content"]; body?: ConfirmationAlertOptions["content"];
}; }
export interface ConfirmationAlertOptions { export interface ConfirmationAlertOptions {
title?: string; title?: string;
@ -31,7 +31,7 @@ export function showConfirmationAlert(options: ConfirmationAlertOptions) {
internalOptions.isDismissable ??= true; internalOptions.isDismissable ??= true;
return Alerts.show(internalOptions); return Alerts.show(internalOptions);
}; }
export const showCustomAlert = (component: React.ComponentType<any>, props: any) => Alerts.openLazy({ export const showCustomAlert = (component: React.ComponentType<any>, props: any) => Alerts.openLazy({
importer: async () => () => React.createElement(component, props), importer: async () => () => React.createElement(component, props),

View file

@ -1,17 +1,17 @@
import { constants } from "@metro/common";
import { color } from "@lib/managers/themes"; import { color } from "@lib/managers/themes";
import { constants } from "@metro/common";
//! This module is only found on 165.0+, under the assumption that iOS 165.0 is the same as Android 165.0. //! 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 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 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. //* 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. // ? To stop duplication, it is now exported in our theming code.
//? These comments are preserved for historical purposes. // ? These comments are preserved for historical purposes.
// const colorModule = findByProps("colors", "meta"); // const colorModule = findByProps("colors", "meta");
//? SemanticColor and default.colors are effectively ThemeColorMap // ? SemanticColor and default.colors are effectively ThemeColorMap
export const semanticColors = (color?.default?.colors ?? constants?.ThemeColorMap); export const semanticColors = (color?.default?.colors ?? constants?.ThemeColorMap);
//? RawColor and default.unsafe_rawColors are effectively Colors // ? 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? //* 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); export const rawColors = (color?.default?.unsafe_rawColors ?? constants?.Colors);

View file

@ -1,4 +1,4 @@
import { ReactNative as RN, stylesheet, constants } from "@metro/common"; import { constants,ReactNative as RN, stylesheet } from "@metro/common";
import { semanticColors } from "@ui/color"; import { semanticColors } from "@ui/color";
export interface CodeblockProps { export interface CodeblockProps {
@ -22,14 +22,14 @@ const styles = stylesheet.createThemedStyleSheet({
}); });
// iOS doesn't support the selectable property on RN.Text... // iOS doesn't support the selectable property on RN.Text...
const InputBasedCodeblock = ({ style, children }: CodeblockProps) => <RN.TextInput editable={false} multiline style={[styles.codeBlock, style && style]} value={children} /> const InputBasedCodeblock = ({ style, children }: CodeblockProps) => <RN.TextInput editable={false} multiline style={[styles.codeBlock, style && style]} value={children} />;
const TextBasedCodeblock = ({ selectable, style, children }: CodeblockProps) => <RN.Text selectable={selectable} style={[styles.codeBlock, style && style]}>{children}</RN.Text> const TextBasedCodeblock = ({ selectable, style, children }: CodeblockProps) => <RN.Text selectable={selectable} style={[styles.codeBlock, style && style]}>{children}</RN.Text>;
export default function Codeblock({ selectable, style, children }: CodeblockProps) { export default function Codeblock({ selectable, style, children }: CodeblockProps) {
if (!selectable) return <TextBasedCodeblock style={style} children={children} />; if (!selectable) return <TextBasedCodeblock style={style} children={children} />;
return RN.Platform.select({ return RN.Platform.select({
ios: <InputBasedCodeblock style={style} children={children} />, ios: <InputBasedCodeblock style={style} children={children} />,
default: <TextBasedCodeblock style={style} children={children} selectable />, default: <TextBasedCodeblock style={style} children={children} selectable />,
}); });
} }

View file

@ -1,6 +1,6 @@
import { findByProps } from "@lib/metro/filters";
import { instead } from "@lib/api/patcher"; import { instead } from "@lib/api/patcher";
import { ReactNative } from "@/lib/metro/common"; import { ReactNative } from "@lib/metro/common";
import { findByProps } from "@lib/metro/filters";
const { ContextMenu: _ContextMenu } = findByProps("ContextMenu"); const { ContextMenu: _ContextMenu } = findByProps("ContextMenu");
const { AccessibilityInfo } = ReactNative; const { AccessibilityInfo } = ReactNative;
@ -10,7 +10,7 @@ type ContextMenuItem = {
iconSource: import("react-native").ImageSourcePropType, iconSource: import("react-native").ImageSourcePropType,
action: () => unknown, action: () => unknown,
[key: string]: any; [key: string]: any;
} };
type ContextMenuProps = { type ContextMenuProps = {
triggerOnLongPress: boolean; triggerOnLongPress: boolean;
@ -19,7 +19,7 @@ type ContextMenuProps = {
title: string; title: string;
children: React.FC; children: React.FC;
[key: string]: any; [key: string]: any;
} };
export default function ContextMenu(props: ContextMenuProps) { export default function ContextMenu(props: ContextMenuProps) {
const ref = React.useRef(null); const ref = React.useRef(null);
@ -41,12 +41,12 @@ export default function ContextMenu(props: ContextMenuProps) {
unpatch(); // and skip this call unpatch(); // and skip this call
} }
}); });
} };
}, []); }, []);
return <ReactNative.View ref={ref}> return <ReactNative.View ref={ref}>
<_ContextMenu <_ContextMenu
{...props} {...props}
/> />
</ReactNative.View> </ReactNative.View>;
} }

View file

@ -1,6 +1,6 @@
import { Strings } from "@/core/i18n"; import { Strings } from "@core/i18n";
import { React, ReactNative as RN, stylesheet } from "@metro/common"; import { React, ReactNative as RN, stylesheet } from "@metro/common";
import { Forms, Button, Codeblock } from "@ui/components"; import { Button, Codeblock, Forms } from "@ui/components";
interface ErrorBoundaryState { interface ErrorBoundaryState {
hasErr: boolean; hasErr: boolean;
@ -47,6 +47,6 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
text={Strings.RETRY} text={Strings.RETRY}
/> />
</RN.ScrollView> </RN.ScrollView>
) );
} }
} }

View file

@ -1,6 +1,6 @@
import { ButtonColors } from "@/lib/utils/types"; import { ButtonColors } from "@lib/utils/types";
import { findByProps } from "@metro/filters"; import { findByProps } from "@metro/filters";
import { Forms, Alert } from "@ui/components"; import { Alert, Forms } from "@ui/components";
const { FormInput } = Forms; const { FormInput } = Forms;
const Alerts = findByProps("openLazy", "close"); const Alerts = findByProps("openLazy", "close");
@ -21,14 +21,14 @@ export default function InputAlert({ title, confirmText, confirmColor, onConfirm
const [error, setError] = React.useState(""); const [error, setError] = React.useState("");
function onConfirmWrapper() { function onConfirmWrapper() {
const asyncOnConfirm = Promise.resolve(onConfirm(value)) const asyncOnConfirm = Promise.resolve(onConfirm(value));
asyncOnConfirm.then(() => { asyncOnConfirm.then(() => {
Alerts.close(); Alerts.close();
}).catch((e: Error) => { }).catch((e: Error) => {
setError(e.message); setError(e.message);
}); });
}; }
return ( return (
<Alert <Alert
@ -43,7 +43,7 @@ export default function InputAlert({ title, confirmText, confirmColor, onConfirm
<FormInput <FormInput
placeholder={placeholder} placeholder={placeholder}
value={value} value={value}
onChange={(v: string | { text: string }) => { onChange={(v: string | { text: string; }) => {
setValue(typeof v === "string" ? v : v.text); setValue(typeof v === "string" ? v : v.text);
if (error) setError(""); if (error) setError("");
}} }}
@ -57,4 +57,4 @@ export default function InputAlert({ title, confirmText, confirmColor, onConfirm
/> />
</Alert> </Alert>
); );
}; }

View file

@ -1,8 +1,8 @@
import { Strings } from "@core/i18n";
import { getAssetIDByName } from "@lib/api/assets";
import { ReactNative as RN } from "@metro/common"; import { ReactNative as RN } from "@metro/common";
import { findByProps } from "@metro/filters"; import { findByProps } from "@metro/filters";
import ErrorBoundary from "@ui/components/ErrorBoundary"; import ErrorBoundary from "@ui/components/ErrorBoundary";
import { getAssetIDByName } from "@/lib/api/assets";
import { Strings } from "@/core/i18n";
const { TextInput } = findByProps("TableRow"); const { TextInput } = findByProps("TableRow");
@ -14,16 +14,16 @@ export interface SearchProps {
function SearchIcon() { function SearchIcon() {
return <RN.Image style={{ transform: [{ scale: 0.8 }]}} source={getAssetIDByName("search")} /> return <RN.Image style={{ transform: [{ scale: 0.8 }] }} source={getAssetIDByName("search")} />;
} }
export default ({ onChangeText, placeholder, style }: SearchProps) => { export default ({ onChangeText, placeholder, style }: SearchProps) => {
const [query, setQuery] = React.useState(""); const [query, setQuery] = React.useState("");
const onChange = (value: string) => { const onChange = (value: string) => {
setQuery(value); setQuery(value);
onChangeText?.(value); onChangeText?.(value);
} };
return <ErrorBoundary> return <ErrorBoundary>
<RN.View style={style}> <RN.View style={style}>
@ -38,5 +38,5 @@ export default ({ onChangeText, placeholder, style }: SearchProps) => {
value={query} value={query}
/> />
</RN.View> </RN.View>
</ErrorBoundary> </ErrorBoundary>;
}; };

View file

@ -1,6 +1,6 @@
import { getAssetIDByName } from "@lib/api/assets";
import { findByProps } from "@lib/metro/filters";
import { ReactNative as RN } from "@metro/common"; import { ReactNative as RN } from "@metro/common";
import { getAssetIDByName } from "@/lib/api/assets";
import { findByProps } from "@/lib/metro/filters";
const { FormRow } = findByProps("FormRow"); const { FormRow } = findByProps("FormRow");
const { TableRow } = findByProps("TableRow"); const { TableRow } = findByProps("TableRow");
@ -30,5 +30,5 @@ export default function Summary({ label, icon, noPadding = false, noAnimation =
<RN.View style={!noPadding && { paddingHorizontal: 15 }}>{children}</RN.View> <RN.View style={!noPadding && { paddingHorizontal: 15 }}>{children}</RN.View>
</>} </>}
</> </>
) );
} }

View file

@ -1,4 +1,3 @@
import { ReactNative as RN } from "@metro/common";
import { findByDisplayName, findByName, findByProps } from "@metro/filters"; import { findByDisplayName, findByName, findByProps } from "@metro/filters";
// Discord // Discord
@ -11,7 +10,7 @@ export const HelpMessage = findByName("HelpMessage");
export const SafeAreaView = findByProps("useSafeAreaInsets").SafeAreaView as any; export const SafeAreaView = findByProps("useSafeAreaInsets").SafeAreaView as any;
// Vendetta // 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 Codeblock } from "@ui/components/Codeblock";
export { default as Search } from "@ui/components/Search"; export { default as ErrorBoundary } from "@ui/components/ErrorBoundary";
export { default as Search } from "@ui/components/Search";
export { default as Summary } from "@ui/components/Summary";

View file

@ -1,5 +1,4 @@
export * as alerts from "./alerts"; export * as alerts from "./alerts";
export * as color from "./color"; export * as color from "./color";
export * as toasts from "./toasts";
export * as settings from "../../core/ui/settings";
export * as components from "./components"; export * as components from "./components";
export * as toasts from "./toasts";

View file

@ -1,12 +1,12 @@
import { ReactNative as RN, stylesheet } from "@metro/common"; import { DeviceManager } from "@lib/api/native/modules";
import { findByName, findByProps, findByStoreName } from "@metro/filters";
import { after } from "@lib/api/patcher"; import { after } from "@lib/api/patcher";
import { toggleSafeMode } from "@lib/debug"; import { toggleSafeMode } from "@lib/debug";
import { DeviceManager } from "@lib/api/native/modules"; import { settings } from "@lib/settings";
import { ButtonColors } from "@lib/utils/types";
import { ReactNative as RN, stylesheet } from "@metro/common";
import { findByName, findByProps, findByStoreName } from "@metro/filters";
import { semanticColors } from "@ui/color"; import { semanticColors } from "@ui/color";
import { Button, Codeblock, ErrorBoundary as _ErrorBoundary, SafeAreaView } from "@ui/components"; import { Button, Codeblock, ErrorBoundary as _ErrorBoundary, SafeAreaView } from "@ui/components";
import { settings } from "@lib/settings";
import { ButtonColors } from "@/lib/utils/types";
const ErrorBoundary = findByName("ErrorBoundary"); const ErrorBoundary = findByName("ErrorBoundary");
@ -80,8 +80,8 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
{ text: "Restart Discord", onPress: this.handleReload }, { text: "Restart Discord", onPress: this.handleReload },
...!settings.safeMode?.enabled ? [{ text: "Restart in Safe Mode", onPress: toggleSafeMode }] : [], ...!settings.safeMode?.enabled ? [{ text: "Restart in Safe Mode", onPress: toggleSafeMode }] : [],
{ text: "Retry Render", color: ButtonColors.RED, onPress: () => this.setState({ info: null, error: null }) }, { text: "Retry Render", color: ButtonColors.RED, onPress: () => this.setState({ info: null, error: null }) },
] ];
return ( return (
<_ErrorBoundary> <_ErrorBoundary>
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
@ -98,7 +98,7 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
<BadgableTabBar <BadgableTabBar
tabs={tabs} tabs={tabs}
activeTab={this.state.activeTab} activeTab={this.state.activeTab}
onTabSelected={(tab: string) => { this.setState({ activeTab: tab }) }} onTabSelected={(tab: string) => { this.setState({ activeTab: tab }); }}
/> />
</RN.View> </RN.View>
<Codeblock <Codeblock
@ -122,10 +122,10 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
size={button.size ?? "small"} size={button.size ?? "small"}
onPress={button.onPress} onPress={button.onPress}
style={DeviceManager.isTablet ? { flex: `0.${buttons.length}`, marginLeft: buttonIndex } : { marginTop: buttonIndex }} style={DeviceManager.isTablet ? { flex: `0.${buttons.length}`, marginLeft: buttonIndex } : { marginTop: buttonIndex }}
/> />;
})} })}
</RN.View> </RN.View>
</SafeAreaView> </SafeAreaView>
</_ErrorBoundary> </_ErrorBoundary>
) );
}); });

View file

@ -1,11 +1,11 @@
import { findByProps } from "@metro/filters"; import { getAssetIDByName } from "@lib/api/assets";
import { ReactNative, toasts } from "@metro/common"; import { ReactNative, toasts } from "@metro/common";
import { getAssetIDByName } from "@/lib/api/assets"; import { findByProps } from "@metro/filters";
const { uuid4 } = findByProps("uuid4"); const { uuid4 } = findByProps("uuid4");
export const showToast = (content: string, asset?: number) => toasts.open({ export const showToast = (content: string, asset?: number) => toasts.open({
//? In build 182205/44707, Discord changed their toasts, source is no longer used, rather icon, and a key is needed. // ? In build 182205/44707, Discord changed their toasts, source is no longer used, rather icon, and a key is needed.
// TODO: We could probably have the developer specify a key themselves, but this works to fix toasts // TODO: We could probably have the developer specify a key themselves, but this works to fix toasts
key: `vd-toast-${uuid4()}`, key: `vd-toast-${uuid4()}`,
content: content, content: content,
@ -18,4 +18,4 @@ showToast.showCopyToClipboard = (message = "Copied plugin URL to clipboard.") =>
if (ReactNative.Platform.OS !== "android" || ReactNative.Platform.Version <= 32) { if (ReactNative.Platform.OS !== "android" || ReactNative.Platform.Version <= 32) {
showToast(message, getAssetIDByName("toast_copy_link")); showToast(message, getAssetIDByName("toast_copy_link"));
} }
} };

View file

@ -1,10 +1,10 @@
export const DISCORD_SERVER = "https://discord.gg/XjYgWXHb9Q"; export const DISCORD_SERVER = "https://discord.gg/XjYgWXHb9Q";
export const GITHUB = "https://github.com/pyoncord"; export const GITHUB = "https://github.com/pyoncord";
export const PROXY_PREFIX = "https://vd-plugins.github.io/proxy"; 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 = /^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; 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;
// These are still Vendetta's // These are still Vendetta's
export const DISCORD_SERVER_ID = "1015931589865246730"; export const DISCORD_SERVER_ID = "1015931589865246730";
export const PLUGINS_CHANNEL_ID = "1091880384561684561"; export const PLUGINS_CHANNEL_ID = "1091880384561684561";
export const THEMES_CHANNEL_ID = "1091880434939482202"; export const THEMES_CHANNEL_ID = "1091880434939482202";

View file

@ -2,7 +2,7 @@ export enum Events {
GET = "GET", GET = "GET",
SET = "SET", SET = "SET",
DEL = "DEL", DEL = "DEL",
}; }
export type EmitterEvent = "SET" | "GET" | "DEL"; export type EmitterEvent = "SET" | "GET" | "DEL";

View file

@ -1,6 +1,6 @@
import { findInTree } from "@lib/utils"; import { findInTree } from "@lib/utils";
import { SearchFilter } from "./findInTree"; import { SearchFilter } from "@lib/utils/findInTree";
export const findInReactTree = (tree: { [key: string]: any }, filter: SearchFilter): any => findInTree(tree, filter, { export const findInReactTree = (tree: { [key: string]: any; }, filter: SearchFilter): any => findInTree(tree, filter, {
walkable: ["props", "children", "child", "sibling"], walkable: ["props", "children", "child", "sibling"],
}); });

View file

@ -1,12 +1,11 @@
// Makes mass-importing utils cleaner, chosen over moving utils to one file // Makes mass-importing utils cleaner, chosen over moving utils to one file
export * as constants from "@lib/utils/constants";
export { createEmitter } from "@lib/utils/emitter";
export { findInReactTree } from "@lib/utils/findInReactTree"; export { findInReactTree } from "@lib/utils/findInReactTree";
export { findInTree } from "@lib/utils/findInTree"; export { findInTree } from "@lib/utils/findInTree";
export * as logger from "@lib/utils/logger";
export { safeFetch } from "@lib/utils/safeFetch"; export { safeFetch } from "@lib/utils/safeFetch";
export * as types from "@lib/utils/types";
export { unfreeze } from "@lib/utils/unfreeze"; export { unfreeze } from "@lib/utils/unfreeze";
export { without } from "@lib/utils/without"; export { without } from "@lib/utils/without";
export { createEmitter } from "@lib/utils/emitter";
export * as constants from "@lib/utils/constants";
export * as logger from "@lib/utils/logger";
export * as types from "@lib/utils/types";

View file

@ -14,4 +14,4 @@ export interface Logger {
export const logModule = findByProps("setLogFn").default; export const logModule = findByProps("setLogFn").default;
const logger: Logger = new logModule("Bunny"); const logger: Logger = new logModule("Bunny");
export default logger; export default logger;

View file

@ -1,5 +1,4 @@
// A really basic fetch wrapper which throws on non-ok response codes // A really basic fetch wrapper which throws on non-ok response codes
export async function safeFetch(input: RequestInfo | URL, options?: RequestInit, timeout = 10000) { export async function safeFetch(input: RequestInfo | URL, options?: RequestInit, timeout = 10000) {
const req = await fetch(input, { const req = await fetch(input, {
signal: timeoutSignal(timeout), signal: timeoutSignal(timeout),

View file

@ -13,4 +13,4 @@ export enum ButtonColors {
LIGHTGREY = "lightgrey", LIGHTGREY = "lightgrey",
WHITE = "white", WHITE = "white",
LINK = "link" LINK = "link"
} }

View file

@ -3,4 +3,4 @@
export function unfreeze(obj: object) { export function unfreeze(obj: object) {
if (Object.isFrozen(obj)) return Object.assign({}, obj); if (Object.isFrozen(obj)) return Object.assign({}, obj);
return obj; return obj;
} }

View file

@ -1,5 +1,5 @@
export function without<O extends object, K extends readonly (keyof O)[]>(object: O, ...keys: K): Omit<O, typeof keys[number]> { export function without<O extends object, K extends readonly (keyof O)[]>(object: O, ...keys: K): Omit<O, typeof keys[number]> {
const cloned = { ...object }; const cloned = { ...object };
keys.forEach((k) => delete cloned[k]); keys.forEach(k => delete cloned[k]);
return cloned; return cloned;
} }

View file

@ -1,6 +1,6 @@
import { createVendettaObject } from "../core/vendettaObject"; import { createVendettaObject } from "@core/vendettaObject";
import * as api from "@lib/api"; import * as api from "@lib/api";
import patcher from "@lib/api/patcher";
import * as debug from "@lib/debug"; import * as debug from "@lib/debug";
import * as managers from "@lib/managers"; import * as managers from "@lib/managers";
import * as metro from "@lib/metro"; import * as metro from "@lib/metro";
@ -8,8 +8,6 @@ import * as settings from "@lib/settings";
import * as ui from "@lib/ui"; import * as ui from "@lib/ui";
import * as utils from "@lib/utils"; import * as utils from "@lib/utils";
import patcher from "@lib/api/patcher";
// You may now worry about my mental state. // You may now worry about my mental state.
function createBunnyObject(unloads: any[]) { function createBunnyObject(unloads: any[]) {
return { return {
@ -59,7 +57,7 @@ function createBunnyObject(unloads: any[]) {
// @ts-expect-error // @ts-expect-error
delete window.bunny; delete window.bunny;
}, },
} };
} }
export type BunnyObject = ReturnType<typeof createBunnyObject>; export type BunnyObject = ReturnType<typeof createBunnyObject>;

2
src/modules.d.ts vendored
View file

@ -2,4 +2,4 @@ declare module "bunny-build" {
const version: string; const version: string;
} }
export * from "bunny-build"; export * from "bunny-build";

View file

@ -16,8 +16,8 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"paths": { "paths": {
"@*": ["src*"],
"@lib/*": ["src/lib/*"], "@lib/*": ["src/lib/*"],
"@core/*": ["src/core/*"],
"@metro/*": ["src/lib/metro/*"], "@metro/*": ["src/lib/metro/*"],
"@ui/*": ["src/lib/ui/*"], "@ui/*": ["src/lib/ui/*"],
"@types": ["src/utils/types.ts"], "@types": ["src/utils/types.ts"],