// Package models provides data structures and types for music service account management // on Bose SoundTouch devices. package models import ( "encoding/xml" "fmt" ) // MusicServiceCredentials represents credentials for music service account operations type MusicServiceCredentials struct { XMLName xml.Name `xml:"credentials"` Source string `xml:"source,attr"` DisplayName string `xml:"displayName,attr,omitempty"` User string `xml:"user"` Pass string `xml:"pass"` } // NewMusicServiceCredentials creates new music service credentials func NewMusicServiceCredentials(source, displayName, user, pass string) *MusicServiceCredentials { return &MusicServiceCredentials{ Source: source, DisplayName: displayName, User: user, Pass: pass, } } // NewSpotifyCredentials creates credentials for Spotify service func NewSpotifyCredentials(user, pass string) *MusicServiceCredentials { return NewMusicServiceCredentials("SPOTIFY", "Spotify Premium", user, pass) } // NewPandoraCredentials creates credentials for Pandora service func NewPandoraCredentials(user, pass string) *MusicServiceCredentials { return NewMusicServiceCredentials("PANDORA", "Pandora Music Service", user, pass) } // NewStoredMusicCredentials creates credentials for STORED_MUSIC (NAS/UPnP) service func NewStoredMusicCredentials(user, displayName string) *MusicServiceCredentials { return NewMusicServiceCredentials("STORED_MUSIC", displayName, user, "") } // NewAmazonMusicCredentials creates credentials for Amazon Music service func NewAmazonMusicCredentials(user, pass string) *MusicServiceCredentials { return NewMusicServiceCredentials("AMAZON", "Amazon Music", user, pass) } // NewDeezerCredentials creates credentials for Deezer service func NewDeezerCredentials(user, pass string) *MusicServiceCredentials { return NewMusicServiceCredentials("DEEZER", "Deezer Premium", user, pass) } // NewIHeartRadioCredentials creates credentials for iHeartRadio service func NewIHeartRadioCredentials(user, pass string) *MusicServiceCredentials { return NewMusicServiceCredentials("IHEART", "iHeartRadio", user, pass) } // Validate ensures the credentials have required fields func (cred *MusicServiceCredentials) Validate() error { if cred.Source == "" { return fmt.Errorf("source cannot be empty") } if cred.User == "" { return fmt.Errorf("user cannot be empty") } // STORED_MUSIC typically doesn't require a password if cred.Source != "STORED_MUSIC" && cred.Pass == "" { return fmt.Errorf("password cannot be empty for %s", cred.Source) } return nil } // IsForRemoval returns true if these credentials are for removing an account (empty password) func (cred *MusicServiceCredentials) IsForRemoval() bool { return cred.Pass == "" } // HasPassword returns true if credentials include a password func (cred *MusicServiceCredentials) HasPassword() bool { return cred.Pass != "" } // GetDescription returns a human-readable description of the service func (cred *MusicServiceCredentials) GetDescription() string { if cred.DisplayName != "" { return cred.DisplayName } switch cred.Source { case "SPOTIFY": return "Spotify Premium" case "PANDORA": return "Pandora Music Service" case "AMAZON": return "Amazon Music" case "DEEZER": return "Deezer Premium" case "IHEART": return "iHeartRadio" case "STORED_MUSIC": return "Network Music Library" case "LOCAL_MUSIC": return "Local Music Server" default: return cred.Source } } // OAuthCredentials represents the credentials sent to /setMusicServiceOAuthAccount type OAuthCredentials struct { XMLName xml.Name `xml:"OAuthCredentials"` Source string `xml:"source,attr"` DisplayName string `xml:"displayName,attr,omitempty"` User string `xml:"user"` Code string `xml:"code"` Version string `xml:"version"` } // NewSpotifyOAuthCredentials creates OAuth credentials for Spotify func NewSpotifyOAuthCredentials(user, code, displayName string) *OAuthCredentials { if displayName == "" { displayName = user } return &OAuthCredentials{ Source: "SPOTIFY", DisplayName: displayName, User: user, Code: code, Version: "token_version_3", } } // NewAmazonOAuthCredentials creates OAuth credentials for Amazon Music (cs1 / "token"). // code is the AmazonSecret JSON envelope stored as the credential in Sources.xml. func NewAmazonOAuthCredentials(user, code, displayName string) *OAuthCredentials { if displayName == "" { displayName = user } return &OAuthCredentials{ Source: "AMAZON", DisplayName: displayName, User: user, Code: code, Version: "token", } } // MusicServiceAccountResponse represents the response from account management operations type MusicServiceAccountResponse struct { XMLName xml.Name `xml:"status"` Status string `xml:",chardata"` } // SourcesUpdatedResponse represents the response from /notification type SourcesUpdatedResponse struct { XMLName xml.Name `xml:"status"` Status string `xml:",chardata"` } // IsSuccess returns true if the account operation was successful func (resp *MusicServiceAccountResponse) IsSuccess() bool { return resp.Status == "/setMusicServiceAccount" || resp.Status == "/removeMusicServiceAccount" || resp.Status == "/notification" }