Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
kmmiio99o 2025-12-05 16:11:08 +01:00
commit 9ca96e1c26
7 changed files with 224 additions and 336 deletions

View file

@ -1,12 +1,10 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "ShiggyCord",
"name": "Kettu",
"dependencies": {
"@gullerya/object-observer": "^6.1.4",
"@shopify/react-native-skia": "^2.4.2",
"@tanstack/react-query": "^5.83.0",
"@types/chroma-js": "~2.4.5",
"@types/lodash": "~4.17.20",
@ -16,7 +14,7 @@
"intl-messageformat": "^10.7.16",
"moment": "2.22.2",
"react-native": "0.78.0",
"react-native-reanimated": "^4.1.3",
"react-native-reanimated": "^3.18.0",
"spitroast": "^1.4.4",
"type-fest": "^4.41.0",
},
@ -474,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@2.4.2", "", { "dependencies": { "canvaskit-wasm": "0.40.0", "react-reconciler": "0.31.0" }, "peerDependencies": { "react": ">=19.0", "react-native": ">=0.78", "react-native-reanimated": ">=3.19.1" }, "optionalPeers": ["react-native", "react-native-reanimated"], "bin": { "setup-skia-web": "scripts/setup-canvaskit.js" } }, "sha512-W+boV6E/hvRPcA8L7QRKnLs+05/iMEDbhVRLIx0UpW7NOQ8EbWCnu16mCU9BYJ/4s/y3NyYNk+wfP45Gf0LARQ=="],
"@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=="],
@ -570,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=="],
@ -686,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=="],
@ -1410,13 +1402,9 @@
"react-native": ["react-native@0.78.0", "", { "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native/assets-registry": "0.78.0", "@react-native/codegen": "0.78.0", "@react-native/community-cli-plugin": "0.78.0", "@react-native/gradle-plugin": "0.78.0", "@react-native/js-polyfills": "0.78.0", "@react-native/normalize-colors": "0.78.0", "@react-native/virtualized-lists": "0.78.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.25.1", "base64-js": "^1.5.1", "chalk": "^4.0.0", "commander": "^12.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.6.3", "memoize-one": "^5.0.0", "metro-runtime": "^0.81.0", "metro-source-map": "^0.81.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.0.1", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.25.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.0.0", "react": "^19.0.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-3PO4tNvCN6BdAKcoY70v1sLfxYCmDR4KS1VTY+kIBKy5Qznp27QNxL7zBQjvS6Jp91gc8N82QbysQrfBlwg9gQ=="],
"react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.2.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q=="],
"react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.1.7", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w=="],
"react-native-reanimated": ["react-native-reanimated@4.1.3", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*", "react-native-worklets": ">=0.5.0" } }, "sha512-GP8wsi1u3nqvC1fMab/m8gfFwFyldawElCcUSBJQgfrXeLmsPPUOpDw44lbLeCpcwUuLa05WTVePdTEwCLTUZg=="],
"react-native-worklets": ["react-native-worklets@0.6.1", "", { "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", "semver": "7.7.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-URca8l7c7Uog7gv4mcg9KILdJlnbvwdS5yfXQYf5TDkD2W1VY1sduEKrD+sA3lUPXH/TG1vmXAvNxCNwPMYgGg=="],
"react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="],
"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-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],

View file

@ -1,58 +1,57 @@
{
"name": "ShiggyCord",
"private": true,
"author": {
"name": "kmmiio99o",
"url": "https://github.com/kmmiio99o"
},
"scripts": {
"build": "node scripts/build.mjs",
"serve": "node scripts/serve.mjs",
"serve:adb": "bun run serve --adb",
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx"
},
"license": "BSD-3-Clause",
"devDependencies": {
"@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"semantic-release": "^24.2.7",
"@swc/core": "^1.13.1",
"@swc/helpers": "^0.5.17",
"@types/node": "^20.19.9",
"@types/yargs-parser": "^21.0.3",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@typescript-eslint/typescript-estree": "^7.18.0",
"chalk": "^5.4.1",
"esbuild": "^0.20.2",
"esbuild-plugin-globals": "^0.2.0",
"eslint": "^8.57.1",
"eslint-plugin-import-alias": "^1.2.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^3.2.0",
"typescript": "^5.8.3",
"yargs-parser": "^21.1.1"
},
"dependencies": {
"@gullerya/object-observer": "^6.1.4",
"@shopify/react-native-skia": "^2.4.2",
"@tanstack/react-query": "^5.83.0",
"@types/chroma-js": "~2.4.5",
"@types/lodash": "~4.17.20",
"@types/react": "19.1.8",
"es-toolkit": "^1.39.7",
"fuzzysort": "^3.1.0",
"intl-messageformat": "^10.7.16",
"moment": "2.22.2",
"react-native": "0.78.0",
"react-native-reanimated": "^4.1.3",
"spitroast": "^1.4.4",
"type-fest": "^4.41.0"
},
"trustedDependencies": [
"@swc/core"
]
}
"name": "Kettu",
"private": true,
"author": {
"name": "cocobo1",
"url": "https://github.com/C0C0B01"
},
"scripts": {
"build": "node scripts/build.mjs",
"serve": "node scripts/serve.mjs",
"serve:adb": "bun run serve --adb",
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx"
},
"license": "BSD-3-Clause",
"devDependencies": {
"@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"semantic-release": "^24.2.7",
"@swc/core": "^1.13.1",
"@swc/helpers": "^0.5.17",
"@types/node": "^20.19.9",
"@types/yargs-parser": "^21.0.3",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@typescript-eslint/typescript-estree": "^7.18.0",
"chalk": "^5.4.1",
"esbuild": "^0.20.2",
"esbuild-plugin-globals": "^0.2.0",
"eslint": "^8.57.1",
"eslint-plugin-import-alias": "^1.2.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^3.2.0",
"typescript": "^5.8.3",
"yargs-parser": "^21.1.1"
},
"dependencies": {
"@gullerya/object-observer": "^6.1.4",
"@tanstack/react-query": "^5.83.0",
"@types/chroma-js": "~2.4.5",
"@types/lodash": "~4.17.20",
"@types/react": "19.1.8",
"es-toolkit": "^1.39.7",
"fuzzysort": "^3.1.0",
"intl-messageformat": "^10.7.16",
"moment": "2.22.2",
"react-native": "0.78.0",
"react-native-reanimated": "^3.18.0",
"spitroast": "^1.4.4",
"type-fest": "^4.41.0"
},
"trustedDependencies": [
"@swc/core"
]
}

View file

@ -3,7 +3,9 @@ import initFixes from "@core/fixes";
import { initFetchI18nStrings } from "@core/i18n";
import initSettings from "@core/ui/settings";
import { initVendettaObject } from "@core/vendetta/api";
import { VdPluginManager } from "@core/vendetta/plugins";
import { updateFonts } from "@lib/addons/fonts";
import { initPlugins, updatePlugins } from "@lib/addons/plugins";
import { initThemes } from "@lib/addons/themes";
import { patchCommands } from "@lib/api/commands";
import { patchLogHook } from "@lib/api/debug";
@ -11,114 +13,36 @@ 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 { InteractionManager } from "react-native";
import { updaterSettings } from "@lib/api/settings";
import { getDebugInfo } from "@lib/api/debug";
// Debug toggle helper (temporary runtime fallback). The helper is dynamically
// imported when needed (to avoid bundling it permanently) and removed after use.
import * as lib from "./lib";
/**
* Start sequence split into critical (UI) and deferred (network/plugin) work.
* The goal is to get the UI ready quickly and run heavy tasks after interactions.
*/
export default async () => {
// Critical initializers that are required for the app's UI and basic features.
const criticalInits = [
initThemes(),
injectFluxInterceptor(),
patchSettings(),
patchLogHook(),
patchCommands(),
patchJsx(),
patchErrorBoundary(),
initVendettaObject(),
initFetchI18nStrings(),
initSettings(),
initFixes(),
];
// Load everything in parallel
// Run critical inits and collect unpatchers/cleanup handlers.
await Promise.all(criticalInits)
.then((u) => u.forEach((f) => f && lib.unload.push(f)))
.catch((e) => {
// Log but don't abort — critical inits failing should be visible in logs.
console.warn("Critical initialization error:", e);
});
// Expose the library object early so UI and other code can access window.bunny.
window.bunny = lib;
logger.log(
"ShiggyCord: UI-critical initialization complete — deferring plugin & network work",
);
// Deferred work: run after interactions to avoid blocking initial paint and navigation.
const runDeferred = async () => {
const { VdPluginManager } = await import("@core/vendetta/plugins");
const { initPlugins, updatePlugins } = await import("@lib/addons/plugins");
// Initialize Vendetta plugins (may start many plugins) — do not block UI.
VdPluginManager.initPlugins()
.then((u) => lib.unload.push(u))
.catch((e) => logger.log("Vendetta init failed:", e));
// Start ShiggyCord (Bunny) plugins now without forcing repository updates.
// Plugin repository fetching is deferred so the app can finish launching first.
// Stagger plugin startup to reduce CPU/memory spikes: use smaller batches and a small interval.
// This keeps the UI responsive while plugins initialize in the background.
initPlugins({ staggerInterval: 500, batchSize: 2 });
// Attempt a lightweight recovery toggle if some core plugins failed to start.
try {
// Dynamically import the helper (if present) but suppress verbose errors.
const mod = await import("@core/debug/toggleCorePlugins").catch(
() => null,
);
if (mod?.default) {
// Run helper with minimal noise; ignore failures.
mod.default({ offDuration: 1500 }).catch(() => {});
}
// Try to remove the helper source file (best-effort, ignore failures).
await import("@lib/api/native/fs")
.then((fs) => fs.removeFile("src/core/debug/toggleCorePlugins.ts"))
.catch(() => {});
} catch {
// suppressed
}
// Update fonts in background
updateFonts().catch((e) => logger.log("updateFonts failed:", e));
// Schedule plugin repository update after a delay (5 minutes) so update work
// does not impact initial launch performance.
setTimeout(
() => {
updatePlugins().catch((e) =>
logger.log("updatePlugins failed (deferred 5min):", e),
);
},
5 * 60 * 1000,
await Promise.all([
initThemes(),
injectFluxInterceptor(),
patchSettings(),
patchLogHook(),
patchCommands(),
patchJsx(),
initVendettaObject(),
initFetchI18nStrings(),
initSettings(),
initFixes(),
patchErrorBoundary(),
updatePlugins(),
updateFonts(),
initPlugins(),
VdPluginManager.initPlugins(),
]).then(
// Push them all to unloader
u => u.forEach(f => f && lib.unload.push(f))
);
// Note: we intentionally moved the call to `updatePlugins()` above so core
// plugins are registered prior to calling `initPlugins()`.
};
// Assign window object
window.bunny = lib;
// Preferred: wait until interactions finish (animations / navigation).
try {
InteractionManager.runAfterInteractions(() => {
// small delay to ensure native lifecycle settled
setTimeout(runDeferred, 50);
});
} catch (e) {
// Fallback if InteractionManager isn't available for any reason.
setTimeout(runDeferred, 200);
}
// Final ready log for basic UI availability.
logger.log("ShiggyCord is ready.");
};
// We good :)
logger.log("Kettu is ready!");
};

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) {
const patches = [
patchStorage(),
patchDefinitionAndResolver(),
patchChatBackground(),
];
if (manifest) updateBunnyColor(manifest, { update: false });
fixStatusBar();
fixKeyboard();
const patches = [
patchStorage(),
patchDefinitionAndResolver(),
patchChatBackground()
];
if (manifest) updateBunnyColor(manifest, { update: false });
return () => patches.forEach((p) => p());
}
return () => patches.forEach(p => p());
}

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.PRIMARY_700 || "#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 === 1 ? fallbackValue : chroma(fallbackValue).alpha(colorDef.opacity).hex();
}
// Fallback to default
return orig(...args);
}),
@ -109,4 +89,4 @@ function extractInfo(themeName: string, colorObj: any): [name: string, colorDef:
const colorDef = tokenReference.SemanticColor[propName];
return [propName, colorDef[themeName]];
}
}

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

