Add random song list query

This commit is contained in:
jeffvli 2023-05-21 07:30:28 -07:00
parent 41a251c2ac
commit de50002ea7
7 changed files with 138 additions and 3 deletions

View file

@ -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,

View file

@ -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,

View file

@ -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: {

View file

@ -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',

View file

@ -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,

View file

@ -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,

View file

@ -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;
}; };