From 087ea44737f6de13b31724b23ec9d0f89d705f0a Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:44:10 -0700 Subject: [PATCH] [enhancement]: use jellyfin 10.9.0 lyrics --- src/renderer/api/jellyfin/jellyfin-api.ts | 2 +- .../api/jellyfin/jellyfin-controller.ts | 10 ++-- .../api/navidrome/navidrome-controller.ts | 37 +++----------- src/renderer/api/utils.ts | 51 +++++++++++++++++++ 4 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/renderer/api/jellyfin/jellyfin-api.ts b/src/renderer/api/jellyfin/jellyfin-api.ts index 87b16456..2667dace 100644 --- a/src/renderer/api/jellyfin/jellyfin-api.ts +++ b/src/renderer/api/jellyfin/jellyfin-api.ts @@ -204,7 +204,7 @@ export const contract = c.router({ }, getSongLyrics: { method: 'GET', - path: 'users/:userId/Items/:id/Lyrics', + path: 'audio/:id/Lyrics', responses: { 200: jfType._response.lyrics, 404: jfType._response.error, diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index e480deda..2958dad0 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -61,7 +61,8 @@ import packageJson from '../../../../package.json'; import { z } from 'zod'; import { JFSongListSort, JFSortOrder } from '/@/renderer/api/jellyfin.types'; import isElectron from 'is-electron'; -import { ServerFeatures } from '/@/renderer/api/features-types'; +import { ServerFeature } from '/@/renderer/api/features-types'; +import { VersionInfo, getFeatures } from '/@/renderer/api/utils'; const formatCommaDelimitedString = (value: string[]) => { return value.join(','); @@ -937,7 +938,6 @@ const getLyrics = async (args: LyricsArgs): Promise => { const res = await jfApiClient(apiClientProps).getSongLyrics({ params: { id: query.songId, - userId: apiClientProps.server?.userId, }, }); @@ -969,6 +969,8 @@ const getSongDetail = async (args: SongDetailArgs): Promise return jfNormalize.song(res.body, apiClientProps.server, ''); }; +const VERSION_INFO: VersionInfo = [['10.9.0', { [ServerFeature.LYRICS_SINGLE_STRUCTURED]: [1] }]]; + const getServerInfo = async (args: ServerInfoArgs): Promise => { const { apiClientProps } = args; @@ -978,9 +980,7 @@ const getServerInfo = async (args: ServerInfoArgs): Promise => { throw new Error('Failed to get server info'); } - const features: ServerFeatures = { - lyricsSingleStructured: true, - }; + const features = getFeatures(VERSION_INFO, res.body.Version); return { features, diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 232df3d3..a74c2d45 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -2,8 +2,6 @@ import { ndApiClient } from '/@/renderer/api/navidrome/navidrome-api'; import { ndNormalize } from '/@/renderer/api/navidrome/navidrome-normalize'; import { ndType } from '/@/renderer/api/navidrome/navidrome-types'; import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api'; -import semverCoerce from 'semver/functions/coerce'; -import semverGte from 'semver/functions/gte'; import { AlbumArtistDetailArgs, AlbumArtistDetailResponse, @@ -52,7 +50,7 @@ import { SimilarSongsArgs, Song, } from '../types'; -import { hasFeature } from '/@/renderer/api/utils'; +import { VersionInfo, getFeatures, hasFeature } from '/@/renderer/api/utils'; import { ServerFeature, ServerFeatures } from '/@/renderer/api/features-types'; import { SubsonicExtensions } from '/@/renderer/api/subsonic/subsonic-types'; import { NDSongListSort } from '/@/renderer/api/navidrome.types'; @@ -486,37 +484,11 @@ const removeFromPlaylist = async ( return null; }; -// The order should be in decreasing version, as the highest version match -// will automatically consider all lower versions matched -const VERSION_INFO: Array<[string, Record]> = [ +const VERSION_INFO: VersionInfo = [ ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }], ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }], ]; -const getFeatures = (version: string): Record => { - const cleanVersion = semverCoerce(version); - const features: Record = {}; - let matched = cleanVersion === null; - - for (const [version, supportedFeatures] of VERSION_INFO) { - if (!matched) { - matched = semverGte(cleanVersion!, version); - } - - if (matched) { - for (const [feature, feat] of Object.entries(supportedFeatures)) { - if (feature in features) { - features[feature].push(...feat); - } else { - features[feature] = feat; - } - } - } - } - - return features; -}; - const getServerInfo = async (args: ServerInfoArgs): Promise => { const { apiClientProps } = args; @@ -527,7 +499,10 @@ const getServerInfo = async (args: ServerInfoArgs): Promise => { throw new Error('Failed to ping server'); } - const navidromeFeatures: Record = getFeatures(ping.body.serverVersion!); + const navidromeFeatures: Record = getFeatures( + VERSION_INFO, + ping.body.serverVersion!, + ); if (ping.body.openSubsonic) { const res = await ssApiClient(apiClientProps).getServerInfo(); diff --git a/src/renderer/api/utils.ts b/src/renderer/api/utils.ts index c7ef7b58..3034d5c4 100644 --- a/src/renderer/api/utils.ts +++ b/src/renderer/api/utils.ts @@ -1,4 +1,6 @@ import { AxiosHeaders } from 'axios'; +import semverCoerce from 'semver/functions/coerce'; +import semverGte from 'semver/functions/gte'; import { z } from 'zod'; import { toast } from '/@/renderer/components'; import { useAuthStore } from '/@/renderer/store'; @@ -48,4 +50,53 @@ export const hasFeature = (server: ServerListItem | null, feature: ServerFeature return server.features[feature] ?? false; }; +export type VersionInfo = ReadonlyArray<[string, Record]>; + +/** + * Returns the available server features given the version string. + * @param versionInfo a list, in DECREASING VERSION order, of the features supported by the server. + * The first version match will automatically consider the rest matched. + * @example + * ``` + * // The CORRECT way to order + * const VERSION_INFO: VersionInfo = [ + * ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }], + * ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }], + * ]; + * // INCORRECT way to order + * const VERSION_INFO: VersionInfo = [ + * ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }], + * ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }], + * ]; + * ``` + * @param version the version string (SemVer) + * @returns a Record containing the matched features (if any) and their versions + */ +export const getFeatures = ( + versionInfo: VersionInfo, + version: string, +): Record => { + const cleanVersion = semverCoerce(version); + const features: Record = {}; + let matched = cleanVersion === null; + + for (const [version, supportedFeatures] of versionInfo) { + if (!matched) { + matched = semverGte(cleanVersion!, version); + } + + if (matched) { + for (const [feature, feat] of Object.entries(supportedFeatures)) { + if (feature in features) { + features[feature].push(...feat); + } else { + features[feature] = [...feat]; + } + } + } + } + + return features; +}; + export const SEPARATOR_STRING = ' ยท ';