mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 18:13:31 +00:00
Add initial album artist detail route
This commit is contained in:
parent
55e2a9bf37
commit
9b8bcb05bd
21 changed files with 1000 additions and 27 deletions
|
|
@ -37,6 +37,8 @@ import type {
|
|||
UserListArgs,
|
||||
RawUserListResponse,
|
||||
FavoriteArgs,
|
||||
TopSongListArgs,
|
||||
RawTopSongListResponse,
|
||||
} from '/@/renderer/api/types';
|
||||
import { subsonicApi } from '/@/renderer/api/subsonic.api';
|
||||
import { jellyfinApi } from '/@/renderer/api/jellyfin.api';
|
||||
|
|
@ -52,6 +54,7 @@ export type ControllerEndpoint = Partial<{
|
|||
getAlbumDetail: (args: AlbumDetailArgs) => Promise<RawAlbumDetailResponse>;
|
||||
getAlbumList: (args: AlbumListArgs) => Promise<RawAlbumListResponse>;
|
||||
getArtistDetail: () => void;
|
||||
getArtistInfo: (args: any) => void;
|
||||
getArtistList: (args: ArtistListArgs) => Promise<RawArtistListResponse>;
|
||||
getFavoritesList: () => void;
|
||||
getFolderItemList: () => void;
|
||||
|
|
@ -64,6 +67,7 @@ export type ControllerEndpoint = Partial<{
|
|||
getPlaylistSongList: (args: PlaylistSongListArgs) => Promise<RawSongListResponse>;
|
||||
getSongDetail: (args: SongDetailArgs) => Promise<RawSongDetailResponse>;
|
||||
getSongList: (args: SongListArgs) => Promise<RawSongListResponse>;
|
||||
getTopSongs: (args: TopSongListArgs) => Promise<RawTopSongListResponse>;
|
||||
getUserList: (args: UserListArgs) => Promise<RawUserListResponse>;
|
||||
updatePlaylist: (args: UpdatePlaylistArgs) => Promise<RawUpdatePlaylistResponse>;
|
||||
updateRating: (args: RatingArgs) => Promise<RawRatingResponse>;
|
||||
|
|
@ -87,6 +91,7 @@ const endpoints: ApiController = {
|
|||
getAlbumDetail: jellyfinApi.getAlbumDetail,
|
||||
getAlbumList: jellyfinApi.getAlbumList,
|
||||
getArtistDetail: undefined,
|
||||
getArtistInfo: undefined,
|
||||
getArtistList: jellyfinApi.getArtistList,
|
||||
getFavoritesList: undefined,
|
||||
getFolderItemList: undefined,
|
||||
|
|
@ -99,6 +104,7 @@ const endpoints: ApiController = {
|
|||
getPlaylistSongList: jellyfinApi.getPlaylistSongList,
|
||||
getSongDetail: undefined,
|
||||
getSongList: jellyfinApi.getSongList,
|
||||
getTopSongs: undefined,
|
||||
getUserList: undefined,
|
||||
updatePlaylist: jellyfinApi.updatePlaylist,
|
||||
updateRating: undefined,
|
||||
|
|
@ -114,6 +120,7 @@ const endpoints: ApiController = {
|
|||
getAlbumDetail: navidromeApi.getAlbumDetail,
|
||||
getAlbumList: navidromeApi.getAlbumList,
|
||||
getArtistDetail: undefined,
|
||||
getArtistInfo: undefined,
|
||||
getArtistList: undefined,
|
||||
getFavoritesList: undefined,
|
||||
getFolderItemList: undefined,
|
||||
|
|
@ -126,6 +133,7 @@ const endpoints: ApiController = {
|
|||
getPlaylistSongList: navidromeApi.getPlaylistSongList,
|
||||
getSongDetail: navidromeApi.getSongDetail,
|
||||
getSongList: navidromeApi.getSongList,
|
||||
getTopSongs: subsonicApi.getTopSongList,
|
||||
getUserList: navidromeApi.getUserList,
|
||||
updatePlaylist: navidromeApi.updatePlaylist,
|
||||
updateRating: subsonicApi.updateRating,
|
||||
|
|
@ -141,6 +149,7 @@ const endpoints: ApiController = {
|
|||
getAlbumDetail: subsonicApi.getAlbumDetail,
|
||||
getAlbumList: subsonicApi.getAlbumList,
|
||||
getArtistDetail: undefined,
|
||||
getArtistInfo: undefined,
|
||||
getArtistList: undefined,
|
||||
getFavoritesList: undefined,
|
||||
getFolderItemList: undefined,
|
||||
|
|
@ -152,6 +161,7 @@ const endpoints: ApiController = {
|
|||
getPlaylistList: undefined,
|
||||
getSongDetail: undefined,
|
||||
getSongList: undefined,
|
||||
getTopSongs: subsonicApi.getTopSongList,
|
||||
getUserList: undefined,
|
||||
updatePlaylist: undefined,
|
||||
updateRating: undefined,
|
||||
|
|
@ -255,6 +265,10 @@ const updateRating = async (args: RatingArgs) => {
|
|||
return (apiController('updateRating') as ControllerEndpoint['updateRating'])?.(args);
|
||||
};
|
||||
|
||||
const getTopSongList = async (args: TopSongListArgs) => {
|
||||
return (apiController('getTopSongs') as ControllerEndpoint['getTopSongs'])?.(args);
|
||||
};
|
||||
|
||||
export const controller = {
|
||||
createFavorite,
|
||||
createPlaylist,
|
||||
|
|
@ -271,6 +285,7 @@ export const controller = {
|
|||
getPlaylistList,
|
||||
getPlaylistSongList,
|
||||
getSongList,
|
||||
getTopSongList,
|
||||
getUserList,
|
||||
updatePlaylist,
|
||||
updateRating,
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ const getAlbumArtistDetail = async (args: AlbumArtistDetailArgs): Promise<JFAlbu
|
|||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams = {
|
||||
fields: 'Genres',
|
||||
fields: 'Genres, Overview',
|
||||
};
|
||||
|
||||
const data = await api
|
||||
|
|
@ -152,7 +152,16 @@ const getAlbumArtistDetail = async (args: AlbumArtistDetailArgs): Promise<JFAlbu
|
|||
})
|
||||
.json<JFAlbumArtistDetailResponse>();
|
||||
|
||||
return data;
|
||||
const similarArtists = await api
|
||||
.get(`artists/${query.id}/similar`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams({ limit: 10 }),
|
||||
signal,
|
||||
})
|
||||
.json<JFAlbumArtistListResponse>();
|
||||
|
||||
return { ...data, similarArtists: { items: similarArtists.Items } };
|
||||
};
|
||||
|
||||
// const getAlbumArtistAlbums = () => {
|
||||
|
|
@ -642,10 +651,14 @@ const normalizeSong = (
|
|||
): Song => {
|
||||
return {
|
||||
album: item.Album,
|
||||
albumArtists: item.AlbumArtists?.map((entry) => ({ id: entry.Id, name: entry.Name })),
|
||||
albumArtists: item.AlbumArtists?.map((entry) => ({
|
||||
id: entry.Id,
|
||||
imageUrl: null,
|
||||
name: entry.Name,
|
||||
})),
|
||||
albumId: item.AlbumId,
|
||||
artistName: item.ArtistItems[0]?.Name,
|
||||
artists: item.ArtistItems.map((entry) => ({ id: entry.Id, name: entry.Name })),
|
||||
artists: item.ArtistItems.map((entry) => ({ id: entry.Id, imageUrl: null, name: entry.Name })),
|
||||
bitRate: item.MediaSources && Number(Math.trunc(item.MediaSources[0]?.Bitrate / 1000)),
|
||||
bpm: null,
|
||||
channels: null,
|
||||
|
|
@ -691,9 +704,10 @@ const normalizeAlbum = (item: JFAlbum, server: ServerListItem, imageSize?: numbe
|
|||
albumArtists:
|
||||
item.AlbumArtists.map((entry) => ({
|
||||
id: entry.Id,
|
||||
imageUrl: null,
|
||||
name: entry.Name,
|
||||
})) || [],
|
||||
artists: item.ArtistItems?.map((entry) => ({ id: entry.Id, name: entry.Name })),
|
||||
artists: item.ArtistItems?.map((entry) => ({ id: entry.Id, imageUrl: null, name: entry.Name })),
|
||||
backdropImageUrl: null,
|
||||
createdAt: item.DateCreated,
|
||||
duration: item.RunTimeTicks / 10000,
|
||||
|
|
@ -747,6 +761,17 @@ const normalizeAlbumArtist = (
|
|||
playCount: item.UserData.PlayCount,
|
||||
serverId: server.id,
|
||||
serverType: ServerType.JELLYFIN,
|
||||
similarArtists: item.similarArtists?.items
|
||||
?.filter((entry) => entry.Name !== 'Various Artists')
|
||||
.map((entry) => ({
|
||||
id: entry.Id,
|
||||
imageUrl: getAlbumArtistCoverArtUrl({
|
||||
baseUrl: server.url,
|
||||
item: entry,
|
||||
size: imageSize || 300,
|
||||
}),
|
||||
name: entry.Name,
|
||||
})),
|
||||
songCount: null,
|
||||
userFavorite: item.UserData.IsFavorite || false,
|
||||
userRating: null,
|
||||
|
|
|
|||
|
|
@ -173,6 +173,10 @@ export type JFAlbumArtist = {
|
|||
PlaybackPositionTicks: number;
|
||||
Played: boolean;
|
||||
};
|
||||
} & {
|
||||
similarArtists: {
|
||||
items: JFAlbumArtist[];
|
||||
};
|
||||
};
|
||||
|
||||
export type JFArtist = {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ import { toast } from '/@/renderer/components/toast';
|
|||
import { useAuthStore } from '/@/renderer/store';
|
||||
import { ServerListItem, ServerType } from '/@/renderer/types';
|
||||
import { parseSearchParams } from '/@/renderer/utils';
|
||||
import { subsonicApi } from '/@/renderer/api/subsonic.api';
|
||||
|
||||
const api = ky.create({
|
||||
hooks: {
|
||||
|
|
@ -183,6 +184,15 @@ const getGenreList = async (args: GenreListArgs): Promise<NDGenreList> => {
|
|||
const getAlbumArtistDetail = async (args: AlbumArtistDetailArgs): Promise<NDAlbumArtistDetail> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const artistInfo = await subsonicApi.getArtistInfo({
|
||||
query: {
|
||||
artistId: query.id,
|
||||
limit: 15,
|
||||
},
|
||||
server,
|
||||
signal,
|
||||
});
|
||||
|
||||
const data = await api
|
||||
.get(`api/artist/${query.id}`, {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||
|
|
@ -191,7 +201,7 @@ const getAlbumArtistDetail = async (args: AlbumArtistDetailArgs): Promise<NDAlbu
|
|||
})
|
||||
.json<NDAlbumArtistDetailResponse>();
|
||||
|
||||
return { ...data };
|
||||
return { ...data, similarArtists: artistInfo.similarArtist };
|
||||
};
|
||||
|
||||
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<NDAlbumArtistList> => {
|
||||
|
|
@ -510,10 +520,10 @@ const normalizeSong = (
|
|||
|
||||
return {
|
||||
album: item.album,
|
||||
albumArtists: [{ id: item.artistId, name: item.artist }],
|
||||
albumArtists: [{ id: item.artistId, imageUrl: null, name: item.artist }],
|
||||
albumId: item.albumId,
|
||||
artistName: item.artist,
|
||||
artists: [{ id: item.artistId, name: item.artist }],
|
||||
artists: [{ id: item.artistId, imageUrl: null, name: item.artist }],
|
||||
bitRate: item.bitRate,
|
||||
bpm: item.bpm ? item.bpm : null,
|
||||
channels: item.channels ? item.channels : null,
|
||||
|
|
@ -559,8 +569,8 @@ const normalizeAlbum = (item: NDAlbum, server: ServerListItem, imageSize?: numbe
|
|||
const imageBackdropUrl = imageUrl?.replace(/size=\d+/, 'size=1000') || null;
|
||||
|
||||
return {
|
||||
albumArtists: [{ id: item.albumArtistId, name: item.albumArtist }],
|
||||
artists: [{ id: item.artistId, name: item.artist }],
|
||||
albumArtists: [{ id: item.albumArtistId, imageUrl: null, name: item.albumArtist }],
|
||||
artists: [{ id: item.artistId, imageUrl: null, name: item.artist }],
|
||||
backdropImageUrl: imageBackdropUrl,
|
||||
createdAt: item.createdAt.split('T')[0],
|
||||
duration: item.duration * 1000 || null,
|
||||
|
|
@ -602,6 +612,12 @@ const normalizeAlbumArtist = (item: NDAlbumArtist, server: ServerListItem): Albu
|
|||
playCount: item.playCount,
|
||||
serverId: server.id,
|
||||
serverType: ServerType.NAVIDROME,
|
||||
similarArtists:
|
||||
item.similarArtists?.map((artist) => ({
|
||||
id: artist.id,
|
||||
imageUrl: artist?.artistImageUrl || null,
|
||||
name: artist.name,
|
||||
})) || null,
|
||||
songCount: item.songCount,
|
||||
userFavorite: item.starred,
|
||||
userRating: item.rating,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { SSArtistInfo } from '/@/renderer/api/subsonic.types';
|
||||
|
||||
export type NDAuthenticate = {
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
|
|
@ -126,6 +128,8 @@ export type NDAlbumArtist = {
|
|||
songCount: number;
|
||||
starred: boolean;
|
||||
starredAt: string;
|
||||
} & {
|
||||
similarArtists?: SSArtistInfo['similarArtist'];
|
||||
};
|
||||
|
||||
export type NDAuthenticationResponse = NDAuthenticate;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import type {
|
|||
NDSong,
|
||||
NDUser,
|
||||
} from '/@/renderer/api/navidrome.types';
|
||||
import { SSGenreList, SSMusicFolderList } from '/@/renderer/api/subsonic.types';
|
||||
import { ssNormalize } from '/@/renderer/api/subsonic.api';
|
||||
import { SSGenreList, SSMusicFolderList, SSSong } from '/@/renderer/api/subsonic.types';
|
||||
import type {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
|
|
@ -29,6 +30,7 @@ import type {
|
|||
RawPlaylistDetailResponse,
|
||||
RawPlaylistListResponse,
|
||||
RawSongListResponse,
|
||||
RawTopSongListResponse,
|
||||
RawUserListResponse,
|
||||
} from '/@/renderer/api/types';
|
||||
import { ServerListItem } from '/@/renderer/types';
|
||||
|
|
@ -92,6 +94,25 @@ const songList = (data: RawSongListResponse | undefined, server: ServerListItem
|
|||
};
|
||||
};
|
||||
|
||||
const topSongList = (data: RawTopSongListResponse | undefined, server: ServerListItem | null) => {
|
||||
let songs;
|
||||
|
||||
switch (server?.type) {
|
||||
case 'jellyfin':
|
||||
break;
|
||||
case 'navidrome':
|
||||
songs = data?.items?.map((item) => ssNormalize.song(item as SSSong, server, ''));
|
||||
break;
|
||||
case 'subsonic':
|
||||
songs = data?.items?.map((item) => ssNormalize.song(item as SSSong, server, ''));
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
items: songs,
|
||||
};
|
||||
};
|
||||
|
||||
const musicFolderList = (
|
||||
data: RawMusicFolderListResponse | undefined,
|
||||
server: ServerListItem | null,
|
||||
|
|
@ -265,5 +286,6 @@ export const normalize = {
|
|||
playlistDetail,
|
||||
playlistList,
|
||||
songList,
|
||||
topSongList,
|
||||
userList,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type {
|
|||
PlaylistSongListQuery,
|
||||
UserListQuery,
|
||||
AlbumArtistDetailQuery,
|
||||
TopSongListQuery,
|
||||
} from './types';
|
||||
|
||||
export const queryKeys = {
|
||||
|
|
@ -22,6 +23,10 @@ export const queryKeys = {
|
|||
return [serverId, 'albumArtists', 'list'] as const;
|
||||
},
|
||||
root: (serverId: string) => [serverId, 'albumArtists'] as const,
|
||||
topSongs: (serverId: string, query?: TopSongListQuery) => {
|
||||
if (query) return [serverId, 'albumArtists', 'topSongs', query] as const;
|
||||
return [serverId, 'albumArtists', 'topSongs'] as const;
|
||||
},
|
||||
},
|
||||
albums: {
|
||||
detail: (serverId: string, query?: AlbumDetailQuery) =>
|
||||
|
|
|
|||
|
|
@ -19,12 +19,20 @@ import type {
|
|||
SSRatingParams,
|
||||
SSAlbumArtistDetailParams,
|
||||
SSAlbumArtistListParams,
|
||||
SSTopSongListParams,
|
||||
SSTopSongListResponse,
|
||||
SSArtistInfoParams,
|
||||
SSArtistInfoResponse,
|
||||
SSArtistInfo,
|
||||
SSSong,
|
||||
SSTopSongList,
|
||||
} from '/@/renderer/api/subsonic.types';
|
||||
import {
|
||||
AlbumArtistDetailArgs,
|
||||
AlbumArtistListArgs,
|
||||
AlbumDetailArgs,
|
||||
AlbumListArgs,
|
||||
ArtistInfoArgs,
|
||||
AuthenticationResponse,
|
||||
FavoriteArgs,
|
||||
FavoriteResponse,
|
||||
|
|
@ -34,8 +42,12 @@ import {
|
|||
RatingArgs,
|
||||
RatingResponse,
|
||||
ServerListItem,
|
||||
ServerType,
|
||||
Song,
|
||||
TopSongListArgs,
|
||||
} from '/@/renderer/api/types';
|
||||
import { toast } from '/@/renderer/components/toast';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
|
||||
const getCoverArtUrl = (args: {
|
||||
baseUrl: string;
|
||||
|
|
@ -50,7 +62,7 @@ const getCoverArtUrl = (args: {
|
|||
}
|
||||
|
||||
return (
|
||||
`${args.baseUrl}/getCoverArt.view` +
|
||||
`${args.baseUrl}/rest/getCoverArt.view` +
|
||||
`?id=${args.coverArtId}` +
|
||||
`&${args.credential}` +
|
||||
'&v=1.13.0' +
|
||||
|
|
@ -65,10 +77,13 @@ const api = ky.create({
|
|||
async (_request, _options, response) => {
|
||||
const data = await response.json();
|
||||
if (data['subsonic-response'].status !== 'ok') {
|
||||
toast.error({
|
||||
message: data['subsonic-response'].error.message,
|
||||
title: 'Issue from Subsonic API',
|
||||
});
|
||||
// Suppress code related to non-linked lastfm or spotify from Navidrome
|
||||
if (data['subsonic-response'].error.code !== 0) {
|
||||
toast.error({
|
||||
message: data['subsonic-response'].error.message,
|
||||
title: 'Issue from Subsonic API',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(data['subsonic-response']), { status: 200 });
|
||||
|
|
@ -325,6 +340,118 @@ const updateRating = async (args: RatingArgs): Promise<RatingResponse> => {
|
|||
};
|
||||
};
|
||||
|
||||
const getTopSongList = async (args: TopSongListArgs): Promise<SSTopSongList> => {
|
||||
const { signal, server, query } = args;
|
||||
const defaultParams = getDefaultParams(server);
|
||||
|
||||
const searchParams: SSTopSongListParams = {
|
||||
artist: query.artist,
|
||||
count: query.limit,
|
||||
...defaultParams,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get('rest/getTopSongs.view', {
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
})
|
||||
.json<SSTopSongListResponse>();
|
||||
|
||||
return {
|
||||
items: data?.topSongs?.song,
|
||||
startIndex: 0,
|
||||
totalRecordCount: data?.topSongs?.song?.length || 0,
|
||||
};
|
||||
};
|
||||
|
||||
const getArtistInfo = async (args: ArtistInfoArgs): Promise<SSArtistInfo> => {
|
||||
const { signal, server, query } = args;
|
||||
const defaultParams = getDefaultParams(server);
|
||||
|
||||
const searchParams: SSArtistInfoParams = {
|
||||
count: query.limit,
|
||||
id: query.artistId,
|
||||
...defaultParams,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get('rest/getArtistInfo2.view', {
|
||||
prefixUrl: server?.url,
|
||||
searchParams,
|
||||
signal,
|
||||
})
|
||||
.json<SSArtistInfoResponse>();
|
||||
|
||||
return data.artistInfo2;
|
||||
};
|
||||
|
||||
const normalizeSong = (item: SSSong, server: ServerListItem, deviceId: string): Song => {
|
||||
const imageUrl =
|
||||
getCoverArtUrl({
|
||||
baseUrl: server.url,
|
||||
coverArtId: item.coverArt,
|
||||
credential: server.credential,
|
||||
size: 300,
|
||||
}) || null;
|
||||
|
||||
const streamUrl = `${server.url}/rest/stream.view?id=${item.id}&v=1.13.0&c=feishin_${deviceId}&${server.credential}`;
|
||||
|
||||
return {
|
||||
album: item.album,
|
||||
albumArtists: [
|
||||
{
|
||||
id: item.artistId || '',
|
||||
imageUrl: null,
|
||||
name: item.artist,
|
||||
},
|
||||
],
|
||||
albumId: item.albumId,
|
||||
artistName: item.artist,
|
||||
artists: [
|
||||
{
|
||||
id: item.artistId || '',
|
||||
imageUrl: null,
|
||||
name: item.artist,
|
||||
},
|
||||
],
|
||||
bitRate: item.bitRate,
|
||||
bpm: null,
|
||||
channels: null,
|
||||
comment: null,
|
||||
compilation: null,
|
||||
container: item.contentType,
|
||||
createdAt: item.created,
|
||||
discNumber: item.discNumber || 1,
|
||||
duration: item.duration,
|
||||
genres: [
|
||||
{
|
||||
id: item.genre,
|
||||
name: item.genre,
|
||||
},
|
||||
],
|
||||
id: item.id,
|
||||
imagePlaceholderUrl: null,
|
||||
imageUrl,
|
||||
itemType: LibraryItem.SONG,
|
||||
lastPlayedAt: null,
|
||||
name: item.title,
|
||||
path: item.path,
|
||||
playCount: item?.playCount || 0,
|
||||
releaseDate: null,
|
||||
releaseYear: item.year ? String(item.year) : null,
|
||||
serverId: server.id,
|
||||
serverType: ServerType.SUBSONIC,
|
||||
size: item.size,
|
||||
streamUrl,
|
||||
trackNumber: item.track,
|
||||
uniqueId: nanoid(),
|
||||
updatedAt: '',
|
||||
userFavorite: item.starred || false,
|
||||
userRating: item.userRating || null,
|
||||
};
|
||||
};
|
||||
|
||||
export const subsonicApi = {
|
||||
authenticate,
|
||||
createFavorite,
|
||||
|
|
@ -333,8 +460,14 @@ export const subsonicApi = {
|
|||
getAlbumArtistList,
|
||||
getAlbumDetail,
|
||||
getAlbumList,
|
||||
getArtistInfo,
|
||||
getCoverArtUrl,
|
||||
getGenreList,
|
||||
getMusicFolderList,
|
||||
getTopSongList,
|
||||
updateRating,
|
||||
};
|
||||
|
||||
export const ssNormalize = {
|
||||
song: normalizeSong,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -65,6 +65,12 @@ export type SSAlbumDetailResponse = {
|
|||
album: SSAlbum;
|
||||
};
|
||||
|
||||
export type SSArtistInfoParams = {
|
||||
count?: number;
|
||||
id: string;
|
||||
includeNotPresent?: boolean;
|
||||
};
|
||||
|
||||
export type SSArtistInfoResponse = {
|
||||
artistInfo2: SSArtistInfo;
|
||||
};
|
||||
|
|
@ -75,6 +81,13 @@ export type SSArtistInfo = {
|
|||
lastFmUrl?: string;
|
||||
mediumImageUrl?: string;
|
||||
musicBrainzId?: string;
|
||||
similarArtist?: {
|
||||
albumCount: string;
|
||||
artistImageUrl?: string;
|
||||
coverArt?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}[];
|
||||
smallImageUrl?: string;
|
||||
};
|
||||
|
||||
|
|
@ -186,3 +199,20 @@ export type SSRatingParams = {
|
|||
export type SSRating = null;
|
||||
|
||||
export type SSRatingResponse = null;
|
||||
|
||||
export type SSTopSongListParams = {
|
||||
artist: string;
|
||||
count?: number;
|
||||
};
|
||||
|
||||
export type SSTopSongListResponse = {
|
||||
topSongs: {
|
||||
song: SSSong[];
|
||||
};
|
||||
};
|
||||
|
||||
export type SSTopSongList = {
|
||||
items: SSSong[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number | null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import {
|
|||
SSAlbumArtistDetail,
|
||||
SSMusicFolderList,
|
||||
SSGenreList,
|
||||
SSTopSongList,
|
||||
} from '/@/renderer/api/subsonic.types';
|
||||
|
||||
export enum LibraryItem {
|
||||
|
|
@ -231,6 +232,7 @@ export type AlbumArtist = {
|
|||
playCount: number | null;
|
||||
serverId: string;
|
||||
serverType: ServerType;
|
||||
similarArtists: RelatedArtist[] | null;
|
||||
songCount: number | null;
|
||||
userFavorite: boolean;
|
||||
userRating: number | null;
|
||||
|
|
@ -256,6 +258,7 @@ export type Artist = {
|
|||
|
||||
export type RelatedArtist = {
|
||||
id: string;
|
||||
imageUrl: string | null;
|
||||
name: string;
|
||||
};
|
||||
|
||||
|
|
@ -959,3 +962,24 @@ export const userListSortMap: UserListSortMap = {
|
|||
name: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// Top Songs List
|
||||
export type RawTopSongListResponse = SSTopSongList | undefined;
|
||||
|
||||
export type TopSongListResponse = BasePaginatedResponse<Song[]>;
|
||||
|
||||
export type TopSongListQuery = {
|
||||
artist: string;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export type TopSongListArgs = { query: TopSongListQuery } & BaseEndpointArgs;
|
||||
|
||||
// Artist Info
|
||||
export type ArtistInfoQuery = {
|
||||
artistId: string;
|
||||
limit: number;
|
||||
musicFolderId?: string;
|
||||
};
|
||||
|
||||
export type ArtistInfoArgs = { query: ArtistInfoQuery } & BaseEndpointArgs;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue