Update favorite/rating endpoints

- Refactor subsonic api endpoints to set the default auth params
- The beforeRequest hook is unable to dynamically set existing params
This commit is contained in:
jeffvli 2023-01-07 16:09:40 -08:00
parent f879171398
commit cfa4e5e45c
4 changed files with 135 additions and 89 deletions

View file

@ -16,7 +16,6 @@ import type {
RawAlbumArtistListResponse, RawAlbumArtistListResponse,
RatingArgs, RatingArgs,
RawRatingResponse, RawRatingResponse,
FavoriteArgs,
RawFavoriteResponse, RawFavoriteResponse,
GenreListArgs, GenreListArgs,
RawGenreListResponse, RawGenreListResponse,
@ -37,6 +36,7 @@ import type {
RawUpdatePlaylistResponse, RawUpdatePlaylistResponse,
UserListArgs, UserListArgs,
RawUserListResponse, RawUserListResponse,
FavoriteArgs,
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { subsonicApi } from '/@/renderer/api/subsonic.api'; import { subsonicApi } from '/@/renderer/api/subsonic.api';
import { jellyfinApi } from '/@/renderer/api/jellyfin.api'; import { jellyfinApi } from '/@/renderer/api/jellyfin.api';
@ -237,8 +237,22 @@ const getUserList = async (args: UserListArgs) => {
return (apiController('getUserList') as ControllerEndpoint['getUserList'])?.(args); return (apiController('getUserList') as ControllerEndpoint['getUserList'])?.(args);
}; };
const createFavorite = async (args: FavoriteArgs) => {
return (apiController('createFavorite') as ControllerEndpoint['createFavorite'])?.(args);
};
const deleteFavorite = async (args: FavoriteArgs) => {
return (apiController('deleteFavorite') as ControllerEndpoint['deleteFavorite'])?.(args);
};
const updateRating = async (args: RatingArgs) => {
return (apiController('updateRating') as ControllerEndpoint['updateRating'])?.(args);
};
export const controller = { export const controller = {
createFavorite,
createPlaylist, createPlaylist,
deleteFavorite,
deletePlaylist, deletePlaylist,
getAlbumArtistList, getAlbumArtistList,
getAlbumDetail, getAlbumDetail,
@ -252,4 +266,5 @@ export const controller = {
getSongList, getSongList,
getUserList, getUserList,
updatePlaylist, updatePlaylist,
updateRating,
}; };

View file

@ -498,26 +498,32 @@ const deletePlaylist = async (args: DeletePlaylistArgs): Promise<null> => {
const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => { const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
const { query, server } = args; const { query, server } = args;
await api.post(`users/${server?.userId}/favoriteitems/${query.id}`, { for (const id of query.id) {
headers: { 'X-MediaBrowser-Token': server?.credential }, await api.post(`users/${server?.userId}/favoriteitems/${id}`, {
prefixUrl: server?.url, headers: { 'X-MediaBrowser-Token': server?.credential },
}); prefixUrl: server?.url,
});
}
return { return {
id: query.id, id: query.id,
type: query.type,
}; };
}; };
const deleteFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => { const deleteFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
const { query, server } = args; const { query, server } = args;
await api.delete(`users/${server?.userId}/favoriteitems/${query.id}`, { for (const id of query.id) {
headers: { 'X-MediaBrowser-Token': server?.credential }, await api.delete(`users/${server?.userId}/favoriteitems/${id}`, {
prefixUrl: server?.url, headers: { 'X-MediaBrowser-Token': server?.credential },
}); prefixUrl: server?.url,
});
}
return { return {
id: query.id, id: query.id,
type: query.type,
}; };
}; };

View file

@ -1,6 +1,6 @@
import ky from 'ky'; import ky from 'ky';
import md5 from 'md5'; import md5 from 'md5';
import { randomString } from '/@/renderer/utils'; import { parseSearchParams, randomString } from '/@/renderer/utils';
import type { import type {
SSAlbumListResponse, SSAlbumListResponse,
SSAlbumDetailResponse, SSAlbumDetailResponse,
@ -16,13 +16,11 @@ import type {
SSAlbumArtistDetail, SSAlbumArtistDetail,
SSAlbumArtistDetailResponse, SSAlbumArtistDetailResponse,
SSFavoriteParams, SSFavoriteParams,
SSFavoriteResponse,
SSRatingParams, SSRatingParams,
SSRatingResponse,
SSAlbumArtistDetailParams, SSAlbumArtistDetailParams,
SSAlbumArtistListParams, SSAlbumArtistListParams,
} from '/@/renderer/api/subsonic.types'; } from '/@/renderer/api/subsonic.types';
import type { import {
AlbumArtistDetailArgs, AlbumArtistDetailArgs,
AlbumArtistListArgs, AlbumArtistListArgs,
AlbumDetailArgs, AlbumDetailArgs,
@ -31,10 +29,12 @@ import type {
FavoriteArgs, FavoriteArgs,
FavoriteResponse, FavoriteResponse,
GenreListArgs, GenreListArgs,
LibraryItem,
MusicFolderListArgs, MusicFolderListArgs,
RatingArgs, RatingArgs,
RatingResponse,
ServerListItem,
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { useAuthStore } from '/@/renderer/store';
import { toast } from '/@/renderer/components/toast'; import { toast } from '/@/renderer/components/toast';
const getCoverArtUrl = (args: { const getCoverArtUrl = (args: {
@ -65,40 +65,40 @@ const api = ky.create({
async (_request, _options, response) => { async (_request, _options, response) => {
const data = await response.json(); const data = await response.json();
if (data['subsonic-response'].status !== 'ok') { if (data['subsonic-response'].status !== 'ok') {
toast.warn({ message: 'Issue from Subsonic API' }); toast.error({
message: data['subsonic-response'].error.message,
title: 'Issue from Subsonic API',
});
} }
return new Response(JSON.stringify(data['subsonic-response']), { status: 200 }); return new Response(JSON.stringify(data['subsonic-response']), { status: 200 });
}, },
], ],
beforeRequest: [
(request) => {
const server = useAuthStore.getState().currentServer;
const searchParams = new URLSearchParams();
if (server) {
const authParams = server.credential.split(/&?\w=/gm);
searchParams.set('u', server.username);
searchParams.set('v', '1.13.0');
searchParams.set('c', 'Feishin');
searchParams.set('f', 'json');
if (authParams?.length === 4) {
searchParams.set('s', authParams[2]);
searchParams.set('t', authParams[3]);
} else if (authParams?.length === 3) {
searchParams.set('p', authParams[2]);
}
}
return ky(request, { searchParams });
},
],
}, },
}); });
const getDefaultParams = (server: ServerListItem | null) => {
if (!server) return {};
const authParams = server.credential.split(/&?\w=/gm);
const params: Record<string, string> = {
c: 'Feishin',
f: 'json',
u: server.username,
v: '1.13.0',
};
if (authParams?.length === 4) {
params.s = authParams[2];
params.t = authParams[3];
} else if (authParams?.length === 3) {
params.p = authParams[2];
}
return params;
};
const authenticate = async ( const authenticate = async (
url: string, url: string,
body: { body: {
@ -129,10 +129,12 @@ const authenticate = async (
const getMusicFolderList = async (args: MusicFolderListArgs): Promise<SSMusicFolderList> => { const getMusicFolderList = async (args: MusicFolderListArgs): Promise<SSMusicFolderList> => {
const { signal, server } = args; const { signal, server } = args;
const defaultParams = getDefaultParams(server);
const data = await api const data = await api
.get('rest/getMusicFolders.view', { .get('rest/getMusicFolders.view', {
prefixUrl: server?.url, prefixUrl: server?.url,
searchParams: defaultParams,
signal, signal,
}) })
.json<SSMusicFolderListResponse>(); .json<SSMusicFolderListResponse>();
@ -144,9 +146,11 @@ export const getAlbumArtistDetail = async (
args: AlbumArtistDetailArgs, args: AlbumArtistDetailArgs,
): Promise<SSAlbumArtistDetail> => { ): Promise<SSAlbumArtistDetail> => {
const { server, signal, query } = args; const { server, signal, query } = args;
const defaultParams = getDefaultParams(server);
const searchParams: SSAlbumArtistDetailParams = { const searchParams: SSAlbumArtistDetailParams = {
id: query.id, id: query.id,
...defaultParams,
}; };
const data = await api const data = await api
@ -162,9 +166,11 @@ export const getAlbumArtistDetail = async (
const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<SSAlbumArtistList> => { const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<SSAlbumArtistList> => {
const { signal, server, query } = args; const { signal, server, query } = args;
const defaultParams = getDefaultParams(server);
const searchParams: SSAlbumArtistListParams = { const searchParams: SSAlbumArtistListParams = {
musicFolderId: query.musicFolderId, musicFolderId: query.musicFolderId,
...defaultParams,
}; };
const data = await api const data = await api
@ -186,10 +192,12 @@ const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise<SSAlbumArt
const getGenreList = async (args: GenreListArgs): Promise<SSGenreList> => { const getGenreList = async (args: GenreListArgs): Promise<SSGenreList> => {
const { server, signal } = args; const { server, signal } = args;
const defaultParams = getDefaultParams(server);
const data = await api const data = await api
.get('rest/getGenres.view', { .get('rest/getGenres.view', {
prefixUrl: server?.url, prefixUrl: server?.url,
searchParams: defaultParams,
signal, signal,
}) })
.json<SSGenreListResponse>(); .json<SSGenreListResponse>();
@ -199,11 +207,17 @@ const getGenreList = async (args: GenreListArgs): Promise<SSGenreList> => {
const getAlbumDetail = async (args: AlbumDetailArgs): Promise<SSAlbumDetail> => { const getAlbumDetail = async (args: AlbumDetailArgs): Promise<SSAlbumDetail> => {
const { server, query, signal } = args; const { server, query, signal } = args;
const defaultParams = getDefaultParams(server);
const searchParams = {
id: query.id,
...defaultParams,
};
const data = await api const data = await api
.get('rest/getAlbum.view', { .get('rest/getAlbum.view', {
prefixUrl: server?.url, prefixUrl: server?.url,
searchParams: { id: query.id }, searchParams: parseSearchParams(searchParams),
signal, signal,
}) })
.json<SSAlbumDetailResponse>(); .json<SSAlbumDetailResponse>();
@ -214,12 +228,15 @@ const getAlbumDetail = async (args: AlbumDetailArgs): Promise<SSAlbumDetail> =>
const getAlbumList = async (args: AlbumListArgs): Promise<SSAlbumList> => { const getAlbumList = async (args: AlbumListArgs): Promise<SSAlbumList> => {
const { server, query, signal } = args; const { server, query, signal } = args;
const defaultParams = getDefaultParams(server);
const normalizedParams = {}; const searchParams = {
...defaultParams,
};
const data = await api const data = await api
.get('rest/getAlbumList2.view', { .get('rest/getAlbumList2.view', {
prefixUrl: server?.url, prefixUrl: server?.url,
searchParams: normalizedParams, searchParams: parseSearchParams(searchParams),
signal, signal,
}) })
.json<SSAlbumListResponse>(); .json<SSAlbumListResponse>();
@ -233,65 +250,79 @@ const getAlbumList = async (args: AlbumListArgs): Promise<SSAlbumList> => {
const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => { const createFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
const { server, query, signal } = args; const { server, query, signal } = args;
const defaultParams = getDefaultParams(server);
const searchParams: SSFavoriteParams = { for (const id of query.id) {
albumId: query.type === 'album' ? query.id : undefined, const searchParams: SSFavoriteParams = {
artistId: query.type === 'albumArtist' ? query.id : undefined, albumId: query.type === LibraryItem.ALBUM ? id : undefined,
id: query.type === 'song' ? query.id : undefined, artistId: query.type === LibraryItem.ALBUM_ARTIST ? id : undefined,
}; id: query.type === LibraryItem.SONG ? id : undefined,
...defaultParams,
};
await api await api.get('rest/star.view', {
.get('rest/star.view', {
prefixUrl: server?.url, prefixUrl: server?.url,
searchParams, searchParams: parseSearchParams(searchParams),
signal, signal,
}) });
.json<SSFavoriteResponse>(); // .json<SSFavoriteResponse>();
}
return { return {
id: query.id, id: query.id,
type: query.type,
}; };
}; };
const deleteFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => { const deleteFavorite = async (args: FavoriteArgs): Promise<FavoriteResponse> => {
const { server, query, signal } = args; const { server, query, signal } = args;
const defaultParams = getDefaultParams(server);
const searchParams: SSFavoriteParams = { for (const id of query.id) {
albumId: query.type === 'album' ? query.id : undefined, const searchParams: SSFavoriteParams = {
artistId: query.type === 'albumArtist' ? query.id : undefined, albumId: query.type === LibraryItem.ALBUM ? id : undefined,
id: query.type === 'song' ? query.id : undefined, artistId: query.type === LibraryItem.ALBUM_ARTIST ? id : undefined,
}; id: query.type === LibraryItem.SONG ? id : undefined,
...defaultParams,
};
await api await api.get('rest/unstar.view', {
.get('rest/unstar.view', {
prefixUrl: server?.url, prefixUrl: server?.url,
searchParams, searchParams: parseSearchParams(searchParams),
signal, signal,
}) });
.json<SSFavoriteResponse>(); // .json<SSFavoriteResponse>();
}
return { return {
id: query.id, id: query.id,
type: query.type,
}; };
}; };
const updateRating = async (args: RatingArgs) => { const updateRating = async (args: RatingArgs): Promise<RatingResponse> => {
const { server, query, signal } = args; const { server, query, signal } = args;
const defaultParams = getDefaultParams(server);
const searchParams: SSRatingParams = { for (const id of query.id) {
const searchParams: SSRatingParams = {
id,
rating: query.rating,
...defaultParams,
};
await api.get('rest/setRating.view', {
prefixUrl: server?.url,
searchParams: parseSearchParams(searchParams),
signal,
});
// .json<SSRatingResponse>();
}
return {
id: query.id, id: query.id,
rating: query.rating, rating: query.rating,
}; };
const data = await api
.get('rest/setRating.view', {
prefixUrl: server?.url,
searchParams,
signal,
})
.json<SSRatingResponse>();
return data;
}; };
export const subsonicApi = { export const subsonicApi = {

View file

@ -753,18 +753,21 @@ export const artistListSortMap: ArtistListSortMap = {
// Favorite // Favorite
export type RawFavoriteResponse = FavoriteResponse | undefined; export type RawFavoriteResponse = FavoriteResponse | undefined;
export type FavoriteResponse = { id: string }; export type FavoriteResponse = { id: string[]; type: FavoriteQuery['type'] };
export type FavoriteQuery = { id: string; type?: 'song' | 'album' | 'albumArtist' }; export type FavoriteQuery = {
id: string[];
type?: LibraryItem.SONG | LibraryItem.ALBUM | LibraryItem.ALBUM_ARTIST;
};
export type FavoriteArgs = { query: FavoriteQuery } & BaseEndpointArgs; export type FavoriteArgs = { query: FavoriteQuery } & BaseEndpointArgs;
// Rating // Rating
export type RawRatingResponse = null | undefined; export type RawRatingResponse = RatingResponse | undefined;
export type RatingResponse = null; export type RatingResponse = { id: string[]; rating: number };
export type RatingQuery = { id: string; rating: number }; export type RatingQuery = { id: string[]; rating: number };
export type RatingArgs = { query: RatingQuery } & BaseEndpointArgs; export type RatingArgs = { query: RatingQuery } & BaseEndpointArgs;
@ -916,15 +919,6 @@ export type MusicFolderListResponse = BasePaginatedResponse<Playlist[]>;
export type MusicFolderListArgs = BaseEndpointArgs; export type MusicFolderListArgs = BaseEndpointArgs;
// Create Favorite
export type RawCreateFavoriteResponse = CreateFavoriteResponse | undefined;
export type CreateFavoriteResponse = { id: string };
export type CreateFavoriteQuery = { comment?: string; name: string; public?: boolean };
export type CreateFavoriteArgs = { query: CreateFavoriteQuery } & BaseEndpointArgs;
// User list // User list
// Playlist List // Playlist List
export type RawUserListResponse = NDUserList | undefined; export type RawUserListResponse = NDUserList | undefined;