diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 4788159a22..b93eefa47c 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -289,7 +289,7 @@ export const NavidromeController: InternalControllerEndpoint = { getAlbumArtistInfo: async (args) => { const { apiClientProps, query } = args; - const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({ + const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo2({ query: { id: query.id, ...(query.limit != null && { count: query.limit }), @@ -300,7 +300,7 @@ export const NavidromeController: InternalControllerEndpoint = { return null; } - const artistInfo = artistInfoRes.body.artistInfo; + const artistInfo = artistInfoRes.body.artistInfo2; const imageUrl = artistInfo?.largeImageUrl || artistInfo?.mediumImageUrl || @@ -312,8 +312,8 @@ export const NavidromeController: InternalControllerEndpoint = { imageUrl, similarArtists: artistInfo?.similarArtist?.map((artist) => ({ - id: artist.id, - imageId: artist.id, + id: String(artist.id), + imageId: String(artist.id), imageUrl: null, name: artist.name, userFavorite: Boolean(artist.starred) || false, diff --git a/src/renderer/api/subsonic/subsonic-api.ts b/src/renderer/api/subsonic/subsonic-api.ts index 1f563963d5..3cf544412c 100644 --- a/src/renderer/api/subsonic/subsonic-api.ts +++ b/src/renderer/api/subsonic/subsonic-api.ts @@ -98,12 +98,12 @@ export const contract = c.router({ 200: ssType._response.getArtist, }, }, - getArtistInfo: { + getArtistInfo2: { method: 'GET', - path: 'getArtistInfo.view', + path: 'getArtistInfo2.view', query: ssType._parameters.artistInfo, responses: { - 200: ssType._response.artistInfo, + 200: ssType._response.artistInfo2, }, }, getArtists: { diff --git a/src/renderer/api/subsonic/subsonic-controller.ts b/src/renderer/api/subsonic/subsonic-controller.ts index a835c3a077..87e45382c2 100644 --- a/src/renderer/api/subsonic/subsonic-controller.ts +++ b/src/renderer/api/subsonic/subsonic-controller.ts @@ -496,7 +496,7 @@ export const SubsonicController: InternalControllerEndpoint = { getAlbumArtistInfo: async (args) => { const { apiClientProps, query } = args; - const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({ + const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo2({ query: { id: query.id, ...(query.limit != null && { count: query.limit }), @@ -507,14 +507,14 @@ export const SubsonicController: InternalControllerEndpoint = { return null; } - const artistInfo = artistInfoRes.body.artistInfo; + const artistInfo = artistInfoRes.body.artistInfo2; return { biography: artistInfo?.biography || null, similarArtists: artistInfo?.similarArtist?.map((artist) => ({ - id: artist.id, - imageId: artist.coverArt ?? artist.id, + id: String(artist.id), + imageId: artist.coverArt ?? String(artist.id), imageUrl: null, name: artist.name, userFavorite: Boolean(artist.starred) || false, diff --git a/src/shared/api/navidrome/navidrome-normalize.ts b/src/shared/api/navidrome/navidrome-normalize.ts index ad9b93bf8c..e781ad3ad2 100644 --- a/src/shared/api/navidrome/navidrome-normalize.ts +++ b/src/shared/api/navidrome/navidrome-normalize.ts @@ -392,7 +392,9 @@ const normalizeAlbum = ( const normalizeAlbumArtist = ( item: z.infer & { - similarArtists?: z.infer['artistInfo']['similarArtist']; + similarArtists?: NonNullable< + z.infer['artistInfo2'] + >['similarArtist']; }, server?: null | ServerListItem, ): AlbumArtist => { @@ -448,8 +450,8 @@ const normalizeAlbumArtist = ( playCount: item.playCount || 0, similarArtists: item.similarArtists?.map((artist) => ({ - id: artist.id, - imageId: artist.id, + id: String(artist.id), + imageId: String(artist.id), imageUrl: null, name: artist.name, userFavorite: Boolean(artist.starred) || false, diff --git a/src/shared/api/subsonic/subsonic-normalize.ts b/src/shared/api/subsonic/subsonic-normalize.ts index e852a55fe7..2d691863fa 100644 --- a/src/shared/api/subsonic/subsonic-normalize.ts +++ b/src/shared/api/subsonic/subsonic-normalize.ts @@ -245,14 +245,14 @@ const normalizeSong = ( const normalizeAlbumArtist = ( item: | (z.infer & { - similarArtists?: z.infer< - typeof ssType._response.artistInfo - >['artistInfo']['similarArtist']; + similarArtists?: NonNullable< + z.infer['artistInfo2'] + >['similarArtist']; }) | (z.infer & { - similarArtists?: z.infer< - typeof ssType._response.artistInfo - >['artistInfo']['similarArtist']; + similarArtists?: NonNullable< + z.infer['artistInfo2'] + >['similarArtist']; }), server?: null | ServerListItemWithCredential, ): AlbumArtist => { @@ -273,8 +273,8 @@ const normalizeAlbumArtist = ( playCount: null, similarArtists: item.similarArtists?.map((artist) => ({ - id: artist.id, - imageId: artist.coverArt ?? artist.id, + id: String(artist.id), + imageId: artist.coverArt ?? String(artist.id), imageUrl: null, name: artist.name, userFavorite: Boolean(artist.starred) || false, diff --git a/src/shared/api/subsonic/subsonic-types.ts b/src/shared/api/subsonic/subsonic-types.ts index 47e693226d..e09783e158 100644 --- a/src/shared/api/subsonic/subsonic-types.ts +++ b/src/shared/api/subsonic/subsonic-types.ts @@ -304,26 +304,33 @@ const artistInfoParameters = z.object({ includeNotPresent: z.boolean().optional(), }); -const artistInfo = z.object({ - artistInfo: z.object({ - biography: z.string().optional(), - largeImageUrl: z.string().optional(), - lastFmUrl: z.string().optional(), - mediumImageUrl: z.string().optional(), - musicBrainzId: z.string().optional(), - similarArtist: z.array( - z.object({ - albumCount: z.string(), - artistImageUrl: z.string().optional(), - coverArt: z.string().optional(), - id: z.string(), - name: z.string(), - starred: z.string().optional(), - userRating: z.number().optional(), - }), - ), - smallImageUrl: z.string().optional(), - }), +// Organizes music according to ID3 tags, and must be queried with an ID3 artist id +// (as returned by getArtists/getArtist). The non-ID3 getArtistInfo resolves the id +// against the folder browsing namespace, where the same id belongs to an unrelated item. +const artistInfo2 = z.object({ + artistInfo2: z + .object({ + biography: z.string().optional(), + largeImageUrl: z.string().optional(), + lastFmUrl: z.string().optional(), + mediumImageUrl: z.string().optional(), + musicBrainzId: z.string().optional(), + similarArtist: z + .array( + z.object({ + albumCount: z.number().or(z.string()).optional(), + artistImageUrl: z.string().optional(), + coverArt: z.string().optional(), + id, + name: z.string(), + starred: z.string().optional(), + userRating: z.number().optional(), + }), + ) + .optional(), + smallImageUrl: z.string().optional(), + }) + .optional(), }); const topSongsListParameters = z.object({ @@ -951,7 +958,7 @@ export const ssType = { albumInfo, albumList, albumListEntry, - artistInfo, + artistInfo2, artistListEntry, authenticate, baseResponse,