Compare commits
75 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 | ||
|
|
36e4bccc6a | ||
|
|
1d73ac91e5 | ||
|
|
1f3ad73135 | ||
|
|
4bb190b81a | ||
|
|
723815e4c5 | ||
|
|
4b64d99ac4 | ||
|
|
cb877a2a99 | ||
|
|
a60f527f3c | ||
|
|
fc6e5bccfd | ||
|
|
185f4ab6f3 | ||
|
|
0dc4e8cdf7 | ||
|
|
a628abc128 | ||
|
|
01a00fc98d | ||
|
|
4c2c6a3058 | ||
|
|
4f4a84ebce | ||
|
|
791956daac | ||
|
|
3c009cc46e | ||
|
|
e345bb14d3 | ||
|
|
3637ba9963 | ||
|
|
2155a2a055 | ||
|
|
dd91831481 | ||
|
|
61751664c5 | ||
|
|
06c8853399 | ||
|
|
727603ff46 | ||
|
|
d31739d8d8 | ||
|
|
ec97bbb3be | ||
|
|
d9ba90e6ef | ||
|
|
3bae44e833 |
25 changed files with 766 additions and 1849 deletions
|
|
@ -32,8 +32,10 @@ Bundle based on ShiggyCord, made just for fun
|
||||||
bun run build
|
bun run build
|
||||||
```
|
```
|
||||||
1. In the newly created `dist` directory, run a HTTP server. I recommend [http-server](https://www.npmjs.com/package/http-server).
|
1. In the newly created `dist` directory, run a HTTP server. I recommend [http-server](https://www.npmjs.com/package/http-server).
|
||||||
1. Go to Settings > Developer enabled earlier. Enable `Load from custom url` and input the IP address and port of the server (e.g. `http://192.168.1.236:4040/shiggycord.js`) in the new input box labelled `ShiggyCord URL`.
|
1. Go to Settings > Developer enabled earlier. Enable `Load from custom url` and input the IP address and port of the server (e.g. `http://192.168.1.236:4040/gooncord.js`) in the new input box labelled `GoonCord URL`.
|
||||||
1. Restart Discord. Upon reload, you should notice that your device will download GoonCord's bundled code from your server, rather than GitHub.
|
1. Restart Discord. Upon reload, you should notice that your device will download GoonCord's bundled code from your server, rather than GitHub.
|
||||||
1. Make your changes, rebuild, reload, go wild!
|
1. Make your changes, rebuild, reload, go wild!
|
||||||
|
|
||||||
Alternatively, you can directly *serve* the bundled code by running `bun run serve`. `gooncord.js` will be served on your local address under the port 4040. You will then insert `http://<local ip address>:4040/gooncord.js` as a custom url and reload. Whenever you restart your mobile client, the script will rebuild the bundle as your client fetches it.
|
Alternatively, you can directly *serve* the bundled code by running `bun run serve`. `gooncord.js` will be served on your local address under the port 4040. You will then insert `http://<local ip address>:4040/gooncord.js` as a custom url and reload. Whenever you restart your mobile client, the script will rebuild the bundle as your client fetches it.
|
||||||
|
|
||||||
|

|
||||||
|
|
|
||||||
51
package.json
51
package.json
|
|
@ -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",
|
||||||
|
|
@ -13,45 +13,40 @@
|
||||||
},
|
},
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
"@swc/core": "^1.15.47",
|
||||||
"@semantic-release/changelog": "^6.0.3",
|
"@swc/helpers": "^0.5.23",
|
||||||
"@semantic-release/exec": "^7.1.0",
|
"@types/node": "^26.1.2",
|
||||||
"@semantic-release/git": "^10.0.1",
|
|
||||||
"semantic-release": "^25.0.3",
|
|
||||||
"@swc/core": "^1.15.24",
|
|
||||||
"@swc/helpers": "^0.5.21",
|
|
||||||
"@types/node": "^25.5.2",
|
|
||||||
"@types/yargs-parser": "^21.0.3",
|
"@types/yargs-parser": "^21.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
"@typescript-eslint/eslint-plugin": "^8.65.0",
|
||||||
"@typescript-eslint/parser": "^8.58.0",
|
"@typescript-eslint/parser": "^8.65.0",
|
||||||
"@typescript-eslint/typescript-estree": "^8.58.0",
|
"@typescript-eslint/typescript-estree": "^8.65.0",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^6.0.0",
|
||||||
"esbuild": "^0.28.0",
|
"esbuild": "^0.28.1",
|
||||||
"esbuild-plugin-globals": "^0.2.0",
|
"esbuild-plugin-globals": "^0.2.0",
|
||||||
"eslint": "^10.2.0",
|
"eslint": "^10.8.0",
|
||||||
"eslint-plugin-import-alias": "^1.2.0",
|
"eslint-plugin-import-alias": "^1.2.0",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^14.0.0",
|
||||||
"eslint-plugin-unused-imports": "^4.4.1",
|
"eslint-plugin-unused-imports": "^4.4.1",
|
||||||
"typescript": "^6.0.2",
|
"typescript": "^7.0.2",
|
||||||
"yargs-parser": "^22.0.0"
|
"yargs-parser": "^22.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gullerya/object-observer": "^6.1.4",
|
"@gullerya/object-observer": "^7.0.0",
|
||||||
"@tanstack/react-query": "^5.96.2",
|
"@tanstack/react-query": "^5.101.4",
|
||||||
"@types/chroma-js": "~3.1.2",
|
"@types/chroma-js": "~3.1.2",
|
||||||
"@types/lodash": "~4.17.24",
|
"@types/lodash": "~4.17.24",
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "19.2.17",
|
||||||
"es-toolkit": "^1.45.1",
|
"es-toolkit": "^1.50.0",
|
||||||
"fuzzysort": "^3.1.0",
|
"fuzzysort": "^3.1.0",
|
||||||
"intl-messageformat": "^11.2.0",
|
"intl-messageformat": "^11.2.12",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"react-devtools": "^7.0.1",
|
"react-devtools": "^7.0.1",
|
||||||
"react-native": "0.84.1",
|
"react-native": "0.86.2",
|
||||||
"react-native-reanimated": "^4.3.0",
|
"react-native-reanimated": "^4.5.3",
|
||||||
"react-native-webview": "^13.16.1",
|
"react-native-webview": "^14.0.1",
|
||||||
"spitroast": "^2.1.6",
|
"spitroast": "^2.1.6",
|
||||||
"type-fest": "^5.5.0"
|
"type-fest": "^5.8.0"
|
||||||
},
|
},
|
||||||
"trustedDependencies": [
|
"trustedDependencies": [
|
||||||
"@swc/core",
|
"@swc/core",
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ const config = {
|
||||||
"!ShiggyCord-deps-shim!": "./shims/depsModule.ts",
|
"!ShiggyCord-deps-shim!": "./shims/depsModule.ts",
|
||||||
spitroast: "./node_modules/spitroast",
|
spitroast: "./node_modules/spitroast",
|
||||||
"react/jsx-runtime": "./shims/jsxRuntime",
|
"react/jsx-runtime": "./shims/jsxRuntime",
|
||||||
|
"@gullerya/object-observer":
|
||||||
|
path.resolve("./node_modules/@gullerya/object-observer/dist/object-observer.min.js"),
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
globalPlugin({
|
globalPlugin({
|
||||||
|
|
@ -75,7 +77,7 @@ const config = {
|
||||||
constModules: {
|
constModules: {
|
||||||
globals: {
|
globals: {
|
||||||
"bunny-build-info": {
|
"bunny-build-info": {
|
||||||
version: `"1.4.1.4"`,
|
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);
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,16 @@ export function formatString<T = void>(
|
||||||
val: Record<string, T>,
|
val: Record<string, T>,
|
||||||
): FormatStringRet<T> {
|
): FormatStringRet<T> {
|
||||||
const str = Strings[key];
|
const str = Strings[key];
|
||||||
// @ts-ignore
|
if (!str) return str as FormatStringRet<T>;
|
||||||
return new IntlMessageFormat(str).format(val);
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
return new IntlMessageFormat(str).format(val);
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e?.name === "SyntaxError") {
|
||||||
|
logger.warn(`[i18n] Failed to parse message for key "${String(key)}":`, e.message);
|
||||||
|
return str as FormatStringRet<T>;
|
||||||
|
}
|
||||||
|
logger.warn(`[i18n] Failed to format message for key "${String(key)}":`, e?.message ?? e);
|
||||||
|
return str as FormatStringRet<T>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { after } from "@lib/api/patcher";
|
import { after } from "@lib/api/patcher";
|
||||||
import { onJsxCreate } from "@lib/api/react/jsx";
|
import { onJsxCreate } from "@lib/api/react/jsx";
|
||||||
import { findByName, findByNameLazy } from "@metro";
|
import { findByNameLazy } from "@metro";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { defineCorePlugin } from "..";
|
import { defineCorePlugin } from "..";
|
||||||
import { FluxDispatcher } from "@metro/common";
|
import { FluxDispatcher } from "@metro/common";
|
||||||
|
|
||||||
|
|
@ -10,14 +9,9 @@ interface Badge {
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomBadge {
|
|
||||||
label: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UserBadgeData {
|
interface UserBadgeData {
|
||||||
roles?: string[];
|
roles?: string[];
|
||||||
custom?: CustomBadge[];
|
custom?: Badge[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BadgeData {
|
interface BadgeData {
|
||||||
|
|
@ -33,6 +27,11 @@ interface RolesData {
|
||||||
[roleName: string]: RoleData;
|
[roleName: string]: RoleData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EquicordBadge {
|
||||||
|
tooltip: string;
|
||||||
|
badge: string;
|
||||||
|
}
|
||||||
|
|
||||||
const useBadgesModule = findByNameLazy("useBadges", false);
|
const useBadgesModule = findByNameLazy("useBadges", false);
|
||||||
|
|
||||||
const badgesCache = new Map<string, Badge[]>();
|
const badgesCache = new Map<string, Badge[]>();
|
||||||
|
|
@ -40,20 +39,20 @@ const badgeProps = new Map<string, Record<string, any>>();
|
||||||
const pendingRequests = new Set<string>();
|
const pendingRequests = new Set<string>();
|
||||||
|
|
||||||
export default defineCorePlugin({
|
export default defineCorePlugin({
|
||||||
manifest: {
|
manifest: {
|
||||||
id: "bunny.badges",
|
id: "bunny.badges",
|
||||||
version: "1.1.0",
|
version: "1.2.0",
|
||||||
type: "plugin",
|
type: "plugin",
|
||||||
spec: 3,
|
spec: 3,
|
||||||
main: "",
|
main: "",
|
||||||
display: {
|
display: {
|
||||||
name: "Badges",
|
name: "Badges",
|
||||||
description: "Adds badges to user's profile",
|
description: "Adds badges to user's profile",
|
||||||
authors: [{ name: "cocobo1" }, { name: "pylixonly" }],
|
authors: [{ name: "cocobo1" }, { name: "pylixonly" }],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
onJsxCreate("ProfileBadge", (component, ret) => {
|
onJsxCreate("ProfileBadge", (component, ret) => {
|
||||||
if (ret.props.id?.startsWith("rain-")) {
|
if (ret.props.id?.startsWith("rain-")) {
|
||||||
const cachedProps = badgeProps.get(ret.props.id);
|
const cachedProps = badgeProps.get(ret.props.id);
|
||||||
|
|
@ -74,27 +73,67 @@ export default defineCorePlugin({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Small helper so one failing/optional source can't take down the others.
|
||||||
|
const safeFetchJson = async <T,>(url: string, fallback: T): Promise<T> => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) return fallback;
|
||||||
|
return (await res.json()) as T;
|
||||||
|
} catch {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchAndProcessBadges = async (userId: string) => {
|
const fetchAndProcessBadges = async (userId: string) => {
|
||||||
if (pendingRequests.has(userId)) return;
|
if (pendingRequests.has(userId)) return;
|
||||||
pendingRequests.add(userId);
|
pendingRequests.add(userId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [badgesRes, rolesRes] = await Promise.all([
|
const [
|
||||||
fetch("https://codeberg.org/chocomint-chan/GoonCord_Badges/raw/branch/main/badges.json"),
|
goonBadgesData,
|
||||||
fetch("https://codeberg.org/chocomint-chan/GoonCord_Badges/raw/branch/main/assets/roles/roles.json"),
|
goonRolesData,
|
||||||
|
rainBadgesData,
|
||||||
|
rainRolesData,
|
||||||
|
equicordData,
|
||||||
|
] = await Promise.all([
|
||||||
|
safeFetchJson<BadgeData>(
|
||||||
|
"https://codeberg.org/chocomint-chan/GoonCord_Badges/raw/branch/main/badges.json",
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
safeFetchJson<RolesData>(
|
||||||
|
"https://codeberg.org/chocomint-chan/GoonCord_Badges/raw/branch/main/assets/roles/roles.json",
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
safeFetchJson<BadgeData>(
|
||||||
|
"https://codeberg.org/raincord/badges/raw/branch/main/badges.json",
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
safeFetchJson<RolesData>(
|
||||||
|
"https://codeberg.org/raincord/badges/raw/branch/main/assets/roles/roles.json",
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
safeFetchJson<Record<string, EquicordBadge[]>>(
|
||||||
|
"https://badge.equicord.org/badges.json",
|
||||||
|
{}
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const badgesData: BadgeData = await badgesRes.json();
|
|
||||||
const rolesData: RolesData = await rolesRes.json();
|
|
||||||
|
|
||||||
const userBadgeData = badgesData[userId] || { roles: [], custom: [] };
|
|
||||||
|
|
||||||
const allBadges: Badge[] = [];
|
const allBadges: Badge[] = [];
|
||||||
|
|
||||||
// process role badges
|
// GoonCord and raincord badge/role sets are keyed independently,
|
||||||
if (userBadgeData.roles) {
|
// so each source is resolved against its own roles file.
|
||||||
userBadgeData.roles.forEach(roleName => {
|
const badgeSources: Array<{ badges: BadgeData; roles: RolesData }> = [
|
||||||
const roleData = rolesData[roleName];
|
{ badges: goonBadgesData, roles: goonRolesData },
|
||||||
|
{ badges: rainBadgesData, roles: rainRolesData },
|
||||||
|
];
|
||||||
|
|
||||||
|
badgeSources.forEach(({ badges, roles }) => {
|
||||||
|
const userBadgeData = badges[userId];
|
||||||
|
if (!userBadgeData) return;
|
||||||
|
|
||||||
|
// process role badges
|
||||||
|
userBadgeData.roles?.forEach(roleName => {
|
||||||
|
const roleData = roles[roleName];
|
||||||
if (roleData) {
|
if (roleData) {
|
||||||
allBadges.push({
|
allBadges.push({
|
||||||
label: roleData.label,
|
label: roleData.label,
|
||||||
|
|
@ -102,12 +141,18 @@ export default defineCorePlugin({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// process custom badges
|
// process custom badges
|
||||||
if (userBadgeData.custom) {
|
if (userBadgeData.custom) {
|
||||||
allBadges.push(...userBadgeData.custom);
|
allBadges.push(...userBadgeData.custom);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// process equicord badges
|
||||||
|
const equicordUserBadges = equicordData[userId] ?? [];
|
||||||
|
equicordUserBadges.forEach(b => {
|
||||||
|
allBadges.push({ label: b.tooltip, url: b.badge });
|
||||||
|
});
|
||||||
|
|
||||||
badgesCache.set(userId, allBadges);
|
badgesCache.set(userId, allBadges);
|
||||||
|
|
||||||
|
|
@ -122,12 +167,14 @@ export default defineCorePlugin({
|
||||||
});
|
});
|
||||||
|
|
||||||
FluxDispatcher.dispatch({ type: "USER_UPDATE", user: { id: userId } });
|
FluxDispatcher.dispatch({ type: "USER_UPDATE", user: { id: userId } });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[bunny.badges] Failed to fetch/process badges:", err);
|
||||||
} finally {
|
} finally {
|
||||||
pendingRequests.delete(userId);
|
pendingRequests.delete(userId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
after("default", useBadgesModule, ([user], result) => {
|
return after("default", useBadgesModule, ([user], result) => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
const userId = user.userId;
|
const userId = user.userId;
|
||||||
|
|
|
||||||
|
|
@ -42,19 +42,19 @@ function transformLinks(content: string, config: FixEmbedSettings): string {
|
||||||
if (config.instagram) {
|
if (config.instagram) {
|
||||||
result = result.replace(
|
result = result.replace(
|
||||||
/https?:\/\/(?:www\.)?instagram\.com(\/[^\s]+)/gi,
|
/https?:\/\/(?:www\.)?instagram\.com(\/[^\s]+)/gi,
|
||||||
"https://uuinstagram.com$1",
|
"https://kkinstagram.com$1",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TikTok: Replace domain, keep path
|
// 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://tnktok.com$1",
|
"https://tiktokez.com$1",
|
||||||
);
|
);
|
||||||
// TikTok short links (vm.tiktok.com)
|
// TikTok short links (vm.tiktok.com)
|
||||||
result = result.replace(
|
result = result.replace(
|
||||||
/https?:\/\/vm\.tiktok\.com\/([A-Za-z0-9]+)\/?/gi,
|
/https?:\/\/vm\.tiktok\.com\/([A-Za-z0-9]+)\/?/gi,
|
||||||
"https://vm.tnktok.com/$1",
|
"https://vm.tiktokez.com/$1",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Reddit: Replace domain, keep path
|
// Reddit: Replace domain, keep path
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ export const getCorePlugins = (): CorePluginRecord => ({
|
||||||
"bunny.nodelete": makeLazyPlugin(() => require("./nodelete")),
|
"bunny.nodelete": makeLazyPlugin(() => require("./nodelete")),
|
||||||
"bunny.maxverstappen": makeLazyPlugin(() => require("./maxverstappen")),
|
"bunny.maxverstappen": makeLazyPlugin(() => require("./maxverstappen")),
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -23,51 +23,54 @@ const { showSimpleActionSheet } = lazyDestructure(() =>
|
||||||
|
|
||||||
// TODO: These styles work weirdly. Low DPI Has cramped text. Fix?
|
// TODO: These styles work weirdly. Low DPI Has cramped text. Fix?
|
||||||
const useStyles = createStyles({
|
const useStyles = createStyles({
|
||||||
card: {
|
card: {
|
||||||
backgroundColor: semanticColors?.CARD_SECONDARY_BG,
|
backgroundColor: semanticColors?.CARD_SECONDARY_BG,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
overflow: "hidden",
|
overflow: "hidden"
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
},
|
},
|
||||||
headerLeading: {
|
headerLeading: {
|
||||||
flexDirection: "column",
|
flex: 1,
|
||||||
justifyContent: "center",
|
flexDirection: "column",
|
||||||
scale: 1.2,
|
justifyContent: "center",
|
||||||
},
|
scale: 1.2
|
||||||
headerTrailing: {
|
},
|
||||||
display: "flex",
|
headerTrailing: {
|
||||||
flexDirection: "row",
|
display: "flex",
|
||||||
gap: 15,
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
gap: 15,
|
||||||
},
|
alignItems: "center"
|
||||||
headerLabel: {
|
},
|
||||||
...TextStyleSheet["heading-md/semibold"],
|
headerLabel: {
|
||||||
color: semanticColors.MOBILE_TEXT_HEADING_PRIMARY,
|
...TextStyleSheet["heading-lg/semibold"],
|
||||||
},
|
},
|
||||||
headerSubtitle: {
|
headerLabelCompact: {
|
||||||
...TextStyleSheet["text-md/semibold"],
|
...TextStyleSheet["text-md/semibold"],
|
||||||
color: semanticColors.TEXT_MUTED,
|
},
|
||||||
},
|
headerSubtitle: {
|
||||||
descriptionLabel: {
|
...TextStyleSheet["text-sm/semibold"],
|
||||||
...TextStyleSheet["text-md/semibold"],
|
color: semanticColors.TEXT_MUTED,
|
||||||
color: semanticColors.TEXT_NORMAL,
|
},
|
||||||
},
|
descriptionLabel: {
|
||||||
actions: {
|
...TextStyleSheet["text-md/semibold"],
|
||||||
flexDirection: "row-reverse",
|
color: "text-strong",
|
||||||
alignItems: "center",
|
},
|
||||||
gap: 5,
|
actions: {
|
||||||
},
|
flexDirection: "row-reverse",
|
||||||
iconStyle: {
|
alignItems: "center",
|
||||||
tintColor: semanticColors.INTERACTIVE_ICON_DEFAULT,
|
gap: 5
|
||||||
opacity: 0.2,
|
},
|
||||||
height: 64,
|
iconStyle: {
|
||||||
width: 64,
|
tintColor: semanticColors.LOGO_PRIMARY,
|
||||||
left: void 0,
|
opacity: 0.2,
|
||||||
right: "30%",
|
height: 64,
|
||||||
top: "-10%",
|
width: 64,
|
||||||
},
|
left: void 0,
|
||||||
|
right: "30%",
|
||||||
|
top: "-10%"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface Action {
|
interface Action {
|
||||||
|
|
@ -108,9 +111,9 @@ export default function AddonCard(props: CardProps) {
|
||||||
<Stack spacing={16}>
|
<Stack spacing={16}>
|
||||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||||
<View style={styles.headerLeading}>
|
<View style={styles.headerLeading}>
|
||||||
<Text style={styles.headerLabel}>{props.headerLabel}</Text>
|
<Text numberOfLines={1} ellipsizeMode="tail" style={styles.headerLabel}>{props.headerLabel}</Text>
|
||||||
{props.headerSublabel && (
|
{props.headerSublabel && (
|
||||||
<Text style={styles.headerSubtitle}>{props.headerSublabel}</Text>
|
<Text numberOfLines={1} ellipsizeMode="tail" style={styles.headerSubtitle}>{props.headerSublabel}</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.headerTrailing, { marginLeft: "auto" }]}>
|
<View style={[styles.headerTrailing, { marginLeft: "auto" }]}>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import {
|
||||||
TableSwitchRow,
|
TableSwitchRow,
|
||||||
Stack,
|
Stack,
|
||||||
} from "@metro/common/components";
|
} from "@metro/common/components";
|
||||||
import { semanticColors } from "@ui/color";
|
|
||||||
import { ScrollView, View } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
|
|
||||||
import ErrorComponentStackCard from "./ErrorComponentStackCard";
|
import ErrorComponentStackCard from "./ErrorComponentStackCard";
|
||||||
|
|
@ -74,8 +73,8 @@ export default function ErrorBoundaryScreen(props: {
|
||||||
<ScrollView fadingEdgeLength={64} contentContainerStyle={{ gap: 12 }}>
|
<ScrollView fadingEdgeLength={64} contentContainerStyle={{ gap: 12 }}>
|
||||||
<Text
|
<Text
|
||||||
variant="heading-md/extrabold"
|
variant="heading-md/extrabold"
|
||||||
|
color="text-default"
|
||||||
style={{
|
style={{
|
||||||
color: semanticColors.HEADER_PRIMARY,
|
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -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} />}
|
||||||
/>
|
/>
|
||||||
|
|
@ -144,7 +144,7 @@ export default function General() {
|
||||||
subLabel={`${debugInfo.bunny.loader.name} loader`}
|
subLabel={`${debugInfo.bunny.loader.name} loader`}
|
||||||
icon={<TableRow.Icon source={findAssetId("DownloadIcon")} />}
|
icon={<TableRow.Icon source={findAssetId("DownloadIcon")} />}
|
||||||
trailing={
|
trailing={
|
||||||
<TableRow.TrailingText text={debugInfo.bunny.loader.version} />
|
<TableRow.TrailingText text={debugInfo.bunny.loader.version ?? ""} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableRowGroup>
|
</TableRowGroup>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
58
src/index.ts
58
src/index.ts
|
|
@ -11,13 +11,8 @@ import { injectFluxInterceptor } from "@lib/api/flux";
|
||||||
import { patchJsx } from "@lib/api/react/jsx";
|
import { patchJsx } from "@lib/api/react/jsx";
|
||||||
import { logger } from "@lib/utils/logger";
|
import { logger } from "@lib/utils/logger";
|
||||||
import { patchSettings } from "@ui/settings";
|
import { patchSettings } from "@ui/settings";
|
||||||
import { updaterSettings } from "@lib/api/settings";
|
|
||||||
import { InteractionManager } from "react-native";
|
import { InteractionManager } from "react-native";
|
||||||
import { getDebugInfo, initDebugger } from "@lib/api/debug";
|
import { getDebugInfo, initDebugger } 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";
|
import * as lib from "./lib";
|
||||||
import { timings } from "@lib/utils/timings";
|
import { timings } from "@lib/utils/timings";
|
||||||
|
|
||||||
|
|
@ -66,52 +61,19 @@ export default async () => {
|
||||||
const { VdPluginManager } = await import("@core/vendetta/plugins");
|
const { VdPluginManager } = await import("@core/vendetta/plugins");
|
||||||
const { initPlugins, updatePlugins } = await import("@lib/addons/plugins");
|
const { initPlugins, updatePlugins } = await import("@lib/addons/plugins");
|
||||||
|
|
||||||
// Initialize Vendetta plugins (may start many plugins) — do not block UI.
|
await Promise.all([
|
||||||
VdPluginManager.initPlugins()
|
VdPluginManager.initPlugins()
|
||||||
.then((u) => lib.unload.push(u))
|
.then((u) => u && lib.unload.push(u))
|
||||||
.catch((e) => logger.log("Vendetta init failed:", e));
|
.catch((e) => logger.log("Vendetta init failed:", e)),
|
||||||
|
initPlugins()
|
||||||
|
.catch((e) => logger.log("initPlugins failed:", e)),
|
||||||
|
]);
|
||||||
|
|
||||||
// Start ShiggyCord (Bunny) plugins now without forcing repository updates.
|
updatePlugins()
|
||||||
// Plugin repository fetching is deferred so the app can finish launching first.
|
.catch((e) => logger.log("updatePlugins failed:", e));
|
||||||
// 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
|
// Update fonts in background
|
||||||
updateFonts().catch((e) => logger.log("updateFonts failed:", e));
|
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Note: we intentionally moved the call to `updatePlugins()` above so core
|
|
||||||
// plugins are registered prior to calling `initPlugins()`.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Preferred: wait until interactions finish (animations / navigation).
|
// Preferred: wait until interactions finish (animations / navigation).
|
||||||
|
|
@ -126,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.");
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -752,27 +752,54 @@ export async function updatePlugins() {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enabledIds = [...registeredPlugins.keys()].filter(
|
||||||
|
(id) => isPluginEnabled(id) && !pluginInstances.has(id),
|
||||||
|
);
|
||||||
|
await Promise.allSettled(
|
||||||
|
enabledIds.map(async (id) => {
|
||||||
|
try {
|
||||||
|
await startPlugin(id).catch((e) => {
|
||||||
|
console.error(`Failed to start plugin ${id}:`, e);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Unexpected error while starting plugin ${id}:`, error);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize plugins but stagger startup to avoid CPU spikes during app start.
|
* Register all installed plugins from local storage manifests without any
|
||||||
|
* network fetch. This lets initPlugins() start plugins immediately using
|
||||||
|
* previously-cached scripts, keeping startup snappy.
|
||||||
*
|
*
|
||||||
* This replaces the original "start all enabled plugins in parallel" approach
|
* Safe to call multiple times — skips already-registered plugins.
|
||||||
* with a configurable staggered startup: plugins are started in small batches and
|
|
||||||
* we await a small interval between batches. This reduces short-lived CPU and I/O contention
|
|
||||||
* that causes jank on low-end devices.
|
|
||||||
*
|
|
||||||
* Backwards-compatible: callers can still call initPlugins() with no arguments.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
* - staggerInterval (ms) - wait time between batches (default: 100)
|
|
||||||
* - batchSize - how many plugins to start concurrently in a single batch (default: 3)
|
|
||||||
*/
|
*/
|
||||||
export async function initPlugins(
|
export async function registerInstalledPlugins() {
|
||||||
options: { staggerInterval?: number; batchSize?: number } = {},
|
await awaitStorage(pluginRepositories, pluginSettings);
|
||||||
) {
|
|
||||||
const { staggerInterval = 100, batchSize = 3 } = options;
|
|
||||||
|
|
||||||
|
const ids = Object.keys(pluginSettings ?? {});
|
||||||
|
if (ids.length === 0) return;
|
||||||
|
|
||||||
|
// Load all manifest files from local storage in parallel.
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
ids.map(async (id) => {
|
||||||
|
if (registeredPlugins.has(id) || isCorePlugin(id)) return;
|
||||||
|
const loaded = await preloadStorageIfExists(`plugins/manifests/${id}.json`);
|
||||||
|
if (loaded) {
|
||||||
|
const manifest = getPreloadedStorage<t.BunnyPluginManifest>(
|
||||||
|
`plugins/manifests/${id}.json`,
|
||||||
|
);
|
||||||
|
if (manifest) {
|
||||||
|
registeredPlugins.set(id, manifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initPlugins() {
|
||||||
// Ensure core plugins are registered prior to any plugin startup logic so
|
// Ensure core plugins are registered prior to any plugin startup logic so
|
||||||
// settings pages and UI that read registered/core plugins show them.
|
// settings pages and UI that read registered/core plugins show them.
|
||||||
// registerCorePlugins is synchronous and idempotent, but call it here as a
|
// registerCorePlugins is synchronous and idempotent, but call it here as a
|
||||||
|
|
@ -784,6 +811,7 @@ export async function initPlugins(
|
||||||
}
|
}
|
||||||
|
|
||||||
await awaitStorage(pluginRepositories, pluginSettings);
|
await awaitStorage(pluginRepositories, pluginSettings);
|
||||||
|
await registerInstalledPlugins();
|
||||||
|
|
||||||
// Collect enabled plugin ids
|
// Collect enabled plugin ids
|
||||||
const enabledIds = [...registeredPlugins.keys()].filter((id) =>
|
const enabledIds = [...registeredPlugins.keys()].filter((id) =>
|
||||||
|
|
@ -792,32 +820,18 @@ export async function initPlugins(
|
||||||
|
|
||||||
if (enabledIds.length === 0) return;
|
if (enabledIds.length === 0) return;
|
||||||
|
|
||||||
// Helper sleep
|
// Start all enabled plugins in parallel
|
||||||
const sleep = (ms: number) => new Promise<void>((res) => setTimeout(res, ms));
|
await Promise.allSettled(
|
||||||
|
enabledIds.map(async (id) => {
|
||||||
// Start plugins in batches to avoid starting all at once.
|
try {
|
||||||
for (let i = 0; i < enabledIds.length; i += batchSize) {
|
// Only attempt to start if still enabled and not already started
|
||||||
const batch = enabledIds.slice(i, i + batchSize);
|
if (!isPluginEnabled(id) || pluginInstances.has(id)) return;
|
||||||
|
await startPlugin(id).catch((e) => {
|
||||||
// Start the batch in parallel but don't let exceptions bubble to halt everything.
|
console.error(`Failed to start plugin ${id}:`, e);
|
||||||
await Promise.allSettled(
|
});
|
||||||
batch.map(async (id) => {
|
} catch (error) {
|
||||||
try {
|
console.error(`Unexpected error while starting plugin ${id}:`, error);
|
||||||
// Only attempt to start if still enabled and not already started
|
}
|
||||||
if (!isPluginEnabled(id) || pluginInstances.has(id)) return;
|
}),
|
||||||
await startPlugin(id).catch((e) => {
|
);
|
||||||
// startPlugin may throw; ensure it doesn't crash the whole batch
|
|
||||||
console.error(`Failed to start plugin ${id}:`, e);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Unexpected error while starting plugin ${id}:`, error);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If there are more plugins to start, wait before starting the next batch.
|
|
||||||
if (i + batchSize < enabledIds.length) {
|
|
||||||
await sleep(staggerInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ export function getCurrentTheme() {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export function getThemeFromLoader(): VdThemeInfo | null {
|
export function getThemeFromLoader(): VdThemeInfo | null {
|
||||||
return getStoredTheme();
|
return getStoredTheme() ?? getCurrentTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -157,6 +157,7 @@ export async function initThemes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
await newAwaitStorage(colorsPref);
|
await newAwaitStorage(colorsPref);
|
||||||
|
await awaitStorage(themes);
|
||||||
|
|
||||||
const currentTheme = getThemeFromLoader();
|
const currentTheme = getThemeFromLoader();
|
||||||
initColors(currentTheme?.data ?? null);
|
initColors(currentTheme?.data ?? null);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ const rainLoaderIdentity = globalThis.__RAIN_LOADER__;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const vendettaLoaderIdentity = globalThis.__vendetta_loader;
|
const vendettaLoaderIdentity = globalThis.__vendetta_loader;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const shiggyLoaderIdentity = globalThis.__SHIGGY_LOADER__;
|
||||||
|
|
||||||
export interface VendettaLoaderIdentity {
|
export interface VendettaLoaderIdentity {
|
||||||
name: string;
|
name: string;
|
||||||
features: {
|
features: {
|
||||||
|
|
@ -36,6 +39,10 @@ export function isRa1nLoader() {
|
||||||
return rainLoaderIdentity != null;
|
return rainLoaderIdentity != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isShiggyLoader() {
|
||||||
|
return shiggyLoaderIdentity != null;
|
||||||
|
}
|
||||||
|
|
||||||
function polyfillVendettaLoaderIdentity() {
|
function polyfillVendettaLoaderIdentity() {
|
||||||
if (!isPyonLoader() || isVendettaLoader() || !isRa1nLoader()) return null;
|
if (!isPyonLoader() || isVendettaLoader() || !isRa1nLoader()) return null;
|
||||||
|
|
||||||
|
|
@ -98,6 +105,8 @@ export function getLoaderIdentity() {
|
||||||
return getVendettaLoaderIdentity();
|
return getVendettaLoaderIdentity();
|
||||||
} else if (isRa1nLoader()) {
|
} else if (isRa1nLoader()) {
|
||||||
return rainLoaderIdentity();
|
return rainLoaderIdentity();
|
||||||
|
} else if (isShiggyLoader()) {
|
||||||
|
return shiggyLoaderIdentity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -116,6 +125,7 @@ export function getLoaderName() {
|
||||||
if (isPyonLoader()) return pyonLoaderIdentity.loaderName;
|
if (isPyonLoader()) return pyonLoaderIdentity.loaderName;
|
||||||
else if (isRa1nLoader()) return rainLoaderIdentity.loadername;
|
else if (isRa1nLoader()) return rainLoaderIdentity.loadername;
|
||||||
else if (isVendettaLoader()) return vendettaLoaderIdentity.name;
|
else if (isVendettaLoader()) return vendettaLoaderIdentity.name;
|
||||||
|
else if (isShiggyLoader()) return shiggyLoaderIdentity.loaderName;
|
||||||
|
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
|
|
@ -123,6 +133,7 @@ export function getLoaderName() {
|
||||||
export function getLoaderVersion(): string | null {
|
export function getLoaderVersion(): string | null {
|
||||||
if (isPyonLoader()) return pyonLoaderIdentity.loaderVersion;
|
if (isPyonLoader()) return pyonLoaderIdentity.loaderVersion;
|
||||||
else if (isRa1nLoader()) return rainLoaderIdentity.loaderVersion;
|
else if (isRa1nLoader()) return rainLoaderIdentity.loaderVersion;
|
||||||
|
else if (isShiggyLoader()) return shiggyLoaderIdentity.loaderVersion;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,7 +155,9 @@ export function isThemeSupported() {
|
||||||
} else if (isVendettaLoader()) {
|
} else if (isVendettaLoader()) {
|
||||||
return vendettaLoaderIdentity!!.features.themes != null;
|
return vendettaLoaderIdentity!!.features.themes != null;
|
||||||
} else if (isRa1nLoader()) {
|
} else if (isRa1nLoader()) {
|
||||||
return false; // Ra1n has theme support disabled, this is here just to make sure it doesnt think it does
|
return false; // Rain has theme support disabled, this is here just to make sure it doesnt think it does
|
||||||
|
} else if (isShiggyLoader()) {
|
||||||
|
return shiggyLoaderIdentity.hasThemeSupport;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -158,6 +171,8 @@ export function getStoredTheme(): VdThemeInfo | null {
|
||||||
if (!themeProp) return null;
|
if (!themeProp) return null;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return globalThis[themeProp] || null;
|
return globalThis[themeProp] || null;
|
||||||
|
} else if (isShiggyLoader()) {
|
||||||
|
return shiggyLoaderIdentity.storedTheme || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -168,6 +183,8 @@ export function getThemeFilePath() {
|
||||||
return "pyoncord/current-theme.json";
|
return "pyoncord/current-theme.json";
|
||||||
} else if (isVendettaLoader()) {
|
} else if (isVendettaLoader()) {
|
||||||
return "vendetta_theme.json";
|
return "vendetta_theme.json";
|
||||||
|
} else if (isShiggyLoader()) {
|
||||||
|
return "ShiggyCord/current-theme.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -241,6 +258,7 @@ export function getLoaderConfigPath() {
|
||||||
|
|
||||||
export function isFontSupported() {
|
export function isFontSupported() {
|
||||||
if (isPyonLoader()) return pyonLoaderIdentity.fontPatch === 2;
|
if (isPyonLoader()) return pyonLoaderIdentity.fontPatch === 2;
|
||||||
|
else if (isShiggyLoader()) return shiggyLoaderIdentity.fontPatch === 2;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -248,4 +266,4 @@ export function isFontSupported() {
|
||||||
export async function clearBundle() {
|
export async function clearBundle() {
|
||||||
// TODO: This should be not be hardcoded, maybe put in loader.json?
|
// TODO: This should be not be hardcoded, maybe put in loader.json?
|
||||||
return void await removeCacheFile("bundle.js");
|
return void await removeCacheFile("bundle.js");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -62,4 +79,4 @@ export function getModule<T extends LoaderModule>({ name, argumentProcessors: pr
|
||||||
*/
|
*/
|
||||||
export function isSafeModeEnabled() {
|
export function isSafeModeEnabled() {
|
||||||
return Boolean(loaderPayload.loader.initConfig.safeMode);
|
return Boolean(loaderPayload.loader.initConfig.safeMode);
|
||||||
}
|
}
|
||||||
|
|
@ -5,15 +5,14 @@ import type { ImageURISource } from "react-native";
|
||||||
import { patchTabsUI } from "./patches/tabs";
|
import { patchTabsUI } from "./patches/tabs";
|
||||||
|
|
||||||
export interface RowConfig {
|
export interface RowConfig {
|
||||||
key: string;
|
key: string;
|
||||||
title: () => string;
|
title: () => string;
|
||||||
onPress?: () => any;
|
onPress?: () => any;
|
||||||
render?: Parameters<typeof lazy>[0];
|
render?: Parameters<typeof lazy>[0];
|
||||||
icon?: ImageURISource | number;
|
icon?: ImageURISource | number;
|
||||||
IconComponent?: React.ReactNode;
|
IconComponent?: React.ReactNode,
|
||||||
usePredicate?: () => boolean;
|
usePredicate?: () => boolean,
|
||||||
useTrailing?: () => string | JSX.Element;
|
useTrailing?: () => string | JSX.Element
|
||||||
rawTabsConfig?: Record<string, any>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registeredSections = {} as {
|
export const registeredSections = {} as {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { after } from "@lib/api/patcher";
|
||||||
import { TableRow } from "@metro/common/components";
|
import { TableRow } from "@metro/common/components";
|
||||||
import { findByNameLazy, findByPropsLazy } from "@metro/wrappers";
|
import { findByNameLazy, findByPropsLazy } from "@metro/wrappers";
|
||||||
import { registeredSections } from "@ui/settings";
|
import { registeredSections } from "@ui/settings";
|
||||||
import { Strings } from "@core/i18n";
|
|
||||||
|
|
||||||
import { CustomPageRenderer, wrapOnPress } from "./shared";
|
import { CustomPageRenderer, wrapOnPress } from "./shared";
|
||||||
import { findInReactTree } from "@lib/utils";
|
import { findInReactTree } from "@lib/utils";
|
||||||
|
|
@ -30,11 +29,8 @@ export function patchTabsUI(unpatches: (() => void | boolean)[]) {
|
||||||
usePredicate: row.usePredicate,
|
usePredicate: row.usePredicate,
|
||||||
useTrailing: row.useTrailing,
|
useTrailing: row.useTrailing,
|
||||||
useTitle: row.title,
|
useTitle: row.title,
|
||||||
useBadge: () => undefined,
|
|
||||||
useIcon: () => row.icon,
|
|
||||||
onPress: wrapOnPress(row.onPress, null, row.render, row.title()),
|
onPress: wrapOnPress(row.onPress, null, row.render, row.title()),
|
||||||
withArrow: true,
|
withArrow: true,
|
||||||
...row.rawTabsConfig,
|
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
@ -52,9 +48,6 @@ export function patchTabsUI(unpatches: (() => void | boolean)[]) {
|
||||||
type: "route",
|
type: "route",
|
||||||
title: () => "ShiggyCord",
|
title: () => "ShiggyCord",
|
||||||
useTitle: () => "ShiggyCord",
|
useTitle: () => "ShiggyCord",
|
||||||
useIcon: () => undefined,
|
|
||||||
useBadge: () => undefined,
|
|
||||||
IconComponent: () => undefined,
|
|
||||||
screen: {
|
screen: {
|
||||||
route: "VendettaCustomPage",
|
route: "VendettaCustomPage",
|
||||||
getComponent: () => CustomPageRenderer,
|
getComponent: () => CustomPageRenderer,
|
||||||
|
|
@ -64,9 +57,6 @@ export function patchTabsUI(unpatches: (() => void | boolean)[]) {
|
||||||
type: "route",
|
type: "route",
|
||||||
title: () => "ShiggyCord",
|
title: () => "ShiggyCord",
|
||||||
useTitle: () => "ShiggyCord",
|
useTitle: () => "ShiggyCord",
|
||||||
useIcon: () => undefined,
|
|
||||||
useBadge: () => undefined,
|
|
||||||
IconComponent: () => undefined,
|
|
||||||
screen: {
|
screen: {
|
||||||
route: "SHIGGYCORD_CUSTOM_PAGE",
|
route: "SHIGGYCORD_CUSTOM_PAGE",
|
||||||
getComponent: () => CustomPageRenderer,
|
getComponent: () => CustomPageRenderer,
|
||||||
|
|
@ -76,9 +66,6 @@ export function patchTabsUI(unpatches: (() => void | boolean)[]) {
|
||||||
type: "route",
|
type: "route",
|
||||||
title: () => "ShiggyCord",
|
title: () => "ShiggyCord",
|
||||||
useTitle: () => "ShiggyCord",
|
useTitle: () => "ShiggyCord",
|
||||||
useIcon: () => undefined,
|
|
||||||
useBadge: () => undefined,
|
|
||||||
IconComponent: () => undefined,
|
|
||||||
screen: {
|
screen: {
|
||||||
route: "BUNNY_CUSTOM_PAGE",
|
route: "BUNNY_CUSTOM_PAGE",
|
||||||
getComponent: () => CustomPageRenderer,
|
getComponent: () => CustomPageRenderer,
|
||||||
|
|
|
||||||
|
|
@ -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