add server info query
This commit is contained in:
parent
9995b2e774
commit
9720fcc202
13 changed files with 173 additions and 10 deletions
|
@ -48,6 +48,8 @@ import type {
|
|||
SearchResponse,
|
||||
LyricsArgs,
|
||||
LyricsResponse,
|
||||
ServerInfo,
|
||||
ServerInfoArgs,
|
||||
} from '/@/renderer/api/types';
|
||||
import { ServerType } from '/@/renderer/types';
|
||||
import { DeletePlaylistResponse, RandomSongListArgs } from './types';
|
||||
|
@ -85,6 +87,7 @@ export type ControllerEndpoint = Partial<{
|
|||
getPlaylistList: (args: PlaylistListArgs) => Promise<PlaylistListResponse>;
|
||||
getPlaylistSongList: (args: PlaylistSongListArgs) => Promise<SongListResponse>;
|
||||
getRandomSongList: (args: RandomSongListArgs) => Promise<SongListResponse>;
|
||||
getServerInfo: (args: ServerInfoArgs) => Promise<ServerInfo>;
|
||||
getSongDetail: (args: SongDetailArgs) => Promise<SongDetailResponse>;
|
||||
getSongList: (args: SongListArgs) => Promise<SongListResponse>;
|
||||
getTopSongs: (args: TopSongListArgs) => Promise<TopSongListResponse>;
|
||||
|
@ -129,6 +132,7 @@ const endpoints: ApiController = {
|
|||
getPlaylistList: jfController.getPlaylistList,
|
||||
getPlaylistSongList: jfController.getPlaylistSongList,
|
||||
getRandomSongList: jfController.getRandomSongList,
|
||||
getServerInfo: jfController.getServerInfo,
|
||||
getSongDetail: jfController.getSongDetail,
|
||||
getSongList: jfController.getSongList,
|
||||
getTopSongs: jfController.getTopSongList,
|
||||
|
@ -165,6 +169,7 @@ const endpoints: ApiController = {
|
|||
getPlaylistList: ndController.getPlaylistList,
|
||||
getPlaylistSongList: ndController.getPlaylistSongList,
|
||||
getRandomSongList: ssController.getRandomSongList,
|
||||
getServerInfo: ssController.getServerInfo,
|
||||
getSongDetail: ndController.getSongDetail,
|
||||
getSongList: ndController.getSongList,
|
||||
getTopSongs: ssController.getTopSongList,
|
||||
|
@ -198,6 +203,7 @@ const endpoints: ApiController = {
|
|||
getMusicFolderList: ssController.getMusicFolderList,
|
||||
getPlaylistDetail: undefined,
|
||||
getPlaylistList: undefined,
|
||||
getServerInfo: ssController.getServerInfo,
|
||||
getSongDetail: undefined,
|
||||
getSongList: undefined,
|
||||
getTopSongs: ssController.getTopSongList,
|
||||
|
@ -481,6 +487,15 @@ const getLyrics = async (args: LyricsArgs) => {
|
|||
)?.(args);
|
||||
};
|
||||
|
||||
const getServerInfo = async (args: ServerInfoArgs) => {
|
||||
return (
|
||||
apiController(
|
||||
'getServerInfo',
|
||||
args.apiClientProps.server?.type,
|
||||
) as ControllerEndpoint['getServerInfo']
|
||||
)?.(args);
|
||||
};
|
||||
|
||||
export const controller = {
|
||||
addToPlaylist,
|
||||
authenticate,
|
||||
|
@ -500,6 +515,7 @@ export const controller = {
|
|||
getPlaylistList,
|
||||
getPlaylistSongList,
|
||||
getRandomSongList,
|
||||
getServerInfo,
|
||||
getSongDetail,
|
||||
getSongList,
|
||||
getTopSongList,
|
||||
|
|
|
@ -150,6 +150,14 @@ export const contract = c.router({
|
|||
400: jfType._response.error,
|
||||
},
|
||||
},
|
||||
getServerInfo: {
|
||||
method: 'GET',
|
||||
path: 'system/info',
|
||||
responses: {
|
||||
200: jfType._response.serverInfo,
|
||||
400: jfType._response.error,
|
||||
},
|
||||
},
|
||||
getSimilarArtistList: {
|
||||
method: 'GET',
|
||||
path: 'artists/:id/similar',
|
||||
|
|
|
@ -49,6 +49,8 @@ import {
|
|||
genreListSortMap,
|
||||
SongDetailArgs,
|
||||
SongDetailResponse,
|
||||
ServerInfo,
|
||||
ServerInfoArgs,
|
||||
} from '/@/renderer/api/types';
|
||||
import { jfApiClient } from '/@/renderer/api/jellyfin/jellyfin-api';
|
||||
import { jfNormalize } from './jellyfin-normalize';
|
||||
|
@ -946,6 +948,18 @@ const getSongDetail = async (args: SongDetailArgs): Promise<SongDetailResponse>
|
|||
return jfNormalize.song(res.body, apiClientProps.server, '');
|
||||
};
|
||||
|
||||
const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
|
||||
const { apiClientProps } = args;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getServerInfo();
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get song detail');
|
||||
}
|
||||
|
||||
return { id: apiClientProps.server?.id, version: res.body.Version };
|
||||
};
|
||||
|
||||
export const jfController = {
|
||||
addToPlaylist,
|
||||
authenticate,
|
||||
|
@ -965,6 +979,7 @@ export const jfController = {
|
|||
getPlaylistList,
|
||||
getPlaylistSongList,
|
||||
getRandomSongList,
|
||||
getServerInfo,
|
||||
getSongDetail,
|
||||
getSongList,
|
||||
getTopSongList,
|
||||
|
|
|
@ -654,6 +654,10 @@ const lyrics = z.object({
|
|||
Lyrics: z.array(lyricText),
|
||||
});
|
||||
|
||||
const serverInfo = z.object({
|
||||
Version: z.string(),
|
||||
});
|
||||
|
||||
export const jfType = {
|
||||
_enum: {
|
||||
albumArtistList: albumArtistListSort,
|
||||
|
@ -707,6 +711,7 @@ export const jfType = {
|
|||
removeFromPlaylist,
|
||||
scrobble,
|
||||
search,
|
||||
serverInfo,
|
||||
song,
|
||||
songList,
|
||||
topSongsList,
|
||||
|
|
|
@ -50,6 +50,13 @@ export const contract = c.router({
|
|||
200: ssType._response.randomSongList,
|
||||
},
|
||||
},
|
||||
getServerInfo: {
|
||||
method: 'GET',
|
||||
path: 'getOpenSubsonicExtensions.view',
|
||||
responses: {
|
||||
200: ssType._response.serverInfo,
|
||||
},
|
||||
},
|
||||
getTopSongsList: {
|
||||
method: 'GET',
|
||||
path: 'getTopSongs.view',
|
||||
|
@ -58,6 +65,13 @@ export const contract = c.router({
|
|||
200: ssType._response.topSongsList,
|
||||
},
|
||||
},
|
||||
ping: {
|
||||
method: 'GET',
|
||||
path: 'ping.view',
|
||||
responses: {
|
||||
200: ssType._response.ping,
|
||||
},
|
||||
},
|
||||
removeFavorite: {
|
||||
method: 'GET',
|
||||
path: 'unstar.view',
|
||||
|
|
|
@ -21,6 +21,8 @@ import {
|
|||
SearchResponse,
|
||||
RandomSongListResponse,
|
||||
RandomSongListArgs,
|
||||
ServerInfo,
|
||||
ServerInfoArgs,
|
||||
} from '/@/renderer/api/types';
|
||||
import { randomString } from '/@/renderer/utils';
|
||||
|
||||
|
@ -368,12 +370,40 @@ const getRandomSongList = async (args: RandomSongListArgs): Promise<RandomSongLi
|
|||
};
|
||||
};
|
||||
|
||||
const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
|
||||
const { apiClientProps } = args;
|
||||
|
||||
const ping = await ssApiClient(apiClientProps).ping();
|
||||
|
||||
if (ping.status !== 200) {
|
||||
throw new Error('Failed to ping server');
|
||||
}
|
||||
|
||||
if (!ping.body.openSubsonic || !ping.body.serverVersion) {
|
||||
return { version: ping.body.version };
|
||||
}
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getServerInfo();
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get server extensions');
|
||||
}
|
||||
|
||||
const features: Record<string, number[]> = {};
|
||||
for (const extension of res.body.openSubsonicExtensions) {
|
||||
features[extension.name] = extension.versions;
|
||||
}
|
||||
|
||||
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion };
|
||||
};
|
||||
|
||||
export const ssController = {
|
||||
authenticate,
|
||||
createFavorite,
|
||||
getArtistInfo,
|
||||
getMusicFolderList,
|
||||
getRandomSongList,
|
||||
getServerInfo,
|
||||
getTopSongList,
|
||||
removeFavorite,
|
||||
scrobble,
|
||||
|
|
|
@ -206,6 +206,21 @@ const randomSongList = z.object({
|
|||
}),
|
||||
});
|
||||
|
||||
const ping = z.object({
|
||||
openSubsonic: z.boolean().optional(),
|
||||
serverVersion: z.string().optional(),
|
||||
version: z.string(),
|
||||
});
|
||||
|
||||
const extension = z.object({
|
||||
name: z.string(),
|
||||
versions: z.number().array(),
|
||||
});
|
||||
|
||||
const serverInfo = z.object({
|
||||
openSubsonicExtensions: z.array(extension),
|
||||
});
|
||||
|
||||
export const ssType = {
|
||||
_parameters: {
|
||||
albumList: albumListParameters,
|
||||
|
@ -229,10 +244,12 @@ export const ssType = {
|
|||
baseResponse,
|
||||
createFavorite,
|
||||
musicFolderList,
|
||||
ping,
|
||||
randomSongList,
|
||||
removeFavorite,
|
||||
scrobble,
|
||||
search3,
|
||||
serverInfo,
|
||||
setRating,
|
||||
song,
|
||||
topSongsList,
|
||||
|
|
|
@ -1139,3 +1139,17 @@ export type FontData = {
|
|||
postscriptName: string;
|
||||
style: string;
|
||||
};
|
||||
|
||||
export type ServerInfoArgs = BaseEndpointArgs;
|
||||
|
||||
export enum SubsonicExtensions {
|
||||
FORM_POST = 'formPost',
|
||||
SONG_LYRICS = 'songLyrics',
|
||||
TRANSCODE_OFFSET = 'transcodeOffset',
|
||||
}
|
||||
|
||||
export type ServerInfo = {
|
||||
features?: Record<string, number[]>;
|
||||
id?: string;
|
||||
version: string;
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@ import { FontType, PlaybackType, PlayerStatus } from '/@/renderer/types';
|
|||
import '@ag-grid-community/styles/ag-grid.css';
|
||||
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { useServerVersion } from '/@/renderer/hooks/use-server-version';
|
||||
|
||||
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
|
||||
|
||||
|
@ -49,6 +50,7 @@ export const App = () => {
|
|||
const remoteSettings = useRemoteSettings();
|
||||
const textStyleRef = useRef<HTMLStyleElement>();
|
||||
useDiscordRpc();
|
||||
useServerVersion();
|
||||
|
||||
useEffect(() => {
|
||||
if (type === FontType.SYSTEM && system) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
InternetProviderLyricResponse,
|
||||
FullLyricsMetadata,
|
||||
LyricGetQuery,
|
||||
SubsonicExtensions,
|
||||
} from '/@/renderer/api/types';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById, useLyricsSettings } from '/@/renderer/store';
|
||||
|
@ -93,16 +94,6 @@ export const useSongLyricsBySong = (
|
|||
if (!server) throw new Error('Server not found');
|
||||
if (!song) return null;
|
||||
|
||||
if (song.lyrics) {
|
||||
return {
|
||||
artist: song.artists?.[0]?.name,
|
||||
lyrics: formatLyrics(song.lyrics),
|
||||
name: song.name,
|
||||
remote: false,
|
||||
source: server?.name ?? 'music server',
|
||||
};
|
||||
}
|
||||
|
||||
if (server.type === ServerType.JELLYFIN) {
|
||||
const jfLyrics = await api.controller
|
||||
.getLyrics({
|
||||
|
@ -120,6 +111,16 @@ export const useSongLyricsBySong = (
|
|||
source: server?.name ?? 'music server',
|
||||
};
|
||||
}
|
||||
} else if (server.features && SubsonicExtensions.SONG_LYRICS in server.features) {
|
||||
console.log(1234);
|
||||
} else if (song.lyrics) {
|
||||
return {
|
||||
artist: song.artists?.[0]?.name,
|
||||
lyrics: formatLyrics(song.lyrics),
|
||||
name: song.name,
|
||||
remote: false,
|
||||
source: server?.name ?? 'music server',
|
||||
};
|
||||
}
|
||||
|
||||
if (fetch) {
|
||||
|
|
|
@ -12,6 +12,8 @@ import { useAuthStoreActions } from '/@/renderer/store';
|
|||
import { ServerListItem, ServerType } from '/@/renderer/types';
|
||||
import { api } from '/@/renderer/api';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { queryClient } from '/@/renderer/lib/react-query';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
|
||||
const localSettings = isElectron() ? window.electron.localSettings : null;
|
||||
|
||||
|
@ -111,6 +113,8 @@ export const EditServerForm = ({ isUpdate, password, server, onCancel }: EditSer
|
|||
localSettings.passwordRemove(server.id);
|
||||
}
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.server.root(server.id) });
|
||||
} catch (err: any) {
|
||||
setIsLoading(false);
|
||||
return toast.error({ message: err?.message });
|
||||
|
|
35
src/renderer/hooks/use-server-version.ts
Normal file
35
src/renderer/hooks/use-server-version.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
|
||||
export const useServerVersion = () => {
|
||||
const { updateServer } = useAuthStoreActions();
|
||||
const server = useCurrentServer();
|
||||
|
||||
const serverInfo = useQuery({
|
||||
enabled: !!server,
|
||||
queryFn: async ({ signal }) => {
|
||||
return controller.getServerInfo({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.server.root(server?.id),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (server && server.id === serverInfo.data?.id) {
|
||||
const { version, features } = serverInfo.data;
|
||||
if (version !== server.version) {
|
||||
updateServer(server.id, {
|
||||
features,
|
||||
version,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [server, serverInfo.data, updateServer]);
|
||||
};
|
|
@ -62,6 +62,7 @@ export enum ServerType {
|
|||
|
||||
export type ServerListItem = {
|
||||
credential: string;
|
||||
features?: Record<string, number[]>;
|
||||
id: string;
|
||||
name: string;
|
||||
ndCredential?: string;
|
||||
|
@ -70,6 +71,7 @@ export type ServerListItem = {
|
|||
url: string;
|
||||
userId: string | null;
|
||||
username: string;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
export enum PlayerStatus {
|
||||
|
|
Reference in a new issue