164 lines
8.9 KiB
Go
164 lines
8.9 KiB
Go
// Package subsonic implements the original Subsonic REST API (through
|
|
// version 1.16.1) on top of VibeSonic's existing database, scanner,
|
|
// streaming, and cover art packages. It does not duplicate business
|
|
// logic: every handler here is a thin adapter from Subsonic's wire
|
|
// format onto internal/database, internal/library, internal/streaming,
|
|
// and internal/coverart.
|
|
//
|
|
// One set of Go structs (this file and its model_*.go siblings) is
|
|
// shared between XML and JSON output via struct tags, so response
|
|
// shape can never drift between formats — see response.go for how they
|
|
// are serialized.
|
|
package subsonic
|
|
|
|
import "encoding/xml"
|
|
|
|
// ServerVersion is the highest Subsonic REST API version VibeSonic
|
|
// implements. Requests from a client claiming a newer protocol version
|
|
// are rejected (error code 20); older clients are accepted, since the
|
|
// protocol is additive.
|
|
const ServerVersion = "1.16.1"
|
|
|
|
// Response is the envelope every /rest/* call returns: exactly one of
|
|
// the payload fields below is set, matching the single child element
|
|
// real Subsonic servers emit inside <subsonic-response>.
|
|
type Response struct {
|
|
XMLName xml.Name `xml:"subsonic-response" json:"-"`
|
|
Xmlns string `xml:"xmlns,attr" json:"-"`
|
|
Status string `xml:"status,attr" json:"status"`
|
|
Version string `xml:"version,attr" json:"version"`
|
|
|
|
// OpenSubsonic core response additions (always populated): the
|
|
// server's real product name and build version, and a flag
|
|
// confirming OpenSubsonic support so clients can detect it from any
|
|
// response, not just getOpenSubsonicExtensions.
|
|
Type string `xml:"type,attr" json:"type"`
|
|
ServerVersion string `xml:"serverVersion,attr" json:"serverVersion"`
|
|
OpenSubsonic bool `xml:"openSubsonic,attr" json:"openSubsonic"`
|
|
|
|
Error *Error `xml:"error,omitempty" json:"error,omitempty"`
|
|
|
|
License *License `xml:"license,omitempty" json:"license,omitempty"`
|
|
MusicFolders *MusicFolders `xml:"musicFolders,omitempty" json:"musicFolders,omitempty"`
|
|
Indexes *Indexes `xml:"indexes,omitempty" json:"indexes,omitempty"`
|
|
Directory *Directory `xml:"directory,omitempty" json:"directory,omitempty"`
|
|
Genres *Genres `xml:"genres,omitempty" json:"genres,omitempty"`
|
|
Artists *ArtistsID3 `xml:"artists,omitempty" json:"artists,omitempty"`
|
|
Artist *ArtistWithAlbumsID3 `xml:"artist,omitempty" json:"artist,omitempty"`
|
|
Album *AlbumWithSongsID3 `xml:"album,omitempty" json:"album,omitempty"`
|
|
Song *Child `xml:"song,omitempty" json:"song,omitempty"`
|
|
Videos *Videos `xml:"videos,omitempty" json:"videos,omitempty"`
|
|
VideoInfo *VideoInfo `xml:"videoInfo,omitempty" json:"videoInfo,omitempty"`
|
|
ArtistInfo *ArtistInfo `xml:"artistInfo,omitempty" json:"artistInfo,omitempty"`
|
|
ArtistInfo2 *ArtistInfo2 `xml:"artistInfo2,omitempty" json:"artistInfo2,omitempty"`
|
|
AlbumInfo *AlbumInfo `xml:"albumInfo,omitempty" json:"albumInfo,omitempty"`
|
|
SimilarSongs *SimilarSongs `xml:"similarSongs,omitempty" json:"similarSongs,omitempty"`
|
|
SimilarSongs2 *SimilarSongs2 `xml:"similarSongs2,omitempty" json:"similarSongs2,omitempty"`
|
|
TopSongs *TopSongs `xml:"topSongs,omitempty" json:"topSongs,omitempty"`
|
|
AlbumList *AlbumList `xml:"albumList,omitempty" json:"albumList,omitempty"`
|
|
AlbumList2 *AlbumList2 `xml:"albumList2,omitempty" json:"albumList2,omitempty"`
|
|
RandomSongs *Songs `xml:"randomSongs,omitempty" json:"randomSongs,omitempty"`
|
|
SongsByGenre *Songs `xml:"songsByGenre,omitempty" json:"songsByGenre,omitempty"`
|
|
NowPlaying *NowPlaying `xml:"nowPlaying,omitempty" json:"nowPlaying,omitempty"`
|
|
Starred *Starred `xml:"starred,omitempty" json:"starred,omitempty"`
|
|
Starred2 *Starred2 `xml:"starred2,omitempty" json:"starred2,omitempty"`
|
|
SearchResult *SearchResult `xml:"searchResult,omitempty" json:"searchResult,omitempty"`
|
|
SearchResult2 *SearchResult2 `xml:"searchResult2,omitempty" json:"searchResult2,omitempty"`
|
|
SearchResult3 *SearchResult3 `xml:"searchResult3,omitempty" json:"searchResult3,omitempty"`
|
|
Playlists *Playlists `xml:"playlists,omitempty" json:"playlists,omitempty"`
|
|
Playlist *PlaylistWithSongs `xml:"playlist,omitempty" json:"playlist,omitempty"`
|
|
JukeboxStatus *JukeboxStatus `xml:"jukeboxStatus,omitempty" json:"jukeboxStatus,omitempty"`
|
|
JukeboxPlaylist *JukeboxPlaylist `xml:"jukeboxPlaylist,omitempty" json:"jukeboxPlaylist,omitempty"`
|
|
InternetRadioStations *InternetRadioStations `xml:"internetRadioStations,omitempty" json:"internetRadioStations,omitempty"`
|
|
ChatMessages *ChatMessages `xml:"chatMessages,omitempty" json:"chatMessages,omitempty"`
|
|
User *User `xml:"user,omitempty" json:"user,omitempty"`
|
|
Users *Users `xml:"users,omitempty" json:"users,omitempty"`
|
|
Bookmarks *Bookmarks `xml:"bookmarks,omitempty" json:"bookmarks,omitempty"`
|
|
PlayQueue *PlayQueue `xml:"playQueue,omitempty" json:"playQueue,omitempty"`
|
|
Shares *Shares `xml:"shares,omitempty" json:"shares,omitempty"`
|
|
Podcasts *Podcasts `xml:"podcasts,omitempty" json:"podcasts,omitempty"`
|
|
NewestPodcasts *NewestPodcasts `xml:"newestPodcasts,omitempty" json:"newestPodcasts,omitempty"`
|
|
ScanStatus *ScanStatus `xml:"scanStatus,omitempty" json:"scanStatus,omitempty"`
|
|
Lyrics *Lyrics `xml:"lyrics,omitempty" json:"lyrics,omitempty"`
|
|
|
|
// OpenSubsonic extensions.
|
|
OpenSubsonicExtensions []OpenSubsonicExtension `xml:"openSubsonicExtensions,omitempty" json:"openSubsonicExtensions,omitempty"`
|
|
TokenInfo *TokenInfo `xml:"tokenInfo,omitempty" json:"tokenInfo,omitempty"`
|
|
PodcastEpisode *PodcastEpisode `xml:"podcastEpisode,omitempty" json:"podcastEpisode,omitempty"`
|
|
LyricsList *LyricsList `xml:"lyricsList,omitempty" json:"lyricsList,omitempty"`
|
|
PlayQueueByIndex *PlayQueueByIndex `xml:"playQueueByIndex,omitempty" json:"playQueueByIndex,omitempty"`
|
|
// SonicMatch is a top-level array (not wrapped in an object) on both
|
|
// getSonicSimilarTracks and findSonicPath, per the OpenSubsonic
|
|
// schema — the two endpoints are otherwise unrelated, so there is no
|
|
// shared wrapper type to name it after.
|
|
SonicMatch []SonicMatch `xml:"sonicMatch,omitempty" json:"sonicMatch,omitempty"`
|
|
TranscodeDecision *TranscodeDecision `xml:"transcodeDecision,omitempty" json:"transcodeDecision,omitempty"`
|
|
}
|
|
|
|
// OpenSubsonicExtension describes one extension this server supports,
|
|
// as returned by getOpenSubsonicExtensions.
|
|
type OpenSubsonicExtension struct {
|
|
Name string `xml:"name,attr" json:"name"`
|
|
Versions []int `xml:"versions" json:"versions"`
|
|
}
|
|
|
|
type TokenInfo struct {
|
|
Username string `xml:"username,attr" json:"username"`
|
|
}
|
|
|
|
// ServerName is the real product name advertised in every response's
|
|
// "type" attribute, per the OpenSubsonic base response additions.
|
|
const ServerName = "vibesonic"
|
|
|
|
// AppVersion is VibeSonic's own build version, advertised in every
|
|
// response's "serverVersion" attribute. Overridable at link time
|
|
// (matching cmd/vibesonic's version variable); defaults to "dev".
|
|
var AppVersion = "dev"
|
|
|
|
func newResponse() *Response {
|
|
return &Response{
|
|
Xmlns: "http://subsonic.org/restapi",
|
|
Status: "ok",
|
|
Version: ServerVersion,
|
|
Type: ServerName,
|
|
ServerVersion: AppVersion,
|
|
OpenSubsonic: true,
|
|
}
|
|
}
|
|
|
|
// Error is the payload of a failed response.
|
|
type Error struct {
|
|
Code int `xml:"code,attr" json:"code"`
|
|
Message string `xml:"message,attr" json:"message"`
|
|
HelpURL string `xml:"helpUrl,attr,omitempty" json:"helpUrl,omitempty"`
|
|
}
|
|
|
|
// --- System ---
|
|
|
|
type License struct {
|
|
Valid bool `xml:"valid,attr" json:"valid"`
|
|
Email string `xml:"email,attr,omitempty" json:"email,omitempty"`
|
|
LicenseExpires string `xml:"licenseExpires,attr,omitempty" json:"licenseExpires,omitempty"`
|
|
}
|
|
|
|
// --- Music folders / genres ---
|
|
|
|
type MusicFolders struct {
|
|
MusicFolder []MusicFolder `xml:"musicFolder" json:"musicFolder"`
|
|
}
|
|
|
|
type MusicFolder struct {
|
|
ID string `xml:"id,attr" json:"id"`
|
|
Name string `xml:"name,attr" json:"name"`
|
|
}
|
|
|
|
type Genres struct {
|
|
Genre []Genre `xml:"genre" json:"genre"`
|
|
}
|
|
|
|
type Genre struct {
|
|
Name string `xml:",chardata" json:"value"`
|
|
SongCount int `xml:"songCount,attr" json:"songCount"`
|
|
AlbumCount int `xml:"albumCount,attr" json:"albumCount"`
|
|
}
|