2023-04-24 01:21:29 -07:00
|
|
|
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 {
|
|
|
|
|
ArtistInfoArgs,
|
|
|
|
|
AuthenticationResponse,
|
|
|
|
|
FavoriteArgs,
|
|
|
|
|
FavoriteResponse,
|
|
|
|
|
LibraryItem,
|
|
|
|
|
MusicFolderListArgs,
|
|
|
|
|
MusicFolderListResponse,
|
2023-04-27 20:32:56 -07:00
|
|
|
SetRatingArgs,
|
2023-04-24 01:21:29 -07:00
|
|
|
RatingResponse,
|
|
|
|
|
ScrobbleArgs,
|
|
|
|
|
ScrobbleResponse,
|
|
|
|
|
SongListResponse,
|
|
|
|
|
TopSongListArgs,
|
|
|
|
|
} from '/@/renderer/api/types';
|
|
|
|
|
import { randomString } from '/@/renderer/utils';
|
|
|
|
|
|
|
|
|
|
const authenticate = async (
|
|
|
|
|
url: string,
|
|
|
|
|
body: {
|
|
|
|
|
legacy?: boolean;
|
|
|
|
|
password: string;
|
|
|
|
|
username: string;
|
|
|
|
|
},
|
|
|
|
|
): Promise<AuthenticationResponse> => {
|
|
|
|
|
let credential: string;
|
|
|
|
|
let credentialParams: {
|
|
|
|
|
p?: string;
|
|
|
|
|
s?: string;
|
|
|
|
|
t?: string;
|
|
|
|
|
u: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const cleanServerUrl = url.replace(/\/$/, '');
|
|
|
|
|
|
|
|
|
|
if (body.legacy) {
|
|
|
|
|
credential = `u=${body.username}&p=${body.password}`;
|
|
|
|
|
credentialParams = {
|
|
|
|
|
p: body.password,
|
|
|
|
|
u: body.username,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
const salt = randomString(12);
|
|
|
|
|
const hash = md5(body.password + salt);
|
|
|
|
|
credential = `u=${body.username}&s=${salt}&t=${hash}`;
|
|
|
|
|
credentialParams = {
|
|
|
|
|
s: salt,
|
|
|
|
|
t: hash,
|
|
|
|
|
u: body.username,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-30 17:55:23 -07:00
|
|
|
await ssApiClient({ server: null, url: cleanServerUrl }).authenticate({
|
2023-04-24 01:21:29 -07:00
|
|
|
query: {
|
|
|
|
|
c: 'Feishin',
|
|
|
|
|
f: 'json',
|
|
|
|
|
v: '1.13.0',
|
|
|
|
|
...credentialParams,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
credential,
|
|
|
|
|
userId: null,
|
|
|
|
|
username: body.username,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getMusicFolderList = async (args: MusicFolderListArgs): Promise<MusicFolderListResponse> => {
|
2023-04-25 16:25:26 -07:00
|
|
|
const { apiClientProps } = args;
|
2023-04-24 01:21:29 -07:00
|
|
|
|
2023-04-25 16:25:26 -07:00
|
|
|
const res = await ssApiClient(apiClientProps).getMusicFolderList({});
|
2023-04-24 01:21:29 -07:00
|
|
|
|
|
|
|
|
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> => {
|
2023-04-25 16:25:26 -07:00
|
|
|
const { query, apiClientProps } = args;
|
2023-04-24 01:21:29 -07:00
|
|
|
|
2023-04-25 16:25:26 -07:00
|
|
|
const res = await ssApiClient(apiClientProps).createFavorite({
|
2023-04-24 01:21:29 -07:00
|
|
|
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');
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 20:32:56 -07:00
|
|
|
return null;
|
2023-04-24 01:21:29 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
|
2023-04-25 16:25:26 -07:00
|
|
|
const { query, apiClientProps } = args;
|
2023-04-24 01:21:29 -07:00
|
|
|
|
2023-04-25 16:25:26 -07:00
|
|
|
const res = await ssApiClient(apiClientProps).removeFavorite({
|
2023-04-24 01:21:29 -07:00
|
|
|
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');
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 20:32:56 -07:00
|
|
|
return null;
|
2023-04-24 01:21:29 -07:00
|
|
|
};
|
|
|
|
|
|
2023-04-27 20:32:56 -07:00
|
|
|
const setRating = async (args: SetRatingArgs): Promise<RatingResponse> => {
|
2023-04-25 16:25:26 -07:00
|
|
|
const { query, apiClientProps } = args;
|
2023-04-24 01:21:29 -07:00
|
|
|
|
|
|
|
|
const itemIds = query.item.map((item) => item.id);
|
|
|
|
|
|
|
|
|
|
for (const id of itemIds) {
|
2023-04-25 16:25:26 -07:00
|
|
|
await ssApiClient(apiClientProps).setRating({
|
2023-04-24 01:21:29 -07:00
|
|
|
query: {
|
|
|
|
|
id,
|
|
|
|
|
rating: query.rating,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getTopSongList = async (args: TopSongListArgs): Promise<SongListResponse> => {
|
2023-04-25 16:25:26 -07:00
|
|
|
const { query, apiClientProps } = args;
|
2023-04-24 01:21:29 -07:00
|
|
|
|
2023-04-25 16:25:26 -07:00
|
|
|
const res = await ssApiClient(apiClientProps).getTopSongsList({
|
2023-04-24 01:21:29 -07:00
|
|
|
query: {
|
|
|
|
|
artist: query.artist,
|
|
|
|
|
count: query.limit,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (res.status !== 200) {
|
|
|
|
|
throw new Error('Failed to get top songs');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
2023-04-30 17:55:23 -07:00
|
|
|
items:
|
|
|
|
|
res.body.topSongs?.song?.map((song) => ssNormalize.song(song, apiClientProps.server, '')) ||
|
|
|
|
|
[],
|
2023-04-24 01:21:29 -07:00
|
|
|
startIndex: 0,
|
2023-04-30 17:55:23 -07:00
|
|
|
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
2023-04-24 01:21:29 -07:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getArtistInfo = async (
|
|
|
|
|
args: ArtistInfoArgs,
|
|
|
|
|
): Promise<z.infer<typeof ssType._response.artistInfo>> => {
|
2023-04-25 16:25:26 -07:00
|
|
|
const { query, apiClientProps } = args;
|
2023-04-24 01:21:29 -07:00
|
|
|
|
2023-04-25 16:25:26 -07:00
|
|
|
const res = await ssApiClient(apiClientProps).getArtistInfo({
|
2023-04-24 01:21:29 -07:00
|
|
|
query: {
|
|
|
|
|
count: query.limit,
|
|
|
|
|
id: query.artistId,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (res.status !== 200) {
|
|
|
|
|
throw new Error('Failed to get artist info');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res.body;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const scrobble = async (args: ScrobbleArgs): Promise<ScrobbleResponse> => {
|
2023-04-25 16:25:26 -07:00
|
|
|
const { query, apiClientProps } = args;
|
2023-04-24 01:21:29 -07:00
|
|
|
|
2023-04-25 16:25:26 -07:00
|
|
|
const res = await ssApiClient(apiClientProps).scrobble({
|
2023-04-24 01:21:29 -07:00
|
|
|
query: {
|
|
|
|
|
id: query.id,
|
|
|
|
|
submission: query.submission,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (res.status !== 200) {
|
|
|
|
|
throw new Error('Failed to scrobble');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const ssController = {
|
|
|
|
|
authenticate,
|
|
|
|
|
createFavorite,
|
|
|
|
|
getArtistInfo,
|
|
|
|
|
getMusicFolderList,
|
|
|
|
|
getTopSongList,
|
|
|
|
|
removeFavorite,
|
|
|
|
|
scrobble,
|
|
|
|
|
setRating,
|
|
|
|
|
};
|