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({ forceRerender }: { forceRerender: () => void }) { const styles = stylesheet.createThemedStyleSheet({ container: { flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY, }, }); return ( } value={storage.factSettings?.sendAsReply ?? true} onValueChange={(v: boolean) => { storage.factSettings.sendAsReply = v; forceRerender(); }} /> } value={storage.factSettings?.includeCitation ?? false} onValueChange={(v: boolean) => { storage.factSettings.includeCitation = v; forceRerender(); }} /> } value={storage.enabledCommands?.catfact ?? true} onValueChange={(v) => { storage.enabledCommands.catfact = v; storage.pendingRestart = true; forceRerender(); }} /> } value={storage.enabledCommands?.dogfact ?? true} onValueChange={(v) => { storage.enabledCommands.dogfact = v; storage.pendingRestart = true; forceRerender(); }} /> } value={storage.enabledCommands?.useless ?? true} onValueChange={(v) => { storage.enabledCommands.useless = v; storage.pendingRestart = true; forceRerender(); }} /> ); } // REFACTORED Gary API Settings Page with working round switches function GaryAPIPage({ forceRerender }: { forceRerender: () => void }) { 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 handleOptionPress = (value: string) => { console.log(`[Gary Settings] Changing API from ${currentSource} to ${value}`); storage.garySettings.imageSource = value; forceRerender(); }; const renderOption = (option: typeof options[0]) => { const isSelected = currentSource === option.value; return ( handleOptionPress(option.value)} android_ripple={{ color: semanticColors.ANDROID_RIPPLE, radius: 300 }} > {isSelected && } {option.title} {option.description} ); }; return ( } value={storage.enabledCommands?.gary ?? true} onValueChange={(v) => { storage.enabledCommands.gary = v; storage.pendingRestart = true; forceRerender(); }} /> 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({ forceRerender }: { forceRerender: () => void }) { const styles = stylesheet.createThemedStyleSheet({ container: { flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY, }, }); return ( } value={storage.listSettings?.pluginListAlwaysDetailed ?? false} onValueChange={(v: boolean) => { storage.listSettings.pluginListAlwaysDetailed = v; forceRerender(); }} /> } value={storage.listSettings?.themeListAlwaysDetailed ?? false} onValueChange={(v: boolean) => { storage.listSettings.themeListAlwaysDetailed = v; forceRerender(); }} /> } value={storage.enabledCommands?.pluginList ?? true} onValueChange={(v) => { storage.enabledCommands.pluginList = v; storage.pendingRestart = true; forceRerender(); }} /> } value={storage.enabledCommands?.themeList ?? true} onValueChange={(v) => { storage.enabledCommands.themeList = v; storage.pendingRestart = true; forceRerender(); }} /> ); } // Image Commands Settings Page function ImageSettingsPage({ forceRerender }: { forceRerender: () => void }) { const styles = stylesheet.createThemedStyleSheet({ container: { flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY, }, }); return ( } value={storage.enabledCommands?.petpet ?? true} onValueChange={(v) => { storage.enabledCommands.petpet = v; storage.pendingRestart = true; forceRerender(); }} /> } value={storage.enabledCommands?.konoself ?? true} onValueChange={(v) => { storage.enabledCommands.konoself = v; storage.pendingRestart = true; forceRerender(); }} /> } value={storage.enabledCommands?.konosend ?? true} onValueChange={(v) => { storage.enabledCommands.konosend = v; storage.pendingRestart = true; forceRerender(); }} /> ); } // Spotify Commands Settings Page function SpotifySettingsPage({ forceRerender }: { forceRerender: () => void }) { 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={(v) => { storage.enabledCommands.spotifyTrack = v; storage.pendingRestart = true; forceRerender(); }} /> } value={storage.enabledCommands?.spotifyAlbum ?? true} onValueChange={(v) => { storage.enabledCommands.spotifyAlbum = v; storage.pendingRestart = true; forceRerender(); }} /> } value={storage.enabledCommands?.spotifyArtists ?? true} onValueChange={(v) => { storage.enabledCommands.spotifyArtists = v; storage.pendingRestart = true; forceRerender(); }} /> } value={storage.enabledCommands?.spotifyCover ?? true} onValueChange={(v) => { storage.enabledCommands.spotifyCover = v; storage.pendingRestart = true; forceRerender(); }} /> 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({ forceRerender }: { forceRerender: () => void }) { const styles = stylesheet.createThemedStyleSheet({ container: { flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY, }, }); return ( } value={storage.enabledCommands?.firstmessage ?? true} onValueChange={(v) => { storage.enabledCommands.firstmessage = v; storage.pendingRestart = true; forceRerender(); }} /> } value={storage.enabledCommands?.sysinfo ?? true} onValueChange={(v) => { storage.enabledCommands.sysinfo = v; storage.pendingRestart = true; forceRerender(); }} /> ); } // UPDATED Credits Page with proper spacing and correct GitHub links 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 Command", 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 all the amazing developers who contributed to this plugin collection!{'\n'} Tap on any contributor 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{'\n'} Built on {new Date().toLocaleDateString()}{'\n'} Made with ❤️ for the community ); } // 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 export default function Settings() { useProxy(storage); const [rerender, forceRerender] = React.useReducer((x) => x + 1, 0); 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, }); } }; }, []); const navigateToPage = (title: string, component: React.ComponentType) => { navigation.push("VendettaCustomPage", { title, render: () => React.createElement(component, { forceRerender }), }); }; return (
{/* Command Categories */} } trailing={} onPress={() => navigateToPage("Facts Commands", FactsSettingsPage)} /> } trailing={} onPress={() => navigateToPage("List Commands", ListSettingsPage)} /> } trailing={} onPress={() => navigateToPage("Image Commands", ImageSettingsPage)} /> } trailing={} onPress={() => navigateToPage("Gary Commands", GaryAPIPage)} /> } trailing={} onPress={() => navigateToPage("Spotify Commands", SpotifySettingsPage)} /> } trailing={} onPress={() => navigateToPage("Other Commands", OtherSettingsPage)} /> {/* More Options */} } trailing={} onPress={() => navigateToPage("Credits", CreditsPage)} /> ); }