From 6b8b649f546cc2b45c739904644dceedeff34c4d Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Sun, 14 Sep 2025 12:06:43 +0300 Subject: [PATCH 01/25] cleanup(revenge): remove unnessacary content --- assets/logo/logo-dark.svg | 4 ---- assets/logo/logo-light.svg | 4 ---- assets/wordmark/wordmark+slogan-dark.svg | 6 ----- assets/wordmark/wordmark+slogan-light.svg | 6 ----- docs/2_building.md | 29 ----------------------- docs/README.md | 13 ---------- 6 files changed, 62 deletions(-) delete mode 100644 assets/logo/logo-dark.svg delete mode 100644 assets/logo/logo-light.svg delete mode 100644 assets/wordmark/wordmark+slogan-dark.svg delete mode 100644 assets/wordmark/wordmark+slogan-light.svg delete mode 100644 docs/2_building.md delete mode 100644 docs/README.md diff --git a/assets/logo/logo-dark.svg b/assets/logo/logo-dark.svg deleted file mode 100644 index cb34519..0000000 --- a/assets/logo/logo-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/logo/logo-light.svg b/assets/logo/logo-light.svg deleted file mode 100644 index b16e22f..0000000 --- a/assets/logo/logo-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/wordmark/wordmark+slogan-dark.svg b/assets/wordmark/wordmark+slogan-dark.svg deleted file mode 100644 index 2f3c148..0000000 --- a/assets/wordmark/wordmark+slogan-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/wordmark/wordmark+slogan-light.svg b/assets/wordmark/wordmark+slogan-light.svg deleted file mode 100644 index eefd512..0000000 --- a/assets/wordmark/wordmark+slogan-light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/2_building.md b/docs/2_building.md deleted file mode 100644 index 9405fa7..0000000 --- a/docs/2_building.md +++ /dev/null @@ -1,29 +0,0 @@ -## Building -1. Install a Revenge loader with config support (any mentioned in the [Installing](#installing) section). -2. Go to **Settings** > **General** and enable **Developer Settings**. -3. Clone the repository - - ```sh - git clone https://github.com/revenge-mod/revenge-bundle.git - ``` - -4. Install dependencies - - ``` - bun i - ``` - -5. Build Revenge's code - - ``` - bun run build - ``` - -6. In the newly created `dist` directory, run a HTTP server. I recommend [http-server](https://www.npmjs.com/package/http-server). -7. 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/revenge.js`) in the new input box labeled `Revenge URL`. -8. Restart Discord. Upon reload, you should notice that your device will download Revenge's bundled code from your server, rather than GitHub. -9. Make your changes, rebuild, reload, go wild! - -Alternatively, you can directly *serve* the bundled code by running `bun serve`. `revenge.js` will be served on your local address under the port 4040. You will then insert `http://:4040/revenge.js` as a custom URL and reload. Whenever you restart your mobile client, the script will rebuild the bundle as your client fetches it. - -If the bundle keeps being cached and not updated, you can instead tap the **Settings** > **Developer** > **Clear JS bundle** option which will prompt you to reload. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index ac3cc11..0000000 --- a/docs/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# 👊 Revenge - -This documentation explains how to use [Revenge](https://github.com/revenge-mod/revenge-bundle). - -## 📖 Table of contents - -TODO. - -## ⏭️ Start here - -The next page will tell you about the prerequisites for using Revenge. - -Continue: [💼 Prerequisites](0_prerequisites.md) \ No newline at end of file From 01ed63f1d6fa51c3f83914e6da897db7cd96043d Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Mon, 15 Sep 2025 23:04:56 +0300 Subject: [PATCH 02/25] feat(core plugins): added NoTrack by default1 --- scripts/build.mjs | 2 +- src/core/plugins/badges/index.tsx | 11 ++++-- src/core/plugins/index.ts | 3 +- src/core/plugins/notrack/index.ts | 39 ++++++++++++++++++++ src/core/plugins/notrack/lib/console.ts | 16 ++++++++ src/core/plugins/notrack/lib/miscellanous.ts | 33 +++++++++++++++++ src/core/plugins/notrack/lib/network.ts | 12 ++++++ src/core/plugins/notrack/lib/sentry.ts | 26 +++++++++++++ src/core/plugins/notrack/lib/utils.ts | 3 ++ src/core/plugins/quickinstall/index.ts | 11 ++++-- src/lib/addons/themes/colors/fix.ts | 2 +- 11 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 src/core/plugins/notrack/index.ts create mode 100644 src/core/plugins/notrack/lib/console.ts create mode 100644 src/core/plugins/notrack/lib/miscellanous.ts create mode 100644 src/core/plugins/notrack/lib/network.ts create mode 100644 src/core/plugins/notrack/lib/sentry.ts create mode 100644 src/core/plugins/notrack/lib/utils.ts diff --git a/scripts/build.mjs b/scripts/build.mjs index fea5c91..ff00c84 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -74,7 +74,7 @@ const config = { constModules: { globals: { "bunny-build-info": { - version: `"1.2.0"` + version: `"1.2.1"` } } }, diff --git a/src/core/plugins/badges/index.tsx b/src/core/plugins/badges/index.tsx index c8d7d10..96914a3 100644 --- a/src/core/plugins/badges/index.tsx +++ b/src/core/plugins/badges/index.tsx @@ -15,10 +15,15 @@ const useBadgesModule = findByName("useBadges", false); export default defineCorePlugin({ manifest: { id: "bunny.badges", - name: "Badges", version: "1.0.0", - description: "Adds badges to user's profile", - authors: [{ name: "pylixonly" }] + type: "plugin", + spec: 3, + main: "", + display: { + name: "Badges", + description: "Adds badges to user's profile", + authors: [{ name: "pylixonly" }] + } }, start() { const propHolder = {} as Record; diff --git a/src/core/plugins/index.ts b/src/core/plugins/index.ts index e270de7..7b423f7 100644 --- a/src/core/plugins/index.ts +++ b/src/core/plugins/index.ts @@ -8,7 +8,8 @@ interface CorePlugin { // Called from @lib/plugins export const getCorePlugins = (): Record => ({ "bunny.quickinstall": require("./quickinstall"), - "bunny.badges": require("./badges") + "bunny.badges": require("./badges"), + "vendetta.notrack": require("./notrack") }); /** diff --git a/src/core/plugins/notrack/index.ts b/src/core/plugins/notrack/index.ts new file mode 100644 index 0000000..a497397 --- /dev/null +++ b/src/core/plugins/notrack/index.ts @@ -0,0 +1,39 @@ +import network from "./lib/network"; +import console from "./lib/console"; +import miscellanous from "./lib/miscellanous"; +import sentry from "./lib/sentry"; +import { defineCorePlugin } from ".."; +import { logger } from "@lib/utils/logger"; + +const patches = [ + network(), + console(), + miscellanous(), + // Sentry causes issues + // sentry(), +]; + +export default defineCorePlugin({ + manifest: { + id: "vendetta.notrack", + version: "1.0.0", + type: "plugin", + spec: 3, + main: "", + display: { + name: "NoTrack", + description: "Disables Discord's telemetry", + authors: [{ name: "maisymoe" }] + } + }, + start() { + logger.log("gyat"); + const patches = [ + network(), + console(), + miscellanous(), + sentry(), + ]; + } +}); + diff --git a/src/core/plugins/notrack/lib/console.ts b/src/core/plugins/notrack/lib/console.ts new file mode 100644 index 0000000..7a69471 --- /dev/null +++ b/src/core/plugins/notrack/lib/console.ts @@ -0,0 +1,16 @@ +import { logger } from "@lib/utils/logger"; + +export default () => { + const sentrified = {}; + + try { + Object.keys(console).forEach((x) => { + sentrified[x] = console[x]; + console[x] = console[x].__sentry_original__ ?? console[x]; + }); + } catch(e) { + logger.log("Failed to de-sentrify console functions!", e); + }; + + return () => Object.keys(console).forEach((x) => console[x] = sentrified[x]); +} diff --git a/src/core/plugins/notrack/lib/miscellanous.ts b/src/core/plugins/notrack/lib/miscellanous.ts new file mode 100644 index 0000000..b6a6093 --- /dev/null +++ b/src/core/plugins/notrack/lib/miscellanous.ts @@ -0,0 +1,33 @@ +import { findByProps } from "@metro"; +import { noop } from "./utils"; + +// Stuff in here is non-specific tracking scattered around the app + +const AnalyticsUtils = findByProps("AnalyticsActionHandlers"); +const SuperPropUtils = findByProps("encodeProperties", "track"); +const VoiceStateUtils = findByProps("getVoiceStateMetadata"); +const CrashReportUtils = findByProps("submitLiveCrashReport"); +const MetricsUtils = findByProps("_metrics"); + +export default () => { + const patches = [ + // Global analytics utilities + ...(["handleTrack", "handleFingerprint"].map((i) => noop(i, AnalyticsUtils.AnalyticsActionHandlers))), + // TODO: This is a getter - look into patching it + // noop("analyticsTrackingStoreMaker", AnalyticsUtils), + + // Super properties tracking + noop("track", SuperPropUtils), + + // Voice state metadata tracking + noop("trackWithMetadata", VoiceStateUtils), + + // Crash reporter + noop("submitLiveCrashReport", CrashReportUtils), + + // Whatever "metrics" implies + noop("push", MetricsUtils._metrics), + ]; + + return () => patches.forEach((p) => p()); +} diff --git a/src/core/plugins/notrack/lib/network.ts b/src/core/plugins/notrack/lib/network.ts new file mode 100644 index 0000000..68f762e --- /dev/null +++ b/src/core/plugins/notrack/lib/network.ts @@ -0,0 +1,12 @@ +import { instead } from "@lib/api/patcher"; + +export default () => { + // https://github.com/yellowsink/shelter-plugins/blob/3a62d1557b704f4617f72882edba826218eb8c6e/plugins/antitrack/index.js#L10-L25 + const analyticsTest = /client-analytics\.braintreegateway\.com|discord\.com\/api\/v9\/(science|track)|app\.adjust\..*|.*\.ingest\.sentry\.io/; + + return instead( + "send", + XMLHttpRequest.prototype, + function (args, orig) { if (!analyticsTest.test(this.__sentry_xhr__?.url)) return orig.apply(this, args) }, + ); +} diff --git a/src/core/plugins/notrack/lib/sentry.ts b/src/core/plugins/notrack/lib/sentry.ts new file mode 100644 index 0000000..5b01b7e --- /dev/null +++ b/src/core/plugins/notrack/lib/sentry.ts @@ -0,0 +1,26 @@ +import { findByProps } from "@metro"; +import { noop } from "./utils"; + +const Sentry = { + initializer: findByProps("initSentry"), + main: globalThis.__SENTRY__?.hub, + client: globalThis.__SENTRY__?.hub?.getClient(), +}; + +export default () => { + const patches = [ + noop("initSentry", Sentry.initializer), + noop("addBreadcrumb", Sentry.main), + ]; + + Sentry.client.getOptions().enabled = false; + Sentry.client.close(); + Sentry.main.getStackTop().scope.clear(); + Sentry.main.getScope().clear(); + + return () => { + patches.forEach((p) => p()); + Sentry.client.getOptions().enabled = true; + Sentry.client.open(); + }; +} diff --git a/src/core/plugins/notrack/lib/utils.ts b/src/core/plugins/notrack/lib/utils.ts new file mode 100644 index 0000000..55f871f --- /dev/null +++ b/src/core/plugins/notrack/lib/utils.ts @@ -0,0 +1,3 @@ +import { instead } from "@lib/api/patcher"; + +export const noop = (prop: string, parent: any) => instead(prop, parent, () => {}); diff --git a/src/core/plugins/quickinstall/index.ts b/src/core/plugins/quickinstall/index.ts index 51f5e94..d8f25da 100644 --- a/src/core/plugins/quickinstall/index.ts +++ b/src/core/plugins/quickinstall/index.ts @@ -7,10 +7,15 @@ let patches = [] as (() => unknown)[]; export default defineCorePlugin({ manifest: { id: "bunny.quickinstall", - name: "QuickInstall", version: "1.0.0", - description: "Quickly install Vendetta plugins and themes", - authors: [{ name: "Vendetta Team" }] + type: "plugin", + spec: 3, + main: "", + display: { + name: "QuickInstall", + description: "Quickly install Vendetta plugins and themes", + authors: [{ name: "Vendetta Team" }] + } }, start() { patches = [patchForumPost(), patchUrl()]; diff --git a/src/lib/addons/themes/colors/fix.ts b/src/lib/addons/themes/colors/fix.ts index eafe546..9099c07 100644 --- a/src/lib/addons/themes/colors/fix.ts +++ b/src/lib/addons/themes/colors/fix.ts @@ -1,6 +1,6 @@ import { StatusBar } from "react-native"; import { findByStoreNameLazy } from "@metro/wrappers"; -import { _colorRef } from "./updater"; // <-- import your colorRef +import { _colorRef } from "./updater"; const ThemeStore = findByStoreNameLazy("ThemeStore"); From 6b6ab12215d9621813710f09ffd74edbf1bd3ad3 Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Tue, 16 Sep 2025 12:18:45 +0300 Subject: [PATCH 03/25] fix(NoTrack): remove unnessacary comments --- src/core/plugins/notrack/index.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/core/plugins/notrack/index.ts b/src/core/plugins/notrack/index.ts index a497397..58865de 100644 --- a/src/core/plugins/notrack/index.ts +++ b/src/core/plugins/notrack/index.ts @@ -27,13 +27,10 @@ export default defineCorePlugin({ } }, start() { - logger.log("gyat"); - const patches = [ - network(), - console(), - miscellanous(), - sentry(), - ]; + network(), + console(), + miscellanous(), + sentry() } }); From 3da8b08c83e67e33197c2c297419292306172bcf Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Tue, 16 Sep 2025 15:06:20 +0300 Subject: [PATCH 04/25] cleanup(Naming): revert old merge commits --- .forgejo/ISSUE_TEMPLATE/feature-request.yml | 46 ------------------- .../components/ErrorBoundaryScreen.tsx | 2 +- src/entry.ts | 9 ++-- 3 files changed, 4 insertions(+), 53 deletions(-) delete mode 100644 .forgejo/ISSUE_TEMPLATE/feature-request.yml diff --git a/.forgejo/ISSUE_TEMPLATE/feature-request.yml b/.forgejo/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index bacf7ed..0000000 --- a/.forgejo/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Feature request -description: Suggest an idea for Kettu -title: "[feature]: " -labels: [enhancement] -body: - - type: markdown - attributes: - value: | - # Revenge feature request - - Before creating a new feature request, please keep the following in mind: - - - **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/revenge-mod/revenge-bundle/issues?q=label%3A%22Feature+request%22). - - **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/revenge-mod/revenge-bundle/blob/main/CONTRIBUTING.md). - - **Do not use the issue page for support**: If you need help or have questions, join us on [Discord](https://discord.gg/ddcQf3s2Uq). - - type: textarea - attributes: - label: Feature description - description: What feature you want Kettu to have? Provide detailed description about what it should look like or where it should be added. - validations: - required: true - - - type: textarea - attributes: - label: Motivation - description: | - A strong motivation is necessary for a feature request to be considered. - - - Why should this feature be implemented? - - What is the explicit use case? - - What are the benefits? - - What makes this feature important? - validations: - required: true - - type: checkboxes - id: acknowledgements - attributes: - label: Acknowledgements - description: Your feature request will be closed if you don't follow the checklist below. - options: - - label: I have checked all open and closed feature requests and this is not a duplicate - required: true - - label: I have chosen an appropriate title. - required: true - - label: All requested information has been provided properly. - required: true diff --git a/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx b/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx index bf4824e..29b5213 100644 --- a/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx +++ b/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx @@ -33,7 +33,7 @@ export default function ErrorBoundaryScreen(props: { Uh oh. - A crash occurred while rendering a component. This could be caused by a plugin, Revenge, or Discord itself. + A crash occurred while rendering a component. This could be caused by a plugin, Kettu, or Discord itself. {debugInfo.os.name}; {debugInfo.discord.build} ({debugInfo.discord.version}); {debugInfo.bunny.version} diff --git a/src/entry.ts b/src/entry.ts index cc1d806..c0f7e4a 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -5,7 +5,7 @@ const { instead } = require("spitroast"); // @ts-ignore - window is defined later in the bundle, so we assign it early globalThis.window = globalThis; -async function initializeRevenge() { +async function initializeKettu() { try { // Make 'freeze' and 'seal' do nothing Object.freeze = Object.seal = Object; @@ -31,7 +31,6 @@ if (typeof window.__r === "undefined") { var _requireFunc: any; // Calls from the native side are deferred until the index.ts(x) is loaded - // Revenge delays the execution of index.ts(x) because Revenge's initialization is asynchronous interface DeferredQueue { object: any; method: string; @@ -102,7 +101,7 @@ if (typeof window.__r === "undefined") { } const startDiscord = async () => { - await initializeRevenge(); + await initializeKettu(); for (const unpatch of unpatches) unpatch(); unpatches.length = 0; @@ -143,7 +142,5 @@ if (typeof window.__r === "undefined") { } }); } else { - // It is too late to late to hook __r, so we just initialize Revenge here - // Likely because of using the legacy loader (from Vendetta) - initializeRevenge(); + initializeKettu(); } \ No newline at end of file From 4c2e327ad383242518a23f3339f26702be74a4e6 Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Sun, 21 Sep 2025 12:04:38 +0300 Subject: [PATCH 05/25] refactor(badges): made badges work and made them for kettu not bunny --- scripts/build.mjs | 2 +- src/core/plugins/badges/index.tsx | 86 ++++++++++++++++++++----------- src/core/ui/settings/index.ts | 12 ++--- 3 files changed, 63 insertions(+), 37 deletions(-) diff --git a/scripts/build.mjs b/scripts/build.mjs index ff00c84..95da306 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -74,7 +74,7 @@ const config = { constModules: { globals: { "bunny-build-info": { - version: `"1.2.1"` + version: `"1.2.2"` } } }, diff --git a/src/core/plugins/badges/index.tsx b/src/core/plugins/badges/index.tsx index 96914a3..08c9828 100644 --- a/src/core/plugins/badges/index.tsx +++ b/src/core/plugins/badges/index.tsx @@ -2,7 +2,6 @@ import { after } from "@lib/api/patcher"; import { onJsxCreate } from "@lib/api/react/jsx"; import { findByName } from "@metro"; import { useEffect, useState } from "react"; - import { defineCorePlugin } from ".."; interface BunnyBadge { @@ -15,52 +14,79 @@ const useBadgesModule = findByName("useBadges", false); export default defineCorePlugin({ manifest: { id: "bunny.badges", - version: "1.0.0", + version: "1.1.0", type: "plugin", spec: 3, main: "", display: { name: "Badges", description: "Adds badges to user's profile", - authors: [{ name: "pylixonly" }] + authors: [{ name: "cocobo1"}, { name: "pylixonly" }] } }, + start() { - const propHolder = {} as Record; const badgeCache = {} as Record; - - onJsxCreate("RenderedBadge", (_, ret) => { - if (ret.props.id.match(/bunny-\d+-\d+/)) { - Object.assign(ret.props, propHolder[ret.props.id]); + const badgeProps = {} as Record; + + onJsxCreate("ProfileBadge", (component, ret) => { + if (ret.props.id?.startsWith("bunny-")) { + const cachedProps = badgeProps[ret.props.id]; + if (cachedProps) { + ret.props.source = cachedProps.source; + ret.props.label = cachedProps.label; + ret.props.id = cachedProps.id; + } } }); - - after("default", useBadgesModule, ([user], r) => { - const [badges, setBadges] = useState(user ? badgeCache[user.userId] ??= [] : []); - - useEffect(() => { - if (user) { - fetch(`https://raw.githubusercontent.com/pyoncord/badges/refs/heads/main/${user.userId}.json`) - .then(r => r.json()) - .then(badges => setBadges(badgeCache[user.userId] = badges)); + + onJsxCreate("RenderedBadge", (component, ret) => { + if (ret.props.id?.startsWith("bunny-")) { + const cachedProps = badgeProps[ret.props.id]; + if (cachedProps) { + Object.assign(ret.props, cachedProps); } - }, [user]); - - if (user) { - badges.forEach((badges, i) => { - propHolder[`bunny-${user.userId}-${i}`] = { - source: { uri: badges.url }, - id: `bunny-${i}`, - label: badges.label + } + }); + + after("default", useBadgesModule, ([user], result) => { + const [badges, setBadges] = useState( + user ? badgeCache[user.userId] || [] : [] + ); + + useEffect(() => { + if (user && !badgeCache[user.userId]) { + fetch(`https://raw.githubusercontent.com/ra1ncord/badges/refs/heads/main/${user.userId}.json`) + .then(r => r.json()) + .then(fetchedBadges => { + badgeCache[user.userId] = fetchedBadges; + setBadges(fetchedBadges); + }) + //user has no badges, but maybe they should get some by contributing + .catch(() => { + badgeCache[user.userId] = []; + }); + } + }, [user?.userId]); + + if (user && badges.length > 0) { + badges.forEach((badge, i) => { + const badgeId = `bunny-${user.userId}-${i}`; + + badgeProps[badgeId] = { + id: badgeId, + source: { uri: badge.url }, + label: badge.label, + userId: user.userId, }; - r.push({ - id: `bunny-${user.userId}-${i}`, - description: badges.label, - icon: "_", + result.push({ + id: badgeId, + description: badge.label, + icon: "dummy", }); }); } }); } -}); +}); \ No newline at end of file diff --git a/src/core/ui/settings/index.ts b/src/core/ui/settings/index.ts index 1b76226..d5f96d8 100644 --- a/src/core/ui/settings/index.ts +++ b/src/core/ui/settings/index.ts @@ -51,12 +51,6 @@ export default function initSettings() { ] }); - // Compat with Vendetta Plugins that use configs in settings - registerSection({ - name: "Vendetta", - items: [] - }); - // Compat with Bunny Plugins that use configs in settings registerSection({ name: "Bunny", @@ -68,4 +62,10 @@ export default function initSettings() { name: "Revenge", items: [] }); + + // Compat with Vendetta Plugins that use configs in settings + registerSection({ + name: "Vendetta", + items: [] + }); } \ No newline at end of file From 5fd1d4f99627dd9148f31d7b243f9679bf239438 Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Sun, 21 Sep 2025 19:59:00 +0300 Subject: [PATCH 06/25] refactor(badges): made badges load from one file instead of a bunch of random ones --- src/core/plugins/badges/index.tsx | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/core/plugins/badges/index.tsx b/src/core/plugins/badges/index.tsx index 08c9828..292fd0e 100644 --- a/src/core/plugins/badges/index.tsx +++ b/src/core/plugins/badges/index.tsx @@ -26,7 +26,7 @@ export default defineCorePlugin({ }, start() { - const badgeCache = {} as Record; + let allBadges: { [x: string]: any; } | null = null; const badgeProps = {} as Record; onJsxCreate("ProfileBadge", (component, ret) => { @@ -50,22 +50,22 @@ export default defineCorePlugin({ }); after("default", useBadgesModule, ([user], result) => { - const [badges, setBadges] = useState( - user ? badgeCache[user.userId] || [] : [] - ); + const [badges, setBadges] = useState([]); useEffect(() => { - if (user && !badgeCache[user.userId]) { - fetch(`https://raw.githubusercontent.com/ra1ncord/badges/refs/heads/main/${user.userId}.json`) + if (!user) return; + + if (!allBadges) { + fetch("https://raw.githubusercontent.com/ra1ncord/badges/main/badges.json") .then(r => r.json()) - .then(fetchedBadges => { - badgeCache[user.userId] = fetchedBadges; - setBadges(fetchedBadges); + .then(data => { + allBadges = data; + //@ts-expect-error + setBadges(allBadges[user.userId] || []); }) - //user has no badges, but maybe they should get some by contributing - .catch(() => { - badgeCache[user.userId] = []; - }); + } else { + //user has no badges, but maybe they should get some by contributing + setBadges(allBadges[user.userId] || []); } }, [user?.userId]); @@ -83,7 +83,7 @@ export default defineCorePlugin({ result.push({ id: badgeId, description: badge.label, - icon: "dummy", + icon: " _", }); }); } From 33dfb4e6dd4cb2213987cc8f583ab731f0cf7f6e Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Sun, 21 Sep 2025 22:01:45 +0200 Subject: [PATCH 07/25] fix(badges): make kettu load badges from codeberg instead of github --- src/core/plugins/badges/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/plugins/badges/index.tsx b/src/core/plugins/badges/index.tsx index 292fd0e..69bfbda 100644 --- a/src/core/plugins/badges/index.tsx +++ b/src/core/plugins/badges/index.tsx @@ -56,7 +56,7 @@ export default defineCorePlugin({ if (!user) return; if (!allBadges) { - fetch("https://raw.githubusercontent.com/ra1ncord/badges/main/badges.json") + fetch("https://codeberg.org/cocobo1/badges/raw/branch/main/badges.json") .then(r => r.json()) .then(data => { allBadges = data; From 6140486b5da6ea9ec4581c50f7b0353826a5fe5d Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Sun, 21 Sep 2025 22:10:38 +0200 Subject: [PATCH 08/25] fix(badges): correct json location --- src/core/plugins/badges/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/plugins/badges/index.tsx b/src/core/plugins/badges/index.tsx index 69bfbda..79baa15 100644 --- a/src/core/plugins/badges/index.tsx +++ b/src/core/plugins/badges/index.tsx @@ -56,7 +56,7 @@ export default defineCorePlugin({ if (!user) return; if (!allBadges) { - fetch("https://codeberg.org/cocobo1/badges/raw/branch/main/badges.json") + fetch("https://codeberg.org/raincord/badges/raw/branch/main/badges.json") .then(r => r.json()) .then(data => { allBadges = data; From f0745a3c9e6577770f4c0ec509b36530face8284 Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Mon, 22 Sep 2025 22:04:55 +0300 Subject: [PATCH 09/25] fix(corecommands): fixed core commands (thanks windows VM user!) --- src/core/commands/debug.ts | 3 ++- src/core/commands/plugins.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/commands/debug.ts b/src/core/commands/debug.ts index 2cf12df..b1975c6 100644 --- a/src/core/commands/debug.ts +++ b/src/core/commands/debug.ts @@ -28,7 +28,8 @@ export default () => { if (ephemeral?.value) { messageUtil.sendBotMessage(ctx.channel.id, content); } else { - messageUtil.sendMessage(ctx.channel.id, { content }); + const fixNonce = Date.now().toString(); + messageUtil.sendMessage(ctx.channel.id, { content }, void 0, {nonce:fixNonce}); } } }; diff --git a/src/core/commands/plugins.ts b/src/core/commands/plugins.ts index b4ba1d8..8933713 100644 --- a/src/core/commands/plugins.ts +++ b/src/core/commands/plugins.ts @@ -36,7 +36,8 @@ export default () => { if (ephemeral?.value) { messageUtil.sendBotMessage(ctx.channel.id, content); } else { - messageUtil.sendMessage(ctx.channel.id, { content }); + const fixNonce = Date.now().toString(); + messageUtil.sendMessage(ctx.channel.id, { content }, void 0, {nonce:fixNonce}); } } }; From ec751eb6b4e32d2fbdf70174947affee01d7c032 Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Mon, 22 Sep 2025 23:08:20 +0300 Subject: [PATCH 10/25] fix(icon): replace missing icon --- src/core/ui/settings/pages/General/About.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ui/settings/pages/General/About.tsx b/src/core/ui/settings/pages/General/About.tsx index 68f07ca..19909a0 100644 --- a/src/core/ui/settings/pages/General/About.tsx +++ b/src/core/ui/settings/pages/General/About.tsx @@ -63,7 +63,7 @@ export default function About() { { label: Strings.BRAND, version: debugInfo.device.brand, - icon: "SparklesIcon" + icon: "StarShootingIcon" }, { label: Strings.MODEL, From f1c25117c520000e37b292474e2ac25d87353934 Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Mon, 22 Sep 2025 22:16:38 +0200 Subject: [PATCH 11/25] fix(icon): replace missing icon --- src/core/ui/settings/pages/General/About.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ui/settings/pages/General/About.tsx b/src/core/ui/settings/pages/General/About.tsx index 19909a0..94f7135 100644 --- a/src/core/ui/settings/pages/General/About.tsx +++ b/src/core/ui/settings/pages/General/About.tsx @@ -63,7 +63,7 @@ export default function About() { { label: Strings.BRAND, version: debugInfo.device.brand, - icon: "StarShootingIcon" + icon: "MagicWandIcon" }, { label: Strings.MODEL, From 4ddb313e1f3a76ea3a720668d45038d5af46bf87 Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Tue, 23 Sep 2025 08:37:59 +0200 Subject: [PATCH 12/25] fix(icons): fix missing icon --- src/core/ui/settings/pages/Developer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ui/settings/pages/Developer/index.tsx b/src/core/ui/settings/pages/Developer/index.tsx index bb710ae..b73c4c5 100644 --- a/src/core/ui/settings/pages/Developer/index.tsx +++ b/src/core/ui/settings/pages/Developer/index.tsx @@ -103,7 +103,7 @@ export default function Developer() { // Thanks for this Revenge team! label={Strings.CLEAR_BUNDLE} - icon={} + icon={} onPress={() => { openAlert("pupu-clear-bundle-reload-confirmation", Date: Tue, 23 Sep 2025 18:11:56 +0300 Subject: [PATCH 13/25] fix(icons): replace missing icons --- src/core/ui/components/AddonPage.tsx | 2 +- src/lib/ui/components/Search.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/ui/components/AddonPage.tsx b/src/core/ui/components/AddonPage.tsx index c802d6f..8d624a5 100644 --- a/src/core/ui/components/AddonPage.tsx +++ b/src/core/ui/components/AddonPage.tsx @@ -175,7 +175,7 @@ export default function AddonPage({ CardComponent, ...props }: setSearch(v)} /> {props.sortOptions && showSimpleActionSheet({ diff --git a/src/lib/ui/components/Search.tsx b/src/lib/ui/components/Search.tsx index 985e155..66b164d 100644 --- a/src/lib/ui/components/Search.tsx +++ b/src/lib/ui/components/Search.tsx @@ -12,7 +12,7 @@ export interface SearchProps { } function SearchIcon() { - return ; + return ; } export default ({ onChangeText, placeholder, style, isRound }: SearchProps) => { From 49221175ef3e519d2aa76da758374fa71f384d7a Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Tue, 23 Sep 2025 18:22:16 +0300 Subject: [PATCH 14/25] settings(ui): added a link to the codeberg --- src/assets/icons/codeberg-logo_icon_white.png | Bin 0 -> 1908 bytes src/assets/icons/revenge.png | Bin 1906 -> 0 bytes src/core/i18n/default.json | 3 ++- src/core/ui/settings/pages/General/index.tsx | 7 +++++++ src/lib/utils/constants.ts | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/assets/icons/codeberg-logo_icon_white.png delete mode 100644 src/assets/icons/revenge.png diff --git a/src/assets/icons/codeberg-logo_icon_white.png b/src/assets/icons/codeberg-logo_icon_white.png new file mode 100644 index 0000000000000000000000000000000000000000..873d3c79881a3dc7b54c058350aee5f549e3e9eb GIT binary patch literal 1908 zcmV-)2aEWLP)EX>4Tx04R}tkv&MmKpe$i8%2?dNIQr)WT;M7L`5963Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4P#Iyou2NQwVT3N2zhIPS;0dyl(!K(JnBn$9s$1I#dwzgxj#p*nzI-X5Q%4*VcNtS#M7I$ z!FiuJ!ius=d`>)S(glehxvqHp#<}2fkY`5BbZVYBLM#^ASZQNcG&SNW;;5?WlpoA^ zta9Gstd*;*c~AbrP)=W2<~q$GB(R7jND!f*iW17O5u;Tn#X^eq<39dD*DsMvAy)~E z91EyGgY5dj|KNAGR(^8AOA5t-&KJk|7zVm_fkw@7zKz?I(eSL(pbC+W48 z7Ci#`wtx+g*P00006VoOIv00000008+zyMF)x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru<^vHEGX|if#W?@~1#wA4K~#9!<(o@vRaF>;zjcbi z7hshb(TOhvF@c2003rnAK#h+98U!c4LZV@SCMKF_jG__^14JVU11l4C03rm8NQhAc zLnJ&@o`MQQQK3L-DW#?Da?oA8H1~P#Ik!Eql9RUg?$^K0zyH1dy?5avhicuS?Mr|w zfvbQkfNr1_Xa<@kbzLw7*d78*2BrXGfYHEBz@P$EI)MGa9$*LX9q@ysGnsKI+t=8h zZF__5v#uU(w%6KzA`m)Y4WD3pneBF`hgRDSwnq;n!zbGQ+OChQz!_kYq-`Y$ussym4P0M?fz1I% z058{|ub+TBC3TjH`U`3#To#4?BHPPr)aRU{9$@eBKSz0V{zeDUV?Y@a%apAgHCoKz)iGuLJJOx9J%~*Rb$H z28V=Ze2QcFRnn+j4|phL;|~Edi|qJzhOyKEUEvKKOL086*`AO~!1NS5YX=^c)L2lB zNe+hBh35$He3%+(VLCt*mZ7O>iXBOt%P7|oj2{$!ZUWv*aa{NGdcasA zo>JH6`6$GWG%(Kg@Cdjor4D`uW|h4)C3S>fa|sae2Jlx3m~y zlgA5A1@Y36!Z=4qz^H^9HSmp}Nn=z5+?sHs^Cf*AvuRlR|w&&k=PJI1J1vnQxcIsUie)hoIP!lq~L5 zU)ADl2zvh}6hP~WJja1IL##=JfF$seqzwtRbuvd57kYM3VzaYMbVUg;xim?|7N$^J z2?FYX)j=&*gSH50s-l6tz%xP1CyAyahDSjlbph{(ydf@5L{+G)R^SmyEhLWJB87doHn_ zo+G@9^OcC6UEoTviJtAVv~jdT`Mm`AEe-+eE2wZRh@FlBMnG>m{Wj(AET0CpM3Vjv z(Y)lE8H_(z1LFzD+U*rqYXsgcv+PDm9lb(mWf>J-leCW7qA^CDZNMjGlv~leDF9F^ zds_{>L+#NRv)-~mL@{sv;v4~zek`&Y90Z;Wk-6458na#(Fuw?aYr@n&?_gwcfgPG@ z<=nMNDGsy+!4)^FoJanh<#V>?67X>z#%D{~O8??$6?OLmi~4-FJ6AJAIc&EA3vvoJ zNNS+}(O6~uwZJzyf4`MGo%h75g@6}b}RoM=r)G= u-W<2hta(7M7qkQWa_{_JG_alMOZyK=G8VwuD^g|v0000vt7YQ`fi zLD7avQ18+(LshFv$LrEk&)lNXUacxRKb^V%+`ZO5-}mja&)#R9bd^PZ9JK0qe7SowAf(4nMRLf5}a(@G>r%b1cShPt_Q%J=3q~U5C?~2fE*}f z04ziXfPm~2=<5IQ{oNdeK)=MHAUA+W0ZJfxgHBJE{t^!Z`Mz~U0>BEG^gsN+rT{pV zD+@@2nZ)BHBBbo^s{0*f%Kol&9pw8a1=f}NlIdz{0YLv2U=?qbkaD@9jW_x$2h5PQtyjK_Llku|R$*x82yRJ9y zSa05TK20dbhSl)Wu*JWJA#+f!91f=rS0&0ILXl*}{~8$^Ys`7NqlO@u6REvVqQCZ) zUhWYqnNnrLQ;Y<+JnS6hXe^RG`-ZavRDfPj4eBjD!=gRbpOC(H%5DaMN~&2-IT#^1 zj!QG#h7BoX9rAY(_{G8}S`WU9^?a(l!f7&-Q%tofsHw6&`@jH~4b?5qpVK$d;HHF7 z)N9$Bj%Mkm$gG-{R8Ia29SO`Avbln9Ep6Ua^IdX(1svOaAs-Eyhzk*MCEMY{khL;O zIJNTR9C?fQg{<9gX)ILiS*kQ`JkoT1<^rZFnIux^Y@OkthZ>P^afjCJ_bkUFC>D9NCrY_E+ zG%{EBx_kT!<;_HGwK=eAkswo&xM3|(XvE&8p@!VY2yYq-6jC~ zIS=ua;7Vs}8!mh6s!9^VG30tdDMi*7aYkfuw0c*f?JGm#{n`+aYIxkH*+l>FO{kmk z#LURU1zXx7opL_HQjzs!@%(J3i&OS8p{wIi(r!d_M{TWn1m?}s(+pyDPv2+cC-!LZ zqKUE4h3^>Q9wsRDQ{|3nGZ4*MbxXqvvCV2`4_HNfGS=%O3(hwi*ueJ=>p0)``BXF+ z(0CS&9y8TgT8ymB&#xxC^g#L`eNg%W$_EmXKA73puFPcDH{KD$Ru+ugU>><~-U(A$ zWsajo*@Ibd9~!!{pq^)udO=e-?lH}f@|8c~%->lLqOuLO)+e1_Psxq>X;Zu+F!0h2`JT%jRy0S8Gq}ZdbM4n}A%L-MVog7D?BO zPyR?w@XjYkzL-KGsjkEm@8uoS7VHTpJ*T19Ki5yHfcI=Y$Siva(Ct~)if}V|w%|Gu z@Crz~e4(wEs&mfNZ~a>LZs-Y1-IW_HqJ%Wt!27wkYK5XHA3;T!v&dUqB|3Q;Glc!# z8j}62e6H@<&G~^KmD@IU-Sc~zJ;3iTy`!`$zR^v(dMQPArsU4s=x*oGqzTf&i&AbKL})PDcEk< zW2pAI4|qM(`oa+`-IQ0)u@k@F^E$=@AJ|Lch+bA-y{vAh@_fG=)9$4@v)6tgvRhR- ziqn5JwD5La{~U0j#qekR7B+k*6V}|Dimo;+`Nb5BZecG^>C>M_%KEDFNtxibaf?sj(c^DncFiAj8n Q(f-HbVB=(c*NV#i3o&gv;s5{u diff --git a/src/core/i18n/default.json b/src/core/i18n/default.json index 33ea5fb..af4fbb0 100644 --- a/src/core/i18n/default.json +++ b/src/core/i18n/default.json @@ -16,6 +16,7 @@ "CLEAR_DATA": "Clear data", "CLEAR_DATA_FAILED": "Failed to clear data for {name}", "CLEAR_DATA_SUCCESSFUL": "Cleared data for {name}", + "CODEBERG": "Codeberg", "CODENAME": "Codename", "COMMAND_DEBUG_DESC": "Send Kettu debug info.", "COMMAND_DEBUG_OPT_EPHEMERALLY": "Send debug info ephemerally.", @@ -113,7 +114,7 @@ "UH_OH": "Uh Oh", "UNINSTALL": "Uninstall", "UNINSTALL_TITLE": "Uninstall {title}", - "URL_PLACEHOLDER": "https://github.com/C0C0B01/Kettu", + "URL_PLACEHOLDER": "https://codeberg.org/cocobo1/Kettu", "VERSION": "Version", "VERSIONS": "Versions" } \ No newline at end of file diff --git a/src/core/ui/settings/pages/General/index.tsx b/src/core/ui/settings/pages/General/index.tsx index cf9558b..6afedf8 100644 --- a/src/core/ui/settings/pages/General/index.tsx +++ b/src/core/ui/settings/pages/General/index.tsx @@ -12,6 +12,7 @@ import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants"; import { NavigationNative } from "@metro/common"; import { AlertActionButton, AlertActions, AlertModal, Stack, TableRow, TableRowGroup, TableSwitchRow } from "@metro/common/components"; import { Linking, ScrollView } from "react-native"; +import CodebergIcon from "@assets/icons/codeberg-logo_icon_white.png"; export default function General() { useProxy(settings); @@ -50,6 +51,12 @@ export default function General() { icon={} onPress={() => Linking.openURL(DISCORD_SERVER)} /> + } + onPress={() => Linking.openURL(CODEBERG)} + /> Date: Tue, 23 Sep 2025 18:36:10 +0300 Subject: [PATCH 15/25] fix(link): fix codeberg link --- src/core/ui/settings/pages/General/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ui/settings/pages/General/index.tsx b/src/core/ui/settings/pages/General/index.tsx index 6afedf8..f7f6071 100644 --- a/src/core/ui/settings/pages/General/index.tsx +++ b/src/core/ui/settings/pages/General/index.tsx @@ -8,7 +8,7 @@ import { getDebugInfo } from "@lib/api/debug"; import { BundleUpdaterManager } from "@lib/api/native/modules"; import { settings } from "@lib/api/settings"; import { openAlert } from "@lib/ui/alerts"; -import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants"; +import { DISCORD_SERVER, GITHUB, CODEBERG } from "@lib/utils/constants"; import { NavigationNative } from "@metro/common"; import { AlertActionButton, AlertActions, AlertModal, Stack, TableRow, TableRowGroup, TableSwitchRow } from "@metro/common/components"; import { Linking, ScrollView } from "react-native"; From b1251247733301449878fdcbf79ada243472f38d Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Tue, 23 Sep 2025 18:40:04 +0300 Subject: [PATCH 16/25] fix(icons): correct icon --- src/core/ui/settings/pages/Developer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ui/settings/pages/Developer/index.tsx b/src/core/ui/settings/pages/Developer/index.tsx index b73c4c5..5d700ff 100644 --- a/src/core/ui/settings/pages/Developer/index.tsx +++ b/src/core/ui/settings/pages/Developer/index.tsx @@ -103,7 +103,7 @@ export default function Developer() { // Thanks for this Revenge team! label={Strings.CLEAR_BUNDLE} - icon={} + icon={} onPress={() => { openAlert("pupu-clear-bundle-reload-confirmation", Date: Sat, 27 Sep 2025 13:15:51 +0300 Subject: [PATCH 17/25] feat(Browser): added a built in plugin and theme browser --- scripts/build.mjs | 2 +- src/core/i18n/default.json | 1 + src/core/ui/settings/index.ts | 6 + .../ui/settings/pages/PluginBrowser/index.tsx | 667 ++++++++++++------ src/lib/utils/constants.ts | 3 +- src/metro/common/components.ts | 1 - 6 files changed, 464 insertions(+), 216 deletions(-) diff --git a/scripts/build.mjs b/scripts/build.mjs index 95da306..75e3ffb 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -74,7 +74,7 @@ const config = { constModules: { globals: { "bunny-build-info": { - version: `"1.2.2"` + version: `"1.3.0"` } } }, diff --git a/src/core/i18n/default.json b/src/core/i18n/default.json index af4fbb0..afd4443 100644 --- a/src/core/i18n/default.json +++ b/src/core/i18n/default.json @@ -8,6 +8,7 @@ "BRAND": "Brand", "PUPU": "Kettu", "PUPU_URL": "Kettu URL", + "BROWSER": "Browser", "BYTECODE": "Bytecode", "CANCEL": "Cancel", "CLEAR": "Clear", diff --git a/src/core/ui/settings/index.ts b/src/core/ui/settings/index.ts index d5f96d8..a671937 100644 --- a/src/core/ui/settings/index.ts +++ b/src/core/ui/settings/index.ts @@ -41,6 +41,12 @@ export default function initSettings() { render: () => import("@core/ui/settings/pages/Fonts"), usePredicate: () => isFontSupported() }, + { + key: "KETTU_BROWSER", + title: () => Strings.BROWSER, + icon: findAssetId("UploadIcon"), + render: () => import("@core/ui/settings/pages/PluginBrowser"), + }, { key: "BUNNY_DEVELOPER", title: () => Strings.DEVELOPER, diff --git a/src/core/ui/settings/pages/PluginBrowser/index.tsx b/src/core/ui/settings/pages/PluginBrowser/index.tsx index 1af3f0f..d30e9a0 100644 --- a/src/core/ui/settings/pages/PluginBrowser/index.tsx +++ b/src/core/ui/settings/pages/PluginBrowser/index.tsx @@ -1,73 +1,223 @@ -import { deleteRepository, installPlugin, isCorePlugin, isPluginInstalled, pluginRepositories, registeredPlugins, uninstallPlugin, updateAllRepository, updateRepository } from "@lib/addons/plugins"; -import { BunnyPluginManifestInternal } from "@lib/addons/plugins/types"; -import { findAssetId } from "@lib/api/assets"; -import { dismissAlert, openAlert } from "@lib/ui/alerts"; -import { AlertActionButton } from "@lib/ui/components/wrappers"; -import { hideSheet, showSheet } from "@lib/ui/sheets"; -import { showToast } from "@lib/ui/toasts"; -import { OFFICIAL_PLUGINS_REPO_URL } from "@lib/utils/constants"; -import { NEXPID_PLUGINS_REPO_URL } from "@lib/utils/constants"; -import isValidHttpUrl from "@lib/utils/isValidHttpUrl"; -import { clipboard, NavigationNative } from "@metro/common"; -import { ActionSheet, AlertActions, AlertModal, Button, Card, FlashList, IconButton, Stack, TableRow, TableRowGroup, Text, TextInput } from "@metro/common/components"; -import { QueryClient, QueryClientProvider, useMutation, useQuery } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; +import { React, NavigationNative } from "@metro/common"; import { View } from "react-native"; +import { Stack, Button, IconButton, Text, Card, FlashList, SegmentedControl, useSegmentedControlState } from "@metro/common/components"; +import { findAssetId } from "@lib/api/assets"; +import safeFetch from "@lib/utils/safeFetch"; +import { showToast } from "@ui/toasts"; +import Search from "@ui/components/Search"; +import { VdPluginManager } from "@core/vendetta/plugins"; +import { getCurrentTheme, installTheme, themes, fetchTheme, removeTheme, selectTheme } from "@lib/addons/themes"; +import { clipboard } from "@metro/common"; +import { hideSheet, showSheet } from "@lib/ui/sheets"; +import { AlertActionButton } from "@lib/ui/components/wrappers"; +import { dismissAlert, openAlert } from "@lib/ui/alerts"; +import { ActionSheet, AlertModal, AlertActions, TableRow, TableRowGroup } from "@metro/common/components"; +import { lazyDestructure } from "@lib/utils/lazy"; +import { findByProps } from "@metro"; -const queryClient = new QueryClient(); +const { showSimpleActionSheet, hideActionSheet } = lazyDestructure(() => findByProps("showSimpleActionSheet")); -async function getManifests() { - await updateAllRepository(); - const plugins = [...registeredPlugins.values()]; - return plugins.filter(p => !isCorePlugin(p.id)); +interface BaseAddonData { + name: string; + description: string; + authors: string[]; + installUrl: string; } -function InstallButton(props: { id: string; }) { - const [installed, setInstalled] = useState(isPluginInstalled(props.id)); - const installationState = useMutation({ - mutationFn: async ({ install }) => { - await (install ? installPlugin : uninstallPlugin)(props.id, true); - }, - onSettled() { - setInstalled(isPluginInstalled(props.id)); - }, - onError(error) { - showToast(error instanceof Error ? error.message : String(error)); +interface PluginData extends BaseAddonData { + status: "working" | "broken" | "warning" | string; + sourceUrl: string; + warningMessage?: string; +} + +interface ThemeData extends BaseAddonData {} + +type AddonData = PluginData | ThemeData; + +const PLUGIN_URL = "https://raw.githubusercontent.com/Purple-EyeZ/Plugins-List/refs/heads/main/src/plugins-data.json"; +const THEME_URL = "https://raw.githubusercontent.com/kmmiio99o/theme-marketplace/refs/heads/main/themes.json"; + +function normalizeIdFromInstallUrl(url: string) { + return url.endsWith("/") ? url : url + "/"; +} + +// @ts-ignore (i cant be bothered to type these) +function InstallButton({ addon, isPluginMode, installing, setInstalling, setRefreshTick }) { + const normId = normalizeIdFromInstallUrl(addon.installUrl); + const [installed, setInstalled] = React.useState(() => + isPluginMode ? Boolean(VdPluginManager.plugins[normId]) : Boolean(themes[addon.installUrl]) + ); + + React.useEffect(() => { + setInstalled(isPluginMode ? Boolean(VdPluginManager.plugins[normId]) : Boolean(themes[addon.installUrl])); + }, [addon.installUrl, setRefreshTick, isPluginMode]); + + const installAddon = async () => { + if (installing.has(normId)) return; + setInstalling((prev: Iterable | null | undefined) => new Set(prev).add(normId)); + try { + if (isPluginMode) { + await VdPluginManager.installPlugin(normId, true); + } else { + await installTheme(addon.installUrl); + } + showToast(`Installed ${addon.name}`, findAssetId("CheckIcon")); + setInstalled(true); + } catch (e) { + showToast(e instanceof Error ? e.message : String(e), findAssetId("CircleXIcon-primary")); + } finally { + setInstalling((prev: Iterable | null | undefined) => { const s = new Set(prev); s.delete(normId); return s; }); + setRefreshTick((t: number) => t + 1); } - }); + }; - return