decouple AlbumArtistInfo from AlbumArtistDetail (#1809)

This commit is contained in:
jeffvli
2026-03-08 22:06:18 -07:00
parent 7dbf8dd9fe
commit 17deac8d65
14 changed files with 386 additions and 156 deletions
+12
View File
@@ -200,6 +200,18 @@ export const controller: GeneralController = {
server.type,
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
},
getAlbumArtistInfo(args) {
const server = getServerById(args.apiClientProps.serverId);
if (!server) {
return Promise.resolve(null);
}
const fn = apiController('getAlbumArtistInfo', server.type);
return fn
? fn(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }))
: Promise.resolve(null);
},
getAlbumArtistList(args) {
const server = getServerById(args.apiClientProps.serverId);
@@ -258,34 +258,54 @@ export const JellyfinController: InternalControllerEndpoint = {
throw new Error('No userId found');
}
const [res, similarArtistsRes] = await Promise.all([
jfApiClient(apiClientProps).getAlbumArtistDetail({
params: {
id: query.id,
userId: apiClientProps.server?.userId,
},
query: {
Fields: JF_FIELDS.ALBUM_ARTIST_DETAIL,
},
}),
jfApiClient(apiClientProps).getSimilarArtistList({
params: {
id: query.id,
},
query: {
Limit: 10,
},
}),
]);
const res = await jfApiClient(apiClientProps).getAlbumArtistDetail({
params: {
id: query.id,
userId: apiClientProps.server?.userId,
},
query: {
Fields: ['Genres', 'Overview', 'SortName'],
},
});
if (res.status !== 200 || similarArtistsRes.status !== 200) {
if (res.status !== 200) {
throw new Error('Failed to get album artist detail');
}
return jfNormalize.albumArtist(
{ ...res.body, similarArtists: similarArtistsRes.body },
apiClientProps.server,
return jfNormalize.albumArtist(res.body, apiClientProps.server);
},
getAlbumArtistInfo: async (args) => {
const { apiClientProps, query } = args;
const similarArtistsRes = await jfApiClient(apiClientProps).getSimilarArtistList({
params: {
id: query.id,
},
query: {
Limit: query.limit ?? 10,
},
});
if (similarArtistsRes.status !== 200) {
return null;
}
const items = similarArtistsRes.body?.Items?.filter(
(entry) => entry.Name !== 'Various Artists',
);
const similarArtists =
items?.map((entry) => ({
id: entry.Id,
imageId: entry.ImageTags?.Primary ? entry.Id : null,
imageUrl: null,
name: entry.Name,
userFavorite: entry.UserData?.IsFavorite || false,
userRating: null,
})) ?? null;
return {
similarArtists,
};
},
getAlbumArtistList: async (args) => {
const { apiClientProps, query } = args;
@@ -190,19 +190,11 @@ export const NavidromeController: InternalControllerEndpoint = {
getAlbumArtistDetail: async (args) => {
const { apiClientProps, query } = args;
const [res, artistInfoRes] = await Promise.all([
ndApiClient(apiClientProps).getAlbumArtistDetail({
params: {
id: query.id,
},
}),
ssApiClient(apiClientProps).getArtistInfo({
query: {
count: 10,
id: query.id,
},
}),
]);
const res = await ndApiClient(apiClientProps).getAlbumArtistDetail({
params: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album artist detail');
@@ -212,22 +204,42 @@ export const NavidromeController: InternalControllerEndpoint = {
throw new Error('Server is required');
}
// Prefer images from getArtistInfo first (which should be proxied)
// Prioritize large > medium > small
return ndNormalize.albumArtist(
{
...res.body.data,
...(artistInfoRes.status === 200 && {
largeImageUrl:
artistInfoRes.body.artistInfo.largeImageUrl ||
artistInfoRes.body.artistInfo.mediumImageUrl ||
artistInfoRes.body.artistInfo.smallImageUrl ||
res.body.data.largeImageUrl,
similarArtists: artistInfoRes.body.artistInfo.similarArtist,
}),
return ndNormalize.albumArtist(res.body.data, apiClientProps.server);
},
getAlbumArtistInfo: async (args) => {
const { apiClientProps, query } = args;
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
query: {
id: query.id,
...(query.limit != null && { count: query.limit }),
},
apiClientProps.server,
);
});
if (artistInfoRes.status !== 200) {
return null;
}
const artistInfo = artistInfoRes.body.artistInfo;
const imageUrl =
artistInfo?.largeImageUrl ||
artistInfo?.mediumImageUrl ||
artistInfo?.smallImageUrl ||
null;
return {
biography: artistInfo?.biography || null,
imageUrl,
similarArtists:
artistInfo?.similarArtist?.map((artist) => ({
id: artist.id,
imageId: null,
imageUrl: artist?.artistImageUrl?.replace(/\?size=\d+/, '') ?? null,
name: artist.name,
userFavorite: Boolean(artist.starred) || false,
userRating: artist.userRating ?? null,
})) ?? null,
};
},
getAlbumArtistList: async (args) => {
const { apiClientProps, query } = args;
+8
View File
@@ -1,5 +1,6 @@
import type {
AlbumArtistDetailQuery,
AlbumArtistInfoQuery,
AlbumArtistListQuery,
AlbumDetailQuery,
AlbumListQuery,
@@ -93,6 +94,13 @@ export const queryKeys: Record<
return [serverId, 'albumArtists', 'infiniteList'] as const;
},
info: (serverId: string, query?: AlbumArtistInfoQuery) => {
if (query) {
return [serverId, 'albumArtists', 'info', query] as const;
}
return [serverId, 'albumArtists', 'info'] as const;
},
list: (serverId: string, query?: AlbumArtistListQuery) => {
const { filter, pagination } = splitPaginatedQuery(query);
if (query && pagination) {
@@ -258,18 +258,11 @@ export const SubsonicController: InternalControllerEndpoint = {
getAlbumArtistDetail: async (args) => {
const { apiClientProps, query } = args;
const [artistInfoRes, res] = await Promise.all([
ssApiClient(apiClientProps).getArtistInfo({
query: {
id: query.id,
},
}),
ssApiClient(apiClientProps).getArtist({
query: {
id: query.id,
},
}),
]);
const res = await ssApiClient(apiClientProps).getArtist({
query: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album artist detail');
@@ -277,11 +270,6 @@ export const SubsonicController: InternalControllerEndpoint = {
const artist = res.body.artist;
let artistInfo;
if (artistInfoRes.status === 200) {
artistInfo = artistInfoRes.body.artistInfo;
}
return {
...ssNormalize.albumArtist(artist, apiClientProps.server),
albums: artist.album?.map((album) =>
@@ -292,10 +280,36 @@ export const SubsonicController: InternalControllerEndpoint = {
args.context?.pathReplaceWith,
),
),
similarArtists: null,
};
},
getAlbumArtistInfo: async (args) => {
const { apiClientProps, query } = args;
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
query: {
id: query.id,
...(query.limit != null && { count: query.limit }),
},
});
if (artistInfoRes.status !== 200) {
return null;
}
const artistInfo = artistInfoRes.body.artistInfo;
return {
biography: artistInfo?.biography || null,
similarArtists:
artistInfo?.similarArtist?.map((artist) =>
ssNormalize.albumArtist(artist, apiClientProps.server),
) || null,
artistInfo?.similarArtist?.map((artist) => ({
id: artist.id,
imageId: null,
imageUrl: null,
name: artist.name,
userFavorite: Boolean(artist.starred) || false,
userRating: artist.userRating ?? null,
})) ?? null,
};
},
getAlbumArtistList: async (args) => {