vd-plugins/plugins/Commands/settings.tsx

1008 lines
33 KiB
TypeScript
Raw Normal View History

import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
2025-10-01 16:18:11 +02:00
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
2025-10-01 16:28:57 +02:00
import { NavigationNative } from "@vendetta/metro/common";
2025-10-01 16:18:11 +02:00
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import { alerts } from "@vendetta/ui";
2025-10-01 21:10:41 +02:00
import { showToast } from "@vendetta/ui/toasts";
2025-10-01 20:22:26 +02:00
const { ScrollView } = RN;
2025-10-01 16:24:21 +02:00
const { FormRow, FormSwitchRow } = Forms;
2025-10-01 20:22:26 +02:00
// Initialize storage with default values - MOVED TO TOP
storage.factSettings ??= {
sendAsReply: true,
includeCitation: false,
};
storage.listSettings ??= {
pluginListAlwaysDetailed: false,
themeListAlwaysDetailed: false,
};
storage.garySettings ??= {
imageSource: "gary",
};
storage.enabledCommands ??= {
catfact: true,
dogfact: true,
useless: true,
petpet: true,
pluginList: true,
themeList: true,
konoself: true,
konosend: true,
firstmessage: true,
sysinfo: true,
spotifyTrack: true,
spotifyAlbum: true,
spotifyArtists: true,
spotifyCover: true,
gary: true,
2025-10-01 21:10:41 +02:00
lovefemboys: false, // Hidden command - disabled by default
2025-10-01 20:22:26 +02:00
};
storage.pendingRestart ??= false;
2025-10-01 21:10:41 +02:00
// Hidden settings storage
storage.hiddenSettings ??= {
enabled: false,
visible: false,
};
2025-10-01 16:36:23 +02:00
// Better Table Row Group Component
2025-10-01 16:24:21 +02:00
function BetterTableRowGroup({
title,
icon,
children,
padding = false,
2025-10-01 21:10:41 +02:00
onTitlePress,
2025-10-01 16:24:21 +02:00
}: React.PropsWithChildren<{
title?: string;
icon?: number;
padding?: boolean;
2025-10-01 21:10:41 +02:00
onTitlePress?: () => void;
2025-10-01 16:24:21 +02:00
}>) {
const styles = stylesheet.createThemedStyleSheet({
main: {
backgroundColor: semanticColors.CARD_PRIMARY_BG,
borderColor: semanticColors.BORDER_FAINT,
borderWidth: 1,
borderRadius: 16,
overflow: "hidden",
flex: 1,
},
titleContainer: {
marginBottom: 8,
marginHorizontal: 0,
marginTop: 8,
gap: 4,
flexDirection: "row",
alignItems: "center",
},
2025-10-01 21:10:41 +02:00
titlePressable: {
flexDirection: "row",
alignItems: "center",
gap: 4,
},
2025-10-01 16:24:21 +02:00
icon: {
width: 16,
height: 16,
marginTop: 1.5,
tintColor: semanticColors.TEXT_MUTED,
},
titleText: {
fontSize: 14,
fontWeight: "600",
color: semanticColors.TEXT_MUTED,
},
});
return (
<RN.View style={{ marginHorizontal: 16, marginTop: 16 }}>
{title && (
<RN.View style={styles.titleContainer}>
2025-10-01 21:10:41 +02:00
{onTitlePress ? (
<RN.Pressable style={styles.titlePressable} onPress={onTitlePress}>
{icon && (
<RN.Image
style={styles.icon}
source={icon}
resizeMode="cover"
/>
)}
<RN.Text style={styles.titleText}>
{title.toUpperCase()}
</RN.Text>
</RN.Pressable>
) : (
<>
{icon && (
<RN.Image
style={styles.icon}
source={icon}
resizeMode="cover"
/>
)}
<RN.Text style={styles.titleText}>
{title.toUpperCase()}
</RN.Text>
</>
2025-10-01 16:24:21 +02:00
)}
</RN.View>
)}
<RN.View style={styles.main}>
{padding ? (
<RN.View style={{ paddingHorizontal: 16, paddingVertical: 16 }}>
{children}
</RN.View>
) : children}
</RN.View>
</RN.View>
);
}
2025-10-01 21:10:41 +02:00
// Header Component with SILENT secret unlock
2025-10-01 16:18:11 +02:00
function Header() {
2025-10-01 21:10:41 +02:00
const [clickCounter, setClickCounter] = React.useState(0);
const [clickTimeout, setClickTimeout] = React.useState<NodeJS.Timeout | null>(null);
2025-10-01 16:18:11 +02:00
const styles = stylesheet.createThemedStyleSheet({
container: {
flexDirection: "column",
alignItems: "center",
paddingVertical: 24,
paddingHorizontal: 16,
},
title: {
fontSize: 28,
fontWeight: "700",
color: semanticColors.TEXT_NORMAL,
marginBottom: 4,
},
subtitle: {
fontSize: 16,
fontWeight: "500",
color: semanticColors.TEXT_MUTED,
},
iconContainer: {
backgroundColor: semanticColors.BACKGROUND_SECONDARY,
borderRadius: 16,
padding: 12,
marginBottom: 16,
},
icon: {
width: 32,
height: 32,
tintColor: semanticColors.TEXT_NORMAL,
},
});
2025-10-01 21:10:41 +02:00
const handleIconPress = () => {
if (storage.hiddenSettings.enabled) {
storage.hiddenSettings.visible = !storage.hiddenSettings.visible;
showToast(
`Hidden settings ${storage.hiddenSettings.visible ? "visible" : "hidden"}`,
getAssetIDByName("SettingsIcon")
);
return;
}
if (clickTimeout) {
clearTimeout(clickTimeout);
}
const newTimeout = setTimeout(() => {
setClickCounter(0);
}, 1000);
setClickTimeout(newTimeout);
const newCounter = clickCounter + 1;
setClickCounter(newCounter);
// Silent counting - no progress notifications
if (newCounter < 10) {
return; // Just count silently
}
// Unlocked! Only show this notification
showToast("🔓 Hidden settings unlocked!", getAssetIDByName("CheckmarkIcon"));
storage.hiddenSettings.enabled = true;
storage.hiddenSettings.visible = true;
setClickCounter(0);
};
2025-10-01 16:18:11 +02:00
return (
<RN.View style={styles.container}>
2025-10-01 21:10:41 +02:00
<RN.Pressable style={styles.iconContainer} onPress={handleIconPress}>
2025-10-01 16:18:11 +02:00
<RN.Image
source={getAssetIDByName("SettingsIcon")}
style={styles.icon}
/>
2025-10-01 21:10:41 +02:00
</RN.Pressable>
2025-10-01 16:18:11 +02:00
<RN.Text style={styles.title}>Commands</RN.Text>
<RN.Text style={styles.subtitle}>A collection of useful commands</RN.Text>
</RN.View>
);
}
2025-10-01 21:10:41 +02:00
// Hidden Commands Settings Page
function HiddenSettingsPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
warningText: {
fontSize: 14,
color: semanticColors.TEXT_DANGER,
textAlign: "center",
lineHeight: 20,
fontWeight: "600",
},
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
textAlign: "center",
lineHeight: 20,
marginTop: 8,
},
});
return (
<ScrollView style={styles.container}>
<RN.View style={{ paddingVertical: 16 }}>
<BetterTableRowGroup title="⚠️ Warning" icon={getAssetIDByName("WarningIcon")} padding={true}>
<RN.Text style={styles.warningText}>
These are hidden commands that may contain mature content or experimental features.
</RN.Text>
<RN.Text style={styles.infoText}>
Use at your own discretion. Commands in this section are disabled by default.
</RN.Text>
</BetterTableRowGroup>
<BetterTableRowGroup title="Hidden Commands" icon={getAssetIDByName("EyeIcon")}>
<FormSwitchRow
label="/lovefemboys"
subLabel="Get random femboy images from r/femboys (NSFW content available)"
leading={<FormRow.Icon source={getAssetIDByName("HeartIcon")} />}
value={storage.enabledCommands.lovefemboys}
onValueChange={(v) => {
storage.enabledCommands.lovefemboys = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
<BetterTableRowGroup title="Hidden Settings Control" icon={getAssetIDByName("SettingsIcon")}>
<FormSwitchRow
label="Keep Hidden Settings Visible"
subLabel="Keep this section visible even when navigating away"
leading={<FormRow.Icon source={getAssetIDByName("EyeIcon")} />}
value={storage.hiddenSettings.visible}
onValueChange={(v) => {
storage.hiddenSettings.visible = v;
}}
/>
<FormRow
label="Reset Hidden Settings"
subLabel="Hide this section and disable all hidden commands"
leading={<FormRow.Icon source={getAssetIDByName("TrashIcon")} />}
onPress={() => {
alerts.showConfirmationAlert({
title: "Reset Hidden Settings",
content: "This will hide the hidden settings section and disable all hidden commands. Are you sure?",
confirmText: "Reset",
onConfirm: () => {
storage.hiddenSettings.enabled = false;
storage.hiddenSettings.visible = false;
storage.enabledCommands.lovefemboys = false;
storage.pendingRestart = true;
showToast("Hidden settings reset", getAssetIDByName("CheckmarkIcon"));
},
cancelText: "Cancel",
});
}}
/>
</BetterTableRowGroup>
</RN.View>
</ScrollView>
);
}
2025-10-01 20:30:08 +02:00
// Facts Commands Settings Page - FIXED with local force update
2025-10-01 19:42:47 +02:00
function FactsSettingsPage() {
useProxy(storage);
2025-10-01 20:30:08 +02:00
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
2025-10-01 19:42:47 +02:00
2025-10-01 16:28:57 +02:00
return (
2025-10-01 20:22:26 +02:00
<ScrollView style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}>
<RN.View style={{ paddingVertical: 16 }}>
2025-10-01 16:36:23 +02:00
<BetterTableRowGroup title="Fact Display Settings" icon={getAssetIDByName("SettingsIcon")}>
<FormSwitchRow
label="Send as Reply"
subLabel="Send facts as a reply to the command message"
2025-10-01 17:20:35 +02:00
leading={<FormRow.Icon source={getAssetIDByName("ArrowAngleLeftUpIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.factSettings.sendAsReply}
onValueChange={(v) => {
storage.factSettings.sendAsReply = v;
}}
2025-10-01 16:36:23 +02:00
/>
<FormSwitchRow
label="Include Source Citation"
subLabel="Include the source of facts when available"
leading={<FormRow.Icon source={getAssetIDByName("LinkIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.factSettings.includeCitation}
onValueChange={(v) => {
storage.factSettings.includeCitation = v;
}}
2025-10-01 16:36:23 +02:00
/>
2025-10-01 16:28:57 +02:00
</BetterTableRowGroup>
2025-10-01 16:56:41 +02:00
<BetterTableRowGroup title="Available Fact Commands" icon={getAssetIDByName("BookmarkIcon")}>
2025-10-01 16:36:23 +02:00
<FormSwitchRow
label="/catfact"
subLabel="Get random cat facts"
2025-10-01 17:20:35 +02:00
leading={<FormRow.Icon source={getAssetIDByName("BookCheckIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.catfact}
onValueChange={(v) => {
storage.enabledCommands.catfact = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:36:23 +02:00
/>
<FormSwitchRow
label="/dogfact"
subLabel="Get random dog facts"
2025-10-01 17:20:35 +02:00
leading={<FormRow.Icon source={getAssetIDByName("BookCheckIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.dogfact}
onValueChange={(v) => {
storage.enabledCommands.dogfact = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:36:23 +02:00
/>
<FormSwitchRow
label="/useless"
subLabel="Get random useless facts"
2025-10-01 17:20:35 +02:00
leading={<FormRow.Icon source={getAssetIDByName("BookCheckIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.useless}
onValueChange={(v) => {
storage.enabledCommands.useless = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:36:23 +02:00
/>
2025-10-01 16:28:57 +02:00
</BetterTableRowGroup>
2025-10-01 20:22:26 +02:00
</RN.View>
</ScrollView>
2025-10-01 16:28:57 +02:00
);
}
2025-10-01 20:30:08 +02:00
// Gary API Settings Page - FIXED with local force update
2025-10-01 19:42:47 +02:00
function GaryAPIPage() {
useProxy(storage);
2025-10-01 20:30:08 +02:00
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
2025-10-01 16:47:00 +02:00
2025-10-01 16:28:57 +02:00
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
2025-10-01 16:36:23 +02:00
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
marginBottom: 16,
textAlign: "center",
},
currentText: {
fontSize: 14,
color: semanticColors.TEXT_NORMAL,
textAlign: "center",
fontWeight: "600",
},
2025-10-01 16:28:57 +02:00
});
2025-10-01 20:22:26 +02:00
const currentSource = storage.garySettings.imageSource;
2025-10-01 17:36:45 +02:00
2025-10-01 16:28:57 +02:00
return (
2025-10-01 20:22:26 +02:00
<ScrollView style={styles.container}>
<RN.View style={{ paddingVertical: 16 }}>
2025-10-01 16:36:23 +02:00
<BetterTableRowGroup title="Gary Command Settings" icon={getAssetIDByName("SettingsIcon")}>
<FormSwitchRow
label="/gary"
subLabel="Send random Gary images to channel"
2025-10-01 17:20:35 +02:00
leading={<FormRow.Icon source={getAssetIDByName("AttachmentIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.gary}
onValueChange={(v) => {
storage.enabledCommands.gary = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:36:23 +02:00
/>
</BetterTableRowGroup>
<BetterTableRowGroup title="Image Source Selection" icon={getAssetIDByName("DownloadIcon")} padding={true}>
<RN.Text style={styles.infoText}>
2025-10-01 16:28:57 +02:00
Choose which API the /gary command should use to fetch images.
</RN.Text>
</BetterTableRowGroup>
2025-10-01 16:56:41 +02:00
<BetterTableRowGroup title="API Options" icon={getAssetIDByName("CloudIcon")}>
2025-10-01 20:22:26 +02:00
<FormSwitchRow
label="Gary API"
subLabel="Original Gary the cat images from api.garythe.cat"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={currentSource === "gary"}
onValueChange={(v) => {
2025-10-01 20:30:08 +02:00
if (v) {
storage.garySettings.imageSource = "gary";
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 20:30:08 +02:00
}
2025-10-01 20:22:26 +02:00
}}
/>
<FormSwitchRow
label="Cat API"
subLabel="Random cat pictures from thecatapi.com"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={currentSource === "catapi"}
onValueChange={(v) => {
2025-10-01 20:30:08 +02:00
if (v) {
storage.garySettings.imageSource = "catapi";
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 20:30:08 +02:00
}
2025-10-01 20:22:26 +02:00
}}
/>
<FormSwitchRow
label="Minker API"
subLabel="Minky images from minky.materii.dev"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={currentSource === "minker"}
onValueChange={(v) => {
2025-10-01 20:30:08 +02:00
if (v) {
storage.garySettings.imageSource = "minker";
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 20:30:08 +02:00
}
2025-10-01 20:22:26 +02:00
}}
/>
<FormSwitchRow
label="Goober API"
subLabel="Goober images from api.garythe.cat/goober"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={currentSource === "goober"}
onValueChange={(v) => {
2025-10-01 20:30:08 +02:00
if (v) {
storage.garySettings.imageSource = "goober";
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 20:30:08 +02:00
}
2025-10-01 20:22:26 +02:00
}}
/>
2025-10-01 16:56:41 +02:00
</BetterTableRowGroup>
2025-10-01 16:28:57 +02:00
2025-10-01 16:36:23 +02:00
<BetterTableRowGroup title="Current Selection" icon={getAssetIDByName("CheckmarkIcon")} padding={true}>
<RN.Text style={styles.currentText}>
2025-10-01 16:28:57 +02:00
Currently using: {
currentSource === "gary" ? "Gary API" :
currentSource === "catapi" ? "Cat API" :
currentSource === "minker" ? "Minker API" :
currentSource === "goober" ? "Goober API" : "Gary API"
}
</RN.Text>
</BetterTableRowGroup>
2025-10-01 20:22:26 +02:00
</RN.View>
</ScrollView>
2025-10-01 16:28:57 +02:00
);
}
2025-10-01 20:30:08 +02:00
// List Settings Page - FIXED with local force update
2025-10-01 19:42:47 +02:00
function ListSettingsPage() {
useProxy(storage);
2025-10-01 20:30:08 +02:00
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
2025-10-01 19:34:55 +02:00
return (
2025-10-01 20:22:26 +02:00
<ScrollView style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}>
<RN.View style={{ paddingVertical: 16 }}>
2025-10-01 16:36:23 +02:00
<BetterTableRowGroup title="List Display Settings" icon={getAssetIDByName("SettingsIcon")}>
2025-10-01 16:24:21 +02:00
<FormSwitchRow
label="Always Send Detailed Plugin List"
subLabel="Always use detailed mode when listing plugins"
leading={<FormRow.Icon source={getAssetIDByName("PuzzlePieceIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.listSettings.pluginListAlwaysDetailed}
onValueChange={(v) => {
storage.listSettings.pluginListAlwaysDetailed = v;
}}
2025-10-01 16:24:21 +02:00
/>
<FormSwitchRow
label="Always Send Detailed Theme List"
subLabel="Always use detailed mode when listing themes"
leading={<FormRow.Icon source={getAssetIDByName("PaintPaletteIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.listSettings.themeListAlwaysDetailed}
onValueChange={(v) => {
storage.listSettings.themeListAlwaysDetailed = v;
}}
2025-10-01 16:24:21 +02:00
/>
2025-10-01 16:36:23 +02:00
</BetterTableRowGroup>
2025-10-01 16:56:41 +02:00
<BetterTableRowGroup title="Available List Commands" icon={getAssetIDByName("ListViewIcon")}>
2025-10-01 16:24:21 +02:00
<FormSwitchRow
label="/plugin-list"
subLabel="List all installed plugins"
leading={<FormRow.Icon source={getAssetIDByName("PuzzlePieceIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.pluginList}
onValueChange={(v) => {
storage.enabledCommands.pluginList = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:24:21 +02:00
/>
<FormSwitchRow
label="/theme-list"
subLabel="List all installed themes"
leading={<FormRow.Icon source={getAssetIDByName("PaintPaletteIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.themeList}
onValueChange={(v) => {
storage.enabledCommands.themeList = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:24:21 +02:00
/>
</BetterTableRowGroup>
2025-10-01 20:22:26 +02:00
</RN.View>
</ScrollView>
2025-10-01 16:36:23 +02:00
);
}
2025-10-01 20:30:08 +02:00
// Image Settings Page - FIXED with local force update
2025-10-01 19:42:47 +02:00
function ImageSettingsPage() {
useProxy(storage);
2025-10-01 20:30:08 +02:00
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
2025-10-01 19:34:55 +02:00
2025-10-01 16:36:23 +02:00
return (
2025-10-01 20:22:26 +02:00
<ScrollView style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}>
<RN.View style={{ paddingVertical: 16 }}>
2025-10-01 19:34:55 +02:00
<BetterTableRowGroup title="Image Commands" icon={getAssetIDByName("ImageIcon")}>
2025-10-01 16:24:21 +02:00
<FormSwitchRow
label="/petpet"
subLabel="Create pet-pet GIF of a user"
2025-10-01 17:20:35 +02:00
leading={<FormRow.Icon source={getAssetIDByName("HandRequestSpeakIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.petpet}
onValueChange={(v) => {
storage.enabledCommands.petpet = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:24:21 +02:00
/>
</BetterTableRowGroup>
2025-10-01 16:36:23 +02:00
<BetterTableRowGroup title="KonoChan Commands" icon={getAssetIDByName("ImageIcon")}>
2025-10-01 16:24:21 +02:00
<FormSwitchRow
label="/konoself"
subLabel="Get random image from KonoChan (private)"
leading={<FormRow.Icon source={getAssetIDByName("EyeIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.konoself}
onValueChange={(v) => {
storage.enabledCommands.konoself = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:24:21 +02:00
/>
<FormSwitchRow
label="/konosend"
subLabel="Send random image from KonoChan to channel"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.konosend}
onValueChange={(v) => {
storage.enabledCommands.konosend = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:24:21 +02:00
/>
</BetterTableRowGroup>
2025-10-01 20:22:26 +02:00
</RN.View>
</ScrollView>
2025-10-01 16:36:23 +02:00
);
}
2025-10-01 20:30:08 +02:00
// Spotify Settings Page - FIXED with local force update
2025-10-01 19:42:47 +02:00
function SpotifySettingsPage() {
useProxy(storage);
2025-10-01 20:30:08 +02:00
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
2025-10-01 19:34:55 +02:00
2025-10-01 16:36:23 +02:00
return (
2025-10-01 20:22:26 +02:00
<ScrollView style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}>
<RN.View style={{ paddingVertical: 16 }}>
2025-10-01 16:56:41 +02:00
<BetterTableRowGroup title="Spotify Commands" icon={getAssetIDByName("SpotifyNeutralIcon")}>
2025-10-01 16:24:21 +02:00
<FormSwitchRow
label="/spotify track"
subLabel="Share your current Spotify track"
2025-10-01 16:56:41 +02:00
leading={<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.spotifyTrack}
onValueChange={(v) => {
storage.enabledCommands.spotifyTrack = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:24:21 +02:00
/>
<FormSwitchRow
label="/spotify album"
subLabel="Share your current track's album"
2025-10-01 16:56:41 +02:00
leading={<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.spotifyAlbum}
onValueChange={(v) => {
storage.enabledCommands.spotifyAlbum = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:24:21 +02:00
/>
<FormSwitchRow
label="/spotify artists"
subLabel="Share your current track's artists"
2025-10-01 16:56:41 +02:00
leading={<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.spotifyArtists}
onValueChange={(v) => {
storage.enabledCommands.spotifyArtists = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:24:21 +02:00
/>
<FormSwitchRow
label="/spotify cover"
subLabel="Share your current track's cover"
2025-10-01 16:56:41 +02:00
leading={<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.spotifyCover}
onValueChange={(v) => {
storage.enabledCommands.spotifyCover = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:24:21 +02:00
/>
</BetterTableRowGroup>
2025-10-01 16:47:00 +02:00
<BetterTableRowGroup title="About Spotify Commands" icon={getAssetIDByName("InfoIcon")} padding={true}>
2025-10-01 20:22:26 +02:00
<RN.Text style={{
fontSize: 14,
color: semanticColors.TEXT_MUTED,
textAlign: "center",
lineHeight: 20,
}}>
2025-10-01 16:47:00 +02:00
These commands allow you to share your current Spotify activity in Discord. Make sure you have Spotify connected to Discord for these commands to work properly.
</RN.Text>
</BetterTableRowGroup>
2025-10-01 20:22:26 +02:00
</RN.View>
</ScrollView>
2025-10-01 16:47:00 +02:00
);
}
2025-10-01 20:30:08 +02:00
// Other Settings Page - FIXED with local force update
2025-10-01 19:42:47 +02:00
function OtherSettingsPage() {
useProxy(storage);
2025-10-01 20:30:08 +02:00
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
2025-10-01 19:34:55 +02:00
2025-10-01 16:47:00 +02:00
return (
2025-10-01 20:22:26 +02:00
<ScrollView style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}>
<RN.View style={{ paddingVertical: 16 }}>
2025-10-01 16:47:00 +02:00
<BetterTableRowGroup title="Message Commands" icon={getAssetIDByName("ChatIcon")}>
<FormSwitchRow
label="/firstmessage"
subLabel="Get the first message in a channel"
leading={<FormRow.Icon source={getAssetIDByName("ChatIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.firstmessage}
onValueChange={(v) => {
storage.enabledCommands.firstmessage = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:47:00 +02:00
/>
</BetterTableRowGroup>
<BetterTableRowGroup title="System Commands" icon={getAssetIDByName("SettingsIcon")}>
<FormSwitchRow
label="/sysinfo"
subLabel="Display system information"
leading={<FormRow.Icon source={getAssetIDByName("SettingsIcon")} />}
2025-10-01 20:22:26 +02:00
value={storage.enabledCommands.sysinfo}
onValueChange={(v) => {
storage.enabledCommands.sysinfo = v;
2025-10-01 19:42:47 +02:00
storage.pendingRestart = true;
2025-10-01 21:10:41 +02:00
forceUpdate();
2025-10-01 19:42:47 +02:00
}}
2025-10-01 16:47:00 +02:00
/>
</BetterTableRowGroup>
2025-10-01 20:22:26 +02:00
</RN.View>
</ScrollView>
2025-10-01 16:36:23 +02:00
);
}
2025-10-01 20:22:26 +02:00
// Credits Page
2025-10-01 16:36:23 +02:00
function CreditsPage() {
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
creditItem: {
flexDirection: "row",
alignItems: "center",
2025-10-01 17:36:45 +02:00
paddingVertical: 16,
paddingHorizontal: 20,
2025-10-01 16:36:23 +02:00
backgroundColor: semanticColors.CARD_PRIMARY_BG,
borderRadius: 12,
marginHorizontal: 16,
2025-10-01 17:36:45 +02:00
marginVertical: 6,
shadowColor: semanticColors.BLACK,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2,
2025-10-01 16:36:23 +02:00
},
avatar: {
2025-10-01 17:36:45 +02:00
width: 48,
height: 48,
borderRadius: 24,
marginRight: 16,
backgroundColor: semanticColors.BACKGROUND_SECONDARY,
2025-10-01 16:36:23 +02:00
},
textContainer: {
flex: 1,
},
commandText: {
2025-10-01 17:36:45 +02:00
fontSize: 17,
2025-10-01 16:36:23 +02:00
fontWeight: "600",
color: semanticColors.TEXT_NORMAL,
2025-10-01 17:36:45 +02:00
marginBottom: 4,
2025-10-01 16:36:23 +02:00
},
authorText: {
2025-10-01 17:36:45 +02:00
fontSize: 15,
2025-10-01 16:36:23 +02:00
color: semanticColors.TEXT_MUTED,
2025-10-01 17:36:45 +02:00
marginBottom: 2,
},
linkText: {
fontSize: 13,
color: semanticColors.TEXT_BRAND,
fontWeight: "500",
2025-10-01 16:36:23 +02:00
},
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
2025-10-01 17:36:45 +02:00
marginBottom: 20,
2025-10-01 16:36:23 +02:00
textAlign: "center",
2025-10-01 17:36:45 +02:00
lineHeight: 20,
2025-10-01 16:36:23 +02:00
},
versionText: {
2025-10-01 17:36:45 +02:00
fontSize: 15,
2025-10-01 16:36:23 +02:00
color: semanticColors.TEXT_NORMAL,
textAlign: "center",
fontWeight: "600",
2025-10-01 17:36:45 +02:00
lineHeight: 22,
2025-10-01 16:36:23 +02:00
},
});
const credits = [
2025-10-01 17:36:45 +02:00
{
command: "Facts Commands",
author: "jdev082",
avatar: "https://github.com/jdev082.png",
github: "https://github.com/jdev082"
},
{
command: "List Commands",
author: "Kitomanari",
avatar: "https://github.com/Kitosight.png",
github: "https://github.com/Kitosight"
},
{
2025-10-01 19:42:47 +02:00
command: "PetPet",
2025-10-01 17:36:45 +02:00
author: "wolfieeee",
avatar: "https://github.com/WolfPlugs.png",
github: "https://github.com/WolfPlugs"
},
{
command: "KonoChan Commands",
author: "btmc727 & Rico040",
avatar: "https://github.com/OTKUSteyler.png",
github: "https://github.com/OTKUSteyler"
},
{
command: "FirstMessage Command",
author: "sapphire",
avatar: "https://github.com/aeongdesu.png",
github: "https://github.com/aeongdesu"
},
{
command: "Sysinfo Command",
author: "mugman",
avatar: "https://github.com/mugman174.png",
github: "https://github.com/mugman174"
},
{
command: "Spotify Commands",
author: "Kitomanari",
avatar: "https://github.com/Kitosight.png",
github: "https://github.com/Kitosight"
},
{
command: "Gary Command",
author: "Zach Orange",
avatar: "https://github.com/Zach11111.png",
github: "https://github.com/Zach11111"
},
2025-10-01 16:36:23 +02:00
];
2025-10-01 17:36:45 +02:00
const handleProfilePress = (githubUrl: string) => {
RN.Linking.openURL(githubUrl);
};
2025-10-01 16:36:23 +02:00
return (
2025-10-01 20:22:26 +02:00
<ScrollView style={styles.container}>
<RN.View style={{ paddingVertical: 16 }}>
2025-10-01 17:20:35 +02:00
<BetterTableRowGroup title="Plugin Authors" icon={getAssetIDByName("HeartIcon")} padding={true}>
2025-10-01 16:36:23 +02:00
<RN.Text style={styles.infoText}>
2025-10-01 19:42:47 +02:00
Thanks to this developers for creating such a nice plugins!{'\n'}
2025-10-01 19:48:03 +02:00
Tap on any developer to visit their GitHub profile.
2025-10-01 16:36:23 +02:00
</RN.Text>
</BetterTableRowGroup>
2025-10-01 16:36:23 +02:00
{credits.map((credit, index) => (
2025-10-01 17:36:45 +02:00
<RN.Pressable
key={index}
style={styles.creditItem}
onPress={() => handleProfilePress(credit.github)}
android_ripple={{ color: semanticColors.ANDROID_RIPPLE, radius: 200 }}
>
2025-10-01 16:36:23 +02:00
<RN.Image
source={{ uri: credit.avatar }}
style={styles.avatar}
/>
<RN.View style={styles.textContainer}>
<RN.Text style={styles.commandText}>{credit.command}</RN.Text>
<RN.Text style={styles.authorText}>by {credit.author}</RN.Text>
2025-10-01 17:36:45 +02:00
<RN.Text style={styles.linkText}>{credit.github.replace('https://github.com/', '@')}</RN.Text>
2025-10-01 16:36:23 +02:00
</RN.View>
2025-10-01 17:36:45 +02:00
</RN.Pressable>
2025-10-01 16:36:23 +02:00
))}
<BetterTableRowGroup title="About" icon={getAssetIDByName("InfoIcon")} padding={true}>
<RN.Text style={styles.versionText}>
Commands Plugin Collection{'\n'}
2025-10-01 19:42:47 +02:00
Version 1.0.0
2025-10-01 16:36:23 +02:00
</RN.Text>
</BetterTableRowGroup>
2025-10-01 20:22:26 +02:00
</RN.View>
</ScrollView>
2025-10-01 16:36:23 +02:00
);
}
2025-10-01 21:10:41 +02:00
// MAIN SETTINGS COMPONENT with hidden settings
2025-10-01 16:36:23 +02:00
export default function Settings() {
2025-10-01 19:42:47 +02:00
useProxy(storage);
2025-10-01 16:36:23 +02:00
const navigation = NavigationNative.useNavigation();
// Check for pending restart when unmounting
React.useEffect(() => {
return () => {
if (storage.pendingRestart) {
storage.pendingRestart = false;
alerts.showConfirmationAlert({
title: "Restart Required",
content:
"You have made changes to commands. Please restart Discord to apply these changes.",
confirmText: "Okay",
cancelText: null,
});
}
};
}, []);
return (
2025-10-01 20:22:26 +02:00
<ScrollView style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}>
<Header />
{/* Command Categories */}
<BetterTableRowGroup title="Command Categories" icon={getAssetIDByName("ChannelListIcon")}>
<FormRow
label="Facts Commands"
subLabel="Cat facts, dog facts, and useless facts"
leading={<FormRow.Icon source={getAssetIDByName("BookmarkIcon")} />}
trailing={<FormRow.Arrow />}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Facts Commands",
render: FactsSettingsPage,
})}
/>
<FormRow
label="List Commands"
subLabel="Plugin lists and theme lists"
leading={<FormRow.Icon source={getAssetIDByName("ListViewIcon")} />}
trailing={<FormRow.Arrow />}
onPress={() => navigation.push("VendettaCustomPage", {
title: "List Commands",
render: ListSettingsPage,
})}
/>
<FormRow
label="Image Commands"
subLabel="PetPet, KonoChan, and image utilities"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
trailing={<FormRow.Arrow />}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Image Commands",
render: ImageSettingsPage,
})}
/>
<FormRow
label="Gary Commands"
subLabel={`Gary images - Current: ${storage.garySettings.imageSource === "gary" ? "Gary API" :
storage.garySettings.imageSource === "catapi" ? "Cat API" :
storage.garySettings.imageSource === "minker" ? "Minker API" :
storage.garySettings.imageSource === "goober" ? "Goober API" : "Gary API"}`}
leading={<FormRow.Icon source={getAssetIDByName("CameraIcon")} />}
trailing={<FormRow.Arrow />}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Gary Commands",
render: GaryAPIPage,
})}
/>
<FormRow
label="Spotify Commands"
subLabel="Share your Spotify activity"
leading={<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />}
trailing={<FormRow.Arrow />}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Spotify Commands",
render: SpotifySettingsPage,
})}
/>
<FormRow
label="Other Commands"
subLabel="System info and miscellaneous"
leading={<FormRow.Icon source={getAssetIDByName("MoreHorizontalIcon")} />}
trailing={<FormRow.Arrow />}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Other Commands",
render: OtherSettingsPage,
})}
/>
</BetterTableRowGroup>
2025-10-01 21:10:41 +02:00
{/* Hidden Settings Section - Only visible when unlocked */}
{storage.hiddenSettings?.enabled && storage.hiddenSettings?.visible && (
<BetterTableRowGroup title="🔓 Hidden Settings" icon={getAssetIDByName("EyeIcon")}>
<FormRow
label="Hidden Commands"
subLabel="Experimental and mature content commands"
leading={<FormRow.Icon source={getAssetIDByName("WarningIcon")} />}
trailing={<FormRow.Arrow />}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Hidden Settings",
render: HiddenSettingsPage,
})}
/>
</BetterTableRowGroup>
)}
2025-10-01 20:22:26 +02:00
{/* More Options */}
<BetterTableRowGroup title="More Options" icon={getAssetIDByName("SettingsIcon")}>
<FormRow
label="Credits"
subLabel="View original authors of the plugins"
leading={<FormRow.Icon source={getAssetIDByName("HeartIcon")} />}
trailing={<FormRow.Arrow />}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Credits",
render: CreditsPage,
})}
/>
</BetterTableRowGroup>
2025-10-01 16:36:23 +02:00
2025-10-01 20:22:26 +02:00
<RN.View style={{ height: 32 }} />
</ScrollView>
);
}