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,
|
SearchResponse,
|
||||||
LyricsArgs,
|
LyricsArgs,
|
||||||
LyricsResponse,
|
LyricsResponse,
|
||||||
|
ServerInfo,
|
||||||
|
ServerInfoArgs,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { ServerType } from '/@/renderer/types';
|
import { ServerType } from '/@/renderer/types';
|
||||||
import { DeletePlaylistResponse, RandomSongListArgs } from './types';
|
import { DeletePlaylistResponse, RandomSongListArgs } from './types';
|
||||||
|
@ -85,6 +87,7 @@ export type ControllerEndpoint = Partial<{
|
||||||
getPlaylistList: (args: PlaylistListArgs) => Promise<PlaylistListResponse>;
|
getPlaylistList: (args: PlaylistListArgs) => Promise<PlaylistListResponse>;
|
||||||
getPlaylistSongList: (args: PlaylistSongListArgs) => Promise<SongListResponse>;
|
getPlaylistSongList: (args: PlaylistSongListArgs) => Promise<SongListResponse>;
|
||||||
getRandomSongList: (args: RandomSongListArgs) => Promise<SongListResponse>;
|
getRandomSongList: (args: RandomSongListArgs) => Promise<SongListResponse>;
|
||||||
|
getServerInfo: (args: ServerInfoArgs) => Promise<ServerInfo>;
|
||||||
getSongDetail: (args: SongDetailArgs) => Promise<SongDetailResponse>;
|
getSongDetail: (args: SongDetailArgs) => Promise<SongDetailResponse>;
|
||||||
getSongList: (args: SongListArgs) => Promise<SongListResponse>;
|
getSongList: (args: SongListArgs) => Promise<SongListResponse>;
|
||||||
getTopSongs: (args: TopSongListArgs) => Promise<TopSongListResponse>;
|
getTopSongs: (args: TopSongListArgs) => Promise<TopSongListResponse>;
|
||||||
|
@ -129,6 +132,7 @@ const endpoints: ApiController = {
|
||||||
getPlaylistList: jfController.getPlaylistList,
|
getPlaylistList: jfController.getPlaylistList,
|
||||||
getPlaylistSongList: jfController.getPlaylistSongList,
|
getPlaylistSongList: jfController.getPlaylistSongList,
|
||||||
getRandomSongList: jfController.getRandomSongList,
|
getRandomSongList: jfController.getRandomSongList,
|
||||||
|
getServerInfo: jfController.getServerInfo,
|
||||||
getSongDetail: jfController.getSongDetail,
|
getSongDetail: jfController.getSongDetail,
|
||||||
getSongList: jfController.getSongList,
|
getSongList: jfController.getSongList,
|
||||||
getTopSongs: jfController.getTopSongList,
|
getTopSongs: jfController.getTopSongList,
|
||||||
|
@ -165,6 +169,7 @@ const endpoints: ApiController = {
|
||||||
getPlaylistList: ndController.getPlaylistList,
|
getPlaylistList: ndController.getPlaylistList,
|
||||||
getPlaylistSongList: ndController.getPlaylistSongList,
|
getPlaylistSongList: ndController.getPlaylistSongList,
|
||||||
getRandomSongList: ssController.getRandomSongList,
|
getRandomSongList: ssController.getRandomSongList,
|
||||||
|
getServerInfo: ssController.getServerInfo,
|
||||||
getSongDetail: ndController.getSongDetail,
|
getSongDetail: ndController.getSongDetail,
|
||||||
getSongList: ndController.getSongList,
|
getSongList: ndController.getSongList,
|
||||||
getTopSongs: ssController.getTopSongList,
|
getTopSongs: ssController.getTopSongList,
|
||||||
|
@ -198,6 +203,7 @@ const endpoints: ApiController = {
|
||||||
getMusicFolderList: ssController.getMusicFolderList,
|
getMusicFolderList: ssController.getMusicFolderList,
|
||||||
getPlaylistDetail: undefined,
|
getPlaylistDetail: undefined,
|
||||||
getPlaylistList: undefined,
|
getPlaylistList: undefined,
|
||||||
|
getServerInfo: ssController.getServerInfo,
|
||||||
getSongDetail: undefined,
|
getSongDetail: undefined,
|
||||||
getSongList: undefined,
|
getSongList: undefined,
|
||||||
getTopSongs: ssController.getTopSongList,
|
getTopSongs: ssController.getTopSongList,
|
||||||
|
@ -481,6 +487,15 @@ const getLyrics = async (args: LyricsArgs) => {
|
||||||
)?.(args);
|
)?.(args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getServerInfo = async (args: ServerInfoArgs) => {
|
||||||
|
return (
|
||||||
|
apiController(
|
||||||
|
'getServerInfo',
|
||||||
|
args.apiClientProps.server?.type,
|
||||||
|
) as ControllerEndpoint['getServerInfo']
|
||||||
|
)?.(args);
|
||||||
|
};
|
||||||
|
|
||||||
export const controller = {
|
export const controller = {
|
||||||
addToPlaylist,
|
addToPlaylist,
|
||||||
authenticate,
|
authenticate,
|
||||||
|
@ -500,6 +515,7 @@ export const controller = {
|
||||||
getPlaylistList,
|
getPlaylistList,
|
||||||
getPlaylistSongList,
|
getPlaylistSongList,
|
||||||
getRandomSongList,
|
getRandomSongList,
|
||||||
|
getServerInfo,
|
||||||
getSongDetail,
|
getSongDetail,
|
||||||
getSongList,
|
getSongList,
|
||||||
getTopSongList,
|
getTopSongList,
|
||||||
|
|
|
@ -150,6 +150,14 @@ export const contract = c.router({
|
||||||
400: jfType._response.error,
|
400: jfType._response.error,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
getServerInfo: {
|
||||||
|
method: 'GET',
|
||||||
|
path: 'system/info',
|
||||||
|
responses: {
|
||||||
|
200: jfType._response.serverInfo,
|
||||||
|
400: jfType._response.error,
|
||||||
|
},
|
||||||
|
},
|
||||||
getSimilarArtistList: {
|
getSimilarArtistList: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: 'artists/:id/similar',
|
path: 'artists/:id/similar',
|
||||||
|
|
|
@ -49,6 +49,8 @@ import {
|
||||||
genreListSortMap,
|
genreListSortMap,
|
||||||
SongDetailArgs,
|
SongDetailArgs,
|
||||||
SongDetailResponse,
|
SongDetailResponse,
|
||||||
|
ServerInfo,
|
||||||
|
ServerInfoArgs,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { jfApiClient } from '/@/renderer/api/jellyfin/jellyfin-api';
|
import { jfApiClient } from '/@/renderer/api/jellyfin/jellyfin-api';
|
||||||
import { jfNormalize } from './jellyfin-normalize';
|
import { jfNormalize } from './jellyfin-normalize';
|
||||||
|
@ -946,6 +948,18 @@ const getSongDetail = async (args: SongDetailArgs): Promise<SongDetailResponse>
|
||||||
return jfNormalize.song(res.body, apiClientProps.server, '');
|
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 = {
|
export const jfController = {
|
||||||
addToPlaylist,
|
addToPlaylist,
|
||||||
authenticate,
|
authenticate,
|
||||||
|
@ -965,6 +979,7 @@ export const jfController = {
|
||||||
getPlaylistList,
|
getPlaylistList,
|
||||||
getPlaylistSongList,
|
getPlaylistSongList,
|
||||||
getRandomSongList,
|
getRandomSongList,
|
||||||
|
getServerInfo,
|
||||||
getSongDetail,
|
getSongDetail,
|
||||||
getSongList,
|
getSongList,
|
||||||
getTopSongList,
|
getTopSongList,
|
||||||
|
|
|
@ -654,6 +654,10 @@ const lyrics = z.object({
|
||||||
Lyrics: z.array(lyricText),
|
Lyrics: z.array(lyricText),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const serverInfo = z.object({
|
||||||
|
Version: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
export const jfType = {
|
export const jfType = {
|
||||||
_enum: {
|
_enum: {
|
||||||
albumArtistList: albumArtistListSort,
|
albumArtistList: albumArtistListSort,
|
||||||
|
@ -707,6 +711,7 @@ export const jfType = {
|
||||||
removeFromPlaylist,
|
removeFromPlaylist,
|
||||||
scrobble,
|
scrobble,
|
||||||
search,
|
search,
|
||||||
|
serverInfo,
|
||||||
song,
|
song,
|
||||||
songList,
|
songList,
|
||||||
topSongsList,
|
topSongsList,
|
||||||
|
|
|
@ -50,6 +50,13 @@ export const contract = c.router({
|
||||||
200: ssType._response.randomSongList,
|
200: ssType._response.randomSongList,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
getServerInfo: {
|
||||||
|
method: 'GET',
|
||||||
|
path: 'getOpenSubsonicExtensions.view',
|
||||||
|
responses: {
|
||||||
|
200: ssType._response.serverInfo,
|
||||||
|
},
|
||||||
|
},
|
||||||
getTopSongsList: {
|
getTopSongsList: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: 'getTopSongs.view',
|
path: 'getTopSongs.view',
|
||||||
|
@ -58,6 +65,13 @@ export const contract = c.router({
|
||||||
200: ssType._response.topSongsList,
|
200: ssType._response.topSongsList,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ping: {
|
||||||
|
method: 'GET',
|
||||||
|
path: 'ping.view',
|
||||||
|
responses: {
|
||||||
|
200: ssType._response.ping,
|
||||||
|
},
|
||||||
|
},
|
||||||
removeFavorite: {
|
removeFavorite: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: 'unstar.view',
|
path: 'unstar.view',
|
||||||
|
|
|
@ -21,6 +21,8 @@ import {
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
RandomSongListResponse,
|
RandomSongListResponse,
|
||||||
RandomSongListArgs,
|
RandomSongListArgs,
|
||||||
|
ServerInfo,
|
||||||
|
ServerInfoArgs,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { randomString } from '/@/renderer/utils';
|
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 = {
|
export const ssController = {
|
||||||
authenticate,
|
authenticate,
|
||||||
createFavorite,
|
createFavorite,
|
||||||
getArtistInfo,
|
getArtistInfo,
|
||||||
getMusicFolderList,
|
getMusicFolderList,
|
||||||
getRandomSongList,
|
getRandomSongList,
|
||||||
|
getServerInfo,
|
||||||
getTopSongList,
|
getTopSongList,
|
||||||
removeFavorite,
|
removeFavorite,
|
||||||
scrobble,
|
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 = {
|
export const ssType = {
|
||||||
_parameters: {
|
_parameters: {
|
||||||
albumList: albumListParameters,
|
albumList: albumListParameters,
|
||||||
|
@ -229,10 +244,12 @@ export const ssType = {
|
||||||
baseResponse,
|
baseResponse,
|
||||||
createFavorite,
|
createFavorite,
|
||||||
musicFolderList,
|
musicFolderList,
|
||||||
|
ping,
|
||||||
randomSongList,
|
randomSongList,
|
||||||
removeFavorite,
|
removeFavorite,
|
||||||
scrobble,
|
scrobble,
|
||||||
search3,
|
search3,
|
||||||
|
serverInfo,
|
||||||
setRating,
|
setRating,
|
||||||
song,
|
song,
|
||||||
topSongsList,
|
topSongsList,
|
||||||
|
|
|
@ -1139,3 +1139,17 @@ export type FontData = {
|
||||||
postscriptName: string;
|
postscriptName: string;
|
||||||
style: 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 '@ag-grid-community/styles/ag-grid.css';
|
||||||
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
|
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
|
import { useServerVersion } from '/@/renderer/hooks/use-server-version';
|
||||||
|
|
||||||
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
|
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ export const App = () => {
|
||||||
const remoteSettings = useRemoteSettings();
|
const remoteSettings = useRemoteSettings();
|
||||||
const textStyleRef = useRef<HTMLStyleElement>();
|
const textStyleRef = useRef<HTMLStyleElement>();
|
||||||
useDiscordRpc();
|
useDiscordRpc();
|
||||||
|
useServerVersion();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (type === FontType.SYSTEM && system) {
|
if (type === FontType.SYSTEM && system) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
InternetProviderLyricResponse,
|
InternetProviderLyricResponse,
|
||||||
FullLyricsMetadata,
|
FullLyricsMetadata,
|
||||||
LyricGetQuery,
|
LyricGetQuery,
|
||||||
|
SubsonicExtensions,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
import { getServerById, useLyricsSettings } from '/@/renderer/store';
|
import { getServerById, useLyricsSettings } from '/@/renderer/store';
|
||||||
|
@ -93,16 +94,6 @@ export const useSongLyricsBySong = (
|
||||||
if (!server) throw new Error('Server not found');
|
if (!server) throw new Error('Server not found');
|
||||||
if (!song) return null;
|
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) {
|
if (server.type === ServerType.JELLYFIN) {
|
||||||
const jfLyrics = await api.controller
|
const jfLyrics = await api.controller
|
||||||
.getLyrics({
|
.getLyrics({
|
||||||
|
@ -120,6 +111,16 @@ export const useSongLyricsBySong = (
|
||||||
source: server?.name ?? 'music server',
|
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) {
|
if (fetch) {
|
||||||
|
|
|
@ -12,6 +12,8 @@ import { useAuthStoreActions } from '/@/renderer/store';
|
||||||
import { ServerListItem, ServerType } from '/@/renderer/types';
|
import { ServerListItem, ServerType } from '/@/renderer/types';
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import i18n from '/@/i18n/i18n';
|
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;
|
const localSettings = isElectron() ? window.electron.localSettings : null;
|
||||||
|
|
||||||
|
@ -111,6 +113,8 @@ export const EditServerForm = ({ isUpdate, password, server, onCancel }: EditSer
|
||||||
localSettings.passwordRemove(server.id);
|
localSettings.passwordRemove(server.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({ queryKey: queryKeys.server.root(server.id) });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return toast.error({ message: err?.message });
|
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 = {
|
export type ServerListItem = {
|
||||||
credential: string;
|
credential: string;
|
||||||
|
features?: Record<string, number[]>;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
ndCredential?: string;
|
ndCredential?: string;
|
||||||
|
@ -70,6 +71,7 @@ export type ServerListItem = {
|
||||||
url: string;
|
url: string;
|
||||||
userId: string | null;
|
userId: string | null;
|
||||||
username: string;
|
username: string;
|
||||||
|
version?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum PlayerStatus {
|
export enum PlayerStatus {
|
||||||
|
|
Reference in a new issue