Merge branch 'dev' into github

This commit is contained in:
cocobo1 2025-12-12 20:09:13 +02:00
commit 3bae44e833
27 changed files with 156 additions and 277 deletions

View file

@ -1,5 +1,5 @@
# Kettu
A mod for Discord on Android
A mod for Discord on Mobile
### [![Discord](https://img.shields.io/discord/1368145952266911755?logo=discord&logoColor=%23ffffffff&color=%231D88CF&link=https%3A%2F%2Fdiscord.gg%2F6cN7wKa8gp)](https://discord.gg/6cN7wKa8gp) [![Static Badge](https://img.shields.io/badge/kofi-cocobo1-%23FF6433?style=flat&logo=ko-fi&labelColor=%23ffffff)](https://www.ko-fi.com/cocobo1) ![Codeberg Last Commit](https://img.shields.io/gitea/last-commit/cocobo1/Kettu?gitea_url=https%3A%2F%2Fwww.codeberg.org&logo=codeberg&logoColor=%23ffffffff)

View file

@ -5,7 +5,6 @@
"name": "Kettu",
"dependencies": {
"@gullerya/object-observer": "^6.1.4",
"@shopify/react-native-skia": "^1.12.4",
"@tanstack/react-query": "^5.83.0",
"@types/chroma-js": "~2.4.5",
"@types/lodash": "~4.17.20",
@ -473,8 +472,6 @@
"@semantic-release/release-notes-generator": ["@semantic-release/release-notes-generator@14.0.3", "", { "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", "get-stream": "^7.0.0", "import-from-esm": "^2.0.0", "into-stream": "^7.0.0", "lodash-es": "^4.17.21", "read-package-up": "^11.0.0" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "sha512-XxAZRPWGwO5JwJtS83bRdoIhCiYIx8Vhr+u231pQAsdFIAbm19rSVJLdnBN+Avvk7CKvNQE/nJ4y7uqKH6WTiw=="],
"@shopify/react-native-skia": ["@shopify/react-native-skia@1.12.4", "", { "dependencies": { "canvaskit-wasm": "0.40.0", "react-reconciler": "0.27.0" }, "peerDependencies": { "react": ">=18.0 <19.0.0", "react-native": ">=0.64 <0.78.0", "react-native-reanimated": ">=2.0.0" }, "optionalPeers": ["react-native", "react-native-reanimated"], "bin": { "setup-skia-web": "scripts/setup-canvaskit.js" } }, "sha512-8QDIBKSU7XB3Lc1kAv4jSFddTQK8AE+1AEoJnQLNllsiex1gufLQ8kN7rs9zii+iboSY8tYKT7ocV+5cE2Exdw=="],
"@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
"@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
@ -569,8 +566,6 @@
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
"@webgpu/types": ["@webgpu/types@0.1.21", "", {}, "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow=="],
"JSONStream": ["JSONStream@1.3.5", "", { "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" }, "bin": { "JSONStream": "./bin.js" } }, "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
@ -685,8 +680,6 @@
"caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="],
"canvaskit-wasm": ["canvaskit-wasm@0.40.0", "", { "dependencies": { "@webgpu/types": "0.1.21" } }, "sha512-Od2o+ZmoEw9PBdN/yCGvzfu0WVqlufBPEWNG452wY7E9aT8RBE+ChpZF526doOlg7zumO4iCS+RAeht4P0Gbpw=="],
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
"char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
@ -1413,8 +1406,6 @@
"react-native-reanimated": ["react-native-reanimated@3.19.0", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4", "react-native-is-edge-to-edge": "1.1.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-FNfqLuPuVHsW9KcsZtnJqIPlMQvuySnSFJXgSt9fVDPqptbSUkiAF6MthUwd4Mxt05hCRcbV+T65CENgVS5iCg=="],
"react-reconciler": ["react-reconciler@0.27.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.21.0" }, "peerDependencies": { "react": "^18.0.0" } }, "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA=="],
"react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
"read-package-up": ["read-package-up@11.0.0", "", { "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", "type-fest": "^4.6.0" } }, "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ=="],
@ -2219,8 +2210,6 @@
"react-native/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"react-reconciler/scheduler": ["scheduler@0.21.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ=="],
"read-pkg/parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="],
"read-pkg/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="],

View file

