navidrome/server/jellyfin/library.go
Deluan 1703b59d24 fix(jellyfin): hex-encode item ids at the API boundary for Jellyfin client compatibility
Finamp (and presumably other clients) parses ids as radix-16, but Navidrome's
base62 nanoids aren't valid hex and crash its queue packing. Hex-encode every
id emitted by the Jellyfin API and decode every id received, keeping the
transform reversible and stateless at the boundary rather than touching any
model id.
2026-07-05 22:07:32 -04:00

49 lines
1.9 KiB
Go

package jellyfin
import (
"context"
"strconv"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/server/jellyfin/dto"
)
// accessibleLibraryIDs returns the ids of the libraries the current user can access. An
// empty slice only occurs for the edge case of a non-admin user with no assigned libraries;
// filter.ApplyLibraryFilter/ApplyArtistLibraryFilter treat it as a no-op (unrestricted).
func accessibleLibraryIDs(ctx context.Context) []int {
u, _ := request.UserFrom(ctx)
return u.Libraries.IDs()
}
// resolveLibraryScope handles ParentId's ambiguity: it can be a library id (browsing a
// UserView) or an entity id (an artist for MusicAlbum queries, an album for Audio queries).
// It's only treated as a library when the user actually has access to it; otherwise
// isLibraryParent is false and callers should fall through to treating parentId as an entity
// id, which safely matches nothing rather than leaking another library's content.
func resolveLibraryScope(ctx context.Context, parentId string) (scopeIDs []int, isLibraryParent bool) {
if parentId != "" {
if id, err := strconv.Atoi(parentId); err == nil {
if u, _ := request.UserFrom(ctx); u.HasLibraryAccess(id) {
return []int{id}, true
}
}
}
return accessibleLibraryIDs(ctx), false
}
// libraryView builds the CollectionFolder BaseItemDto Jellyfin clients use to represent a
// library as a top-level browsing node. It's shared by getUserViews (which lists every
// accessible library) and getItem (which resolves a single one), since Finamp fetches a
// UserView's id as a plain item to load the library.
func libraryView(lib model.Library) dto.BaseItemDto {
return dto.BaseItemDto{
Id: dto.EncodeID(strconv.Itoa(lib.ID)),
Name: lib.Name,
Type: "CollectionFolder",
CollectionType: "music",
IsFolder: true,
BackdropImageTags: []string{},
}
}