mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-08 13:00:13 +02:00
Add first iteration of new subsonic controller
This commit is contained in:
@@ -1,28 +1,15 @@
|
||||
import md5 from 'md5';
|
||||
import { z } from 'zod';
|
||||
import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
||||
import { ssNormalize } from '/@/renderer/api/subsonic/subsonic-normalize';
|
||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import { subsonicApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
||||
import { subsonicNormalize } from '/@/renderer/api/subsonic/subsonic-normalize';
|
||||
import { AlbumListSortType, SubsonicApi } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import {
|
||||
ArtistInfoArgs,
|
||||
AlbumListSort,
|
||||
AuthenticationResponse,
|
||||
FavoriteArgs,
|
||||
FavoriteResponse,
|
||||
ControllerEndpoint,
|
||||
LibraryItem,
|
||||
MusicFolderListArgs,
|
||||
MusicFolderListResponse,
|
||||
SetRatingArgs,
|
||||
RatingResponse,
|
||||
ScrobbleArgs,
|
||||
ScrobbleResponse,
|
||||
SongListResponse,
|
||||
TopSongListArgs,
|
||||
SearchArgs,
|
||||
SearchResponse,
|
||||
RandomSongListResponse,
|
||||
RandomSongListArgs,
|
||||
} from '/@/renderer/api/types';
|
||||
import { randomString } from '/@/renderer/utils';
|
||||
import { fsLog } from '/@/logger';
|
||||
|
||||
const authenticate = async (
|
||||
url: string,
|
||||
@@ -59,7 +46,7 @@ const authenticate = async (
|
||||
};
|
||||
}
|
||||
|
||||
await ssApiClient({ server: null, url: cleanServerUrl }).authenticate({
|
||||
await subsonicApiClient({ server: null, url: cleanServerUrl }).ping({
|
||||
query: {
|
||||
c: 'Feishin',
|
||||
f: 'json',
|
||||
@@ -75,246 +62,402 @@ const authenticate = async (
|
||||
};
|
||||
};
|
||||
|
||||
const getMusicFolderList = async (args: MusicFolderListArgs): Promise<MusicFolderListResponse> => {
|
||||
const { apiClientProps } = args;
|
||||
export const SubsonicController: ControllerEndpoint = {
|
||||
addToPlaylist: async (args) => {
|
||||
const { body, query, apiClientProps } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getMusicFolderList({});
|
||||
|
||||
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({
|
||||
const res = await subsonicApiClient(apiClientProps).updatePlaylist({
|
||||
query: {
|
||||
id,
|
||||
rating: query.rating,
|
||||
playlistId: query.id,
|
||||
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> => {
|
||||
const { query, apiClientProps } = args;
|
||||
return null;
|
||||
},
|
||||
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({
|
||||
query: {
|
||||
artist: query.artist,
|
||||
count: query.limit,
|
||||
},
|
||||
});
|
||||
const res = await subsonicApiClient(apiClientProps).star({
|
||||
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 get top songs');
|
||||
}
|
||||
if (res.status !== 200) {
|
||||
fsLog.error('Failed to create favorite');
|
||||
throw new Error('Failed to create favorite');
|
||||
}
|
||||
|
||||
return {
|
||||
items:
|
||||
res.body.topSongs?.song?.map((song) =>
|
||||
ssNormalize.song(song, apiClientProps.server, ''),
|
||||
) || [],
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
||||
};
|
||||
};
|
||||
return null;
|
||||
},
|
||||
createPlaylist: async (args) => {
|
||||
const { body, apiClientProps } = args;
|
||||
|
||||
const getArtistInfo = async (
|
||||
args: ArtistInfoArgs,
|
||||
): Promise<z.infer<typeof ssType._response.artistInfo>> => {
|
||||
const { query, apiClientProps } = args;
|
||||
const res = await subsonicApiClient(apiClientProps).createPlaylist({
|
||||
query: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getArtistInfo({
|
||||
query: {
|
||||
count: query.limit,
|
||||
id: query.artistId,
|
||||
},
|
||||
});
|
||||
if (res.status !== 200) {
|
||||
fsLog.error('Failed to create playlist');
|
||||
throw new Error('Failed to create playlist');
|
||||
}
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get artist info');
|
||||
}
|
||||
return {
|
||||
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> => {
|
||||
const { query, apiClientProps } = args;
|
||||
if (res.status !== 200) {
|
||||
fsLog.error('Failed to delete favorite');
|
||||
throw new Error('Failed to delete favorite');
|
||||
}
|
||||
|
||||
const res = await ssApiClient(apiClientProps).scrobble({
|
||||
query: {
|
||||
id: query.id,
|
||||
submission: query.submission,
|
||||
},
|
||||
});
|
||||
return null;
|
||||
},
|
||||
deletePlaylist: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to scrobble');
|
||||
}
|
||||
const res = await subsonicApiClient(apiClientProps).deletePlaylist({
|
||||
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> => {
|
||||
const { query, apiClientProps } = args;
|
||||
return null;
|
||||
},
|
||||
getAlbumArtistDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).search3({
|
||||
query: {
|
||||
const artistInfoRes = await subsonicApiClient(apiClientProps).getArtistInfo({
|
||||
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,
|
||||
albumOffset: query.albumStartIndex,
|
||||
artistCount: query.albumArtistLimit,
|
||||
@@ -322,61 +465,64 @@ const search3 = async (args: SearchArgs): Promise<SearchResponse> => {
|
||||
query: query.query,
|
||||
songCount: query.songLimit,
|
||||
songOffset: query.songStartIndex,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to search');
|
||||
}
|
||||
const res = await subsonicApiClient(apiClientProps).search3({
|
||||
query: searchQuery,
|
||||
});
|
||||
|
||||
return {
|
||||
albumArtists: res.body.searchResult3?.artist?.map((artist) =>
|
||||
ssNormalize.albumArtist(artist, apiClientProps.server),
|
||||
),
|
||||
albums: res.body.searchResult3?.album?.map((album) =>
|
||||
ssNormalize.album(album, apiClientProps.server),
|
||||
),
|
||||
songs: res.body.searchResult3?.song?.map((song) =>
|
||||
ssNormalize.song(song, apiClientProps.server, ''),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const getRandomSongList = async (args: RandomSongListArgs): Promise<RandomSongListResponse> => {
|
||||
const { query, apiClientProps } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getRandomSongList({
|
||||
query: {
|
||||
fromYear: query.minYear,
|
||||
genre: query.genre,
|
||||
musicFolderId: query.musicFolderId,
|
||||
size: query.limit,
|
||||
toYear: query.maxYear,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get random songs');
|
||||
}
|
||||
|
||||
return {
|
||||
items: res.body.randomSongs?.song?.map((song) =>
|
||||
ssNormalize.song(song, apiClientProps.server, ''),
|
||||
),
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.body.randomSongs?.song?.length || 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const ssController = {
|
||||
authenticate,
|
||||
createFavorite,
|
||||
getArtistInfo,
|
||||
getMusicFolderList,
|
||||
getRandomSongList,
|
||||
getTopSongList,
|
||||
removeFavorite,
|
||||
scrobble,
|
||||
search3,
|
||||
setRating,
|
||||
if (res.status !== 200) {
|
||||
fsLog.error('Failed to search', {
|
||||
context: searchQuery,
|
||||
});
|
||||
throw new Error('Failed to search');
|
||||
}
|
||||
|
||||
return {
|
||||
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),
|
||||
),
|
||||
songs: res.body['subsonic-response'].searchResult3?.song?.map((song) =>
|
||||
subsonicNormalize.song(song, apiClientProps.server, ''),
|
||||
),
|
||||
};
|
||||
},
|
||||
setRating: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
|
||||
const itemIds = query.item.map((item) => item.id);
|
||||
|
||||
for (const id of itemIds) {
|
||||
await subsonicApiClient(apiClientProps).setRating({
|
||||
query: {
|
||||
id,
|
||||
rating: query.rating,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
updatePlaylist: async (args) => {
|
||||
const { body, query, apiClientProps } = args;
|
||||
|
||||
const res = await subsonicApiClient(apiClientProps).updatePlaylist({
|
||||
query: {
|
||||
comment: body.comment,
|
||||
name: body.name,
|
||||
playlistId: query.id,
|
||||
public: body.public,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
fsLog.error('Failed to update playlist');
|
||||
throw new Error('Failed to update playlist');
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user