This repository has been archived on 2025-03-19. You can view files and clone it, but cannot push or open issues or pull requests.
feishin/src/renderer/api/navidrome/navidrome-controller.ts
2023-04-23 19:57:10 -07:00

505 lines
12 KiB
TypeScript

import {
AlbumArtistDetailArgs,
AlbumArtistDetailResponse,
AddToPlaylistArgs,
AddToPlaylistResponse,
CreatePlaylistResponse,
CreatePlaylistArgs,
DeletePlaylistArgs,
DeletePlaylistResponse,
AlbumArtistListResponse,
AlbumArtistListArgs,
albumArtistListSortMap,
sortOrderMap,
AuthenticationResponse,
UserListResponse,
UserListArgs,
userListSortMap,
GenreListArgs,
GenreListResponse,
AlbumDetailResponse,
AlbumDetailArgs,
AlbumListArgs,
albumListSortMap,
AlbumListResponse,
SongListResponse,
SongListArgs,
songListSortMap,
SongDetailResponse,
SongDetailArgs,
UpdatePlaylistArgs,
UpdatePlaylistResponse,
PlaylistListResponse,
PlaylistDetailArgs,
PlaylistListArgs,
playlistListSortMap,
PlaylistDetailResponse,
PlaylistSongListArgs,
PlaylistSongListResponse,
RemoveFromPlaylistResponse,
RemoveFromPlaylistArgs,
} from '../types';
import { ndApiClient } from '/@/renderer/api/navidrome/navidrome-api';
import { ndNormalize } from '/@/renderer/api/navidrome/navidrome-normalize';
import { ndType } from '/@/renderer/api/navidrome/navidrome-types';
const authenticate = async (
url: string,
body: { password: string; username: string },
): Promise<AuthenticationResponse> => {
const cleanServerUrl = url.replace(/\/$/, '');
const res = await ndApiClient({ server: cleanServerUrl }).authenticate({
body: {
password: body.password,
username: body.username,
},
});
if (res.status !== 200) {
throw new Error('Failed to authenticate');
}
return {
credential: `u=${body.username}&s=${res.body.data.subsonicSalt}&t=${res.body.data.subsonicToken}`,
ndCredential: res.body.data.token,
userId: res.body.data.id,
username: res.body.data.username,
};
};
const getUserList = async (args: UserListArgs): Promise<UserListResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getUserList({
query: {
_end: query.startIndex + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: userListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
...query.ndParams,
},
});
if (res.status !== 200) {
throw new Error('Failed to get user list');
}
return {
items: res.body.data.map((user) => ndNormalize.user(user)),
startIndex: query?.startIndex || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
};
const getGenreList = async (args: GenreListArgs): Promise<GenreListResponse> => {
const { server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getGenreList({});
if (res.status !== 200) {
throw new Error('Failed to get genre list');
}
return {
items: res.body.data,
startIndex: 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
};
const getAlbumArtistDetail = async (
args: AlbumArtistDetailArgs,
): Promise<AlbumArtistDetailResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getAlbumArtistDetail({
params: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album artist detail');
}
return ndNormalize.albumArtist(res.body.data, server);
};
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<AlbumArtistListResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getAlbumArtistList({
query: {
_end: query.startIndex + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: albumArtistListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
name: query.searchTerm,
...query._custom?.navidrome,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album artist list');
}
return {
items: res.body.data.map((albumArtist) => ndNormalize.albumArtist(albumArtist, server)),
startIndex: query.startIndex,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
};
const getAlbumDetail = async (args: AlbumDetailArgs): Promise<AlbumDetailResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const albumRes = await ndApiClient({ server, signal }).getAlbumDetail({
params: {
id: query.id,
},
});
const songsData = await ndApiClient({ server, signal }).getSongList({
query: {
_end: 0,
_order: 'ASC',
_sort: 'album',
_start: 0,
album_id: [query.id],
},
});
if (albumRes.status !== 200 || songsData.status !== 200) {
throw new Error('Failed to get album detail');
}
return ndNormalize.album({ ...albumRes.body.data, songs: songsData.body.data }, server);
};
const getAlbumList = async (args: AlbumListArgs): Promise<AlbumListResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getAlbumList({
query: {
_end: query.startIndex + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: albumListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
artist_id: query.artistIds?.[0],
name: query.searchTerm,
...query._custom?.navidrome,
},
});
if (res.status !== 200) {
throw new Error('Failed to get album list');
}
return {
items: res.body.data.map((album) => ndNormalize.album(album, server)),
startIndex: query?.startIndex || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
};
const getSongList = async (args: SongListArgs): Promise<SongListResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getSongList({
query: {
_end: query.startIndex + (query.limit || -1),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: songListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
album_id: query.albumIds,
artist_id: query.artistIds,
title: query.searchTerm,
...query._custom?.navidrome,
},
});
if (res.status !== 200) {
throw new Error('Failed to get song list');
}
return {
items: res.body.data.map((song) => ndNormalize.song(song, server, '')),
startIndex: query?.startIndex || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
};
const getSongDetail = async (args: SongDetailArgs): Promise<SongDetailResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getSongDetail({
params: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to get song detail');
}
return ndNormalize.song(res.body.data, server, '');
};
const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => {
const { body, server } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server }).createPlaylist({
body: {
comment: body.comment,
name: body.name,
public: body._custom.navidrome?.public,
rules: body._custom.navidrome?.rules,
sync: body._custom.navidrome?.sync,
},
});
if (res.status !== 200) {
throw new Error('Failed to create playlist');
}
return {
id: res.body.data.id,
name: body.name,
};
};
const updatePlaylist = async (args: UpdatePlaylistArgs): Promise<UpdatePlaylistResponse> => {
const { query, body, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).updatePlaylist({
body: {
comment: body.comment || '',
name: body.name,
public: body.ndParams?.public || false,
rules: body.ndParams?.rules ? body.ndParams?.rules : undefined,
sync: body.ndParams?.sync || undefined,
},
params: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to update playlist');
}
return {
id: res.body.data.id,
};
};
const deletePlaylist = async (args: DeletePlaylistArgs): Promise<DeletePlaylistResponse> => {
const { query, server } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server }).deletePlaylist({
body: null,
params: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to delete playlist');
}
return null;
};
const getPlaylistList = async (args: PlaylistListArgs): Promise<PlaylistListResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getPlaylistList({
query: {
_end: query.startIndex + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : undefined,
_start: query.startIndex,
...query._custom?.navidrome,
},
});
if (res.status !== 200) {
throw new Error('Failed to get playlist list');
}
return {
items: res.body.data.map((item) => ndNormalize.playlist(item, server)),
startIndex: query?.startIndex || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
};
const getPlaylistDetail = async (args: PlaylistDetailArgs): Promise<PlaylistDetailResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getPlaylistDetail({
params: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to get playlist detail');
}
return ndNormalize.playlist(res.body.data, server);
};
const getPlaylistSongList = async (
args: PlaylistSongListArgs,
): Promise<PlaylistSongListResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).getPlaylistSongList({
params: {
id: query.id,
},
query: {
_end: query.startIndex + (query.limit || 0),
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : 'ASC',
_sort: query.sortBy ? songListSortMap.navidrome[query.sortBy] : ndType._enum.songList.ID,
_start: query.startIndex,
},
});
if (res.status !== 200) {
throw new Error('Failed to get playlist song list');
}
return {
items: res.body.data.map((item) => ndNormalize.song(item, server, '')),
startIndex: query?.startIndex || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
};
};
const addToPlaylist = async (args: AddToPlaylistArgs): Promise<AddToPlaylistResponse> => {
const { body, query, server } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server }).addToPlaylist({
body: {
ids: body.songId,
},
params: {
id: query.id,
},
});
if (res.status !== 200) {
throw new Error('Failed to add to playlist');
}
return null;
};
const removeFromPlaylist = async (
args: RemoveFromPlaylistArgs,
): Promise<RemoveFromPlaylistResponse> => {
const { query, server, signal } = args;
if (!server) {
throw new Error('No server');
}
const res = await ndApiClient({ server, signal }).removeFromPlaylist({
body: null,
params: {
id: query.id,
},
query: {
ids: query.songId,
},
});
if (res.status !== 200) {
throw new Error('Failed to remove from playlist');
}
return null;
};
export const ndController = {
addToPlaylist,
authenticate,
createPlaylist,
deletePlaylist,
getAlbumArtistDetail,
getAlbumArtistList,
getAlbumDetail,
getAlbumList,
getGenreList,
getPlaylistDetail,
getPlaylistList,
getPlaylistSongList,
getSongDetail,
getSongList,
getUserList,
removeFromPlaylist,
updatePlaylist,
};