mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
Add additional controller endpoints
This commit is contained in:
@@ -28,8 +28,14 @@ import type {
|
||||
RawPlaylistDetailResponse,
|
||||
PlaylistListArgs,
|
||||
RawPlaylistListResponse,
|
||||
MusicFolderListArgs,
|
||||
RawMusicFolderListResponse,
|
||||
PlaylistSongListArgs,
|
||||
ArtistListArgs,
|
||||
RawArtistListResponse,
|
||||
} from '/@/api/types';
|
||||
import { subsonicApi } from '/@/api/subsonic.api';
|
||||
import { jellyfinApi } from '/@/api/jellyfin.api';
|
||||
|
||||
export type ControllerEndpoint = Partial<{
|
||||
clearPlaylist: () => void;
|
||||
@@ -42,15 +48,16 @@ export type ControllerEndpoint = Partial<{
|
||||
getAlbumDetail: (args: AlbumDetailArgs) => Promise<RawAlbumDetailResponse>;
|
||||
getAlbumList: (args: AlbumListArgs) => Promise<RawAlbumListResponse>;
|
||||
getArtistDetail: () => void;
|
||||
getArtistList: () => void;
|
||||
getArtistList: (args: ArtistListArgs) => Promise<RawArtistListResponse>;
|
||||
getFavoritesList: () => void;
|
||||
getFolderItemList: () => void;
|
||||
getFolderList: () => void;
|
||||
getFolderSongs: () => void;
|
||||
getGenreList: (args: GenreListArgs) => Promise<RawGenreListResponse>;
|
||||
getMusicFolderList: () => void;
|
||||
getMusicFolderList: (args: MusicFolderListArgs) => Promise<RawMusicFolderListResponse>;
|
||||
getPlaylistDetail: (args: PlaylistDetailArgs) => Promise<RawPlaylistDetailResponse>;
|
||||
getPlaylistList: (args: PlaylistListArgs) => Promise<RawPlaylistListResponse>;
|
||||
getPlaylistSongList: (args: PlaylistSongListArgs) => Promise<RawSongListResponse>;
|
||||
getSongDetail: (args: SongDetailArgs) => Promise<RawSongDetailResponse>;
|
||||
getSongList: (args: SongListArgs) => Promise<RawSongListResponse>;
|
||||
updatePlaylist: () => void;
|
||||
@@ -66,25 +73,27 @@ type ApiController = {
|
||||
const endpoints: ApiController = {
|
||||
jellyfin: {
|
||||
clearPlaylist: undefined,
|
||||
createFavorite: undefined,
|
||||
createPlaylist: undefined,
|
||||
deleteFavorite: undefined,
|
||||
deletePlaylist: undefined,
|
||||
getAlbumArtistDetail: undefined,
|
||||
getAlbumArtistList: undefined,
|
||||
getAlbumDetail: undefined,
|
||||
getAlbumList: undefined,
|
||||
createFavorite: jellyfinApi.createFavorite,
|
||||
createPlaylist: jellyfinApi.createPlaylist,
|
||||
deleteFavorite: jellyfinApi.deleteFavorite,
|
||||
deletePlaylist: jellyfinApi.deletePlaylist,
|
||||
getAlbumArtistDetail: jellyfinApi.getAlbumArtistDetail,
|
||||
getAlbumArtistList: jellyfinApi.getAlbumArtistList,
|
||||
getAlbumDetail: jellyfinApi.getAlbumDetail,
|
||||
getAlbumList: jellyfinApi.getAlbumList,
|
||||
getArtistDetail: undefined,
|
||||
getArtistList: undefined,
|
||||
getArtistList: jellyfinApi.getArtistList,
|
||||
getFavoritesList: undefined,
|
||||
getFolderItemList: undefined,
|
||||
getFolderList: undefined,
|
||||
getFolderSongs: undefined,
|
||||
getGenreList: undefined,
|
||||
getMusicFolderList: undefined,
|
||||
getPlaylistDetail: undefined,
|
||||
getPlaylistList: undefined,
|
||||
getSongList: undefined,
|
||||
getGenreList: jellyfinApi.getGenreList,
|
||||
getMusicFolderList: jellyfinApi.getMusicFolderList,
|
||||
getPlaylistDetail: jellyfinApi.getPlaylistDetail,
|
||||
getPlaylistList: jellyfinApi.getPlaylistList,
|
||||
getPlaylistSongList: jellyfinApi.getPlaylistSongList,
|
||||
getSongDetail: undefined,
|
||||
getSongList: jellyfinApi.getSongList,
|
||||
updatePlaylist: undefined,
|
||||
updateRating: undefined,
|
||||
},
|
||||
@@ -108,6 +117,8 @@ const endpoints: ApiController = {
|
||||
getMusicFolderList: undefined,
|
||||
getPlaylistDetail: navidromeApi.getPlaylistDetail,
|
||||
getPlaylistList: navidromeApi.getPlaylistList,
|
||||
getPlaylistSongList: navidromeApi.getPlaylistSongList,
|
||||
getSongDetail: navidromeApi.getSongDetail,
|
||||
getSongList: navidromeApi.getSongList,
|
||||
updatePlaylist: undefined,
|
||||
updateRating: subsonicApi.updateRating,
|
||||
@@ -132,6 +143,7 @@ const endpoints: ApiController = {
|
||||
getMusicFolderList: undefined,
|
||||
getPlaylistDetail: undefined,
|
||||
getPlaylistList: undefined,
|
||||
getSongDetail: undefined,
|
||||
getSongList: undefined,
|
||||
updatePlaylist: undefined,
|
||||
updateRating: undefined,
|
||||
|
||||
@@ -0,0 +1,675 @@
|
||||
import ky from 'ky';
|
||||
import _ from 'lodash';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import type {
|
||||
JFAlbum,
|
||||
JFAlbumArtistDetail,
|
||||
JFAlbumArtistDetailResponse,
|
||||
JFAlbumArtistList,
|
||||
JFAlbumArtistListParams,
|
||||
JFAlbumArtistListResponse,
|
||||
JFAlbumDetail,
|
||||
JFAlbumDetailResponse,
|
||||
JFAlbumList,
|
||||
JFAlbumListParams,
|
||||
JFAlbumListResponse,
|
||||
JFArtistList,
|
||||
JFArtistListParams,
|
||||
JFArtistListResponse,
|
||||
JFAuthenticate,
|
||||
JFCreatePlaylistResponse,
|
||||
JFGenreList,
|
||||
JFGenreListResponse,
|
||||
JFMusicFolderList,
|
||||
JFMusicFolderListResponse,
|
||||
JFPlaylistDetail,
|
||||
JFPlaylistDetailResponse,
|
||||
JFPlaylistList,
|
||||
JFPlaylistListResponse,
|
||||
JFSong,
|
||||
JFSongList,
|
||||
JFSongListParams,
|
||||
JFSongListResponse,
|
||||
} from '/@/api/jellyfin.types';
|
||||
import { JFCollectionType } from '/@/api/jellyfin.types';
|
||||
import type {
|
||||
Album,
|
||||
AlbumArtistDetailArgs,
|
||||
AlbumArtistListArgs,
|
||||
AlbumDetailArgs,
|
||||
AlbumListArgs,
|
||||
ArtistListArgs,
|
||||
AuthenticationResponse,
|
||||
CreatePlaylistArgs,
|
||||
CreatePlaylistResponse,
|
||||
DeletePlaylistArgs,
|
||||
FavoriteArgs,
|
||||
FavoriteResponse,
|
||||
GenreListArgs,
|
||||
MusicFolderListArgs,
|
||||
PlaylistDetailArgs,
|
||||
PlaylistListArgs,
|
||||
PlaylistSongListArgs,
|
||||
Song,
|
||||
SongListArgs,
|
||||
} from '/@/api/types';
|
||||
import { songListSortMap } from '/@/api/types';
|
||||
import { albumListSortMap } from '/@/api/types';
|
||||
import { artistListSortMap } from '/@/api/types';
|
||||
import { sortOrderMap } from '/@/api/types';
|
||||
import { albumArtistListSortMap } from '/@/api/types';
|
||||
import type { ServerListItem } from '/@/store';
|
||||
import { useAuthStore } from '/@/store';
|
||||
import { ServerType } from '/@/types';
|
||||
import { parseSearchParams } from '/@/utils';
|
||||
|
||||
const api = ky.create({});
|
||||
|
||||
const authenticate = async (
|
||||
url: string,
|
||||
body: {
|
||||
password: string;
|
||||
username: string;
|
||||
},
|
||||
): Promise<AuthenticationResponse> => {
|
||||
const cleanServerUrl = url.replace(/\/$/, '');
|
||||
|
||||
const data = await ky
|
||||
.post(`${cleanServerUrl}/users/authenticatebyname`, {
|
||||
headers: {
|
||||
'X-Emby-Authorization':
|
||||
'MediaBrowser Client="Feishin", Device="PC", DeviceId="Feishin", Version="0.0.1-alpha1"',
|
||||
},
|
||||
json: {
|
||||
pw: body.password,
|
||||
username: body.username,
|
||||
},
|
||||
})
|
||||
.json<JFAuthenticate>();
|
||||
|
||||
return {
|
||||
credential: data.AccessToken,
|
||||
userId: data.User.Id,
|
||||
username: data.User.Name,
|
||||
};
|
||||
};
|
||||
|
||||
const getMusicFolderList = async (args: MusicFolderListArgs): Promise<JFMusicFolderList> => {
|
||||
const { signal } = args;
|
||||
const userId = useAuthStore.getState().currentServer?.userId;
|
||||
|
||||
const data = await api
|
||||
.get(`users/${userId}/items`, {
|
||||
signal,
|
||||
})
|
||||
.json<JFMusicFolderListResponse>();
|
||||
|
||||
const musicFolders = data.Items.filter(
|
||||
(folder) => folder.CollectionType === JFCollectionType.MUSIC,
|
||||
);
|
||||
|
||||
return {
|
||||
items: musicFolders,
|
||||
startIndex: data.StartIndex,
|
||||
totalRecordCount: data.TotalRecordCount,
|
||||
};
|
||||
};
|
||||
|
||||
const getGenreList = async (args: GenreListArgs): Promise<JFGenreList> => {
|
||||
const { signal, server } = args;
|
||||
|
||||
const data = await api
|
||||
.get('genres', {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
signal,
|
||||
})
|
||||
.json<JFGenreListResponse>();
|
||||
return data;
|
||||
};
|
||||
|
||||
const getAlbumArtistDetail = async (args: AlbumArtistDetailArgs): Promise<JFAlbumArtistDetail> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams = {
|
||||
fields: 'Genres',
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get(`/users/${server?.userId}/items/${query.id}`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
})
|
||||
.json<JFAlbumArtistDetailResponse>();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
// const getAlbumArtistAlbums = () => {
|
||||
// const { data: albumData } = await api.get(`/users/${auth.username}/items`, {
|
||||
// params: {
|
||||
// artistIds: options.id,
|
||||
// fields: 'AudioInfo, ParentId, Genres, DateCreated, ChildCount, ParentId',
|
||||
// includeItemTypes: 'MusicAlbum',
|
||||
// parentId: options.musicFolderId,
|
||||
// recursive: true,
|
||||
// sortBy: 'SortName',
|
||||
// },
|
||||
// });
|
||||
|
||||
// const { data: similarData } = await api.get(`/artists/${options.id}/similar`, {
|
||||
// params: { limit: 15, parentId: options.musicFolderId, userId: auth.username },
|
||||
// });
|
||||
// };
|
||||
|
||||
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<JFAlbumArtistList> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams: JFAlbumArtistListParams = {
|
||||
limit: query.limit,
|
||||
parentId: query.musicFolderId,
|
||||
recursive: true,
|
||||
sortBy: albumArtistListSortMap.jellyfin[query.sortBy],
|
||||
sortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
startIndex: query.startIndex,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get('artists/albumArtists', {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
})
|
||||
.json<JFAlbumArtistListResponse>();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getArtistList = async (args: ArtistListArgs): Promise<JFArtistList> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams: JFArtistListParams = {
|
||||
limit: query.limit,
|
||||
parentId: query.musicFolderId,
|
||||
recursive: true,
|
||||
sortBy: artistListSortMap.jellyfin[query.sortBy],
|
||||
sortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
startIndex: query.startIndex,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get('artists', {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
})
|
||||
.json<JFArtistListResponse>();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getAlbumDetail = async (args: AlbumDetailArgs): Promise<JFAlbumDetail> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams = {
|
||||
fields: 'Genres, DateCreated, ChildCount',
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get(`users/${server?.userId}/items/${query.id}`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams,
|
||||
signal,
|
||||
})
|
||||
.json<JFAlbumDetailResponse>();
|
||||
|
||||
const songsSearchParams = {
|
||||
fields: 'Genres, DateCreated, MediaSources, ParentId',
|
||||
parentId: query.id,
|
||||
sortBy: 'SortName',
|
||||
};
|
||||
|
||||
const songsData = await api
|
||||
.get(`users/${server?.userId}/items`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: songsSearchParams,
|
||||
signal,
|
||||
})
|
||||
.json<JFSongListResponse>();
|
||||
|
||||
return { ...data, songs: songsData.Items };
|
||||
};
|
||||
|
||||
const getAlbumList = async (args: AlbumListArgs): Promise<JFAlbumList> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams: JFAlbumListParams = {
|
||||
includeItemTypes: 'MusicAlbum',
|
||||
limit: query.limit,
|
||||
parentId: query.musicFolderId,
|
||||
recursive: true,
|
||||
sortBy: albumListSortMap.jellyfin[query.sortBy],
|
||||
sortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
startIndex: query.startIndex,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get(`users/${server?.userId}/items`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
})
|
||||
.json<JFAlbumListResponse>();
|
||||
|
||||
return {
|
||||
items: data.Items,
|
||||
startIndex: query.startIndex,
|
||||
totalRecordCount: data.TotalRecordCount,
|
||||
};
|
||||
};
|
||||
|
||||
const getSongList = async (args: SongListArgs): Promise<JFSongList> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams: JFSongListParams = {
|
||||
fields: 'Genres, DateCreated, MediaSources, ParentId',
|
||||
includeItemTypes: 'Audio',
|
||||
limit: query.limit,
|
||||
parentId: query.musicFolderId,
|
||||
recursive: true,
|
||||
sortBy: songListSortMap.jellyfin[query.sortBy],
|
||||
sortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
startIndex: query.startIndex,
|
||||
...query.jfParams,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get(`users/${server?.userId}/items`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
})
|
||||
.json<JFSongListResponse>();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<JFPlaylistDetail> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams = {
|
||||
fields: 'Genres, DateCreated, MediaSources, ChildCount, ParentId',
|
||||
ids: query.id,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get(`users/${server?.userId}/items/${query.id}`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams,
|
||||
signal,
|
||||
})
|
||||
.json<JFPlaylistDetailResponse>();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getPlaylistSongList = async (args: PlaylistSongListArgs): Promise<JFSongList> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams: JFSongListParams = {
|
||||
fields: 'Genres, DateCreated, MediaSources, UserData, ParentId',
|
||||
includeItemTypes: 'Audio',
|
||||
sortOrder: query.sortOrder ? sortOrderMap.jellyfin[query.sortOrder] : undefined,
|
||||
startIndex: 0,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get(`playlists/${query.id}/items`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
})
|
||||
.json<JFSongListResponse>();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getPlaylistList = async (args: PlaylistListArgs): Promise<JFPlaylistList> => {
|
||||
const { server, signal } = args;
|
||||
|
||||
const searchParams = {
|
||||
fields: 'ChildCount, Genres, DateCreated, ParentId, Overview',
|
||||
includeItemTypes: 'Playlist',
|
||||
recursive: true,
|
||||
sortBy: 'SortName',
|
||||
sortOrder: 'Ascending',
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get(`/users/${server?.userId}/items`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
})
|
||||
.json<JFPlaylistListResponse>();
|
||||
|
||||
const playlistData = data.Items.filter((item) => item.MediaType === 'Audio');
|
||||
|
||||
return {
|
||||
Items: playlistData,
|
||||
StartIndex: 0,
|
||||
TotalRecordCount: playlistData.length,
|
||||
};
|
||||
};
|
||||
|
||||
const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => {
|
||||
const { query, server } = args;
|
||||
|
||||
const body = {
|
||||
MediaType: 'Audio',
|
||||
Name: query.name,
|
||||
UserId: server?.userId,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.post('playlists', {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
json: body,
|
||||
prefixUrl: server?.url,
|
||||
})
|
||||
.json<JFCreatePlaylistResponse>();
|
||||
|
||||
return {
|
||||
id: data.Id,
|
||||
name: query.name,
|
||||
};
|
||||
};
|
||||
|
||||
const deletePlaylist = async (args: DeletePlaylistArgs): Promise<null> => {
|
||||
const { query, server } = args;
|
||||
|
||||
await api.delete(`items/${query.id}`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
||||
const { query, server } = args;
|
||||
|
||||
await api.post(`users/${server?.userId}/favoriteitems/${query.id}`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
});
|
||||
|
||||
return {
|
||||
id: query.id,
|
||||
};
|
||||
};
|
||||
|
||||
const deleteFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
||||
const { query, server } = args;
|
||||
|
||||
await api.delete(`users/${server?.userId}/favoriteitems/${query.id}`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
});
|
||||
|
||||
return {
|
||||
id: query.id,
|
||||
};
|
||||
};
|
||||
|
||||
const getStreamUrl = (args: {
|
||||
container?: string;
|
||||
deviceId: string;
|
||||
eTag?: string;
|
||||
id: string;
|
||||
mediaSourceId?: string;
|
||||
server: ServerListItem;
|
||||
}) => {
|
||||
const { id, server, deviceId } = args;
|
||||
|
||||
return (
|
||||
`${server?.url}/audio` +
|
||||
`/${id}/universal` +
|
||||
`?userId=${server.userId}` +
|
||||
`&deviceId=${deviceId}` +
|
||||
'&audioCodec=aac' +
|
||||
`&api_key=${server.credential}` +
|
||||
`&playSessionId=${deviceId}` +
|
||||
'&container=opus,mp3,aac,m4a,m4b,flac,wav,ogg' +
|
||||
'&transcodingContainer=ts' +
|
||||
'&transcodingProtocol=hls'
|
||||
);
|
||||
};
|
||||
|
||||
const getAlbumCoverArtUrl = (args: { baseUrl: string; item: JFAlbum; size: number }) => {
|
||||
const size = args.size ? args.size : 300;
|
||||
|
||||
if (!args.item.ImageTags?.Primary && !args.item?.AlbumPrimaryImageTag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
`${args.baseUrl}/Items` +
|
||||
`/${args.item.Id}` +
|
||||
'/Images/Primary' +
|
||||
`?width=${size}&height=${size}` +
|
||||
'&quality=96'
|
||||
);
|
||||
};
|
||||
|
||||
const getSongCoverArtUrl = (args: { baseUrl: string; item: JFSong; size: number }) => {
|
||||
const size = args.size ? args.size : 300;
|
||||
|
||||
if (!args.item.ImageTags?.Primary) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (args.item.ImageTags.Primary) {
|
||||
return (
|
||||
`${args.baseUrl}/Items` +
|
||||
`/${args.item.Id}` +
|
||||
'/Images/Primary' +
|
||||
`?width=${size}&height=${size}` +
|
||||
'&quality=96'
|
||||
);
|
||||
}
|
||||
|
||||
if (!args.item?.AlbumPrimaryImageTag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fall back to album art if no image embedded
|
||||
return (
|
||||
`${args.baseUrl}/Items` +
|
||||
`/${args.item?.AlbumId}` +
|
||||
'/Images/Primary' +
|
||||
`?width=${size}&height=${size}` +
|
||||
'&quality=96'
|
||||
);
|
||||
};
|
||||
|
||||
const normalizeAlbum = (item: JFAlbum, server: ServerListItem, imageSize?: number): Album => {
|
||||
return {
|
||||
albumArtists:
|
||||
item.AlbumArtists?.map((entry) => ({
|
||||
id: entry.Id,
|
||||
name: entry.Name,
|
||||
})) || [],
|
||||
artists: item.ArtistItems?.map((entry) => ({ id: entry.Id, name: entry.Name })),
|
||||
backdropImageUrl: null,
|
||||
createdAt: item.DateCreated,
|
||||
duration: item.RunTimeTicks / 10000000,
|
||||
genres: item.GenreItems?.map((entry) => ({ id: entry.Id, name: entry.Name })),
|
||||
id: item.Id,
|
||||
imagePlaceholderUrl: null,
|
||||
imageUrl: getAlbumCoverArtUrl({
|
||||
baseUrl: server.url,
|
||||
item,
|
||||
size: imageSize || 300,
|
||||
}),
|
||||
isCompilation: null,
|
||||
isFavorite: item.UserData?.IsFavorite || false,
|
||||
name: item.Name,
|
||||
playCount: item.UserData?.PlayCount || 0,
|
||||
rating: null,
|
||||
releaseDate: item.PremiereDate || null,
|
||||
releaseYear: item.ProductionYear,
|
||||
serverType: ServerType.JELLYFIN,
|
||||
size: null,
|
||||
songCount: item?.ChildCount || null,
|
||||
uniqueId: nanoid(),
|
||||
updatedAt: item?.DateLastMediaAdded || item.DateCreated,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeSong = (
|
||||
item: JFSong,
|
||||
server: ServerListItem,
|
||||
deviceId: string,
|
||||
imageSize?: number,
|
||||
): Song => {
|
||||
return {
|
||||
album: item.Album,
|
||||
albumArtists: item.AlbumArtists?.map((entry) => ({ id: entry.Id, name: entry.Name })),
|
||||
albumId: item.AlbumId,
|
||||
artistName: item.ArtistItems[0]?.Name,
|
||||
artists: item.ArtistItems.map((entry) => ({ id: entry.Id, name: entry.Name })),
|
||||
bitRate: item.MediaSources && Number(Math.trunc(item.MediaSources[0]?.Bitrate / 1000)),
|
||||
compilation: null,
|
||||
container: (item.MediaSources && item.MediaSources[0]?.Container) || null,
|
||||
createdAt: item.DateCreated,
|
||||
discNumber: (item.ParentIndexNumber && item.ParentIndexNumber) || 1,
|
||||
duration: item.RunTimeTicks / 10000000,
|
||||
genres: item.GenreItems.map((entry: any) => ({ id: entry.Id, name: entry.Name })),
|
||||
id: item.Id,
|
||||
imageUrl: getSongCoverArtUrl({ baseUrl: server.url, item, size: imageSize || 300 }),
|
||||
isFavorite: (item.UserData && item.UserData.IsFavorite) || false,
|
||||
name: item.Name,
|
||||
path: (item.MediaSources && item.MediaSources[0]?.Path) || null,
|
||||
playCount: (item.UserData && item.UserData.PlayCount) || 0,
|
||||
releaseDate: (item.ProductionYear && new Date(item.ProductionYear, 0, 1).toISOString()) || null,
|
||||
releaseYear: (item.ProductionYear && String(item.ProductionYear)) || null,
|
||||
serverId: server.id,
|
||||
size: item.MediaSources && item.MediaSources[0]?.Size,
|
||||
streamUrl: getStreamUrl({
|
||||
container: item.MediaSources[0]?.Container,
|
||||
deviceId,
|
||||
eTag: item.MediaSources[0]?.ETag,
|
||||
id: item.Id,
|
||||
mediaSourceId: item.MediaSources[0]?.Id,
|
||||
server,
|
||||
}),
|
||||
trackNumber: item.IndexNumber,
|
||||
type: ServerType.JELLYFIN,
|
||||
uniqueId: nanoid(),
|
||||
updatedAt: item.DateCreated,
|
||||
};
|
||||
};
|
||||
|
||||
// const normalizeArtist = (item: any) => {
|
||||
// return {
|
||||
// album: (item.album || []).map((entry: any) => normalizeAlbum(entry)),
|
||||
// albumCount: item.AlbumCount,
|
||||
// duration: item.RunTimeTicks / 10000000,
|
||||
// genre: item.GenreItems && item.GenreItems.map((entry: any) => normalizeItem(entry)),
|
||||
// id: item.Id,
|
||||
// image: getCoverArtUrl(item),
|
||||
// info: {
|
||||
// biography: item.Overview,
|
||||
// externalUrl: (item.ExternalUrls || []).map((entry: any) => normalizeItem(entry)),
|
||||
// imageUrl: undefined,
|
||||
// similarArtist: (item.similarArtist || []).map((entry: any) => normalizeArtist(entry)),
|
||||
// },
|
||||
// starred: item.UserData && item.UserData?.IsFavorite ? 'true' : undefined,
|
||||
// title: item.Name,
|
||||
// uniqueId: nanoid(),
|
||||
// };
|
||||
// };
|
||||
|
||||
// const normalizePlaylist = (item: any) => {
|
||||
// return {
|
||||
// changed: item.DateLastMediaAdded,
|
||||
// comment: item.Overview,
|
||||
// created: item.DateCreated,
|
||||
// duration: item.RunTimeTicks / 10000000,
|
||||
// genre: item.GenreItems && item.GenreItems.map((entry: any) => normalizeItem(entry)),
|
||||
// id: item.Id,
|
||||
// image: getCoverArtUrl(item, 350),
|
||||
// owner: undefined,
|
||||
// public: undefined,
|
||||
// song: [],
|
||||
// songCount: item.ChildCount,
|
||||
// title: item.Name,
|
||||
// uniqueId: nanoid(),
|
||||
// };
|
||||
// };
|
||||
|
||||
// const normalizeGenre = (item: any) => {
|
||||
// return {
|
||||
// albumCount: undefined,
|
||||
// id: item.Id,
|
||||
// songCount: undefined,
|
||||
// title: item.Name,
|
||||
// type: Item.Genre,
|
||||
// uniqueId: nanoid(),
|
||||
// };
|
||||
// };
|
||||
|
||||
// const normalizeFolder = (item: any) => {
|
||||
// return {
|
||||
// created: item.DateCreated,
|
||||
// id: item.Id,
|
||||
// image: getCoverArtUrl(item, 150),
|
||||
// isDir: true,
|
||||
// title: item.Name,
|
||||
// type: Item.Folder,
|
||||
// uniqueId: nanoid(),
|
||||
// };
|
||||
// };
|
||||
|
||||
// const normalizeScanStatus = () => {
|
||||
// return {
|
||||
// count: 'N/a',
|
||||
// scanning: false,
|
||||
// };
|
||||
// };
|
||||
|
||||
export const jellyfinApi = {
|
||||
authenticate,
|
||||
createFavorite,
|
||||
createPlaylist,
|
||||
deleteFavorite,
|
||||
deletePlaylist,
|
||||
getAlbumArtistDetail,
|
||||
getAlbumArtistList,
|
||||
getAlbumDetail,
|
||||
getAlbumList,
|
||||
getArtistList,
|
||||
getGenreList,
|
||||
getMusicFolderList,
|
||||
getPlaylistDetail,
|
||||
getPlaylistList,
|
||||
getPlaylistSongList,
|
||||
getSongList,
|
||||
};
|
||||
|
||||
export const jfNormalize = {
|
||||
album: normalizeAlbum,
|
||||
song: normalizeSong,
|
||||
};
|
||||
@@ -1,32 +1,90 @@
|
||||
export type JFBaseResponse = {
|
||||
export type JFBasePaginatedResponse = {
|
||||
StartIndex: number;
|
||||
TotalRecordCount: number;
|
||||
};
|
||||
|
||||
export interface JFMusicFoldersResponse extends JFBaseResponse {
|
||||
export interface JFMusicFolderListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFMusicFolder[];
|
||||
}
|
||||
|
||||
export interface JFGenreResponse extends JFBaseResponse {
|
||||
export type JFMusicFolderList = {
|
||||
items: JFMusicFolder[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export interface JFGenreListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFGenre[];
|
||||
}
|
||||
|
||||
export interface JFAlbumArtistsResponse extends JFBaseResponse {
|
||||
export type JFGenreList = JFGenreListResponse;
|
||||
|
||||
export type JFAlbumArtistDetailResponse = JFAlbumArtist;
|
||||
|
||||
export type JFAlbumArtistDetail = JFAlbumArtistDetailResponse;
|
||||
|
||||
export interface JFAlbumArtistListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFAlbumArtist[];
|
||||
}
|
||||
|
||||
export interface JFArtistsResponse extends JFBaseResponse {
|
||||
export type JFAlbumArtistList = JFAlbumArtistListResponse;
|
||||
|
||||
export interface JFArtistListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFAlbumArtist[];
|
||||
}
|
||||
|
||||
export interface JFAlbumsResponse extends JFBaseResponse {
|
||||
export type JFArtistList = JFArtistListResponse;
|
||||
|
||||
export interface JFAlbumListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFAlbum[];
|
||||
}
|
||||
|
||||
export interface JFSongsResponse extends JFBaseResponse {
|
||||
export type JFAlbumList = {
|
||||
items: JFAlbum[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type JFAlbumDetailResponse = JFAlbum;
|
||||
|
||||
export type JFAlbumDetail = JFAlbum & { songs?: JFSong[] };
|
||||
|
||||
export interface JFSongListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFSong[];
|
||||
}
|
||||
|
||||
export type JFSongList = JFSongListResponse;
|
||||
|
||||
export interface JFPlaylistListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFPlaylist[];
|
||||
}
|
||||
|
||||
export type JFPlaylistList = JFPlaylistListResponse;
|
||||
|
||||
export type JFPlaylistDetailResponse = JFPlaylist;
|
||||
|
||||
export type JFPlaylistDetail = JFPlaylist & { songs?: JFSong[] };
|
||||
|
||||
export type JFPlaylist = {
|
||||
BackdropImageTags: string[];
|
||||
ChannelId: null;
|
||||
ChildCount?: number;
|
||||
DateCreated: string;
|
||||
GenreItems: GenreItem[];
|
||||
Genres: string[];
|
||||
Id: string;
|
||||
ImageBlurHashes: ImageBlurHashes;
|
||||
ImageTags: ImageTags;
|
||||
IsFolder: boolean;
|
||||
LocationType: string;
|
||||
MediaType: string;
|
||||
Name: string;
|
||||
RunTimeTicks: number;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
UserData: UserData;
|
||||
};
|
||||
|
||||
export type JFRequestParams = {
|
||||
albumArtistIds?: string;
|
||||
artistIds?: string;
|
||||
@@ -114,10 +172,13 @@ export type JFArtist = {
|
||||
export type JFAlbum = {
|
||||
AlbumArtist: string;
|
||||
AlbumArtists: JFGenericItem[];
|
||||
AlbumPrimaryImageTag: string;
|
||||
ArtistItems: JFGenericItem[];
|
||||
Artists: string[];
|
||||
ChannelId: null;
|
||||
ChildCount?: number;
|
||||
DateCreated: string;
|
||||
DateLastMediaAdded?: string;
|
||||
ExternalUrls: ExternalURL[];
|
||||
GenreItems: JFGenericItem[];
|
||||
Genres: string[];
|
||||
@@ -134,6 +195,7 @@ export type JFAlbum = {
|
||||
RunTimeTicks: number;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
UserData?: UserData;
|
||||
} & {
|
||||
songs?: JFSong[];
|
||||
};
|
||||
@@ -168,6 +230,7 @@ export type JFSong = {
|
||||
ServerId: string;
|
||||
SortName: string;
|
||||
Type: string;
|
||||
UserData?: UserData;
|
||||
};
|
||||
|
||||
type ImageBlurHashes = {
|
||||
@@ -405,6 +468,21 @@ type Policy = {
|
||||
SyncPlayAccess: string;
|
||||
};
|
||||
|
||||
type JFBaseParams = {
|
||||
enableImageTypes?: JFImageType[];
|
||||
fields?: string;
|
||||
imageTypeLimit?: number;
|
||||
parentId?: string;
|
||||
recursive?: boolean;
|
||||
};
|
||||
|
||||
type JFPaginationParams = {
|
||||
limit?: number;
|
||||
nameStartsWith?: string;
|
||||
sortOrder?: JFSortOrder;
|
||||
startIndex?: number;
|
||||
};
|
||||
|
||||
export enum JFSortOrder {
|
||||
ASC = 'Ascending',
|
||||
DESC = 'Descending',
|
||||
@@ -412,22 +490,81 @@ export enum JFSortOrder {
|
||||
|
||||
export enum JFAlbumListSort {
|
||||
ALBUM_ARTIST = 'AlbumArtist,SortName',
|
||||
COMMUNITY_RATING = 'CommunityRating,SortName',
|
||||
CRITIC_RATING = 'CriticRating,SortName',
|
||||
NAME = 'SortName',
|
||||
RANDOM = 'Random,SortName',
|
||||
RATING = 'CommunityRating,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'ProductionYear,PremiereDate,SortName',
|
||||
}
|
||||
|
||||
export type JFAlbumListParams = {
|
||||
enableImageTypes: JFImageType[];
|
||||
imageTypeLimit: number;
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
includeItemTypes: 'MusicAlbum';
|
||||
limit?: number;
|
||||
parentId: string;
|
||||
recursive: boolean;
|
||||
sortBy: JFAlbumListSort;
|
||||
sortOrder: JFSortOrder;
|
||||
startIndex: number;
|
||||
sortBy?: JFAlbumListSort;
|
||||
years?: string;
|
||||
} & JFBaseParams &
|
||||
JFPaginationParams;
|
||||
|
||||
export enum JFSongListSort {
|
||||
ALBUM = 'Album,SortName',
|
||||
ALBUM_ARTIST = 'AlbumArtist,Album,SortName',
|
||||
ARTIST = 'Artist,Album,SortName',
|
||||
DURATION = 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME = 'Name,SortName',
|
||||
PLAY_COUNT = 'PlayCount,SortName',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RECENTLY_PLAYED = 'DatePlayed,SortName',
|
||||
RELEASE_DATE = 'PremiereDate,AlbumArtist,Album,SortName',
|
||||
}
|
||||
|
||||
export type JFSongListParams = {
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
includeItemTypes: 'Audio';
|
||||
sortBy?: JFSongListSort;
|
||||
years?: string;
|
||||
} & JFBaseParams &
|
||||
JFPaginationParams;
|
||||
|
||||
export enum JFAlbumArtistListSort {
|
||||
ALBUM = 'Album,SortName',
|
||||
DURATION = 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME = 'Name,SortName',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'PremiereDate,AlbumArtist,Album,SortName',
|
||||
}
|
||||
|
||||
export type JFAlbumArtistListParams = {
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
sortBy?: JFAlbumArtistListSort;
|
||||
years?: string;
|
||||
} & JFBaseParams &
|
||||
JFPaginationParams;
|
||||
|
||||
export enum JFArtistListSort {
|
||||
ALBUM = 'Album,SortName',
|
||||
DURATION = 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME = 'Name,SortName',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'PremiereDate,AlbumArtist,Album,SortName',
|
||||
}
|
||||
|
||||
export type JFArtistListParams = {
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
sortBy?: JFArtistListSort;
|
||||
years?: string;
|
||||
} & JFBaseParams &
|
||||
JFPaginationParams;
|
||||
|
||||
export type JFCreatePlaylistResponse = {
|
||||
Id: string;
|
||||
};
|
||||
|
||||
export type JFCreatePlaylist = JFCreatePlaylistResponse;
|
||||
|
||||
@@ -4,7 +4,6 @@ import type {
|
||||
NDGenreListResponse,
|
||||
NDArtistListResponse,
|
||||
NDAlbumDetail,
|
||||
NDSongListResponse,
|
||||
NDAlbumListParams,
|
||||
NDAlbumList,
|
||||
NDSongDetailResponse,
|
||||
@@ -12,18 +11,14 @@ import type {
|
||||
NDSong,
|
||||
NDAuthenticationResponse,
|
||||
NDAlbumDetailResponse,
|
||||
NDSongList,
|
||||
NDSongDetail,
|
||||
NDGenreList,
|
||||
NDAlbumArtistListParams,
|
||||
NDAlbumArtistListSort,
|
||||
NDAlbumArtistDetail,
|
||||
NDAlbumListResponse,
|
||||
NDAlbumArtistDetailResponse,
|
||||
NDAlbumArtistList,
|
||||
NDSongListParams,
|
||||
NDSongListSort,
|
||||
NDCreatePlaylist,
|
||||
NDCreatePlaylistParams,
|
||||
NDCreatePlaylistResponse,
|
||||
NDDeletePlaylist,
|
||||
@@ -31,11 +26,13 @@ import type {
|
||||
NDPlaylistListParams,
|
||||
NDPlaylistDetail,
|
||||
NDPlaylistList,
|
||||
NDPlaylistListSort,
|
||||
NDPlaylistListResponse,
|
||||
NDPlaylistDetailResponse,
|
||||
NDSongList,
|
||||
NDSongListResponse,
|
||||
} from '/@/api/navidrome.types';
|
||||
import { NDAlbumListSort } from '/@/api/navidrome.types';
|
||||
import { NDPlaylistListSort } from '/@/api/navidrome.types';
|
||||
import { NDSongListSort } from '/@/api/navidrome.types';
|
||||
import { NDSortOrder } from '/@/api/navidrome.types';
|
||||
import type {
|
||||
Album,
|
||||
@@ -52,12 +49,18 @@ import type {
|
||||
DeletePlaylistArgs,
|
||||
PlaylistListArgs,
|
||||
PlaylistDetailArgs,
|
||||
CreatePlaylistResponse,
|
||||
PlaylistSongListArgs,
|
||||
} from '/@/api/types';
|
||||
import { SortOrder } from '/@/api/types';
|
||||
import { playlistListSortMap } from '/@/api/types';
|
||||
import { albumArtistListSortMap } from '/@/api/types';
|
||||
import { songListSortMap } from '/@/api/types';
|
||||
import { albumListSortMap, sortOrderMap } from '/@/api/types';
|
||||
import { toast } from '/@/components';
|
||||
import type { ServerListItem } from '/@/store';
|
||||
import { useAuthStore } from '/@/store';
|
||||
import { ServerType } from '/@/types';
|
||||
import { parseSearchParams } from '/@/utils';
|
||||
|
||||
const api = ky.create({
|
||||
hooks: {
|
||||
@@ -95,56 +98,6 @@ const api = ky.create({
|
||||
},
|
||||
});
|
||||
|
||||
// api.interceptors.request.use(
|
||||
// (config) => {
|
||||
// const server = useAuthStore.getState().currentServer;
|
||||
|
||||
// config.baseURL = server?.url;
|
||||
// config.headers = {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'x-nd-authorization': `Bearer ${server?.ndCredential}`,
|
||||
// };
|
||||
|
||||
// return config;
|
||||
// },
|
||||
// (err) => {
|
||||
// return Promise.reject(err);
|
||||
// },
|
||||
// );
|
||||
|
||||
// api.interceptors.response.use(
|
||||
// (res) => {
|
||||
// const serverId = useAuthStore.getState().currentServer?.id;
|
||||
|
||||
// if (serverId) {
|
||||
// useAuthStore.getState().actions.updateServer(serverId, {
|
||||
// ndCredential: res.headers['x-nd-authorization'] as string,
|
||||
// });
|
||||
// }
|
||||
|
||||
// return res;
|
||||
// },
|
||||
// async (err) => {
|
||||
// if (!err.response) {
|
||||
// return Promise.reject(err);
|
||||
// }
|
||||
|
||||
// if (err.response && err.response.status === 401) {
|
||||
// toast.error({
|
||||
// message: 'Your session has expired.',
|
||||
// });
|
||||
|
||||
// const serverId = useAuthStore.getState().currentServer?.id;
|
||||
|
||||
// if (serverId) {
|
||||
// useAuthStore.getState().actions.setCurrentServer(null);
|
||||
// useAuthStore.getState().actions.updateServer(serverId, { ndCredential: undefined });
|
||||
// }
|
||||
// }
|
||||
// return Promise.reject(err);
|
||||
// },
|
||||
// );
|
||||
|
||||
const authenticate = async (
|
||||
url: string,
|
||||
body: { password: string; username: string },
|
||||
@@ -193,22 +146,7 @@ const getAlbumArtistDetail = async (args: AlbumArtistDetailArgs): Promise<NDAlbu
|
||||
})
|
||||
.json<NDAlbumArtistDetailResponse>();
|
||||
|
||||
const albumsData = await api
|
||||
.get('api/album', {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: {
|
||||
_end: 0,
|
||||
_order: NDSortOrder.ASC,
|
||||
_sort: NDAlbumListSort.YEAR,
|
||||
_start: 0,
|
||||
artist_id: query.id,
|
||||
} as NDAlbumListParams,
|
||||
signal,
|
||||
})
|
||||
.json<NDAlbumListResponse>();
|
||||
|
||||
return { ...data, albums: albumsData };
|
||||
return { ...data };
|
||||
};
|
||||
|
||||
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<NDAlbumArtistList> => {
|
||||
@@ -216,8 +154,8 @@ const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<NDAlbumArt
|
||||
|
||||
const searchParams: NDAlbumArtistListParams = {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
||||
_sort: query.sortBy as NDAlbumArtistListSort,
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: albumArtistListSortMap.navidrome[query.sortBy],
|
||||
_start: query.startIndex,
|
||||
...query.ndParams,
|
||||
};
|
||||
@@ -267,8 +205,8 @@ const getAlbumList = async (args: AlbumListArgs): Promise<NDAlbumList> => {
|
||||
|
||||
const searchParams: NDAlbumListParams = {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
||||
_sort: query.sortBy as NDAlbumListSort,
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: albumListSortMap.navidrome[query.sortBy],
|
||||
_start: query.startIndex,
|
||||
...query.ndParams,
|
||||
};
|
||||
@@ -295,8 +233,8 @@ const getSongList = async (args: SongListArgs): Promise<NDSongList> => {
|
||||
|
||||
const searchParams: NDSongListParams = {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
||||
_sort: query.sortBy as NDSongListSort,
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: songListSortMap.navidrome[query.sortBy],
|
||||
_start: query.startIndex,
|
||||
...query.ndParams,
|
||||
};
|
||||
@@ -324,6 +262,7 @@ const getSongDetail = async (args: SongDetailArgs): Promise<NDSongDetail> => {
|
||||
const data = await api
|
||||
.get(`api/song/${query.id}`, {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||
prefixUrl: server?.url,
|
||||
signal,
|
||||
})
|
||||
.json<NDSongDetailResponse>();
|
||||
@@ -331,7 +270,7 @@ const getSongDetail = async (args: SongDetailArgs): Promise<NDSongDetail> => {
|
||||
return data;
|
||||
};
|
||||
|
||||
const createPlaylist = async (args: CreatePlaylistArgs): Promise<NDCreatePlaylist> => {
|
||||
const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const json: NDCreatePlaylistParams = {
|
||||
@@ -344,11 +283,15 @@ const createPlaylist = async (args: CreatePlaylistArgs): Promise<NDCreatePlaylis
|
||||
.post('api/playlist', {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||
json,
|
||||
prefixUrl: server?.url,
|
||||
signal,
|
||||
})
|
||||
.json<NDCreatePlaylistResponse>();
|
||||
|
||||
return data;
|
||||
return {
|
||||
id: data.id,
|
||||
name: query.name,
|
||||
};
|
||||
};
|
||||
|
||||
const deletePlaylist = async (args: DeletePlaylistArgs): Promise<NDDeletePlaylist> => {
|
||||
@@ -357,6 +300,7 @@ const deletePlaylist = async (args: DeletePlaylistArgs): Promise<NDDeletePlaylis
|
||||
const data = await api
|
||||
.delete(`api/playlist/${query.id}`, {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||
prefixUrl: server?.url,
|
||||
signal,
|
||||
})
|
||||
.json<NDDeletePlaylistResponse>();
|
||||
@@ -369,12 +313,12 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise<NDPlaylistList>
|
||||
|
||||
const searchParams: NDPlaylistListParams = {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
||||
_sort: query.sortBy as NDPlaylistListSort,
|
||||
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : NDSortOrder.ASC,
|
||||
_sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : NDPlaylistListSort.NAME,
|
||||
_start: query.startIndex,
|
||||
};
|
||||
|
||||
const res = await api.get('api/song', {
|
||||
const res = await api.get('api/playlist', {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||
prefixUrl: server?.url,
|
||||
searchParams,
|
||||
@@ -395,8 +339,9 @@ const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<NDPlaylistDe
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const data = await api
|
||||
.get(`api/song/${query.id}`, {
|
||||
.get(`api/playlist/${query.id}`, {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||
prefixUrl: server?.url,
|
||||
signal,
|
||||
})
|
||||
.json<NDPlaylistDetailResponse>();
|
||||
@@ -404,6 +349,33 @@ const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<NDPlaylistDe
|
||||
return data;
|
||||
};
|
||||
|
||||
const getPlaylistSongList = async (args: PlaylistSongListArgs): Promise<NDSongList> => {
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams: NDSongListParams & { playlist_id: string } = {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : NDSortOrder.ASC,
|
||||
_sort: query.sortBy ? songListSortMap.navidrome[query.sortBy] : NDSongListSort.ID,
|
||||
_start: query.startIndex,
|
||||
playlist_id: query.id,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get(`api/playlist/${query.id}/tracks`, {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
})
|
||||
.json<NDSongListResponse>();
|
||||
|
||||
return {
|
||||
items: data,
|
||||
startIndex: query?.startIndex || 0,
|
||||
totalRecordCount: data.length,
|
||||
};
|
||||
};
|
||||
|
||||
const getCoverArtUrl = (args: {
|
||||
baseUrl: string;
|
||||
coverArtId: string;
|
||||
@@ -426,7 +398,7 @@ const getCoverArtUrl = (args: {
|
||||
);
|
||||
};
|
||||
|
||||
const normalizeAlbum = (item: NDAlbum, server: ServerListItem, imageSize?: number) => {
|
||||
const normalizeAlbum = (item: NDAlbum, server: ServerListItem, imageSize?: number): Album => {
|
||||
const imageUrl = getCoverArtUrl({
|
||||
baseUrl: server.url,
|
||||
coverArtId: item.coverArtId,
|
||||
@@ -434,13 +406,14 @@ const normalizeAlbum = (item: NDAlbum, server: ServerListItem, imageSize?: numbe
|
||||
size: imageSize || 300,
|
||||
});
|
||||
|
||||
const imagePlaceholderUrl = imageUrl?.replace(/size=\d+/, 'size=50');
|
||||
const imagePlaceholderUrl = imageUrl?.replace(/size=\d+/, 'size=50') || null;
|
||||
|
||||
return {
|
||||
albumArtists: [{ id: item.albumArtistId, name: item.albumArtist }],
|
||||
artists: [{ id: item.artistId, name: item.artist }],
|
||||
backdropImageUrl: null,
|
||||
createdAt: item.createdAt,
|
||||
duration: null,
|
||||
genres: item.genres,
|
||||
id: item.id,
|
||||
imagePlaceholderUrl,
|
||||
@@ -457,7 +430,7 @@ const normalizeAlbum = (item: NDAlbum, server: ServerListItem, imageSize?: numbe
|
||||
songCount: item.songCount,
|
||||
uniqueId: nanoid(),
|
||||
updatedAt: item.updatedAt,
|
||||
} as Album;
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeSong = (
|
||||
@@ -465,7 +438,7 @@ const normalizeSong = (
|
||||
server: ServerListItem,
|
||||
deviceId: string,
|
||||
imageSize?: number,
|
||||
) => {
|
||||
): Song => {
|
||||
const imageUrl = getCoverArtUrl({
|
||||
baseUrl: server.url,
|
||||
coverArtId: item.albumId,
|
||||
@@ -490,6 +463,8 @@ const normalizeSong = (
|
||||
imageUrl,
|
||||
isFavorite: item.starred,
|
||||
name: item.title,
|
||||
path: item.path,
|
||||
playCount: item.playCount,
|
||||
releaseDate: new Date(item.year, 0, 1).toISOString(),
|
||||
releaseYear: String(item.year),
|
||||
serverId: server.id,
|
||||
@@ -499,7 +474,7 @@ const normalizeSong = (
|
||||
type: ServerType.NAVIDROME,
|
||||
uniqueId: nanoid(),
|
||||
updatedAt: item.updatedAt,
|
||||
} as Song;
|
||||
};
|
||||
};
|
||||
|
||||
export const navidromeApi = {
|
||||
@@ -513,6 +488,7 @@ export const navidromeApi = {
|
||||
getGenreList,
|
||||
getPlaylistDetail,
|
||||
getPlaylistList,
|
||||
getPlaylistSongList,
|
||||
getSongDetail,
|
||||
getSongList,
|
||||
};
|
||||
|
||||
@@ -45,8 +45,6 @@ export type NDAlbum = {
|
||||
starred: boolean;
|
||||
starredAt: string;
|
||||
updatedAt: string;
|
||||
} & {
|
||||
songs?: NDSong[];
|
||||
};
|
||||
|
||||
export type NDSong = {
|
||||
@@ -119,7 +117,7 @@ export type NDAuthenticationResponse = NDAuthenticate;
|
||||
|
||||
export type NDAlbumArtistList = NDAlbumArtist[];
|
||||
|
||||
export type NDAlbumArtistDetail = NDAlbumArtist & { albums: NDAlbumListResponse };
|
||||
export type NDAlbumArtistDetail = NDAlbumArtist;
|
||||
|
||||
export type NDAlbumArtistDetailResponse = NDAlbumArtist;
|
||||
|
||||
@@ -129,7 +127,7 @@ export type NDGenreListResponse = NDGenre[];
|
||||
|
||||
export type NDAlbumDetailResponse = NDAlbum;
|
||||
|
||||
export type NDAlbumDetail = NDAlbum & { songs: NDSongListResponse };
|
||||
export type NDAlbumDetail = NDAlbum & { songs?: NDSongListResponse };
|
||||
|
||||
export type NDAlbumListResponse = NDAlbum[];
|
||||
|
||||
@@ -183,6 +181,7 @@ export enum NDAlbumListSort {
|
||||
DURATION = 'duration',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'playCount',
|
||||
PLAY_DATE = 'play_date',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'recently_added',
|
||||
@@ -193,6 +192,7 @@ export enum NDAlbumListSort {
|
||||
|
||||
export type NDAlbumListParams = {
|
||||
_sort?: NDAlbumListSort;
|
||||
album_id?: string;
|
||||
artist_id?: string;
|
||||
compilation?: boolean;
|
||||
genre_id?: string;
|
||||
@@ -213,7 +213,9 @@ export enum NDSongListSort {
|
||||
CHANNELS = 'channels',
|
||||
COMMENT = 'comment',
|
||||
DURATION = 'duration',
|
||||
FAVORITED = 'starred ASC, starredAt ASC',
|
||||
GENRE = 'genre',
|
||||
ID = 'id',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'playCount',
|
||||
PLAY_DATE = 'playDate',
|
||||
@@ -231,11 +233,11 @@ export type NDSongListParams = {
|
||||
|
||||
export enum NDAlbumArtistListSort {
|
||||
ALBUM_COUNT = 'albumCount',
|
||||
FAVORITED = 'starred ASC, starredAt ASC',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'playCount',
|
||||
RATING = 'rating',
|
||||
SONG_COUNT = 'songCount',
|
||||
STARRED = 'starred ASC, starredAt ASC',
|
||||
}
|
||||
|
||||
export type NDAlbumArtistListParams = {
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
import type { AlbumListQuery } from './types';
|
||||
import type { AlbumListQuery, SongListQuery } from './types';
|
||||
import type { AlbumDetailQuery } from './types';
|
||||
|
||||
export const queryKeys = {
|
||||
albums: {
|
||||
detail: (serverId: string, query: AlbumDetailQuery) => ['albums', serverId, query] as const,
|
||||
list: (serverId: string, params: AlbumListQuery) =>
|
||||
[serverId, 'albums', 'list', serverId, params] as const,
|
||||
detail: (serverId: string, query: AlbumDetailQuery) =>
|
||||
[serverId, 'albums', 'detail', query] as const,
|
||||
list: (serverId: string, query: AlbumListQuery) =>
|
||||
[serverId, 'albums', 'list', serverId, query] as const,
|
||||
root: ['albums'],
|
||||
serverRoot: (serverId: string) => [serverId, 'albums'],
|
||||
songs: (serverId: string, query: SongListQuery) =>
|
||||
[serverId, 'albums', 'songs', query] as const,
|
||||
},
|
||||
genres: {
|
||||
list: (serverId: string) => [serverId, 'genres', 'list'] as const,
|
||||
root: (serverId: string) => [serverId, 'genres'] as const,
|
||||
},
|
||||
ping: (url: string) => ['ping', url] as const,
|
||||
server: {
|
||||
root: (serverId: string) => [serverId] as const,
|
||||
},
|
||||
servers: {
|
||||
list: (params?: any) => ['servers', 'list', params] as const,
|
||||
map: () => ['servers', 'map'] as const,
|
||||
root: ['servers'],
|
||||
},
|
||||
tasks: {
|
||||
list: () => ['tasks', 'list'] as const,
|
||||
root: ['tasks'],
|
||||
},
|
||||
users: {
|
||||
detail: (userId: string) => ['users', userId] as const,
|
||||
list: (params?: any) => ['users', 'list', params] as const,
|
||||
root: ['users'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,7 +16,6 @@ import type {
|
||||
SSAlbumArtistDetail,
|
||||
SSAlbumArtistDetailResponse,
|
||||
SSFavoriteParams,
|
||||
SSFavorite,
|
||||
SSFavoriteResponse,
|
||||
SSRatingParams,
|
||||
SSRatingResponse,
|
||||
@@ -30,6 +29,7 @@ import type {
|
||||
AlbumListArgs,
|
||||
AuthenticationResponse,
|
||||
FavoriteArgs,
|
||||
FavoriteResponse,
|
||||
GenreListArgs,
|
||||
RatingArgs,
|
||||
} from '/@/api/types';
|
||||
@@ -222,7 +222,7 @@ const getAlbumList = async (args: AlbumListArgs): Promise<SSAlbumList> => {
|
||||
};
|
||||
};
|
||||
|
||||
const createFavorite = async (args: FavoriteArgs): Promise<SSFavorite> => {
|
||||
const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
||||
const { query, signal } = args;
|
||||
|
||||
const searchParams: SSFavoriteParams = {
|
||||
@@ -231,17 +231,19 @@ const createFavorite = async (args: FavoriteArgs): Promise<SSFavorite> => {
|
||||
id: query.type === 'song' ? query.id : undefined,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
await api
|
||||
.get('/rest/star.view', {
|
||||
searchParams,
|
||||
signal,
|
||||
})
|
||||
.json<SSFavoriteResponse>();
|
||||
|
||||
return data;
|
||||
return {
|
||||
id: query.id,
|
||||
};
|
||||
};
|
||||
|
||||
const deleteFavorite = async (args: FavoriteArgs) => {
|
||||
const deleteFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
||||
const { query, signal } = args;
|
||||
|
||||
const searchParams: SSFavoriteParams = {
|
||||
@@ -250,14 +252,16 @@ const deleteFavorite = async (args: FavoriteArgs) => {
|
||||
id: query.type === 'song' ? query.id : undefined,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
await api
|
||||
.get('/rest/unstar.view', {
|
||||
searchParams,
|
||||
signal,
|
||||
})
|
||||
.json<SSFavoriteResponse>();
|
||||
|
||||
return data;
|
||||
return {
|
||||
id: query.id,
|
||||
};
|
||||
};
|
||||
|
||||
const updateRating = async (args: RatingArgs) => {
|
||||
|
||||
@@ -1,29 +1,43 @@
|
||||
import type { ServerListItem } from '/@/store';
|
||||
import type { ServerType } from '/@//types';
|
||||
import type { JFAlbumListSort, JFSortOrder } from '/@/api/jellyfin.types';
|
||||
import type {
|
||||
JFAlbumArtistDetail,
|
||||
JFAlbumArtistList,
|
||||
JFAlbumDetail,
|
||||
JFAlbumList,
|
||||
JFArtistList,
|
||||
JFGenreList,
|
||||
JFMusicFolderList,
|
||||
JFPlaylistDetail,
|
||||
JFPlaylistList,
|
||||
JFSongList,
|
||||
} from '/@/api/jellyfin.types';
|
||||
import { JFAlbumArtistListSort, JFArtistListSort } from '/@/api/jellyfin.types';
|
||||
import { JFAlbumListSort, JFSongListSort } from '/@/api/jellyfin.types';
|
||||
import { JFSortOrder } from '/@/api/jellyfin.types';
|
||||
import type {
|
||||
NDAlbumArtistDetail,
|
||||
NDAlbumArtistList,
|
||||
NDAlbumArtistListSort,
|
||||
NDAlbumDetail,
|
||||
NDAlbumList,
|
||||
NDAlbumListSort,
|
||||
NDCreatePlaylist,
|
||||
NDDeletePlaylist,
|
||||
NDGenreList,
|
||||
NDOrder,
|
||||
NDPlaylistDetail,
|
||||
NDPlaylistList,
|
||||
NDPlaylistListSort,
|
||||
NDSongDetail,
|
||||
NDSongList,
|
||||
NDSongListSort,
|
||||
} from '/@/api/navidrome.types';
|
||||
import { NDPlaylistListSort } from '/@/api/navidrome.types';
|
||||
import { NDAlbumArtistListSort } from '/@/api/navidrome.types';
|
||||
import { NDSongListSort } from '/@/api/navidrome.types';
|
||||
import { NDAlbumListSort, NDSortOrder } from '/@/api/navidrome.types';
|
||||
import type {
|
||||
SSAlbumArtistDetail,
|
||||
SSAlbumArtistList,
|
||||
SSAlbumDetail,
|
||||
SSAlbumList,
|
||||
SSMusicFolderList,
|
||||
} from '/@/api/subsonic.types';
|
||||
|
||||
export enum SortOrder {
|
||||
@@ -31,6 +45,27 @@ export enum SortOrder {
|
||||
DESC = 'DESC',
|
||||
}
|
||||
|
||||
type SortOrderMap = {
|
||||
jellyfin: Record<SortOrder, JFSortOrder>;
|
||||
navidrome: Record<SortOrder, NDSortOrder>;
|
||||
subsonic: Record<SortOrder, undefined>;
|
||||
};
|
||||
|
||||
export const sortOrderMap: SortOrderMap = {
|
||||
jellyfin: {
|
||||
ASC: JFSortOrder.ASC,
|
||||
DESC: JFSortOrder.DESC,
|
||||
},
|
||||
navidrome: {
|
||||
ASC: NDSortOrder.ASC,
|
||||
DESC: NDSortOrder.DESC,
|
||||
},
|
||||
subsonic: {
|
||||
ASC: undefined,
|
||||
DESC: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export enum ExternalSource {
|
||||
LASTFM = 'LASTFM',
|
||||
MUSICBRAINZ = 'MUSICBRAINZ',
|
||||
@@ -88,6 +123,7 @@ export type Album = {
|
||||
artists: RelatedArtist[];
|
||||
backdropImageUrl: string | null;
|
||||
createdAt: string;
|
||||
duration: number | null;
|
||||
genres: Genre[];
|
||||
id: string;
|
||||
imagePlaceholderUrl: string | null;
|
||||
@@ -115,17 +151,19 @@ export type Song = {
|
||||
artists: RelatedArtist[];
|
||||
bitRate: number;
|
||||
compilation: boolean | null;
|
||||
container: string;
|
||||
container: string | null;
|
||||
createdAt: string;
|
||||
discNumber: number;
|
||||
duration: number;
|
||||
genres: Genre[];
|
||||
id: string;
|
||||
imageUrl: string;
|
||||
imageUrl: string | null;
|
||||
isFavorite: boolean;
|
||||
name: string;
|
||||
releaseDate: string;
|
||||
releaseYear: string;
|
||||
path: string | null;
|
||||
playCount: number;
|
||||
releaseDate: string | null;
|
||||
releaseYear: string | null;
|
||||
serverId: string;
|
||||
size: number;
|
||||
streamUrl: string;
|
||||
@@ -192,8 +230,8 @@ type BaseEndpointArgs = {
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
// Genre List ---------------------------------------------------------------------------
|
||||
export type RawGenreListResponse = NDGenreList | undefined;
|
||||
// Genre List
|
||||
export type RawGenreListResponse = NDGenreList | JFGenreList | undefined;
|
||||
|
||||
export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefined;
|
||||
|
||||
@@ -201,14 +239,35 @@ export type GenreListArgs = { query: GenreListQuery } & BaseEndpointArgs;
|
||||
|
||||
export type GenreListQuery = null;
|
||||
|
||||
// Album List ---------------------------------------------------------------------------
|
||||
export type RawAlbumListResponse = NDAlbumList | SSAlbumList | undefined;
|
||||
// Album List
|
||||
export type RawAlbumListResponse = NDAlbumList | SSAlbumList | JFAlbumList | undefined;
|
||||
|
||||
export type AlbumListResponse = BasePaginatedResponse<Album[]> | null | undefined;
|
||||
|
||||
export type AlbumListSort = NDAlbumListSort | JFAlbumListSort;
|
||||
export enum AlbumListSort {
|
||||
ALBUM_ARTIST = 'albumArtist',
|
||||
ARTIST = 'artist',
|
||||
COMMUNITY_RATING = 'communityRating',
|
||||
CRITIC_RATING = 'criticRating',
|
||||
DURATION = 'duration',
|
||||
FAVORITED = 'favorited',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'playCount',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'recentlyAdded',
|
||||
RECENTLY_PLAYED = 'recentlyPlayed',
|
||||
RELEASE_DATE = 'releaseDate',
|
||||
SONG_COUNT = 'songCount',
|
||||
YEAR = 'year',
|
||||
}
|
||||
|
||||
export type AlbumListQuery = {
|
||||
jfParams?: {
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
years?: string;
|
||||
};
|
||||
limit?: number;
|
||||
musicFolderId?: string;
|
||||
ndParams?: {
|
||||
@@ -227,8 +286,68 @@ export type AlbumListQuery = {
|
||||
|
||||
export type AlbumListArgs = { query: AlbumListQuery } & BaseEndpointArgs;
|
||||
|
||||
// Album Detail -------------------------------------------------------------------------
|
||||
export type RawAlbumDetailResponse = NDAlbumDetail | SSAlbumDetail | undefined;
|
||||
type AlbumListSortMap = {
|
||||
jellyfin: Record<AlbumListSort, JFAlbumListSort | undefined>;
|
||||
navidrome: Record<AlbumListSort, NDAlbumListSort | undefined>;
|
||||
subsonic: Record<AlbumListSort, undefined>;
|
||||
};
|
||||
|
||||
export const albumListSortMap: AlbumListSortMap = {
|
||||
jellyfin: {
|
||||
albumArtist: JFAlbumListSort.ALBUM_ARTIST,
|
||||
artist: undefined,
|
||||
communityRating: JFAlbumListSort.COMMUNITY_RATING,
|
||||
criticRating: JFAlbumListSort.CRITIC_RATING,
|
||||
duration: undefined,
|
||||
favorited: undefined,
|
||||
name: JFAlbumListSort.NAME,
|
||||
playCount: undefined,
|
||||
random: JFAlbumListSort.RANDOM,
|
||||
rating: undefined,
|
||||
recentlyAdded: JFAlbumListSort.RECENTLY_ADDED,
|
||||
recentlyPlayed: undefined,
|
||||
releaseDate: JFAlbumListSort.RELEASE_DATE,
|
||||
songCount: undefined,
|
||||
year: undefined,
|
||||
},
|
||||
navidrome: {
|
||||
albumArtist: NDAlbumListSort.ALBUM_ARTIST,
|
||||
artist: NDAlbumListSort.ARTIST,
|
||||
communityRating: undefined,
|
||||
criticRating: undefined,
|
||||
duration: NDAlbumListSort.DURATION,
|
||||
favorited: NDAlbumListSort.STARRED,
|
||||
name: NDAlbumListSort.NAME,
|
||||
playCount: NDAlbumListSort.PLAY_COUNT,
|
||||
random: NDAlbumListSort.RANDOM,
|
||||
rating: NDAlbumListSort.RATING,
|
||||
recentlyAdded: NDAlbumListSort.RECENTLY_ADDED,
|
||||
recentlyPlayed: NDAlbumListSort.PLAY_DATE,
|
||||
releaseDate: undefined,
|
||||
songCount: NDAlbumListSort.SONG_COUNT,
|
||||
year: NDAlbumListSort.YEAR,
|
||||
},
|
||||
subsonic: {
|
||||
albumArtist: undefined,
|
||||
artist: undefined,
|
||||
communityRating: undefined,
|
||||
criticRating: undefined,
|
||||
duration: undefined,
|
||||
favorited: undefined,
|
||||
name: undefined,
|
||||
playCount: undefined,
|
||||
random: undefined,
|
||||
rating: undefined,
|
||||
recentlyAdded: undefined,
|
||||
recentlyPlayed: undefined,
|
||||
releaseDate: undefined,
|
||||
songCount: undefined,
|
||||
year: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// Album Detail
|
||||
export type RawAlbumDetailResponse = NDAlbumDetail | SSAlbumDetail | JFAlbumDetail | undefined;
|
||||
|
||||
export type AlbumDetailResponse = Album | null | undefined;
|
||||
|
||||
@@ -236,14 +355,38 @@ export type AlbumDetailQuery = { id: string };
|
||||
|
||||
export type AlbumDetailArgs = { query: AlbumDetailQuery } & BaseEndpointArgs;
|
||||
|
||||
// Song List ----------------------------------------------------------------------------
|
||||
export type RawSongListResponse = NDSongList | undefined;
|
||||
// Song List
|
||||
export type RawSongListResponse = NDSongList | JFSongList | undefined;
|
||||
|
||||
export type SongListResponse = BasePaginatedResponse<Song[]>;
|
||||
|
||||
export type SongListSort = NDSongListSort;
|
||||
export enum SongListSort {
|
||||
ALBUM_ARTIST = 'albumArtist',
|
||||
ARTIST = 'artist',
|
||||
BPM = 'bpm',
|
||||
CHANNELS = 'channels',
|
||||
COMMENT = 'comment',
|
||||
DURATION = 'duration',
|
||||
FAVORITED = 'favorited',
|
||||
GENRE = 'genre',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'playCount',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'recentlyAdded',
|
||||
RECENTLY_PLAYED = 'recentlyPlayed',
|
||||
RELEASE_DATE = 'releaseDate',
|
||||
YEAR = 'year',
|
||||
}
|
||||
|
||||
export type SongListQuery = {
|
||||
jfParams?: {
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
includeItemTypes: 'Audio';
|
||||
sortBy?: JFSongListSort;
|
||||
years?: string;
|
||||
};
|
||||
limit?: number;
|
||||
musicFolderId?: string;
|
||||
ndParams?: {
|
||||
@@ -262,7 +405,70 @@ export type SongListQuery = {
|
||||
|
||||
export type SongListArgs = { query: SongListQuery } & BaseEndpointArgs;
|
||||
|
||||
// Song Detail -------------------------------------------------------------------------
|
||||
type SongListSortMap = {
|
||||
jellyfin: Record<SongListSort, JFSongListSort | undefined>;
|
||||
navidrome: Record<SongListSort, NDSongListSort | undefined>;
|
||||
subsonic: Record<SongListSort, undefined>;
|
||||
};
|
||||
|
||||
export const songListSortMap: SongListSortMap = {
|
||||
jellyfin: {
|
||||
albumArtist: JFSongListSort.ALBUM_ARTIST,
|
||||
artist: JFSongListSort.ARTIST,
|
||||
bpm: undefined,
|
||||
channels: undefined,
|
||||
comment: undefined,
|
||||
duration: JFSongListSort.DURATION,
|
||||
favorited: undefined,
|
||||
genre: undefined,
|
||||
name: JFSongListSort.NAME,
|
||||
playCount: JFSongListSort.PLAY_COUNT,
|
||||
random: JFSongListSort.RANDOM,
|
||||
rating: undefined,
|
||||
recentlyAdded: JFSongListSort.RECENTLY_ADDED,
|
||||
recentlyPlayed: JFSongListSort.RECENTLY_PLAYED,
|
||||
releaseDate: JFSongListSort.RELEASE_DATE,
|
||||
year: undefined,
|
||||
},
|
||||
navidrome: {
|
||||
albumArtist: NDSongListSort.ALBUM_ARTIST,
|
||||
artist: NDSongListSort.ARTIST,
|
||||
bpm: NDSongListSort.BPM,
|
||||
channels: NDSongListSort.CHANNELS,
|
||||
comment: NDSongListSort.COMMENT,
|
||||
duration: NDSongListSort.DURATION,
|
||||
favorited: NDSongListSort.FAVORITED,
|
||||
genre: NDSongListSort.GENRE,
|
||||
name: NDSongListSort.NAME,
|
||||
playCount: NDSongListSort.PLAY_COUNT,
|
||||
random: undefined,
|
||||
rating: NDSongListSort.RATING,
|
||||
recentlyAdded: NDSongListSort.PLAY_DATE,
|
||||
recentlyPlayed: NDSongListSort.PLAY_DATE,
|
||||
releaseDate: undefined,
|
||||
year: NDSongListSort.YEAR,
|
||||
},
|
||||
subsonic: {
|
||||
albumArtist: undefined,
|
||||
artist: undefined,
|
||||
bpm: undefined,
|
||||
channels: undefined,
|
||||
comment: undefined,
|
||||
duration: undefined,
|
||||
favorited: undefined,
|
||||
genre: undefined,
|
||||
name: undefined,
|
||||
playCount: undefined,
|
||||
random: undefined,
|
||||
rating: undefined,
|
||||
recentlyAdded: undefined,
|
||||
recentlyPlayed: undefined,
|
||||
releaseDate: undefined,
|
||||
year: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// Song Detail
|
||||
export type RawSongDetailResponse = NDSongDetail | undefined;
|
||||
|
||||
export type SongDetailResponse = Song | null | undefined;
|
||||
@@ -271,12 +477,28 @@ export type SongDetailQuery = { id: string };
|
||||
|
||||
export type SongDetailArgs = { query: SongDetailQuery } & BaseEndpointArgs;
|
||||
|
||||
// Album Artist List -------------------------------------------------------------------
|
||||
export type RawAlbumArtistListResponse = NDAlbumArtistList | SSAlbumArtistList | undefined;
|
||||
// Album Artist List
|
||||
export type RawAlbumArtistListResponse =
|
||||
| NDAlbumArtistList
|
||||
| SSAlbumArtistList
|
||||
| JFAlbumArtistList
|
||||
| undefined;
|
||||
|
||||
export type AlbumArtistListResponse = BasePaginatedResponse<AlbumArtist[]>;
|
||||
|
||||
export type AlbumArtistListSort = NDAlbumArtistListSort;
|
||||
export enum AlbumArtistListSort {
|
||||
ALBUM = 'album',
|
||||
ALBUM_COUNT = 'albumCount',
|
||||
DURATION = 'duration',
|
||||
FAVORITED = 'favorited',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'playCount',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'recentlyAdded',
|
||||
RELEASE_DATE = 'releaseDate',
|
||||
SONG_COUNT = 'songCount',
|
||||
}
|
||||
|
||||
export type AlbumArtistListQuery = {
|
||||
limit?: number;
|
||||
@@ -293,8 +515,60 @@ export type AlbumArtistListQuery = {
|
||||
|
||||
export type AlbumArtistListArgs = { query: AlbumArtistListQuery } & BaseEndpointArgs;
|
||||
|
||||
// Album Artist Detail -----------------------------------------------------------------
|
||||
export type RawAlbumArtistDetailResponse = NDAlbumArtistDetail | SSAlbumArtistDetail | undefined;
|
||||
type AlbumArtistListSortMap = {
|
||||
jellyfin: Record<AlbumArtistListSort, JFAlbumArtistListSort | undefined>;
|
||||
navidrome: Record<AlbumArtistListSort, NDAlbumArtistListSort | undefined>;
|
||||
subsonic: Record<AlbumArtistListSort, undefined>;
|
||||
};
|
||||
|
||||
export const albumArtistListSortMap: AlbumArtistListSortMap = {
|
||||
jellyfin: {
|
||||
album: JFAlbumArtistListSort.ALBUM,
|
||||
albumCount: undefined,
|
||||
duration: JFAlbumArtistListSort.DURATION,
|
||||
favorited: undefined,
|
||||
name: JFAlbumArtistListSort.NAME,
|
||||
playCount: undefined,
|
||||
random: JFAlbumArtistListSort.RANDOM,
|
||||
rating: undefined,
|
||||
recentlyAdded: JFAlbumArtistListSort.RECENTLY_ADDED,
|
||||
releaseDate: undefined,
|
||||
songCount: undefined,
|
||||
},
|
||||
navidrome: {
|
||||
album: undefined,
|
||||
albumCount: NDAlbumArtistListSort.ALBUM_COUNT,
|
||||
duration: undefined,
|
||||
favorited: NDAlbumArtistListSort.FAVORITED,
|
||||
name: NDAlbumArtistListSort.NAME,
|
||||
playCount: NDAlbumArtistListSort.PLAY_COUNT,
|
||||
random: undefined,
|
||||
rating: NDAlbumArtistListSort.RATING,
|
||||
recentlyAdded: undefined,
|
||||
releaseDate: undefined,
|
||||
songCount: NDAlbumArtistListSort.SONG_COUNT,
|
||||
},
|
||||
subsonic: {
|
||||
album: undefined,
|
||||
albumCount: undefined,
|
||||
duration: undefined,
|
||||
favorited: undefined,
|
||||
name: undefined,
|
||||
playCount: undefined,
|
||||
random: undefined,
|
||||
rating: undefined,
|
||||
recentlyAdded: undefined,
|
||||
releaseDate: undefined,
|
||||
songCount: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// Album Artist Detail
|
||||
export type RawAlbumArtistDetailResponse =
|
||||
| NDAlbumArtistDetail
|
||||
| SSAlbumArtistDetail
|
||||
| JFAlbumArtistDetail
|
||||
| undefined;
|
||||
|
||||
export type AlbumArtistDetailResponse = BasePaginatedResponse<AlbumArtist[]>;
|
||||
|
||||
@@ -302,20 +576,100 @@ export type AlbumArtistDetailQuery = { id: string };
|
||||
|
||||
export type AlbumArtistDetailArgs = { query: AlbumArtistDetailQuery } & BaseEndpointArgs;
|
||||
|
||||
// Artist List -------------------------------------------------------------------------
|
||||
// Artist List
|
||||
export type RawArtistListResponse = JFArtistList | undefined;
|
||||
|
||||
// Artist Detail -----------------------------------------------------------------------
|
||||
export type ArtistListResponse = BasePaginatedResponse<Artist[]>;
|
||||
|
||||
// Favorite ----------------------------------------------------------------------------
|
||||
export type RawFavoriteResponse = null | undefined;
|
||||
export enum ArtistListSort {
|
||||
ALBUM = 'album',
|
||||
ALBUM_COUNT = 'albumCount',
|
||||
DURATION = 'duration',
|
||||
FAVORITED = 'favorited',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'playCount',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'recentlyAdded',
|
||||
RELEASE_DATE = 'releaseDate',
|
||||
SONG_COUNT = 'songCount',
|
||||
}
|
||||
|
||||
export type FavoriteResponse = null;
|
||||
export type ArtistListQuery = {
|
||||
limit?: number;
|
||||
musicFolderId?: string;
|
||||
ndParams?: {
|
||||
genre_id?: string;
|
||||
name?: string;
|
||||
starred?: boolean;
|
||||
};
|
||||
sortBy: ArtistListSort;
|
||||
sortOrder: SortOrder;
|
||||
startIndex: number;
|
||||
};
|
||||
|
||||
export type ArtistListArgs = { query: ArtistListQuery } & BaseEndpointArgs;
|
||||
|
||||
type ArtistListSortMap = {
|
||||
jellyfin: Record<ArtistListSort, JFArtistListSort | undefined>;
|
||||
navidrome: Record<ArtistListSort, undefined>;
|
||||
subsonic: Record<ArtistListSort, undefined>;
|
||||
};
|
||||
|
||||
export const artistListSortMap: ArtistListSortMap = {
|
||||
jellyfin: {
|
||||
album: JFArtistListSort.ALBUM,
|
||||
albumCount: undefined,
|
||||
duration: JFArtistListSort.DURATION,
|
||||
favorited: undefined,
|
||||
name: JFArtistListSort.NAME,
|
||||
playCount: undefined,
|
||||
random: JFArtistListSort.RANDOM,
|
||||
rating: undefined,
|
||||
recentlyAdded: JFArtistListSort.RECENTLY_ADDED,
|
||||
releaseDate: undefined,
|
||||
songCount: undefined,
|
||||
},
|
||||
navidrome: {
|
||||
album: undefined,
|
||||
albumCount: undefined,
|
||||
duration: undefined,
|
||||
favorited: undefined,
|
||||
name: undefined,
|
||||
playCount: undefined,
|
||||
random: undefined,
|
||||
rating: undefined,
|
||||
recentlyAdded: undefined,
|
||||
releaseDate: undefined,
|
||||
songCount: undefined,
|
||||
},
|
||||
subsonic: {
|
||||
album: undefined,
|
||||
albumCount: undefined,
|
||||
duration: undefined,
|
||||
favorited: undefined,
|
||||
name: undefined,
|
||||
playCount: undefined,
|
||||
random: undefined,
|
||||
rating: undefined,
|
||||
recentlyAdded: undefined,
|
||||
releaseDate: undefined,
|
||||
songCount: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// Artist Detail
|
||||
|
||||
// Favorite
|
||||
export type RawFavoriteResponse = FavoriteResponse | undefined;
|
||||
|
||||
export type FavoriteResponse = { id: string };
|
||||
|
||||
export type FavoriteQuery = { id: string; type?: 'song' | 'album' | 'albumArtist' };
|
||||
|
||||
export type FavoriteArgs = { query: FavoriteQuery } & BaseEndpointArgs;
|
||||
|
||||
// Rating -------------------------------------------------------------------------------
|
||||
// Rating
|
||||
export type RawRatingResponse = null | undefined;
|
||||
|
||||
export type RatingResponse = null;
|
||||
@@ -324,16 +678,16 @@ export type RatingQuery = { id: string; rating: number };
|
||||
|
||||
export type RatingArgs = { query: RatingQuery } & BaseEndpointArgs;
|
||||
|
||||
// Create Playlist -----------------------------------------------------------------------
|
||||
export type RawCreatePlaylistResponse = NDCreatePlaylist | undefined;
|
||||
// Create Playlist
|
||||
export type RawCreatePlaylistResponse = CreatePlaylistResponse | undefined;
|
||||
|
||||
export type CreatePlaylistResponse = null;
|
||||
export type CreatePlaylistResponse = { id: string; name: string };
|
||||
|
||||
export type CreatePlaylistQuery = { comment?: string; name: string; public?: boolean };
|
||||
|
||||
export type CreatePlaylistArgs = { query: CreatePlaylistQuery } & BaseEndpointArgs;
|
||||
|
||||
// Delete Playlist -----------------------------------------------------------------------
|
||||
// Delete Playlist
|
||||
export type RawDeletePlaylistResponse = NDDeletePlaylist | undefined;
|
||||
|
||||
export type DeletePlaylistResponse = null;
|
||||
@@ -342,8 +696,8 @@ export type DeletePlaylistQuery = { id: string };
|
||||
|
||||
export type DeletePlaylistArgs = { query: DeletePlaylistQuery } & BaseEndpointArgs;
|
||||
|
||||
// Playlist List -------------------------------------------------------------------------
|
||||
export type RawPlaylistListResponse = NDPlaylistList | undefined;
|
||||
// Playlist List
|
||||
export type RawPlaylistListResponse = NDPlaylistList | JFPlaylistList | undefined;
|
||||
|
||||
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]>;
|
||||
|
||||
@@ -359,8 +713,41 @@ export type PlaylistListQuery = {
|
||||
|
||||
export type PlaylistListArgs = { query: PlaylistListQuery } & BaseEndpointArgs;
|
||||
|
||||
// Playlist Detail -----------------------------------------------------------------------
|
||||
export type RawPlaylistDetailResponse = NDPlaylistDetail | undefined;
|
||||
type PlaylistListSortMap = {
|
||||
jellyfin: Record<PlaylistListSort, undefined>;
|
||||
navidrome: Record<PlaylistListSort, NDPlaylistListSort | undefined>;
|
||||
subsonic: Record<PlaylistListSort, undefined>;
|
||||
};
|
||||
|
||||
export const playlistListSortMap: PlaylistListSortMap = {
|
||||
jellyfin: {
|
||||
duration: undefined,
|
||||
name: undefined,
|
||||
owner: undefined,
|
||||
public: undefined,
|
||||
songCount: undefined,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
navidrome: {
|
||||
duration: NDPlaylistListSort.DURATION,
|
||||
name: NDPlaylistListSort.NAME,
|
||||
owner: NDPlaylistListSort.OWNER,
|
||||
public: NDPlaylistListSort.PUBLIC,
|
||||
songCount: NDPlaylistListSort.SONG_COUNT,
|
||||
updatedAt: NDPlaylistListSort.UPDATED_AT,
|
||||
},
|
||||
subsonic: {
|
||||
duration: undefined,
|
||||
name: undefined,
|
||||
owner: undefined,
|
||||
public: undefined,
|
||||
songCount: undefined,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// Playlist Detail
|
||||
export type RawPlaylistDetailResponse = NDPlaylistDetail | JFPlaylistDetail | undefined;
|
||||
|
||||
export type PlaylistDetailResponse = BasePaginatedResponse<Playlist[]>;
|
||||
|
||||
@@ -369,3 +756,38 @@ export type PlaylistDetailQuery = {
|
||||
};
|
||||
|
||||
export type PlaylistDetailArgs = { query: PlaylistDetailQuery } & BaseEndpointArgs;
|
||||
|
||||
// Playlist Songs
|
||||
export type RawPlaylistSongListResponse = JFSongList | undefined;
|
||||
|
||||
export type PlaylistSongListResponse = BasePaginatedResponse<Song[]>;
|
||||
|
||||
export type PlaylistSongListQuery = {
|
||||
id: string;
|
||||
limit?: number;
|
||||
sortBy?: SongListSort;
|
||||
sortOrder?: SortOrder;
|
||||
startIndex: number;
|
||||
};
|
||||
|
||||
export type PlaylistSongListArgs = { query: PlaylistSongListQuery } & BaseEndpointArgs;
|
||||
|
||||
// Music Folder List
|
||||
export type RawMusicFolderListResponse = SSMusicFolderList | JFMusicFolderList | undefined;
|
||||
|
||||
export type MusicFolderListResponse = BasePaginatedResponse<Playlist[]>;
|
||||
|
||||
export type MusicFolderListQuery = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type MusicFolderListArgs = { query: MusicFolderListQuery } & BaseEndpointArgs;
|
||||
|
||||
// Create Favorite
|
||||
export type RawCreateFavoriteResponse = CreateFavoriteResponse | undefined;
|
||||
|
||||
export type CreateFavoriteResponse = { id: string };
|
||||
|
||||
export type CreateFavoriteQuery = { comment?: string; name: string; public?: boolean };
|
||||
|
||||
export type CreateFavoriteArgs = { query: CreateFavoriteQuery } & BaseEndpointArgs;
|
||||
|
||||
Reference in New Issue
Block a user