package models import "encoding/xml" // SupportedURLsResponse represents the response from the /supportedURLs endpoint type SupportedURLsResponse struct { XMLName xml.Name `xml:"supportedURLs"` DeviceID string `xml:"deviceID,attr"` URLs []URL `xml:"URL"` } // URL represents a single supported URL endpoint type URL struct { Location string `xml:"location,attr"` } // GetURLs returns a slice of all supported URL locations func (s *SupportedURLsResponse) GetURLs() []string { urls := make([]string, len(s.URLs)) for i, url := range s.URLs { urls[i] = url.Location } return urls } // HasURL checks if a specific URL location is supported func (s *SupportedURLsResponse) HasURL(location string) bool { for _, url := range s.URLs { if url.Location == location { return true } } return false } // GetURLCount returns the total number of supported URLs func (s *SupportedURLsResponse) GetURLCount() int { return len(s.URLs) } // GetCoreURLs returns URLs for core device functionality func (s *SupportedURLsResponse) GetCoreURLs() []string { coreEndpoints := []string{ "/info", "/capabilities", "/name", "/sources", "/volume", "/bass", "/balance", "/presets", "/nowPlaying", "/clock", "/key", "/powerManagement", } var available []string for _, endpoint := range coreEndpoints { if s.HasURL(endpoint) { available = append(available, endpoint) } } return available } // GetStreamingURLs returns URLs for streaming service functionality func (s *SupportedURLsResponse) GetStreamingURLs() []string { streamingEndpoints := []string{ "/navigate", "/search", "/addStation", "/removeStation", "/serviceAvailability", "/sources", "/select", } var available []string for _, endpoint := range streamingEndpoints { if s.HasURL(endpoint) { available = append(available, endpoint) } } return available } // GetAdvancedURLs returns URLs for advanced audio and system functionality func (s *SupportedURLsResponse) GetAdvancedURLs() []string { advancedEndpoints := []string{ "/audiodspcontrols", "/audioproducttonecontrols", "/audioproductlevelcontrols", "/videoSyncAudioDelay", "/group", "/setZone", "/addZoneSlave", "/removeZoneSlave", "/bassCapabilities", "/recents", "/trackInfo", } var available []string for _, endpoint := range advancedEndpoints { if s.HasURL(endpoint) { available = append(available, endpoint) } } return available } // GetNetworkURLs returns URLs for network and connectivity functionality func (s *SupportedURLsResponse) GetNetworkURLs() []string { networkEndpoints := []string{ "/networkInfo", "/netStats", "/wifiProfile", "/bluetoothInfo", "/airplay", "/wirelessProfile", } var available []string for _, endpoint := range networkEndpoints { if s.HasURL(endpoint) { available = append(available, endpoint) } } return available } // HasCorePlaybackSupport checks if device supports basic playback functionality func (s *SupportedURLsResponse) HasCorePlaybackSupport() bool { required := []string{"/nowPlaying", "/key", "/volume"} for _, endpoint := range required { if !s.HasURL(endpoint) { return false } } return true } // HasPresetSupport checks if device supports preset functionality func (s *SupportedURLsResponse) HasPresetSupport() bool { return s.HasURL("/presets") } // HasMultiroomSupport checks if device supports multiroom/zone functionality func (s *SupportedURLsResponse) HasMultiroomSupport() bool { return s.HasURL("/setZone") || s.HasURL("/addZoneSlave") } // HasAdvancedAudioSupport checks if device supports advanced audio controls func (s *SupportedURLsResponse) HasAdvancedAudioSupport() bool { return s.HasURL("/audiodspcontrols") || s.HasURL("/audioproducttonecontrols") } // HasStreamingSupport checks if device supports streaming service navigation func (s *SupportedURLsResponse) HasStreamingSupport() bool { return s.HasURL("/navigate") || s.HasURL("/search") } // GetUnsupportedURLs returns a list of common URLs that this device doesn't support func (s *SupportedURLsResponse) GetUnsupportedURLs(checkList []string) []string { var unsupported []string for _, endpoint := range checkList { if !s.HasURL(endpoint) { unsupported = append(unsupported, endpoint) } } return unsupported } // EndpointFeature represents a feature that maps to one or more endpoints type EndpointFeature struct { Name string `json:"name"` Description string `json:"description"` Endpoints []string `json:"endpoints"` Category string `json:"category"` Essential bool `json:"essential"` // Required for basic operation CLICommand string `json:"cli_command"` // Corresponding CLI command } // GetEndpointFeatureMap returns a comprehensive mapping of endpoints to implemented features func GetEndpointFeatureMap() []EndpointFeature { return []EndpointFeature{ // Core Device Information { Name: "Device Information", Description: "Basic device details, name, and identification", Endpoints: []string{"/info", "/name"}, Category: "Core", Essential: true, CLICommand: "info get, name get/set", }, { Name: "Device Capabilities", Description: "Supported device features and endpoints discovery", Endpoints: []string{"/capabilities", "/supportedURLs"}, Category: "Core", Essential: true, CLICommand: "capabilities, supported-urls", }, { Name: "Network Information", Description: "Network configuration and connectivity status", Endpoints: []string{"/networkInfo", "/netStats", "/wifiProfile"}, Category: "Network", Essential: false, CLICommand: "network info", }, // Audio Control { Name: "Volume Control", Description: "Audio volume management and adjustment", Endpoints: []string{"/volume"}, Category: "Audio", Essential: true, CLICommand: "volume get/set/up/down", }, { Name: "Bass Control", Description: "Bass level adjustment and capabilities", Endpoints: []string{"/bass", "/bassCapabilities"}, Category: "Audio", Essential: false, CLICommand: "bass get/set/up/down", }, { Name: "Balance Control", Description: "Left/right audio balance adjustment", Endpoints: []string{"/balance"}, Category: "Audio", Essential: false, CLICommand: "balance get/set/left/right", }, { Name: "Advanced Audio Controls", Description: "DSP controls, tone controls, and audio processing", Endpoints: []string{"/audiodspcontrols", "/audioproducttonecontrols", "/audioproductlevelcontrols"}, Category: "Audio", Essential: false, CLICommand: "audio dsp/tone/level", }, // Playback Control { Name: "Playback Control", Description: "Play, pause, stop, and track navigation", Endpoints: []string{"/key", "/nowPlaying"}, Category: "Playback", Essential: true, CLICommand: "play start/stop/pause, key send", }, { Name: "Track Information", Description: "Currently playing track details and metadata", Endpoints: []string{"/trackInfo", "/recents"}, Category: "Playback", Essential: false, CLICommand: "play now, track get", }, // Source Management { Name: "Audio Sources", Description: "Available audio sources and source selection", Endpoints: []string{"/sources", "/select"}, Category: "Sources", Essential: true, CLICommand: "source list/select/spotify/bluetooth/aux", }, { Name: "Service Availability", Description: "Streaming service availability and status", Endpoints: []string{"/serviceAvailability"}, Category: "Sources", Essential: false, CLICommand: "source availability/compare", }, // Content Navigation { Name: "Content Navigation", Description: "Browse music libraries and streaming services", Endpoints: []string{"/navigate", "/search"}, Category: "Content", Essential: false, CLICommand: "browse content/tunein/pandora/spotify", }, { Name: "Station Management", Description: "Add, remove, and manage radio stations", Endpoints: []string{"/addStation", "/removeStation"}, Category: "Content", Essential: false, CLICommand: "station add/remove/search/list", }, // Presets { Name: "Preset Management", Description: "Store and recall favorite content as presets", Endpoints: []string{"/presets"}, Category: "Presets", Essential: false, CLICommand: "preset list/set/select/remove", }, // Multiroom/Zone { Name: "Multiroom Zones", Description: "Create and manage speaker groups", Endpoints: []string{"/setZone", "/getZone", "/addZoneSlave", "/removeZoneSlave"}, Category: "Multiroom", Essential: false, CLICommand: "zone create/add/remove/list", }, // System Features { Name: "Clock and Time", Description: "Device clock settings and time display", Endpoints: []string{"/clock"}, Category: "System", Essential: false, CLICommand: "clock get/set", }, { Name: "Power Management", Description: "Device power state and standby control", Endpoints: []string{"/powerManagement"}, Category: "System", Essential: false, CLICommand: "key power", }, { Name: "Bluetooth Connectivity", Description: "Bluetooth pairing and device management", Endpoints: []string{"/bluetoothInfo", "/bluetoothPair"}, Category: "Network", Essential: false, CLICommand: "source bluetooth", }, { Name: "AirPlay Support", Description: "Apple AirPlay streaming capability", Endpoints: []string{"/airplay"}, Category: "Network", Essential: false, CLICommand: "source select --source AIRPLAY", }, } } // GetFeaturesByCategory returns features grouped by category func (s *SupportedURLsResponse) GetFeaturesByCategory() map[string][]EndpointFeature { features := GetEndpointFeatureMap() result := make(map[string][]EndpointFeature) for _, feature := range features { // Check if device supports this feature (any of its endpoints) supported := false for _, endpoint := range feature.Endpoints { if s.HasURL(endpoint) { supported = true break } } if supported { result[feature.Category] = append(result[feature.Category], feature) } } return result } // GetSupportedFeatures returns all features supported by this device func (s *SupportedURLsResponse) GetSupportedFeatures() []EndpointFeature { features := GetEndpointFeatureMap() var supported []EndpointFeature for _, feature := range features { // Check if device supports this feature (any of its endpoints) hasSupport := false for _, endpoint := range feature.Endpoints { if s.HasURL(endpoint) { hasSupport = true break } } if hasSupport { supported = append(supported, feature) } } return supported } // GetUnsupportedFeatures returns features not supported by this device func (s *SupportedURLsResponse) GetUnsupportedFeatures() []EndpointFeature { features := GetEndpointFeatureMap() var unsupported []EndpointFeature for _, feature := range features { // Check if device supports this feature (any of its endpoints) hasSupport := false for _, endpoint := range feature.Endpoints { if s.HasURL(endpoint) { hasSupport = true break } } if !hasSupport { unsupported = append(unsupported, feature) } } return unsupported } // GetPartiallyImplementedFeatures returns features where only some endpoints are supported func (s *SupportedURLsResponse) GetPartiallyImplementedFeatures() []EndpointFeature { features := GetEndpointFeatureMap() var partial []EndpointFeature for _, feature := range features { if len(feature.Endpoints) <= 1 { continue // Skip single-endpoint features } supportedCount := 0 for _, endpoint := range feature.Endpoints { if s.HasURL(endpoint) { supportedCount++ } } // Partially implemented if some but not all endpoints are supported if supportedCount > 0 && supportedCount < len(feature.Endpoints) { partial = append(partial, feature) } } return partial } // GetMissingEssentialFeatures returns essential features that are not supported func (s *SupportedURLsResponse) GetMissingEssentialFeatures() []EndpointFeature { features := GetEndpointFeatureMap() var missing []EndpointFeature for _, feature := range features { if !feature.Essential { continue } // Check if device supports this essential feature hasSupport := false for _, endpoint := range feature.Endpoints { if s.HasURL(endpoint) { hasSupport = true break } } if !hasSupport { missing = append(missing, feature) } } return missing } // GetFeatureCompleteness returns a completeness score (0-100) based on supported features func (s *SupportedURLsResponse) GetFeatureCompleteness() (int, int, int) { features := GetEndpointFeatureMap() var total, supported, essential int for _, feature := range features { total++ if feature.Essential { essential++ } // Check if device supports this feature hasSupport := false for _, endpoint := range feature.Endpoints { if s.HasURL(endpoint) { hasSupport = true break } } if hasSupport { supported++ } } completeness := 0 if total > 0 { completeness = (supported * 100) / total } return completeness, supported, total }