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

View file

@ -6,7 +6,8 @@
"dev": "node build.mjs",
"build": "node build.mjs --release",
"serve": "node build.mjs --serve",
"watch": "node build.mjs --watch"
"watch": "node build.mjs --watch",
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx"
},
"keywords": [
"discord",
@ -26,13 +27,22 @@
"@types/node": "^20.11.21",
"@types/react": "18.2.60",
"@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",
"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",
"typescript": "^5.3.3",
"typescript-transform-paths": "^3.4.7"
},
"dependencies": {
"@swc/helpers": "0.5.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"fuzzysort": "^2.0.4",
"intl-messageformat": "^10.5.11",
"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 { messageUtil } from "@metro/common";
import { ApplicationCommand, ApplicationCommandOptionType } from "@/lib/api/commands/types";
export default () => <ApplicationCommand>{
name: "debug",
@ -31,4 +31,4 @@ export default () => <ApplicationCommand>{
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 { Strings } from "@/core/i18n";
import { ApplicationCommand, ApplicationCommandOptionType } from "@/lib/api/commands/types";
import { findByProps } from "@metro/filters";
const util = findByProps("inspect");
const AsyncFunction = (async () => void 0).constructor;
@ -38,4 +38,4 @@ export default () => <ApplicationCommand>{
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 { Strings } from "@/core/i18n";
import { ApplicationCommand, ApplicationCommandOptionType } from "@/lib/api/commands/types";
import { messageUtil } from "@metro/common";
export default () => <ApplicationCommand>{
name: "plugins",
@ -38,4 +38,4 @@ export default () => <ApplicationCommand>{
messageUtil.sendMessage(ctx.channel.id, { content });
}
}
}
};

View file

@ -6,10 +6,10 @@
* - @pylixonly
*/
import { FluxDispatcher, moment } from "@metro/common";
import logger from "@lib/utils/logger";
import { FluxDispatcher, moment } from "@metro/common";
function onDispatch({ locale }: { locale: string }) {
function onDispatch({ locale }: { locale: string; }) {
// Timestamps
try {
moment.locale(locale.toLowerCase());
@ -22,5 +22,5 @@ function onDispatch({ locale }: { locale: string }) {
}
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";
const IntlMessageFormat = findByName("MessageFormat") as typeof import("intl-messageformat").default;
@ -11,7 +12,10 @@ export const Strings = new Proxy({}, {
}
}) 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];
// @ts-ignore
return new IntlMessageFormat(str).format(val);
}
}

View file

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

View file

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

View file

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

View file

@ -1,16 +1,16 @@
import { findByProps, find } from "@metro/filters";
import { ReactNative as RN, channels, url } from "@metro/common";
import { PROXY_PREFIX, THEMES_CHANNEL_ID } from "@lib/utils/constants";
import { formatString, Strings } from "@core/i18n";
import { getAssetIDByName } from "@lib/api/assets";
import { isThemeSupported } from "@lib/api/native/loader";
import { after, instead } from "@lib/api/patcher";
import { installPlugin } from "@lib/managers/plugins";
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 { getAssetIDByName } from "@/lib/api/assets";
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 { openURL } = url;
const { getChannelId } = channels;
@ -38,7 +38,7 @@ export default () => {
const patches = new Array<Function>();
patches.push(
after("showSimpleActionSheet", showSimpleActionSheet, (args) => {
after("showSimpleActionSheet", showSimpleActionSheet, args => {
if (args[0].key !== "LongPressUrl") return;
const {
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));
// @ts-ignore
Promise.allSettled ??= allSettled;
Promise.allSettled ??= allSettled;

View file

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

View file

@ -1,9 +1,9 @@
import { ReactNative as RN, clipboard } from "@metro/common";
import { showToast } from "@ui/toasts";
import { Asset } from "@lib/api/assets";
import { clipboard, ReactNative as RN } from "@metro/common";
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;
@ -17,5 +17,5 @@ export default function AssetDisplay({ asset }: AssetDisplayProps) {
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 { findByProps } from "@metro/filters";
import { getAssetIDByName } from "@/lib/api/assets";
import { semanticColors } from "@ui/color";
import ContextMenu from "@ui/components/ContextMenu";
const { TableRow, TableRowIcon, TableSwitchRow, TableCheckboxRow, TableRowGroup } = findByProps("TableRow");
@ -33,7 +33,7 @@ const styles = stylesheet.createThemedStyleSheet({
marginLeft: 5,
tintColor: semanticColors?.INTERACTIVE_NORMAL,
}
})
});
interface Action {
icon: string;
@ -77,43 +77,43 @@ export default function Card(props: CardProps) {
return (
<RN.View style={[styles.card, { marginTop: props.index !== 0 ? 10 : 0 }]}>
<TableRowGroup>
{props.toggleType && (props.toggleType === "switch" ?
<TableSwitchRow
{...headerProps}
value={props.toggleValue}
onValueChange={props.onToggleChange}
/> : props.toggleType === "radio" ?
<TableCheckboxRow
{...headerProps}
onPress={() => {
pressableState = !pressableState;
props.onToggleChange?.(pressableState)
}}
checked={props.toggleValue}
/> : undefined) || <TableRow {...headerProps} />}
<TableRow
label={props.descriptionLabel}
trailing={
<RN.View style={styles.actions}>
{props.overflowActions &&
<ContextMenu
triggerOnLongPress={false}
items={props.overflowActions.map(i => ({
label: i.label,
iconSource: getAssetIDByName(i.icon),
action: i.onPress
}))}
align="auto"
title={props.overflowTitle!!}
children={(props) => <RN.TouchableOpacity {...props}>
<RN.Image style={styles.icon} source={getAssetIDByName("ic_more_24px")} />
</RN.TouchableOpacity>}
/>
}
</RN.View>
}
/>
{props.toggleType && (props.toggleType === "switch" ?
<TableSwitchRow
{...headerProps}
value={props.toggleValue}
onValueChange={props.onToggleChange}
/> : props.toggleType === "radio" ?
<TableCheckboxRow
{...headerProps}
onPress={() => {
pressableState = !pressableState;
props.onToggleChange?.(pressableState);
}}
checked={props.toggleValue}
/> : undefined) || <TableRow {...headerProps} />}
<TableRow
label={props.descriptionLabel}
trailing={
<RN.View style={styles.actions}>
{props.overflowActions &&
<ContextMenu
triggerOnLongPress={false}
items={props.overflowActions.map(i => ({
label: i.label,
iconSource: getAssetIDByName(i.icon),
action: i.onPress
}))}
align="auto"
title={props.overflowTitle!!}
children={props => <RN.TouchableOpacity {...props}>
<RN.Image style={styles.icon} source={getAssetIDByName("ic_more_24px")} />
</RN.TouchableOpacity>}
/>
}
</RN.View>
}
/>
</TableRowGroup>
</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 { clipboard, ReactNative as RN, stylesheet } from "@metro/common";
import { showInputAlert } from "@ui/alerts";
import { getAssetIDByName } from "@/lib/api/assets";
import { semanticColors } from "@ui/color";
import { Strings } from "@/core/i18n";
const styles = stylesheet.createThemedStyleSheet({
icon: {
@ -20,7 +20,7 @@ interface InstallButtonProps {
export default function InstallButton({ alertTitle, installFunction: fetchFunction }: InstallButtonProps) {
return (
<RN.TouchableOpacity onPress={() =>
clipboard.getString().then((content) =>
clipboard.getString().then(content =>
showInputAlert({
title: alertTitle,
initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",

View file

@ -1,13 +1,13 @@
import { NavigationNative, clipboard } from "@metro/common";
import { removePlugin, startPlugin, stopPlugin, getSettings, fetchPlugin, BunnyPlugin } from "@lib/managers/plugins";
import { formatString, Strings } from "@core/i18n";
import Card, { CardWrapper } from "@core/ui/components/Card";
import { getAssetIDByName } from "@lib/api/assets";
import { MMKVManager } from "@lib/api/native/modules";
import { getAssetIDByName } from "@/lib/api/assets";
import { showToast } from "@ui/toasts";
import { useProxy } from "@lib/api/storage";
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 Card, { CardWrapper } from "@/core/ui/components/Card";
import { useProxy } from "@/lib/api/storage";
import { ButtonColors } from "@/lib/utils/types";
import { Strings, formatString } from "@/core/i18n";
import { showToast } from "@ui/toasts";
async function stopThenStart(plugin: BunnyPlugin, callback: Function) {
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"));
}).catch(() => {
showToast("Failed to refetch plugin!", getAssetIDByName("Small"));
})
});
});
},
},
@ -72,7 +72,7 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
showToast.showCopyToClipboard();
}
},
{
{
icon: "ic_download_24px",
label: plugin.update ? Strings.DISABLE_UPDATES : Strings.ENABLE_UPDATES,
onPress: () => {
@ -108,7 +108,7 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
isDestructive: true,
onPress: () => showConfirmationAlert({
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,
cancelText: Strings.CANCEL,
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 { useProxy } from "@/lib/api/storage";
import { getAssetIDByName } from "@/lib/api/assets";
import { getScreens } from "@/core/ui/settings/data";
import { ErrorBoundary, Forms } from "@ui/components";
import { Strings } from "@core/i18n";
import { getScreens } from "@core/ui/settings/data";
import { getAssetIDByName } from "@lib/api/assets";
import { useProxy } from "@lib/api/storage";
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;
@ -12,7 +12,7 @@ export default function SettingsSection() {
const navigation = NavigationNative.useNavigation();
useProxy(settings);
const screens = getScreens()
const screens = getScreens();
return (
<ErrorBoundary>
@ -30,5 +30,5 @@ export default function SettingsSection() {
))}
</FormSection>
</ErrorBoundary>
)
}
);
}

View file

@ -1,14 +1,14 @@
import { clipboard } from "@metro/common";
import { Theme, applyTheme, fetchTheme, removeTheme, selectTheme, themes } from "@lib/managers/themes";
import { useProxy } from "@/lib/api/storage";
import { formatString, Strings } from "@core/i18n";
import Card, { CardWrapper } from "@core/ui/components/Card";
import { getAssetIDByName } from "@lib/api/assets";
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 { 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) {
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%
showToast(Strings.THEMES_RELOAD_FOR_CHANGES, getAssetIDByName("yellow-alert"));
} catch (e: any) {
console.error("Error while selectAndApply,", e)
}
console.error("Error while selectAndApply,", e);
}
}
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™
if (removed) return null;
const authors = theme.data.authors;
const { authors } = theme.data;
return (
<Card
@ -57,7 +57,7 @@ export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
onConfirm: () => BundleUpdaterManager.reload(),
})
});
} else {
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,
confirmColor: ButtonColors.RED,
onConfirm: () => {
removeTheme(theme.id).then((wasSelected) => {
removeTheme(theme.id).then(wasSelected => {
setRemoved(true);
if (wasSelected) selectAndApply(false, theme.id);
}).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 { getAssetIDByName } from "@/lib/api/assets";
import { showToast } from "@ui/toasts";
import { Forms } from "@ui/components";
import { findByProps } from "@/lib/metro/filters";
import { showToast } from "@ui/toasts";
interface VersionProps {
label: string;
@ -15,7 +15,7 @@ const { TableRow } = findByProps("TableRow");
const { FormText } = Forms;
export default function Version({ label, version, icon, padding }: VersionProps) {
return (
return (
<TableRow
style={padding && { paddingHorizontal: 15, paddingVertical: -15 }}
label={label}
@ -26,5 +26,5 @@ export default function Version({ label, version, icon, padding }: VersionProps)
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 { 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 { 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 InstallButton from "@/core/ui/components/InstallButton";
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";
import { showToast } from "@ui/toasts";
const { useSafeAreaInsets } = findByProps("useSafeAreaInsets");
@ -31,10 +31,10 @@ interface Screen {
render: React.ComponentType<any>;
}
const useStyles = stylesheet.createStyles({
container: {
const useStyles = stylesheet.createStyles({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY
backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY
}
});
@ -58,7 +58,7 @@ export const getScreens = (): Screen[] => [
headerRight: () => (
<InstallButton
alertTitle={Strings.INSTALL_PLUGIN}
installFunction={async (input) => {
installFunction={async input => {
if (!input.startsWith(PROXY_PREFIX) && !settings.developerSettings)
setImmediate(() => showConfirmationAlert({
title: Strings.MODAL_UNPROXIED_PLUGIN_HEADER,
@ -99,16 +99,16 @@ export const getScreens = (): Screen[] => [
key: "VendettaCustomPage",
title: "Vendetta Page",
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();
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,
render: s.render,
...s.options,
@ -128,12 +128,12 @@ export const getYouData = () => {
}),
titleConfig: keyMap(screens, "title"),
relationships: keyMap(screens, null),
rendererConfigs: keyMap(screens, (s) => {
rendererConfigs: keyMap(screens, s => {
const WrappedComponent = React.memo(({ navigation, route }: any) => {
const styles = useStyles();
const { bottom: paddingBottom } = useSafeAreaInsets();
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 {
@ -145,7 +145,7 @@ export const getYouData = () => {
route: s.key,
getComponent: () => WrappedComponent,
}
}
};
}),
};
};

View file

@ -1,12 +1,12 @@
// TODO: make settings patches part of the api
import patchPanels from "@/core/ui/settings/patches/panels";
import patchYou from "@/core/ui/settings/patches/you";
import patchPanels from "@core/ui/settings/patches/panels";
import patchYou from "@core/ui/settings/patches/you";
export default function initSettings() {
const patches = [
patchPanels(),
patchYou(),
]
];
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 { all } from "@/lib/api/assets";
import { Forms, Search, ErrorBoundary } from "@ui/components";
import AssetDisplay from "@/core/ui/components/AssetDisplay";
import { ErrorBoundary, Forms, Search } from "@ui/components";
const { FormDivider } = Forms;
@ -24,5 +24,5 @@ export default function AssetBrowser() {
/>
</RN.View>
</ErrorBoundary>
)
}
);
}

View file

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

View file

@ -1,14 +1,14 @@
import { ReactNative as RN, stylesheet, url } from "@metro/common";
import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants";
import { Strings } from "@core/i18n";
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 { 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 { 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 { Strings } from "@/core/i18n";
import { Summary } from "@ui/components";
const { Stack, TableRow, TableSwitchRow, TableRowGroup } = findByProps("TableRow");
const debugInfo = getDebugInfo();
@ -18,7 +18,7 @@ const styles = stylesheet.createThemedStyleSheet({
flex: 1,
backgroundColor: semanticColors.BACKGROUND_MOBILE_SECONDARY,
}
})
});
export default function General() {
useProxy(settings);
@ -137,5 +137,5 @@ export default function General() {
</TableRowGroup>
</Stack>
</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 { 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() {
useProxy(settings)
useProxy(settings);
return (
<AddonPage<BunnyPlugin>
@ -16,5 +16,5 @@ export default function Plugins() {
safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS}
card={PluginCard}
/>
)
}
);
}

View file

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

View file

@ -1,9 +1,9 @@
import { i18n } from "@metro/common";
import { findByName } from "@metro/filters";
import SettingsSection from "@core/ui/components/SettingsSection";
import { getPanelsScreens } from "@core/ui/settings/data";
import { after } from "@lib/api/patcher";
import { findInReactTree } from "@lib/utils";
import { getPanelsScreens } from "@/core/ui/settings/data";
import SettingsSection from "@/core/ui/components/SettingsSection";
import { i18n } from "@metro/common";
import { findByName } from "@metro/filters";
const screensModule = findByName("getScreens", 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
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
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));
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 { getScreens, getYouData } from "@/core/ui/settings/data";
import { i18n } from "@lib/metro/common";
import { Strings } from "@/core/i18n";
import { findByProps } from "@metro/filters";
export default function patchYou() {
const patches = new Array<Function>;
@ -53,7 +53,7 @@ function oldYouPatch(patches: Function[]) {
const oldRelationships = miscModule.SETTING_RELATIONSHIPS;
const oldRendererConfigs = miscModule.SETTING_RENDERER_CONFIGS;
miscModule.SETTING_RELATIONSHIPS = { ...oldRelationships, ...data.relationships };
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(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;
@ -102,5 +102,5 @@ function manipulateSections(sections: any[], layout: any) {
// Upload Logs button be gone
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 { 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 plugins from "@lib/managers/plugins";
import * as themes from "@lib/managers/themes";
import * as commands from "@/lib/api/commands";
import * as storage from "@/lib/api/storage";
import * as metro from "@metro/filters";
import { loaderConfig, settings } from "@lib/settings";
import * as utils from "@lib/utils";
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 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 => ({
patcher: {
@ -21,7 +21,7 @@ export const createVendettaObject = (unloads: any[]): any => ({
after: patcher.after,
instead: patcher.instead
},
metro: {
metro: {
find: (filter: (m: any) => boolean) => metro.find(filter),
findAll: (filter: (m: any) => boolean) => metro.findAll(filter),
findByProps: (...props: any) => metro.findByProps(...props),
@ -60,9 +60,9 @@ export const createVendettaObject = (unloads: any[]): any => ({
constants: {
DISCORD_SERVER: "https://discord.gg/n9QQ4XhhJP",
GITHUB: "https://github.com/vendetta-mod",
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_MULTI: /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g,
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_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",
PLUGINS_CHANNEL_ID: "1091880384561684561",
THEMES_CHANNEL_ID: "1091880434939482202",

View file

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

View file

@ -1,5 +1,5 @@
import { assets } from "@metro/common";
import { after } from "@lib/api/patcher";
import { assets } from "@metro/common";
export const all: Record<string, Asset> = {};
@ -19,7 +19,7 @@ export function patchAssets() {
if (!asset) break;
if (all[asset.name]) continue;
all[asset.name] = { ...asset, id: id };
};
}
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 getAssetByName = (name: string): Asset => all[name];
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 { ApplicationCommand, ApplicationCommandType, ApplicationCommandInputType } from "./types";
import { commands as commandsModule } from "@metro/common";
import { ApplicationCommand, ApplicationCommandInputType, ApplicationCommandType } from "./types";
let commands: ApplicationCommand[] = [];
@ -11,9 +12,9 @@ export function patchCommands() {
// Register core commands
[
// require("@/core/commands/eval"),
require("@/core/commands/debug"),
require("@/core/commands/plugins")
// require("@core/commands/eval"),
require("@core/commands/debug"),
require("@core/commands/plugins")
].forEach(r => registerCommand(r.default()));
return () => {

View file

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

View file

@ -1,5 +1,5 @@
export * as assets from "./assets";
export * as commands from "./commands";
export * as native from "./native";
export * as patcher from "./patcher";
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");
// return void await FileManager.writeFile("documents", `${prefix}${path}`, data, "base64");
// }
// }

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import * as _spitroast 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 { FileManager, MMKVManager } from "../native/modules";
export interface StorageBackend {
get: () => unknown | Promise<unknown>;
set: (data: unknown) => void | Promise<void>;
}
const ILLEGAL_CHARS_REGEX = /[<>:"\/\\|?*]/g;
const ILLEGAL_CHARS_REGEX = /[<>:"/\\|?*]/g;
const filePathFixer = (file: string): string => RN.Platform.select({
default: file,
@ -16,11 +16,11 @@ const filePathFixer = (file: string): string => RN.Platform.select({
const getMMKVPath = (name: string): string => {
if (ILLEGAL_CHARS_REGEX.test(name)) {
// 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}`;
}
};
export const purgeStorage = async (store: string) => {
if (await MMKVManager.getItem(store)) {
@ -31,7 +31,7 @@ export const purgeStorage = async (store: string) => {
if (await FileManager.fileExists(`${FileManager.getConstants().DocumentsDirPath}/${mmkvPath}`)) {
await FileManager.removeFile?.("documents", mmkvPath);
}
}
};
export const createMMKVBackend = (store: string, defaultData = {}) => {
const mmkvPath = getMMKVPath(store);
@ -47,7 +47,7 @@ export const createMMKVBackend = (store: string, defaultData = {}) => {
if (oldData === "!!LARGE_VALUE!!") {
const cachePath = `${FileManager.getConstants().CacheDirPath}/mmkv/${store}`;
if (await FileManager.fileExists(cachePath)) {
oldData = await FileManager.readFile(cachePath, "utf8")
oldData = await FileManager.readFile(cachePath, "utf8");
} else {
console.log(`${store}: Experienced data loss :(`);
oldData = defaultStr;
@ -67,7 +67,7 @@ export const createMMKVBackend = (store: string, defaultData = {}) => {
console.log(`Successfully migrated ${store} store from MMKV storage to fs`);
}
})());
}
};
export const createFileBackend = (file: string, defaultData = {}, migratePromise?: Promise<void>): StorageBackend => {
return {
@ -86,7 +86,7 @@ export const createFileBackend = (file: string, defaultData = {}, migratePromise
await FileManager.writeFile("documents", filePathFixer(file), JSON.stringify(defaultData), "utf8");
return JSON.parse(await FileManager.readFile(path, "utf8"));
},
set: async (data) => {
set: async data => {
await migratePromise;
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 { createEmitter, Emitter } from "@lib/utils/emitter";
const emitterSymbol = Symbol.for("vendetta.storage.emitter");
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 {
const emitter = (storage as any)[emitterSymbol] as Emitter;
const [, forceUpdate] = React.useReducer((n) => ~n, 0);
const [, forceUpdate] = React.useReducer(n => ~n, 0);
React.useEffect(() => {
const listener = () => forceUpdate();
@ -93,16 +93,16 @@ export function wrapSync<T extends Promise<any>>(store: T): Awaited<T> {
const awaitQueue: (() => void)[] = [];
const awaitInit = (cb: () => void) => (awaited ? cb() : awaitQueue.push(cb));
store.then((v) => {
store.then(v => {
awaited = v;
awaitQueue.forEach((cb) => cb());
awaitQueue.forEach(cb => cb());
});
return new Proxy({} as Awaited<T>, {
...Object.fromEntries(
Object.getOwnPropertyNames(Reflect)
// @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) {
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";

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 { 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 logger from "@lib/utils/logger";
import { getLoaderName, isThemeSupported } from "./api/native/loader";
import type { PlatformConstants } from "react-native";
import { ReactNative as RN } from "@metro/common";
import { showToast } from "@ui/toasts";
import { version } from "bunny-build";
import type { PlatformConstants } from "react-native";
export let socket: WebSocket;
export interface RNConstants extends PlatformConstants {
@ -30,7 +30,7 @@ export interface RNConstants extends PlatformConstants {
}
export async function toggleSafeMode() {
settings.safeMode = { ...settings.safeMode, enabled: !settings.safeMode?.enabled }
settings.safeMode = { ...settings.safeMode, enabled: !settings.safeMode?.enabled };
if (isThemeSupported()) {
if (getThemeFromLoader()?.id) settings.safeMode!.currentThemeId = getThemeFromLoader()!.id;
if (settings.safeMode?.enabled) {
@ -68,7 +68,7 @@ export function connectToDebugger(url: string) {
}
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] }));
logger.log(args[0]);
});
@ -76,7 +76,7 @@ export function patchLogHook() {
return () => {
socket && socket.close();
unpatch();
}
};
}
export const versionHash = version;
@ -111,7 +111,7 @@ export function getDebugInfo() {
},
hermes: {
version: hermesVer,
buildType: hermesProps["Build"],
buildType: hermesProps.Build,
bytecodeVersion: hermesProps["Bytecode Version"],
},
...RN.Platform.select(
@ -151,5 +151,5 @@ export function getDebugInfo() {
}
}
)!
}
};
}

View file

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

View file

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

View file

@ -18,11 +18,11 @@ function createThemedStyleSheet<T extends StyleSheet.NamedStyles<T>>(sheet: T) {
for (const key in sheet) {
// @ts-ignore
sheet[key] = new Proxy(ReactNative.StyleSheet.flatten(sheet[key]), {
get(target, prop, receiver) {
get(target, prop, receiver) {
const res = Reflect.get(target, prop, receiver);
return colorResolver.isSemanticColor(res)
return colorResolver.isSemanticColor(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 module = window.modules[id]?.publicModule?.exports;
if (!module || module === window || module["proxygone"] === null) {
if (!module || module === window || module.proxygone === null) {
blacklist(id);
continue;
}
@ -44,9 +44,9 @@ const onModuleCheck = (exports: any) => {
} catch {
return args[0];
}
})
});
}
}
};
// Function to filter through modules
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;
}
};
export const modules = window.modules;
export const { modules } = window;
export const find = filterModules(modules, true);
export const findAll = filterModules(modules);
const propsFilter = (props: (string | symbol)[]) => (m: any) => props.every((p) => m[p] !== undefined);
const propsFilter = (props: (string | symbol)[]) => (m: any) => props.every(p => m[p] !== undefined);
const nameFilter = (name: string, defaultExp: boolean) => (defaultExp ? (m: any) => m?.name === name : (m: any) => m?.default?.name === name);
const dNameFilter = (displayName: string, defaultExp: boolean) => (defaultExp ? (m: any) => m?.displayName === displayName : (m: any) => m?.default?.displayName === displayName);
const tNameFilter = (typeName: string, defaultExp: boolean) => (defaultExp ? (m: any) => m?.type?.name === typeName : (m: any) => m?.default?.type?.name === typeName);

View file

@ -1,3 +1,3 @@
export * as common from "./common";
export * 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";
export interface Settings {

View file

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

View file

@ -1,17 +1,17 @@
import { constants } from "@metro/common";
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.
//* In 167.1, most if not all traces of the old color modules were removed.
//* In 168.6, Discord restructured EVERYTHING again. SemanticColor on this module no longer works when passed to a stylesheet. We must now use what you see below.
//* In 173.10, Discord restructured a lot of the app. These changes included making the color module impossible to early-find.
//? To stop duplication, it is now exported in our theming code.
//? These comments are preserved for historical purposes.
// ? To stop duplication, it is now exported in our theming code.
// ? These comments are preserved for historical purposes.
// const colorModule = findByProps("colors", "meta");
//? SemanticColor and default.colors are effectively ThemeColorMap
// ? SemanticColor and default.colors are effectively 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?
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";
export interface CodeblockProps {
@ -22,14 +22,14 @@ const styles = stylesheet.createThemedStyleSheet({
});
// 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 TextBasedCodeblock = ({ selectable, style, children }: CodeblockProps) => <RN.Text selectable={selectable} style={[styles.codeBlock, style && style]}>{children}</RN.Text>
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>;
export default function Codeblock({ selectable, style, children }: CodeblockProps) {
if (!selectable) return <TextBasedCodeblock style={style} children={children} />;
return RN.Platform.select({
ios: <InputBasedCodeblock style={style} children={children} />,
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 { ReactNative } from "@/lib/metro/common";
import { ReactNative } from "@lib/metro/common";
import { findByProps } from "@lib/metro/filters";
const { ContextMenu: _ContextMenu } = findByProps("ContextMenu");
const { AccessibilityInfo } = ReactNative;
@ -10,7 +10,7 @@ type ContextMenuItem = {
iconSource: import("react-native").ImageSourcePropType,
action: () => unknown,
[key: string]: any;
}
};
type ContextMenuProps = {
triggerOnLongPress: boolean;
@ -19,7 +19,7 @@ type ContextMenuProps = {
title: string;
children: React.FC;
[key: string]: any;
}
};
export default function ContextMenu(props: ContextMenuProps) {
const ref = React.useRef(null);
@ -41,12 +41,12 @@ export default function ContextMenu(props: ContextMenuProps) {
unpatch(); // and skip this call
}
});
}
};
}, []);
return <ReactNative.View ref={ref}>
<_ContextMenu
{...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 { Forms, Button, Codeblock } from "@ui/components";
import { Button, Codeblock, Forms } from "@ui/components";
interface ErrorBoundaryState {
hasErr: boolean;
@ -47,6 +47,6 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
text={Strings.RETRY}
/>
</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 { Forms, Alert } from "@ui/components";
import { Alert, Forms } from "@ui/components";
const { FormInput } = Forms;
const Alerts = findByProps("openLazy", "close");
@ -21,14 +21,14 @@ export default function InputAlert({ title, confirmText, confirmColor, onConfirm
const [error, setError] = React.useState("");
function onConfirmWrapper() {
const asyncOnConfirm = Promise.resolve(onConfirm(value))
const asyncOnConfirm = Promise.resolve(onConfirm(value));
asyncOnConfirm.then(() => {
Alerts.close();
}).catch((e: Error) => {
setError(e.message);
});
};
}
return (
<Alert
@ -43,7 +43,7 @@ export default function InputAlert({ title, confirmText, confirmColor, onConfirm
<FormInput
placeholder={placeholder}
value={value}
onChange={(v: string | { text: string }) => {
onChange={(v: string | { text: string; }) => {
setValue(typeof v === "string" ? v : v.text);
if (error) setError("");
}}
@ -57,4 +57,4 @@ export default function InputAlert({ title, confirmText, confirmColor, onConfirm
/>
</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 { findByProps } from "@metro/filters";
import ErrorBoundary from "@ui/components/ErrorBoundary";
import { getAssetIDByName } from "@/lib/api/assets";
import { Strings } from "@/core/i18n";
const { TextInput } = findByProps("TableRow");
@ -14,16 +14,16 @@ export interface SearchProps {
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) => {
const [query, setQuery] = React.useState("");
const onChange = (value: string) => {
setQuery(value);
onChangeText?.(value);
}
};
return <ErrorBoundary>
<RN.View style={style}>
@ -38,5 +38,5 @@ export default ({ onChangeText, placeholder, style }: SearchProps) => {
value={query}
/>
</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 { getAssetIDByName } from "@/lib/api/assets";
import { findByProps } from "@/lib/metro/filters";
const { FormRow } = findByProps("FormRow");
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>
</>}
</>
)
}
);
}

View file

@ -1,4 +1,3 @@
import { ReactNative as RN } from "@metro/common";
import { findByDisplayName, findByName, findByProps } from "@metro/filters";
// Discord
@ -11,7 +10,7 @@ export const HelpMessage = findByName("HelpMessage");
export const SafeAreaView = findByProps("useSafeAreaInsets").SafeAreaView as any;
// Vendetta
export { default as Summary } from "@ui/components/Summary";
export { default as ErrorBoundary } from "@ui/components/ErrorBoundary";
export { default as Codeblock } from "@ui/components/Codeblock";
export { default as Search } from "@ui/components/Search";
export { default as 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 color from "./color";
export * as toasts from "./toasts";
export * as settings from "../../core/ui/settings";
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 { findByName, findByProps, findByStoreName } from "@metro/filters";
import { DeviceManager } from "@lib/api/native/modules";
import { after } from "@lib/api/patcher";
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 { Button, Codeblock, ErrorBoundary as _ErrorBoundary, SafeAreaView } from "@ui/components";
import { settings } from "@lib/settings";
import { ButtonColors } from "@/lib/utils/types";
const ErrorBoundary = findByName("ErrorBoundary");
@ -80,8 +80,8 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
{ text: "Restart Discord", onPress: this.handleReload },
...!settings.safeMode?.enabled ? [{ text: "Restart in Safe Mode", onPress: toggleSafeMode }] : [],
{ text: "Retry Render", color: ButtonColors.RED, onPress: () => this.setState({ info: null, error: null }) },
]
];
return (
<_ErrorBoundary>
<SafeAreaView style={styles.container}>
@ -98,7 +98,7 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
<BadgableTabBar
tabs={tabs}
activeTab={this.state.activeTab}
onTabSelected={(tab: string) => { this.setState({ activeTab: tab }) }}
onTabSelected={(tab: string) => { this.setState({ activeTab: tab }); }}
/>
</RN.View>
<Codeblock
@ -122,10 +122,10 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
size={button.size ?? "small"}
onPress={button.onPress}
style={DeviceManager.isTablet ? { flex: `0.${buttons.length}`, marginLeft: buttonIndex } : { marginTop: buttonIndex }}
/>
/>;
})}
</RN.View>
</SafeAreaView>
</_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 { getAssetIDByName } from "@/lib/api/assets";
import { findByProps } from "@metro/filters";
const { uuid4 } = findByProps("uuid4");
export const showToast = (content: string, asset?: number) => toasts.open({
//? In build 182205/44707, Discord changed their toasts, source is no longer used, rather icon, and a key is needed.
// ? 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
key: `vd-toast-${uuid4()}`,
content: content,
@ -18,4 +18,4 @@ showToast.showCopyToClipboard = (message = "Copied plugin URL to clipboard.") =>
if (ReactNative.Platform.OS !== "android" || ReactNative.Platform.Version <= 32) {
showToast(message, getAssetIDByName("toast_copy_link"));
}
}
};

View file

@ -1,10 +1,10 @@
export const DISCORD_SERVER = "https://discord.gg/XjYgWXHb9Q";
export const GITHUB = "https://github.com/pyoncord";
export const PROXY_PREFIX = "https://vd-plugins.github.io/proxy";
export const HTTP_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/;
export const HTTP_REGEX_MULTI = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
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;
// These are still Vendetta's
export const DISCORD_SERVER_ID = "1015931589865246730";
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",
SET = "SET",
DEL = "DEL",
};
}
export type EmitterEvent = "SET" | "GET" | "DEL";

View file

@ -1,6 +1,6 @@
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"],
});
});

View file

@ -1,12 +1,11 @@
// 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 { findInTree } from "@lib/utils/findInTree";
export * as logger from "@lib/utils/logger";
export { safeFetch } from "@lib/utils/safeFetch";
export * as types from "@lib/utils/types";
export { unfreeze } from "@lib/utils/unfreeze";
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;
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
export async function safeFetch(input: RequestInfo | URL, options?: RequestInit, timeout = 10000) {
const req = await fetch(input, {
signal: timeoutSignal(timeout),

View file

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

View file

@ -3,4 +3,4 @@
export function unfreeze(obj: object) {
if (Object.isFrozen(obj)) return Object.assign({}, 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]> {
const cloned = { ...object };
keys.forEach((k) => delete cloned[k]);
keys.forEach(k => delete cloned[k]);
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 patcher from "@lib/api/patcher";
import * as debug from "@lib/debug";
import * as managers from "@lib/managers";
import * as metro from "@lib/metro";
@ -8,8 +8,6 @@ import * as settings from "@lib/settings";
import * as ui from "@lib/ui";
import * as utils from "@lib/utils";
import patcher from "@lib/api/patcher";
// You may now worry about my mental state.
function createBunnyObject(unloads: any[]) {
return {
@ -59,7 +57,7 @@ function createBunnyObject(unloads: any[]) {
// @ts-expect-error
delete window.bunny;
},
}
};
}
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;
}
export * from "bunny-build";
export * from "bunny-build";

View file

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