feat: adds option to view top rated songs in the Favorite Songs section (#2289)

* Adds Top Rating option songs to Artist's favorite songs section
This commit is contained in:
Derek Simonds
2026-08-03 19:36:08 -07:00
committed by GitHub
parent 71fc632501
commit fd9380a149
9 changed files with 239 additions and 28 deletions
+12
View File
@@ -535,6 +535,18 @@ export const controller: GeneralController = {
server.type,
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
},
getFavoriteSongs(args) {
const server = getServerById(args.apiClientProps.serverId);
if (!server) {
throw new Error(`${i18n.t('error.apiRouteError')}: getFavoriteSongs`);
}
return apiController(
'getFavoriteSongs',
server.type,
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
},
getFolder(args) {
const server = getServerById(args.apiClientProps.serverId);
@@ -679,6 +679,43 @@ export const JellyfinController: InternalControllerEndpoint = {
return `${apiClientProps.server?.url}/items/${query.id}/download?apiKey=${apiClientProps.server?.credential}`;
},
getFavoriteSongs: async (args) => {
const { apiClientProps, query } = args;
if (!apiClientProps.server?.userId) {
throw new Error('No userId found');
}
// Gets songs sorted by play count and filters favorited songs
const res = await jfApiClient(apiClientProps).getTopSongsList({
params: {
userId: apiClientProps.server?.userId,
},
query: {
ArtistIds: query.artistId,
Fields: JF_FIELDS.SONG,
IncludeItemTypes: 'Audio',
IsFavorite: true,
Limit: query.limit,
Recursive: true,
SortBy: JFSongListSort.PLAY_COUNT,
SortOrder: 'Descending',
UserId: apiClientProps.server?.userId,
},
});
if (res.status !== 200) {
throw new Error('Failed to get top song list');
}
const items = res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server));
return {
items,
startIndex: 0,
totalRecordCount: res.body.TotalRecordCount,
};
},
getFolder: async (args) => {
const { apiClientProps, query } = args;
const userId = apiClientProps.server?.userId;
@@ -557,6 +557,57 @@ export const NavidromeController: InternalControllerEndpoint = {
);
},
getDownloadUrl: SubsonicController.getDownloadUrl,
getFavoriteSongs: async (args) => {
const { apiClientProps, query } = args;
// if user selects 'rating'
if (query.type === 'rating') {
const res = await NavidromeController.getSongList({
apiClientProps,
query: {
artistIds: [query.artistId],
sortBy: SongListSort.RATING,
sortOrder: SortOrder.DESC,
startIndex: 0,
},
});
const songsWithHighRating = orderBy(
res.items.filter((song) => song.userRating !== null && song.userRating > 2),
['userRating', 'userFavorite', 'playCount', 'albumId', 'trackNumber'],
['desc', 'desc', 'desc', 'asc', 'asc'],
);
return {
items: songsWithHighRating,
startIndex: 0,
totalRecordCount: res.totalRecordCount,
};
}
// else if user selects 'favorite'
const res = await NavidromeController.getSongList({
apiClientProps,
query: {
artistIds: [query.artistId],
sortBy: SongListSort.FAVORITED,
sortOrder: SortOrder.DESC,
startIndex: 0,
},
});
const songsWithFavorite = orderBy(
res.items.filter((song) => song.userFavorite),
['userFavorite', 'userRating', 'playCount', 'albumId', 'trackNumber'],
['desc', 'desc', 'desc', 'asc', 'asc'],
);
return {
items: songsWithFavorite,
startIndex: 0,
totalRecordCount: res.totalRecordCount,
};
},
getFolder: SubsonicController.getFolder,
getGenreList: async (args) => {
const { apiClientProps, query } = args;
+4 -3
View File
@@ -7,6 +7,7 @@ import type {
AlbumRadioQuery,
ArtistListQuery,
ArtistRadioQuery,
FavoriteSongListQuery,
FolderQuery,
GenreListQuery,
LyricSearchQuery,
@@ -75,9 +76,9 @@ export const queryKeys: Record<
return [serverId, 'albumArtists', 'detail'] as const;
},
favoriteSongs: (serverId: string, artistId?: string) => {
if (artistId) {
return [serverId, 'albumArtists', 'favoriteSongs', artistId] as const;
favoriteSongs: (serverId: string, query?: FavoriteSongListQuery) => {
if (query) {
return [serverId, 'albumArtists', 'favoriteSongs', query] as const;
}
return [serverId, 'albumArtists', 'favoriteSongs'] as const;
@@ -976,6 +976,56 @@ export const SubsonicController: InternalControllerEndpoint = {
'&c=Feishin'
);
},
getFavoriteSongs: async (args) => {
const { apiClientProps, query } = args;
// if user selects 'rating'
if (query.type === 'rating') {
const res = await SubsonicController.getSongList({
apiClientProps,
query: {
artistIds: [query.artistId],
sortBy: SongListSort.RATING,
sortOrder: SortOrder.DESC,
startIndex: 0,
},
});
const songsWithHighRating = orderBy(
res.items.filter((song) => song.userRating !== null && song.userRating > 2),
['userRating', 'userFavorite', 'playCount', 'albumId', 'trackNumber'],
['desc', 'desc', 'desc', 'asc', 'asc'],
);
return {
items: songsWithHighRating,
startIndex: 0,
totalRecordCount: res.totalRecordCount,
};
}
// else if user selects 'favorites'
const res = await SubsonicController.getSongList({
apiClientProps,
query: {
artistIds: [query.artistId],
sortBy: SongListSort.FAVORITED,
sortOrder: SortOrder.DESC,
startIndex: 0,
},
});
const songsWithFavorite = orderBy(
res.items.filter((song) => song.userFavorite),
['userFavorite', 'userRating', 'playCount', 'albumId', 'trackNumber'],
['desc', 'desc', 'desc', 'asc', 'asc'],
);
return {
items: songsWithFavorite,
startIndex: 0,
totalRecordCount: res.totalRecordCount,
};
},
getFolder: async ({ apiClientProps, query }) => {
const sortOrder = (query.sortOrder?.toLowerCase() ?? 'asc') as 'asc' | 'desc';