mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
restructure files onto electron-vite boilerplate
This commit is contained in:
parent
91ce2cd8a1
commit
1cf587bc8f
457 changed files with 9927 additions and 11705 deletions
|
|
@ -1,10 +1,11 @@
|
|||
import { useAuthStore } from '/@/renderer/store';
|
||||
import { toast } from '/@/renderer/components/toast/index';
|
||||
import type { ServerType, ControllerEndpoint, AuthenticationResponse } from '/@/renderer/api/types';
|
||||
import type { AuthenticationResponse, ControllerEndpoint, ServerType } from '/@/renderer/api/types';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { JellyfinController } from '/@/renderer/api/jellyfin/jellyfin-controller';
|
||||
import { NavidromeController } from '/@/renderer/api/navidrome/navidrome-controller';
|
||||
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
|
||||
import { JellyfinController } from '/@/renderer/api/jellyfin/jellyfin-controller';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { toast } from '/@/renderer/components/toast/index';
|
||||
import { useAuthStore } from '/@/renderer/store';
|
||||
|
||||
type ApiController = {
|
||||
jellyfin: ControllerEndpoint;
|
||||
|
|
|
|||
|
|
@ -1,97 +1,56 @@
|
|||
export type JFBasePaginatedResponse = {
|
||||
StartIndex: number;
|
||||
TotalRecordCount: number;
|
||||
};
|
||||
|
||||
export interface JFMusicFolderListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFMusicFolder[];
|
||||
export enum JFAlbumArtistListSort {
|
||||
ALBUM = 'Album,SortName',
|
||||
DURATION = 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME = 'SortName,Name',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'PremiereDate,AlbumArtist,Album,SortName',
|
||||
}
|
||||
|
||||
export type JFMusicFolderList = JFMusicFolder[];
|
||||
|
||||
export interface JFGenreListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFGenre[];
|
||||
export enum JFAlbumListSort {
|
||||
ALBUM_ARTIST = 'AlbumArtist,SortName',
|
||||
COMMUNITY_RATING = 'CommunityRating,SortName',
|
||||
CRITIC_RATING = 'CriticRating,SortName',
|
||||
NAME = 'SortName',
|
||||
PLAY_COUNT = 'PlayCount',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'ProductionYear,PremiereDate,SortName',
|
||||
}
|
||||
|
||||
export type JFGenreList = JFGenreListResponse;
|
||||
export enum JFArtistListSort {
|
||||
ALBUM = 'Album,SortName',
|
||||
DURATION = 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME = 'SortName,Name',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'PremiereDate,AlbumArtist,Album,SortName',
|
||||
}
|
||||
|
||||
export enum JFCollectionType {
|
||||
MUSIC = 'music',
|
||||
PLAYLISTS = 'playlists',
|
||||
}
|
||||
|
||||
export enum JFExternalType {
|
||||
MUSICBRAINZ = 'MusicBrainz',
|
||||
THEAUDIODB = 'TheAudioDb',
|
||||
}
|
||||
|
||||
export enum JFGenreListSort {
|
||||
NAME = 'SortName',
|
||||
}
|
||||
|
||||
export type JFAlbumArtistDetailResponse = JFAlbumArtist;
|
||||
|
||||
export type JFAlbumArtistDetail = JFAlbumArtistDetailResponse;
|
||||
|
||||
export interface JFAlbumArtistListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFAlbumArtist[];
|
||||
export enum JFImageType {
|
||||
LOGO = 'Logo',
|
||||
PRIMARY = 'Primary',
|
||||
}
|
||||
|
||||
export type JFAlbumArtistList = {
|
||||
items: JFAlbumArtist[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export interface JFArtistListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFAlbumArtist[];
|
||||
export enum JFItemType {
|
||||
AUDIO = 'Audio',
|
||||
MUSICALBUM = 'MusicAlbum',
|
||||
}
|
||||
|
||||
export type JFArtistList = JFArtistListResponse;
|
||||
|
||||
export interface JFAlbumListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFAlbum[];
|
||||
}
|
||||
|
||||
export type JFAlbumList = {
|
||||
items: JFAlbum[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type JFAlbumDetailResponse = JFAlbum;
|
||||
|
||||
export type JFAlbumDetail = JFAlbum & { songs?: JFSong[] };
|
||||
|
||||
export interface JFSongListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFSong[];
|
||||
}
|
||||
|
||||
export type JFSongList = {
|
||||
items: JFSong[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type JFAddToPlaylistResponse = {
|
||||
added: number;
|
||||
};
|
||||
|
||||
export type JFAddToPlaylistParams = {
|
||||
ids: string[];
|
||||
userId: string;
|
||||
};
|
||||
|
||||
export type JFAddToPlaylist = null;
|
||||
|
||||
export type JFRemoveFromPlaylistResponse = null;
|
||||
|
||||
export type JFRemoveFromPlaylistParams = {
|
||||
entryIds: string[];
|
||||
};
|
||||
|
||||
export type JFRemoveFromPlaylist = null;
|
||||
|
||||
export interface JFPlaylistListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFPlaylist[];
|
||||
}
|
||||
|
||||
export type JFPlaylistList = {
|
||||
items: JFPlaylist[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export enum JFPlaylistListSort {
|
||||
ALBUM_ARTIST = 'AlbumArtist,SortName',
|
||||
DURATION = 'Runtime',
|
||||
|
|
@ -100,124 +59,34 @@ export enum JFPlaylistListSort {
|
|||
SONG_COUNT = 'ChildCount',
|
||||
}
|
||||
|
||||
export type JFPlaylistDetailResponse = JFPlaylist;
|
||||
export enum JFSongListSort {
|
||||
ALBUM = 'Album,SortName',
|
||||
ALBUM_ARTIST = 'AlbumArtist,Album,SortName',
|
||||
ARTIST = 'Artist,Album,SortName',
|
||||
COMMUNITY_RATING = 'CommunityRating,SortName',
|
||||
DURATION = 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME = 'Name',
|
||||
PLAY_COUNT = 'PlayCount,SortName',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RECENTLY_PLAYED = 'DatePlayed,SortName',
|
||||
RELEASE_DATE = 'PremiereDate,AlbumArtist,Album,SortName',
|
||||
}
|
||||
|
||||
export type JFPlaylistDetail = JFPlaylist & { songs?: JFSong[] };
|
||||
export enum JFSortOrder {
|
||||
ASC = 'Ascending',
|
||||
DESC = 'Descending',
|
||||
}
|
||||
|
||||
export type JFPlaylist = {
|
||||
BackdropImageTags: string[];
|
||||
ChannelId: null;
|
||||
ChildCount?: number;
|
||||
DateCreated: string;
|
||||
GenreItems: GenreItem[];
|
||||
Genres: string[];
|
||||
Id: string;
|
||||
ImageBlurHashes: ImageBlurHashes;
|
||||
ImageTags: ImageTags;
|
||||
IsFolder: boolean;
|
||||
LocationType: string;
|
||||
MediaType: string;
|
||||
Name: string;
|
||||
Overview?: string;
|
||||
RunTimeTicks: number;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
UserData: UserData;
|
||||
export type JFAddToPlaylist = null;
|
||||
|
||||
export type JFAddToPlaylistParams = {
|
||||
ids: string[];
|
||||
userId: string;
|
||||
};
|
||||
|
||||
export type JFRequestParams = {
|
||||
albumArtistIds?: string;
|
||||
artistIds?: string;
|
||||
enableImageTypes?: string;
|
||||
enableTotalRecordCount?: boolean;
|
||||
enableUserData?: boolean;
|
||||
excludeItemTypes?: string;
|
||||
fields?: string;
|
||||
imageTypeLimit?: number;
|
||||
includeItemTypes?: string;
|
||||
isFavorite?: boolean;
|
||||
limit?: number;
|
||||
parentId?: string;
|
||||
recursive?: boolean;
|
||||
searchTerm?: string;
|
||||
sortBy?: string;
|
||||
sortOrder?: 'Ascending' | 'Descending';
|
||||
startIndex?: number;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
export type JFMusicFolder = {
|
||||
BackdropImageTags: string[];
|
||||
ChannelId: null;
|
||||
CollectionType: string;
|
||||
Id: string;
|
||||
ImageBlurHashes: ImageBlurHashes;
|
||||
ImageTags: ImageTags;
|
||||
IsFolder: boolean;
|
||||
LocationType: string;
|
||||
Name: string;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
UserData: UserData;
|
||||
};
|
||||
|
||||
export type JFGenre = {
|
||||
BackdropImageTags: any[];
|
||||
ChannelId: null;
|
||||
Id: string;
|
||||
ImageBlurHashes: any;
|
||||
ImageTags: ImageTags;
|
||||
LocationType: string;
|
||||
Name: string;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
};
|
||||
|
||||
export type JFAlbumArtist = {
|
||||
BackdropImageTags: string[];
|
||||
ChannelId: null;
|
||||
DateCreated: string;
|
||||
ExternalUrls: ExternalURL[];
|
||||
GenreItems: GenreItem[];
|
||||
Genres: string[];
|
||||
Id: string;
|
||||
ImageBlurHashes: any;
|
||||
ImageTags: ImageTags;
|
||||
LocationType: string;
|
||||
Name: string;
|
||||
Overview?: string;
|
||||
RunTimeTicks: number;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
UserData: {
|
||||
IsFavorite: boolean;
|
||||
Key: string;
|
||||
PlayCount: number;
|
||||
PlaybackPositionTicks: number;
|
||||
Played: boolean;
|
||||
};
|
||||
} & {
|
||||
similarArtists: {
|
||||
items: JFAlbumArtist[];
|
||||
};
|
||||
};
|
||||
|
||||
export type JFArtist = {
|
||||
BackdropImageTags: string[];
|
||||
ChannelId: null;
|
||||
DateCreated: string;
|
||||
ExternalUrls: ExternalURL[];
|
||||
GenreItems: GenreItem[];
|
||||
Genres: string[];
|
||||
Id: string;
|
||||
ImageBlurHashes: any;
|
||||
ImageTags: string[];
|
||||
LocationType: string;
|
||||
Name: string;
|
||||
Overview?: string;
|
||||
RunTimeTicks: number;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
export type JFAddToPlaylistResponse = {
|
||||
added: number;
|
||||
};
|
||||
|
||||
export type JFAlbum = {
|
||||
|
|
@ -251,6 +120,244 @@ export type JFAlbum = {
|
|||
songs?: JFSong[];
|
||||
};
|
||||
|
||||
export type JFAlbumArtist = {
|
||||
BackdropImageTags: string[];
|
||||
ChannelId: null;
|
||||
DateCreated: string;
|
||||
ExternalUrls: ExternalURL[];
|
||||
GenreItems: GenreItem[];
|
||||
Genres: string[];
|
||||
Id: string;
|
||||
ImageBlurHashes: any;
|
||||
ImageTags: ImageTags;
|
||||
LocationType: string;
|
||||
Name: string;
|
||||
Overview?: string;
|
||||
RunTimeTicks: number;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
UserData: {
|
||||
IsFavorite: boolean;
|
||||
Key: string;
|
||||
PlaybackPositionTicks: number;
|
||||
PlayCount: number;
|
||||
Played: boolean;
|
||||
};
|
||||
} & {
|
||||
similarArtists: {
|
||||
items: JFAlbumArtist[];
|
||||
};
|
||||
};
|
||||
|
||||
export type JFAlbumArtistDetail = JFAlbumArtistDetailResponse;
|
||||
|
||||
export type JFAlbumArtistDetailResponse = JFAlbumArtist;
|
||||
|
||||
export type JFAlbumArtistList = {
|
||||
items: JFAlbumArtist[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type JFAlbumArtistListParams = JFBaseParams &
|
||||
JFPaginationParams & {
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
sortBy?: JFAlbumArtistListSort;
|
||||
years?: string;
|
||||
};
|
||||
|
||||
export interface JFAlbumArtistListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFAlbumArtist[];
|
||||
}
|
||||
|
||||
export type JFAlbumDetail = JFAlbum & { songs?: JFSong[] };
|
||||
|
||||
export type JFAlbumDetailResponse = JFAlbum;
|
||||
|
||||
export type JFAlbumList = {
|
||||
items: JFAlbum[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type JFAlbumListParams = JFBaseParams &
|
||||
JFPaginationParams & {
|
||||
albumArtistIds?: string;
|
||||
artistIds?: string;
|
||||
filters?: string;
|
||||
genreIds?: string;
|
||||
genres?: string;
|
||||
includeItemTypes: 'MusicAlbum';
|
||||
isFavorite?: boolean;
|
||||
searchTerm?: string;
|
||||
sortBy?: JFAlbumListSort;
|
||||
tags?: string;
|
||||
years?: string;
|
||||
};
|
||||
|
||||
export interface JFAlbumListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFAlbum[];
|
||||
}
|
||||
|
||||
export type JFArtist = {
|
||||
BackdropImageTags: string[];
|
||||
ChannelId: null;
|
||||
DateCreated: string;
|
||||
ExternalUrls: ExternalURL[];
|
||||
GenreItems: GenreItem[];
|
||||
Genres: string[];
|
||||
Id: string;
|
||||
ImageBlurHashes: any;
|
||||
ImageTags: string[];
|
||||
LocationType: string;
|
||||
Name: string;
|
||||
Overview?: string;
|
||||
RunTimeTicks: number;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
};
|
||||
|
||||
export type JFArtistList = JFArtistListResponse;
|
||||
|
||||
export type JFArtistListParams = JFBaseParams &
|
||||
JFPaginationParams & {
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
sortBy?: JFArtistListSort;
|
||||
years?: string;
|
||||
};
|
||||
|
||||
export interface JFArtistListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFAlbumArtist[];
|
||||
}
|
||||
|
||||
export interface JFAuthenticate {
|
||||
AccessToken: string;
|
||||
ServerId: string;
|
||||
SessionInfo: SessionInfo;
|
||||
User: User;
|
||||
}
|
||||
|
||||
export type JFBasePaginatedResponse = {
|
||||
StartIndex: number;
|
||||
TotalRecordCount: number;
|
||||
};
|
||||
|
||||
export type JFCreatePlaylist = JFCreatePlaylistResponse;
|
||||
|
||||
export type JFCreatePlaylistResponse = {
|
||||
Id: string;
|
||||
};
|
||||
|
||||
export type JFGenericItem = {
|
||||
Id: string;
|
||||
Name: string;
|
||||
};
|
||||
|
||||
export type JFGenre = {
|
||||
BackdropImageTags: any[];
|
||||
ChannelId: null;
|
||||
Id: string;
|
||||
ImageBlurHashes: any;
|
||||
ImageTags: ImageTags;
|
||||
LocationType: string;
|
||||
Name: string;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
};
|
||||
|
||||
export type JFGenreList = JFGenreListResponse;
|
||||
|
||||
export interface JFGenreListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFGenre[];
|
||||
}
|
||||
|
||||
export type JFMusicFolder = {
|
||||
BackdropImageTags: string[];
|
||||
ChannelId: null;
|
||||
CollectionType: string;
|
||||
Id: string;
|
||||
ImageBlurHashes: ImageBlurHashes;
|
||||
ImageTags: ImageTags;
|
||||
IsFolder: boolean;
|
||||
LocationType: string;
|
||||
Name: string;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
UserData: UserData;
|
||||
};
|
||||
|
||||
export type JFMusicFolderList = JFMusicFolder[];
|
||||
|
||||
export interface JFMusicFolderListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFMusicFolder[];
|
||||
}
|
||||
|
||||
export type JFPlaylist = {
|
||||
BackdropImageTags: string[];
|
||||
ChannelId: null;
|
||||
ChildCount?: number;
|
||||
DateCreated: string;
|
||||
GenreItems: GenreItem[];
|
||||
Genres: string[];
|
||||
Id: string;
|
||||
ImageBlurHashes: ImageBlurHashes;
|
||||
ImageTags: ImageTags;
|
||||
IsFolder: boolean;
|
||||
LocationType: string;
|
||||
MediaType: string;
|
||||
Name: string;
|
||||
Overview?: string;
|
||||
RunTimeTicks: number;
|
||||
ServerId: string;
|
||||
Type: string;
|
||||
UserData: UserData;
|
||||
};
|
||||
|
||||
export type JFPlaylistDetail = JFPlaylist & { songs?: JFSong[] };
|
||||
|
||||
export type JFPlaylistDetailResponse = JFPlaylist;
|
||||
|
||||
export type JFPlaylistList = {
|
||||
items: JFPlaylist[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export interface JFPlaylistListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFPlaylist[];
|
||||
}
|
||||
|
||||
export type JFRemoveFromPlaylist = null;
|
||||
|
||||
export type JFRemoveFromPlaylistParams = {
|
||||
entryIds: string[];
|
||||
};
|
||||
|
||||
export type JFRemoveFromPlaylistResponse = null;
|
||||
|
||||
export type JFRequestParams = {
|
||||
albumArtistIds?: string;
|
||||
artistIds?: string;
|
||||
enableImageTypes?: string;
|
||||
enableTotalRecordCount?: boolean;
|
||||
enableUserData?: boolean;
|
||||
excludeItemTypes?: string;
|
||||
fields?: string;
|
||||
imageTypeLimit?: number;
|
||||
includeItemTypes?: string;
|
||||
isFavorite?: boolean;
|
||||
limit?: number;
|
||||
parentId?: string;
|
||||
recursive?: boolean;
|
||||
searchTerm?: string;
|
||||
sortBy?: string;
|
||||
sortOrder?: 'Ascending' | 'Descending';
|
||||
startIndex?: number;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
export type JFSong = {
|
||||
Album: string;
|
||||
AlbumArtist: string;
|
||||
|
|
@ -285,23 +392,56 @@ export type JFSong = {
|
|||
UserData?: UserData;
|
||||
};
|
||||
|
||||
type ImageBlurHashes = {
|
||||
Backdrop?: any;
|
||||
Logo?: any;
|
||||
Primary?: any;
|
||||
export type JFSongList = {
|
||||
items: JFSong[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
type ImageTags = {
|
||||
Logo?: string;
|
||||
Primary?: string;
|
||||
export type JFSongListParams = JFBaseParams &
|
||||
JFPaginationParams & {
|
||||
albumArtistIds?: string;
|
||||
albumIds?: string;
|
||||
artistIds?: string;
|
||||
contributingArtistIds?: string;
|
||||
filters?: string;
|
||||
genreIds?: string;
|
||||
genres?: string;
|
||||
ids?: string;
|
||||
includeItemTypes: 'Audio';
|
||||
searchTerm?: string;
|
||||
sortBy?: JFSongListSort;
|
||||
years?: string;
|
||||
};
|
||||
|
||||
export interface JFSongListResponse extends JFBasePaginatedResponse {
|
||||
Items: JFSong[];
|
||||
}
|
||||
|
||||
type Capabilities = {
|
||||
PlayableMediaTypes: any[];
|
||||
SupportedCommands: any[];
|
||||
SupportsContentUploading: boolean;
|
||||
SupportsMediaControl: boolean;
|
||||
SupportsPersistentIdentifier: boolean;
|
||||
SupportsSync: boolean;
|
||||
};
|
||||
|
||||
type UserData = {
|
||||
IsFavorite: boolean;
|
||||
Key: string;
|
||||
PlayCount: number;
|
||||
PlaybackPositionTicks: number;
|
||||
Played: boolean;
|
||||
type Configuration = {
|
||||
DisplayCollectionsView: boolean;
|
||||
DisplayMissingEpisodes: boolean;
|
||||
EnableLocalPassword: boolean;
|
||||
EnableNextEpisodeAutoPlay: boolean;
|
||||
GroupedFolders: any[];
|
||||
HidePlayedInLatest: boolean;
|
||||
LatestItemsExcludes: any[];
|
||||
MyMediaExcludes: any[];
|
||||
OrderedViews: any[];
|
||||
PlayDefaultAudioTrack: boolean;
|
||||
RememberAudioSelections: boolean;
|
||||
RememberSubtitleSelections: boolean;
|
||||
SubtitleLanguagePreference: string;
|
||||
SubtitleMode: string;
|
||||
};
|
||||
|
||||
type ExternalURL = {
|
||||
|
|
@ -314,9 +454,32 @@ type GenreItem = {
|
|||
Name: string;
|
||||
};
|
||||
|
||||
export type JFGenericItem = {
|
||||
Id: string;
|
||||
Name: string;
|
||||
type ImageBlurHashes = {
|
||||
Backdrop?: any;
|
||||
Logo?: any;
|
||||
Primary?: any;
|
||||
};
|
||||
|
||||
type ImageTags = {
|
||||
Logo?: string;
|
||||
Primary?: string;
|
||||
};
|
||||
|
||||
type JFBaseParams = {
|
||||
enableImageTypes?: JFImageType[];
|
||||
fields?: string;
|
||||
imageTypeLimit?: number;
|
||||
parentId?: string;
|
||||
recursive?: boolean;
|
||||
searchTerm?: string;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
type JFPaginationParams = {
|
||||
limit?: number;
|
||||
nameStartsWith?: string;
|
||||
sortOrder?: JFSortOrder;
|
||||
startIndex?: number;
|
||||
};
|
||||
|
||||
type MediaSources = {
|
||||
|
|
@ -380,32 +543,53 @@ type MediaStream = {
|
|||
Width?: number;
|
||||
};
|
||||
|
||||
export enum JFExternalType {
|
||||
MUSICBRAINZ = 'MusicBrainz',
|
||||
THEAUDIODB = 'TheAudioDb',
|
||||
}
|
||||
type PlayState = {
|
||||
CanSeek: boolean;
|
||||
IsMuted: boolean;
|
||||
IsPaused: boolean;
|
||||
RepeatMode: string;
|
||||
};
|
||||
|
||||
export enum JFImageType {
|
||||
LOGO = 'Logo',
|
||||
PRIMARY = 'Primary',
|
||||
}
|
||||
|
||||
export enum JFItemType {
|
||||
AUDIO = 'Audio',
|
||||
MUSICALBUM = 'MusicAlbum',
|
||||
}
|
||||
|
||||
export enum JFCollectionType {
|
||||
MUSIC = 'music',
|
||||
PLAYLISTS = 'playlists',
|
||||
}
|
||||
|
||||
export interface JFAuthenticate {
|
||||
AccessToken: string;
|
||||
ServerId: string;
|
||||
SessionInfo: SessionInfo;
|
||||
User: User;
|
||||
}
|
||||
type Policy = {
|
||||
AccessSchedules: any[];
|
||||
AuthenticationProviderId: string;
|
||||
BlockedChannels: any[];
|
||||
BlockedMediaFolders: any[];
|
||||
BlockedTags: any[];
|
||||
BlockUnratedItems: any[];
|
||||
EnableAllChannels: boolean;
|
||||
EnableAllDevices: boolean;
|
||||
EnableAllFolders: boolean;
|
||||
EnableAudioPlaybackTranscoding: boolean;
|
||||
EnableContentDeletion: boolean;
|
||||
EnableContentDeletionFromFolders: any[];
|
||||
EnableContentDownloading: boolean;
|
||||
EnabledChannels: any[];
|
||||
EnabledDevices: any[];
|
||||
EnabledFolders: any[];
|
||||
EnableLiveTvAccess: boolean;
|
||||
EnableLiveTvManagement: boolean;
|
||||
EnableMediaConversion: boolean;
|
||||
EnableMediaPlayback: boolean;
|
||||
EnablePlaybackRemuxing: boolean;
|
||||
EnablePublicSharing: boolean;
|
||||
EnableRemoteAccess: boolean;
|
||||
EnableRemoteControlOfOtherUsers: boolean;
|
||||
EnableSharedDeviceControl: boolean;
|
||||
EnableSyncTranscoding: boolean;
|
||||
EnableUserPreferenceAccess: boolean;
|
||||
EnableVideoPlaybackTranscoding: boolean;
|
||||
ForceRemoteSourceTranscoding: boolean;
|
||||
InvalidLoginAttemptCount: number;
|
||||
IsAdministrator: boolean;
|
||||
IsDisabled: boolean;
|
||||
IsHidden: boolean;
|
||||
LoginAttemptsBeforeLockout: number;
|
||||
MaxActiveSessions: number;
|
||||
PasswordResetProviderId: string;
|
||||
RemoteClientBitrateLimit: number;
|
||||
SyncPlayAccess: string;
|
||||
};
|
||||
|
||||
type SessionInfo = {
|
||||
AdditionalUsers: any[];
|
||||
|
|
@ -421,8 +605,8 @@ type SessionInfo = {
|
|||
LastPlaybackCheckIn: string;
|
||||
NowPlayingQueue: any[];
|
||||
NowPlayingQueueFullItems: any[];
|
||||
PlayState: PlayState;
|
||||
PlayableMediaTypes: any[];
|
||||
PlayState: PlayState;
|
||||
RemoteEndPoint: string;
|
||||
ServerId: string;
|
||||
SupportedCommands: any[];
|
||||
|
|
@ -432,22 +616,6 @@ type SessionInfo = {
|
|||
UserName: string;
|
||||
};
|
||||
|
||||
type Capabilities = {
|
||||
PlayableMediaTypes: any[];
|
||||
SupportedCommands: any[];
|
||||
SupportsContentUploading: boolean;
|
||||
SupportsMediaControl: boolean;
|
||||
SupportsPersistentIdentifier: boolean;
|
||||
SupportsSync: boolean;
|
||||
};
|
||||
|
||||
type PlayState = {
|
||||
CanSeek: boolean;
|
||||
IsMuted: boolean;
|
||||
IsPaused: boolean;
|
||||
RepeatMode: string;
|
||||
};
|
||||
|
||||
type User = {
|
||||
Configuration: Configuration;
|
||||
EnableAutoLogin: boolean;
|
||||
|
|
@ -462,178 +630,10 @@ type User = {
|
|||
ServerId: string;
|
||||
};
|
||||
|
||||
type Configuration = {
|
||||
DisplayCollectionsView: boolean;
|
||||
DisplayMissingEpisodes: boolean;
|
||||
EnableLocalPassword: boolean;
|
||||
EnableNextEpisodeAutoPlay: boolean;
|
||||
GroupedFolders: any[];
|
||||
HidePlayedInLatest: boolean;
|
||||
LatestItemsExcludes: any[];
|
||||
MyMediaExcludes: any[];
|
||||
OrderedViews: any[];
|
||||
PlayDefaultAudioTrack: boolean;
|
||||
RememberAudioSelections: boolean;
|
||||
RememberSubtitleSelections: boolean;
|
||||
SubtitleLanguagePreference: string;
|
||||
SubtitleMode: string;
|
||||
type UserData = {
|
||||
IsFavorite: boolean;
|
||||
Key: string;
|
||||
PlaybackPositionTicks: number;
|
||||
PlayCount: number;
|
||||
Played: boolean;
|
||||
};
|
||||
|
||||
type Policy = {
|
||||
AccessSchedules: any[];
|
||||
AuthenticationProviderId: string;
|
||||
BlockUnratedItems: any[];
|
||||
BlockedChannels: any[];
|
||||
BlockedMediaFolders: any[];
|
||||
BlockedTags: any[];
|
||||
EnableAllChannels: boolean;
|
||||
EnableAllDevices: boolean;
|
||||
EnableAllFolders: boolean;
|
||||
EnableAudioPlaybackTranscoding: boolean;
|
||||
EnableContentDeletion: boolean;
|
||||
EnableContentDeletionFromFolders: any[];
|
||||
EnableContentDownloading: boolean;
|
||||
EnableLiveTvAccess: boolean;
|
||||
EnableLiveTvManagement: boolean;
|
||||
EnableMediaConversion: boolean;
|
||||
EnableMediaPlayback: boolean;
|
||||
EnablePlaybackRemuxing: boolean;
|
||||
EnablePublicSharing: boolean;
|
||||
EnableRemoteAccess: boolean;
|
||||
EnableRemoteControlOfOtherUsers: boolean;
|
||||
EnableSharedDeviceControl: boolean;
|
||||
EnableSyncTranscoding: boolean;
|
||||
EnableUserPreferenceAccess: boolean;
|
||||
EnableVideoPlaybackTranscoding: boolean;
|
||||
EnabledChannels: any[];
|
||||
EnabledDevices: any[];
|
||||
EnabledFolders: any[];
|
||||
ForceRemoteSourceTranscoding: boolean;
|
||||
InvalidLoginAttemptCount: number;
|
||||
IsAdministrator: boolean;
|
||||
IsDisabled: boolean;
|
||||
IsHidden: boolean;
|
||||
LoginAttemptsBeforeLockout: number;
|
||||
MaxActiveSessions: number;
|
||||
PasswordResetProviderId: string;
|
||||
RemoteClientBitrateLimit: number;
|
||||
SyncPlayAccess: string;
|
||||
};
|
||||
|
||||
type JFBaseParams = {
|
||||
enableImageTypes?: JFImageType[];
|
||||
fields?: string;
|
||||
imageTypeLimit?: number;
|
||||
parentId?: string;
|
||||
recursive?: boolean;
|
||||
searchTerm?: string;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
type JFPaginationParams = {
|
||||
limit?: number;
|
||||
nameStartsWith?: string;
|
||||
sortOrder?: JFSortOrder;
|
||||
startIndex?: number;
|
||||
};
|
||||
|
||||
export enum JFSortOrder {
|
||||
ASC = 'Ascending',
|
||||
DESC = 'Descending',
|
||||
}
|
||||
|
||||
export enum JFAlbumListSort {
|
||||
ALBUM_ARTIST = 'AlbumArtist,SortName',
|
||||
COMMUNITY_RATING = 'CommunityRating,SortName',
|
||||
CRITIC_RATING = 'CriticRating,SortName',
|
||||
NAME = 'SortName',
|
||||
PLAY_COUNT = 'PlayCount',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'ProductionYear,PremiereDate,SortName',
|
||||
}
|
||||
|
||||
export type JFAlbumListParams = {
|
||||
albumArtistIds?: string;
|
||||
artistIds?: string;
|
||||
filters?: string;
|
||||
genreIds?: string;
|
||||
genres?: string;
|
||||
includeItemTypes: 'MusicAlbum';
|
||||
isFavorite?: boolean;
|
||||
searchTerm?: string;
|
||||
sortBy?: JFAlbumListSort;
|
||||
tags?: string;
|
||||
years?: string;
|
||||
} & JFBaseParams &
|
||||
JFPaginationParams;
|
||||
|
||||
export enum JFSongListSort {
|
||||
ALBUM = 'Album,SortName',
|
||||
ALBUM_ARTIST = 'AlbumArtist,Album,SortName',
|
||||
ARTIST = 'Artist,Album,SortName',
|
||||
COMMUNITY_RATING = 'CommunityRating,SortName',
|
||||
DURATION = 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME = 'Name',
|
||||
PLAY_COUNT = 'PlayCount,SortName',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RECENTLY_PLAYED = 'DatePlayed,SortName',
|
||||
RELEASE_DATE = 'PremiereDate,AlbumArtist,Album,SortName',
|
||||
}
|
||||
|
||||
export type JFSongListParams = {
|
||||
albumArtistIds?: string;
|
||||
albumIds?: string;
|
||||
artistIds?: string;
|
||||
contributingArtistIds?: string;
|
||||
filters?: string;
|
||||
genreIds?: string;
|
||||
genres?: string;
|
||||
ids?: string;
|
||||
includeItemTypes: 'Audio';
|
||||
searchTerm?: string;
|
||||
sortBy?: JFSongListSort;
|
||||
years?: string;
|
||||
} & JFBaseParams &
|
||||
JFPaginationParams;
|
||||
|
||||
export enum JFAlbumArtistListSort {
|
||||
ALBUM = 'Album,SortName',
|
||||
DURATION = 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME = 'SortName,Name',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'PremiereDate,AlbumArtist,Album,SortName',
|
||||
}
|
||||
|
||||
export type JFAlbumArtistListParams = {
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
sortBy?: JFAlbumArtistListSort;
|
||||
years?: string;
|
||||
} & JFBaseParams &
|
||||
JFPaginationParams;
|
||||
|
||||
export enum JFArtistListSort {
|
||||
ALBUM = 'Album,SortName',
|
||||
DURATION = 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME = 'SortName,Name',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'PremiereDate,AlbumArtist,Album,SortName',
|
||||
}
|
||||
|
||||
export type JFArtistListParams = {
|
||||
filters?: string;
|
||||
genres?: string;
|
||||
sortBy?: JFArtistListSort;
|
||||
years?: string;
|
||||
} & JFBaseParams &
|
||||
JFPaginationParams;
|
||||
|
||||
export type JFCreatePlaylistResponse = {
|
||||
Id: string;
|
||||
};
|
||||
|
||||
export type JFCreatePlaylist = JFCreatePlaylistResponse;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import { useAuthStore } from '/@/renderer/store';
|
||||
import { jfType } from '/@/renderer/api/jellyfin/jellyfin-types';
|
||||
import { initClient, initContract } from '@ts-rest/core';
|
||||
import axios, { AxiosError, AxiosResponse, isAxiosError, Method } from 'axios';
|
||||
import qs from 'qs';
|
||||
import { ServerListItem } from '/@/renderer/api/types';
|
||||
import omitBy from 'lodash/omitBy';
|
||||
import qs from 'qs';
|
||||
import { z } from 'zod';
|
||||
import { authenticationFailure, getClientType } from '/@/renderer/api/utils';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
import packageJson from '../../../../package.json';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { jfType } from '/@/renderer/api/jellyfin/jellyfin-types';
|
||||
import { ServerListItem } from '/@/renderer/api/types';
|
||||
import { authenticationFailure, getClientType } from '/@/renderer/api/utils';
|
||||
import { useAuthStore } from '/@/renderer/store';
|
||||
|
||||
const c = initContract();
|
||||
|
||||
export const contract = c.router({
|
||||
|
|
@ -356,14 +358,14 @@ export const createAuthHeader = (): string => {
|
|||
};
|
||||
|
||||
export const jfApiClient = (args: {
|
||||
server: ServerListItem | null;
|
||||
server: null | ServerListItem;
|
||||
signal?: AbortSignal;
|
||||
url?: string;
|
||||
}) => {
|
||||
const { server, url, signal } = args;
|
||||
const { server, signal, url } = args;
|
||||
|
||||
return initClient(contract, {
|
||||
api: async ({ path, method, headers, body }) => {
|
||||
api: async ({ body, headers, method, path }) => {
|
||||
let baseUrl: string | undefined;
|
||||
let token: string | undefined;
|
||||
|
||||
|
|
@ -395,7 +397,7 @@ export const jfApiClient = (args: {
|
|||
headers: result.headers as any,
|
||||
status: result.status,
|
||||
};
|
||||
} catch (e: Error | AxiosError | any) {
|
||||
} catch (e: any | AxiosError | Error) {
|
||||
if (isAxiosError(e)) {
|
||||
if (e.code === 'ERR_NETWORK') {
|
||||
throw new Error(
|
||||
|
|
|
|||
|
|
@ -1,23 +1,25 @@
|
|||
import chunk from 'lodash/chunk';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { jfNormalize } from './jellyfin-normalize';
|
||||
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
import { JFSongListSort, JFSortOrder } from '/@/renderer/api/jellyfin.types';
|
||||
import { jfApiClient } from '/@/renderer/api/jellyfin/jellyfin-api';
|
||||
import { jfType } from '/@/renderer/api/jellyfin/jellyfin-types';
|
||||
import {
|
||||
albumArtistListSortMap,
|
||||
sortOrderMap,
|
||||
albumListSortMap,
|
||||
songListSortMap,
|
||||
playlistListSortMap,
|
||||
genreListSortMap,
|
||||
Song,
|
||||
Played,
|
||||
ControllerEndpoint,
|
||||
genreListSortMap,
|
||||
LibraryItem,
|
||||
Played,
|
||||
playlistListSortMap,
|
||||
Song,
|
||||
songListSortMap,
|
||||
sortOrderMap,
|
||||
} from '/@/renderer/api/types';
|
||||
import { jfApiClient } from '/@/renderer/api/jellyfin/jellyfin-api';
|
||||
import { jfNormalize } from './jellyfin-normalize';
|
||||
import { jfType } from '/@/renderer/api/jellyfin/jellyfin-types';
|
||||
import { z } from 'zod';
|
||||
import { JFSongListSort, JFSortOrder } from '/@/renderer/api/jellyfin.types';
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
import { VersionInfo, getFeatures, hasFeature } from '/@/renderer/api/utils';
|
||||
import chunk from 'lodash/chunk';
|
||||
import { getFeatures, hasFeature, VersionInfo } from '/@/renderer/api/utils';
|
||||
|
||||
const formatCommaDelimitedString = (value: string[]) => {
|
||||
return value.join(',');
|
||||
|
|
@ -41,7 +43,7 @@ const VERSION_INFO: VersionInfo = [
|
|||
|
||||
export const JellyfinController: ControllerEndpoint = {
|
||||
addToPlaylist: async (args) => {
|
||||
const { query, body, apiClientProps } = args;
|
||||
const { apiClientProps, body, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -89,7 +91,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
createFavorite: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -108,7 +110,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
createPlaylist: async (args) => {
|
||||
const { body, apiClientProps } = args;
|
||||
const { apiClientProps, body } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -132,7 +134,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
deleteFavorite: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -151,7 +153,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
deletePlaylist: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).deletePlaylist({
|
||||
params: {
|
||||
|
|
@ -166,7 +168,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
getAlbumArtistDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -201,7 +203,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
);
|
||||
},
|
||||
getAlbumArtistList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getAlbumArtistList({
|
||||
query: {
|
||||
|
|
@ -236,7 +238,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getAlbumDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -274,7 +276,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
);
|
||||
},
|
||||
getAlbumList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -334,7 +336,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getArtistList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getArtistList({
|
||||
query: {
|
||||
|
|
@ -404,7 +406,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getLyrics: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -453,7 +455,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getPlaylistDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -477,7 +479,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
return jfNormalize.playlist(res.body, apiClientProps.server);
|
||||
},
|
||||
getPlaylistList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -515,7 +517,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getPlaylistSongList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -547,7 +549,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getRandomSongList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -668,7 +670,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
}, []);
|
||||
},
|
||||
getSongDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getSongDetail({
|
||||
params: {
|
||||
|
|
@ -684,7 +686,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
return jfNormalize.song(res.body, apiClientProps.server, '');
|
||||
},
|
||||
getSongList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -864,7 +866,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getTranscodingUrl: (args) => {
|
||||
const { base, format, bitrate } = args.query;
|
||||
const { base, bitrate, format } = args.query;
|
||||
let url = base.replace('transcodingProtocol=hls', 'transcodingProtocol=http');
|
||||
if (format) {
|
||||
url = url.replace('audioCodec=aac', `audioCodec=${format}`);
|
||||
|
|
@ -892,7 +894,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
}
|
||||
},
|
||||
removeFromPlaylist: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const chunks = chunk(query.songId, MAX_ITEMS_PER_PLAYLIST_ADD);
|
||||
|
||||
|
|
@ -914,7 +916,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
scrobble: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const position = query.position && Math.round(query.position);
|
||||
|
||||
|
|
@ -978,7 +980,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
search: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
@ -1071,7 +1073,7 @@ export const JellyfinController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
updatePlaylist: async (args) => {
|
||||
const { query, body, apiClientProps } = args;
|
||||
const { apiClientProps, body, query } = args;
|
||||
|
||||
if (!apiClientProps.server?.userId) {
|
||||
throw new Error('No userId found');
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import { z } from 'zod';
|
||||
import { JFAlbum, JFPlaylist, JFMusicFolder, JFGenre } from '/@/renderer/api/jellyfin.types';
|
||||
|
||||
import { JFAlbum, JFGenre, JFMusicFolder, JFPlaylist } from '/@/renderer/api/jellyfin.types';
|
||||
import { jfType } from '/@/renderer/api/jellyfin/jellyfin-types';
|
||||
import {
|
||||
Song,
|
||||
LibraryItem,
|
||||
Album,
|
||||
AlbumArtist,
|
||||
Playlist,
|
||||
MusicFolder,
|
||||
Genre,
|
||||
LibraryItem,
|
||||
MusicFolder,
|
||||
Playlist,
|
||||
RelatedArtist,
|
||||
ServerListItem,
|
||||
ServerType,
|
||||
RelatedArtist,
|
||||
Song,
|
||||
} from '/@/renderer/api/types';
|
||||
|
||||
const getStreamUrl = (args: {
|
||||
|
|
@ -21,9 +22,9 @@ const getStreamUrl = (args: {
|
|||
eTag?: string;
|
||||
id: string;
|
||||
mediaSourceId?: string;
|
||||
server: ServerListItem | null;
|
||||
server: null | ServerListItem;
|
||||
}) => {
|
||||
const { id, server, deviceId } = args;
|
||||
const { deviceId, id, server } = args;
|
||||
|
||||
return (
|
||||
`${server?.url}/audio` +
|
||||
|
|
@ -122,9 +123,9 @@ const getPlaylistCoverArtUrl = (args: { baseUrl: string; item: JFPlaylist; size:
|
|||
);
|
||||
};
|
||||
|
||||
type AlbumOrSong = z.infer<typeof jfType._response.song> | z.infer<typeof jfType._response.album>;
|
||||
type AlbumOrSong = z.infer<typeof jfType._response.album> | z.infer<typeof jfType._response.song>;
|
||||
|
||||
const getPeople = (item: AlbumOrSong): Record<string, RelatedArtist[]> | null => {
|
||||
const getPeople = (item: AlbumOrSong): null | Record<string, RelatedArtist[]> => {
|
||||
if (item.People) {
|
||||
const participants: Record<string, RelatedArtist[]> = {};
|
||||
|
||||
|
|
@ -151,7 +152,7 @@ const getPeople = (item: AlbumOrSong): Record<string, RelatedArtist[]> | null =>
|
|||
return null;
|
||||
};
|
||||
|
||||
const getTags = (item: AlbumOrSong): Record<string, string[]> | null => {
|
||||
const getTags = (item: AlbumOrSong): null | Record<string, string[]> => {
|
||||
if (item.Tags) {
|
||||
const tags: Record<string, string[]> = {};
|
||||
for (const tag of item.Tags) {
|
||||
|
|
@ -166,7 +167,7 @@ const getTags = (item: AlbumOrSong): Record<string, string[]> | null => {
|
|||
|
||||
const normalizeSong = (
|
||||
item: z.infer<typeof jfType._response.song>,
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
deviceId: string,
|
||||
imageSize?: number,
|
||||
): Song => {
|
||||
|
|
@ -252,7 +253,7 @@ const normalizeSong = (
|
|||
|
||||
const normalizeAlbum = (
|
||||
item: z.infer<typeof jfType._response.album>,
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): Album => {
|
||||
return {
|
||||
|
|
@ -312,7 +313,7 @@ const normalizeAlbumArtist = (
|
|||
item: z.infer<typeof jfType._response.albumArtist> & {
|
||||
similarArtists?: z.infer<typeof jfType._response.albumArtistList>;
|
||||
},
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): AlbumArtist => {
|
||||
const similarArtists =
|
||||
|
|
@ -361,7 +362,7 @@ const normalizeAlbumArtist = (
|
|||
|
||||
const normalizePlaylist = (
|
||||
item: z.infer<typeof jfType._response.playlist>,
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): Playlist => {
|
||||
const imageUrl = getPlaylistCoverArtUrl({
|
||||
|
|
@ -445,7 +446,7 @@ const getGenreCoverArtUrl = (args: {
|
|||
);
|
||||
};
|
||||
|
||||
const normalizeGenre = (item: JFGenre, server: ServerListItem | null): Genre => {
|
||||
const normalizeGenre = (item: JFGenre, server: null | ServerListItem): Genre => {
|
||||
return {
|
||||
albumCount: undefined,
|
||||
id: item.Id,
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ const imageBlurHashes = z.object({
|
|||
const userData = z.object({
|
||||
IsFavorite: z.boolean(),
|
||||
Key: z.string(),
|
||||
PlayCount: z.number(),
|
||||
PlaybackPositionTicks: z.number(),
|
||||
PlayCount: z.number(),
|
||||
Played: z.boolean(),
|
||||
});
|
||||
|
||||
|
|
@ -187,13 +187,13 @@ const sessionInfo = z.object({
|
|||
LastPlaybackCheckIn: z.string(),
|
||||
NowPlayingQueue: z.array(z.any()),
|
||||
NowPlayingQueueFullItems: z.array(z.any()),
|
||||
PlayableMediaTypes: z.array(z.any()),
|
||||
PlayState: z.object({
|
||||
CanSeek: z.boolean(),
|
||||
IsMuted: z.boolean(),
|
||||
IsPaused: z.boolean(),
|
||||
RepeatMode: z.string(),
|
||||
}),
|
||||
PlayableMediaTypes: z.array(z.any()),
|
||||
RemoteEndPoint: z.string(),
|
||||
ServerId: z.string(),
|
||||
SupportedCommands: z.array(z.any()),
|
||||
|
|
@ -223,10 +223,10 @@ const configuration = z.object({
|
|||
const policy = z.object({
|
||||
AccessSchedules: z.array(z.any()),
|
||||
AuthenticationProviderId: z.string(),
|
||||
BlockUnratedItems: z.array(z.any()),
|
||||
BlockedChannels: z.array(z.any()),
|
||||
BlockedMediaFolders: z.array(z.any()),
|
||||
BlockedTags: z.array(z.any()),
|
||||
BlockUnratedItems: z.array(z.any()),
|
||||
EnableAllChannels: z.boolean(),
|
||||
EnableAllDevices: z.boolean(),
|
||||
EnableAllFolders: z.boolean(),
|
||||
|
|
@ -234,6 +234,9 @@ const policy = z.object({
|
|||
EnableContentDeletion: z.boolean(),
|
||||
EnableContentDeletionFromFolders: z.array(z.any()),
|
||||
EnableContentDownloading: z.boolean(),
|
||||
EnabledChannels: z.array(z.any()),
|
||||
EnabledDevices: z.array(z.any()),
|
||||
EnabledFolders: z.array(z.any()),
|
||||
EnableLiveTvAccess: z.boolean(),
|
||||
EnableLiveTvManagement: z.boolean(),
|
||||
EnableMediaConversion: z.boolean(),
|
||||
|
|
@ -246,9 +249,6 @@ const policy = z.object({
|
|||
EnableSyncTranscoding: z.boolean(),
|
||||
EnableUserPreferenceAccess: z.boolean(),
|
||||
EnableVideoPlaybackTranscoding: z.boolean(),
|
||||
EnabledChannels: z.array(z.any()),
|
||||
EnabledDevices: z.array(z.any()),
|
||||
EnabledFolders: z.array(z.any()),
|
||||
ForceRemoteSourceTranscoding: z.boolean(),
|
||||
InvalidLoginAttemptCount: z.number(),
|
||||
IsAdministrator: z.boolean(),
|
||||
|
|
@ -414,8 +414,8 @@ const song = z.object({
|
|||
ImageTags: imageTags,
|
||||
IndexNumber: z.number(),
|
||||
IsFolder: z.boolean(),
|
||||
LUFS: z.number().optional(),
|
||||
LocationType: z.string(),
|
||||
LUFS: z.number().optional(),
|
||||
MediaSources: z.array(mediaSources),
|
||||
MediaType: z.string(),
|
||||
Name: z.string(),
|
||||
|
|
@ -654,8 +654,8 @@ const favorite = z.object({
|
|||
Key: z.string(),
|
||||
LastPlayedDate: z.string(),
|
||||
Likes: z.boolean(),
|
||||
PlayCount: z.number(),
|
||||
PlaybackPositionTicks: z.number(),
|
||||
PlayCount: z.number(),
|
||||
Played: z.boolean(),
|
||||
PlayedPercentage: z.number(),
|
||||
Rating: z.number(),
|
||||
|
|
|
|||
|
|
@ -1,30 +1,77 @@
|
|||
import { SSArtistInfo } from '/@/renderer/api/subsonic.types';
|
||||
|
||||
export type NDAuthenticate = {
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
name: string;
|
||||
subsonicSalt: string;
|
||||
subsonicToken: string;
|
||||
token: string;
|
||||
username: string;
|
||||
export enum NDAlbumArtistListSort {
|
||||
ALBUM_COUNT = 'albumCount',
|
||||
FAVORITED = 'starred_at',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'playCount',
|
||||
RATING = 'rating',
|
||||
SONG_COUNT = 'songCount',
|
||||
}
|
||||
|
||||
export enum NDAlbumListSort {
|
||||
ALBUM_ARTIST = 'album_artist',
|
||||
ARTIST = 'artist',
|
||||
DURATION = 'duration',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'play_count',
|
||||
PLAY_DATE = 'play_date',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'recently_added',
|
||||
SONG_COUNT = 'songCount',
|
||||
STARRED = 'starred_at',
|
||||
YEAR = 'max_year',
|
||||
}
|
||||
|
||||
export enum NDGenreListSort {
|
||||
NAME = 'name',
|
||||
}
|
||||
|
||||
export enum NDPlaylistListSort {
|
||||
DURATION = 'duration',
|
||||
NAME = 'name',
|
||||
OWNER = 'owner_name',
|
||||
PUBLIC = 'public',
|
||||
SONG_COUNT = 'songCount',
|
||||
UPDATED_AT = 'updatedAt',
|
||||
}
|
||||
|
||||
export enum NDSongListSort {
|
||||
ALBUM = 'album',
|
||||
ALBUM_ARTIST = 'order_album_artist_name',
|
||||
ALBUM_SONGS = 'album',
|
||||
ARTIST = 'artist',
|
||||
BPM = 'bpm',
|
||||
CHANNELS = 'channels',
|
||||
COMMENT = 'comment',
|
||||
DURATION = 'duration',
|
||||
FAVORITED = 'starred_at',
|
||||
GENRE = 'genre',
|
||||
ID = 'id',
|
||||
PLAY_COUNT = 'playCount',
|
||||
PLAY_DATE = 'playDate',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'createdAt',
|
||||
TITLE = 'title',
|
||||
TRACK = 'track',
|
||||
YEAR = 'year',
|
||||
}
|
||||
|
||||
export enum NDSortOrder {
|
||||
ASC = 'ASC',
|
||||
DESC = 'DESC',
|
||||
}
|
||||
|
||||
export type NDAddToPlaylist = null;
|
||||
|
||||
export type NDAddToPlaylistBody = {
|
||||
ids: string[];
|
||||
};
|
||||
|
||||
export type NDUser = {
|
||||
createdAt: string;
|
||||
email: string;
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
lastAccessAt: string;
|
||||
lastLoginAt: string;
|
||||
name: string;
|
||||
updatedAt: string;
|
||||
userName: string;
|
||||
};
|
||||
|
||||
export type NDGenre = {
|
||||
id: string;
|
||||
name: string;
|
||||
export type NDAddToPlaylistResponse = {
|
||||
added: number;
|
||||
};
|
||||
|
||||
export type NDAlbum = {
|
||||
|
|
@ -61,6 +108,193 @@ export type NDAlbum = {
|
|||
updatedAt: string;
|
||||
} & { songs?: NDSong[] };
|
||||
|
||||
export type NDAlbumArtist = {
|
||||
albumCount: number;
|
||||
biography: string;
|
||||
externalInfoUpdatedAt: string;
|
||||
externalUrl: string;
|
||||
fullText: string;
|
||||
genres: NDGenre[];
|
||||
id: string;
|
||||
largeImageUrl?: string;
|
||||
mbzArtistId: string;
|
||||
mediumImageUrl?: string;
|
||||
name: string;
|
||||
orderArtistName: string;
|
||||
playCount: number;
|
||||
playDate: string;
|
||||
rating: number;
|
||||
size: number;
|
||||
smallImageUrl?: string;
|
||||
songCount: number;
|
||||
starred: boolean;
|
||||
starredAt: string;
|
||||
} & {
|
||||
similarArtists?: SSArtistInfo['similarArtist'];
|
||||
};
|
||||
|
||||
export type NDAlbumArtistDetail = NDAlbumArtist;
|
||||
|
||||
export type NDAlbumArtistDetailResponse = NDAlbumArtist;
|
||||
|
||||
export type NDAlbumArtistList = {
|
||||
items: NDAlbumArtist[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type NDAlbumArtistListParams = NDOrder &
|
||||
NDPagination & {
|
||||
_sort?: NDAlbumArtistListSort;
|
||||
genre_id?: string;
|
||||
starred?: boolean;
|
||||
};
|
||||
|
||||
export type NDAlbumDetail = NDAlbum & { songs?: NDSongListResponse };
|
||||
|
||||
export type NDAlbumDetailResponse = NDAlbum;
|
||||
|
||||
export type NDAlbumList = {
|
||||
items: NDAlbum[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type NDAlbumListParams = NDOrder &
|
||||
NDPagination & {
|
||||
_sort?: NDAlbumListSort;
|
||||
album_id?: string;
|
||||
artist_id?: string;
|
||||
compilation?: boolean;
|
||||
genre_id?: string;
|
||||
has_rating?: boolean;
|
||||
id?: string;
|
||||
name?: string;
|
||||
recently_played?: boolean;
|
||||
starred?: boolean;
|
||||
year?: number;
|
||||
};
|
||||
|
||||
export type NDAlbumListResponse = NDAlbum[];
|
||||
|
||||
export type NDArtistListResponse = NDAlbumArtist[];
|
||||
|
||||
export type NDAuthenticate = {
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
name: string;
|
||||
subsonicSalt: string;
|
||||
subsonicToken: string;
|
||||
token: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export type NDAuthenticationResponse = NDAuthenticate;
|
||||
|
||||
export type NDCreatePlaylist = NDCreatePlaylistResponse;
|
||||
|
||||
export type NDCreatePlaylistParams = {
|
||||
comment?: string;
|
||||
name: string;
|
||||
public?: boolean;
|
||||
rules?: null | Record<string, any>;
|
||||
};
|
||||
|
||||
export type NDCreatePlaylistResponse = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type NDDeletePlaylist = NDDeletePlaylistResponse;
|
||||
|
||||
export type NDDeletePlaylistParams = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type NDDeletePlaylistResponse = null;
|
||||
|
||||
export type NDGenre = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type NDGenreList = NDGenre[];
|
||||
|
||||
export type NDGenreListParams = NDOrder &
|
||||
NDPagination & {
|
||||
_sort?: NDGenreListSort;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export type NDGenreListResponse = NDGenre[];
|
||||
|
||||
export type NDOrder = {
|
||||
_order?: NDSortOrder;
|
||||
};
|
||||
|
||||
export type NDPagination = {
|
||||
_end?: number;
|
||||
_start?: number;
|
||||
};
|
||||
|
||||
export type NDPlaylist = {
|
||||
comment: string;
|
||||
createdAt: string;
|
||||
duration: number;
|
||||
evaluatedAt: string;
|
||||
id: string;
|
||||
name: string;
|
||||
ownerId: string;
|
||||
ownerName: string;
|
||||
path: string;
|
||||
public: boolean;
|
||||
rules: null | Record<string, any>;
|
||||
size: number;
|
||||
songCount: number;
|
||||
sync: boolean;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type NDPlaylistDetail = NDPlaylist;
|
||||
|
||||
export type NDPlaylistDetailResponse = NDPlaylist;
|
||||
|
||||
export type NDPlaylistList = {
|
||||
items: NDPlaylist[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type NDPlaylistListParams = NDOrder &
|
||||
NDPagination & {
|
||||
_sort?: NDPlaylistListSort;
|
||||
owner_id?: string;
|
||||
};
|
||||
|
||||
export type NDPlaylistListResponse = NDPlaylist[];
|
||||
|
||||
export type NDPlaylistSong = NDSong & {
|
||||
mediaFileId: string;
|
||||
playlistId: string;
|
||||
};
|
||||
|
||||
export type NDPlaylistSongList = {
|
||||
items: NDPlaylistSong[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type NDPlaylistSongListResponse = NDPlaylistSong[];
|
||||
|
||||
export type NDRemoveFromPlaylist = null;
|
||||
|
||||
export type NDRemoveFromPlaylistParams = {
|
||||
id: string[];
|
||||
};
|
||||
|
||||
export type NDRemoveFromPlaylistResponse = {
|
||||
ids: string[];
|
||||
};
|
||||
|
||||
export type NDSong = {
|
||||
album: string;
|
||||
albumArtist: string;
|
||||
|
|
@ -107,275 +341,41 @@ export type NDSong = {
|
|||
year: number;
|
||||
};
|
||||
|
||||
export type NDAlbumArtist = {
|
||||
albumCount: number;
|
||||
biography: string;
|
||||
externalInfoUpdatedAt: string;
|
||||
externalUrl: string;
|
||||
fullText: string;
|
||||
genres: NDGenre[];
|
||||
id: string;
|
||||
largeImageUrl?: string;
|
||||
mbzArtistId: string;
|
||||
mediumImageUrl?: string;
|
||||
name: string;
|
||||
orderArtistName: string;
|
||||
playCount: number;
|
||||
playDate: string;
|
||||
rating: number;
|
||||
size: number;
|
||||
smallImageUrl?: string;
|
||||
songCount: number;
|
||||
starred: boolean;
|
||||
starredAt: string;
|
||||
} & {
|
||||
similarArtists?: SSArtistInfo['similarArtist'];
|
||||
};
|
||||
|
||||
export type NDAuthenticationResponse = NDAuthenticate;
|
||||
|
||||
export type NDAlbumArtistList = {
|
||||
items: NDAlbumArtist[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type NDAlbumArtistDetail = NDAlbumArtist;
|
||||
|
||||
export type NDAlbumArtistDetailResponse = NDAlbumArtist;
|
||||
|
||||
export type NDGenreList = NDGenre[];
|
||||
|
||||
export type NDGenreListResponse = NDGenre[];
|
||||
|
||||
export type NDAlbumDetailResponse = NDAlbum;
|
||||
|
||||
export type NDAlbumDetail = NDAlbum & { songs?: NDSongListResponse };
|
||||
|
||||
export type NDAlbumListResponse = NDAlbum[];
|
||||
|
||||
export type NDAlbumList = {
|
||||
items: NDAlbum[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type NDSongDetail = NDSong;
|
||||
|
||||
export type NDSongDetailResponse = NDSong;
|
||||
|
||||
export type NDSongListResponse = NDSong[];
|
||||
|
||||
export type NDSongList = {
|
||||
items: NDSong[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type NDArtistListResponse = NDAlbumArtist[];
|
||||
export type NDSongListParams = NDOrder &
|
||||
NDPagination & {
|
||||
_sort?: NDSongListSort;
|
||||
album_id?: string[];
|
||||
artist_id?: string[];
|
||||
genre_id?: string;
|
||||
starred?: boolean;
|
||||
};
|
||||
|
||||
export type NDPagination = {
|
||||
_end?: number;
|
||||
_start?: number;
|
||||
};
|
||||
|
||||
export enum NDSortOrder {
|
||||
ASC = 'ASC',
|
||||
DESC = 'DESC',
|
||||
}
|
||||
|
||||
export type NDOrder = {
|
||||
_order?: NDSortOrder;
|
||||
};
|
||||
|
||||
export enum NDGenreListSort {
|
||||
NAME = 'name',
|
||||
}
|
||||
|
||||
export type NDGenreListParams = {
|
||||
_sort?: NDGenreListSort;
|
||||
id?: string;
|
||||
} & NDPagination &
|
||||
NDOrder;
|
||||
|
||||
export enum NDAlbumListSort {
|
||||
ALBUM_ARTIST = 'album_artist',
|
||||
ARTIST = 'artist',
|
||||
DURATION = 'duration',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'play_count',
|
||||
PLAY_DATE = 'play_date',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'recently_added',
|
||||
SONG_COUNT = 'songCount',
|
||||
STARRED = 'starred_at',
|
||||
YEAR = 'max_year',
|
||||
}
|
||||
|
||||
export type NDAlbumListParams = {
|
||||
_sort?: NDAlbumListSort;
|
||||
album_id?: string;
|
||||
artist_id?: string;
|
||||
compilation?: boolean;
|
||||
genre_id?: string;
|
||||
has_rating?: boolean;
|
||||
id?: string;
|
||||
name?: string;
|
||||
recently_played?: boolean;
|
||||
starred?: boolean;
|
||||
year?: number;
|
||||
} & NDPagination &
|
||||
NDOrder;
|
||||
|
||||
export enum NDSongListSort {
|
||||
ALBUM = 'album',
|
||||
ALBUM_ARTIST = 'order_album_artist_name',
|
||||
ALBUM_SONGS = 'album',
|
||||
ARTIST = 'artist',
|
||||
BPM = 'bpm',
|
||||
CHANNELS = 'channels',
|
||||
COMMENT = 'comment',
|
||||
DURATION = 'duration',
|
||||
FAVORITED = 'starred_at',
|
||||
GENRE = 'genre',
|
||||
ID = 'id',
|
||||
PLAY_COUNT = 'playCount',
|
||||
PLAY_DATE = 'playDate',
|
||||
RANDOM = 'random',
|
||||
RATING = 'rating',
|
||||
RECENTLY_ADDED = 'createdAt',
|
||||
TITLE = 'title',
|
||||
TRACK = 'track',
|
||||
YEAR = 'year',
|
||||
}
|
||||
|
||||
export type NDSongListParams = {
|
||||
_sort?: NDSongListSort;
|
||||
album_id?: string[];
|
||||
artist_id?: string[];
|
||||
genre_id?: string;
|
||||
starred?: boolean;
|
||||
} & NDPagination &
|
||||
NDOrder;
|
||||
|
||||
export enum NDAlbumArtistListSort {
|
||||
ALBUM_COUNT = 'albumCount',
|
||||
FAVORITED = 'starred_at',
|
||||
NAME = 'name',
|
||||
PLAY_COUNT = 'playCount',
|
||||
RATING = 'rating',
|
||||
SONG_COUNT = 'songCount',
|
||||
}
|
||||
|
||||
export type NDAlbumArtistListParams = {
|
||||
_sort?: NDAlbumArtistListSort;
|
||||
genre_id?: string;
|
||||
starred?: boolean;
|
||||
} & NDPagination &
|
||||
NDOrder;
|
||||
|
||||
export type NDAddToPlaylistResponse = {
|
||||
added: number;
|
||||
};
|
||||
|
||||
export type NDAddToPlaylistBody = {
|
||||
ids: string[];
|
||||
};
|
||||
|
||||
export type NDAddToPlaylist = null;
|
||||
|
||||
export type NDRemoveFromPlaylistResponse = {
|
||||
ids: string[];
|
||||
};
|
||||
|
||||
export type NDRemoveFromPlaylistParams = {
|
||||
id: string[];
|
||||
};
|
||||
|
||||
export type NDRemoveFromPlaylist = null;
|
||||
|
||||
export type NDCreatePlaylistParams = {
|
||||
comment?: string;
|
||||
name: string;
|
||||
public?: boolean;
|
||||
rules?: Record<string, any> | null;
|
||||
};
|
||||
|
||||
export type NDCreatePlaylistResponse = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type NDCreatePlaylist = NDCreatePlaylistResponse;
|
||||
export type NDSongListResponse = NDSong[];
|
||||
|
||||
export type NDUpdatePlaylistParams = Partial<NDPlaylist>;
|
||||
|
||||
export type NDUpdatePlaylistResponse = NDPlaylist;
|
||||
|
||||
export type NDDeletePlaylistParams = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type NDDeletePlaylistResponse = null;
|
||||
|
||||
export type NDDeletePlaylist = NDDeletePlaylistResponse;
|
||||
|
||||
export type NDPlaylist = {
|
||||
comment: string;
|
||||
export type NDUser = {
|
||||
createdAt: string;
|
||||
duration: number;
|
||||
evaluatedAt: string;
|
||||
email: string;
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
lastAccessAt: string;
|
||||
lastLoginAt: string;
|
||||
name: string;
|
||||
ownerId: string;
|
||||
ownerName: string;
|
||||
path: string;
|
||||
public: boolean;
|
||||
rules: Record<string, any> | null;
|
||||
size: number;
|
||||
songCount: number;
|
||||
sync: boolean;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type NDPlaylistDetail = NDPlaylist;
|
||||
|
||||
export type NDPlaylistDetailResponse = NDPlaylist;
|
||||
|
||||
export type NDPlaylistList = {
|
||||
items: NDPlaylist[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export type NDPlaylistListResponse = NDPlaylist[];
|
||||
|
||||
export enum NDPlaylistListSort {
|
||||
DURATION = 'duration',
|
||||
NAME = 'name',
|
||||
OWNER = 'owner_name',
|
||||
PUBLIC = 'public',
|
||||
SONG_COUNT = 'songCount',
|
||||
UPDATED_AT = 'updatedAt',
|
||||
}
|
||||
|
||||
export type NDPlaylistListParams = {
|
||||
_sort?: NDPlaylistListSort;
|
||||
owner_id?: string;
|
||||
} & NDPagination &
|
||||
NDOrder;
|
||||
|
||||
export type NDPlaylistSong = NDSong & {
|
||||
mediaFileId: string;
|
||||
playlistId: string;
|
||||
};
|
||||
|
||||
export type NDPlaylistSongListResponse = NDPlaylistSong[];
|
||||
|
||||
export type NDPlaylistSongList = {
|
||||
items: NDPlaylistSong[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
userName: string;
|
||||
};
|
||||
|
||||
export const NDSongQueryFields = [
|
||||
|
|
@ -515,12 +515,9 @@ export const NDSongQueryNumberOperators = [
|
|||
{ label: 'is in the range', value: 'inTheRange' },
|
||||
];
|
||||
|
||||
export type NDUserListParams = {
|
||||
_sort?: NDUserListSort;
|
||||
} & NDPagination &
|
||||
NDOrder;
|
||||
|
||||
export type NDUserListResponse = NDUser[];
|
||||
export enum NDUserListSort {
|
||||
NAME = 'name',
|
||||
}
|
||||
|
||||
export type NDUserList = {
|
||||
items: NDUser[];
|
||||
|
|
@ -528,6 +525,9 @@ export type NDUserList = {
|
|||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export enum NDUserListSort {
|
||||
NAME = 'name',
|
||||
}
|
||||
export type NDUserListParams = NDOrder &
|
||||
NDPagination & {
|
||||
_sort?: NDUserListSort;
|
||||
};
|
||||
|
||||
export type NDUserListResponse = NDUser[];
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import { initClient, initContract } from '@ts-rest/core';
|
||||
import axios, { Method, AxiosError, AxiosResponse, isAxiosError } from 'axios';
|
||||
import axios, { AxiosError, AxiosResponse, isAxiosError, Method } from 'axios';
|
||||
import isElectron from 'is-electron';
|
||||
import debounce from 'lodash/debounce';
|
||||
import omitBy from 'lodash/omitBy';
|
||||
import qs from 'qs';
|
||||
import { ndType } from './navidrome-types';
|
||||
import { authenticationFailure, resultWithHeaders } from '/@/renderer/api/utils';
|
||||
import { useAuthStore } from '/@/renderer/store';
|
||||
import { ServerListItem } from '/@/renderer/api/types';
|
||||
import { toast } from '/@/renderer/components/toast';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
const localSettings = isElectron() ? window.electron.localSettings : null;
|
||||
import { ndType } from './navidrome-types';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { ServerListItem } from '/@/renderer/api/types';
|
||||
import { authenticationFailure, resultWithHeaders } from '/@/renderer/api/utils';
|
||||
import { toast } from '/@/renderer/components/toast';
|
||||
import { useAuthStore } from '/@/renderer/store';
|
||||
|
||||
const localSettings = isElectron() ? window.api.localSettings : null;
|
||||
|
||||
const c = initContract();
|
||||
|
||||
|
|
@ -275,7 +277,7 @@ axiosClient.interceptors.response.use(
|
|||
// eslint-disable-next-line promise/no-promise-in-callback
|
||||
return localSettings
|
||||
.passwordGet(currentServer.id)
|
||||
.then(async (password: string | null) => {
|
||||
.then(async (password: null | string) => {
|
||||
authSuccess = false;
|
||||
|
||||
if (password === null) {
|
||||
|
|
@ -367,14 +369,14 @@ axiosClient.interceptors.response.use(
|
|||
);
|
||||
|
||||
export const ndApiClient = (args: {
|
||||
server: ServerListItem | null;
|
||||
server: null | ServerListItem;
|
||||
signal?: AbortSignal;
|
||||
url?: string;
|
||||
}) => {
|
||||
const { server, url, signal } = args;
|
||||
const { server, signal, url } = args;
|
||||
|
||||
return initClient(contract, {
|
||||
api: async ({ path, method, headers, body }) => {
|
||||
api: async ({ body, headers, method, path }) => {
|
||||
let baseUrl: string | undefined;
|
||||
let token: string | undefined;
|
||||
|
||||
|
|
@ -406,7 +408,7 @@ export const ndApiClient = (args: {
|
|||
headers: result.headers as any,
|
||||
status: result.status,
|
||||
};
|
||||
} catch (e: Error | AxiosError | any) {
|
||||
} catch (e: any | AxiosError | Error) {
|
||||
if (isAxiosError(e)) {
|
||||
if (e.code === 'ERR_NETWORK') {
|
||||
throw new Error(
|
||||
|
|
|
|||
|
|
@ -1,28 +1,29 @@
|
|||
import {
|
||||
albumArtistListSortMap,
|
||||
albumListSortMap,
|
||||
AuthenticationResponse,
|
||||
ControllerEndpoint,
|
||||
genreListSortMap,
|
||||
playlistListSortMap,
|
||||
PlaylistSongListArgs,
|
||||
PlaylistSongListResponse,
|
||||
ServerListItem,
|
||||
Song,
|
||||
songListSortMap,
|
||||
sortOrderMap,
|
||||
userListSortMap,
|
||||
} from '../types';
|
||||
|
||||
import { ServerFeature, ServerFeatures } from '/@/renderer/api/features-types';
|
||||
import { NDSongListSort } from '/@/renderer/api/navidrome.types';
|
||||
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 {
|
||||
albumArtistListSortMap,
|
||||
sortOrderMap,
|
||||
AuthenticationResponse,
|
||||
userListSortMap,
|
||||
albumListSortMap,
|
||||
songListSortMap,
|
||||
playlistListSortMap,
|
||||
PlaylistSongListArgs,
|
||||
PlaylistSongListResponse,
|
||||
genreListSortMap,
|
||||
Song,
|
||||
ControllerEndpoint,
|
||||
ServerListItem,
|
||||
} from '../types';
|
||||
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';
|
||||
import { ssNormalize } from '/@/renderer/api/subsonic/subsonic-normalize';
|
||||
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
|
||||
import { ssNormalize } from '/@/renderer/api/subsonic/subsonic-normalize';
|
||||
import { SubsonicExtensions } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import { getFeatures, hasFeature, VersionInfo } from '/@/renderer/api/utils';
|
||||
|
||||
const VERSION_INFO: VersionInfo = [
|
||||
['0.55.0', { [ServerFeature.BFR]: [1] }],
|
||||
|
|
@ -48,7 +49,7 @@ const NAVIDROME_ROLES: Array<string | { label: string; value: string }> = [
|
|||
|
||||
const EXCLUDED_TAGS = new Set<string>(['disctotal', 'genre', 'tracktotal']);
|
||||
|
||||
const excludeMissing = (server: ServerListItem | null) => {
|
||||
const excludeMissing = (server: null | ServerListItem) => {
|
||||
if (hasFeature(server, ServerFeature.BFR)) {
|
||||
return { missing: false };
|
||||
}
|
||||
|
|
@ -58,7 +59,7 @@ const excludeMissing = (server: ServerListItem | null) => {
|
|||
|
||||
export const NavidromeController: ControllerEndpoint = {
|
||||
addToPlaylist: async (args) => {
|
||||
const { body, query, apiClientProps } = args;
|
||||
const { apiClientProps, body, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).addToPlaylist({
|
||||
body: {
|
||||
|
|
@ -98,7 +99,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
},
|
||||
createFavorite: SubsonicController.createFavorite,
|
||||
createPlaylist: async (args) => {
|
||||
const { body, apiClientProps } = args;
|
||||
const { apiClientProps, body } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).createPlaylist({
|
||||
body: {
|
||||
|
|
@ -120,7 +121,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
},
|
||||
deleteFavorite: SubsonicController.deleteFavorite,
|
||||
deletePlaylist: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).deletePlaylist({
|
||||
params: {
|
||||
|
|
@ -135,7 +136,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
getAlbumArtistDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getAlbumArtistDetail({
|
||||
params: {
|
||||
|
|
@ -176,7 +177,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
);
|
||||
},
|
||||
getAlbumArtistList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getAlbumArtistList({
|
||||
query: {
|
||||
|
|
@ -217,7 +218,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getAlbumDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const albumRes = await ndApiClient(apiClientProps).getAlbumDetail({
|
||||
params: {
|
||||
|
|
@ -245,7 +246,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
);
|
||||
},
|
||||
getAlbumInfo: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const albumInfo = await ssApiClient(apiClientProps).getAlbumInfo2({
|
||||
query: {
|
||||
|
|
@ -265,7 +266,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getAlbumList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getAlbumList({
|
||||
query: {
|
||||
|
|
@ -299,7 +300,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getArtistList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getAlbumArtistList({
|
||||
query: {
|
||||
|
|
@ -341,7 +342,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
}).then((result) => result!.totalRecordCount!),
|
||||
getDownloadUrl: SubsonicController.getDownloadUrl,
|
||||
getGenreList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getGenreList({
|
||||
query: {
|
||||
|
|
@ -366,7 +367,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
getLyrics: SubsonicController.getLyrics,
|
||||
getMusicFolderList: SubsonicController.getMusicFolderList,
|
||||
getPlaylistDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getPlaylistDetail({
|
||||
params: {
|
||||
|
|
@ -381,7 +382,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
return ndNormalize.playlist(res.body.data, apiClientProps.server);
|
||||
},
|
||||
getPlaylistList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
const customQuery = query._custom?.navidrome;
|
||||
|
||||
// Smart playlists only became available in 0.48.0. Do not filter for previous versions
|
||||
|
|
@ -420,7 +421,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getPlaylistSongList: async (args: PlaylistSongListArgs): Promise<PlaylistSongListResponse> => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getPlaylistSongList({
|
||||
params: {
|
||||
|
|
@ -548,7 +549,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
}, []);
|
||||
},
|
||||
getSongDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getSongDetail({
|
||||
params: {
|
||||
|
|
@ -563,7 +564,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
return ndNormalize.song(res.body.data, apiClientProps.server);
|
||||
},
|
||||
getSongList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getSongList({
|
||||
query: {
|
||||
|
|
@ -642,7 +643,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
getTopSongs: SubsonicController.getTopSongs,
|
||||
getTranscodingUrl: SubsonicController.getTranscodingUrl,
|
||||
getUserList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getUserList({
|
||||
query: {
|
||||
|
|
@ -682,7 +683,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
}
|
||||
},
|
||||
removeFromPlaylist: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).removeFromPlaylist({
|
||||
params: {
|
||||
|
|
@ -703,7 +704,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
search: SubsonicController.search,
|
||||
setRating: SubsonicController.setRating,
|
||||
shareItem: async (args) => {
|
||||
const { body, apiClientProps } = args;
|
||||
const { apiClientProps, body } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).shareItem({
|
||||
body: {
|
||||
|
|
@ -724,7 +725,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
updatePlaylist: async (args) => {
|
||||
const { query, body, apiClientProps } = args;
|
||||
const { apiClientProps, body, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).updatePlaylist({
|
||||
body: {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import z from 'zod';
|
||||
|
||||
import { ndType } from './navidrome-types';
|
||||
|
||||
import { NDGenre } from '/@/renderer/api/navidrome.types';
|
||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import {
|
||||
Song,
|
||||
LibraryItem,
|
||||
Album,
|
||||
Playlist,
|
||||
User,
|
||||
AlbumArtist,
|
||||
Genre,
|
||||
LibraryItem,
|
||||
Playlist,
|
||||
RelatedArtist,
|
||||
ServerListItem,
|
||||
ServerType,
|
||||
RelatedArtist,
|
||||
Song,
|
||||
User,
|
||||
} from '/@/renderer/api/types';
|
||||
import z from 'zod';
|
||||
import { ndType } from './navidrome-types';
|
||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import { NDGenre } from '/@/renderer/api/navidrome.types';
|
||||
|
||||
const getImageUrl = (args: { url: string | null }) => {
|
||||
const getImageUrl = (args: { url: null | string }) => {
|
||||
const { url } = args;
|
||||
if (url === '/app/artist-placeholder.webp') {
|
||||
return null;
|
||||
|
|
@ -51,19 +53,19 @@ interface WithDate {
|
|||
playDate?: string;
|
||||
}
|
||||
|
||||
const normalizePlayDate = (item: WithDate): string | null => {
|
||||
const normalizePlayDate = (item: WithDate): null | string => {
|
||||
return !item.playDate || item.playDate.includes('0001-') ? null : item.playDate;
|
||||
};
|
||||
|
||||
const getArtists = (
|
||||
item:
|
||||
| z.infer<typeof ndType._response.song>
|
||||
| z.infer<typeof ndType._response.album>
|
||||
| z.infer<typeof ndType._response.playlistSong>
|
||||
| z.infer<typeof ndType._response.album>,
|
||||
| z.infer<typeof ndType._response.song>,
|
||||
) => {
|
||||
let albumArtists: RelatedArtist[] | undefined;
|
||||
let artists: RelatedArtist[] | undefined;
|
||||
let participants: Record<string, RelatedArtist[]> | null = null;
|
||||
let participants: null | Record<string, RelatedArtist[]> = null;
|
||||
|
||||
if (item.participants) {
|
||||
participants = {};
|
||||
|
|
@ -120,8 +122,8 @@ const getArtists = (
|
|||
};
|
||||
|
||||
const normalizeSong = (
|
||||
item: z.infer<typeof ndType._response.song> | z.infer<typeof ndType._response.playlistSong>,
|
||||
server: ServerListItem | null,
|
||||
item: z.infer<typeof ndType._response.playlistSong> | z.infer<typeof ndType._response.song>,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): Song => {
|
||||
let id;
|
||||
|
|
@ -204,7 +206,7 @@ const normalizeAlbum = (
|
|||
item: z.infer<typeof ndType._response.album> & {
|
||||
songs?: z.infer<typeof ndType._response.songList>;
|
||||
},
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): Album => {
|
||||
const imageUrl = getCoverArtUrl({
|
||||
|
|
@ -268,7 +270,7 @@ const normalizeAlbumArtist = (
|
|||
item: z.infer<typeof ndType._response.albumArtist> & {
|
||||
similarArtists?: z.infer<typeof ssType._response.artistInfo>['artistInfo']['similarArtist'];
|
||||
},
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
): AlbumArtist => {
|
||||
let imageUrl = getImageUrl({ url: item?.largeImageUrl || null });
|
||||
|
||||
|
|
@ -332,7 +334,7 @@ const normalizeAlbumArtist = (
|
|||
|
||||
const normalizePlaylist = (
|
||||
item: z.infer<typeof ndType._response.playlist>,
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): Playlist => {
|
||||
const imageUrl = getCoverArtUrl({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
NDAlbumArtistListSort,
|
||||
NDAlbumListSort,
|
||||
|
|
|
|||
|
|
@ -1,28 +1,30 @@
|
|||
import { QueryFunctionContext } from '@tanstack/react-query';
|
||||
import { LyricSource } from './types';
|
||||
|
||||
import type {
|
||||
AlbumListQuery,
|
||||
SongListQuery,
|
||||
AlbumDetailQuery,
|
||||
AlbumArtistListQuery,
|
||||
ArtistListQuery,
|
||||
PlaylistListQuery,
|
||||
PlaylistDetailQuery,
|
||||
PlaylistSongListQuery,
|
||||
UserListQuery,
|
||||
AlbumArtistDetailQuery,
|
||||
TopSongListQuery,
|
||||
SearchQuery,
|
||||
SongDetailQuery,
|
||||
RandomSongListQuery,
|
||||
LyricsQuery,
|
||||
LyricSearchQuery,
|
||||
AlbumArtistListQuery,
|
||||
AlbumDetailQuery,
|
||||
AlbumListQuery,
|
||||
ArtistListQuery,
|
||||
GenreListQuery,
|
||||
LyricSearchQuery,
|
||||
LyricsQuery,
|
||||
PlaylistDetailQuery,
|
||||
PlaylistListQuery,
|
||||
PlaylistSongListQuery,
|
||||
RandomSongListQuery,
|
||||
SearchQuery,
|
||||
SimilarSongsQuery,
|
||||
SongDetailQuery,
|
||||
SongListQuery,
|
||||
TopSongListQuery,
|
||||
UserListQuery,
|
||||
} from './types';
|
||||
|
||||
import { LyricSource } from './types';
|
||||
|
||||
export const splitPaginatedQuery = (key: any) => {
|
||||
const { startIndex, limit, ...filter } = key || {};
|
||||
const { limit, startIndex, ...filter } = key || {};
|
||||
|
||||
if (startIndex !== undefined || limit !== undefined) {
|
||||
return {
|
||||
|
|
@ -51,7 +53,7 @@ export const queryKeys: Record<
|
|||
> = {
|
||||
albumArtists: {
|
||||
count: (serverId: string, query?: AlbumArtistListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
|
||||
if (query && pagination) {
|
||||
return [serverId, 'albumArtists', 'count', filter, pagination] as const;
|
||||
|
|
@ -68,7 +70,7 @@ export const queryKeys: Record<
|
|||
return [serverId, 'albumArtists', 'detail'] as const;
|
||||
},
|
||||
list: (serverId: string, query?: AlbumArtistListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
if (query && pagination) {
|
||||
return [serverId, 'albumArtists', 'list', filter, pagination] as const;
|
||||
}
|
||||
|
|
@ -87,7 +89,7 @@ export const queryKeys: Record<
|
|||
},
|
||||
albums: {
|
||||
count: (serverId: string, query?: AlbumListQuery, artistId?: string) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
|
||||
if (query && pagination && artistId) {
|
||||
return [serverId, 'albums', 'count', artistId, filter, pagination] as const;
|
||||
|
|
@ -110,7 +112,7 @@ export const queryKeys: Record<
|
|||
detail: (serverId: string, query?: AlbumDetailQuery) =>
|
||||
[serverId, 'albums', 'detail', query] as const,
|
||||
list: (serverId: string, query?: AlbumListQuery, artistId?: string) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
|
||||
if (query && pagination && artistId) {
|
||||
return [serverId, 'albums', 'list', artistId, filter, pagination] as const;
|
||||
|
|
@ -144,7 +146,7 @@ export const queryKeys: Record<
|
|||
},
|
||||
artists: {
|
||||
list: (serverId: string, query?: ArtistListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
if (query && pagination) {
|
||||
return [serverId, 'artists', 'list', filter, pagination] as const;
|
||||
}
|
||||
|
|
@ -159,7 +161,7 @@ export const queryKeys: Record<
|
|||
},
|
||||
genres: {
|
||||
list: (serverId: string, query?: GenreListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
if (query && pagination) {
|
||||
return [serverId, 'genres', 'list', filter, pagination] as const;
|
||||
}
|
||||
|
|
@ -177,7 +179,7 @@ export const queryKeys: Record<
|
|||
},
|
||||
playlists: {
|
||||
detail: (serverId: string, id?: string, query?: PlaylistDetailQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
if (query && pagination) {
|
||||
return [serverId, 'playlists', id, 'detail', filter, pagination] as const;
|
||||
}
|
||||
|
|
@ -190,7 +192,7 @@ export const queryKeys: Record<
|
|||
return [serverId, 'playlists', 'detail'] as const;
|
||||
},
|
||||
detailSongList: (serverId: string, id: string, query?: PlaylistSongListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
|
||||
if (query && id && pagination) {
|
||||
return [serverId, 'playlists', id, 'detailSongList', filter, pagination] as const;
|
||||
|
|
@ -205,7 +207,7 @@ export const queryKeys: Record<
|
|||
return [serverId, 'playlists', 'detailSongList'] as const;
|
||||
},
|
||||
list: (serverId: string, query?: PlaylistListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
if (query && pagination) {
|
||||
return [serverId, 'playlists', 'list', filter, pagination] as const;
|
||||
}
|
||||
|
|
@ -218,7 +220,7 @@ export const queryKeys: Record<
|
|||
},
|
||||
root: (serverId: string) => [serverId, 'playlists'] as const,
|
||||
songList: (serverId: string, id?: string, query?: PlaylistSongListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
if (query && id && pagination) {
|
||||
return [serverId, 'playlists', id, 'songList', filter, pagination] as const;
|
||||
}
|
||||
|
|
@ -246,7 +248,7 @@ export const queryKeys: Record<
|
|||
},
|
||||
songs: {
|
||||
count: (serverId: string, query?: SongListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
if (query && pagination) {
|
||||
return [serverId, 'songs', 'count', filter, pagination] as const;
|
||||
}
|
||||
|
|
@ -262,7 +264,7 @@ export const queryKeys: Record<
|
|||
return [serverId, 'songs', 'detail'] as const;
|
||||
},
|
||||
list: (serverId: string, query?: SongListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
const { filter, pagination } = splitPaginatedQuery(query);
|
||||
if (query && pagination) {
|
||||
return [serverId, 'songs', 'list', filter, pagination] as const;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,13 @@
|
|||
export type SSBaseResponse = {
|
||||
serverVersion?: 'string';
|
||||
status: 'string';
|
||||
type?: 'string';
|
||||
version: 'string';
|
||||
export type SSAlbum = SSAlbumListEntry & {
|
||||
song: SSSong[];
|
||||
};
|
||||
|
||||
export type SSMusicFolderList = SSMusicFolder[];
|
||||
|
||||
export type SSMusicFolderListResponse = {
|
||||
musicFolders: {
|
||||
musicFolder: SSMusicFolder[];
|
||||
};
|
||||
};
|
||||
|
||||
export type SSGenreList = SSGenre[];
|
||||
|
||||
export type SSGenreListResponse = {
|
||||
genres: {
|
||||
genre: SSGenre[];
|
||||
};
|
||||
};
|
||||
export type SSAlbumArtistDetail = SSAlbumArtistListEntry & { album: SSAlbumListEntry[] };
|
||||
|
||||
export type SSAlbumArtistDetailParams = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type SSAlbumArtistDetail = SSAlbumArtistListEntry & { album: SSAlbumListEntry[] };
|
||||
|
||||
export type SSAlbumArtistDetailResponse = {
|
||||
artist: SSAlbumArtistListEntry & {
|
||||
album: SSAlbumListEntry[];
|
||||
|
|
@ -36,7 +17,19 @@ export type SSAlbumArtistDetailResponse = {
|
|||
export type SSAlbumArtistList = {
|
||||
items: SSAlbumArtistListEntry[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number | null;
|
||||
totalRecordCount: null | number;
|
||||
};
|
||||
|
||||
export type SSAlbumArtistListEntry = {
|
||||
albumCount: string;
|
||||
artistImageUrl?: string;
|
||||
coverArt?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type SSAlbumArtistListParams = {
|
||||
musicFolderId?: string;
|
||||
};
|
||||
|
||||
export type SSAlbumArtistListResponse = {
|
||||
|
|
@ -47,72 +40,16 @@ export type SSAlbumArtistListResponse = {
|
|||
};
|
||||
};
|
||||
|
||||
export type SSAlbumList = {
|
||||
items: SSAlbumListEntry[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number | null;
|
||||
};
|
||||
|
||||
export type SSAlbumListResponse = {
|
||||
albumList2: {
|
||||
album: SSAlbumListEntry[];
|
||||
};
|
||||
};
|
||||
|
||||
export type SSAlbumDetail = Omit<SSAlbum, 'song'> & { songs: SSSong[] };
|
||||
|
||||
export type SSAlbumDetailResponse = {
|
||||
album: SSAlbum;
|
||||
};
|
||||
|
||||
export type SSArtistInfoParams = {
|
||||
count?: number;
|
||||
id: string;
|
||||
includeNotPresent?: boolean;
|
||||
};
|
||||
|
||||
export type SSArtistInfoResponse = {
|
||||
artistInfo2: SSArtistInfo;
|
||||
};
|
||||
|
||||
export type SSArtistInfo = {
|
||||
biography: string;
|
||||
largeImageUrl?: string;
|
||||
lastFmUrl?: string;
|
||||
mediumImageUrl?: string;
|
||||
musicBrainzId?: string;
|
||||
similarArtist?: {
|
||||
albumCount: string;
|
||||
artistImageUrl?: string;
|
||||
coverArt?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}[];
|
||||
smallImageUrl?: string;
|
||||
};
|
||||
|
||||
export type SSMusicFolder = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type SSGenre = {
|
||||
albumCount?: number;
|
||||
songCount?: number;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type SSArtistIndex = {
|
||||
artist: SSAlbumArtistListEntry[];
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type SSAlbumArtistListEntry = {
|
||||
albumCount: string;
|
||||
artistImageUrl?: string;
|
||||
coverArt?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
export type SSAlbumList = {
|
||||
items: SSAlbumListEntry[];
|
||||
startIndex: number;
|
||||
totalRecordCount: null | number;
|
||||
};
|
||||
|
||||
export type SSAlbumListEntry = {
|
||||
|
|
@ -135,9 +72,111 @@ export type SSAlbumListEntry = {
|
|||
year: number;
|
||||
};
|
||||
|
||||
export type SSAlbum = {
|
||||
song: SSSong[];
|
||||
} & SSAlbumListEntry;
|
||||
export type SSAlbumListParams = {
|
||||
fromYear?: number;
|
||||
genre?: string;
|
||||
musicFolderId?: string;
|
||||
offset?: number;
|
||||
size?: number;
|
||||
toYear?: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type SSAlbumListResponse = {
|
||||
albumList2: {
|
||||
album: SSAlbumListEntry[];
|
||||
};
|
||||
};
|
||||
|
||||
export type SSArtistIndex = {
|
||||
artist: SSAlbumArtistListEntry[];
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type SSArtistInfo = {
|
||||
biography: string;
|
||||
largeImageUrl?: string;
|
||||
lastFmUrl?: string;
|
||||
mediumImageUrl?: string;
|
||||
musicBrainzId?: string;
|
||||
similarArtist?: {
|
||||
albumCount: string;
|
||||
artistImageUrl?: string;
|
||||
coverArt?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}[];
|
||||
smallImageUrl?: string;
|
||||
};
|
||||
|
||||
export type SSArtistInfoParams = {
|
||||
count?: number;
|
||||
id: string;
|
||||
includeNotPresent?: boolean;
|
||||
};
|
||||
|
||||
export type SSArtistInfoResponse = {
|
||||
artistInfo2: SSArtistInfo;
|
||||
};
|
||||
|
||||
export type SSBaseResponse = {
|
||||
serverVersion?: 'string';
|
||||
status: 'string';
|
||||
type?: 'string';
|
||||
version: 'string';
|
||||
};
|
||||
|
||||
export type SSFavorite = null;
|
||||
|
||||
export type SSFavoriteParams = {
|
||||
albumId?: string;
|
||||
artistId?: string;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export type SSFavoriteResponse = null;
|
||||
|
||||
export type SSGenre = {
|
||||
albumCount?: number;
|
||||
songCount?: number;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type SSGenreList = SSGenre[];
|
||||
|
||||
export type SSGenreListResponse = {
|
||||
genres: {
|
||||
genre: SSGenre[];
|
||||
};
|
||||
};
|
||||
|
||||
export type SSMusicFolder = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type SSMusicFolderList = SSMusicFolder[];
|
||||
|
||||
export type SSMusicFolderListResponse = {
|
||||
musicFolders: {
|
||||
musicFolder: SSMusicFolder[];
|
||||
};
|
||||
};
|
||||
|
||||
export type SSRating = null;
|
||||
|
||||
export type SSRatingParams = {
|
||||
id: string;
|
||||
rating: number;
|
||||
};
|
||||
|
||||
export type SSRatingResponse = null;
|
||||
|
||||
export type SSScrobbleParams = {
|
||||
id: string;
|
||||
submission?: boolean;
|
||||
time?: number;
|
||||
};
|
||||
|
||||
export type SSSong = {
|
||||
album: string;
|
||||
|
|
@ -167,39 +206,12 @@ export type SSSong = {
|
|||
year: number;
|
||||
};
|
||||
|
||||
export type SSAlbumListParams = {
|
||||
fromYear?: number;
|
||||
genre?: string;
|
||||
musicFolderId?: string;
|
||||
offset?: number;
|
||||
size?: number;
|
||||
toYear?: number;
|
||||
type: string;
|
||||
export type SSTopSongList = {
|
||||
items: SSSong[];
|
||||
startIndex: number;
|
||||
totalRecordCount: null | number;
|
||||
};
|
||||
|
||||
export type SSAlbumArtistListParams = {
|
||||
musicFolderId?: string;
|
||||
};
|
||||
|
||||
export type SSFavoriteParams = {
|
||||
albumId?: string;
|
||||
artistId?: string;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export type SSFavorite = null;
|
||||
|
||||
export type SSFavoriteResponse = null;
|
||||
|
||||
export type SSRatingParams = {
|
||||
id: string;
|
||||
rating: number;
|
||||
};
|
||||
|
||||
export type SSRating = null;
|
||||
|
||||
export type SSRatingResponse = null;
|
||||
|
||||
export type SSTopSongListParams = {
|
||||
artist: string;
|
||||
count?: number;
|
||||
|
|
@ -210,15 +222,3 @@ export type SSTopSongListResponse = {
|
|||
song: SSSong[];
|
||||
};
|
||||
};
|
||||
|
||||
export type SSTopSongList = {
|
||||
items: SSSong[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number | null;
|
||||
};
|
||||
|
||||
export type SSScrobbleParams = {
|
||||
id: string;
|
||||
submission?: boolean;
|
||||
time?: number;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { initClient, initContract } from '@ts-rest/core';
|
||||
import axios, { Method, AxiosError, isAxiosError, AxiosResponse } from 'axios';
|
||||
import axios, { AxiosError, AxiosResponse, isAxiosError, Method } from 'axios';
|
||||
import omitBy from 'lodash/omitBy';
|
||||
import qs from 'qs';
|
||||
import { z } from 'zod';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import { ServerListItem } from '/@/renderer/api/types';
|
||||
import { toast } from '/@/renderer/components/toast/index';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
const c = initContract();
|
||||
|
||||
|
|
@ -284,15 +285,15 @@ const silentlyTransformResponse = (data: any) => {
|
|||
};
|
||||
|
||||
export const ssApiClient = (args: {
|
||||
server: ServerListItem | null;
|
||||
server: null | ServerListItem;
|
||||
signal?: AbortSignal;
|
||||
silent?: boolean;
|
||||
url?: string;
|
||||
}) => {
|
||||
const { server, url, signal, silent } = args;
|
||||
const { server, signal, silent, url } = args;
|
||||
|
||||
return initClient(contract, {
|
||||
api: async ({ path, method, headers, body }) => {
|
||||
api: async ({ body, headers, method, path }) => {
|
||||
let baseUrl: string | undefined;
|
||||
const authParams: Record<string, any> = {};
|
||||
|
||||
|
|
@ -339,7 +340,7 @@ export const ssApiClient = (args: {
|
|||
headers: result.headers as any,
|
||||
status: result.status,
|
||||
};
|
||||
} catch (e: Error | AxiosError | any) {
|
||||
} catch (e: any | AxiosError | Error) {
|
||||
if (isAxiosError(e)) {
|
||||
if (e.code === 'ERR_NETWORK') {
|
||||
throw new Error(
|
||||
|
|
|
|||
|
|
@ -2,44 +2,45 @@ import dayjs from 'dayjs';
|
|||
import filter from 'lodash/filter';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import md5 from 'md5';
|
||||
|
||||
import { ServerFeatures } from '/@/renderer/api/features-types';
|
||||
import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
||||
import { ssNormalize } from '/@/renderer/api/subsonic/subsonic-normalize';
|
||||
import { AlbumListSortType, SubsonicExtensions } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import {
|
||||
LibraryItem,
|
||||
Song,
|
||||
ControllerEndpoint,
|
||||
sortSongList,
|
||||
sortAlbumArtistList,
|
||||
PlaylistListSort,
|
||||
GenreListSort,
|
||||
AlbumListSort,
|
||||
ControllerEndpoint,
|
||||
GenreListSort,
|
||||
LibraryItem,
|
||||
PlaylistListSort,
|
||||
Song,
|
||||
sortAlbumArtistList,
|
||||
sortAlbumList,
|
||||
SortOrder,
|
||||
sortSongList,
|
||||
} from '/@/renderer/api/types';
|
||||
import { randomString } from '/@/renderer/utils';
|
||||
import { ServerFeatures } from '/@/renderer/api/features-types';
|
||||
|
||||
const ALBUM_LIST_SORT_MAPPING: Record<AlbumListSort, AlbumListSortType | undefined> = {
|
||||
[AlbumListSort.RANDOM]: AlbumListSortType.RANDOM,
|
||||
[AlbumListSort.ALBUM_ARTIST]: AlbumListSortType.ALPHABETICAL_BY_ARTIST,
|
||||
[AlbumListSort.PLAY_COUNT]: AlbumListSortType.FREQUENT,
|
||||
[AlbumListSort.RECENTLY_ADDED]: AlbumListSortType.NEWEST,
|
||||
[AlbumListSort.FAVORITED]: AlbumListSortType.STARRED,
|
||||
[AlbumListSort.YEAR]: AlbumListSortType.BY_YEAR,
|
||||
[AlbumListSort.NAME]: AlbumListSortType.ALPHABETICAL_BY_NAME,
|
||||
[AlbumListSort.COMMUNITY_RATING]: undefined,
|
||||
[AlbumListSort.DURATION]: undefined,
|
||||
[AlbumListSort.CRITIC_RATING]: undefined,
|
||||
[AlbumListSort.RATING]: undefined,
|
||||
[AlbumListSort.ARTIST]: undefined,
|
||||
[AlbumListSort.COMMUNITY_RATING]: undefined,
|
||||
[AlbumListSort.CRITIC_RATING]: undefined,
|
||||
[AlbumListSort.DURATION]: undefined,
|
||||
[AlbumListSort.FAVORITED]: AlbumListSortType.STARRED,
|
||||
[AlbumListSort.NAME]: AlbumListSortType.ALPHABETICAL_BY_NAME,
|
||||
[AlbumListSort.PLAY_COUNT]: AlbumListSortType.FREQUENT,
|
||||
[AlbumListSort.RANDOM]: AlbumListSortType.RANDOM,
|
||||
[AlbumListSort.RATING]: undefined,
|
||||
[AlbumListSort.RECENTLY_ADDED]: AlbumListSortType.NEWEST,
|
||||
[AlbumListSort.RECENTLY_PLAYED]: AlbumListSortType.RECENT,
|
||||
[AlbumListSort.RELEASE_DATE]: undefined,
|
||||
[AlbumListSort.SONG_COUNT]: undefined,
|
||||
[AlbumListSort.YEAR]: AlbumListSortType.BY_YEAR,
|
||||
};
|
||||
|
||||
export const SubsonicController: ControllerEndpoint = {
|
||||
addToPlaylist: async ({ body, query, apiClientProps }) => {
|
||||
addToPlaylist: async ({ apiClientProps, body, query }) => {
|
||||
const res = await ssApiClient(apiClientProps).updatePlaylist({
|
||||
query: {
|
||||
playlistId: query.id,
|
||||
|
|
@ -98,7 +99,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
createFavorite: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).createFavorite({
|
||||
query: {
|
||||
|
|
@ -114,7 +115,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
|
||||
return null;
|
||||
},
|
||||
createPlaylist: async ({ body, apiClientProps }) => {
|
||||
createPlaylist: async ({ apiClientProps, body }) => {
|
||||
const res = await ssApiClient(apiClientProps).createPlaylist({
|
||||
query: {
|
||||
name: body.name,
|
||||
|
|
@ -131,7 +132,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
deleteFavorite: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).removeFavorite({
|
||||
query: {
|
||||
|
|
@ -148,7 +149,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
deletePlaylist: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).deletePlaylist({
|
||||
query: {
|
||||
|
|
@ -163,7 +164,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
getAlbumArtistDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
|
||||
query: {
|
||||
|
|
@ -198,7 +199,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getAlbumArtistList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getArtists({
|
||||
query: {
|
||||
|
|
@ -237,7 +238,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
getAlbumArtistListCount: (args) =>
|
||||
SubsonicController.getAlbumArtistList(args).then((res) => res!.totalRecordCount!),
|
||||
getAlbumDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getAlbum({
|
||||
query: {
|
||||
|
|
@ -252,7 +253,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
return ssNormalize.album(res.body.album, apiClientProps.server);
|
||||
},
|
||||
getAlbumList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (query.searchTerm) {
|
||||
const res = await ssApiClient(apiClientProps).search3({
|
||||
|
|
@ -398,7 +399,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getAlbumListCount: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
if (query.searchTerm) {
|
||||
let fetchNextPage = true;
|
||||
|
|
@ -516,7 +517,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
return totalRecordCount;
|
||||
},
|
||||
getArtistList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getArtists({
|
||||
query: {
|
||||
|
|
@ -570,7 +571,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
'&c=Feishin'
|
||||
);
|
||||
},
|
||||
getGenreList: async ({ query, apiClientProps }) => {
|
||||
getGenreList: async ({ apiClientProps, query }) => {
|
||||
const sortOrder = query.sortOrder.toLowerCase() as 'asc' | 'desc';
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getGenres({});
|
||||
|
|
@ -624,7 +625,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getPlaylistDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getPlaylist({
|
||||
query: {
|
||||
|
|
@ -638,7 +639,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
|
||||
return ssNormalize.playlist(res.body.playlist, apiClientProps.server);
|
||||
},
|
||||
getPlaylistList: async ({ query, apiClientProps }) => {
|
||||
getPlaylistList: async ({ apiClientProps, query }) => {
|
||||
const sortOrder = query.sortOrder.toLowerCase() as 'asc' | 'desc';
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getPlaylists({});
|
||||
|
|
@ -686,7 +687,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
totalRecordCount: results.length,
|
||||
};
|
||||
},
|
||||
getPlaylistListCount: async ({ query, apiClientProps }) => {
|
||||
getPlaylistListCount: async ({ apiClientProps, query }) => {
|
||||
const res = await ssApiClient(apiClientProps).getPlaylists({});
|
||||
|
||||
if (res.status !== 200) {
|
||||
|
|
@ -705,7 +706,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
|
||||
return results.length;
|
||||
},
|
||||
getPlaylistSongList: async ({ query, apiClientProps }) => {
|
||||
getPlaylistSongList: async ({ apiClientProps, query }) => {
|
||||
const res = await ssApiClient(apiClientProps).getPlaylist({
|
||||
query: {
|
||||
id: query.id,
|
||||
|
|
@ -731,7 +732,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getRandomSongList: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getRandomSongList({
|
||||
query: {
|
||||
|
|
@ -842,7 +843,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
}, []);
|
||||
},
|
||||
getSongDetail: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getSong({
|
||||
query: {
|
||||
|
|
@ -856,7 +857,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
|
||||
return ssNormalize.song(res.body.song, apiClientProps.server);
|
||||
},
|
||||
getSongList: async ({ query, apiClientProps }) => {
|
||||
getSongList: async ({ apiClientProps, query }) => {
|
||||
const fromAlbumPromises = [];
|
||||
const artistDetailPromises = [];
|
||||
let results: any[] = [];
|
||||
|
|
@ -1028,7 +1029,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getSongListCount: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
let fetchNextPage = true;
|
||||
let startIndex = 0;
|
||||
|
|
@ -1196,7 +1197,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
return totalRecordCount;
|
||||
},
|
||||
getStructuredLyrics: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getStructuredLyrics({
|
||||
query: {
|
||||
|
|
@ -1238,7 +1239,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
});
|
||||
},
|
||||
getTopSongs: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getTopSongsList({
|
||||
query: {
|
||||
|
|
@ -1261,7 +1262,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
getTranscodingUrl: (args) => {
|
||||
const { base, format, bitrate } = args.query;
|
||||
const { base, bitrate, format } = args.query;
|
||||
let url = base;
|
||||
if (format) {
|
||||
url += `&format=${format}`;
|
||||
|
|
@ -1272,7 +1273,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
|
||||
return url;
|
||||
},
|
||||
removeFromPlaylist: async ({ query, apiClientProps }) => {
|
||||
removeFromPlaylist: async ({ apiClientProps, query }) => {
|
||||
const res = await ssApiClient(apiClientProps).updatePlaylist({
|
||||
query: {
|
||||
playlistId: query.id,
|
||||
|
|
@ -1287,7 +1288,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
scrobble: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).scrobble({
|
||||
query: {
|
||||
|
|
@ -1304,7 +1305,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
},
|
||||
|
||||
search: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).search3({
|
||||
query: {
|
||||
|
|
@ -1335,7 +1336,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
};
|
||||
},
|
||||
setRating: async (args) => {
|
||||
const { query, apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const itemIds = query.item.map((item) => item.id);
|
||||
|
||||
|
|
@ -1351,7 +1352,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||
return null;
|
||||
},
|
||||
updatePlaylist: async (args) => {
|
||||
const { body, query, apiClientProps } = args;
|
||||
const { apiClientProps, body, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).updatePlaylist({
|
||||
query: {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import {
|
||||
QueueSong,
|
||||
LibraryItem,
|
||||
AlbumArtist,
|
||||
Album,
|
||||
AlbumArtist,
|
||||
Genre,
|
||||
LibraryItem,
|
||||
Playlist,
|
||||
QueueSong,
|
||||
RelatedArtist,
|
||||
ServerListItem,
|
||||
ServerType,
|
||||
Playlist,
|
||||
Genre,
|
||||
RelatedArtist,
|
||||
} from '/@/renderer/api/types';
|
||||
|
||||
const getCoverArtUrl = (args: {
|
||||
|
|
@ -37,9 +38,9 @@ const getCoverArtUrl = (args: {
|
|||
|
||||
const getArtists = (
|
||||
item:
|
||||
| z.infer<typeof ssType._response.song>
|
||||
| z.infer<typeof ssType._response.album>
|
||||
| z.infer<typeof ssType._response.albumListEntry>,
|
||||
| z.infer<typeof ssType._response.albumListEntry>
|
||||
| z.infer<typeof ssType._response.song>,
|
||||
) => {
|
||||
const albumArtists: RelatedArtist[] = item.albumArtists
|
||||
? item.albumArtists.map((item) => ({
|
||||
|
|
@ -69,7 +70,7 @@ const getArtists = (
|
|||
},
|
||||
];
|
||||
|
||||
let participants: Record<string, RelatedArtist[]> | null = null;
|
||||
let participants: null | Record<string, RelatedArtist[]> = null;
|
||||
|
||||
if (item.contributors) {
|
||||
participants = {};
|
||||
|
|
@ -98,9 +99,9 @@ const getArtists = (
|
|||
|
||||
const getGenres = (
|
||||
item:
|
||||
| z.infer<typeof ssType._response.song>
|
||||
| z.infer<typeof ssType._response.album>
|
||||
| z.infer<typeof ssType._response.albumListEntry>,
|
||||
| z.infer<typeof ssType._response.albumListEntry>
|
||||
| z.infer<typeof ssType._response.song>,
|
||||
): Genre[] => {
|
||||
return item.genres
|
||||
? item.genres.map((genre) => ({
|
||||
|
|
@ -123,7 +124,7 @@ const getGenres = (
|
|||
|
||||
const normalizeSong = (
|
||||
item: z.infer<typeof ssType._response.song>,
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
size?: number,
|
||||
): QueueSong => {
|
||||
const imageUrl =
|
||||
|
|
@ -194,7 +195,7 @@ const normalizeAlbumArtist = (
|
|||
item:
|
||||
| z.infer<typeof ssType._response.albumArtist>
|
||||
| z.infer<typeof ssType._response.artistListEntry>,
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): AlbumArtist => {
|
||||
const imageUrl =
|
||||
|
|
@ -229,7 +230,7 @@ const normalizeAlbumArtist = (
|
|||
|
||||
const normalizeAlbum = (
|
||||
item: z.infer<typeof ssType._response.album> | z.infer<typeof ssType._response.albumListEntry>,
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
imageSize?: number,
|
||||
): Album => {
|
||||
const imageUrl =
|
||||
|
|
@ -280,7 +281,7 @@ const normalizePlaylist = (
|
|||
item:
|
||||
| z.infer<typeof ssType._response.playlist>
|
||||
| z.infer<typeof ssType._response.playlistListEntry>,
|
||||
server: ServerListItem | null,
|
||||
server: null | ServerListItem,
|
||||
): Playlist => {
|
||||
return {
|
||||
description: item.comment || null,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,10 +3,11 @@ import isElectron from 'is-electron';
|
|||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverGte from 'semver/functions/gte';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
import { ServerListItem } from '/@/renderer/api/types';
|
||||
import { toast } from '/@/renderer/components/toast';
|
||||
import { useAuthStore } from '/@/renderer/store';
|
||||
import { ServerListItem } from '/@/renderer/api/types';
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
|
||||
// Since ts-rest client returns a strict response type, we need to add the headers to the body object
|
||||
export const resultWithHeaders = <ItemType extends z.ZodTypeAny>(itemSchema: ItemType) => {
|
||||
|
|
@ -29,7 +30,7 @@ export const resultSubsonicBaseResponse = <ItemType extends z.ZodRawShape>(
|
|||
});
|
||||
};
|
||||
|
||||
export const authenticationFailure = (currentServer: ServerListItem | null) => {
|
||||
export const authenticationFailure = (currentServer: null | ServerListItem) => {
|
||||
toast.error({
|
||||
message: 'Your session has expired.',
|
||||
});
|
||||
|
|
@ -43,7 +44,7 @@ export const authenticationFailure = (currentServer: ServerListItem | null) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const hasFeature = (server: ServerListItem | null, feature: ServerFeature): boolean => {
|
||||
export const hasFeature = (server: null | ServerListItem, feature: ServerFeature): boolean => {
|
||||
if (!server || !server.features) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue