mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-10 04:36:14 +00:00
This commit completes the music service account management implementation and resolves all golangci-lint issues across the codebase. Music Service Account Management: • Add/remove accounts for all major streaming services (Spotify, Pandora, Amazon Music, Deezer, iHeartRadio) • Support for network music libraries (NAS/UPnP/DLNA servers) • Generic account management with service-specific convenience methods • Full CLI integration with 14 account management commands • Comprehensive test coverage with mock HTTP servers • Complete API documentation and usage examples New CLI Commands: • account list - List configured accounts • account add/remove - Generic account management • account add-spotify/remove-spotify - Spotify Premium • account add-pandora/remove-pandora - Pandora Music Service • account add-amazon/remove-amazon - Amazon Music • account add-deezer/remove-deezer - Deezer Premium • account add-iheart/remove-iheart - iHeartRadio • account add-nas/remove-nas - Network music libraries New API Methods: • SetMusicServiceAccount() / RemoveMusicServiceAccount() - Generic methods • AddSpotifyAccount() / RemoveSpotifyAccount() - Convenience methods • AddPandoraAccount() / RemovePandoraAccount() - Convenience methods • AddAmazonMusicAccount() / RemoveAmazonMusicAccount() - Convenience methods • AddDeezerAccount() / RemoveDeezerAccount() - Convenience methods • AddIHeartRadioAccount() / RemoveIHeartRadioAccount() - Convenience methods • AddStoredMusicAccount() / RemoveStoredMusicAccount() - Network libraries golangci-lint Fixes (36 issues resolved): • errcheck (3): Fixed unchecked w.Write() returns in tests • gocritic (3): Rewrote if-else chains to switch statements • gocyclo (6): Reduced cyclomatic complexity via helper function extraction • govet (12): Removed unused test data and field assignments • revive (6): Added package comments and fixed unused parameters • staticcheck (2): Replaced deprecated strings.Title usage • thelper (6): Added t.Helper() calls to test helper functions • unused (1): Removed unused createTestApp() function • whitespace/wsl_v5 (7): Fixed whitespace and formatting issues Code Quality Improvements: • All functions now have complexity < 15 (down from max 28) • Consistent error handling and validation patterns • Better separation of concerns with extracted helper functions • Zero external dependencies added for simple fixes • Comprehensive documentation with usage examples • Full backward compatibility maintained Files Added: • pkg/models/account.go - Account management models • pkg/models/account_test.go - Account model tests • pkg/client/account_test.go - Account client tests • cmd/soundtouch-cli/cmd_account.go - Account CLI commands • examples/account-management/ - Complete usage example • Updated docs/CLI-REFERENCE.md with account management section The implementation provides a complete, production-ready music service account management system with full CLI and programmatic API support.
257 lines
7.0 KiB
Go
257 lines
7.0 KiB
Go
package models
|
|
|
|
import "encoding/xml"
|
|
|
|
// RecentsResponse represents the response from the /recents endpoint
|
|
type RecentsResponse struct {
|
|
XMLName xml.Name `xml:"recents"`
|
|
Items []RecentsResponseItem `xml:"recent"`
|
|
}
|
|
|
|
// RecentsResponseItem represents a recently played item from the /recents API endpoint
|
|
type RecentsResponseItem struct {
|
|
XMLName xml.Name `xml:"recent"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
UTCTime int64 `xml:"utcTime,attr"`
|
|
ID string `xml:"id,attr,omitempty"`
|
|
ContentItem *ContentItem `xml:"contentItem"`
|
|
}
|
|
|
|
// GetItemCount returns the number of recent items
|
|
func (r *RecentsResponse) GetItemCount() int {
|
|
return len(r.Items)
|
|
}
|
|
|
|
// IsEmpty returns true if there are no recent items
|
|
func (r *RecentsResponse) IsEmpty() bool {
|
|
return len(r.Items) == 0
|
|
}
|
|
|
|
// GetMostRecent returns the most recently played item (first in the list)
|
|
func (r *RecentsResponse) GetMostRecent() *RecentsResponseItem {
|
|
if len(r.Items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return &r.Items[0]
|
|
}
|
|
|
|
// GetItemsBySource returns recent items filtered by source type
|
|
func (r *RecentsResponse) GetItemsBySource(source string) []RecentsResponseItem {
|
|
var filtered []RecentsResponseItem
|
|
|
|
for _, item := range r.Items {
|
|
if item.ContentItem != nil && item.ContentItem.Source == source {
|
|
filtered = append(filtered, item)
|
|
}
|
|
}
|
|
|
|
return filtered
|
|
}
|
|
|
|
// GetSpotifyItems returns only Spotify recent items
|
|
func (r *RecentsResponse) GetSpotifyItems() []RecentsResponseItem {
|
|
return r.GetItemsBySource("SPOTIFY")
|
|
}
|
|
|
|
// GetLocalMusicItems returns only local music recent items
|
|
func (r *RecentsResponse) GetLocalMusicItems() []RecentsResponseItem {
|
|
return r.GetItemsBySource("LOCAL_MUSIC")
|
|
}
|
|
|
|
// GetStoredMusicItems returns only stored music recent items
|
|
func (r *RecentsResponse) GetStoredMusicItems() []RecentsResponseItem {
|
|
return r.GetItemsBySource("STORED_MUSIC")
|
|
}
|
|
|
|
// GetTuneInItems returns only TuneIn radio recent items
|
|
func (r *RecentsResponse) GetTuneInItems() []RecentsResponseItem {
|
|
return r.GetItemsBySource("TUNEIN")
|
|
}
|
|
|
|
// GetPandoraItems returns only Pandora recent items
|
|
func (r *RecentsResponse) GetPandoraItems() []RecentsResponseItem {
|
|
return r.GetItemsBySource("PANDORA")
|
|
}
|
|
|
|
// GetPresetableItems returns recent items that can be saved as presets
|
|
func (r *RecentsResponse) GetPresetableItems() []RecentsResponseItem {
|
|
var presetable []RecentsResponseItem
|
|
|
|
for _, item := range r.Items {
|
|
if item.ContentItem != nil && item.ContentItem.IsPresetable {
|
|
presetable = append(presetable, item)
|
|
}
|
|
}
|
|
|
|
return presetable
|
|
}
|
|
|
|
// GetItemsByType returns recent items filtered by content type
|
|
func (r *RecentsResponse) GetItemsByType(contentType string) []RecentsResponseItem {
|
|
var filtered []RecentsResponseItem
|
|
|
|
for _, item := range r.Items {
|
|
if item.ContentItem != nil && item.ContentItem.Type == contentType {
|
|
filtered = append(filtered, item)
|
|
}
|
|
}
|
|
|
|
return filtered
|
|
}
|
|
|
|
// GetTracks returns only track-type recent items
|
|
func (r *RecentsResponse) GetTracks() []RecentsResponseItem {
|
|
return r.GetItemsByType("track")
|
|
}
|
|
|
|
// GetStations returns only station-type recent items
|
|
func (r *RecentsResponse) GetStations() []RecentsResponseItem {
|
|
return r.GetItemsByType("stationurl")
|
|
}
|
|
|
|
// GetPlaylistsAndAlbums returns playlist and album-type recent items
|
|
func (r *RecentsResponse) GetPlaylistsAndAlbums() []RecentsResponseItem {
|
|
var items []RecentsResponseItem
|
|
|
|
for _, item := range r.Items {
|
|
if item.ContentItem != nil {
|
|
contentType := item.ContentItem.Type
|
|
if contentType == "playlist" || contentType == "album" || contentType == "container" {
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
}
|
|
|
|
return items
|
|
}
|
|
|
|
// HasContent returns true if the recent item has content information
|
|
func (ri *RecentsResponseItem) HasContent() bool {
|
|
return ri.ContentItem != nil
|
|
}
|
|
|
|
// GetDisplayName returns the display name for the recent item
|
|
func (ri *RecentsResponseItem) GetDisplayName() string {
|
|
if ri.ContentItem != nil && ri.ContentItem.ItemName != "" {
|
|
return ri.ContentItem.ItemName
|
|
}
|
|
|
|
return "Unknown Item"
|
|
}
|
|
|
|
// GetSource returns the content source
|
|
func (ri *RecentsResponseItem) GetSource() string {
|
|
if ri.ContentItem != nil {
|
|
return ri.ContentItem.Source
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// GetSourceAccount returns the source account
|
|
func (ri *RecentsResponseItem) GetSourceAccount() string {
|
|
if ri.ContentItem != nil {
|
|
return ri.ContentItem.SourceAccount
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// GetLocation returns the content location
|
|
func (ri *RecentsResponseItem) GetLocation() string {
|
|
if ri.ContentItem != nil {
|
|
return ri.ContentItem.Location
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// GetContentType returns the content type
|
|
func (ri *RecentsResponseItem) GetContentType() string {
|
|
if ri.ContentItem != nil {
|
|
return ri.ContentItem.Type
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// IsPresetable returns true if the item can be saved as a preset
|
|
func (ri *RecentsResponseItem) IsPresetable() bool {
|
|
return ri.ContentItem != nil && ri.ContentItem.IsPresetable
|
|
}
|
|
|
|
// IsTrack returns true if the recent item is a track
|
|
func (ri *RecentsResponseItem) IsTrack() bool {
|
|
return ri.GetContentType() == "track"
|
|
}
|
|
|
|
// IsStation returns true if the recent item is a radio station
|
|
func (ri *RecentsResponseItem) IsStation() bool {
|
|
return ri.GetContentType() == "stationurl"
|
|
}
|
|
|
|
// IsPlaylist returns true if the recent item is a playlist
|
|
func (ri *RecentsResponseItem) IsPlaylist() bool {
|
|
return ri.GetContentType() == "playlist"
|
|
}
|
|
|
|
// IsAlbum returns true if the recent item is an album
|
|
func (ri *RecentsResponseItem) IsAlbum() bool {
|
|
return ri.GetContentType() == "album"
|
|
}
|
|
|
|
// IsContainer returns true if the recent item is a container (folder/collection)
|
|
func (ri *RecentsResponseItem) IsContainer() bool {
|
|
contentType := ri.GetContentType()
|
|
return contentType == "container" || contentType == "dir"
|
|
}
|
|
|
|
// IsSpotifyContent returns true if the recent item is from Spotify
|
|
func (ri *RecentsResponseItem) IsSpotifyContent() bool {
|
|
return ri.GetSource() == "SPOTIFY"
|
|
}
|
|
|
|
// IsLocalContent returns true if the recent item is from local sources
|
|
func (ri *RecentsResponseItem) IsLocalContent() bool {
|
|
source := ri.GetSource()
|
|
return source == "LOCAL_MUSIC" || source == "STORED_MUSIC"
|
|
}
|
|
|
|
// IsStreamingContent returns true if the recent item is from streaming services
|
|
func (ri *RecentsResponseItem) IsStreamingContent() bool {
|
|
source := ri.GetSource()
|
|
|
|
return source == "SPOTIFY" || source == "PANDORA" || source == "TUNEIN" ||
|
|
source == "AMAZON" || source == "DEEZER" || source == "IHEART"
|
|
}
|
|
|
|
// GetArtwork returns the artwork URL if available
|
|
func (ri *RecentsResponseItem) GetArtwork() string {
|
|
if ri.ContentItem != nil {
|
|
return ri.ContentItem.ContainerArt
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// HasArtwork returns true if artwork is available
|
|
func (ri *RecentsResponseItem) HasArtwork() bool {
|
|
return ri.GetArtwork() != ""
|
|
}
|
|
|
|
// GetUTCTime returns the UTC timestamp when the item was played
|
|
func (ri *RecentsResponseItem) GetUTCTime() int64 {
|
|
return ri.UTCTime
|
|
}
|
|
|
|
// HasID returns true if the recent item has an ID
|
|
func (ri *RecentsResponseItem) HasID() bool {
|
|
return ri.ID != ""
|
|
}
|
|
|
|
// GetID returns the recent item ID
|
|
func (ri *RecentsResponseItem) GetID() string {
|
|
return ri.ID
|
|
}
|