package subsonic import ( "time" "github.com/benya/temporserv/internal/library" "github.com/benya/temporserv/internal/playlist" "github.com/benya/temporserv/internal/scanner" ) type Envelope struct { SubsonicResponse Response `json:"subsonic-response"` } type Response struct { Status string `json:"status"` Version string `json:"version"` Type string `json:"type"` Server string `json:"serverVersion"` OpenAPI bool `json:"openSubsonic"` Artists []ArtistRef `json:"artists,omitempty"` Artist *ArtistFull `json:"artist,omitempty"` Album *AlbumFull `json:"album,omitempty"` Song *SongFull `json:"song,omitempty"` RandomSong []SongRef `json:"randomSongs,omitempty"` SearchResult3 *SearchResult3 `json:"searchResult3,omitempty"` Starred2 *Starred2 `json:"starred2,omitempty"` Playlists *Playlists `json:"playlists,omitempty"` Playlist *Playlist `json:"playlist,omitempty"` ScanStatus *ScanStatus `json:"scanStatus,omitempty"` Error *ErrorRef `json:"error,omitempty"` } type ArtistRef struct { ID string `json:"id"` Name string `json:"name"` } type SongRef struct { ID string `json:"id"` Title string `json:"title"` Album string `json:"album"` Artist string `json:"artist"` AlbumID string `json:"albumId,omitempty"` ArtistID string `json:"artistId,omitempty"` CoverArt string `json:"coverArt,omitempty"` } type ArtistFull struct { ID string `json:"id"` Name string `json:"name"` CoverArt string `json:"coverArt,omitempty"` AlbumCount int `json:"albumCount,omitempty"` Albums []AlbumRef `json:"album,omitempty"` } type AlbumRef struct { ID string `json:"id"` Name string `json:"name"` Artist string `json:"artist"` ArtistID string `json:"artistId"` Year int `json:"year,omitempty"` CoverArt string `json:"coverArt,omitempty"` } type AlbumFull struct { ID string `json:"id"` Name string `json:"name"` Artist string `json:"artist"` ArtistID string `json:"artistId"` Year int `json:"year,omitempty"` CoverArt string `json:"coverArt,omitempty"` Song []SongRef `json:"song,omitempty"` } type SongFull struct { ID string `json:"id"` Title string `json:"title"` Album string `json:"album"` Artist string `json:"artist"` AlbumID string `json:"albumId"` ArtistID string `json:"artistId"` Track int `json:"track,omitempty"` Duration int `json:"duration,omitempty"` CoverArt string `json:"coverArt,omitempty"` } type ScanStatus struct { Scanning bool `json:"scanning"` Count int `json:"count"` FolderCount int `json:"folderCount"` LastError string `json:"lastError,omitempty"` StartedAt string `json:"startedAt,omitempty"` FinishedAt string `json:"finishedAt,omitempty"` } type SearchResult3 struct { Artist []ArtistRef `json:"artist,omitempty"` Album []AlbumRef `json:"album,omitempty"` Song []SongRef `json:"song,omitempty"` } type Starred2 struct { Artist []ArtistRef `json:"artist,omitempty"` Album []AlbumRef `json:"album,omitempty"` Song []SongRef `json:"song,omitempty"` } type Playlists struct { Playlist []PlaylistSummary `json:"playlist,omitempty"` } type Playlist struct { ID string `json:"id"` Name string `json:"name"` Owner string `json:"owner"` Public bool `json:"public"` Comment string `json:"comment,omitempty"` SongCount int `json:"songCount"` Duration int `json:"duration,omitempty"` Created string `json:"created,omitempty"` Changed string `json:"changed,omitempty"` Entry []SongRef `json:"entry,omitempty"` } type PlaylistSummary struct { ID string `json:"id"` Name string `json:"name"` Owner string `json:"owner"` Public bool `json:"public"` SongCount int `json:"songCount"` Duration int `json:"duration,omitempty"` Created string `json:"created,omitempty"` Changed string `json:"changed,omitempty"` } type ErrorRef struct { Code int `json:"code"` Message string `json:"message"` } func PingResponse() Envelope { return Envelope{ SubsonicResponse: Response{ Status: "ok", Version: "1.16.1", Type: "temporserv", Server: "0.1.0", OpenAPI: true, }, } } func ArtistsResponse(artists []library.Artist) Envelope { response := PingResponse() for _, artist := range artists { response.SubsonicResponse.Artists = append(response.SubsonicResponse.Artists, ArtistRef{ ID: artist.ID, Name: artist.Name, }) } return response } func RandomSongsResponse(tracks []library.Track) Envelope { response := PingResponse() for _, track := range tracks { response.SubsonicResponse.RandomSong = append(response.SubsonicResponse.RandomSong, SongRef{ ID: track.ID, Title: track.Title, Album: track.AlbumTitle, Artist: track.ArtistName, AlbumID: track.AlbumID, ArtistID: track.ArtistID, CoverArt: track.CoverArtID, }) } return response } func ArtistResponse(artist library.ArtistDetail) Envelope { response := PingResponse() item := &ArtistFull{ ID: artist.ID, Name: artist.Name, CoverArt: artist.CoverArtID, AlbumCount: artist.AlbumCount, } for _, album := range artist.Albums { item.Albums = append(item.Albums, AlbumRef{ ID: album.ID, Name: album.Title, Artist: album.ArtistName, ArtistID: album.ArtistID, Year: album.Year, CoverArt: album.CoverArtID, }) } response.SubsonicResponse.Artist = item return response } func Search3Response(results library.SearchResults) Envelope { response := PingResponse() payload := &SearchResult3{} for _, artist := range results.Artists { payload.Artist = append(payload.Artist, ArtistRef{ ID: artist.ID, Name: artist.Name, }) } for _, album := range results.Albums { payload.Album = append(payload.Album, AlbumRef{ ID: album.ID, Name: album.Title, Artist: album.ArtistName, ArtistID: album.ArtistID, Year: album.Year, CoverArt: album.CoverArtID, }) } for _, track := range results.Tracks { payload.Song = append(payload.Song, SongRef{ ID: track.ID, Title: track.Title, Album: track.AlbumTitle, Artist: track.ArtistName, AlbumID: track.AlbumID, ArtistID: track.ArtistID, CoverArt: track.CoverArtID, }) } response.SubsonicResponse.SearchResult3 = payload return response } func Starred2Response(results library.StarredResults) Envelope { response := PingResponse() payload := &Starred2{} for _, artist := range results.Artists { payload.Artist = append(payload.Artist, ArtistRef{ ID: artist.ID, Name: artist.Name, }) } for _, album := range results.Albums { payload.Album = append(payload.Album, AlbumRef{ ID: album.ID, Name: album.Title, Artist: album.ArtistName, ArtistID: album.ArtistID, Year: album.Year, CoverArt: album.CoverArtID, }) } for _, track := range results.Tracks { payload.Song = append(payload.Song, SongRef{ ID: track.ID, Title: track.Title, Album: track.AlbumTitle, Artist: track.ArtistName, AlbumID: track.AlbumID, ArtistID: track.ArtistID, CoverArt: track.CoverArtID, }) } response.SubsonicResponse.Starred2 = payload return response } func PlaylistsResponse(owner string, playlists []playlist.Summary) Envelope { response := PingResponse() payload := &Playlists{} for _, item := range playlists { payload.Playlist = append(payload.Playlist, PlaylistSummary{ ID: item.ID, Name: item.Name, Owner: owner, Public: item.Public, SongCount: item.SongCount, Duration: item.Duration, Created: formatTime(item.CreatedAt), Changed: formatTime(item.UpdatedAt), }) } response.SubsonicResponse.Playlists = payload return response } func PlaylistResponse(owner string, detail playlist.Detail) Envelope { response := PingResponse() item := &Playlist{ ID: detail.ID, Name: detail.Name, Owner: owner, Public: detail.Public, Comment: detail.Comment, SongCount: detail.SongCount, Duration: detail.Duration, Created: formatTime(detail.CreatedAt), Changed: formatTime(detail.UpdatedAt), } for _, track := range detail.Tracks { item.Entry = append(item.Entry, SongRef{ ID: track.ID, Title: track.Title, Album: track.AlbumTitle, Artist: track.ArtistName, AlbumID: track.AlbumID, ArtistID: track.ArtistID, CoverArt: track.CoverArtID, }) } response.SubsonicResponse.Playlist = item return response } func AlbumResponse(album library.AlbumDetail) Envelope { response := PingResponse() item := &AlbumFull{ ID: album.ID, Name: album.Title, Artist: album.ArtistName, ArtistID: album.ArtistID, Year: album.Year, CoverArt: album.CoverArtID, } for _, track := range album.Tracks { item.Song = append(item.Song, SongRef{ ID: track.ID, Title: track.Title, Album: track.AlbumTitle, Artist: track.ArtistName, }) } response.SubsonicResponse.Album = item return response } func SongResponse(track library.Track) Envelope { response := PingResponse() response.SubsonicResponse.Song = &SongFull{ ID: track.ID, Title: track.Title, Album: track.AlbumTitle, Artist: track.ArtistName, AlbumID: track.AlbumID, ArtistID: track.ArtistID, Track: track.TrackNumber, Duration: track.DurationSecs, CoverArt: track.CoverArtID, } return response } func ScanStatusResponse(status scanner.Status) Envelope { response := PingResponse() response.SubsonicResponse.ScanStatus = &ScanStatus{ Scanning: status.Scanning, Count: status.Tracks, FolderCount: status.Albums, LastError: status.LastError, StartedAt: formatTime(status.StartedAt), FinishedAt: formatTime(status.FinishedAt), } return response } func ErrorResponse(code int, message string) Envelope { response := PingResponse() response.SubsonicResponse.Status = "failed" response.SubsonicResponse.Error = &ErrorRef{ Code: code, Message: message, } return response } func formatTime(value time.Time) string { if value.IsZero() { return "" } return value.Format(time.RFC3339) }