import { plugin } from "@vendetta"; import { useProxy } from "@vendetta/storage"; import { React, ReactNative as RN } from "@vendetta/metro/common"; import { Linking } from "react-native"; import { NavigationNative } from "@vendetta/metro/common"; import { findByProps } from "@vendetta/metro"; import { showToast } from "@vendetta/ui/toasts"; import { getAssetIDByName } from "@vendetta/ui/assets"; const { ScrollView } = findByProps("ScrollView"); const { TableRowGroup, TableSwitchRow, Stack, TableRow } = findByProps( "TableSwitchRow", "TableRowGroup", "Stack", "TableRow", ); const { TextInput } = findByProps("TextInput"); import Constants from "../../constants"; import { serviceFactory } from "../../services/ServiceFactory"; import { ServiceType } from "../../../../defs"; plugin.storage.username ??= ""; plugin.storage.apiKey ??= ""; plugin.storage.appName ??= "Music"; plugin.storage.timeInterval ??= 5; plugin.storage.showTimestamp ??= true; plugin.storage.listeningTo ??= true; plugin.storage.ignoreSpotify ??= true; plugin.storage.verboseLogging ??= false; plugin.storage.service ??= "lastfm"; plugin.storage.librefmUsername ??= ""; plugin.storage.librefmApiKey ??= ""; plugin.storage.listenbrainzUsername ??= ""; plugin.storage.listenbrainzToken ??= ""; plugin.storage.addToSidebar ??= false; plugin.storage.showLargeText ??= true; plugin.storage.ignoreList ??= []; const get = (k: string, fallback?: any) => plugin.storage[k] ?? fallback; const set = (k: string, v: any) => (plugin.storage[k] = v); // Last.fm Settings Page function LastFmSettingsPage() { useProxy(plugin.storage); const [, forceUpdate] = React.useReducer((x) => x + 1, 0); const testConnection = async () => { showToast("Testing Last.fm connection...", getAssetIDByName("ClockIcon")); try { const isValid = await serviceFactory.testService("lastfm"); if (isValid) { showToast( "✅ Last.fm connection successful!", getAssetIDByName("CheckIcon"), ); } else { showToast("❌ Last.fm connection failed", getAssetIDByName("XIcon")); } } catch (error) { showToast("❌ Connection error", getAssetIDByName("XIcon")); } }; return ( { set("username", v); forceUpdate(); }} isClearable /> { set("apiKey", v); forceUpdate(); }} secureTextEntry={true} isClearable /> } onPress={testConnection} /> } onPress={async () => { try { await Linking.openURL("https://www.last.fm/api/account/create"); } catch (error) { console.error("Failed to open Last.fm API URL:", error); showToast( "Failed to open web browser. Please visit: https://www.last.fm/api/account/create", getAssetIDByName("XIcon"), ); } }} /> ); } // Libre.fm Settings Page function LibreFmSettingsPage() { useProxy(plugin.storage); const [, forceUpdate] = React.useReducer((x) => x + 1, 0); const testConnection = async () => { showToast("Testing Libre.fm connection...", getAssetIDByName("ClockIcon")); try { const isValid = await serviceFactory.testService("librefm"); if (isValid) { showToast( "✅ Libre.fm connection successful!", getAssetIDByName("CheckIcon"), ); } else { showToast("❌ Libre.fm connection failed", getAssetIDByName("XIcon")); } } catch (error) { showToast("❌ Connection error", getAssetIDByName("XIcon")); } }; return ( { set("librefmUsername", v); forceUpdate(); }} isClearable /> { set("librefmApiKey", v); forceUpdate(); }} secureTextEntry={true} isClearable /> } onPress={testConnection} /> } onPress={async () => { try { await Linking.openURL("https://www.last.fm/api/account/create"); } catch (error) { console.error("Failed to open Last.fm API URL:", error); showToast( "Failed to open web browser. Please visit: https://www.last.fm/api/account/create", getAssetIDByName("XIcon"), ); } }} /> ); } // ListenBrainz Settings Page function ListenBrainzSettingsPage() { useProxy(plugin.storage); const [, forceUpdate] = React.useReducer((x) => x + 1, 0); const testConnection = async () => { showToast( "Testing ListenBrainz connection...", getAssetIDByName("ClockIcon"), ); try { const isValid = await serviceFactory.testService("listenbrainz"); if (isValid) { showToast( "✅ ListenBrainz connection successful!", getAssetIDByName("CheckIcon"), ); } else { showToast( "❌ ListenBrainz connection failed", getAssetIDByName("XIcon"), ); } } catch (error) { showToast("❌ Connection error", getAssetIDByName("XIcon")); } }; return ( { set("listenbrainzUsername", v); forceUpdate(); }} isClearable /> { set("listenbrainzToken", v); forceUpdate(); }} secureTextEntry={true} isClearable /> } onPress={testConnection} /> } onPress={async () => { try { await Linking.openURL("https://listenbrainz.org/settings/"); } catch (error) { console.error( "Failed to open ListenBrainz settings URL:", error, ); showToast( "Failed to open web browser. Please visit: https://listenbrainz.org/settings/", getAssetIDByName("XIcon"), ); } }} /> ); } // Display Settings Page function DisplaySettingsPage() { useProxy(plugin.storage); const [, forceUpdate] = React.useReducer((x) => x + 1, 0); return ( { set("appName", v); forceUpdate(); }} isClearable /> { const interval = Number(v); if (interval >= Constants.MIN_UPDATE_INTERVAL) { set("timeInterval", interval); forceUpdate(); } }} keyboardType="numeric" isClearable /> { set("listeningTo", value); forceUpdate(); }} /> { set("showLargeText", value); forceUpdate(); }} /> { set("showTimestamp", value); forceUpdate(); }} /> ); } function IgnoreListSettingsPage() { useProxy(plugin.storage); const [, forceUpdate] = React.useReducer((x) => x + 1, 0); const [newAppName, setNewAppName] = React.useState(""); const addAppToIgnoreList = () => { if (!newAppName.trim()) { showToast("Please enter an app name", getAssetIDByName("Small")); return; } const ignoreList = get("ignoreList", []); if (!ignoreList.includes(newAppName.trim())) { set("ignoreList", [...ignoreList, newAppName.trim()]); setNewAppName(""); forceUpdate(); showToast("App added to ignore list", getAssetIDByName("Check")); } else { showToast("App already in ignore list", getAssetIDByName("Warning")); } }; const removeAppFromIgnoreList = (appName: string) => { const ignoreList = get("ignoreList", []); set( "ignoreList", ignoreList.filter((app: string) => app !== appName), ); forceUpdate(); showToast("App removed from ignore list", getAssetIDByName("Check")); }; return ( } onPress={addAppToIgnoreList} /> {get("ignoreList", []).length > 0 && ( {get("ignoreList", []).map((appName: string, index: number) => ( removeAppFromIgnoreList(appName)} style={{ padding: 8, backgroundColor: "#ff4d4d", borderRadius: 12, width: 24, height: 24, justifyContent: "center", alignItems: "center", }} > } /> ))} )} ); } // Logging Settings Page function LoggingSettingsPage() { useProxy(plugin.storage); const [, forceUpdate] = React.useReducer((x) => x + 1, 0); return ( { set("verboseLogging", value); forceUpdate(); }} /> ); } // Main Settings Page export default function Settings() { useProxy(plugin.storage); const navigation = NavigationNative.useNavigation(); const [, forceUpdate] = React.useReducer((x) => x + 1, 0); const currentService = get("service") as ServiceType; const getCredentialStatus = (service: ServiceType) => { switch (service) { case "lastfm": { return get("username") && get("apiKey") ? "✅ Configured" : "❌ Missing credentials"; } case "librefm": { return get("librefmUsername") && get("librefmApiKey") ? "✅ Configured" : "❌ Missing credentials"; } case "listenbrainz": { return get("listenbrainzUsername") ? "✅ Configured" : "❌ Missing username"; } default: return "❓ Unknown"; } }; return ( {/* Service Selection */} {(["lastfm", "librefm", "listenbrainz"] as ServiceType[]).map( (service) => ( { if (value && service !== currentService) { set("service", service); forceUpdate(); } }} /> ), )} {/* Service Configuration */} } onPress={() => navigation.push("VendettaCustomPage", { title: "Last.fm Settings", render: LastFmSettingsPage, }) } /> } onPress={() => navigation.push("VendettaCustomPage", { title: "Libre.fm Settings", render: LibreFmSettingsPage, }) } /> } onPress={() => navigation.push("VendettaCustomPage", { title: "ListenBrainz Settings", render: ListenBrainzSettingsPage, }) } /> {/* Plugin Configuration */} } onPress={() => navigation.push("VendettaCustomPage", { title: "Display Settings", render: DisplaySettingsPage, }) } /> } onPress={() => navigation.push("VendettaCustomPage", { title: "Ignore List Settings", render: IgnoreListSettingsPage, }) } /> } onPress={() => navigation.push("VendettaCustomPage", { title: "Logging Settings", render: LoggingSettingsPage, }) } /> { if (value) { const { showConfirmationAlert } = window; showConfirmationAlert({ title: "Warning: Potential Discord Crash", content: "Enabling 'Add to Sidebar' may cause Discord to crash or become unstable. This feature is experimental and not recommended.", confirmText: "Enable Anyway", cancelText: "Cancel", confirmColor: "red", onConfirm: () => { set("addToSidebar", true); forceUpdate(); showToast( "Sidebar feature enabled - use with caution", getAssetIDByName("Warning"), ); }, onCancel: () => forceUpdate(), }); } else { set("addToSidebar", false); forceUpdate(); } }} /> {/* About */} ); }