mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
Add additional controller endpoints
This commit is contained in:
@@ -28,8 +28,14 @@ import type {
|
|||||||
RawPlaylistDetailResponse,
|
RawPlaylistDetailResponse,
|
||||||
PlaylistListArgs,
|
PlaylistListArgs,
|
||||||
RawPlaylistListResponse,
|
RawPlaylistListResponse,
|
||||||
|
MusicFolderListArgs,
|
||||||
|
RawMusicFolderListResponse,
|
||||||
|
PlaylistSongListArgs,
|
||||||
|
ArtistListArgs,
|
||||||
|
RawArtistListResponse,
|
||||||
} from '/@/api/types';
|
} from '/@/api/types';
|
||||||
import { subsonicApi } from '/@/api/subsonic.api';
|
import { subsonicApi } from '/@/api/subsonic.api';
|
||||||
|
import { jellyfinApi } from '/@/api/jellyfin.api';
|
||||||
|
|
||||||
export type ControllerEndpoint = Partial<{
|
export type ControllerEndpoint = Partial<{
|
||||||
clearPlaylist: () => void;
|
clearPlaylist: () => void;
|
||||||
@@ -42,15 +48,16 @@ export type ControllerEndpoint = Partial<{
|
|||||||
getAlbumDetail: (args: AlbumDetailArgs) => Promise<RawAlbumDetailResponse>;
|
getAlbumDetail: (args: AlbumDetailArgs) => Promise<RawAlbumDetailResponse>;
|
||||||
getAlbumList: (args: AlbumListArgs) => Promise<RawAlbumListResponse>;
|
getAlbumList: (args: AlbumListArgs) => Promise<RawAlbumListResponse>;
|
||||||
getArtistDetail: () => void;
|
getArtistDetail: () => void;
|
||||||
getArtistList: () => void;
|
getArtistList: (args: ArtistListArgs) => Promise<RawArtistListResponse>;
|
||||||
getFavoritesList: () => void;
|
getFavoritesList: () => void;
|
||||||
getFolderItemList: () => void;
|
getFolderItemList: () => void;
|
||||||
getFolderList: () => void;
|
getFolderList: () => void;
|
||||||
getFolderSongs: () => void;
|
getFolderSongs: () => void;
|
||||||
getGenreList: (args: GenreListArgs) => Promise<RawGenreListResponse>;
|
getGenreList: (args: GenreListArgs) => Promise<RawGenreListResponse>;
|
||||||
getMusicFolderList: () => void;
|
getMusicFolderList: (args: MusicFolderListArgs) => Promise<RawMusicFolderListResponse>;
|
||||||
getPlaylistDetail: (args: PlaylistDetailArgs) => Promise<RawPlaylistDetailResponse>;
|
getPlaylistDetail: (args: PlaylistDetailArgs) => Promise<RawPlaylistDetailResponse>;
|
||||||
getPlaylistList: (args: PlaylistListArgs) => Promise<RawPlaylistListResponse>;
|
getPlaylistList: (args: PlaylistListArgs) => Promise<RawPlaylistListResponse>;
|
||||||
|
getPlaylistSongList: (args: PlaylistSongListArgs) => Promise<RawSongListResponse>;
|
||||||
getSongDetail: (args: SongDetailArgs) => Promise<RawSongDetailResponse>;
|
getSongDetail: (args: SongDetailArgs) => Promise<RawSongDetailResponse>;
|
||||||
getSongList: (args: SongListArgs) => Promise<RawSongListResponse>;
|
getSongList: (args: SongListArgs) => Promise<RawSongListResponse>;
|
||||||
updatePlaylist: () => void;
|
updatePlaylist: () => void;
|
||||||
@@ -66,25 +73,27 @@ type ApiController = {
|
|||||||
const endpoints: ApiController = {
|
const endpoints: ApiController = {
|
||||||
jellyfin: {
|
jellyfin: {
|
||||||
clearPlaylist: undefined,
|
clearPlaylist: undefined,
|
||||||
createFavorite: undefined,
|
createFavorite: jellyfinApi.createFavorite,
|
||||||
createPlaylist: undefined,
|
createPlaylist: jellyfinApi.createPlaylist,
|
||||||
deleteFavorite: undefined,
|
deleteFavorite: jellyfinApi.deleteFavorite,
|
||||||
deletePlaylist: undefined,
|
deletePlaylist: jellyfinApi.deletePlaylist,
|
||||||
getAlbumArtistDetail: undefined,
|
getAlbumArtistDetail: jellyfinApi.getAlbumArtistDetail,
|
||||||
getAlbumArtistList: undefined,
|
getAlbumArtistList: jellyfinApi.getAlbumArtistList,
|
||||||
getAlbumDetail: undefined,
|
getAlbumDetail: jellyfinApi.getAlbumDetail,
|
||||||
getAlbumList: undefined,
|
getAlbumList: jellyfinApi.getAlbumList,
|
||||||
getArtistDetail: undefined,
|
getArtistDetail: undefined,
|
||||||
getArtistList: undefined,
|
getArtistList: jellyfinApi.getArtistList,
|
||||||
getFavoritesList: undefined,
|
getFavoritesList: undefined,
|
||||||
getFolderItemList: undefined,
|
getFolderItemList: undefined,
|
||||||
getFolderList: undefined,
|
getFolderList: undefined,
|
||||||
getFolderSongs: undefined,
|
getFolderSongs: undefined,
|
||||||
getGenreList: undefined,
|
getGenreList: jellyfinApi.getGenreList,
|
||||||
getMusicFolderList: undefined,
|
getMusicFolderList: jellyfinApi.getMusicFolderList,
|
||||||
getPlaylistDetail: undefined,
|
getPlaylistDetail: jellyfinApi.getPlaylistDetail,
|
||||||
getPlaylistList: undefined,
|
getPlaylistList: jellyfinApi.getPlaylistList,
|
||||||
getSongList: undefined,
|
getPlaylistSongList: jellyfinApi.getPlaylistSongList,
|
||||||
|
getSongDetail: undefined,
|
||||||
|
getSongList: jellyfinApi.getSongList,
|
||||||
updatePlaylist: undefined,
|
updatePlaylist: undefined,
|
||||||
updateRating: undefined,
|
updateRating: undefined,
|
||||||
},
|
},
|
||||||
@@ -108,6 +117,8 @@ const endpoints: ApiController = {
|
|||||||
getMusicFolderList: undefined,
|
getMusicFolderList: undefined,
|
||||||
getPlaylistDetail: navidromeApi.getPlaylistDetail,
|
getPlaylistDetail: navidromeApi.getPlaylistDetail,
|
||||||
getPlaylistList: navidromeApi.getPlaylistList,
|
getPlaylistList: navidromeApi.getPlaylistList,
|
||||||
|
getPlaylistSongList: navidromeApi.getPlaylistSongList,
|
||||||
|
getSongDetail: navidromeApi.getSongDetail,
|
||||||
getSongList: navidromeApi.getSongList,
|
getSongList: navidromeApi.getSongList,
|
||||||
updatePlaylist: undefined,
|
updatePlaylist: undefined,
|
||||||
updateRating: subsonicApi.updateRating,
|
updateRating: subsonicApi.updateRating,
|
||||||
@@ -132,6 +143,7 @@ const endpoints: ApiController = {
|
|||||||
getMusicFolderList: undefined,
|
getMusicFolderList: undefined,
|
||||||
getPlaylistDetail: undefined,
|
getPlaylistDetail: undefined,
|
||||||
getPlaylistList: undefined,
|
getPlaylistList: undefined,
|
||||||
|
getSongDetail: undefined,
|
||||||
getSongList: undefined,
|
getSongList: undefined,
|
||||||
updatePlaylist: undefined,
|
updatePlaylist: undefined,
|
||||||
updateRating: 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;
|
StartIndex: number;
|
||||||
TotalRecordCount: number;
|
TotalRecordCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface JFMusicFoldersResponse extends JFBaseResponse {
|
export interface JFMusicFolderListResponse extends JFBasePaginatedResponse {
|
||||||
Items: JFMusicFolder[];
|
Items: JFMusicFolder[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JFGenreResponse extends JFBaseResponse {
|
export type JFMusicFolderList = {
|
||||||
|
items: JFMusicFolder[];
|
||||||
|
startIndex: number;
|
||||||
|
totalRecordCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface JFGenreListResponse extends JFBasePaginatedResponse {
|
||||||
Items: JFGenre[];
|
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[];
|
Items: JFAlbumArtist[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JFArtistsResponse extends JFBaseResponse {
|
export type JFAlbumArtistList = JFAlbumArtistListResponse;
|
||||||
|
|
||||||
|
export interface JFArtistListResponse extends JFBasePaginatedResponse {
|
||||||
Items: JFAlbumArtist[];
|
Items: JFAlbumArtist[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JFAlbumsResponse extends JFBaseResponse {
|
export type JFArtistList = JFArtistListResponse;
|
||||||
|
|
||||||
|
export interface JFAlbumListResponse extends JFBasePaginatedResponse {
|
||||||
Items: JFAlbum[];
|
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[];
|
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 = {
|
export type JFRequestParams = {
|
||||||
albumArtistIds?: string;
|
albumArtistIds?: string;
|
||||||
artistIds?: string;
|
artistIds?: string;
|
||||||
@@ -114,10 +172,13 @@ export type JFArtist = {
|
|||||||
export type JFAlbum = {
|
export type JFAlbum = {
|
||||||
AlbumArtist: string;
|
AlbumArtist: string;
|
||||||
AlbumArtists: JFGenericItem[];
|
AlbumArtists: JFGenericItem[];
|
||||||
|
AlbumPrimaryImageTag: string;
|
||||||
ArtistItems: JFGenericItem[];
|
ArtistItems: JFGenericItem[];
|
||||||
Artists: string[];
|
Artists: string[];
|
||||||
ChannelId: null;
|
ChannelId: null;
|
||||||
|
ChildCount?: number;
|
||||||
DateCreated: string;
|
DateCreated: string;
|
||||||
|
DateLastMediaAdded?: string;
|
||||||
ExternalUrls: ExternalURL[];
|
ExternalUrls: ExternalURL[];
|
||||||
GenreItems: JFGenericItem[];
|
GenreItems: JFGenericItem[];
|
||||||
Genres: string[];
|
Genres: string[];
|
||||||
@@ -134,6 +195,7 @@ export type JFAlbum = {
|
|||||||
RunTimeTicks: number;
|
RunTimeTicks: number;
|
||||||
ServerId: string;
|
ServerId: string;
|
||||||
Type: string;
|
Type: string;
|
||||||
|
UserData?: UserData;
|
||||||
} & {
|
} & {
|
||||||
songs?: JFSong[];
|
songs?: JFSong[];
|
||||||
};
|
};
|
||||||
@@ -168,6 +230,7 @@ export type JFSong = {
|
|||||||
ServerId: string;
|
ServerId: string;
|
||||||
SortName: string;
|
SortName: string;
|
||||||
Type: string;
|
Type: string;
|
||||||
|
UserData?: UserData;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ImageBlurHashes = {
|
type ImageBlurHashes = {
|
||||||
@@ -405,6 +468,21 @@ type Policy = {
|
|||||||
SyncPlayAccess: string;
|
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 {
|
export enum JFSortOrder {
|
||||||
ASC = 'Ascending',
|
ASC = 'Ascending',
|
||||||
DESC = 'Descending',
|
DESC = 'Descending',
|
||||||
@@ -412,22 +490,81 @@ export enum JFSortOrder {
|
|||||||
|
|
||||||
export enum JFAlbumListSort {
|
export enum JFAlbumListSort {
|
||||||
ALBUM_ARTIST = 'AlbumArtist,SortName',
|
ALBUM_ARTIST = 'AlbumArtist,SortName',
|
||||||
|
COMMUNITY_RATING = 'CommunityRating,SortName',
|
||||||
CRITIC_RATING = 'CriticRating,SortName',
|
CRITIC_RATING = 'CriticRating,SortName',
|
||||||
NAME = 'SortName',
|
NAME = 'SortName',
|
||||||
RANDOM = 'Random,SortName',
|
RANDOM = 'Random,SortName',
|
||||||
RATING = 'CommunityRating,SortName',
|
|
||||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||||
RELEASE_DATE = 'ProductionYear,PremiereDate,SortName',
|
RELEASE_DATE = 'ProductionYear,PremiereDate,SortName',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type JFAlbumListParams = {
|
export type JFAlbumListParams = {
|
||||||
enableImageTypes: JFImageType[];
|
filters?: string;
|
||||||
imageTypeLimit: number;
|
genres?: string;
|
||||||
includeItemTypes: 'MusicAlbum';
|
includeItemTypes: 'MusicAlbum';
|
||||||
limit?: number;
|
sortBy?: JFAlbumListSort;
|
||||||
parentId: string;
|
years?: string;
|
||||||
recursive: boolean;
|
} & JFBaseParams &
|
||||||
sortBy: JFAlbumListSort;
|
JFPaginationParams;
|
||||||
sortOrder: JFSortOrder;
|
|
||||||
startIndex: number;
|
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,
|
NDGenreListResponse,
|
||||||
NDArtistListResponse,
|
NDArtistListResponse,
|
||||||
NDAlbumDetail,
|
NDAlbumDetail,
|
||||||
NDSongListResponse,
|
|
||||||
NDAlbumListParams,
|
NDAlbumListParams,
|
||||||
NDAlbumList,
|
NDAlbumList,
|
||||||
NDSongDetailResponse,
|
NDSongDetailResponse,
|
||||||
@@ -12,18 +11,14 @@ import type {
|
|||||||
NDSong,
|
NDSong,
|
||||||
NDAuthenticationResponse,
|
NDAuthenticationResponse,
|
||||||
NDAlbumDetailResponse,
|
NDAlbumDetailResponse,
|
||||||
NDSongList,
|
|
||||||
NDSongDetail,
|
NDSongDetail,
|
||||||
NDGenreList,
|
NDGenreList,
|
||||||
NDAlbumArtistListParams,
|
NDAlbumArtistListParams,
|
||||||
NDAlbumArtistListSort,
|
|
||||||
NDAlbumArtistDetail,
|
NDAlbumArtistDetail,
|
||||||
NDAlbumListResponse,
|
NDAlbumListResponse,
|
||||||
NDAlbumArtistDetailResponse,
|
NDAlbumArtistDetailResponse,
|
||||||
NDAlbumArtistList,
|
NDAlbumArtistList,
|
||||||
NDSongListParams,
|
NDSongListParams,
|
||||||
NDSongListSort,
|
|
||||||
NDCreatePlaylist,
|
|
||||||
NDCreatePlaylistParams,
|
NDCreatePlaylistParams,
|
||||||
NDCreatePlaylistResponse,
|
NDCreatePlaylistResponse,
|
||||||
NDDeletePlaylist,
|
NDDeletePlaylist,
|
||||||
@@ -31,11 +26,13 @@ import type {
|
|||||||
NDPlaylistListParams,
|
NDPlaylistListParams,
|
||||||
NDPlaylistDetail,
|
NDPlaylistDetail,
|
||||||
NDPlaylistList,
|
NDPlaylistList,
|
||||||
NDPlaylistListSort,
|
|
||||||
NDPlaylistListResponse,
|
NDPlaylistListResponse,
|
||||||
NDPlaylistDetailResponse,
|
NDPlaylistDetailResponse,
|
||||||
|
NDSongList,
|
||||||
|
NDSongListResponse,
|
||||||
} from '/@/api/navidrome.types';
|
} 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 { NDSortOrder } from '/@/api/navidrome.types';
|
||||||
import type {
|
import type {
|
||||||
Album,
|
Album,
|
||||||
@@ -52,12 +49,18 @@ import type {
|
|||||||
DeletePlaylistArgs,
|
DeletePlaylistArgs,
|
||||||
PlaylistListArgs,
|
PlaylistListArgs,
|
||||||
PlaylistDetailArgs,
|
PlaylistDetailArgs,
|
||||||
|
CreatePlaylistResponse,
|
||||||
|
PlaylistSongListArgs,
|
||||||
} from '/@/api/types';
|
} 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 { toast } from '/@/components';
|
||||||
import type { ServerListItem } from '/@/store';
|
import type { ServerListItem } from '/@/store';
|
||||||
import { useAuthStore } from '/@/store';
|
import { useAuthStore } from '/@/store';
|
||||||
import { ServerType } from '/@/types';
|
import { ServerType } from '/@/types';
|
||||||
|
import { parseSearchParams } from '/@/utils';
|
||||||
|
|
||||||
const api = ky.create({
|
const api = ky.create({
|
||||||
hooks: {
|
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 (
|
const authenticate = async (
|
||||||
url: string,
|
url: string,
|
||||||
body: { password: string; username: string },
|
body: { password: string; username: string },
|
||||||
@@ -193,22 +146,7 @@ const getAlbumArtistDetail = async (args: AlbumArtistDetailArgs): Promise<NDAlbu
|
|||||||
})
|
})
|
||||||
.json<NDAlbumArtistDetailResponse>();
|
.json<NDAlbumArtistDetailResponse>();
|
||||||
|
|
||||||
const albumsData = await api
|
return { ...data };
|
||||||
.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 };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<NDAlbumArtistList> => {
|
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<NDAlbumArtistList> => {
|
||||||
@@ -216,8 +154,8 @@ const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<NDAlbumArt
|
|||||||
|
|
||||||
const searchParams: NDAlbumArtistListParams = {
|
const searchParams: NDAlbumArtistListParams = {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
_sort: query.sortBy as NDAlbumArtistListSort,
|
_sort: albumArtistListSortMap.navidrome[query.sortBy],
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
...query.ndParams,
|
...query.ndParams,
|
||||||
};
|
};
|
||||||
@@ -267,8 +205,8 @@ const getAlbumList = async (args: AlbumListArgs): Promise<NDAlbumList> => {
|
|||||||
|
|
||||||
const searchParams: NDAlbumListParams = {
|
const searchParams: NDAlbumListParams = {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
_sort: query.sortBy as NDAlbumListSort,
|
_sort: albumListSortMap.navidrome[query.sortBy],
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
...query.ndParams,
|
...query.ndParams,
|
||||||
};
|
};
|
||||||
@@ -295,8 +233,8 @@ const getSongList = async (args: SongListArgs): Promise<NDSongList> => {
|
|||||||
|
|
||||||
const searchParams: NDSongListParams = {
|
const searchParams: NDSongListParams = {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
_sort: query.sortBy as NDSongListSort,
|
_sort: songListSortMap.navidrome[query.sortBy],
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
...query.ndParams,
|
...query.ndParams,
|
||||||
};
|
};
|
||||||
@@ -324,6 +262,7 @@ const getSongDetail = async (args: SongDetailArgs): Promise<NDSongDetail> => {
|
|||||||
const data = await api
|
const data = await api
|
||||||
.get(`api/song/${query.id}`, {
|
.get(`api/song/${query.id}`, {
|
||||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
signal,
|
signal,
|
||||||
})
|
})
|
||||||
.json<NDSongDetailResponse>();
|
.json<NDSongDetailResponse>();
|
||||||
@@ -331,7 +270,7 @@ const getSongDetail = async (args: SongDetailArgs): Promise<NDSongDetail> => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createPlaylist = async (args: CreatePlaylistArgs): Promise<NDCreatePlaylist> => {
|
const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => {
|
||||||
const { query, server, signal } = args;
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
const json: NDCreatePlaylistParams = {
|
const json: NDCreatePlaylistParams = {
|
||||||
@@ -344,11 +283,15 @@ const createPlaylist = async (args: CreatePlaylistArgs): Promise<NDCreatePlaylis
|
|||||||
.post('api/playlist', {
|
.post('api/playlist', {
|
||||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
json,
|
json,
|
||||||
|
prefixUrl: server?.url,
|
||||||
signal,
|
signal,
|
||||||
})
|
})
|
||||||
.json<NDCreatePlaylistResponse>();
|
.json<NDCreatePlaylistResponse>();
|
||||||
|
|
||||||
return data;
|
return {
|
||||||
|
id: data.id,
|
||||||
|
name: query.name,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const deletePlaylist = async (args: DeletePlaylistArgs): Promise<NDDeletePlaylist> => {
|
const deletePlaylist = async (args: DeletePlaylistArgs): Promise<NDDeletePlaylist> => {
|
||||||
@@ -357,6 +300,7 @@ const deletePlaylist = async (args: DeletePlaylistArgs): Promise<NDDeletePlaylis
|
|||||||
const data = await api
|
const data = await api
|
||||||
.delete(`api/playlist/${query.id}`, {
|
.delete(`api/playlist/${query.id}`, {
|
||||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
signal,
|
signal,
|
||||||
})
|
})
|
||||||
.json<NDDeletePlaylistResponse>();
|
.json<NDDeletePlaylistResponse>();
|
||||||
@@ -369,12 +313,12 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise<NDPlaylistList>
|
|||||||
|
|
||||||
const searchParams: NDPlaylistListParams = {
|
const searchParams: NDPlaylistListParams = {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: query.sortOrder === SortOrder.ASC ? NDSortOrder.ASC : NDSortOrder.DESC,
|
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : NDSortOrder.ASC,
|
||||||
_sort: query.sortBy as NDPlaylistListSort,
|
_sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : NDPlaylistListSort.NAME,
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await api.get('api/song', {
|
const res = await api.get('api/playlist', {
|
||||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
prefixUrl: server?.url,
|
prefixUrl: server?.url,
|
||||||
searchParams,
|
searchParams,
|
||||||
@@ -395,8 +339,9 @@ const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<NDPlaylistDe
|
|||||||
const { query, server, signal } = args;
|
const { query, server, signal } = args;
|
||||||
|
|
||||||
const data = await api
|
const data = await api
|
||||||
.get(`api/song/${query.id}`, {
|
.get(`api/playlist/${query.id}`, {
|
||||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||||
|
prefixUrl: server?.url,
|
||||||
signal,
|
signal,
|
||||||
})
|
})
|
||||||
.json<NDPlaylistDetailResponse>();
|
.json<NDPlaylistDetailResponse>();
|
||||||
@@ -404,6 +349,33 @@ const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<NDPlaylistDe
|
|||||||
return data;
|
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: {
|
const getCoverArtUrl = (args: {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
coverArtId: 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({
|
const imageUrl = getCoverArtUrl({
|
||||||
baseUrl: server.url,
|
baseUrl: server.url,
|
||||||
coverArtId: item.coverArtId,
|
coverArtId: item.coverArtId,
|
||||||
@@ -434,13 +406,14 @@ const normalizeAlbum = (item: NDAlbum, server: ServerListItem, imageSize?: numbe
|
|||||||
size: imageSize || 300,
|
size: imageSize || 300,
|
||||||
});
|
});
|
||||||
|
|
||||||
const imagePlaceholderUrl = imageUrl?.replace(/size=\d+/, 'size=50');
|
const imagePlaceholderUrl = imageUrl?.replace(/size=\d+/, 'size=50') || null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
albumArtists: [{ id: item.albumArtistId, name: item.albumArtist }],
|
albumArtists: [{ id: item.albumArtistId, name: item.albumArtist }],
|
||||||
artists: [{ id: item.artistId, name: item.artist }],
|
artists: [{ id: item.artistId, name: item.artist }],
|
||||||
backdropImageUrl: null,
|
backdropImageUrl: null,
|
||||||
createdAt: item.createdAt,
|
createdAt: item.createdAt,
|
||||||
|
duration: null,
|
||||||
genres: item.genres,
|
genres: item.genres,
|
||||||
id: item.id,
|
id: item.id,
|
||||||
imagePlaceholderUrl,
|
imagePlaceholderUrl,
|
||||||
@@ -457,7 +430,7 @@ const normalizeAlbum = (item: NDAlbum, server: ServerListItem, imageSize?: numbe
|
|||||||
songCount: item.songCount,
|
songCount: item.songCount,
|
||||||
uniqueId: nanoid(),
|
uniqueId: nanoid(),
|
||||||
updatedAt: item.updatedAt,
|
updatedAt: item.updatedAt,
|
||||||
} as Album;
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeSong = (
|
const normalizeSong = (
|
||||||
@@ -465,7 +438,7 @@ const normalizeSong = (
|
|||||||
server: ServerListItem,
|
server: ServerListItem,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
imageSize?: number,
|
imageSize?: number,
|
||||||
) => {
|
): Song => {
|
||||||
const imageUrl = getCoverArtUrl({
|
const imageUrl = getCoverArtUrl({
|
||||||
baseUrl: server.url,
|
baseUrl: server.url,
|
||||||
coverArtId: item.albumId,
|
coverArtId: item.albumId,
|
||||||
@@ -490,6 +463,8 @@ const normalizeSong = (
|
|||||||
imageUrl,
|
imageUrl,
|
||||||
isFavorite: item.starred,
|
isFavorite: item.starred,
|
||||||
name: item.title,
|
name: item.title,
|
||||||
|
path: item.path,
|
||||||
|
playCount: item.playCount,
|
||||||
releaseDate: new Date(item.year, 0, 1).toISOString(),
|
releaseDate: new Date(item.year, 0, 1).toISOString(),
|
||||||
releaseYear: String(item.year),
|
releaseYear: String(item.year),
|
||||||
serverId: server.id,
|
serverId: server.id,
|
||||||
@@ -499,7 +474,7 @@ const normalizeSong = (
|
|||||||
type: ServerType.NAVIDROME,
|
type: ServerType.NAVIDROME,
|
||||||
uniqueId: nanoid(),
|
uniqueId: nanoid(),
|
||||||
updatedAt: item.updatedAt,
|
updatedAt: item.updatedAt,
|
||||||
} as Song;
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const navidromeApi = {
|
export const navidromeApi = {
|
||||||
@@ -513,6 +488,7 @@ export const navidromeApi = {
|
|||||||
getGenreList,
|
getGenreList,
|
||||||
getPlaylistDetail,
|
getPlaylistDetail,
|
||||||
getPlaylistList,
|
getPlaylistList,
|
||||||
|
getPlaylistSongList,
|
||||||
getSongDetail,
|
getSongDetail,
|
||||||
getSongList,
|
getSongList,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ export type NDAlbum = {
|
|||||||
starred: boolean;
|
starred: boolean;
|
||||||
starredAt: string;
|
starredAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
} & {
|
|
||||||
songs?: NDSong[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NDSong = {
|
export type NDSong = {
|
||||||
@@ -119,7 +117,7 @@ export type NDAuthenticationResponse = NDAuthenticate;
|
|||||||
|
|
||||||
export type NDAlbumArtistList = NDAlbumArtist[];
|
export type NDAlbumArtistList = NDAlbumArtist[];
|
||||||
|
|
||||||
export type NDAlbumArtistDetail = NDAlbumArtist & { albums: NDAlbumListResponse };
|
export type NDAlbumArtistDetail = NDAlbumArtist;
|
||||||
|
|
||||||
export type NDAlbumArtistDetailResponse = NDAlbumArtist;
|
export type NDAlbumArtistDetailResponse = NDAlbumArtist;
|
||||||
|
|
||||||
@@ -129,7 +127,7 @@ export type NDGenreListResponse = NDGenre[];
|
|||||||
|
|
||||||
export type NDAlbumDetailResponse = NDAlbum;
|
export type NDAlbumDetailResponse = NDAlbum;
|
||||||
|
|
||||||
export type NDAlbumDetail = NDAlbum & { songs: NDSongListResponse };
|
export type NDAlbumDetail = NDAlbum & { songs?: NDSongListResponse };
|
||||||
|
|
||||||
export type NDAlbumListResponse = NDAlbum[];
|
export type NDAlbumListResponse = NDAlbum[];
|
||||||
|
|
||||||
@@ -183,6 +181,7 @@ export enum NDAlbumListSort {
|
|||||||
DURATION = 'duration',
|
DURATION = 'duration',
|
||||||
NAME = 'name',
|
NAME = 'name',
|
||||||
PLAY_COUNT = 'playCount',
|
PLAY_COUNT = 'playCount',
|
||||||
|
PLAY_DATE = 'play_date',
|
||||||
RANDOM = 'random',
|
RANDOM = 'random',
|
||||||
RATING = 'rating',
|
RATING = 'rating',
|
||||||
RECENTLY_ADDED = 'recently_added',
|
RECENTLY_ADDED = 'recently_added',
|
||||||
@@ -193,6 +192,7 @@ export enum NDAlbumListSort {
|
|||||||
|
|
||||||
export type NDAlbumListParams = {
|
export type NDAlbumListParams = {
|
||||||
_sort?: NDAlbumListSort;
|
_sort?: NDAlbumListSort;
|
||||||
|
album_id?: string;
|
||||||
artist_id?: string;
|
artist_id?: string;
|
||||||
compilation?: boolean;
|
compilation?: boolean;
|
||||||
genre_id?: string;
|
genre_id?: string;
|
||||||
@@ -213,7 +213,9 @@ export enum NDSongListSort {
|
|||||||
CHANNELS = 'channels',
|
CHANNELS = 'channels',
|
||||||
COMMENT = 'comment',
|
COMMENT = 'comment',
|
||||||
DURATION = 'duration',
|
DURATION = 'duration',
|
||||||
|
FAVORITED = 'starred ASC, starredAt ASC',
|
||||||
GENRE = 'genre',
|
GENRE = 'genre',
|
||||||
|
ID = 'id',
|
||||||
NAME = 'name',
|
NAME = 'name',
|
||||||
PLAY_COUNT = 'playCount',
|
PLAY_COUNT = 'playCount',
|
||||||
PLAY_DATE = 'playDate',
|
PLAY_DATE = 'playDate',
|
||||||
@@ -231,11 +233,11 @@ export type NDSongListParams = {
|
|||||||
|
|
||||||
export enum NDAlbumArtistListSort {
|
export enum NDAlbumArtistListSort {
|
||||||
ALBUM_COUNT = 'albumCount',
|
ALBUM_COUNT = 'albumCount',
|
||||||
|
FAVORITED = 'starred ASC, starredAt ASC',
|
||||||
NAME = 'name',
|
NAME = 'name',
|
||||||
PLAY_COUNT = 'playCount',
|
PLAY_COUNT = 'playCount',
|
||||||
RATING = 'rating',
|
RATING = 'rating',
|
||||||
SONG_COUNT = 'songCount',
|
SONG_COUNT = 'songCount',
|
||||||
STARRED = 'starred ASC, starredAt ASC',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NDAlbumArtistListParams = {
|
export type NDAlbumArtistListParams = {
|
||||||
|
|||||||
@@ -1,33 +1,22 @@
|
|||||||
import type { AlbumListQuery } from './types';
|
import type { AlbumListQuery, SongListQuery } from './types';
|
||||||
import type { AlbumDetailQuery } from './types';
|
import type { AlbumDetailQuery } from './types';
|
||||||
|
|
||||||
export const queryKeys = {
|
export const queryKeys = {
|
||||||
albums: {
|
albums: {
|
||||||
detail: (serverId: string, query: AlbumDetailQuery) => ['albums', serverId, query] as const,
|
detail: (serverId: string, query: AlbumDetailQuery) =>
|
||||||
list: (serverId: string, params: AlbumListQuery) =>
|
[serverId, 'albums', 'detail', query] as const,
|
||||||
[serverId, 'albums', 'list', serverId, params] as const,
|
list: (serverId: string, query: AlbumListQuery) =>
|
||||||
|
[serverId, 'albums', 'list', serverId, query] as const,
|
||||||
root: ['albums'],
|
root: ['albums'],
|
||||||
|
serverRoot: (serverId: string) => [serverId, 'albums'],
|
||||||
|
songs: (serverId: string, query: SongListQuery) =>
|
||||||
|
[serverId, 'albums', 'songs', query] as const,
|
||||||
},
|
},
|
||||||
genres: {
|
genres: {
|
||||||
list: (serverId: string) => [serverId, 'genres', 'list'] as const,
|
list: (serverId: string) => [serverId, 'genres', 'list'] as const,
|
||||||
root: (serverId: string) => [serverId, 'genres'] as const,
|
root: (serverId: string) => [serverId, 'genres'] as const,
|
||||||
},
|
},
|
||||||
ping: (url: string) => ['ping', url] as const,
|
|
||||||
server: {
|
server: {
|
||||||
root: (serverId: string) => [serverId] as const,
|
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,
|
SSAlbumArtistDetail,
|
||||||
SSAlbumArtistDetailResponse,
|
SSAlbumArtistDetailResponse,
|
||||||
SSFavoriteParams,
|
SSFavoriteParams,
|
||||||
SSFavorite,
|
|
||||||
SSFavoriteResponse,
|
SSFavoriteResponse,
|
||||||
SSRatingParams,
|
SSRatingParams,
|
||||||
SSRatingResponse,
|
SSRatingResponse,
|
||||||
@@ -30,6 +29,7 @@ import type {
|
|||||||
AlbumListArgs,
|
AlbumListArgs,
|
||||||
AuthenticationResponse,
|
AuthenticationResponse,
|
||||||
FavoriteArgs,
|
FavoriteArgs,
|
||||||
|
FavoriteResponse,
|
||||||
GenreListArgs,
|
GenreListArgs,
|
||||||
RatingArgs,
|
RatingArgs,
|
||||||
} from '/@/api/types';
|
} 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 { query, signal } = args;
|
||||||
|
|
||||||
const searchParams: SSFavoriteParams = {
|
const searchParams: SSFavoriteParams = {
|
||||||
@@ -231,17 +231,19 @@ const createFavorite = async (args: FavoriteArgs): Promise<SSFavorite> => {
|
|||||||
id: query.type === 'song' ? query.id : undefined,
|
id: query.type === 'song' ? query.id : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = await api
|
await api
|
||||||
.get('/rest/star.view', {
|
.get('/rest/star.view', {
|
||||||
searchParams,
|
searchParams,
|
||||||
signal,
|
signal,
|
||||||
})
|
})
|
||||||
.json<SSFavoriteResponse>();
|
.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 { query, signal } = args;
|
||||||
|
|
||||||
const searchParams: SSFavoriteParams = {
|
const searchParams: SSFavoriteParams = {
|
||||||
@@ -250,14 +252,16 @@ const deleteFavorite = async (args: FavoriteArgs) => {
|
|||||||
id: query.type === 'song' ? query.id : undefined,
|
id: query.type === 'song' ? query.id : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = await api
|
await api
|
||||||
.get('/rest/unstar.view', {
|
.get('/rest/unstar.view', {
|
||||||
searchParams,
|
searchParams,
|
||||||
signal,
|
signal,
|
||||||
})
|
})
|
||||||
.json<SSFavoriteResponse>();
|
.json<SSFavoriteResponse>();
|
||||||
|
|
||||||
return data;
|
return {
|
||||||
|
id: query.id,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRating = async (args: RatingArgs) => {
|
const updateRating = async (args: RatingArgs) => {
|
||||||
|
|||||||
@@ -1,29 +1,43 @@
|
|||||||
import type { ServerListItem } from '/@/store';
|
import type { ServerListItem } from '/@/store';
|
||||||
import type { ServerType } from '/@//types';
|
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 {
|
import type {
|
||||||
NDAlbumArtistDetail,
|
NDAlbumArtistDetail,
|
||||||
NDAlbumArtistList,
|
NDAlbumArtistList,
|
||||||
NDAlbumArtistListSort,
|
|
||||||
NDAlbumDetail,
|
NDAlbumDetail,
|
||||||
NDAlbumList,
|
NDAlbumList,
|
||||||
NDAlbumListSort,
|
|
||||||
NDCreatePlaylist,
|
|
||||||
NDDeletePlaylist,
|
NDDeletePlaylist,
|
||||||
NDGenreList,
|
NDGenreList,
|
||||||
NDOrder,
|
NDOrder,
|
||||||
NDPlaylistDetail,
|
NDPlaylistDetail,
|
||||||
NDPlaylistList,
|
NDPlaylistList,
|
||||||
NDPlaylistListSort,
|
|
||||||
NDSongDetail,
|
NDSongDetail,
|
||||||
NDSongList,
|
NDSongList,
|
||||||
NDSongListSort,
|
|
||||||
} from '/@/api/navidrome.types';
|
} 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 {
|
import type {
|
||||||
SSAlbumArtistDetail,
|
SSAlbumArtistDetail,
|
||||||
SSAlbumArtistList,
|
SSAlbumArtistList,
|
||||||
SSAlbumDetail,
|
SSAlbumDetail,
|
||||||
SSAlbumList,
|
SSAlbumList,
|
||||||
|
SSMusicFolderList,
|
||||||
} from '/@/api/subsonic.types';
|
} from '/@/api/subsonic.types';
|
||||||
|
|
||||||
export enum SortOrder {
|
export enum SortOrder {
|
||||||
@@ -31,6 +45,27 @@ export enum SortOrder {
|
|||||||
DESC = 'DESC',
|
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 {
|
export enum ExternalSource {
|
||||||
LASTFM = 'LASTFM',
|
LASTFM = 'LASTFM',
|
||||||
MUSICBRAINZ = 'MUSICBRAINZ',
|
MUSICBRAINZ = 'MUSICBRAINZ',
|
||||||
@@ -88,6 +123,7 @@ export type Album = {
|
|||||||
artists: RelatedArtist[];
|
artists: RelatedArtist[];
|
||||||
backdropImageUrl: string | null;
|
backdropImageUrl: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
duration: number | null;
|
||||||
genres: Genre[];
|
genres: Genre[];
|
||||||
id: string;
|
id: string;
|
||||||
imagePlaceholderUrl: string | null;
|
imagePlaceholderUrl: string | null;
|
||||||
@@ -115,17 +151,19 @@ export type Song = {
|
|||||||
artists: RelatedArtist[];
|
artists: RelatedArtist[];
|
||||||
bitRate: number;
|
bitRate: number;
|
||||||
compilation: boolean | null;
|
compilation: boolean | null;
|
||||||
container: string;
|
container: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
discNumber: number;
|
discNumber: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
genres: Genre[];
|
genres: Genre[];
|
||||||
id: string;
|
id: string;
|
||||||
imageUrl: string;
|
imageUrl: string | null;
|
||||||
isFavorite: boolean;
|
isFavorite: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
releaseDate: string;
|
path: string | null;
|
||||||
releaseYear: string;
|
playCount: number;
|
||||||
|
releaseDate: string | null;
|
||||||
|
releaseYear: string | null;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
size: number;
|
size: number;
|
||||||
streamUrl: string;
|
streamUrl: string;
|
||||||
@@ -192,8 +230,8 @@ type BaseEndpointArgs = {
|
|||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Genre List ---------------------------------------------------------------------------
|
// Genre List
|
||||||
export type RawGenreListResponse = NDGenreList | undefined;
|
export type RawGenreListResponse = NDGenreList | JFGenreList | undefined;
|
||||||
|
|
||||||
export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefined;
|
export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefined;
|
||||||
|
|
||||||
@@ -201,14 +239,35 @@ export type GenreListArgs = { query: GenreListQuery } & BaseEndpointArgs;
|
|||||||
|
|
||||||
export type GenreListQuery = null;
|
export type GenreListQuery = null;
|
||||||
|
|
||||||
// Album List ---------------------------------------------------------------------------
|
// Album List
|
||||||
export type RawAlbumListResponse = NDAlbumList | SSAlbumList | undefined;
|
export type RawAlbumListResponse = NDAlbumList | SSAlbumList | JFAlbumList | undefined;
|
||||||
|
|
||||||
export type AlbumListResponse = BasePaginatedResponse<Album[]> | null | 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 = {
|
export type AlbumListQuery = {
|
||||||
|
jfParams?: {
|
||||||
|
filters?: string;
|
||||||
|
genres?: string;
|
||||||
|
years?: string;
|
||||||
|
};
|
||||||
limit?: number;
|
limit?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string;
|
||||||
ndParams?: {
|
ndParams?: {
|
||||||
@@ -227,8 +286,68 @@ export type AlbumListQuery = {
|
|||||||
|
|
||||||
export type AlbumListArgs = { query: AlbumListQuery } & BaseEndpointArgs;
|
export type AlbumListArgs = { query: AlbumListQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Album Detail -------------------------------------------------------------------------
|
type AlbumListSortMap = {
|
||||||
export type RawAlbumDetailResponse = NDAlbumDetail | SSAlbumDetail | undefined;
|
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;
|
export type AlbumDetailResponse = Album | null | undefined;
|
||||||
|
|
||||||
@@ -236,14 +355,38 @@ export type AlbumDetailQuery = { id: string };
|
|||||||
|
|
||||||
export type AlbumDetailArgs = { query: AlbumDetailQuery } & BaseEndpointArgs;
|
export type AlbumDetailArgs = { query: AlbumDetailQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Song List ----------------------------------------------------------------------------
|
// Song List
|
||||||
export type RawSongListResponse = NDSongList | undefined;
|
export type RawSongListResponse = NDSongList | JFSongList | undefined;
|
||||||
|
|
||||||
export type SongListResponse = BasePaginatedResponse<Song[]>;
|
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 = {
|
export type SongListQuery = {
|
||||||
|
jfParams?: {
|
||||||
|
filters?: string;
|
||||||
|
genres?: string;
|
||||||
|
includeItemTypes: 'Audio';
|
||||||
|
sortBy?: JFSongListSort;
|
||||||
|
years?: string;
|
||||||
|
};
|
||||||
limit?: number;
|
limit?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string;
|
||||||
ndParams?: {
|
ndParams?: {
|
||||||
@@ -262,7 +405,70 @@ export type SongListQuery = {
|
|||||||
|
|
||||||
export type SongListArgs = { query: SongListQuery } & BaseEndpointArgs;
|
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 RawSongDetailResponse = NDSongDetail | undefined;
|
||||||
|
|
||||||
export type SongDetailResponse = Song | null | undefined;
|
export type SongDetailResponse = Song | null | undefined;
|
||||||
@@ -271,12 +477,28 @@ export type SongDetailQuery = { id: string };
|
|||||||
|
|
||||||
export type SongDetailArgs = { query: SongDetailQuery } & BaseEndpointArgs;
|
export type SongDetailArgs = { query: SongDetailQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Album Artist List -------------------------------------------------------------------
|
// Album Artist List
|
||||||
export type RawAlbumArtistListResponse = NDAlbumArtistList | SSAlbumArtistList | undefined;
|
export type RawAlbumArtistListResponse =
|
||||||
|
| NDAlbumArtistList
|
||||||
|
| SSAlbumArtistList
|
||||||
|
| JFAlbumArtistList
|
||||||
|
| undefined;
|
||||||
|
|
||||||
export type AlbumArtistListResponse = BasePaginatedResponse<AlbumArtist[]>;
|
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 = {
|
export type AlbumArtistListQuery = {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
@@ -293,8 +515,60 @@ export type AlbumArtistListQuery = {
|
|||||||
|
|
||||||
export type AlbumArtistListArgs = { query: AlbumArtistListQuery } & BaseEndpointArgs;
|
export type AlbumArtistListArgs = { query: AlbumArtistListQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Album Artist Detail -----------------------------------------------------------------
|
type AlbumArtistListSortMap = {
|
||||||
export type RawAlbumArtistDetailResponse = NDAlbumArtistDetail | SSAlbumArtistDetail | undefined;
|
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[]>;
|
export type AlbumArtistDetailResponse = BasePaginatedResponse<AlbumArtist[]>;
|
||||||
|
|
||||||
@@ -302,20 +576,100 @@ export type AlbumArtistDetailQuery = { id: string };
|
|||||||
|
|
||||||
export type AlbumArtistDetailArgs = { query: AlbumArtistDetailQuery } & BaseEndpointArgs;
|
export type AlbumArtistDetailArgs = { query: AlbumArtistDetailQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Artist List -------------------------------------------------------------------------
|
// Artist List
|
||||||
|
export type RawArtistListResponse = JFArtistList | undefined;
|
||||||
|
|
||||||
// Artist Detail -----------------------------------------------------------------------
|
export type ArtistListResponse = BasePaginatedResponse<Artist[]>;
|
||||||
|
|
||||||
// Favorite ----------------------------------------------------------------------------
|
export enum ArtistListSort {
|
||||||
export type RawFavoriteResponse = null | undefined;
|
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 FavoriteQuery = { id: string; type?: 'song' | 'album' | 'albumArtist' };
|
||||||
|
|
||||||
export type FavoriteArgs = { query: FavoriteQuery } & BaseEndpointArgs;
|
export type FavoriteArgs = { query: FavoriteQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Rating -------------------------------------------------------------------------------
|
// Rating
|
||||||
export type RawRatingResponse = null | undefined;
|
export type RawRatingResponse = null | undefined;
|
||||||
|
|
||||||
export type RatingResponse = null;
|
export type RatingResponse = null;
|
||||||
@@ -324,16 +678,16 @@ export type RatingQuery = { id: string; rating: number };
|
|||||||
|
|
||||||
export type RatingArgs = { query: RatingQuery } & BaseEndpointArgs;
|
export type RatingArgs = { query: RatingQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Create Playlist -----------------------------------------------------------------------
|
// Create Playlist
|
||||||
export type RawCreatePlaylistResponse = NDCreatePlaylist | undefined;
|
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 CreatePlaylistQuery = { comment?: string; name: string; public?: boolean };
|
||||||
|
|
||||||
export type CreatePlaylistArgs = { query: CreatePlaylistQuery } & BaseEndpointArgs;
|
export type CreatePlaylistArgs = { query: CreatePlaylistQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Delete Playlist -----------------------------------------------------------------------
|
// Delete Playlist
|
||||||
export type RawDeletePlaylistResponse = NDDeletePlaylist | undefined;
|
export type RawDeletePlaylistResponse = NDDeletePlaylist | undefined;
|
||||||
|
|
||||||
export type DeletePlaylistResponse = null;
|
export type DeletePlaylistResponse = null;
|
||||||
@@ -342,8 +696,8 @@ export type DeletePlaylistQuery = { id: string };
|
|||||||
|
|
||||||
export type DeletePlaylistArgs = { query: DeletePlaylistQuery } & BaseEndpointArgs;
|
export type DeletePlaylistArgs = { query: DeletePlaylistQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Playlist List -------------------------------------------------------------------------
|
// Playlist List
|
||||||
export type RawPlaylistListResponse = NDPlaylistList | undefined;
|
export type RawPlaylistListResponse = NDPlaylistList | JFPlaylistList | undefined;
|
||||||
|
|
||||||
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]>;
|
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]>;
|
||||||
|
|
||||||
@@ -359,8 +713,41 @@ export type PlaylistListQuery = {
|
|||||||
|
|
||||||
export type PlaylistListArgs = { query: PlaylistListQuery } & BaseEndpointArgs;
|
export type PlaylistListArgs = { query: PlaylistListQuery } & BaseEndpointArgs;
|
||||||
|
|
||||||
// Playlist Detail -----------------------------------------------------------------------
|
type PlaylistListSortMap = {
|
||||||
export type RawPlaylistDetailResponse = NDPlaylistDetail | undefined;
|
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[]>;
|
export type PlaylistDetailResponse = BasePaginatedResponse<Playlist[]>;
|
||||||
|
|
||||||
@@ -369,3 +756,38 @@ export type PlaylistDetailQuery = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PlaylistDetailArgs = { query: PlaylistDetailQuery } & BaseEndpointArgs;
|
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