vd-plugins/plugins/Commands/sidebar.tsx

236 lines
7.7 KiB
TypeScript
Raw Normal View History

import { storage } from "@vendetta/plugin";
import { React, NavigationNative } from "@vendetta/metro/common";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { after } from "@vendetta/patcher";
import { Forms } from "@vendetta/ui/components";
import { findInReactTree } from "@vendetta/utils";
import { findByProps } from "@vendetta/metro";
import { logger } from "@vendetta";
import Settings from "./settings/settings";
const { FormSection, FormRow } = Forms;
const { TableRowIcon } = findByProps("TableRowIcon");
const bunny = window.bunny;
declare global {
interface Window {
bunny: any;
}
}
const tabsNavigationRef = bunny?.metro?.findByPropsLazy("getRootNavigationRef");
const settingConstants = bunny?.metro?.findByPropsLazy(
2026-05-09 10:20:55 +02:00
"SETTING_RENDERER_CONFIG",
);
const createListModule = bunny?.metro?.findByPropsLazy("createList");
const SettingsOverviewScreen = bunny?.metro?.findByNameLazy(
2026-05-09 10:20:55 +02:00
"SettingsOverviewScreen",
false,
);
function Section({ tabs }) {
2026-05-09 10:20:55 +02:00
const navigation = NavigationNative.useNavigation();
return React.createElement(FormRow, {
label: tabs.title(),
leading: React.createElement(FormRow.Icon, { source: tabs.icon }),
trailing: React.createElement(React.Fragment, {}, [
tabs.trailing ? tabs.trailing() : null,
React.createElement(FormRow.Arrow, { key: "arrow" }),
]),
onPress: () => {
const Component = tabs.page;
navigation.navigate("VendettaCustomPage", {
title: tabs.title(),
render: () => React.createElement(Component),
});
},
});
}
function patchPanelUI(tabs, patches) {
2026-05-09 10:20:55 +02:00
try {
patches.push(
after(
"default",
bunny?.metro?.findByPropsLazy(["renderTitle", "sections"], false),
(_, ret) => {
const UserSettingsOverview = findInReactTree(
ret.props.children,
(n) => n.type?.name === "UserSettingsOverview",
);
2026-05-09 10:20:55 +02:00
if (UserSettingsOverview) {
patches.push(
after(
"render",
UserSettingsOverview.type.prototype,
(_args, res) => {
const sections = findInReactTree(
res.props.children,
(n) => n?.children?.[1]?.type === FormSection,
)?.children;
if (sections) {
const index = sections.findIndex((c) =>
["BILLING_SETTINGS", "PREMIUM_SETTINGS"].includes(
c?.props?.label,
),
);
sections.splice(
-~index || 4,
0,
React.createElement(Section, { key: tabs.key, tabs }),
);
}
},
),
);
}
},
2026-05-09 10:20:55 +02:00
true,
),
);
} catch (error) {
logger.info("Panel UI patch failed graciously 💔", error);
}
}
function patchTabsUI(tabs, patches) {
2026-05-09 10:20:55 +02:00
if (!settingConstants || !tabsNavigationRef) {
console.warn(
"[Commands Plugin] Missing required constants for tabs UI patch",
);
return;
}
2026-05-09 10:20:55 +02:00
const row = {
[tabs.key]: {
type: "pressable",
title: tabs.title,
icon: tabs.icon,
IconComponent:
tabs.icon &&
(() => React.createElement(TableRowIcon, { source: tabs.icon })),
2026-05-09 10:20:55 +02:00
usePredicate: tabs.predicate,
useTrailing: tabs.trailing,
onPress: () => {
const navigation = tabsNavigationRef.getRootNavigationRef();
const Component = tabs.page;
navigation.navigate("VendettaCustomPage", {
title: tabs.title(),
render: () => React.createElement(Component),
});
},
withArrow: true,
},
};
let rendererConfigValue = settingConstants.SETTING_RENDERER_CONFIG;
Object.defineProperty(settingConstants, "SETTING_RENDERER_CONFIG", {
enumerable: true,
configurable: true,
get: () => ({
...rendererConfigValue,
...row,
}),
set: (v) => (rendererConfigValue = v),
});
2026-05-09 10:20:55 +02:00
const firstRender = Symbol("pinToSettings meow meow");
2026-05-09 10:20:55 +02:00
try {
if (!createListModule) return;
patches.push(
after("createList", createListModule, function (args, ret) {
if (!args[0][firstRender]) {
args[0][firstRender] = true;
const [config] = args;
const sections = config.sections;
const section = sections?.find((x: any) =>
["Bunny", "Revenge", "Kettu", "Vencore", "ShiggyCord"].some(
(mod) => x.label === mod && x.title === mod,
),
);
if (section?.settings) {
section.settings = [...section.settings, tabs.key];
}
}
}),
);
} catch {
if (!SettingsOverviewScreen) return;
patches.push(
after("default", SettingsOverviewScreen, (args, ret) => {
if (!args[0][firstRender]) {
args[0][firstRender] = true;
const { sections } = findInReactTree(
ret,
(i) => i.props?.sections,
).props;
const section = sections?.find((x: any) =>
["Bunny", "Revenge", "Kettu", "Vencore", "ShiggyCord"].some(
(mod) => x.label === mod && x.title === mod,
),
);
if (section?.settings) {
section.settings = [...section.settings, tabs.key];
}
}
}),
);
}
}
function patchSettingsPin(tabs) {
2026-05-09 10:20:55 +02:00
const patches = [];
2026-05-09 10:20:55 +02:00
let disabled = false;
2026-05-09 10:20:55 +02:00
const realPredicate = tabs.predicate || (() => true);
tabs.predicate = () => (disabled ? false : realPredicate());
2026-05-09 10:20:55 +02:00
patchPanelUI(tabs, patches);
patchTabsUI(tabs, patches);
patches.push(() => (disabled = true));
2026-05-09 10:20:55 +02:00
return () => {
for (const x of patches) {
x();
}
};
}
export default function patchSidebar() {
2026-05-09 10:20:55 +02:00
// Check if sidebar is enabled in storage
if (storage.sidebarEnabled === false) {
console.log("[Commands Plugin] Sidebar disabled in settings");
return () => {};
}
2026-05-09 10:20:55 +02:00
console.log("[Commands Plugin] Patching sidebar...");
2026-05-09 10:20:55 +02:00
try {
const unpatch = patchSettingsPin({
key: "CommandsPlugin",
icon: getAssetIDByName("SettingsIcon"),
title: () => "Commands",
predicate: () => storage.sidebarEnabled !== false,
page: Settings,
});
2026-05-09 10:20:55 +02:00
console.log("[Commands Plugin] Successfully patched sidebar");
return unpatch;
} catch (error) {
console.error("[Commands Plugin] Failed to patch sidebar:", error);
return () => {};
}
}