package models import "encoding/xml" // NavigateRequest represents a request to navigate content sources type NavigateRequest struct { XMLName xml.Name `xml:"navigate"` Source string `xml:"source,attr"` SourceAccount string `xml:"sourceAccount,attr,omitempty"` Menu string `xml:"menu,attr,omitempty"` Sort string `xml:"sort,attr,omitempty"` StartItem int `xml:"startItem"` NumItems int `xml:"numItems"` Item *NavigateItem `xml:"item,omitempty"` } // NavigateResponse represents the response from a navigate request type NavigateResponse struct { XMLName xml.Name `xml:"navigateResponse"` Source string `xml:"source,attr"` SourceAccount string `xml:"sourceAccount,attr,omitempty"` TotalItems int `xml:"totalItems"` Items []NavigateItem `xml:"items>item"` } // NavigateItem represents a single item in a navigate response type NavigateItem struct { XMLName xml.Name `xml:"item"` Playable int `xml:"Playable,attr,omitempty"` Name string `xml:"name"` Type string `xml:"type"` ContentItem *ContentItem `xml:"ContentItem,omitempty"` MediaItemContainer *MediaItemContainer `xml:"mediaItemContainer,omitempty"` ArtistName string `xml:"artistName,omitempty"` AlbumName string `xml:"albumName,omitempty"` } // MediaItemContainer represents a media container within a navigate item type MediaItemContainer struct { XMLName xml.Name `xml:"mediaItemContainer"` Offset int `xml:"offset,attr"` ContentItem *ContentItem `xml:"ContentItem,omitempty"` } // AddStationRequest represents a request to add a station type AddStationRequest struct { XMLName xml.Name `xml:"addStation"` Source string `xml:"source,attr"` SourceAccount string `xml:"sourceAccount,attr,omitempty"` Token string `xml:"token,attr,omitempty"` Name string `xml:"name"` } // RemoveStationRequest represents a request to remove a station // Note: For removeStation, we send the ContentItem directly, not wrapped type RemoveStationRequest = ContentItem // StationResponse represents the response from add/remove station operations type StationResponse struct { XMLName xml.Name `xml:"status"` Status string `xml:",chardata"` } // SearchStationRequest represents a request to search for stations type SearchStationRequest struct { XMLName xml.Name `xml:"search"` Source string `xml:"source,attr"` SourceAccount string `xml:"sourceAccount,attr,omitempty"` SearchTerm string `xml:",chardata"` } // SearchStationResponse represents the response from a station search type SearchStationResponse struct { XMLName xml.Name `xml:"results"` DeviceID string `xml:"deviceID,attr"` Source string `xml:"source,attr"` SourceAccount string `xml:"sourceAccount,attr,omitempty"` Songs []SearchResult `xml:"songs>searchResult"` Artists []SearchResult `xml:"artists>searchResult"` Stations []SearchResult `xml:"stations>searchResult"` } // SearchResult represents a single search result (song, artist, or station) type SearchResult struct { XMLName xml.Name `xml:"searchResult"` Source string `xml:"source,attr"` SourceAccount string `xml:"sourceAccount,attr,omitempty"` Token string `xml:"token,attr"` Name string `xml:"name"` Artist string `xml:"artist,omitempty"` Album string `xml:"album,omitempty"` Logo string `xml:"logo,omitempty"` Description string `xml:"description,omitempty"` } // NewNavigateRequest creates a new navigate request for browsing content func NewNavigateRequest(source, sourceAccount string, startItem, numItems int) *NavigateRequest { return &NavigateRequest{ Source: source, SourceAccount: sourceAccount, StartItem: startItem, NumItems: numItems, } } // NewNavigateRequestWithMenu creates a navigate request with menu and sort parameters func NewNavigateRequestWithMenu(source, sourceAccount, menu, sort string, startItem, numItems int) *NavigateRequest { return &NavigateRequest{ Source: source, SourceAccount: sourceAccount, Menu: menu, Sort: sort, StartItem: startItem, NumItems: numItems, } } // NewNavigateRequestWithItem creates a navigate request to browse a specific container item func NewNavigateRequestWithItem(source, sourceAccount string, startItem, numItems int, item *ContentItem) *NavigateRequest { navigateItem := &NavigateItem{ Playable: 1, Name: item.ItemName, Type: "dir", ContentItem: item, } return &NavigateRequest{ Source: source, SourceAccount: sourceAccount, StartItem: startItem, NumItems: numItems, Item: navigateItem, } } // NewAddStationRequest creates a new add station request func NewAddStationRequest(source, sourceAccount, token, name string) *AddStationRequest { return &AddStationRequest{ Source: source, SourceAccount: sourceAccount, Token: token, Name: name, } } // NewRemoveStationRequest creates a new remove station request func NewRemoveStationRequest(contentItem *ContentItem) *RemoveStationRequest { return contentItem } // NewSearchStationRequest creates a new search station request func NewSearchStationRequest(source, sourceAccount, searchTerm string) *SearchStationRequest { return &SearchStationRequest{ Source: source, SourceAccount: sourceAccount, SearchTerm: searchTerm, } } // GetPlayableItems returns only the playable items from the navigate response func (nr *NavigateResponse) GetPlayableItems() []NavigateItem { var playable []NavigateItem for _, item := range nr.Items { if item.Playable == 1 { playable = append(playable, item) } } return playable } // GetDirectories returns only the directory items from the navigate response func (nr *NavigateResponse) GetDirectories() []NavigateItem { var directories []NavigateItem for _, item := range nr.Items { if item.Type == "dir" { directories = append(directories, item) } } return directories } // GetTracks returns only the track items from the navigate response func (nr *NavigateResponse) GetTracks() []NavigateItem { var tracks []NavigateItem for _, item := range nr.Items { if item.Type == "track" { tracks = append(tracks, item) } } return tracks } // GetStations returns only the station items from the navigate response func (nr *NavigateResponse) GetStations() []NavigateItem { var stations []NavigateItem for _, item := range nr.Items { if item.Type == "stationurl" || (item.ContentItem != nil && item.ContentItem.Type == "stationurl") { stations = append(stations, item) } } return stations } // IsEmpty returns true if the navigate response contains no items func (nr *NavigateResponse) IsEmpty() bool { return nr.TotalItems == 0 || len(nr.Items) == 0 } // GetDisplayName returns the display name for a navigate item func (ni *NavigateItem) GetDisplayName() string { if ni.Name != "" { return ni.Name } if ni.ContentItem != nil && ni.ContentItem.ItemName != "" { return ni.ContentItem.ItemName } return "Unknown Item" } // IsPlayable returns true if the navigate item can be played directly func (ni *NavigateItem) IsPlayable() bool { return ni.Playable == 1 } // IsDirectory returns true if the navigate item is a directory/container func (ni *NavigateItem) IsDirectory() bool { return ni.Type == "dir" } // IsTrack returns true if the navigate item is a track func (ni *NavigateItem) IsTrack() bool { return ni.Type == "track" } // IsStation returns true if the navigate item is a radio station func (ni *NavigateItem) IsStation() bool { return ni.Type == "stationurl" || (ni.ContentItem != nil && ni.ContentItem.Type == "stationurl") } // GetContentItem returns the ContentItem for this navigate item func (ni *NavigateItem) GetContentItem() *ContentItem { return ni.ContentItem } // GetArtwork returns the artwork URL if available func (ni *NavigateItem) GetArtwork() string { if ni.ContentItem != nil && ni.ContentItem.ContainerArt != "" { return ni.ContentItem.ContainerArt } return "" } // GetAllResults returns all search results regardless of type func (sr *SearchStationResponse) GetAllResults() []SearchResult { var allResults []SearchResult allResults = append(allResults, sr.Songs...) allResults = append(allResults, sr.Artists...) allResults = append(allResults, sr.Stations...) return allResults } // GetSongs returns only song results func (sr *SearchStationResponse) GetSongs() []SearchResult { return sr.Songs } // GetArtists returns only artist results func (sr *SearchStationResponse) GetArtists() []SearchResult { return sr.Artists } // GetStations returns only station results func (sr *SearchStationResponse) GetStations() []SearchResult { return sr.Stations } // HasResults returns true if the search response contains any results func (sr *SearchStationResponse) HasResults() bool { return len(sr.Songs) > 0 || len(sr.Artists) > 0 || len(sr.Stations) > 0 } // GetResultCount returns the total number of results func (sr *SearchStationResponse) GetResultCount() int { return len(sr.Songs) + len(sr.Artists) + len(sr.Stations) } // IsEmpty returns true if the search response contains no results func (sr *SearchStationResponse) IsEmpty() bool { return !sr.HasResults() } // GetDisplayName returns the display name for a search result func (sr *SearchResult) GetDisplayName() string { if sr.Name != "" { return sr.Name } return "Unknown" } // GetArtworkURL returns the logo/artwork URL if available func (sr *SearchResult) GetArtworkURL() string { return sr.Logo } // IsArtist returns true if this is an artist result func (sr *SearchResult) IsArtist() bool { // Artist result: no artist field (name is the artist) and no description return sr.Artist == "" && sr.Album == "" && sr.Description == "" } // IsSong returns true if this is a song result func (sr *SearchResult) IsSong() bool { // Song result: has artist field populated return sr.Artist != "" } // IsStation returns true if this is a station result func (sr *SearchResult) IsStation() bool { // Station result: has description or is neither artist nor song return sr.Description != "" || (!sr.IsSong() && !sr.IsArtist()) } // GetFullTitle returns a full title with artist if available func (sr *SearchResult) GetFullTitle() string { if sr.Artist != "" { return sr.Name + " - " + sr.Artist } return sr.Name }