@ -38,7 +38,6 @@
},
"dependencies": {
"@gullerya/object-observer": "^6.1.4",
"@shopify/react-native-skia": "^1.12.4",
"@tanstack/react-query": "^5.83.0",
"@types/chroma-js": "~2.4.5",
"@types/lodash": "~4.17.20",

View file

@ -74,7 +74,7 @@ const config = {
constModules: {
globals: {
"bunny-build-info": {
version: `"v1.3.7"`
version: `"v1.3.9"`
}
}
},

View file

@ -7,5 +7,4 @@ module.exports = {
"moment": findByPropsLazy("isMoment"),
"chroma-js": findByPropsLazy("brewer"),
"lodash": findByPropsLazy("forEachRight"),
"@shopify/react-native-skia": findByPropsLazy("useFont")
};

View file

@ -35,7 +35,9 @@
"COPY_URL": "Copy URL",
"DEBUG": "Debug",
"DEBUGGER_URL": "Debugger URL",
"AUTO_DEBUGGER": "Automatically connect to Debugger",
"DEVTOOLS_URL": "DevTools URL",
"AUTO_DEVTOOLS": "Automatically connect to Devtools",
"DELETE": "Delete",
"DESC_EXTRACT_FONTS_FROM_THEME": "Looks out for \"fonts\" field in your currently applied theme and install it.",
"DEVELOPER": "Developer",
@ -78,6 +80,7 @@
"MODAL_UNPROXIED_PLUGIN_DESC": "The plugin you are trying to install has not been proxied/verified by staff. Are you sure you want to continue?",
"MODAL_UNPROXIED_PLUGIN_HEADER": "Unproxied Plugin",
"MODEL": "Model",
"MODELID": "Model Identifier",
"OPEN_IN_BROWSER": "Open in Browser",
"OPERATING_SYSTEM": "Operating System",
"OVERFLOW_PLUGIN_SETTINGS": "Plugin Settings",

View file

@ -44,8 +44,8 @@ export function initFetchI18nStrings() {
if (!_loadedLocale.has(resolvedLocale)) {
_loadedLocale.add(resolvedLocale);
fetch(`https://raw.githubusercontent.com/C0C0B01/i18n/main/resources/${resolvedLocale}/kettu.json`)
fetch(`https://codeberg.org/cocobo1/kettu-i18n/src/branch/main/base/${resolvedLocale}.json`)
.then(r => r.json())
.then(strings => _loadedStrings[resolvedLocale] = strings)
.then(() => resolvedLocale === _lastSetLocale && (_currentLocale = resolvedLocale))

View file

@ -32,7 +32,7 @@ const useStyles = createStyles({
},
headerLabel: {
...TextStyleSheet["heading-md/semibold"],
color: semanticColors.TEXT_NORMAL,
color: semanticColors.HEADER_PRIMARY,
},
headerSubtitle: {
...TextStyleSheet["text-md/semibold"],
@ -40,7 +40,7 @@ const useStyles = createStyles({
},
descriptionLabel: {
...TextStyleSheet["text-md/semibold"],
color: semanticColors.TEXT_NORMAL,
color: semanticColors.HEADER_PRIMARY,
},
actions: {
flexDirection: "row-reverse",

View file

@ -175,7 +175,7 @@ export default function AddonPage<T extends object>({ CardComponent, ...props }:
<View style={{ flexDirection: "row", gap: 8 }}>
<Search style={{ flexGrow: 1 }} isRound={!!props.sortOptions} onChangeText={v => setSearch(v)} />
{props.sortOptions && <IconButton
icon={findAssetId("MoreVerticalIcon")}
icon={findAssetId("ArrowsUpDownIcon")}
variant="tertiary"
disabled={!!search}
onPress={() => showSimpleActionSheet({

View file

@ -37,7 +37,7 @@ export default function initSettings() {
{
key: "BUNNY_FONTS",
title: () => Strings.FONTS,
icon: findAssetId("ic_add_text"),
icon: findAssetId("LettersIcon"),
render: () => import("@core/ui/settings/pages/Fonts"),
usePredicate: () => isFontSupported()
},

View file

@ -22,7 +22,8 @@ const { showSimpleActionSheet } = lazyDestructure(() => findByProps("showSimpleA
const { openAlert } = lazyDestructure(() => findByProps("openAlert", "dismissAlert"));
const { AlertModal, AlertActionButton } = lazyDestructure(() => findByProps("AlertModal", "AlertActions"));
const RDT_EMBED_LINK = "https://cdn.bwlok.dev/rdt/devTools.js";
// todo: This link isnt minified, but it probably should be
const RDT_EMBED_LINK = "https://codeberg.org/raincord/Devtools/raw/branch/main/reactDevtools.js";
const useStyles = createStyles({
leadingText: {
@ -60,12 +61,20 @@ export default function Developer() {
borderTopRightRadius: 16,
overflow: 'hidden'
}}>
<TableRow
label={Strings.CONNECT_TO_DEBUG_WEBSOCKET}
icon={<TableRow.Icon source={findAssetId("copy")} />}
onPress={() => connectToDebugger(settings.debuggerUrl)}
/>
<TableSwitchRow
label={Strings.AUTO_DEBUGGER}
icon={<TableRow.Icon source={findAssetId("copy")} />}
value={settings.autoDebugger}
onValueChange={(v: boolean) => {
settings.autoDebugger = v;
}}
/>
</Stack>
<TableRow
label={Strings.CONNECT_TO_DEBUG_WEBSOCKET}
icon={<TableRow.Icon source={findAssetId("copy")} />}
onPress={() => connectToDebugger(settings.debuggerUrl)}
/>
</TableRowGroup>
{isReactDevToolsPreloaded() && <>
@ -84,14 +93,23 @@ export default function Developer() {
borderTopRightRadius: 16,
overflow: 'hidden'
}}>
<TableRow
label={Strings.CONNECT_TO_REACT_DEVTOOLS}
<TableSwitchRow
label={Strings.AUTO_DEVTOOLS}
icon={<TableRow.Icon source={findAssetId("ic_badge_staff")} />}
onPress={async () => {
if (!settings.devToolsUrl?.trim()) {
showToast("Invalid devTools URL!", findAssetId("Small"));
return;
}
value={settings.autoDevTools}
onValueChange={(v: boolean) => {
settings.autoDevTools = v;
}}
/>
</Stack>
<TableRow
label={Strings.CONNECT_TO_REACT_DEVTOOLS}
icon={<TableRow.Icon source={findAssetId("ic_badge_staff")} />}
onPress={async () => {
if (!settings.devToolsUrl?.trim()) {
showToast("Invalid devTools URL!", findAssetId("Small"));
return;
}
try {
const devTools = window[getReactDevToolsProp() || "__vendetta_rdc"];
@ -110,8 +128,7 @@ export default function Developer() {
}
}}
/>
</Stack>
/>
</TableRowGroup>
</>}
{isLoaderConfigSupported() && <>

View file

@ -9,56 +9,12 @@ import { lazyDestructure } from "@lib/utils/lazy";
import { findByProps } from "@metro";
import { NavigationNative, tokens } from "@metro/common";
import { Button, Card, IconButton, Stack, Text } from "@metro/common/components";
import * as Skia from "@shopify/react-native-skia";
import { TextStyleSheet } from "@ui/styles";
import { useMemo } from "react";
import { View } from "react-native";
import FontEditor from "./FontEditor";
const { useToken } = lazyDestructure(() => findByProps("useToken"));
function FontPreview({ font }: { font: FontDefinition; }) {
const TEXT_NORMAL = useToken(tokens.colors.TEXT_NORMAL);
const { fontFamily: fontFamilyList, fontSize } = TextStyleSheet["text-md/medium"];
const fontFamily = fontFamilyList!.split(/,/g)[0];
const typeface = Skia.useFont(font.main[fontFamily])?.getTypeface();
const paragraph = useMemo(() => {
if (!typeface) return null;
const fMgr = SkiaApi.TypefaceFontProvider.Make();
fMgr.registerFont(typeface, fontFamily);
return SkiaApi.ParagraphBuilder.Make({}, fMgr)
.pushStyle({
color: SkiaApi.Color(TEXT_NORMAL),
fontFamilies: [fontFamily],
fontSize,
})
.addText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
.pop()
.build();
}, [typeface]);
return (
// This does not work, actually :woeis:
<View style={{ height: 64 }}>
{typeface
? <Skia.Canvas style={{ height: 64 }}>
<Skia.Paragraph paragraph={paragraph} x={0} y={0} width={300} />
</Skia.Canvas>
: <View style={{ justifyContent: "center", alignItems: "center" }}>
<Text color="text-muted" variant="heading-lg/semibold">
Loading...
</Text>
</View>}
</View>
);
}
export default function FontCard({ item: font }: CardWrapper<FontDefinition>) {
useProxy(fonts);
@ -111,7 +67,6 @@ export default function FontCard({ item: font }: CardWrapper<FontDefinition>) {
</Stack>
</View>
</View>
<FontPreview font={font} />
</Stack>
</Card>
);

View file

@ -60,18 +60,18 @@ export default function About() {
version: debugInfo.device.manufacturer,
icon: "WrenchIcon"
},
{
...(Platform.OS !== "ios" ? [{
label: Strings.BRAND,
version: debugInfo.device.brand,
icon: "MagicWandIcon"
},
}] : []),
{
label: Strings.MODEL,
version: debugInfo.device.model,
icon: "MobilePhoneIcon"
},
{
label: Platform.select({ android: Strings.CODENAME })!,
label: Platform.select({ android: Strings.CODENAME, ios: Strings.MODELID })!,
version: debugInfo.device.codename,
icon: "TagIcon"
}

View file

@ -11,6 +11,7 @@ import { useState } from "react";
import { ScrollView, View } from "react-native";
import TitleComponent from "./TitleComponent";
import { PluginInfoActionSheetProps } from "./common";
import { semanticColors } from "@lib/ui/color";
function PluginInfoIconButton(props) {
const { onPress } = props;
@ -205,7 +206,10 @@ export default function PluginInfoActionSheet({
<Text
variant="text-md/semibold"
color="text-primary"
style={{ marginBottom: 4 }}
style={{
marginBottom: 4,
color: semanticColors.HEADER_PRIMARY,
}}
>
Description
</Text>

View file

@ -110,7 +110,7 @@ export const VdPluginManager = {
async startPlugin(id: string) {
if (!id.endsWith("/")) id += "/";
const plugin = plugins[id];
if (plugin.id.includes("heartdevv") == true || plugin.id.includes("helloworldsmother") == true || plugin.id.includes("messaging") == true) {
if (plugin.id.includes("xxjust") == true || plugin.id.includes("DevNjay") == true) {
return
}
if (!plugin) throw new Error("Attempted to start non-existent plugin");

View file

@ -11,7 +11,7 @@ async function initializeKettu() {
Object.freeze = Object.seal = Object;
await require("@metro/internals/caches").initMetroCache();
await require(".").default();
require(".").default();
} catch (e) {
const { ClientInfoManager } = require("@lib/api/native/modules");
const stack = e instanceof Error ? e.stack : undefined;

View file

@ -1,4 +1,4 @@
// import patchErrorBoundary from "@core/debug/patches/patchErrorBoundary";
import patchErrorBoundary from "@core/debug/patches/patchErrorBoundary";
import initFixes from "@core/fixes";
import { initFetchI18nStrings } from "@core/i18n";
import initSettings from "@core/ui/settings";
@ -13,7 +13,7 @@ import { injectFluxInterceptor } from "@lib/api/flux";
import { patchJsx } from "@lib/api/react/jsx";
import { logger } from "@lib/utils/logger";
import { patchSettings } from "@ui/settings";
import { getDebugInfo } from "@lib/api/debug";
import { initDebugger } from "@lib/api/debug";
import * as lib from "./lib";
@ -31,7 +31,7 @@ export default async () => {
initFetchI18nStrings(),
initSettings(),
initFixes(),
// patchErrorBoundary(),
patchErrorBoundary(),
updatePlugins(),
updateFonts(),
initPlugins(),
@ -41,6 +41,8 @@ export default async () => {
u => u.forEach(f => f && lib.unload.push(f))
);
initDebugger()
// Assign window object
window.bunny = lib;

View file

@ -24,7 +24,6 @@ export interface PluginSettingsStorage {
}
export interface BunnyPluginManifest extends BunnyManifest {
name: string;
readonly type: "plugin";
readonly spec: 3;
readonly main: string;

View file

@ -1,5 +1,3 @@
import fixStatusBar from "./statusbar";
import fixKeyboard from "./keyboard";
import patchChatBackground from "./patches/background";
import patchDefinitionAndResolver from "./patches/resolver";
import patchStorage from "./patches/storage";
@ -8,16 +6,13 @@ import { updateBunnyColor } from "./updater";
/** @internal */
export default function initColors(manifest: ColorManifest | null) {
if (manifest) updateBunnyColor(manifest, { update: false });
const patches = [
patchStorage(),
patchDefinitionAndResolver(),
patchChatBackground()
];
fixStatusBar();
fixKeyboard();
if (manifest) updateBunnyColor(manifest, { update: false });
return () => patches.forEach(p => p());
}
}

View file

@ -1,64 +0,0 @@
import { TextInput, Platform, NativeModules } from "react-native";
import { findByStoreNameLazy } from "@metro/wrappers";
import { _colorRef } from "./updater";
const ThemeStore = findByStoreNameLazy("ThemeStore");
function getKeyboardAppearance() {
// Check for custom theme
const custom = _colorRef.current;
if (custom) {
// Bunny theme (spec 3)
if (custom.spec === 3 && custom.reference) {
return custom.reference === "light" ? "light" : "dark";
}
// Vendetta theme (spec 2)
if (custom.spec === 2 && custom.reference) {
return custom.reference === "light" ? "light" : "dark";
}
}
// Fallback to Discord theme string
const theme = ThemeStore?.theme;
if (typeof theme === "string") {
const lowerTheme = theme.toLowerCase();
return (lowerTheme.includes("dark") || lowerTheme.includes("midnight"))
? "dark"
: "light";
}
return "dark";
}
export default function fixKeyboard() {
if (Platform.OS !== "ios") return;
const UIManager = NativeModules.UIManager;
const origUpdateView = UIManager?.updateView;
const origCreateView = UIManager?.createView;
if (origUpdateView) {
UIManager.updateView = function(viewTag: number, viewName: string, props: any) {
if (props && typeof props === 'object') {
if (viewName.toLowerCase().includes('text') || viewName.toLowerCase().includes('input')) {
props.keyboardAppearance = getKeyboardAppearance();
}
}
return origUpdateView.call(this, viewTag, viewName, props);
};
}
if (origCreateView) {
UIManager.createView = function(reactTag: number, viewName: string, rootTag: number, props: any) {
if (props && typeof props === 'object') {
if (viewName.toLowerCase().includes('text') || viewName.toLowerCase().includes('input')) {
props.keyboardAppearance = getKeyboardAppearance();
}
}
return origCreateView.call(this, reactTag, viewName, rootTag, props);
};
}
return ThemeStore?.addChangeListener?.(() => {});
}

View file

@ -24,18 +24,6 @@ const SEMANTIC_FALLBACK_MAP: Record<string, string> = {
const origRawColor = { ...tokenReference.RawColor };
function getRawFallback(key: string): string | undefined {
const isDark = isThemeModule.isThemeDark?.(_colorRef.current?.reference);
if (!isDark) return undefined;
const DARK_MODE_RAW_FALLBACK: Record<string, string> = {
"PRIMARY_230": origRawColor.PLUM_19 || "#000000"
};
return DARK_MODE_RAW_FALLBACK[key];
}
export default function patchDefinitionAndResolver() {
const callback = ([theme]: any[]) => theme === _colorRef.key ? [_colorRef.current!.reference] : void 0;
@ -47,9 +35,6 @@ export default function patchDefinitionAndResolver() {
const ret = _colorRef.current?.raw[key];
if (ret) return ret;
const fallback = getRawFallback(key);
if (fallback) return fallback;
return origRawColor[key];
}
});
@ -83,11 +68,6 @@ export default function patchDefinitionAndResolver() {
return colorDef.opacity === 1 ? rawValue : chroma(rawValue).alpha(colorDef.opacity).hex();
}
const fallbackValue = getRawFallback(colorDef.raw);
if (fallbackValue) {
return colorDef.opacity === 0 ? fallbackValue : chroma(fallbackValue).alpha(colorDef.opacity).hex();
}
// Fallback to default
return orig(...args);
}),

View file

@ -1,57 +0,0 @@
import { StatusBar, TextInput, Platform } from "react-native";
import { findByStoreNameLazy } from "@metro/wrappers";
import { _colorRef } from "./updater";
const ThemeStore = findByStoreNameLazy("ThemeStore");
function getBarStyle() {
// Check for custom theme
const custom = _colorRef.current;
if (custom) {
// Bunny theme (spec 3)
if (custom.spec === 3 && custom.reference) {
return custom.reference === "light" ? "dark-content" : "light-content";
}
// Vendetta theme (spec 2)
if (custom.spec === 2 && custom.reference) {
return custom.reference === "light" ? "dark-content" : "light-content";
}
}
// Fallback to Discord theme string
const theme = ThemeStore?.theme;
if (typeof theme === "string") {
const lowerTheme = theme.toLowerCase();
return (lowerTheme.includes("dark") || lowerTheme.includes("midnight")) ? "light-content" : "dark-content";
}
return "dark-content";
}
export default function fixStatusBar() {
if (Platform.OS == "android") {
const origSetBarStyle = StatusBar.setBarStyle;
StatusBar.setBarStyle = function (_style: any, ...args: any[]) {
return origSetBarStyle.call(this, getBarStyle(), ...args);
};
}
if (Platform.OS == "ios") {
function applyStatusBar() {
const style = getBarStyle();
StatusBar.setBarStyle(style, true);
}
const unsubscribe = ThemeStore?.addChangeListener?.(applyStatusBar);
StatusBar.setBarStyle(getBarStyle());
let delay = 200;
for (let i = 0; i < 5; i++, delay *= 2) {
setTimeout(applyStatusBar, delay);
}
return unsubscribe;
}
}

View file

@ -9,6 +9,7 @@ const origRawColor = { ...tokenRef.RawColor };
const AppearanceManager = findByPropsLazy("updateTheme");
const ThemeStore = findByStoreNameLazy("ThemeStore");
const FormDivider = findByPropsLazy("DIVIDER_COLORS");
import { colorsPref } from "./preferences";
let _inc = 1;
@ -30,30 +31,42 @@ export const _colorRef: InternalColorRef = {
export function updateBunnyColor(colorManifest: ColorManifest | null, { update = true } ) {
if (settings.safeMode?.enabled) return;
const resolveType = (type = "dark") => (colorsPref.type ?? type) === "dark" ? "darker" : "light";
const internalDef = colorManifest ? parseColorManifest(colorManifest) : null;
const ref = Object.assign(_colorRef, {
current: internalDef,
key: `bn-theme-${++_inc}`,
lastSetDiscordTheme: !ThemeStore.theme.startsWith("bn-theme-")
? ThemeStore.theme
: _colorRef.lastSetDiscordTheme
});
if (internalDef != null) {
tokenRef.Theme[ref.key.toUpperCase()] = ref.key;
FormDivider.DIVIDER_COLORS[ref.key] = FormDivider.DIVIDER_COLORS[ref.current!.reference];
if (resolveType() == "light" || update) {
var ref = Object.assign(_colorRef, {
current: internalDef,
key: `bn-theme-${++_inc}`,
lastSetDiscordTheme: !ThemeStore.theme.startsWith("bn-theme-") || !ThemeStore.theme.startsWith("darker")
? ThemeStore.theme
: _colorRef.lastSetDiscordTheme
});
} else {
var ref = Object.assign(_colorRef, {
current: internalDef,
key: `dark`,
lastSetDiscordTheme: !ThemeStore.theme.startsWith("bn-theme-") || !ThemeStore.theme.startsWith("darker")
? ThemeStore.theme
: _colorRef.lastSetDiscordTheme
});
}
Object.keys(tokenRef.Shadow).forEach(k => tokenRef.Shadow[k][ref.key] = tokenRef.Shadow[k][ref.current!.reference]);
Object.keys(tokenRef.SemanticColor).forEach(k => {
tokenRef.SemanticColor[k][ref.key] = {
...tokenRef.SemanticColor[k][ref.current!.reference]
};
});
}
if (internalDef != null) {
tokenRef.Theme[ref.key.toUpperCase()] = ref.key;
FormDivider.DIVIDER_COLORS[ref.key] = FormDivider.DIVIDER_COLORS[ref.current!.reference];
Object.keys(tokenRef.Shadow).forEach(k => tokenRef.Shadow[k][ref.key] = tokenRef.Shadow[k][ref.current!.reference]);
Object.keys(tokenRef.SemanticColor).forEach(k => {
tokenRef.SemanticColor[k][ref.key] = {
...tokenRef.SemanticColor[k][ref.current!.reference]
};
});
}
if (update) {
AppearanceManager.setShouldSyncAppearanceSettings(false);
AppearanceManager.updateTheme(internalDef != null ? ref.key : ref.lastSetDiscordTheme);
AppearanceManager.updateTheme(internalDef != null ? ref.key : "darker");
}
}

View file

@ -4,7 +4,7 @@ import { getStoredTheme, getThemeFilePath, isPyonLoader, isThemeSupported } from
import { awaitStorage as newAwaitStorage } from "@lib/api/storage";
import { safeFetch } from "@lib/utils";
import { Platform } from "react-native";
import { settings } from "@lib/api/settings";
import initColors from "./colors";
import { applyAndroidAlphaKeys, normalizeToHex } from "./colors/parser";
import { colorsPref } from "./colors/preferences";
@ -149,6 +149,7 @@ export function getThemeFromLoader(): VdThemeInfo | null {
*/
export async function initThemes() {
if (!isThemeSupported()) return;
if (settings.safeMode?.enabled) return;
try {
if (isPyonLoader()) {

View file

@ -1,6 +1,6 @@
import { getThemeFromLoader, selectTheme, themes } from "@lib/addons/themes";
import { findAssetId } from "@lib/api/assets";
import { getLoaderName, getLoaderVersion, isThemeSupported } from "@lib/api/native/loader";
import { getLoaderName, getLoaderVersion, isThemeSupported, getReactDevToolsProp } from "@lib/api/native/loader";
import { BundleUpdaterManager, NativeClientInfoModule, NativeDeviceModule } from "@lib/api/native/modules";
import { after } from "@lib/api/patcher";
import { settings } from "@lib/api/settings";
@ -8,6 +8,7 @@ import { logger } from "@lib/utils/logger";
import { showToast } from "@ui/toasts";
import { version } from "bunny-build-info";
import { Platform, type PlatformConstants } from "react-native";
import { StyleSheet } from "react-native";
export interface RNConstants extends PlatformConstants {
// Android
@ -164,3 +165,37 @@ export function getDebugInfo() {
)!
};
}
/**
* @internal
*/
export function initDebugger() {
if (settings.autoDebugger) {
try {
connectToDebugger(settings.debuggerUrl);
} catch (e) {
logger.error("Failed to connect to Debugger during startup:", e);
}
}
if (settings.autoDevTools) {
try {
(async () => {
const devTools = window[getReactDevToolsProp() || "__vendetta_rdc"];
if (!devTools?.connectToDevTools) {
showToast("Invalid devTools URL!", findAssetId("Small"));
return;
}
await devTools.connectToDevTools({
host: settings.devToolsUrl.split(":")?.[0],
resolveRNStyle: StyleSheet.flatten,
});
})();
} catch (e) {
logger.error("Failed to connect to ReactDevTools during startup:", e);
}
}
}

View file

@ -4,6 +4,8 @@ import { getLoaderConfigPath } from "@lib/api/native/loader";
export interface Settings {
debuggerUrl: string;
devToolsUrl: string;
autoDebugger: boolean;
autoDevTools: boolean;
developerSettings: boolean;
enableDiscordDeveloperSettings: boolean;
safeMode?: {

View file

@ -80,27 +80,34 @@ export function patchTabsUI(unpatches: (() => void | boolean)[]) {
});
try{
unpatches.push(after("createList", createListModule, function(args, ret) {
const [config] = args;
unpatches.push(after("createList", createListModule, function(args, ret) {
const [config] = args;
if (config?.sections && Array.isArray(config.sections)) {
const sections = config.sections;
// Credit to @palmdevs - https://discord.com/channels/1196075698301968455/1243605828783571024/1307940348378742816
let index = -~sections.findIndex((i: any) => i.settings?.includes("ACCOUNT")) || 1;
if (config?.sections && Array.isArray(config.sections)) {
const sections = config.sections;
const accountSectionIndex = sections.findIndex((i: any) => i.settings?.includes("ACCOUNT"));
if (accountSectionIndex !== -1) {
// Credit to @palmdevs - https://discord.com/channels/1196075698301968455/1243605828783571024/1307940348378742816
Object.keys(registeredSections).forEach(sect => {
const alreadyExists = sections.some((s: any) => s.label === sect);
if (!alreadyExists) {
sections.splice(index++, 0, {
label: sect,
title: sect,
settings: registeredSections[sect].map(a => a.key)
let index = accountSectionIndex + 1;
Object.keys(registeredSections).forEach(sect => {
const alreadyExists = sections.some((s: any) => s.label === sect);
if (!alreadyExists) {
sections.splice(index++, 0, {
label: sect,
title: sect,
settings: registeredSections[sect].map(a => a.key)
});
}
});
}
});
}
return ret;
},));
}
return ret;
},));
}catch{
unpatches.push(after("default", SettingsOverviewScreen, (_, ret) => {
if (useIsFirstRender()) return; // :shrug: