Phase 3: OpenSubsonic core (response envelope, error codes 42-44, getOpenSubsonicExtensions) and apiKeyAuthentication extension with tokenInfo, web API key management

This commit is contained in:
2026-08-23 08:04:01 -05:00
parent 3b4883780b
commit e38d31d384
17 changed files with 763 additions and 37 deletions
+50 -1
View File
@@ -28,6 +28,14 @@ type Response struct {
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"`
@@ -72,16 +80,57 @@ type Response struct {
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}
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 ---