mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
feat(jellyfin): add authenticated System/Info endpoint
Wire up GET /System/Info returning the previously unused dto.SystemInfo, available to any authenticated user, matching real Jellyfin's authorization (FirstTimeSetupOrIgnoreParentalControl, not admin-only). Feishin calls this endpoint on connect and reads Version to feature-gate; it previously got the unhandled-route 404. The advertised version stays 10.8.13: Feishin unlocks structured lyrics and public-playlist share permissions at >=10.9.0, and this API serves neither (no lyrics endpoint; playlist user permissions are stubs), so a higher version would falsely advertise capabilities.
This commit is contained in:
parent
caa0043030
commit
c582ed31fa
@ -115,7 +115,7 @@ favorites-only (`Filters=IsFavorite` or the standalone `isFavorite=true`); `Sort
|
||||
|
||||
| Area | Endpoints |
|
||||
|---|---|
|
||||
| Handshake / system | `GET System/Info/Public`, `GET`/`POST System/Ping`, `GET QuickConnect/Enabled` |
|
||||
| Handshake / system | `GET System/Info/Public`, `GET System/Info` (authenticated), `GET`/`POST System/Ping`, `GET QuickConnect/Enabled` |
|
||||
| Auth | `POST Users/AuthenticateByName`, `GET Users/Public` |
|
||||
| Users | `GET UserViews`, `GET Users/{userId}/Views`, `GET Users/Me`, `GET Users/{userId}` |
|
||||
| Browsing | `GET Items`, `GET Users/{userId}/Items`, `GET Items/{itemId}`, `GET Users/{userId}/Items/{itemId}`, `GET Users/{userId}/Items/Latest`, `DELETE Items/{itemId}` (playlists only) |
|
||||
|
||||
@ -92,6 +92,7 @@ func (api *Router) routes() http.Handler {
|
||||
// Subsonic's getPlayer, so Jellyfin clients show up in the players list (and scrobbling has a
|
||||
// player) even before the first playback report.
|
||||
r.Use(api.withPlayer)
|
||||
r.Get("/system/info", api.getSystemInfo)
|
||||
r.Get("/userviews", api.getUserViews)
|
||||
r.Get("/users/{userId}/views", api.getUserViews)
|
||||
r.Get("/users/me", api.getCurrentUser)
|
||||
|
||||
@ -30,6 +30,27 @@ var _ = Describe("System", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GET /System/Info", func() {
|
||||
It("returns system info to any authenticated user, matching the public Version", func() {
|
||||
w := getAs(regularUser, "/System/Info")
|
||||
var info map[string]any
|
||||
parseInto(w, &info)
|
||||
Expect(info["Version"]).ToNot(BeEmpty())
|
||||
Expect(info["SupportsLibraryMonitor"]).To(BeTrue())
|
||||
|
||||
pub := rawReq("GET", "/System/Info/Public", "")
|
||||
var pubInfo map[string]any
|
||||
parseInto(pub, &pubInfo)
|
||||
Expect(info["Version"]).To(Equal(pubInfo["Version"]))
|
||||
Expect(info["Id"]).To(Equal(pubInfo["Id"]))
|
||||
})
|
||||
|
||||
It("rejects unauthenticated requests", func() {
|
||||
w := rawReq("GET", "/System/Info", "")
|
||||
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GET/POST /System/Ping", func() {
|
||||
It("answers GET with a plain-text server name", func() {
|
||||
w := rawReq("GET", "/System/Ping", "")
|
||||
|
||||
@ -83,6 +83,13 @@ func (api *Router) getPublicSystemInfo(w http.ResponseWriter, r *http.Request) {
|
||||
api.ok(w, r, api.publicInfo(r))
|
||||
}
|
||||
|
||||
func (api *Router) getSystemInfo(w http.ResponseWriter, r *http.Request) {
|
||||
api.ok(w, r, dto.SystemInfo{
|
||||
PublicSystemInfo: api.publicInfo(r),
|
||||
SupportsLibraryMonitor: true,
|
||||
})
|
||||
}
|
||||
|
||||
// ping answers /System/Ping with a bare plain-text server name (not JSON-quoted): Jellyfin's
|
||||
// server does this and clients parse the raw body.
|
||||
func (api *Router) ping(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@ -38,6 +38,27 @@ var _ = Describe("System", func() {
|
||||
Expect(info.ServerName).To(HavePrefix("Navidrome"))
|
||||
})
|
||||
|
||||
It("returns authenticated system info with the public fields plus library monitor support", func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
conf.Server.Jellyfin.ServerName = ""
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/System/Info", nil)
|
||||
api.getSystemInfo(w, r)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(w.Header().Get("Content-Type")).To(ContainSubstring("application/json"))
|
||||
var info dto.SystemInfo
|
||||
Expect(json.Unmarshal(w.Body.Bytes(), &info)).To(Succeed())
|
||||
Expect(info.Id).ToNot(BeEmpty())
|
||||
Expect(info.Version).To(Equal(jellyfinVersion))
|
||||
Expect(info.ProductName).To(Equal("Jellyfin Server"))
|
||||
Expect(info.ServerName).To(HavePrefix("Navidrome"))
|
||||
Expect(info.SupportsLibraryMonitor).To(BeTrue())
|
||||
Expect(info.HasPendingRestart).To(BeFalse())
|
||||
Expect(info.IsShuttingDown).To(BeFalse())
|
||||
})
|
||||
|
||||
It("advertises a LocalAddress with the request scheme, host and Jellyfin base path", func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user