import { plugin } from "@vendetta";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { findByProps } from "@vendetta/metro";
import { NavigationNative } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import { alerts } from "@vendetta/ui";
const { ScrollView } = findByProps("ScrollView");
const { FormRow, FormSwitchRow } = Forms;
// Better Table Row Group Component
function BetterTableRowGroup({
title,
icon,
children,
padding = false,
}: React.PropsWithChildren<{
title?: string;
icon?: number;
padding?: boolean;
}>) {
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",
},
icon: {
width: 16,
height: 16,
marginTop: 1.5,
tintColor: semanticColors.TEXT_MUTED,
},
titleText: {
fontSize: 14,
fontWeight: "600",
color: semanticColors.TEXT_MUTED,
},
});
return (
{title && (
{icon && (
)}
{title.toUpperCase()}
)}
{padding ? (
{children}
) : children}
);
}
// Header Component
function Header() {
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,
},
});
return (
Commands
A collection of useful commands
);
}
// Facts Commands Settings Page
function FactsSettingsPage() {
useProxy(storage);
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
});
return (
}
value={storage.factSettings?.sendAsReply ?? true}
onValueChange={() => {
storage.factSettings.sendAsReply = !storage.factSettings.sendAsReply;
}}
/>
}
value={storage.factSettings?.includeCitation ?? false}
onValueChange={() => {
storage.factSettings.includeCitation = !storage.factSettings.includeCitation;
}}
/>
}
value={storage.enabledCommands?.catfact ?? true}
onValueChange={() => {
storage.enabledCommands.catfact = !storage.enabledCommands.catfact;
storage.pendingRestart = true;
}}
/>
}
value={storage.enabledCommands?.dogfact ?? true}
onValueChange={() => {
storage.enabledCommands.dogfact = !storage.enabledCommands.dogfact;
storage.pendingRestart = true;
}}
/>
}
value={storage.enabledCommands?.useless ?? true}
onValueChange={() => {
storage.enabledCommands.useless = !storage.enabledCommands.useless;
storage.pendingRestart = true;
}}
/>
);
}
// Gary API Settings Page with proper reactivity
function GaryAPIPage() {
useProxy(storage);
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
marginBottom: 16,
textAlign: "center",
},
currentText: {
fontSize: 14,
color: semanticColors.TEXT_NORMAL,
textAlign: "center",
fontWeight: "600",
},
optionContainer: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 12,
paddingHorizontal: 16,
marginHorizontal: 0,
backgroundColor: "transparent",
},
radioContainer: {
flexDirection: "row",
alignItems: "center",
flex: 1,
},
radioButton: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
borderColor: semanticColors.TEXT_MUTED,
alignItems: "center",
justifyContent: "center",
marginRight: 12,
},
radioButtonSelected: {
borderColor: semanticColors.BRAND_500,
},
radioInner: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: semanticColors.BRAND_500,
},
optionIcon: {
width: 20,
height: 20,
marginRight: 12,
tintColor: semanticColors.TEXT_MUTED,
},
optionIconSelected: {
tintColor: semanticColors.BRAND_500,
},
textContainer: {
flex: 1,
},
optionTitle: {
fontSize: 16,
fontWeight: "500",
color: semanticColors.TEXT_NORMAL,
},
optionTitleSelected: {
color: semanticColors.BRAND_500,
fontWeight: "600",
},
optionDescription: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
marginTop: 2,
lineHeight: 18,
},
});
const currentSource = storage.garySettings?.imageSource || "gary";
const options = [
{
value: "gary",
title: "Gary API",
description: "Original Gary the cat images from api.garythe.cat",
icon: getAssetIDByName("ImageIcon"),
},
{
value: "catapi",
title: "Cat API",
description: "Random cat pictures from thecatapi.com",
icon: getAssetIDByName("ImageIcon"),
},
{
value: "minker",
title: "Minker API",
description: "Minky images from minky.materii.dev",
icon: getAssetIDByName("ImageIcon"),
},
{
value: "goober",
title: "Goober API",
description: "Goober images from api.garythe.cat/goober",
icon: getAssetIDByName("ImageIcon"),
},
];
const renderOption = (option: typeof options[0]) => {
const isSelected = currentSource === option.value;
return (
{
storage.garySettings.imageSource = option.value;
}}
android_ripple={{ color: semanticColors.ANDROID_RIPPLE, radius: 300 }}
>
{isSelected && }
{option.title}
{option.description}
);
};
return (
}
value={storage.enabledCommands?.gary ?? true}
onValueChange={() => {
storage.enabledCommands.gary = !storage.enabledCommands.gary;
storage.pendingRestart = true;
}}
/>
Choose which API the /gary command should use to fetch images.
{options.map(renderOption)}
Currently using: {
currentSource === "gary" ? "Gary API" :
currentSource === "catapi" ? "Cat API" :
currentSource === "minker" ? "Minker API" :
currentSource === "goober" ? "Goober API" : "Gary API"
}
Tap an option above to change the image source.
);
}
// List Commands Settings Page
function ListSettingsPage() {
useProxy(storage);
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
});
return (
}
value={storage.listSettings?.pluginListAlwaysDetailed ?? false}
onValueChange={() => {
storage.listSettings.pluginListAlwaysDetailed = !storage.listSettings.pluginListAlwaysDetailed;
}}
/>
}
value={storage.listSettings?.themeListAlwaysDetailed ?? false}
onValueChange={() => {
storage.listSettings.themeListAlwaysDetailed = !storage.listSettings.themeListAlwaysDetailed;
}}
/>
}
value={storage.enabledCommands?.pluginList ?? true}
onValueChange={() => {
storage.enabledCommands.pluginList = !storage.enabledCommands.pluginList;
storage.pendingRestart = true;
}}
/>
}
value={storage.enabledCommands?.themeList ?? true}
onValueChange={() => {
storage.enabledCommands.themeList = !storage.enabledCommands.themeList;
storage.pendingRestart = true;
}}
/>
);
}
// Image Commands Settings Page
function ImageSettingsPage() {
useProxy(storage);
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
});
return (
}
value={storage.enabledCommands?.petpet ?? true}
onValueChange={() => {
storage.enabledCommands.petpet = !storage.enabledCommands.petpet;
storage.pendingRestart = true;
}}
/>
}
value={storage.enabledCommands?.konoself ?? true}
onValueChange={() => {
storage.enabledCommands.konoself = !storage.enabledCommands.konoself;
storage.pendingRestart = true;
}}
/>
}
value={storage.enabledCommands?.konosend ?? true}
onValueChange={() => {
storage.enabledCommands.konosend = !storage.enabledCommands.konosend;
storage.pendingRestart = true;
}}
/>
);
}
// Spotify Commands Settings Page
function SpotifySettingsPage() {
useProxy(storage);
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
textAlign: "center",
lineHeight: 20,
},
});
return (
}
value={storage.enabledCommands?.spotifyTrack ?? true}
onValueChange={() => {
storage.enabledCommands.spotifyTrack = !storage.enabledCommands.spotifyTrack;
storage.pendingRestart = true;
}}
/>
}
value={storage.enabledCommands?.spotifyAlbum ?? true}
onValueChange={() => {
storage.enabledCommands.spotifyAlbum = !storage.enabledCommands.spotifyAlbum;
storage.pendingRestart = true;
}}
/>
}
value={storage.enabledCommands?.spotifyArtists ?? true}
onValueChange={() => {
storage.enabledCommands.spotifyArtists = !storage.enabledCommands.spotifyArtists;
storage.pendingRestart = true;
}}
/>
}
value={storage.enabledCommands?.spotifyCover ?? true}
onValueChange={() => {
storage.enabledCommands.spotifyCover = !storage.enabledCommands.spotifyCover;
storage.pendingRestart = true;
}}
/>
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.
);
}
// Other Commands Settings Page
function OtherSettingsPage() {
useProxy(storage);
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
});
return (
}
value={storage.enabledCommands?.firstmessage ?? true}
onValueChange={() => {
storage.enabledCommands.firstmessage = !storage.enabledCommands.firstmessage;
storage.pendingRestart = true;
}}
/>
}
value={storage.enabledCommands?.sysinfo ?? true}
onValueChange={() => {
storage.enabledCommands.sysinfo = !storage.enabledCommands.sysinfo;
storage.pendingRestart = true;
}}
/>
);
}
// FIXED Credits Page - changed "contributor" to "developer"
function CreditsPage() {
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
creditItem: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 16,
paddingHorizontal: 20,
backgroundColor: semanticColors.CARD_PRIMARY_BG,
borderRadius: 12,
marginHorizontal: 16,
marginVertical: 6,
shadowColor: semanticColors.BLACK,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2,
},
avatar: {
width: 48,
height: 48,
borderRadius: 24,
marginRight: 16,
backgroundColor: semanticColors.BACKGROUND_SECONDARY,
},
textContainer: {
flex: 1,
},
commandText: {
fontSize: 17,
fontWeight: "600",
color: semanticColors.TEXT_NORMAL,
marginBottom: 4,
},
authorText: {
fontSize: 15,
color: semanticColors.TEXT_MUTED,
marginBottom: 2,
},
linkText: {
fontSize: 13,
color: semanticColors.TEXT_BRAND,
fontWeight: "500",
},
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
marginBottom: 20,
textAlign: "center",
lineHeight: 20,
},
versionText: {
fontSize: 15,
color: semanticColors.TEXT_NORMAL,
textAlign: "center",
fontWeight: "600",
lineHeight: 22,
},
});
const credits = [
{
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"
},
{
command: "PetPet",
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"
},
];
const handleProfilePress = (githubUrl: string) => {
RN.Linking.openURL(githubUrl);
};
return (
Thanks to this developers for creating such a nice plugins!{'\n'}
Tap on any developer to visit their GitHub profile.
{credits.map((credit, index) => (
handleProfilePress(credit.github)}
android_ripple={{ color: semanticColors.ANDROID_RIPPLE, radius: 200 }}
>
{credit.command}
by {credit.author}
{credit.github.replace('https://github.com/', '@')}
))}
Commands Plugin Collection{'\n'}
Version 1.0.0
);
}
// Initialize storage with default values
if (!storage.factSettings) {
storage.factSettings = {
sendAsReply: true,
includeCitation: false,
};
}
if (!storage.listSettings) {
storage.listSettings = {
pluginListAlwaysDetailed: false,
themeListAlwaysDetailed: false,
};
}
if (!storage.garySettings) {
storage.garySettings = {
imageSource: "gary",
};
}
if (!storage.enabledCommands) {
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,
};
}
if (!storage.pendingRestart) {
storage.pendingRestart = false;
}
// Main Settings Component - COMPLETELY FIXED
export default function Settings() {
useProxy(storage);
const navigation = NavigationNative.useNavigation();
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
});
// 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 (
{/* Command Categories */}
}
trailing={}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Facts Commands",
render: FactsSettingsPage,
})}
/>
}
trailing={}
onPress={() => navigation.push("VendettaCustomPage", {
title: "List Commands",
render: ListSettingsPage,
})}
/>
}
trailing={}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Image Commands",
render: ImageSettingsPage,
})}
/>
}
trailing={}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Gary Commands",
render: GaryAPIPage,
})}
/>
}
trailing={}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Spotify Commands",
render: SpotifySettingsPage,
})}
/>
}
trailing={}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Other Commands",
render: OtherSettingsPage,
})}
/>
{/* More Options */}
}
trailing={}
onPress={() => navigation.push("VendettaCustomPage", {
title: "Credits",
render: CreditsPage,
})}
/>
);
}