From cfa4e5e45c3e1fa631145e8d08a787574ab3ceb5 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sat, 7 Jan 2023 16:09:40 -0800 Subject: [PATCH] Update favorite/rating endpoints - Refactor subsonic api endpoints to set the default auth params - The beforeRequest hook is unable to dynamically set existing params --- src/renderer/api/controller.ts | 17 +++- src/renderer/api/jellyfin.api.ts | 22 +++-- src/renderer/api/subsonic.api.ts | 163 ++++++++++++++++++------------- src/renderer/api/types.ts | 22 ++--- 4 files changed, 135 insertions(+), 89 deletions(-) diff --git a/src/renderer/api/controller.ts b/src/renderer/api/controller.ts index bef36311..dcfb58b8 100644 --- a/src/renderer/api/controller.ts +++ b/src/renderer/api/controller.ts @@ -16,7 +16,6 @@ import type { RawAlbumArtistListResponse, RatingArgs, RawRatingResponse, - FavoriteArgs, RawFavoriteResponse, GenreListArgs, RawGenreListResponse, @@ -37,6 +36,7 @@ import type { RawUpdatePlaylistResponse, UserListArgs, RawUserListResponse, + FavoriteArgs, } from '/@/renderer/api/types'; import { subsonicApi } from '/@/renderer/api/subsonic.api'; import { jellyfinApi } from '/@/renderer/api/jellyfin.api'; @@ -237,8 +237,22 @@ const getUserList = async (args: UserListArgs) => { 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 = { + createFavorite, createPlaylist, + deleteFavorite, deletePlaylist, getAlbumArtistList, getAlbumDetail, @@ -252,4 +266,5 @@ export const controller = { getSongList, getUserList, updatePlaylist, + updateRating, }; diff --git a/src/renderer/api/jellyfin.api.ts b/src/renderer/api/jellyfin.api.ts index dae29691..242d3da6 100644 --- a/src/renderer/api/jellyfin.api.ts +++ b/src/renderer/api/jellyfin.api.ts @@ -498,26 +498,32 @@ const deletePlaylist = async (args: DeletePlaylistArgs): Promise => { const createFavorite = async (args: FavoriteArgs): Promise => { const { query, server } = args; - await api.post(`users/${server?.userId}/favoriteitems/${query.id}`, { - headers: { 'X-MediaBrowser-Token': server?.credential }, - prefixUrl: server?.url, - }); + for (const id of query.id) { + await api.post(`users/${server?.userId}/favoriteitems/${id}`, { + headers: { 'X-MediaBrowser-Token': server?.credential }, + prefixUrl: server?.url, + }); + } return { id: query.id, + type: query.type, }; }; const deleteFavorite = async (args: FavoriteArgs): Promise => { const { query, server } = args; - await api.delete(`users/${server?.userId}/favoriteitems/${query.id}`, { - headers: { 'X-MediaBrowser-Token': server?.credential }, - prefixUrl: server?.url, - }); + for (const id of query.id) { + await api.delete(`users/${server?.userId}/favoriteitems/${id}`, { + headers: { 'X-MediaBrowser-Token': server?.credential }, + prefixUrl: server?.url, + }); + } return { id: query.id, + type: query.type, }; }; diff --git a/src/renderer/api/subsonic.api.ts b/src/renderer/api/subsonic.api.ts index 19710c47..4acc0901 100644 --- a/src/renderer/api/subsonic.api.ts +++ b/src/renderer/api/subsonic.api.ts @@ -1,6 +1,6 @@ import ky from 'ky'; import md5 from 'md5'; -import { randomString } from '/@/renderer/utils'; +import { parseSearchParams, randomString } from '/@/renderer/utils'; import type { SSAlbumListResponse, SSAlbumDetailResponse, @@ -16,13 +16,11 @@ import type { SSAlbumArtistDetail, SSAlbumArtistDetailResponse, SSFavoriteParams, - SSFavoriteResponse, SSRatingParams, - SSRatingResponse, SSAlbumArtistDetailParams, SSAlbumArtistListParams, } from '/@/renderer/api/subsonic.types'; -import type { +import { AlbumArtistDetailArgs, AlbumArtistListArgs, AlbumDetailArgs, @@ -31,10 +29,12 @@ import type { FavoriteArgs, FavoriteResponse, GenreListArgs, + LibraryItem, MusicFolderListArgs, RatingArgs, + RatingResponse, + ServerListItem, } from '/@/renderer/api/types'; -import { useAuthStore } from '/@/renderer/store'; import { toast } from '/@/renderer/components/toast'; const getCoverArtUrl = (args: { @@ -65,40 +65,40 @@ const api = ky.create({ async (_request, _options, response) => { const data = await response.json(); 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 }); }, ], - 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 = { + 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 ( url: string, body: { @@ -129,10 +129,12 @@ const authenticate = async ( const getMusicFolderList = async (args: MusicFolderListArgs): Promise => { const { signal, server } = args; + const defaultParams = getDefaultParams(server); const data = await api .get('rest/getMusicFolders.view', { prefixUrl: server?.url, + searchParams: defaultParams, signal, }) .json(); @@ -144,9 +146,11 @@ export const getAlbumArtistDetail = async ( args: AlbumArtistDetailArgs, ): Promise => { const { server, signal, query } = args; + const defaultParams = getDefaultParams(server); const searchParams: SSAlbumArtistDetailParams = { id: query.id, + ...defaultParams, }; const data = await api @@ -162,9 +166,11 @@ export const getAlbumArtistDetail = async ( const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise => { const { signal, server, query } = args; + const defaultParams = getDefaultParams(server); const searchParams: SSAlbumArtistListParams = { musicFolderId: query.musicFolderId, + ...defaultParams, }; const data = await api @@ -186,10 +192,12 @@ const getAlbumArtistList = async (args: AlbumArtistListArgs): Promise => { const { server, signal } = args; + const defaultParams = getDefaultParams(server); const data = await api .get('rest/getGenres.view', { prefixUrl: server?.url, + searchParams: defaultParams, signal, }) .json(); @@ -199,11 +207,17 @@ const getGenreList = async (args: GenreListArgs): Promise => { const getAlbumDetail = async (args: AlbumDetailArgs): Promise => { 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: { id: query.id }, + searchParams: parseSearchParams(searchParams), signal, }) .json(); @@ -214,12 +228,15 @@ const getAlbumDetail = async (args: AlbumDetailArgs): Promise => const getAlbumList = async (args: AlbumListArgs): Promise => { const { server, query, signal } = args; + const defaultParams = getDefaultParams(server); - const normalizedParams = {}; + const searchParams = { + ...defaultParams, + }; const data = await api .get('rest/getAlbumList2.view', { prefixUrl: server?.url, - searchParams: normalizedParams, + searchParams: parseSearchParams(searchParams), signal, }) .json(); @@ -233,65 +250,79 @@ const getAlbumList = async (args: AlbumListArgs): Promise => { const createFavorite = async (args: FavoriteArgs): Promise => { const { server, query, signal } = args; + const defaultParams = getDefaultParams(server); - const searchParams: SSFavoriteParams = { - albumId: query.type === 'album' ? query.id : undefined, - artistId: query.type === 'albumArtist' ? query.id : undefined, - id: query.type === 'song' ? query.id : undefined, - }; + for (const id of query.id) { + const searchParams: SSFavoriteParams = { + albumId: query.type === LibraryItem.ALBUM ? id : undefined, + artistId: query.type === LibraryItem.ALBUM_ARTIST ? id : undefined, + id: query.type === LibraryItem.SONG ? id : undefined, + ...defaultParams, + }; - await api - .get('rest/star.view', { + await api.get('rest/star.view', { prefixUrl: server?.url, - searchParams, + searchParams: parseSearchParams(searchParams), signal, - }) - .json(); + }); + // .json(); + } return { id: query.id, + type: query.type, }; }; const deleteFavorite = async (args: FavoriteArgs): Promise => { const { server, query, signal } = args; + const defaultParams = getDefaultParams(server); - const searchParams: SSFavoriteParams = { - albumId: query.type === 'album' ? query.id : undefined, - artistId: query.type === 'albumArtist' ? query.id : undefined, - id: query.type === 'song' ? query.id : undefined, - }; + for (const id of query.id) { + const searchParams: SSFavoriteParams = { + albumId: query.type === LibraryItem.ALBUM ? id : undefined, + artistId: query.type === LibraryItem.ALBUM_ARTIST ? id : undefined, + id: query.type === LibraryItem.SONG ? id : undefined, + ...defaultParams, + }; - await api - .get('rest/unstar.view', { + await api.get('rest/unstar.view', { prefixUrl: server?.url, - searchParams, + searchParams: parseSearchParams(searchParams), signal, - }) - .json(); + }); + // .json(); + } return { id: query.id, + type: query.type, }; }; -const updateRating = async (args: RatingArgs) => { +const updateRating = async (args: RatingArgs): Promise => { 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(); + } + + return { id: query.id, rating: query.rating, }; - - const data = await api - .get('rest/setRating.view', { - prefixUrl: server?.url, - searchParams, - signal, - }) - .json(); - - return data; }; export const subsonicApi = { diff --git a/src/renderer/api/types.ts b/src/renderer/api/types.ts index 172f619e..338ad337 100644 --- a/src/renderer/api/types.ts +++ b/src/renderer/api/types.ts @@ -753,18 +753,21 @@ export const artistListSortMap: ArtistListSortMap = { // Favorite 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; // 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; @@ -916,15 +919,6 @@ export type MusicFolderListResponse = BasePaginatedResponse; 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 // Playlist List export type RawUserListResponse = NDUserList | undefined;