@ -1,21 +1,10 @@
import {
awaitStorage,
createFileBackend,
createMMKVBackend,
createStorage,
wrapSync,
} from "@core/vendetta/storage";
import { awaitStorage, createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@core/vendetta/storage";
import { writeFile } from "@lib/api/native/fs";
import {
getStoredTheme,
getThemeFilePath,
isPyonLoader,
isThemeSupported,
} from "@lib/api/native/loader";
import { getStoredTheme, getThemeFilePath, isPyonLoader, isThemeSupported } from "@lib/api/native/loader";
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";
@ -23,157 +12,157 @@ import { VendettaThemeManifest } from "./colors/types";
import { updateBunnyColor } from "./colors/updater";
export interface VdThemeInfo {
id: string;
selected: boolean;
data: VendettaThemeManifest;
id: string;
selected: boolean;
data: VendettaThemeManifest;
}
export const themes = wrapSync(
createStorage<Record<string, VdThemeInfo>>(
createMMKVBackend("VENDETTA_THEMES"),
),
);
export const themes = wrapSync(createStorage<Record<string, VdThemeInfo>>(createMMKVBackend("VENDETTA_THEMES")));
/**
* @internal
*/
export async function writeThemeToNative(theme: VdThemeInfo | {}) {
if (typeof theme !== "object") throw new Error("Theme must be an object");
if (typeof theme !== "object") throw new Error("Theme must be an object");
// Save the current theme as current-theme.json. When supported by loader,
// this json will be written to appropriate path and be used to theme the native side.
await createFileBackend(getThemeFilePath() || "theme.json").set(theme);
// Save the current theme as current-theme.json. When supported by loader,
// this json will be written to appropriate path and be used to theme the native side.
await createFileBackend(getThemeFilePath() || "theme.json").set(theme);
}
// Process data for some compatiblity with native side
function processData(data: VendettaThemeManifest) {
if (data.semanticColors) {
const { semanticColors } = data;
if (data.semanticColors) {
const { semanticColors } = data;
for (const key in semanticColors) {
for (const index in semanticColors[key]) {
semanticColors[key][index] &&=
normalizeToHex(semanticColors[key][index] as string) || false;
}
}
}
if (data.rawColors) {
const { rawColors } = data;
for (const key in rawColors) {
const normalized = normalizeToHex(rawColors[key]);
if (normalized) data.rawColors[key] = normalized;
for (const key in semanticColors) {
for (const index in semanticColors[key]) {
semanticColors[key][index] &&= normalizeToHex(semanticColors[key][index] as string) || false;
}
}
}
if (Platform.OS === "android") applyAndroidAlphaKeys(rawColors);
}
if (data.rawColors) {
const { rawColors } = data;
// this field is required by the spec but vd seems to ignore this
// so are most vd themes
data.spec ??= 2;
for (const key in rawColors) {
const normalized = normalizeToHex(rawColors[key]);
if (normalized) data.rawColors[key] = normalized;
}
return data;
if (Platform.OS === "android") applyAndroidAlphaKeys(rawColors);
}
if (data.spec === undefined) {
if (!('theme_color_map' in data)) {
data.spec = 2;
}
//If not then Enmity theme and no spec
}
return data;
}
function validateTheme(themeJSON: any): boolean {
if (typeof themeJSON !== "object" || themeJSON === null) return false;
if (themeJSON.spec !== 2 && themeJSON.spec !== 3) return false;
if (themeJSON.spec === 3 && !themeJSON.main) return false;
if (typeof themeJSON !== "object" || themeJSON === null) return false;
return true;
if (themeJSON.spec === 3 && !themeJSON.main) return false;
if (themeJSON.spec === 2) return true;
if (themeJSON.theme_color_map) return true;
return themeJSON.spec === 2 || themeJSON.spec === 3;
}
export async function fetchTheme(url: string, selected = false) {
let themeJSON: any;
let themeJSON: any;
try {
themeJSON = await (await safeFetch(url, { cache: "no-store" })).json();
} catch {
throw new Error(`Failed to fetch theme at ${url}`);
}
try {
themeJSON = await (await safeFetch(url, { cache: "no-store" })).json();
} catch {
throw new Error(`Failed to fetch theme at ${url}`);
}
// Validate theme
if (!validateTheme(themeJSON)) throw new Error(`Invalid theme at ${url}`);
// Validate theme
if (!validateTheme(themeJSON)) throw new Error(`Invalid theme at ${url}`);
themes[url] = {
id: url,
selected: selected,
data: processData(themeJSON),
};
themes[url] = {
id: url,
selected: selected,
data: processData(themeJSON),
};
if (selected) {
writeThemeToNative(themes[url]);
updateBunnyColor(themes[url].data, { update: true });
}
if (selected) {
writeThemeToNative(themes[url]);
updateBunnyColor(themes[url].data, { update: true });
}
}
export async function installTheme(url: string) {
if (typeof url !== "string" || url in themes)
throw new Error("Theme already installed");
await fetchTheme(url);
if (typeof url !== "string" || url in themes) throw new Error("Theme already installed");
await fetchTheme(url);
}
export function selectTheme(theme: VdThemeInfo | null, write = true) {
if (theme) theme.selected = true;
Object.keys(themes).forEach(
(k) => (themes[k].selected = themes[k].id === theme?.id),
);
if (theme) theme.selected = true;
Object.keys(themes).forEach(
k => themes[k].selected = themes[k].id === theme?.id
);
if (theme == null && write) {
updateBunnyColor(null, { update: false, startup: true });
return writeThemeToNative({});
} else if (theme) {
updateBunnyColor(theme.data, { update: true, startup: false });
return writeThemeToNative(theme);
}
if (theme == null && write) {
updateBunnyColor(null, { update: true });
return writeThemeToNative({});
} else if (theme) {
updateBunnyColor(theme.data, { update: true });
return writeThemeToNative(theme);
}
}
export async function removeTheme(id: string) {
const theme = themes[id];
if (theme.selected) await selectTheme(null);
delete themes[id];
const theme = themes[id];
if (theme.selected) await selectTheme(null);
delete themes[id];
return theme.selected;
return theme.selected;
}
export async function updateThemes() {
await awaitStorage(themes);
const currentTheme = getThemeFromLoader();
await Promise.allSettled(
Object.keys(themes).map((id) => fetchTheme(id, currentTheme?.id === id)),
);
await awaitStorage(themes);
const currentTheme = getThemeFromLoader();
await Promise.allSettled(Object.keys(themes).map(id => fetchTheme(id, currentTheme?.id === id)));
}
export function getCurrentTheme() {
return Object.values(themes).find((t) => t.selected) ?? null;
return Object.values(themes).find(t => t.selected) ?? null;
}
/**
* @internal
*/
export function getThemeFromLoader(): VdThemeInfo | null {
return getStoredTheme();
return getStoredTheme();
}
/**
* @internal
*/
export async function initThemes() {
if (!isThemeSupported()) return;
if (!isThemeSupported()) return;
if (settings.safeMode?.enabled) return;
try {
if (isPyonLoader()) {
writeFile("../vendetta_theme.json", "null");
try {
if (isPyonLoader()) {
writeFile("../vendetta_theme.json", "null");
}
await newAwaitStorage(colorsPref);
const currentTheme = getThemeFromLoader();
initColors(currentTheme?.data ?? null);
updateThemes().catch(e => console.error("Failed to update themes", e));
} catch (e) {
console.error("Failed to initialize themes", e);
}
await newAwaitStorage(colorsPref);
const currentTheme = getThemeFromLoader();
initColors(currentTheme?.data ?? null);
updateThemes().catch((e) => console.error("Failed to update themes", e));
} catch (e) {
console.error("Failed to initialize themes", e);
}
}