Add random song list query
This commit is contained in:
parent
41a251c2ac
commit
de50002ea7
7 changed files with 138 additions and 3 deletions
|
@ -48,7 +48,7 @@ import type {
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { ServerType } from '/@/renderer/types';
|
import { ServerType } from '/@/renderer/types';
|
||||||
import { DeletePlaylistResponse } from './types';
|
import { DeletePlaylistResponse, RandomSongListArgs } from './types';
|
||||||
import { ndController } from '/@/renderer/api/navidrome/navidrome-controller';
|
import { ndController } from '/@/renderer/api/navidrome/navidrome-controller';
|
||||||
import { ssController } from '/@/renderer/api/subsonic/subsonic-controller';
|
import { ssController } from '/@/renderer/api/subsonic/subsonic-controller';
|
||||||
import { jfController } from '/@/renderer/api/jellyfin/jellyfin-controller';
|
import { jfController } from '/@/renderer/api/jellyfin/jellyfin-controller';
|
||||||
|
@ -80,6 +80,7 @@ export type ControllerEndpoint = Partial<{
|
||||||
getPlaylistDetail: (args: PlaylistDetailArgs) => Promise<PlaylistDetailResponse>;
|
getPlaylistDetail: (args: PlaylistDetailArgs) => Promise<PlaylistDetailResponse>;
|
||||||
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>;
|
||||||
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>;
|
||||||
|
@ -122,6 +123,7 @@ const endpoints: ApiController = {
|
||||||
getPlaylistDetail: jfController.getPlaylistDetail,
|
getPlaylistDetail: jfController.getPlaylistDetail,
|
||||||
getPlaylistList: jfController.getPlaylistList,
|
getPlaylistList: jfController.getPlaylistList,
|
||||||
getPlaylistSongList: jfController.getPlaylistSongList,
|
getPlaylistSongList: jfController.getPlaylistSongList,
|
||||||
|
getRandomSongList: jfController.getRandomSongList,
|
||||||
getSongDetail: undefined,
|
getSongDetail: undefined,
|
||||||
getSongList: jfController.getSongList,
|
getSongList: jfController.getSongList,
|
||||||
getTopSongs: jfController.getTopSongList,
|
getTopSongs: jfController.getTopSongList,
|
||||||
|
@ -156,6 +158,7 @@ const endpoints: ApiController = {
|
||||||
getPlaylistDetail: ndController.getPlaylistDetail,
|
getPlaylistDetail: ndController.getPlaylistDetail,
|
||||||
getPlaylistList: ndController.getPlaylistList,
|
getPlaylistList: ndController.getPlaylistList,
|
||||||
getPlaylistSongList: ndController.getPlaylistSongList,
|
getPlaylistSongList: ndController.getPlaylistSongList,
|
||||||
|
getRandomSongList: ssController.getRandomSongList,
|
||||||
getSongDetail: ndController.getSongDetail,
|
getSongDetail: ndController.getSongDetail,
|
||||||
getSongList: ndController.getSongList,
|
getSongList: ndController.getSongList,
|
||||||
getTopSongs: ssController.getTopSongList,
|
getTopSongs: ssController.getTopSongList,
|
||||||
|
@ -436,6 +439,15 @@ const search = async (args: SearchArgs) => {
|
||||||
)?.(args);
|
)?.(args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRandomSongList = async (args: RandomSongListArgs) => {
|
||||||
|
return (
|
||||||
|
apiController(
|
||||||
|
'getRandomSongList',
|
||||||
|
args.apiClientProps.server?.type,
|
||||||
|
) as ControllerEndpoint['getRandomSongList']
|
||||||
|
)?.(args);
|
||||||
|
};
|
||||||
|
|
||||||
export const controller = {
|
export const controller = {
|
||||||
addToPlaylist,
|
addToPlaylist,
|
||||||
authenticate,
|
authenticate,
|
||||||
|
@ -453,6 +465,7 @@ export const controller = {
|
||||||
getPlaylistDetail,
|
getPlaylistDetail,
|
||||||
getPlaylistList,
|
getPlaylistList,
|
||||||
getPlaylistSongList,
|
getPlaylistSongList,
|
||||||
|
getRandomSongList,
|
||||||
getSongDetail,
|
getSongDetail,
|
||||||
getSongList,
|
getSongList,
|
||||||
getTopSongList,
|
getTopSongList,
|
||||||
|
|
|
@ -42,12 +42,15 @@ import {
|
||||||
PlaylistListResponse,
|
PlaylistListResponse,
|
||||||
SearchArgs,
|
SearchArgs,
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
|
RandomSongListResponse,
|
||||||
|
RandomSongListArgs,
|
||||||
} 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';
|
||||||
import { jfType } from '/@/renderer/api/jellyfin/jellyfin-types';
|
import { jfType } from '/@/renderer/api/jellyfin/jellyfin-types';
|
||||||
import packageJson from '../../../../package.json';
|
import packageJson from '../../../../package.json';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { JFSongListSort, JFSortOrder } from '/@/renderer/api/jellyfin.types';
|
||||||
|
|
||||||
const formatCommaDelimitedString = (value: string[]) => {
|
const formatCommaDelimitedString = (value: string[]) => {
|
||||||
return value.join(',');
|
return value.join(',');
|
||||||
|
@ -798,6 +801,50 @@ const search = async (args: SearchArgs): Promise<SearchResponse> => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRandomSongList = async (args: RandomSongListArgs): Promise<RandomSongListResponse> => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
if (!apiClientProps.server?.userId) {
|
||||||
|
throw new Error('No userId found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const yearsGroup = [];
|
||||||
|
if (query.minYear && query.maxYear) {
|
||||||
|
for (let i = Number(query.minYear); i <= Number(query.maxYear); i += 1) {
|
||||||
|
yearsGroup.push(String(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const yearsFilter = yearsGroup.length ? formatCommaDelimitedString(yearsGroup) : undefined;
|
||||||
|
|
||||||
|
const res = await jfApiClient(apiClientProps).getSongList({
|
||||||
|
params: {
|
||||||
|
userId: apiClientProps.server?.userId,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
Fields: 'Genres, DateCreated, MediaSources, ParentId',
|
||||||
|
GenreIds: query.genre ? query.genre : undefined,
|
||||||
|
IncludeItemTypes: 'Audio',
|
||||||
|
Limit: query.limit,
|
||||||
|
ParentId: query.musicFolderId,
|
||||||
|
Recursive: true,
|
||||||
|
SortBy: JFSongListSort.RANDOM,
|
||||||
|
SortOrder: JFSortOrder.ASC,
|
||||||
|
StartIndex: 0,
|
||||||
|
Years: yearsFilter,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('Failed to get random songs');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
|
||||||
|
startIndex: 0,
|
||||||
|
totalRecordCount: res.body.Items.length || 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
export const jfController = {
|
export const jfController = {
|
||||||
addToPlaylist,
|
addToPlaylist,
|
||||||
authenticate,
|
authenticate,
|
||||||
|
@ -815,6 +862,7 @@ export const jfController = {
|
||||||
getPlaylistDetail,
|
getPlaylistDetail,
|
||||||
getPlaylistList,
|
getPlaylistList,
|
||||||
getPlaylistSongList,
|
getPlaylistSongList,
|
||||||
|
getRandomSongList,
|
||||||
getSongList,
|
getSongList,
|
||||||
getTopSongList,
|
getTopSongList,
|
||||||
removeFromPlaylist,
|
removeFromPlaylist,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
||||||
TopSongListQuery,
|
TopSongListQuery,
|
||||||
SearchQuery,
|
SearchQuery,
|
||||||
SongDetailQuery,
|
SongDetailQuery,
|
||||||
|
RandomSongListQuery,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export const queryKeys: Record<
|
export const queryKeys: Record<
|
||||||
|
@ -76,8 +77,8 @@ export const queryKeys: Record<
|
||||||
return [serverId, 'playlists', 'list'] as const;
|
return [serverId, 'playlists', 'list'] as const;
|
||||||
},
|
},
|
||||||
root: (serverId: string) => [serverId, 'playlists'] as const,
|
root: (serverId: string) => [serverId, 'playlists'] as const,
|
||||||
songList: (serverId: string, id: string, query?: PlaylistSongListQuery) => {
|
songList: (serverId: string, id?: string, query?: PlaylistSongListQuery) => {
|
||||||
if (query) return [serverId, 'playlists', id, 'songList', query] as const;
|
if (query && id) return [serverId, 'playlists', id, 'songList', query] as const;
|
||||||
if (id) return [serverId, 'playlists', id, 'songList'] as const;
|
if (id) return [serverId, 'playlists', id, 'songList'] as const;
|
||||||
return [serverId, 'playlists', 'songList'] as const;
|
return [serverId, 'playlists', 'songList'] as const;
|
||||||
},
|
},
|
||||||
|
@ -101,6 +102,10 @@ export const queryKeys: Record<
|
||||||
if (query) return [serverId, 'songs', 'list', query] as const;
|
if (query) return [serverId, 'songs', 'list', query] as const;
|
||||||
return [serverId, 'songs', 'list'] as const;
|
return [serverId, 'songs', 'list'] as const;
|
||||||
},
|
},
|
||||||
|
randomSongList: (serverId: string, query?: RandomSongListQuery) => {
|
||||||
|
if (query) return [serverId, 'songs', 'randomSongList', query] as const;
|
||||||
|
return [serverId, 'songs', 'randomSongList'] as const;
|
||||||
|
},
|
||||||
root: (serverId: string) => [serverId, 'songs'] as const,
|
root: (serverId: string) => [serverId, 'songs'] as const,
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
|
|
|
@ -41,6 +41,14 @@ export const contract = c.router({
|
||||||
200: ssType._response.musicFolderList,
|
200: ssType._response.musicFolderList,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
getRandomSongList: {
|
||||||
|
method: 'GET',
|
||||||
|
path: 'getRandomSongs.view',
|
||||||
|
query: ssType._parameters.randomSongList,
|
||||||
|
responses: {
|
||||||
|
200: ssType._response.randomSongList,
|
||||||
|
},
|
||||||
|
},
|
||||||
getTopSongsList: {
|
getTopSongsList: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: 'getTopSongs.view',
|
path: 'getTopSongs.view',
|
||||||
|
|
|
@ -19,6 +19,8 @@ import {
|
||||||
TopSongListArgs,
|
TopSongListArgs,
|
||||||
SearchArgs,
|
SearchArgs,
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
|
RandomSongListResponse,
|
||||||
|
RandomSongListArgs,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { randomString } from '/@/renderer/utils';
|
import { randomString } from '/@/renderer/utils';
|
||||||
|
|
||||||
|
@ -339,11 +341,40 @@ const search3 = async (args: SearchArgs): Promise<SearchResponse> => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRandomSongList = async (args: RandomSongListArgs): Promise<RandomSongListResponse> => {
|
||||||
|
const { query, apiClientProps } = args;
|
||||||
|
|
||||||
|
const res = await ssApiClient(apiClientProps).getRandomSongList({
|
||||||
|
query: {
|
||||||
|
fromYear: query.minYear,
|
||||||
|
genre: query.genre,
|
||||||
|
musicFolderId: query.musicFolderId,
|
||||||
|
size: query.limit,
|
||||||
|
toYear: query.maxYear,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('Failed to get random songs');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('res', res);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: res.body.randomSongs?.song?.map((song) =>
|
||||||
|
ssNormalize.song(song, apiClientProps.server, ''),
|
||||||
|
),
|
||||||
|
startIndex: 0,
|
||||||
|
totalRecordCount: res.body.randomSongs?.song?.length || 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const ssController = {
|
export const ssController = {
|
||||||
authenticate,
|
authenticate,
|
||||||
createFavorite,
|
createFavorite,
|
||||||
getArtistInfo,
|
getArtistInfo,
|
||||||
getMusicFolderList,
|
getMusicFolderList,
|
||||||
|
getRandomSongList,
|
||||||
getTopSongList,
|
getTopSongList,
|
||||||
removeFavorite,
|
removeFavorite,
|
||||||
scrobble,
|
scrobble,
|
||||||
|
|
|
@ -192,12 +192,27 @@ const search3Parameters = z.object({
|
||||||
songOffset: z.number().optional(),
|
songOffset: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const randomSongListParameters = z.object({
|
||||||
|
fromYear: z.number().optional(),
|
||||||
|
genre: z.string().optional(),
|
||||||
|
musicFolderId: z.string().optional(),
|
||||||
|
size: z.number().optional(),
|
||||||
|
toYear: z.number().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const randomSongList = z.object({
|
||||||
|
randomSongs: z.object({
|
||||||
|
song: z.array(song),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export const ssType = {
|
export const ssType = {
|
||||||
_parameters: {
|
_parameters: {
|
||||||
albumList: albumListParameters,
|
albumList: albumListParameters,
|
||||||
artistInfo: artistInfoParameters,
|
artistInfo: artistInfoParameters,
|
||||||
authenticate: authenticateParameters,
|
authenticate: authenticateParameters,
|
||||||
createFavorite: createFavoriteParameters,
|
createFavorite: createFavoriteParameters,
|
||||||
|
randomSongList: randomSongListParameters,
|
||||||
removeFavorite: removeFavoriteParameters,
|
removeFavorite: removeFavoriteParameters,
|
||||||
scrobble: scrobbleParameters,
|
scrobble: scrobbleParameters,
|
||||||
search3: search3Parameters,
|
search3: search3Parameters,
|
||||||
|
@ -214,6 +229,7 @@ export const ssType = {
|
||||||
baseResponse,
|
baseResponse,
|
||||||
createFavorite,
|
createFavorite,
|
||||||
musicFolderList,
|
musicFolderList,
|
||||||
|
randomSongList,
|
||||||
removeFavorite,
|
removeFavorite,
|
||||||
scrobble,
|
scrobble,
|
||||||
search3,
|
search3,
|
||||||
|
|
|
@ -1002,6 +1002,20 @@ export type SearchResponse = {
|
||||||
songs: Song[];
|
songs: Song[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RandomSongListQuery = {
|
||||||
|
genre?: string;
|
||||||
|
limit?: number;
|
||||||
|
maxYear?: number;
|
||||||
|
minYear?: number;
|
||||||
|
musicFolderId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RandomSongListArgs = {
|
||||||
|
query: RandomSongListQuery;
|
||||||
|
} & BaseEndpointArgs;
|
||||||
|
|
||||||
|
export type RandomSongListResponse = SongListResponse;
|
||||||
|
|
||||||
export const instanceOfCancellationError = (error: any) => {
|
export const instanceOfCancellationError = (error: any) => {
|
||||||
return 'revert' in error;
|
return 'revert' in error;
|
||||||
};
|
};
|
||||||
|
|
Reference in a new issue