Compare commits
47 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1547421074 | ||
|
|
f3b7935ab0 | ||
|
|
2fca83c60a | ||
|
|
c663a3724c | ||
|
|
94e651a50e | ||
|
|
40726d5b9a | ||
|
|
d8b35b5501 | ||
|
|
32d32d3f02 | ||
|
|
3914eb8e11 | ||
|
|
ad9963ade2 | ||
|
|
363f9984f6 | ||
|
|
f5c9edd1fd | ||
|
|
c1b2498009 | ||
|
|
1e381e4940 | ||
|
|
e87aff3801 | ||
|
|
0ed3508d7c | ||
|
|
c1b8f5969e | ||
|
|
24ebe343bd | ||
|
|
d0b633fa17 | ||
|
|
9dfe5ca671 | ||
|
|
bdb448eb94 | ||
|
|
2c91da18ea | ||
|
|
1d630e1e3a | ||
|
|
2c0c24cb86 | ||
|
|
a42fb7ce36 | ||
|
|
f48aea843f | ||
|
|
8430031566 | ||
|
|
fa8ebe30bd | ||
|
|
e6e5728d25 | ||
|
|
5f41539026 | ||
|
|
1b99480b82 | ||
|
|
151b07434e | ||
|
|
11a6e4d1c4 | ||
|
|
cbcbd53cc0 | ||
|
|
d26de6be8e | ||
|
|
b51ae0677d | ||
|
|
5cdb7d9969 | ||
|
|
687f47318b | ||
|
|
1071349bbc | ||
|
|
72ce900cd8 | ||
|
|
ecef05061b | ||
|
|
15915a08b9 | ||
|
|
ad46e065c9 | ||
|
|
f5bbcecade | ||
|
|
3f5643931c | ||
|
|
28fe79ade4 | ||
|
|
5858340d04 |
15 changed files with 111 additions and 301 deletions
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "ShiggyCord",
|
"name": "GoonCord",
|
||||||
"private": true,
|
"private": true,
|
||||||
"author": {
|
"author": {
|
||||||
"name": "kmmiio99o",
|
"name": "OTKUSteyler",
|
||||||
"url": "https://github.com/kmmiio99o"
|
"url": "https://github.com/OTKUSteyler"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node scripts/build.mjs",
|
"build": "node scripts/build.mjs",
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ const config = {
|
||||||
constModules: {
|
constModules: {
|
||||||
globals: {
|
globals: {
|
||||||
"bunny-build-info": {
|
"bunny-build-info": {
|
||||||
version: `"1.4.1.7"`,
|
version: `"1.4.1.8"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,20 @@ export default () =>
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
execute([ephemeral], ctx) {
|
execute([ephemeral], ctx) {
|
||||||
const info = getDebugInfo();
|
const info = getDebugInfo();
|
||||||
const content = [
|
|
||||||
"**💦GoonCord💦 Debug Info**",
|
if(info.device.codename === undefined) { info.device.codename = ""}
|
||||||
`> 💦GoonCord💦: ${info.bunny.version} (${info.bunny.loader.name} ${info.bunny.loader.version})`,
|
else{ info.device.codename = "(" + info.device.codename + ")" }
|
||||||
`> Discord: ${info.discord.version} (${info.discord.build})`,
|
|
||||||
`> React: ${info.react.version} (RN ${info.react.nativeVersion})`,
|
const content = [
|
||||||
`> Hermes: ${info.hermes.version} (bcv${info.hermes.bytecodeVersion})`,
|
"**💦GoonCord💦 Debug Info**",
|
||||||
`> System: ${info.os.name} ${info.os.version} ${info.os.sdk ? `(SDK ${info.os.sdk})` : ""}`.trimEnd(),
|
`> GoonCord: ${info.bunny.version} (${info.bunny.loader.name} ${info.bunny.loader.version})`,
|
||||||
`> Device: ${info.device.model}`,
|
`> Discord: ${info.discord.version} (${info.discord.build})`,
|
||||||
].join("\n");
|
`> React: ${info.react.version} (RN ${info.react.nativeVersion})`,
|
||||||
|
`> Hermes: ${info.hermes.version} (bcv${info.hermes.bytecodeVersion})`,
|
||||||
|
`> System: ${info.os.name} ${info.os.version} ${info.os.sdk ? `(SDK ${info.os.sdk})` : ""}`.trimEnd(),
|
||||||
|
`> Device: ${info.device.model} ${info.device.codename}`,
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
if (ephemeral?.value) {
|
if (ephemeral?.value) {
|
||||||
messageUtil.sendBotMessage(ctx.channel.id, content);
|
messageUtil.sendBotMessage(ctx.channel.id, content);
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
import { instead } from "@lib/api/patcher";
|
|
||||||
import { logger } from "@lib/utils/logger";
|
|
||||||
import { _lazyContextSymbol } from "@metro/lazy";
|
|
||||||
import { requireModule } from "@metro/internals/modules";
|
|
||||||
import { LazyModuleContext } from "@metro/types";
|
|
||||||
import { findByNameLazy } from "@metro/wrappers";
|
|
||||||
|
|
||||||
function log(...messages: any[]) {
|
|
||||||
try {
|
|
||||||
logger.warn("[ShiggyCord] patchAnimatedValue:", ...messages);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Discord's bundled React Native throws `AnimatedValue: Attempting to set value
|
|
||||||
* to undefined` when a `undefined` value reaches an `Animated.Value` — either
|
|
||||||
* through `setValue()` (which calls `_updateValue`) or through the constructor.
|
|
||||||
* Some of Discord's own flows (and third-party plugins) hit this during
|
|
||||||
* animation setup, which bubbles up to the React ErrorBoundary and makes
|
|
||||||
* ShiggyCord render its custom crash screen.
|
|
||||||
*
|
|
||||||
* Fix the root cause instead of masking the symptom:
|
|
||||||
* - `_updateValue` is a prototype method, so one patch covers every instance no
|
|
||||||
* matter which module captured the class reference.
|
|
||||||
* - The constructor is guarded by swapping the public `Animated.Value`
|
|
||||||
* reference (and the module's `default` export) for a patched proxy so that
|
|
||||||
* `new Animated.Value(undefined)` can no longer throw.
|
|
||||||
*/
|
|
||||||
export default function patchAnimatedValue() {
|
|
||||||
const unpatchers: Array<() => boolean> = [];
|
|
||||||
|
|
||||||
const install = (Class: any, source: string) => {
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
!Class?.prototype ||
|
|
||||||
typeof Class.prototype._updateValue !== "function"
|
|
||||||
) {
|
|
||||||
log(`${source}: resolved value is not AnimatedValue, skipping`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setValue(undefined) → _updateValue(undefined). Keep the last value.
|
|
||||||
unpatchers.push(
|
|
||||||
instead(
|
|
||||||
"_updateValue",
|
|
||||||
Class.prototype,
|
|
||||||
function (this: any, args: any[], origFunc: Function) {
|
|
||||||
if (args[0] === undefined) args[0] = this?._value ?? 0;
|
|
||||||
return origFunc(...args);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Constructor guard: coerce non-number to 0 so `new Animated.Value(undefined)` can't throw.
|
|
||||||
const guard = (args: any[], origFunc: Function) => {
|
|
||||||
if (typeof args[0] !== "number") args[0] = 0;
|
|
||||||
return origFunc(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1. Module exports (`require(...).default`).
|
|
||||||
try {
|
|
||||||
const ctxt: LazyModuleContext | undefined =
|
|
||||||
findByNameLazy("AnimatedValue")[_lazyContextSymbol];
|
|
||||||
if (ctxt?.moduleId != null) {
|
|
||||||
const moduleExports = requireModule(ctxt.moduleId);
|
|
||||||
if (moduleExports?.default === Class) {
|
|
||||||
unpatchers.push(instead("default", moduleExports, guard));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
// 2. The public `Animated.Value` namespace reference.
|
|
||||||
try {
|
|
||||||
const ReactNative: any = require("react-native");
|
|
||||||
const Animated = ReactNative?.Animated;
|
|
||||||
if (Animated && Animated.Value === Class) {
|
|
||||||
unpatchers.push(instead("Value", Animated, guard));
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
log(`${source}: patched _updateValue + constructor guard`);
|
|
||||||
} catch (e) {
|
|
||||||
log(`${source}: patch failed`, e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Primary: Discord's own RN Animated namespace (most reliable, no full scan).
|
|
||||||
try {
|
|
||||||
const ReactNative: any = require("react-native");
|
|
||||||
install(ReactNative?.Animated?.Value, "react-native.Animated.Value");
|
|
||||||
if (unpatchers.length > 0) {
|
|
||||||
return () => unpatchers.forEach((unpatch) => unpatch());
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log("react-native.Animated path failed", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: byName lookup.
|
|
||||||
try {
|
|
||||||
const ctxt: LazyModuleContext =
|
|
||||||
findByNameLazy("AnimatedValue")[_lazyContextSymbol];
|
|
||||||
if (ctxt) {
|
|
||||||
ctxt.getExports((Class: any) => install(Class, "findByNameLazy"));
|
|
||||||
} else {
|
|
||||||
log("no lazy context found");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log("findByNameLazy path failed", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => unpatchers.forEach((unpatch) => unpatch());
|
|
||||||
}
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { byProps } from "@metro/filters";
|
|
||||||
import { findExports } from "@metro/finders";
|
|
||||||
import { findByStoreNameLazy } from "@metro/wrappers";
|
|
||||||
import { instead } from "@lib/api/patcher";
|
|
||||||
import { logger } from "@lib/utils/logger";
|
|
||||||
import { isSemanticColor, resolveSemanticColor, semanticColors } from "@ui/color";
|
|
||||||
|
|
||||||
const ThemeStore = findByStoreNameLazy("ThemeStore");
|
|
||||||
|
|
||||||
function log(...messages: any[]) {
|
|
||||||
try {
|
|
||||||
logger.warn("[ShiggyCord] patchSafeCard:", ...messages);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cardHealthy = false;
|
|
||||||
|
|
||||||
const FALLBACK_STYLE = {
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 16,
|
|
||||||
};
|
|
||||||
|
|
||||||
function resolveCardBackground(): string | undefined {
|
|
||||||
try {
|
|
||||||
const color = semanticColors?.CARD_BACKGROUND_DEFAULT;
|
|
||||||
if (isSemanticColor(color)) return resolveSemanticColor(color);
|
|
||||||
if (typeof color === "string") return color;
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function patchSafeCard() {
|
|
||||||
const unpatchers: Array<() => boolean> = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const cardModule = findExports(byProps("Card"));
|
|
||||||
const origCard = cardModule?.Card;
|
|
||||||
if (typeof origCard !== "function") {
|
|
||||||
log("no Card function on the resolved module, skipping");
|
|
||||||
return () => unpatchers.forEach((unpatch) => unpatch());
|
|
||||||
}
|
|
||||||
|
|
||||||
log("resolved design Card from first-match module");
|
|
||||||
|
|
||||||
let cardBackground = resolveCardBackground();
|
|
||||||
|
|
||||||
const unsubscribeTheme = ThemeStore?.addChangeListener?.(() => {
|
|
||||||
const resolved = resolveCardBackground();
|
|
||||||
if (resolved) cardBackground = resolved;
|
|
||||||
});
|
|
||||||
if (typeof unsubscribeTheme === "function") {
|
|
||||||
unpatchers.push(() => {
|
|
||||||
unsubscribeTheme();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class CardProbeBoundary extends React.Component<
|
|
||||||
{
|
|
||||||
fallback: React.ReactNode;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
},
|
|
||||||
{ hasError: boolean }
|
|
||||||
> {
|
|
||||||
state = { hasError: false };
|
|
||||||
|
|
||||||
static getDerivedStateFromError() {
|
|
||||||
return { hasError: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidCatch(error: unknown) {
|
|
||||||
try {
|
|
||||||
log("Card render threw, using View fallback", error);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.hasError) return this.props.fallback;
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HealthyProbe extends React.Component<{
|
|
||||||
onHealthy: () => void;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}> {
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.onHealthy();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SafeCard = function SafeCard(props: any) {
|
|
||||||
if (cardHealthy) return React.createElement(origCard, props);
|
|
||||||
|
|
||||||
return React.createElement(
|
|
||||||
CardProbeBoundary,
|
|
||||||
{
|
|
||||||
fallback: React.createElement(
|
|
||||||
View,
|
|
||||||
{
|
|
||||||
style: [
|
|
||||||
FALLBACK_STYLE,
|
|
||||||
...(cardBackground
|
|
||||||
? [{ backgroundColor: cardBackground }]
|
|
||||||
: []),
|
|
||||||
props?.style,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
props?.children,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
React.createElement(
|
|
||||||
HealthyProbe,
|
|
||||||
{
|
|
||||||
onHealthy: () => {
|
|
||||||
cardHealthy = true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
React.createElement(origCard, props),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
unpatchers.push(
|
|
||||||
instead("Card", cardModule, (args: any[]) => SafeCard(args[0] ?? {})),
|
|
||||||
);
|
|
||||||
|
|
||||||
log("patched design Card (adaptive probe on first mount)");
|
|
||||||
} catch (e) {
|
|
||||||
log("patch failed", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => unpatchers.forEach((unpatch) => unpatch());
|
|
||||||
}
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
"IMAGE_FILES": "Image Files",
|
"IMAGE_FILES": "Image Files",
|
||||||
"TEXT_FILES": "Text Files",
|
"TEXT_FILES": "Text Files",
|
||||||
"BRAND": "Brand",
|
"BRAND": "Brand",
|
||||||
"SHIGGYCORD": "💦GoonCord💦",
|
"SHIGGYCORD": "GoonCord",
|
||||||
"SHIGGYCORD_URL": "GoonCord URL",
|
"SHIGGYCORD_URL": "GoonCord URL",
|
||||||
"BROWSER": "Browser",
|
"BROWSER": "Browser",
|
||||||
"BYTECODE": "Bytecode",
|
"BYTECODE": "Bytecode",
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
"PLUGINS": "Plugins",
|
"PLUGINS": "Plugins",
|
||||||
"REFETCH": "Refetch",
|
"REFETCH": "Refetch",
|
||||||
"RELOAD": "Reload",
|
"RELOAD": "Reload",
|
||||||
"RELOAD_DISCORD": "💦ReGoon💦 your Discord",
|
"RELOAD_DISCORD": "ReGoon your Discord",
|
||||||
"RELOAD_IN_NORMAL_MODE": "Reload in Normal Mode",
|
"RELOAD_IN_NORMAL_MODE": "Reload in Normal Mode",
|
||||||
"RELOAD_IN_NORMAL_MODE_DESC": "Safe mode currently enabled, tap to reload in normal mode",
|
"RELOAD_IN_NORMAL_MODE_DESC": "Safe mode currently enabled, tap to reload in normal mode",
|
||||||
"RELOAD_IN_SAFE_MODE": "Reload in Safe Mode",
|
"RELOAD_IN_SAFE_MODE": "Reload in Safe Mode",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ const { TableRowGroup, TableRow, TableSwitchRow, Stack } = findByProps(
|
||||||
"TableSwitchRow",
|
"TableSwitchRow",
|
||||||
"Stack",
|
"Stack",
|
||||||
);
|
);
|
||||||
const { ScrollView, Text, Linking } = require("react-native");
|
const { ScrollView, Text } = require("react-native");
|
||||||
|
|
||||||
type FixEmbedSettings = {
|
type FixEmbedSettings = {
|
||||||
twitter: boolean;
|
twitter: boolean;
|
||||||
|
|
@ -45,11 +45,11 @@ function transformLinks(content: string, config: FixEmbedSettings): string {
|
||||||
"https://kkinstagram.com$1",
|
"https://kkinstagram.com$1",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TikTok: Replace domain, keep path (use embedez.com for all TikTok links)
|
// TikTok: Replace domain, keep path
|
||||||
if (config.tiktok) {
|
if (config.tiktok) {
|
||||||
result = result.replace(
|
result = result.replace(
|
||||||
/https?:\/\/(?:www\.)?tiktok\.com(\/[^\s]+)/gi,
|
/https?:\/\/(?:www\.)?tiktok\.com(\/[^\s]+)/gi,
|
||||||
"https://embedez.com$1",
|
"https://tiktokez.com$1",
|
||||||
);
|
);
|
||||||
// TikTok short links (vm.tiktok.com)
|
// TikTok short links (vm.tiktok.com)
|
||||||
result = result.replace(
|
result = result.replace(
|
||||||
|
|
@ -99,6 +99,7 @@ export default defineCorePlugin({
|
||||||
setConfig((prev) => ({ ...prev, [key]: value }));
|
setConfig((prev) => ({ ...prev, [key]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Prefer table-style rows (TableRowGroup / TableSwitchRow) and Stack layout similar to other core plugins.
|
||||||
const {
|
const {
|
||||||
TableRowGroup: _TRG,
|
TableRowGroup: _TRG,
|
||||||
TableRow: _TR,
|
TableRow: _TR,
|
||||||
|
|
@ -106,6 +107,7 @@ export default defineCorePlugin({
|
||||||
Stack: _S,
|
Stack: _S,
|
||||||
} = findByProps("TableRowGroup", "TableRow", "TableSwitchRow", "Stack");
|
} = findByProps("TableRowGroup", "TableRow", "TableSwitchRow", "Stack");
|
||||||
|
|
||||||
|
// Fallback if the table-style components are not available in the host environment
|
||||||
if (!_TRG || !_TSR || !_TR || !_S) {
|
if (!_TRG || !_TSR || !_TR || !_S) {
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
ScrollView,
|
ScrollView,
|
||||||
|
|
@ -118,6 +120,7 @@ export default defineCorePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the resolved components
|
||||||
const TableRowGroup = _TRG;
|
const TableRowGroup = _TRG;
|
||||||
const TableRow = _TR;
|
const TableRow = _TR;
|
||||||
const TableSwitchRow = _TSR;
|
const TableSwitchRow = _TSR;
|
||||||
|
|
@ -161,12 +164,6 @@ export default defineCorePlugin({
|
||||||
"This plugin automatically converts social media links to privacy-friendly alternative frontends for better embeds.",
|
"This plugin automatically converts social media links to privacy-friendly alternative frontends for better embeds.",
|
||||||
disabled: true,
|
disabled: true,
|
||||||
}),
|
}),
|
||||||
React.createElement(TableRow, {
|
|
||||||
label: "EmbedEZ",
|
|
||||||
subLabel: "TikTok embed fix powered by embedez.com — tap to open",
|
|
||||||
onPress: () => Linking.openURL("https://embedez.com/"),
|
|
||||||
arrow: true,
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export default function About() {
|
||||||
variant="text-md/medium"
|
variant="text-md/medium"
|
||||||
style={{ textAlign: "center", marginTop: 4 }}
|
style={{ textAlign: "center", marginTop: 4 }}
|
||||||
>
|
>
|
||||||
💦Goon your discord client💦!
|
💦Goon💦 your discord client!
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ export default function General() {
|
||||||
>
|
>
|
||||||
<TableRowGroup title="App Information">
|
<TableRowGroup title="App Information">
|
||||||
<TableRow
|
<TableRow
|
||||||
label="💦GoonCord💦"
|
label="GoonCord"
|
||||||
icon={<TableRow.Icon source={{ uri: shiggycordIcon ?? "" }} />}
|
icon={<TableRow.Icon source={{ uri: shiggycordIcon ?? "" }} />}
|
||||||
trailing={<TableRow.TrailingText text={debugInfo.bunny.version} />}
|
trailing={<TableRow.TrailingText text={debugInfo.bunny.version} />}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ async function initializeShiggyCord() {
|
||||||
Object.freeze = Object.seal = Object;
|
Object.freeze = Object.seal = Object;
|
||||||
|
|
||||||
await require("@metro/internals/caches").initMetroCache();
|
await require("@metro/internals/caches").initMetroCache();
|
||||||
await require(".").default();
|
require(".").default();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { ClientInfoManager } = require("@lib/api/native/modules");
|
const { ClientInfoManager } = require("@lib/api/native/modules");
|
||||||
const stack = e instanceof Error ? e.stack : undefined;
|
const stack = e instanceof Error ? e.stack : undefined;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import patchAnimatedValue from "@core/debug/patches/patchAnimatedValue";
|
|
||||||
import patchErrorBoundary from "@core/debug/patches/patchErrorBoundary";
|
import patchErrorBoundary from "@core/debug/patches/patchErrorBoundary";
|
||||||
import patchSafeCard from "@core/debug/patches/patchSafeCard";
|
|
||||||
import initFixes from "@core/fixes";
|
import initFixes from "@core/fixes";
|
||||||
import { initFetchI18nStrings } from "@core/i18n";
|
import { initFetchI18nStrings } from "@core/i18n";
|
||||||
import initSettings from "@core/ui/settings";
|
import initSettings from "@core/ui/settings";
|
||||||
|
|
@ -32,8 +30,6 @@ export default async () => {
|
||||||
["patchCommands", () => patchCommands()],
|
["patchCommands", () => patchCommands()],
|
||||||
["patchJsx", () => patchJsx()],
|
["patchJsx", () => patchJsx()],
|
||||||
["patchErrorBoundary", () => patchErrorBoundary()],
|
["patchErrorBoundary", () => patchErrorBoundary()],
|
||||||
["patchAnimatedValue", () => patchAnimatedValue()],
|
|
||||||
["patchSafeCard", () => patchSafeCard()],
|
|
||||||
["initVendettaObject", () => initVendettaObject()],
|
["initVendettaObject", () => initVendettaObject()],
|
||||||
["initFetchI18nStrings", () => initFetchI18nStrings()],
|
["initFetchI18nStrings", () => initFetchI18nStrings()],
|
||||||
["initSettings", () => initSettings()],
|
["initSettings", () => initSettings()],
|
||||||
|
|
@ -92,5 +88,5 @@ export default async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final ready log for basic UI availability.
|
// Final ready log for basic UI availability.
|
||||||
logger.log("GoonCord is ready.");
|
logger.log("ShiggyCord is ready.");
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -40,19 +40,55 @@ export function updateBunnyColor(colorManifest: ColorManifest | null, { update =
|
||||||
});
|
});
|
||||||
|
|
||||||
if (internalDef != null) {
|
if (internalDef != null) {
|
||||||
tokenRef.Theme[ref.key.toUpperCase()] = ref.key;
|
// Register the synthetic key BEFORE calling native, and verify it actually
|
||||||
FormDivider.DIVIDER_COLORS[ref.key] = FormDivider.DIVIDER_COLORS[ref.current!.reference];
|
// landed in every table native reads from. If any registration step fails,
|
||||||
|
// bail out to a known-good theme instead of handing native an unknown key.
|
||||||
|
try {
|
||||||
|
tokenRef.Theme[ref.key.toUpperCase()] = ref.key;
|
||||||
|
|
||||||
Object.keys(tokenRef.Shadow).forEach(k => tokenRef.Shadow[k][ref.key] = tokenRef.Shadow[k][ref.current!.reference]);
|
if (!FormDivider.DIVIDER_COLORS[ref.current!.reference]) {
|
||||||
Object.keys(tokenRef.SemanticColor).forEach(k => {
|
throw new Error(`Missing divider color reference: ${ref.current!.reference}`);
|
||||||
tokenRef.SemanticColor[k][ref.key] = {
|
}
|
||||||
...tokenRef.SemanticColor[k][ref.current!.reference]
|
FormDivider.DIVIDER_COLORS[ref.key] = FormDivider.DIVIDER_COLORS[ref.current!.reference];
|
||||||
};
|
|
||||||
});
|
Object.keys(tokenRef.Shadow).forEach(k => {
|
||||||
|
if (!(ref.current!.reference in tokenRef.Shadow[k])) {
|
||||||
|
throw new Error(`Missing shadow reference: ${ref.current!.reference}`);
|
||||||
|
}
|
||||||
|
tokenRef.Shadow[k][ref.key] = tokenRef.Shadow[k][ref.current!.reference];
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(tokenRef.SemanticColor).forEach(k => {
|
||||||
|
if (!(ref.current!.reference in tokenRef.SemanticColor[k])) {
|
||||||
|
throw new Error(`Missing semantic color reference: ${ref.current!.reference}`);
|
||||||
|
}
|
||||||
|
tokenRef.SemanticColor[k][ref.key] = {
|
||||||
|
...tokenRef.SemanticColor[k][ref.current!.reference]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to register custom theme key, falling back", e);
|
||||||
|
ref.current = null; // force fallback path below
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update) {
|
if (update) {
|
||||||
AppearanceManager.setShouldSyncAppearanceSettings(false);
|
AppearanceManager.setShouldSyncAppearanceSettings(false);
|
||||||
AppearanceManager.updateTheme(internalDef != null ? ref.key : ref.lastSetDiscordTheme);
|
|
||||||
|
const targetTheme = ref.current != null ? ref.key : ref.lastSetDiscordTheme;
|
||||||
|
|
||||||
|
// This is the actual crash site: a raw call into native code with no
|
||||||
|
// guard. Wrap it so a native-side rejection can't take the whole
|
||||||
|
// bridge/thread down — fall back to the last known-good Discord theme.
|
||||||
|
try {
|
||||||
|
AppearanceManager.updateTheme(targetTheme);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Native rejected theme "${targetTheme}", reverting to "${ref.lastSetDiscordTheme}"`, e);
|
||||||
|
try {
|
||||||
|
AppearanceManager.updateTheme(ref.lastSetDiscordTheme);
|
||||||
|
} catch (e2) {
|
||||||
|
console.error("Fallback theme also failed to apply", e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,64 @@
|
||||||
import { ImageLoader } from "./";
|
import { ImageLoader } from "./";
|
||||||
import type { LoaderPayload } from "./types";
|
import type { LoaderPayload } from "./types";
|
||||||
|
|
||||||
type InteropReturn = Promise<{ ret: string } | { err: string } | { cancelled: true; reason: string }>;
|
type InteropReturn = Promise<{ ret: string } | { err: string } | { cancelled: true; reason: string }>;
|
||||||
|
|
||||||
export const loaderPayload = window.__PYON_LOADER__ as LoaderPayload;
|
export const loaderPayload = window.__PYON_LOADER__ as LoaderPayload;
|
||||||
|
|
||||||
export function isModuleRegistered(module: string) {
|
export function isModuleRegistered(module: string) {
|
||||||
return loaderPayload.loader.modules[module] !== undefined;
|
return loaderPayload.loader.modules[module] !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFunctionRegistered(module: string, function_: string) {
|
export function isFunctionRegistered(module: string, function_: string) {
|
||||||
return loaderPayload.loader.modules[module]?.functions[function_] !== undefined;
|
return loaderPayload.loader.modules[module]?.functions[function_] !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function callFunction(module: string, functionName: string, args: unknown[]) {
|
export async function callFunction(module: string, functionName: string, args: unknown[]) {
|
||||||
if (!isFunctionRegistered(module, functionName)) {
|
if (!isFunctionRegistered(module, functionName)) {
|
||||||
throw new Error(`Function ${module}.${functionName} is not registered`);
|
throw new Error(`Function ${module}.${functionName} is not registered`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise: InteropReturn = ImageLoader.queryCache([
|
const promise: InteropReturn = ImageLoader.queryCache([
|
||||||
"__pyon_bridge",
|
"__pyon_bridge",
|
||||||
JSON.stringify({ m: module, f: functionName, a: args }),
|
JSON.stringify({ m: module, f: functionName, a: args }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await promise;
|
const result = await promise;
|
||||||
|
|
||||||
if ("ret" in result) {
|
if ("ret" in result) {
|
||||||
return JSON.parse(result.ret);
|
return JSON.parse(result.ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("cancelled" in result) {
|
if ("cancelled" in result) {
|
||||||
throw new Error(`Function ${module}.${functionName} was cancelled: ${result.reason}`);
|
throw new Error(`Function ${module}.${functionName} was cancelled: ${result.reason}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("err" in result) {
|
if ("err" in result) {
|
||||||
throw new Error(result.err);
|
throw new Error(result.err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoaderModule {
|
export interface LoaderModule {
|
||||||
hasFunction(functionName: string): boolean;
|
hasFunction(functionName: string): boolean;
|
||||||
callFunction(functionName: string, args: unknown[]): Promise<unknown>;
|
callFunction(functionName: string, args: unknown[]): Promise<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModuleProps<T extends LoaderModule> = {
|
type ModuleProps<T extends LoaderModule> = {
|
||||||
name: string;
|
name: string;
|
||||||
argumentProcessors?: Partial<{ [key in keyof T]: (args: any[]) => any[] }>;
|
argumentProcessors?: Partial<{ [key in keyof T]: (args: any[]) => any[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getModule<T extends LoaderModule>({ name, argumentProcessors: processArgs }: ModuleProps<T>) {
|
export function getModule<T extends LoaderModule>({ name, argumentProcessors: processArgs }: ModuleProps<T>) {
|
||||||
const module = loaderPayload.loader.modules[name];
|
const module = loaderPayload.loader.modules[name];
|
||||||
if (!module) {
|
if (!module) {
|
||||||
throw new Error(`Module ${name} is not registered`);
|
throw new Error(`Module ${name} is not registered`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseObject: Record<string, any> = {
|
const baseObject: Record<string, any> = {
|
||||||
hasFunction: (functionName: string) => isFunctionRegistered(name, functionName),
|
hasFunction: (functionName: string) => isFunctionRegistered(name, functionName),
|
||||||
callFunction: (functionName: string, args: unknown[]) => callFunction(name, functionName, args),
|
callFunction: (functionName: string, args: unknown[]) => callFunction(name, functionName, args),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [functionName] of Object.entries(module.functions)) {
|
for (const [functionName] of Object.entries(module.functions)) {
|
||||||
baseObject[functionName] = (...args: unknown[]) =>
|
baseObject[functionName] = (...args: unknown[]) =>
|
||||||
callFunction(
|
callFunction(
|
||||||
|
|
@ -53,8 +68,10 @@ export function getModule<T extends LoaderModule>({ name, argumentProcessors: pr
|
||||||
processArgs && functionName in processArgs ? processArgs[functionName](args) : args,
|
processArgs && functionName in processArgs ? processArgs[functionName](args) : args,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseObject as T;
|
return baseObject as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if safe mode is currently enabled. The result remains consistent over time.
|
* Determines if safe mode is currently enabled. The result remains consistent over time.
|
||||||
* This will not change until the app is restarted. For draft changes, use the InitConfigStore.
|
* This will not change until the app is restarted. For draft changes, use the InitConfigStore.
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,64 @@
|
||||||
import { ImageLoader } from "./";
|
import { ImageLoader } from "./";
|
||||||
import type { LoaderPayload } from "./types";
|
import type { LoaderPayload } from "./types";
|
||||||
|
|
||||||
type InteropReturn = Promise<{ ret: string } | { err: string } | { cancelled: true; reason: string }>;
|
type InteropReturn = Promise<{ ret: string } | { err: string } | { cancelled: true; reason: string }>;
|
||||||
|
|
||||||
export const loaderPayload = window.__PYON_LOADER__ as LoaderPayload;
|
export const loaderPayload = window.__PYON_LOADER__ as LoaderPayload;
|
||||||
|
|
||||||
export function isModuleRegistered(module: string) {
|
export function isModuleRegistered(module: string) {
|
||||||
return loaderPayload.loader.modules[module] !== undefined;
|
return loaderPayload.loader.modules[module] !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFunctionRegistered(module: string, function_: string) {
|
export function isFunctionRegistered(module: string, function_: string) {
|
||||||
return loaderPayload.loader.modules[module]?.functions[function_] !== undefined;
|
return loaderPayload.loader.modules[module]?.functions[function_] !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function callFunction(module: string, functionName: string, args: unknown[]) {
|
export async function callFunction(module: string, functionName: string, args: unknown[]) {
|
||||||
if (!isFunctionRegistered(module, functionName)) {
|
if (!isFunctionRegistered(module, functionName)) {
|
||||||
throw new Error(`Function ${module}.${functionName} is not registered`);
|
throw new Error(`Function ${module}.${functionName} is not registered`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise: InteropReturn = ImageLoader.queryCache([
|
const promise: InteropReturn = ImageLoader.queryCache([
|
||||||
"__pyon_bridge",
|
"__pyon_bridge",
|
||||||
JSON.stringify({ m: module, f: functionName, a: args }),
|
JSON.stringify({ m: module, f: functionName, a: args }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await promise;
|
const result = await promise;
|
||||||
|
|
||||||
if ("ret" in result) {
|
if ("ret" in result) {
|
||||||
return JSON.parse(result.ret);
|
return JSON.parse(result.ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("cancelled" in result) {
|
if ("cancelled" in result) {
|
||||||
throw new Error(`Function ${module}.${functionName} was cancelled: ${result.reason}`);
|
throw new Error(`Function ${module}.${functionName} was cancelled: ${result.reason}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("err" in result) {
|
if ("err" in result) {
|
||||||
throw new Error(result.err);
|
throw new Error(result.err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoaderModule {
|
export interface LoaderModule {
|
||||||
hasFunction(functionName: string): boolean;
|
hasFunction(functionName: string): boolean;
|
||||||
callFunction(functionName: string, args: unknown[]): Promise<unknown>;
|
callFunction(functionName: string, args: unknown[]): Promise<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModuleProps<T extends LoaderModule> = {
|
type ModuleProps<T extends LoaderModule> = {
|
||||||
name: string;
|
name: string;
|
||||||
argumentProcessors?: Partial<{ [key in keyof T]: (args: any[]) => any[] }>;
|
argumentProcessors?: Partial<{ [key in keyof T]: (args: any[]) => any[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getModule<T extends LoaderModule>({ name, argumentProcessors: processArgs }: ModuleProps<T>) {
|
export function getModule<T extends LoaderModule>({ name, argumentProcessors: processArgs }: ModuleProps<T>) {
|
||||||
const module = loaderPayload.loader.modules[name];
|
const module = loaderPayload.loader.modules[name];
|
||||||
if (!module) {
|
if (!module) {
|
||||||
throw new Error(`Module ${name} is not registered`);
|
throw new Error(`Module ${name} is not registered`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseObject: Record<string, any> = {
|
const baseObject: Record<string, any> = {
|
||||||
hasFunction: (functionName: string) => isFunctionRegistered(name, functionName),
|
hasFunction: (functionName: string) => isFunctionRegistered(name, functionName),
|
||||||
callFunction: (functionName: string, args: unknown[]) => callFunction(name, functionName, args),
|
callFunction: (functionName: string, args: unknown[]) => callFunction(name, functionName, args),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [functionName] of Object.entries(module.functions)) {
|
for (const [functionName] of Object.entries(module.functions)) {
|
||||||
baseObject[functionName] = (...args: unknown[]) =>
|
baseObject[functionName] = (...args: unknown[]) =>
|
||||||
callFunction(
|
callFunction(
|
||||||
|
|
@ -53,8 +68,10 @@ export function getModule<T extends LoaderModule>({ name, argumentProcessors: pr
|
||||||
processArgs && functionName in processArgs ? processArgs[functionName](args) : args,
|
processArgs && functionName in processArgs ? processArgs[functionName](args) : args,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseObject as T;
|
return baseObject as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if safe mode is currently enabled. The result remains consistent over time.
|
* Determines if safe mode is currently enabled. The result remains consistent over time.
|
||||||
* This will not change until the app is restarted. For draft changes, use the InitConfigStore.
|
* This will not change until the app is restarted. For draft changes, use the InitConfigStore.
|
||||||
|
|
|
||||||
|
|
@ -117,17 +117,13 @@ for (const [id, moduleExports] of cacher.getModules()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportsKeysLength = Reflect.ownKeys(actualExports).length;
|
const exportsKeysLength = Reflect.ownKeys(actualExports).length;
|
||||||
if (_source[prop] && exportsKeysLength >= _source[prop]) {
|
if (_source[prop] && exportsKeysLength <= _source[prop]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_module[prop] = actualExports[prop];
|
_module[prop] = actualExports[prop];
|
||||||
_source[prop] = Reflect.ownKeys(actualExports).length;
|
_source[prop] = Reflect.ownKeys(actualExports).length;
|
||||||
cacher.cacheId(id);
|
cacher.cacheId(id);
|
||||||
|
|
||||||
if (exportsKeysLength === 1) {
|
|
||||||
redesignProps.delete(prop);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue