mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
Add first iteration of new subsonic controller
This commit is contained in:
+25
-153
@@ -1,100 +1,38 @@
|
|||||||
import { useAuthStore } from '/@/renderer/store';
|
import { RandomSongListArgs } from './types';
|
||||||
import { toast } from '/@/renderer/components/toast/index';
|
import i18n from '/@/i18n/i18n';
|
||||||
|
import { jfController } from '/@/renderer/api/jellyfin/jellyfin-controller';
|
||||||
|
import { NavidromeController } from '/@/renderer/api/navidrome/navidrome-controller';
|
||||||
|
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
|
||||||
import type {
|
import type {
|
||||||
AlbumDetailArgs,
|
AddToPlaylistArgs,
|
||||||
AlbumListArgs,
|
|
||||||
SongListArgs,
|
|
||||||
SongDetailArgs,
|
|
||||||
AlbumArtistDetailArgs,
|
AlbumArtistDetailArgs,
|
||||||
AlbumArtistListArgs,
|
AlbumArtistListArgs,
|
||||||
SetRatingArgs,
|
AlbumDetailArgs,
|
||||||
GenreListArgs,
|
AlbumListArgs,
|
||||||
|
ArtistListArgs,
|
||||||
|
ControllerEndpoint,
|
||||||
CreatePlaylistArgs,
|
CreatePlaylistArgs,
|
||||||
DeletePlaylistArgs,
|
DeletePlaylistArgs,
|
||||||
|
FavoriteArgs,
|
||||||
|
GenreListArgs,
|
||||||
|
LyricsArgs,
|
||||||
|
MusicFolderListArgs,
|
||||||
PlaylistDetailArgs,
|
PlaylistDetailArgs,
|
||||||
PlaylistListArgs,
|
PlaylistListArgs,
|
||||||
MusicFolderListArgs,
|
|
||||||
PlaylistSongListArgs,
|
PlaylistSongListArgs,
|
||||||
ArtistListArgs,
|
RemoveFromPlaylistArgs,
|
||||||
|
ScrobbleArgs,
|
||||||
|
SearchArgs,
|
||||||
|
SetRatingArgs,
|
||||||
|
SongDetailArgs,
|
||||||
|
SongListArgs,
|
||||||
|
TopSongListArgs,
|
||||||
UpdatePlaylistArgs,
|
UpdatePlaylistArgs,
|
||||||
UserListArgs,
|
UserListArgs,
|
||||||
FavoriteArgs,
|
|
||||||
TopSongListArgs,
|
|
||||||
AddToPlaylistArgs,
|
|
||||||
AddToPlaylistResponse,
|
|
||||||
RemoveFromPlaylistArgs,
|
|
||||||
RemoveFromPlaylistResponse,
|
|
||||||
ScrobbleArgs,
|
|
||||||
ScrobbleResponse,
|
|
||||||
AlbumArtistDetailResponse,
|
|
||||||
FavoriteResponse,
|
|
||||||
CreatePlaylistResponse,
|
|
||||||
AlbumArtistListResponse,
|
|
||||||
AlbumDetailResponse,
|
|
||||||
AlbumListResponse,
|
|
||||||
ArtistListResponse,
|
|
||||||
GenreListResponse,
|
|
||||||
MusicFolderListResponse,
|
|
||||||
PlaylistDetailResponse,
|
|
||||||
PlaylistListResponse,
|
|
||||||
RatingResponse,
|
|
||||||
SongDetailResponse,
|
|
||||||
SongListResponse,
|
|
||||||
TopSongListResponse,
|
|
||||||
UpdatePlaylistResponse,
|
|
||||||
UserListResponse,
|
|
||||||
AuthenticationResponse,
|
|
||||||
SearchArgs,
|
|
||||||
SearchResponse,
|
|
||||||
LyricsArgs,
|
|
||||||
LyricsResponse,
|
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
|
import { toast } from '/@/renderer/components/toast/index';
|
||||||
|
import { useAuthStore } from '/@/renderer/store';
|
||||||
import { ServerType } from '/@/renderer/types';
|
import { ServerType } from '/@/renderer/types';
|
||||||
import { DeletePlaylistResponse, RandomSongListArgs } from './types';
|
|
||||||
import { ndController } from '/@/renderer/api/navidrome/navidrome-controller';
|
|
||||||
import { ssController } from '/@/renderer/api/subsonic/subsonic-controller';
|
|
||||||
import { jfController } from '/@/renderer/api/jellyfin/jellyfin-controller';
|
|
||||||
import i18n from '/@/i18n/i18n';
|
|
||||||
|
|
||||||
export type ControllerEndpoint = Partial<{
|
|
||||||
addToPlaylist: (args: AddToPlaylistArgs) => Promise<AddToPlaylistResponse>;
|
|
||||||
authenticate: (
|
|
||||||
url: string,
|
|
||||||
body: { password: string; username: string },
|
|
||||||
) => Promise<AuthenticationResponse>;
|
|
||||||
clearPlaylist: () => void;
|
|
||||||
createFavorite: (args: FavoriteArgs) => Promise<FavoriteResponse>;
|
|
||||||
createPlaylist: (args: CreatePlaylistArgs) => Promise<CreatePlaylistResponse>;
|
|
||||||
deleteFavorite: (args: FavoriteArgs) => Promise<FavoriteResponse>;
|
|
||||||
deletePlaylist: (args: DeletePlaylistArgs) => Promise<DeletePlaylistResponse>;
|
|
||||||
getAlbumArtistDetail: (args: AlbumArtistDetailArgs) => Promise<AlbumArtistDetailResponse>;
|
|
||||||
getAlbumArtistList: (args: AlbumArtistListArgs) => Promise<AlbumArtistListResponse>;
|
|
||||||
getAlbumDetail: (args: AlbumDetailArgs) => Promise<AlbumDetailResponse>;
|
|
||||||
getAlbumList: (args: AlbumListArgs) => Promise<AlbumListResponse>;
|
|
||||||
getArtistDetail: () => void;
|
|
||||||
getArtistInfo: (args: any) => void;
|
|
||||||
getArtistList: (args: ArtistListArgs) => Promise<ArtistListResponse>;
|
|
||||||
getFavoritesList: () => void;
|
|
||||||
getFolderItemList: () => void;
|
|
||||||
getFolderList: () => void;
|
|
||||||
getFolderSongs: () => void;
|
|
||||||
getGenreList: (args: GenreListArgs) => Promise<GenreListResponse>;
|
|
||||||
getLyrics: (args: LyricsArgs) => Promise<LyricsResponse>;
|
|
||||||
getMusicFolderList: (args: MusicFolderListArgs) => Promise<MusicFolderListResponse>;
|
|
||||||
getPlaylistDetail: (args: PlaylistDetailArgs) => Promise<PlaylistDetailResponse>;
|
|
||||||
getPlaylistList: (args: PlaylistListArgs) => Promise<PlaylistListResponse>;
|
|
||||||
getPlaylistSongList: (args: PlaylistSongListArgs) => Promise<SongListResponse>;
|
|
||||||
getRandomSongList: (args: RandomSongListArgs) => Promise<SongListResponse>;
|
|
||||||
getSongDetail: (args: SongDetailArgs) => Promise<SongDetailResponse>;
|
|
||||||
getSongList: (args: SongListArgs) => Promise<SongListResponse>;
|
|
||||||
getTopSongs: (args: TopSongListArgs) => Promise<TopSongListResponse>;
|
|
||||||
getUserList: (args: UserListArgs) => Promise<UserListResponse>;
|
|
||||||
removeFromPlaylist: (args: RemoveFromPlaylistArgs) => Promise<RemoveFromPlaylistResponse>;
|
|
||||||
scrobble: (args: ScrobbleArgs) => Promise<ScrobbleResponse>;
|
|
||||||
search: (args: SearchArgs) => Promise<SearchResponse>;
|
|
||||||
setRating: (args: SetRatingArgs) => Promise<RatingResponse>;
|
|
||||||
updatePlaylist: (args: UpdatePlaylistArgs) => Promise<UpdatePlaylistResponse>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
type ApiController = {
|
type ApiController = {
|
||||||
jellyfin: ControllerEndpoint;
|
jellyfin: ControllerEndpoint;
|
||||||
@@ -139,74 +77,8 @@ const endpoints: ApiController = {
|
|||||||
setRating: undefined,
|
setRating: undefined,
|
||||||
updatePlaylist: jfController.updatePlaylist,
|
updatePlaylist: jfController.updatePlaylist,
|
||||||
},
|
},
|
||||||
navidrome: {
|
navidrome: NavidromeController,
|
||||||
addToPlaylist: ndController.addToPlaylist,
|
subsonic: SubsonicController,
|
||||||
authenticate: ndController.authenticate,
|
|
||||||
clearPlaylist: undefined,
|
|
||||||
createFavorite: ssController.createFavorite,
|
|
||||||
createPlaylist: ndController.createPlaylist,
|
|
||||||
deleteFavorite: ssController.removeFavorite,
|
|
||||||
deletePlaylist: ndController.deletePlaylist,
|
|
||||||
getAlbumArtistDetail: ndController.getAlbumArtistDetail,
|
|
||||||
getAlbumArtistList: ndController.getAlbumArtistList,
|
|
||||||
getAlbumDetail: ndController.getAlbumDetail,
|
|
||||||
getAlbumList: ndController.getAlbumList,
|
|
||||||
getArtistDetail: undefined,
|
|
||||||
getArtistInfo: undefined,
|
|
||||||
getArtistList: undefined,
|
|
||||||
getFavoritesList: undefined,
|
|
||||||
getFolderItemList: undefined,
|
|
||||||
getFolderList: undefined,
|
|
||||||
getFolderSongs: undefined,
|
|
||||||
getGenreList: ndController.getGenreList,
|
|
||||||
getLyrics: undefined,
|
|
||||||
getMusicFolderList: ssController.getMusicFolderList,
|
|
||||||
getPlaylistDetail: ndController.getPlaylistDetail,
|
|
||||||
getPlaylistList: ndController.getPlaylistList,
|
|
||||||
getPlaylistSongList: ndController.getPlaylistSongList,
|
|
||||||
getRandomSongList: ssController.getRandomSongList,
|
|
||||||
getSongDetail: ndController.getSongDetail,
|
|
||||||
getSongList: ndController.getSongList,
|
|
||||||
getTopSongs: ssController.getTopSongList,
|
|
||||||
getUserList: ndController.getUserList,
|
|
||||||
removeFromPlaylist: ndController.removeFromPlaylist,
|
|
||||||
scrobble: ssController.scrobble,
|
|
||||||
search: ssController.search3,
|
|
||||||
setRating: ssController.setRating,
|
|
||||||
updatePlaylist: ndController.updatePlaylist,
|
|
||||||
},
|
|
||||||
subsonic: {
|
|
||||||
authenticate: ssController.authenticate,
|
|
||||||
clearPlaylist: undefined,
|
|
||||||
createFavorite: ssController.createFavorite,
|
|
||||||
createPlaylist: undefined,
|
|
||||||
deleteFavorite: ssController.removeFavorite,
|
|
||||||
deletePlaylist: undefined,
|
|
||||||
getAlbumArtistDetail: undefined,
|
|
||||||
getAlbumArtistList: undefined,
|
|
||||||
getAlbumDetail: undefined,
|
|
||||||
getAlbumList: undefined,
|
|
||||||
getArtistDetail: undefined,
|
|
||||||
getArtistInfo: undefined,
|
|
||||||
getArtistList: undefined,
|
|
||||||
getFavoritesList: undefined,
|
|
||||||
getFolderItemList: undefined,
|
|
||||||
getFolderList: undefined,
|
|
||||||
getFolderSongs: undefined,
|
|
||||||
getGenreList: undefined,
|
|
||||||
getLyrics: undefined,
|
|
||||||
getMusicFolderList: ssController.getMusicFolderList,
|
|
||||||
getPlaylistDetail: undefined,
|
|
||||||
getPlaylistList: undefined,
|
|
||||||
getSongDetail: undefined,
|
|
||||||
getSongList: undefined,
|
|
||||||
getTopSongs: ssController.getTopSongList,
|
|
||||||
getUserList: undefined,
|
|
||||||
scrobble: ssController.scrobble,
|
|
||||||
search: ssController.search3,
|
|
||||||
setRating: undefined,
|
|
||||||
updatePlaylist: undefined,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const apiController = (endpoint: keyof ControllerEndpoint, type?: ServerType) => {
|
const apiController = (endpoint: keyof ControllerEndpoint, type?: ServerType) => {
|
||||||
|
|||||||
@@ -39,11 +39,13 @@ import {
|
|||||||
RemoveFromPlaylistResponse,
|
RemoveFromPlaylistResponse,
|
||||||
RemoveFromPlaylistArgs,
|
RemoveFromPlaylistArgs,
|
||||||
genreListSortMap,
|
genreListSortMap,
|
||||||
|
ControllerEndpoint,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { ndApiClient } from '/@/renderer/api/navidrome/navidrome-api';
|
import { ndApiClient } from '/@/renderer/api/navidrome/navidrome-api';
|
||||||
import { ndNormalize } from '/@/renderer/api/navidrome/navidrome-normalize';
|
import { ndNormalize } from '/@/renderer/api/navidrome/navidrome-normalize';
|
||||||
import { ndType } from '/@/renderer/api/navidrome/navidrome-types';
|
import { ndType } from '/@/renderer/api/navidrome/navidrome-types';
|
||||||
import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
import { subsonicApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
||||||
|
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
|
||||||
|
|
||||||
const authenticate = async (
|
const authenticate = async (
|
||||||
url: string,
|
url: string,
|
||||||
@@ -129,7 +131,7 @@ const getAlbumArtistDetail = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
|
const artistInfoRes = await subsonicApiClient(apiClientProps).getArtistInfo({
|
||||||
query: {
|
query: {
|
||||||
count: 10,
|
count: 10,
|
||||||
id: query.id,
|
id: query.id,
|
||||||
@@ -148,15 +150,16 @@ const getAlbumArtistDetail = async (
|
|||||||
{
|
{
|
||||||
...res.body.data,
|
...res.body.data,
|
||||||
...(artistInfoRes.status === 200 && {
|
...(artistInfoRes.status === 200 && {
|
||||||
similarArtists: artistInfoRes.body.artistInfo.similarArtist,
|
similarArtists: artistInfoRes.body['subsonic-response'].artistInfo.similarArtist,
|
||||||
...(!res.body.data.largeImageUrl && {
|
...(!res.body.data.largeImageUrl && {
|
||||||
largeImageUrl: artistInfoRes.body.artistInfo.largeImageUrl,
|
largeImageUrl: artistInfoRes.body['subsonic-response'].artistInfo.largeImageUrl,
|
||||||
}),
|
}),
|
||||||
...(!res.body.data.mediumImageUrl && {
|
...(!res.body.data.mediumImageUrl && {
|
||||||
largeImageUrl: artistInfoRes.body.artistInfo.mediumImageUrl,
|
largeImageUrl:
|
||||||
|
artistInfoRes.body['subsonic-response'].artistInfo.mediumImageUrl,
|
||||||
}),
|
}),
|
||||||
...(!res.body.data.smallImageUrl && {
|
...(!res.body.data.smallImageUrl && {
|
||||||
largeImageUrl: artistInfoRes.body.artistInfo.smallImageUrl,
|
largeImageUrl: artistInfoRes.body['subsonic-response'].artistInfo.smallImageUrl,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -322,7 +325,7 @@ const updatePlaylist = async (args: UpdatePlaylistArgs): Promise<UpdatePlaylistR
|
|||||||
name: body.name,
|
name: body.name,
|
||||||
public: body._custom?.navidrome?.public || false,
|
public: body._custom?.navidrome?.public || false,
|
||||||
rules: body._custom?.navidrome?.rules ? body._custom.navidrome.rules : undefined,
|
rules: body._custom?.navidrome?.rules ? body._custom.navidrome.rules : undefined,
|
||||||
sync: body._custom?.navidrome?.sync || undefined,
|
sync: body._custom?.navidrome?.sync,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
@@ -340,7 +343,6 @@ const deletePlaylist = async (args: DeletePlaylistArgs): Promise<DeletePlaylistR
|
|||||||
const { query, apiClientProps } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
const res = await ndApiClient(apiClientProps).deletePlaylist({
|
const res = await ndApiClient(apiClientProps).deletePlaylist({
|
||||||
body: null,
|
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
@@ -360,7 +362,9 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise<PlaylistListResp
|
|||||||
query: {
|
query: {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
_sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : undefined,
|
_sort: query.sortBy
|
||||||
|
? playlistListSortMap.navidrome[query.sortBy]
|
||||||
|
: playlistListSortMap.navidrome.name,
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
q: query.searchTerm,
|
q: query.searchTerm,
|
||||||
...query._custom?.navidrome,
|
...query._custom?.navidrome,
|
||||||
@@ -449,7 +453,6 @@ const removeFromPlaylist = async (
|
|||||||
const { query, apiClientProps } = args;
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
const res = await ndApiClient(apiClientProps).removeFromPlaylist({
|
const res = await ndApiClient(apiClientProps).removeFromPlaylist({
|
||||||
body: null,
|
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
@@ -465,6 +468,41 @@ const removeFromPlaylist = async (
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NavidromeController: ControllerEndpoint = {
|
||||||
|
addToPlaylist,
|
||||||
|
authenticate,
|
||||||
|
clearPlaylist: undefined,
|
||||||
|
createFavorite: SubsonicController.createFavorite,
|
||||||
|
createPlaylist,
|
||||||
|
deleteFavorite: SubsonicController.deleteFavorite,
|
||||||
|
deletePlaylist,
|
||||||
|
getAlbumArtistDetail,
|
||||||
|
getAlbumArtistList,
|
||||||
|
getAlbumDetail,
|
||||||
|
getAlbumList,
|
||||||
|
getArtistDetail: undefined,
|
||||||
|
getArtistInfo: undefined,
|
||||||
|
getFavoritesList: undefined,
|
||||||
|
getFolderItemList: undefined,
|
||||||
|
getFolderList: undefined,
|
||||||
|
getFolderSongs: undefined,
|
||||||
|
getGenreList,
|
||||||
|
getMusicFolderList: SubsonicController.getMusicFolderList,
|
||||||
|
getPlaylistDetail,
|
||||||
|
getPlaylistList,
|
||||||
|
getPlaylistSongList,
|
||||||
|
getRandomSongList: SubsonicController.getRandomSongList,
|
||||||
|
getSongDetail,
|
||||||
|
getSongList,
|
||||||
|
getTopSongs: SubsonicController.getTopSongs,
|
||||||
|
getUserList,
|
||||||
|
removeFromPlaylist,
|
||||||
|
scrobble: SubsonicController.scrobble,
|
||||||
|
search: SubsonicController.search,
|
||||||
|
setRating: SubsonicController.setRating,
|
||||||
|
updatePlaylist,
|
||||||
|
};
|
||||||
|
|
||||||
export const ndController = {
|
export const ndController = {
|
||||||
addToPlaylist,
|
addToPlaylist,
|
||||||
authenticate,
|
authenticate,
|
||||||
|
|||||||
@@ -1,28 +1,15 @@
|
|||||||
import md5 from 'md5';
|
import md5 from 'md5';
|
||||||
import { z } from 'zod';
|
import { subsonicApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
||||||
import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
import { subsonicNormalize } from '/@/renderer/api/subsonic/subsonic-normalize';
|
||||||
import { ssNormalize } from '/@/renderer/api/subsonic/subsonic-normalize';
|
import { AlbumListSortType, SubsonicApi } from '/@/renderer/api/subsonic/subsonic-types';
|
||||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
|
||||||
import {
|
import {
|
||||||
ArtistInfoArgs,
|
AlbumListSort,
|
||||||
AuthenticationResponse,
|
AuthenticationResponse,
|
||||||
FavoriteArgs,
|
ControllerEndpoint,
|
||||||
FavoriteResponse,
|
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
MusicFolderListArgs,
|
|
||||||
MusicFolderListResponse,
|
|
||||||
SetRatingArgs,
|
|
||||||
RatingResponse,
|
|
||||||
ScrobbleArgs,
|
|
||||||
ScrobbleResponse,
|
|
||||||
SongListResponse,
|
|
||||||
TopSongListArgs,
|
|
||||||
SearchArgs,
|
|
||||||
SearchResponse,
|
|
||||||
RandomSongListResponse,
|
|
||||||
RandomSongListArgs,
|
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { randomString } from '/@/renderer/utils';
|
import { randomString } from '/@/renderer/utils';
|
||||||
|
import { fsLog } from '/@/logger';
|
||||||
|
|
||||||
const authenticate = async (
|
const authenticate = async (
|
||||||
url: string,
|
url: string,
|
||||||
@@ -59,7 +46,7 @@ const authenticate = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await ssApiClient({ server: null, url: cleanServerUrl }).authenticate({
|
await subsonicApiClient({ server: null, url: cleanServerUrl }).ping({
|
||||||
query: {
|
query: {
|
||||||
c: 'Feishin',
|
c: 'Feishin',
|
||||||
f: 'json',
|
f: 'json',
|
||||||
@@ -75,246 +62,402 @@ const authenticate = async (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMusicFolderList = async (args: MusicFolderListArgs): Promise<MusicFolderListResponse> => {
|
export const SubsonicController: ControllerEndpoint = {
|
||||||
const { apiClientProps } = args;
|
addToPlaylist: async (args) => {
|
||||||
|
const { body, query, apiClientProps } = args;
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).getMusicFolderList({});
|
const res = await subsonicApiClient(apiClientProps).updatePlaylist({
|
||||||
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error('Failed to get music folder list');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
items: res.body.musicFolders.musicFolder,
|
|
||||||
startIndex: 0,
|
|
||||||
totalRecordCount: res.body.musicFolders.musicFolder.length,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// export const getAlbumArtistDetail = async (
|
|
||||||
// args: AlbumArtistDetailArgs,
|
|
||||||
// ): Promise<SSAlbumArtistDetail> => {
|
|
||||||
// const { server, signal, query } = args;
|
|
||||||
// const defaultParams = getDefaultParams(server);
|
|
||||||
|
|
||||||
// const searchParams: SSAlbumArtistDetailParams = {
|
|
||||||
// id: query.id,
|
|
||||||
// ...defaultParams,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const data = await api
|
|
||||||
// .get('/getArtist.view', {
|
|
||||||
// prefixUrl: server?.url,
|
|
||||||
// searchParams,
|
|
||||||
// signal,
|
|
||||||
// })
|
|
||||||
// .json<SSAlbumArtistDetailResponse>();
|
|
||||||
|
|
||||||
// return data.artist;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<SSAlbumArtistList> => {
|
|
||||||
// const { signal, server, query } = args;
|
|
||||||
// const defaultParams = getDefaultParams(server);
|
|
||||||
|
|
||||||
// const searchParams: SSAlbumArtistListParams = {
|
|
||||||
// musicFolderId: query.musicFolderId,
|
|
||||||
// ...defaultParams,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const data = await api
|
|
||||||
// .get('rest/getArtists.view', {
|
|
||||||
// prefixUrl: server?.url,
|
|
||||||
// searchParams,
|
|
||||||
// signal,
|
|
||||||
// })
|
|
||||||
// .json<SSAlbumArtistListResponse>();
|
|
||||||
|
|
||||||
// const artists = (data.artists?.index || []).flatMap((index: SSArtistIndex) => index.artist);
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// items: artists,
|
|
||||||
// startIndex: query.startIndex,
|
|
||||||
// totalRecordCount: null,
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const getGenreList = async (args: GenreListArgs): Promise<SSGenreList> => {
|
|
||||||
// const { server, signal } = args;
|
|
||||||
// const defaultParams = getDefaultParams(server);
|
|
||||||
|
|
||||||
// const data = await api
|
|
||||||
// .get('rest/getGenres.view', {
|
|
||||||
// prefixUrl: server?.url,
|
|
||||||
// searchParams: defaultParams,
|
|
||||||
// signal,
|
|
||||||
// })
|
|
||||||
// .json<SSGenreListResponse>();
|
|
||||||
|
|
||||||
// return data.genres.genre;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const getAlbumDetail = async (args: AlbumDetailArgs): Promise<SSAlbumDetail> => {
|
|
||||||
// const { server, query, signal } = args;
|
|
||||||
// const defaultParams = getDefaultParams(server);
|
|
||||||
|
|
||||||
// const searchParams = {
|
|
||||||
// id: query.id,
|
|
||||||
// ...defaultParams,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const data = await api
|
|
||||||
// .get('rest/getAlbum.view', {
|
|
||||||
// prefixUrl: server?.url,
|
|
||||||
// searchParams: parseSearchParams(searchParams),
|
|
||||||
// signal,
|
|
||||||
// })
|
|
||||||
// .json<SSAlbumDetailResponse>();
|
|
||||||
|
|
||||||
// const { song: songs, ...dataWithoutSong } = data.album;
|
|
||||||
// return { ...dataWithoutSong, songs };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const getAlbumList = async (args: AlbumListArgs): Promise<SSAlbumList> => {
|
|
||||||
// const { server, query, signal } = args;
|
|
||||||
// const defaultParams = getDefaultParams(server);
|
|
||||||
|
|
||||||
// const searchParams = {
|
|
||||||
// ...defaultParams,
|
|
||||||
// };
|
|
||||||
// const data = await api
|
|
||||||
// .get('rest/getAlbumList2.view', {
|
|
||||||
// prefixUrl: server?.url,
|
|
||||||
// searchParams: parseSearchParams(searchParams),
|
|
||||||
// signal,
|
|
||||||
// })
|
|
||||||
// .json<SSAlbumListResponse>();
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// items: data.albumList2.album,
|
|
||||||
// startIndex: query.startIndex,
|
|
||||||
// totalRecordCount: null,
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
|
||||||
const { query, apiClientProps } = args;
|
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).createFavorite({
|
|
||||||
query: {
|
|
||||||
albumId: query.type === LibraryItem.ALBUM ? query.id : undefined,
|
|
||||||
artistId: query.type === LibraryItem.ALBUM_ARTIST ? query.id : undefined,
|
|
||||||
id: query.type === LibraryItem.SONG ? query.id : undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error('Failed to create favorite');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
|
||||||
const { query, apiClientProps } = args;
|
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).removeFavorite({
|
|
||||||
query: {
|
|
||||||
albumId: query.type === LibraryItem.ALBUM ? query.id : undefined,
|
|
||||||
artistId: query.type === LibraryItem.ALBUM_ARTIST ? query.id : undefined,
|
|
||||||
id: query.type === LibraryItem.SONG ? query.id : undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error('Failed to delete favorite');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setRating = async (args: SetRatingArgs): Promise<RatingResponse> => {
|
|
||||||
const { query, apiClientProps } = args;
|
|
||||||
|
|
||||||
const itemIds = query.item.map((item) => item.id);
|
|
||||||
|
|
||||||
for (const id of itemIds) {
|
|
||||||
await ssApiClient(apiClientProps).setRating({
|
|
||||||
query: {
|
query: {
|
||||||
id,
|
playlistId: query.id,
|
||||||
rating: query.rating,
|
songIdToAdd: body.songId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
if (res.status !== 200) {
|
||||||
};
|
fsLog.error('Failed to add to playlist');
|
||||||
|
throw new Error('Failed to add to playlist');
|
||||||
|
}
|
||||||
|
|
||||||
const getTopSongList = async (args: TopSongListArgs): Promise<SongListResponse> => {
|
return null;
|
||||||
const { query, apiClientProps } = args;
|
},
|
||||||
|
authenticate: async (url, body) => {
|
||||||
|
const res = await authenticate(url, body);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
createFavorite: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).getTopSongsList({
|
const res = await subsonicApiClient(apiClientProps).star({
|
||||||
query: {
|
query: {
|
||||||
artist: query.artist,
|
albumId: query.type === LibraryItem.ALBUM ? query.id : undefined,
|
||||||
count: query.limit,
|
artistId: query.type === LibraryItem.ALBUM_ARTIST ? query.id : undefined,
|
||||||
},
|
id: query.type === LibraryItem.SONG ? query.id : undefined,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error('Failed to get top songs');
|
fsLog.error('Failed to create favorite');
|
||||||
}
|
throw new Error('Failed to create favorite');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return null;
|
||||||
items:
|
},
|
||||||
res.body.topSongs?.song?.map((song) =>
|
createPlaylist: async (args) => {
|
||||||
ssNormalize.song(song, apiClientProps.server, ''),
|
const { body, apiClientProps } = args;
|
||||||
) || [],
|
|
||||||
startIndex: 0,
|
|
||||||
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getArtistInfo = async (
|
const res = await subsonicApiClient(apiClientProps).createPlaylist({
|
||||||
args: ArtistInfoArgs,
|
query: {
|
||||||
): Promise<z.infer<typeof ssType._response.artistInfo>> => {
|
name: body.name,
|
||||||
const { query, apiClientProps } = args;
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).getArtistInfo({
|
if (res.status !== 200) {
|
||||||
query: {
|
fsLog.error('Failed to create playlist');
|
||||||
count: query.limit,
|
throw new Error('Failed to create playlist');
|
||||||
id: query.artistId,
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
return {
|
||||||
throw new Error('Failed to get artist info');
|
id: res.body['subsonic-response'].playlist.id,
|
||||||
}
|
name: res.body['subsonic-response'].playlist.name,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
deleteFavorite: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
return res.body;
|
const res = await subsonicApiClient(apiClientProps).unstar({
|
||||||
};
|
query: {
|
||||||
|
albumId: query.type === LibraryItem.ALBUM ? query.id : undefined,
|
||||||
|
artistId: query.type === LibraryItem.ALBUM_ARTIST ? query.id : undefined,
|
||||||
|
id: query.type === LibraryItem.SONG ? query.id : undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const scrobble = async (args: ScrobbleArgs): Promise<ScrobbleResponse> => {
|
if (res.status !== 200) {
|
||||||
const { query, apiClientProps } = args;
|
fsLog.error('Failed to delete favorite');
|
||||||
|
throw new Error('Failed to delete favorite');
|
||||||
|
}
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).scrobble({
|
return null;
|
||||||
query: {
|
},
|
||||||
id: query.id,
|
deletePlaylist: async (args) => {
|
||||||
submission: query.submission,
|
const { query, apiClientProps } = args;
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
const res = await subsonicApiClient(apiClientProps).deletePlaylist({
|
||||||
throw new Error('Failed to scrobble');
|
query: {
|
||||||
}
|
id: query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return null;
|
if (res.status !== 200) {
|
||||||
};
|
fsLog.error('Failed to delete playlist');
|
||||||
|
throw new Error('Failed to delete playlist');
|
||||||
|
}
|
||||||
|
|
||||||
const search3 = async (args: SearchArgs): Promise<SearchResponse> => {
|
return null;
|
||||||
const { query, apiClientProps } = args;
|
},
|
||||||
|
getAlbumArtistDetail: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).search3({
|
const artistInfoRes = await subsonicApiClient(apiClientProps).getArtistInfo({
|
||||||
query: {
|
query: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getArtist({
|
||||||
|
query: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get album artist detail');
|
||||||
|
throw new Error('Failed to get album artist detail');
|
||||||
|
}
|
||||||
|
|
||||||
|
const artist = res.body['subsonic-response'].artist;
|
||||||
|
|
||||||
|
let artistInfo;
|
||||||
|
if (artistInfoRes.status === 200) {
|
||||||
|
artistInfo = artistInfoRes.body['subsonic-response'].artistInfo;
|
||||||
|
fsLog.warn('Failed to get artist info');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...subsonicNormalize.albumArtist(artist, apiClientProps.server),
|
||||||
|
albums: artist.album.map((album) =>
|
||||||
|
subsonicNormalize.album(album, apiClientProps.server),
|
||||||
|
),
|
||||||
|
artistInfo,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getAlbumArtistList: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getArtists({
|
||||||
|
query: {
|
||||||
|
musicFolderId: query.musicFolderId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get album artist list');
|
||||||
|
throw new Error('Failed to get album artist list');
|
||||||
|
}
|
||||||
|
|
||||||
|
const artists = (res.body['subsonic-response'].artists?.index || []).flatMap(
|
||||||
|
(index) => index.artist,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: artists.map((artist) =>
|
||||||
|
subsonicNormalize.albumArtist(artist, apiClientProps.server),
|
||||||
|
),
|
||||||
|
startIndex: query.startIndex,
|
||||||
|
totalRecordCount: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getAlbumDetail: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getAlbum({
|
||||||
|
query: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get album detail', {
|
||||||
|
context: { id: query.id },
|
||||||
|
});
|
||||||
|
throw new Error('Failed to get album detail');
|
||||||
|
}
|
||||||
|
|
||||||
|
return subsonicNormalize.album(res.body['subsonic-response'].album, apiClientProps.server);
|
||||||
|
},
|
||||||
|
getAlbumList: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const sortType: Record<AlbumListSort, AlbumListSortType | undefined> = {
|
||||||
|
[AlbumListSort.RANDOM]: SubsonicApi.getAlbumList2.enum.AlbumListSortType.RANDOM,
|
||||||
|
[AlbumListSort.ALBUM_ARTIST]:
|
||||||
|
SubsonicApi.getAlbumList2.enum.AlbumListSortType.ALPHABETICAL_BY_ARTIST,
|
||||||
|
[AlbumListSort.PLAY_COUNT]: SubsonicApi.getAlbumList2.enum.AlbumListSortType.FREQUENT,
|
||||||
|
[AlbumListSort.RECENTLY_ADDED]: SubsonicApi.getAlbumList2.enum.AlbumListSortType.NEWEST,
|
||||||
|
[AlbumListSort.FAVORITED]: SubsonicApi.getAlbumList2.enum.AlbumListSortType.STARRED,
|
||||||
|
[AlbumListSort.YEAR]: SubsonicApi.getAlbumList2.enum.AlbumListSortType.RECENT,
|
||||||
|
[AlbumListSort.NAME]:
|
||||||
|
SubsonicApi.getAlbumList2.enum.AlbumListSortType.ALPHABETICAL_BY_NAME,
|
||||||
|
[AlbumListSort.COMMUNITY_RATING]: undefined,
|
||||||
|
[AlbumListSort.DURATION]: undefined,
|
||||||
|
[AlbumListSort.CRITIC_RATING]: undefined,
|
||||||
|
[AlbumListSort.RATING]: undefined,
|
||||||
|
[AlbumListSort.ARTIST]: undefined,
|
||||||
|
[AlbumListSort.RECENTLY_PLAYED]: undefined,
|
||||||
|
[AlbumListSort.RELEASE_DATE]: undefined,
|
||||||
|
[AlbumListSort.SONG_COUNT]: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getAlbumList2({
|
||||||
|
query: {
|
||||||
|
fromYear: query.minYear,
|
||||||
|
genre: query.genre,
|
||||||
|
musicFolderId: query.musicFolderId,
|
||||||
|
offset: query.startIndex,
|
||||||
|
size: query.limit,
|
||||||
|
toYear: query.maxYear,
|
||||||
|
type:
|
||||||
|
sortType[query.sortBy] ??
|
||||||
|
SubsonicApi.getAlbumList2.enum.AlbumListSortType.ALPHABETICAL_BY_NAME,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get album list');
|
||||||
|
throw new Error('Failed to get album list');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: res.body['subsonic-response'].albumList2.album.map((album) =>
|
||||||
|
subsonicNormalize.album(album, apiClientProps.server),
|
||||||
|
),
|
||||||
|
startIndex: query.startIndex,
|
||||||
|
totalRecordCount: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getAlbumSongList: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getAlbum({
|
||||||
|
query: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get album song list');
|
||||||
|
throw new Error('Failed to get album song list');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: res.body['subsonic-response'].album.song.map((song) =>
|
||||||
|
subsonicNormalize.song(song, apiClientProps.server, ''),
|
||||||
|
),
|
||||||
|
startIndex: 0,
|
||||||
|
totalRecordCount: res.body['subsonic-response'].album.song.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getArtistInfo: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getArtistInfo({
|
||||||
|
query: {
|
||||||
|
id: query.artistId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get artist info', {
|
||||||
|
context: { id: query.artistId },
|
||||||
|
});
|
||||||
|
throw new Error('Failed to get artist info');
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.body['subsonic-response'].artistInfo;
|
||||||
|
},
|
||||||
|
getGenreList: async (args) => {
|
||||||
|
const { apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getGenres({});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get genre list');
|
||||||
|
throw new Error('Failed to get genre list');
|
||||||
|
}
|
||||||
|
|
||||||
|
const genres = res.body['subsonic-response'].genres.genre.map(subsonicNormalize.genre);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: genres,
|
||||||
|
startIndex: 0,
|
||||||
|
totalRecordCount: genres.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getMusicFolderList: async (args) => {
|
||||||
|
const { apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getMusicFolders({});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get music folder list');
|
||||||
|
throw new Error('Failed to get music folder list');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: res.body['subsonic-response'].musicFolders.musicFolder.map(
|
||||||
|
subsonicNormalize.musicFolder,
|
||||||
|
),
|
||||||
|
startIndex: 0,
|
||||||
|
totalRecordCount: res.body['subsonic-response'].musicFolders.musicFolder.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getRandomSongList: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getRandomSongs({
|
||||||
|
query: {
|
||||||
|
fromYear: query.minYear,
|
||||||
|
genre: query.genre,
|
||||||
|
musicFolderId: query.musicFolderId,
|
||||||
|
size: query.limit,
|
||||||
|
toYear: query.maxYear,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get random songs');
|
||||||
|
throw new Error('Failed to get random songs');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: res.body['subsonic-response'].randomSongs?.song?.map((song) =>
|
||||||
|
subsonicNormalize.song(song, apiClientProps.server, ''),
|
||||||
|
),
|
||||||
|
startIndex: 0,
|
||||||
|
totalRecordCount: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getSongDetail: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getSong({
|
||||||
|
query: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get song detail');
|
||||||
|
throw new Error('Failed to get song detail');
|
||||||
|
}
|
||||||
|
|
||||||
|
return subsonicNormalize.song(
|
||||||
|
res.body['subsonic-response'].song,
|
||||||
|
apiClientProps.server,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getTopSongs: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).getTopSongs({
|
||||||
|
query: {
|
||||||
|
artist: query.artist,
|
||||||
|
count: query.limit,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to get top songs', {
|
||||||
|
context: { artist: query.artist },
|
||||||
|
});
|
||||||
|
throw new Error('Failed to get top songs');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items:
|
||||||
|
res.body['subsonic-response'].topSongs?.song?.map((song) =>
|
||||||
|
subsonicNormalize.song(song, apiClientProps.server, ''),
|
||||||
|
) || [],
|
||||||
|
startIndex: 0,
|
||||||
|
totalRecordCount: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
scrobble: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).scrobble({
|
||||||
|
query: {
|
||||||
|
id: query.id,
|
||||||
|
submission: query.submission,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
fsLog.error('Failed to scrobble', {
|
||||||
|
context: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
throw new Error('Failed to scrobble');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
search: async (args) => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const searchQuery = {
|
||||||
albumCount: query.albumLimit,
|
albumCount: query.albumLimit,
|
||||||
albumOffset: query.albumStartIndex,
|
albumOffset: query.albumStartIndex,
|
||||||
artistCount: query.albumArtistLimit,
|
artistCount: query.albumArtistLimit,
|
||||||
@@ -322,61 +465,64 @@ const search3 = async (args: SearchArgs): Promise<SearchResponse> => {
|
|||||||
query: query.query,
|
query: query.query,
|
||||||
songCount: query.songLimit,
|
songCount: query.songLimit,
|
||||||
songOffset: query.songStartIndex,
|
songOffset: query.songStartIndex,
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
const res = await subsonicApiClient(apiClientProps).search3({
|
||||||
throw new Error('Failed to search');
|
query: searchQuery,
|
||||||
}
|
});
|
||||||
|
|
||||||
return {
|
if (res.status !== 200) {
|
||||||
albumArtists: res.body.searchResult3?.artist?.map((artist) =>
|
fsLog.error('Failed to search', {
|
||||||
ssNormalize.albumArtist(artist, apiClientProps.server),
|
context: searchQuery,
|
||||||
),
|
});
|
||||||
albums: res.body.searchResult3?.album?.map((album) =>
|
throw new Error('Failed to search');
|
||||||
ssNormalize.album(album, apiClientProps.server),
|
}
|
||||||
),
|
|
||||||
songs: res.body.searchResult3?.song?.map((song) =>
|
return {
|
||||||
ssNormalize.song(song, apiClientProps.server, ''),
|
albumArtists: res.body['subsonic-response'].searchResult3?.artist?.map((artist) =>
|
||||||
),
|
subsonicNormalize.albumArtist(artist, apiClientProps.server),
|
||||||
};
|
),
|
||||||
};
|
albums: res.body['subsonic-response'].searchResult3?.album?.map((album) =>
|
||||||
|
subsonicNormalize.album(album, apiClientProps.server),
|
||||||
const getRandomSongList = async (args: RandomSongListArgs): Promise<RandomSongListResponse> => {
|
),
|
||||||
const { query, apiClientProps } = args;
|
songs: res.body['subsonic-response'].searchResult3?.song?.map((song) =>
|
||||||
|
subsonicNormalize.song(song, apiClientProps.server, ''),
|
||||||
const res = await ssApiClient(apiClientProps).getRandomSongList({
|
),
|
||||||
query: {
|
};
|
||||||
fromYear: query.minYear,
|
},
|
||||||
genre: query.genre,
|
setRating: async (args) => {
|
||||||
musicFolderId: query.musicFolderId,
|
const { query, apiClientProps } = args;
|
||||||
size: query.limit,
|
|
||||||
toYear: query.maxYear,
|
const itemIds = query.item.map((item) => item.id);
|
||||||
},
|
|
||||||
});
|
for (const id of itemIds) {
|
||||||
|
await subsonicApiClient(apiClientProps).setRating({
|
||||||
if (res.status !== 200) {
|
query: {
|
||||||
throw new Error('Failed to get random songs');
|
id,
|
||||||
}
|
rating: query.rating,
|
||||||
|
},
|
||||||
return {
|
});
|
||||||
items: res.body.randomSongs?.song?.map((song) =>
|
}
|
||||||
ssNormalize.song(song, apiClientProps.server, ''),
|
|
||||||
),
|
return null;
|
||||||
startIndex: 0,
|
},
|
||||||
totalRecordCount: res.body.randomSongs?.song?.length || 0,
|
updatePlaylist: async (args) => {
|
||||||
};
|
const { body, query, apiClientProps } = args;
|
||||||
};
|
|
||||||
|
const res = await subsonicApiClient(apiClientProps).updatePlaylist({
|
||||||
export const ssController = {
|
query: {
|
||||||
authenticate,
|
comment: body.comment,
|
||||||
createFavorite,
|
name: body.name,
|
||||||
getArtistInfo,
|
playlistId: query.id,
|
||||||
getMusicFolderList,
|
public: body.public,
|
||||||
getRandomSongList,
|
},
|
||||||
getTopSongList,
|
});
|
||||||
removeFavorite,
|
|
||||||
scrobble,
|
if (res.status !== 200) {
|
||||||
search3,
|
fsLog.error('Failed to update playlist');
|
||||||
setRating,
|
throw new Error('Failed to update playlist');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { SubsonicApi } from '/@/renderer/api/subsonic/subsonic-types';
|
import { SubsonicApi } from '/@/renderer/api/subsonic/subsonic-types';
|
||||||
import { QueueSong, LibraryItem, AlbumArtist, Album, Genre } from '/@/renderer/api/types';
|
import {
|
||||||
|
QueueSong,
|
||||||
|
LibraryItem,
|
||||||
|
AlbumArtist,
|
||||||
|
Album,
|
||||||
|
Genre,
|
||||||
|
MusicFolder,
|
||||||
|
} from '/@/renderer/api/types';
|
||||||
import { ServerListItem, ServerType } from '/@/renderer/types';
|
import { ServerListItem, ServerType } from '/@/renderer/types';
|
||||||
|
|
||||||
const getCoverArtUrl = (args: {
|
const getCoverArtUrl = (args: {
|
||||||
@@ -105,7 +112,9 @@ const normalizeSong = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const normalizeAlbumArtist = (
|
const normalizeAlbumArtist = (
|
||||||
item: z.infer<typeof SubsonicApi._baseTypes.artist>,
|
item:
|
||||||
|
| z.infer<typeof SubsonicApi._baseTypes.artist>
|
||||||
|
| z.infer<typeof SubsonicApi._baseTypes.artistListEntry>,
|
||||||
server: ServerListItem | null,
|
server: ServerListItem | null,
|
||||||
): AlbumArtist => {
|
): AlbumArtist => {
|
||||||
const imageUrl =
|
const imageUrl =
|
||||||
@@ -202,9 +211,19 @@ const normalizeGenre = (item: z.infer<typeof SubsonicApi._baseTypes.genre>): Gen
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeMusicFolder = (
|
||||||
|
item: z.infer<typeof SubsonicApi._baseTypes.musicFolder>,
|
||||||
|
): MusicFolder => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const subsonicNormalize = {
|
export const subsonicNormalize = {
|
||||||
album: normalizeAlbum,
|
album: normalizeAlbum,
|
||||||
albumArtist: normalizeAlbumArtist,
|
albumArtist: normalizeAlbumArtist,
|
||||||
genre: normalizeGenre,
|
genre: normalizeGenre,
|
||||||
|
musicFolder: normalizeMusicFolder,
|
||||||
song: normalizeSong,
|
song: normalizeSong,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -156,23 +156,21 @@ const playlistListEntry = playlist.omit({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const artistInfo = z.object({
|
const artistInfo = z.object({
|
||||||
artistInfo: z.object({
|
biography: z.string().optional(),
|
||||||
biography: z.string().optional(),
|
largeImageUrl: z.string().optional(),
|
||||||
largeImageUrl: z.string().optional(),
|
lastFmUrl: z.string().optional(),
|
||||||
lastFmUrl: z.string().optional(),
|
mediumImageUrl: z.string().optional(),
|
||||||
mediumImageUrl: z.string().optional(),
|
musicBrainzId: z.string().optional(),
|
||||||
musicBrainzId: z.string().optional(),
|
similarArtist: z.array(
|
||||||
similarArtist: z.array(
|
z.object({
|
||||||
z.object({
|
albumCount: z.string(),
|
||||||
albumCount: z.string(),
|
artistImageUrl: z.string().optional(),
|
||||||
artistImageUrl: z.string().optional(),
|
coverArt: z.string().optional(),
|
||||||
coverArt: z.string().optional(),
|
id: z.string(),
|
||||||
id: z.string(),
|
name: z.string(),
|
||||||
name: z.string(),
|
}),
|
||||||
}),
|
),
|
||||||
),
|
smallImageUrl: z.string().optional(),
|
||||||
smallImageUrl: z.string().optional(),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const albumInfo = z.object({
|
const albumInfo = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user