From df0adbae3de20105fe7765bf4d7a0cbf93597ff5 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Fri, 18 Feb 2022 14:27:39 +0300 Subject: [PATCH 01/23] Disable implicit any --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index ee4527663..82a831f89 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, - "noImplicitAny": false, + "noImplicitAny": true, "allowSyntheticDefaultImports": true, "strict": true, "module": "ESNext", From 6e1845ef0c92b27b8408e9f7f33987be8b040962 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 15 Feb 2022 23:17:31 +0300 Subject: [PATCH 02/23] Add dependencies --- package-lock.json | 53 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +++ 2 files changed, 56 insertions(+) diff --git a/package-lock.json b/package-lock.json index 68d5a3804..6697a4d95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2651,6 +2651,21 @@ "localforage": "*" } }, + "@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "dev": true + }, + "@types/lodash-es": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.6.tgz", + "integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -2687,6 +2702,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", + "dev": true + }, "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -2699,6 +2720,26 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, + "@types/react": { + "version": "17.0.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz", + "integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", + "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -2714,6 +2755,12 @@ "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", "dev": true }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, "@types/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", @@ -4686,6 +4733,12 @@ "css-tree": "^1.1.2" } }, + "csstype": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", + "dev": true + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", diff --git a/package.json b/package.json index b37e61f0d..39268a956 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "@babel/preset-env": "7.16.11", "@babel/preset-react": "7.16.7", "@babel/preset-typescript": "7.16.7", + "@types/lodash-es": "4.17.6", + "@types/react": "17.0.39", + "@types/react-dom": "17.0.11", "@typescript-eslint/eslint-plugin": "5.12.0", "@typescript-eslint/parser": "5.12.0", "@uupaa/dynamic-import-polyfill": "1.0.2", From 7ed78e0daa7b9189cd05a912a783c981526b7807 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 15 Feb 2022 23:42:46 +0300 Subject: [PATCH 03/23] Add jellyfin-apiclient declarations --- package-lock.json | 26 ++++ package.json | 1 + src/apiclient.d.ts | 350 +++++++++++++++++++++++++++++++++++++++++++++ src/global.d.ts | 5 +- 4 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 src/apiclient.d.ts diff --git a/package-lock.json b/package-lock.json index 6697a4d95..bf8808747 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2513,6 +2513,17 @@ "string.prototype.matchall": "^4.0.6" } }, + "@thornbill/jellyfin-sdk": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@thornbill/jellyfin-sdk/-/jellyfin-sdk-0.4.1.tgz", + "integrity": "sha512-DuUeSiIvk1qcEYp9oxnKc/e4T0zj3LujXhITQ6L6TqGOo8miNiBgU8OjmidAUwPc2ibCLAK2rM5BcNVAEwaFmw==", + "dev": true, + "requires": { + "axios": "^0.26.0", + "compare-versions": "^4.0.0", + "normalize-url": "^6.1.0" + } + }, "@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -3587,6 +3598,15 @@ "integrity": "sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==", "dev": true }, + "axios": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz", + "integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==", + "dev": true, + "requires": { + "follow-redirects": "^1.14.8" + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -4199,6 +4219,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "compare-versions": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-4.1.3.tgz", + "integrity": "sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg==", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", diff --git a/package.json b/package.json index 39268a956..d27e43887 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@babel/preset-env": "7.16.11", "@babel/preset-react": "7.16.7", "@babel/preset-typescript": "7.16.7", + "@thornbill/jellyfin-sdk": "0.4.1", "@types/lodash-es": "4.17.6", "@types/react": "17.0.39", "@types/react-dom": "17.0.11", diff --git a/src/apiclient.d.ts b/src/apiclient.d.ts new file mode 100644 index 000000000..565ac35a8 --- /dev/null +++ b/src/apiclient.d.ts @@ -0,0 +1,350 @@ +// TODO: Move to jellyfin-apiclient +declare module 'jellyfin-apiclient' { + import { + AllThemeMediaResult, + AuthenticationResult, + BaseItemDto, + BaseItemDtoQueryResult, + BufferRequestDto, + ClientCapabilities, + CountryInfo, + CultureDto, + DeviceOptions, + DisplayPreferencesDto, + EndPointInfo, + FileSystemEntryInfo, + GeneralCommand, + GroupInfoDto, + GuideInfo, + IgnoreWaitRequestDto, + ImageInfo, + ImageProviderInfo, + ImageType, + ItemCounts, + LiveTvInfo, + MovePlaylistItemRequestDto, + NewGroupRequestDto, + NextItemRequestDto, + NotificationResultDto, + NotificationsSummaryDto, + ParentalRating, + PingRequestDto, + PlaybackInfoResponse, + PlaybackProgressInfo, + PlaybackStartInfo, + PlaybackStopInfo, + PlayCommand, + PlaystateCommand, + PluginInfo, + PluginSecurityInfo, + PreviousItemRequestDto, + QueryFiltersLegacy, + QueueRequestDto, + QuickConnectResult, + QuickConnectState, + ReadyRequestDto, + RecommendationDto, + RemoteImageResult, + RemoveFromPlaylistRequestDto, + SearchHintResult, + SeekRequestDto, + SeriesTimerInfoDto, + SeriesTimerInfoDtoQueryResult, + ServerConfiguration, + SessionInfo, + SetPlaylistItemRequestDto, + SetRepeatModeRequestDto, + SetShuffleModeRequestDto, + SystemInfo, + TaskInfo, + TaskTriggerInfo, + TimerInfoDto, + TimerInfoDtoQueryResult, + UserConfiguration, + UserDto, + UserItemDataDto, + UserPolicy, + UtcTimeResponse, + VirtualFolderInfo + } from '@thornbill/jellyfin-sdk/dist/generated-client'; + + class ApiClient { + constructor(serverAddress: string, appName: string, appVersion: string, deviceName: string, deviceId: string); + + accessToken(): string; + addMediaPath(virtualFolderName: string, mediaPath: string, networkSharePath: string, refreshLibrary?: boolean): Promise; + addVirtualFolder(name: string, type?: string, refreshLibrary?: boolean, libraryOptions?: any): Promise; + appName(): string; + appVersion(): string; + authenticateUserByName(name: string, password: string): Promise; + cancelLiveTvSeriesTimer(id: string): Promise; + cancelLiveTvTimer(id: string): Promise; + cancelSyncItems(itemIds: string[], targetId?: string): Promise; + clearAuthenticationInfo(): void; + clearUserItemRating(userId: string, itemId: string): Promise; + closeWebSocket(): void; + createLiveTvSeriesTimer(item: string): Promise; + createLiveTvTimer(item: string): Promise; + createPackageReview(review: any): Promise; + createSyncPlayGroup(options?: NewGroupRequestDto): Promise; + createUser(user: UserDto): Promise; + deleteDevice(deviceId: string): Promise; + deleteItemImage(itemId: string, imageType: ImageType, imageIndex?: number): Promise; + deleteItem(itemId: string): Promise; + deleteLiveTvRecording(id: string): Promise; + deleteUserImage(userId: string, imageType: ImageType, imageIndex?: number): Promise; + deleteUser(userId: string): Promise; + detectBitrate(force: boolean): Promise; + deviceId(): string; + deviceName(): string; + disablePlugin(id: string, version: string): Promise; + downloadRemoteImage(options: any): Promise; + enablePlugin(id: string, version: string): Promise; + encodeName(name: string): string; + ensureWebSocket(): void; + fetch(request: any, includeAuthorization?: boolean): Promise; + fetchWithFailover(request: any, enableReconnection?: boolean): Promise; + getAdditionalVideoParts(userId?: string, itemId: string): Promise; + getAlbumArtists(userId: string, options?: any): Promise; + getAncestorItems(itemId: string, userId?: string): Promise; + getArtist(name: string, userId?: string): Promise; + getArtists(userId: string, options?: any): Promise; + getAvailablePlugins(options?: any): Promise; + getAvailableRemoteImages(options: any): Promise; + getContentUploadHistory(): Promise; + getCountries(): Promise; + getCriticReviews(itemId: string, options?: any): Promise; + getCultures(): Promise; + getCurrentUserId(): string; + getDateParamValue(date: Date): string; + getDefaultImageQuality(imageType: ImageType): number; + getDevicesOptions(): Promise; + getDirectoryContents(path: string, options?: any): Promise; + getDisplayPreferences(id: string, userId: string, app: string): Promise; + getDownloadSpeed(byteSize: number): Promise; + getDrives(): Promise; + getEndpointInfo(): Promise; + getEpisodes(itemId: string, options?: any): Promise; + getFilters(options?: any): Promise; + getGenre(name: string, userId?: string): Promise; + getGenres(userId: string, options?: any): Promise; + getImageUrl(itemId: string, options?: any): string; + getInstalledPlugins(): Promise; + getInstantMixFromItem(itemId: string, options?: any): Promise; + getIntros(itemId: string): Promise; + getItemCounts(userId?: string): Promise; + getItemDownloadUrl(itemId: string): string; + getItemImageInfos(itemId: string): Promise; + getItems(userId: string, options?: any): Promise; + getItem(userId: string, itemId: string): Promise; + getJSON(url: string, includeAuthorization?: boolean): Promise; + getLatestItems(options?: any): Promise; + getLiveStreamMediaInfo(liveStreamId: string): Promise; + getLiveTvChannel(id: string, userId?: string): Promise; + getLiveTvChannels(options?: any): Promise; + getLiveTvGuideInfo(userId: string): Promise; + getLiveTvInfo(userId: string): Promise; + getLiveTvProgram(id: string, userId?: string): Promise; + getLiveTvPrograms(options?: any): Promise; + getLiveTvRecommendedPrograms(options?: any): Promise; + getLiveTvRecordingGroup(id: string): Promise; + getLiveTvRecordingGroups(options?: any): Promise; + getLiveTvRecording(id: string, userId?: string): Promise; + getLiveTvRecordingSeries(options?: any): Promise; + getLiveTvRecordings(options?: any): Promise; + getLiveTvSeriesTimer(id: string): Promise; + getLiveTvSeriesTimers(options?: any): Promise; + getLiveTvTimer(id: string): Promise; + getLiveTvTimers(options?: any): Promise; + getLocalTrailers(userId: string, itemId: string): Promise; + getMovieRecommendations(options?: any): Promise; + getMusicGenre(name: string, userId?: string): Promise; + getMusicGenres(userId: string, options?: any): Promise; + getNamedConfiguration(name: string): Promise; + getNetworkDevices(): Promise; + getNetworkShares(path: string): Promise; + getNewLiveTvTimerDefaults(options?: any): Promise; + getNextUpEpisodes(options?: any): Promise; + getNotificationSummary(userId: string): Promise; + getNotifications(userId: string, options?: any): Promise; + getPackageInfo(name: string, guid: string): Promise; + getPackageReviews(packageId: string, minRating?: string, maxRating?: string, limit?: string): Promise; + getParentalRatings(): Promise; + getParentPath(path: string): Promise; + getPeople(userId: string, options?: any): Promise; + getPerson(name: string, userId?: string): Promise; + getPhysicalPaths(): Promise; + getPlaybackInfo(itemId: string, options: any, deviceProfile: any): Promise; + getPluginConfiguration(id: string): Promise; + getPublicSystemInfo(): Promise; + getPublicUsers(): Promise; + getQuickConnect(verb: string): Promise; + getReadySyncItems(deviceId: string): Promise; + getRecordingFolders(userId: string): Promise; + getRegistrationInfo(feature: string): Promise; + getRemoteImageProviders(options: any): Promise; + getResumableItems(userId: string, options?: any): Promise; + getRootFolder(userId: string): Promise; + getSavedEndpointInfo(): EndPointInfo; + getScaledImageUrl(itemId: string, options?: any): string; + getScheduledTask(id: string): Promise; + getScheduledTasks(options?: any): Promise; + getSearchHints(options?: any): Promise; + getSeasons(itemId: string, options?: any): Promise; + getServerConfiguration(): Promise; + getServerTime(): Promise; + getSessions(options?: any): Promise; + getSimilarItems(itemId: string, options?: any): Promise; + getSpecialFeatures(userId: string, itemId: string): Promise; + getStudio(name: string, userId?: string): Promise; + getStudios(userId: string, options?: any): Promise; + getSyncPlayGroups(): Promise; + getSyncStatus(itemId: string): Promise; + getSystemInfo(): Promise; + getThemeMedia(userId?: string, itemId: string, inherit?: boolean): Promise; + getThumbImageUrl(item: BaseItemDto, options?: any): string; + getUpcomingEpisodes(options?: any): Promise; + getUrl(name: string, params?: any, serverAddress?: string): string; + get(url: string): Promise; + getUserImageUrl(userId: string, options?: any): string; + getUsers(options?: any): Promise; + getUser(userId: string): Promise; + getUserViews(options?: any, userId: string): Promise; + getVirtualFolders(): Promise; + handleMessageReceived(msg: any): void; + installPlugin(name: string, guid: string, version?: string): Promise; + isLoggedIn(): boolean; + isMessageChannelOpen(): boolean; + isMinServerVersion(version: string): boolean; + isWebSocketOpen(): boolean; + isWebSocketOpenOrConnecting(): boolean; + isWebSocketSupported(): boolean; + joinSyncPlayGroup(options?: any): Promise; + leaveSyncPlayGroup(): Promise; + logout(): Promise; + markNotificationsRead(userId: string, idList: string[], isRead: boolean): Promise; + markPlayed(userId: string, itemId: string, date: Date): Promise; + markUnplayed(userId: string, itemId: string, date: Date): Promise; + openWebSocket(): void; + quickConnect(secret: string): Promise; + refreshItem(itemId: string, options?: any): Promise; + removeMediaPath(virtualFolderName: string, mediaPath: string, refreshLibrary?: boolean): Promise; + removeVirtualFolder(name: string, refreshLibrary?: boolean): Promise; + renameVirtualFolder(name: string, newName: string, refreshLibrary?: boolean): Promise; + reportCapabilities(capabilities: ClientCapabilities): Promise; + reportOfflineActions(actions: any): Promise; + reportPlaybackProgress(options: PlaybackProgressInfo): Promise; + reportPlaybackStart(options: PlaybackStartInfo): Promise; + reportPlaybackStopped(options: PlaybackStopInfo): Promise; + reportSyncJobItemTransferred(syncJobItemId: string): Promise; + requestSyncPlayBuffering(options?: BufferRequestDto): Promise; + requestSyncPlayMovePlaylistItem(options?: MovePlaylistItemRequestDto): Promise; + requestSyncPlayNextItem(options?: NextItemRequestDto): Promise; + requestSyncPlayPause(): Promise; + requestSyncPlayPreviousItem(options?: PreviousItemRequestDto): Promise; + requestSyncPlayQueue(options?: QueueRequestDto): Promise; + requestSyncPlayReady(options?: ReadyRequestDto): Promise; + requestSyncPlayRemoveFromPlaylist(options?: RemoveFromPlaylistRequestDto): Promise; + requestSyncPlaySeek(options?: SeekRequestDto): Promise; + requestSyncPlaySetIgnoreWait(options?: IgnoreWaitRequestDto): Promise; + requestSyncPlaySetNewQueue(options?: NewGroupRequestDto): Promise; + requestSyncPlaySetPlaylistItem(options?: SetPlaylistItemRequestDto): Promise; + requestSyncPlaySetRepeatMode(options?: SetRepeatModeRequestDto): Promise; + requestSyncPlaySetShuffleMode(options?: SetShuffleModeRequestDto): Promise; + requestSyncPlayUnpause(): Promise; + resetEasyPassword(userId: string): Promise; + resetLiveTvTuner(id: string): Promise; + resetUserPassword(userId: string): Promise; + restartServer(): Promise; + sendCommand(sessionId: string, command: any): Promise; + sendMessageCommand(sessionId: string, options: GeneralCommand): Promise; + sendMessage(name: string, data: any): void; + sendPlayCommand(sessionId: string, options: PlayCommand): Promise; + sendPlayStateCommand(sessionId: string, command: PlaystateCommand, options?: any): Promise; + sendSyncPlayPing(options?: PingRequestDto): Promise; + sendWebSocketMessage(name: string, data: any): void; + serverAddress(val?: string): string; + serverId(): string; + serverVersion(): string + setAuthenticationInfo(accessKey?: string, userId?: string): void; + setRequestHeaders(headers: any): void; + setSystemInfo(info: SystemInfo): void; + shutdownServer(): Promise; + startScheduledTask(id: string): Promise; + stopActiveEncodings(playSessionId: string): Promise; + stopScheduledTask(id: string): Promise; + syncData(data: any): Promise; + uninstallPluginByVersion(id: string, version: string): Promise; + uninstallPlugin(id: string): Promise; + updateDisplayPreferences(id: string, obj: DisplayPreferencesDto, userId: string, app: string): Promise; + updateEasyPassword(userId: string, newPassword: string): Promise; + updateFavoriteStatus(userId: string, itemId: string, isFavorite: boolean): Promise; + updateItemImageIndex(itemId: string, imageType: ImageType, imageIndex: number, newIndex: number): Promise; + updateItem(item: BaseItemDto): Promise; + updateLiveTvSeriesTimer(item: SeriesTimerInfoDto): Promise; + updateLiveTvTimer(item: TimerInfoDto): Promise; + updateMediaPath(virtualFolderName: string, pathInfo: any): Promise; + updateNamedConfiguration(name: string, configuration: any): Promise; + updatePluginConfiguration(id: string, configuration: any): Promise; + updatePluginSecurityInfo(info: PluginSecurityInfo): Promise; + updateScheduledTaskTriggers(id: string, triggers: TaskTriggerInfo[]): Promise; + updateServerConfiguration(configuration: ServerConfiguration): Promise; + updateServerInfo(server: any, serverUrl: string): void; + updateUserConfiguration(userId: string, configuration: UserConfiguration): Promise; + updateUserItemRating(userId: string, itemId: string, likes: boolean): Promise; + updateUserPassword(userId: string, currentPassword: string, newPassword: string): Promise; + updateUserPolicy(userId: string, policy: UserPolicy): Promise; + updateUser(user: UserDto): Promise; + updateVirtualFolderOptions(id: string, libraryOptions?: any): Promise; + uploadItemImage(itemId: string, imageType: ImageType, file: File): Promise; + uploadItemSubtitle(itemId: string, language: string, isForced: boolean, file: File): Promise; + uploadUserImage(userId: string, imageType: ImageType, file: File): Promise; + } + + class AppStore { + constructor(); + + getItem(name: string): string|null; + removeItem(name: string): void; + setItem(name: string, value: string): void; + } + + class ConnectionManager { + constructor(credentialProvider: Credentials, appName: string, appVersion: string, deviceName: string, deviceId: string, capabilities: ClientCapabilities); + + addApiClient(apiClient: ApiClient): void; + clearData(): void; + connect(options?: any): Promise; + connectToAddress(address: string, options?: any): Promise; + connectToServer(server: any, options?: any): Promise; + connectToServers(servers: any[], options?: any): Promise; + deleteServer(serverId: string): Promise; + getApiClient(item: BaseItemDto|string): ApiClient; + getApiClients(): ApiClient[]; + getAvailableServers(): any[]; + getOrCreateApiClient(serverId: string): ApiClient; + getSavedServers(): any[]; + handleMessageReceived(msg: any): void; + logout(): Promise; + minServerVersion(val?: string): string; + user(apiClient: ApiClient): Promise; + } + + class Credentials { + constructor(key?: string); + + addOrUpdateServer(list: any[], server: any): any; + clear(): void; + credentials(data?: any): any; + } + + interface Event { + type: string; + } + + const Events: { + off(obj: any, eventName: string, fn: (e: Event, ...args: any[]) => void): void; + on(obj: any, eventName: string, fn: (e: Event, ...args: any[]) => void): void; + trigger(obj: any, eventName: string, ...args: any[]): void; + }; +} diff --git a/src/global.d.ts b/src/global.d.ts index e3243f505..942f37b0c 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,5 +1,8 @@ export declare global { + import { ApiClient, Events } from 'jellyfin-apiclient'; + interface Window { - ApiClient: any; + ApiClient: ApiClient; + Events: Events; } } From 85e9aff281b9eeced79fcc1407fd1804cdf21d29 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sat, 19 Feb 2022 12:43:11 +0300 Subject: [PATCH 04/23] docs: fix documentation for TypeScript --- src/components/cardbuilder/cardBuilder.js | 18 +++++++++++++----- src/scripts/dom.js | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 17bc60818..781eb3684 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -481,12 +481,20 @@ import ServerConnections from '../ServerConnections'; return null; } + /** + * @typedef {Object} CardImageUrl + * @property {string} imgUrl - Image URL. + * @property {string} blurhash - Image blurhash. + * @property {boolean} forceName - Force name. + * @property {boolean} coverImage - Use cover style. + */ + /** Get the URL of the card's image. * @param {Object} item - Item for which to generate a card. * @param {Object} apiClient - API client object. * @param {Object} options - Options of the card. * @param {string} shape - Shape of the desired image. - * @returns {Object} Object representing the URL of the card's image. + * @returns {CardImageUrl} Object representing the URL of the card's image. */ function getCardImageUrl(item, apiClient, options, shape) { item = item.ProgramInfo || item; @@ -639,7 +647,7 @@ import ServerConnections from '../ServerConnections'; /** * Generates an index used to select the default color of a card based on a string. - * @param {string} str - String to use for generating the index. + * @param {?string} [str] - String to use for generating the index. * @returns {number} Index of the color. */ function getDefaultColorIndex(str) { @@ -726,8 +734,8 @@ import ServerConnections from '../ServerConnections'; /** * Returns the air time text for the item based on the given times. * @param {object} item - Item used to generate the air time text. - * @param {string} showAirDateTime - ISO8601 date for the start of the show. - * @param {string} showAirEndTime - ISO8601 date for the end of the show. + * @param {boolean} showAirDateTime - ISO8601 date for the start of the show. + * @param {boolean} showAirEndTime - ISO8601 date for the end of the show. * @returns {string} The air time text for the item based on the given dates. */ function getAirTimeText(item, showAirDateTime, showAirEndTime) { @@ -1129,7 +1137,7 @@ import ServerConnections from '../ServerConnections'; /** * Returns the default background class for a card based on a string. - * @param {string} str - Text used to generate the background class. + * @param {?string} [str] - Text used to generate the background class. * @returns {string} CSS classes for default card backgrounds. */ export function getDefaultBackgroundClass(str) { diff --git a/src/scripts/dom.js b/src/scripts/dom.js index 63935edbb..c08230f06 100644 --- a/src/scripts/dom.js +++ b/src/scripts/dom.js @@ -9,7 +9,7 @@ * Returns parent of element with specified attribute value. * @param {HTMLElement} elem - Element whose parent need to find. * @param {string} name - Attribute name. - * @param {mixed} value - Attribute value. + * @param {mixed} [value] - Attribute value. * @returns {HTMLElement} Parent with specified attribute value. */ export function parentWithAttribute(elem, name, value) { From c14daafffb418f49c2850b840975af47161c536f Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Fri, 18 Feb 2022 13:14:26 +0300 Subject: [PATCH 05/23] Remove type casting --- src/components/search/LiveTVSearchResults.tsx | 3 +-- src/components/search/SearchResults.tsx | 3 +-- src/components/search/SearchSuggestions.tsx | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/search/LiveTVSearchResults.tsx b/src/components/search/LiveTVSearchResults.tsx index f449a59fa..9f9874db2 100644 --- a/src/components/search/LiveTVSearchResults.tsx +++ b/src/components/search/LiveTVSearchResults.tsx @@ -72,8 +72,7 @@ const LiveTVSearchResults: FunctionComponent = ({ serv setChannels([]); if (query && collectionType === 'livetv') { - // TODO: Remove type casting once we're using a properly typed API client - const apiClient = (ServerConnections as any).getApiClient(serverId); + const apiClient = ServerConnections.getApiClient(serverId); // Movies row fetchItems(apiClient, { diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index 0b3d8213f..e814eba7f 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -99,8 +99,7 @@ const SearchResults: FunctionComponent = ({ serverId, parent setPeople([]); if (query) { - // TODO: Remove type casting once we're using a properly typed API client - const apiClient = (ServerConnections as any).getApiClient(serverId); + const apiClient = ServerConnections.getApiClient(serverId); // Movie libraries if (!collectionType || isMovies()) { diff --git a/src/components/search/SearchSuggestions.tsx b/src/components/search/SearchSuggestions.tsx index a09016e41..c13659c60 100644 --- a/src/components/search/SearchSuggestions.tsx +++ b/src/components/search/SearchSuggestions.tsx @@ -27,8 +27,7 @@ const SearchSuggestions: FunctionComponent = ({ serverId const [ suggestions, setSuggestions ] = useState([]); useEffect(() => { - // TODO: Remove type casting once we're using a properly typed API client - const apiClient = (ServerConnections as any).getApiClient(serverId); + const apiClient = ServerConnections.getApiClient(serverId); apiClient.getItems(apiClient.getCurrentUserId(), { SortBy: 'IsFavoriteOrLiked,Random', From 8338bb550a2439ed57b635371b7f64f708b43cd8 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sat, 19 Feb 2022 00:22:13 +0300 Subject: [PATCH 06/23] Use type from API --- src/components/search/SearchResultsRow.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/search/SearchResultsRow.tsx b/src/components/search/SearchResultsRow.tsx index bdbce785a..10bdd1d9a 100644 --- a/src/components/search/SearchResultsRow.tsx +++ b/src/components/search/SearchResultsRow.tsx @@ -1,3 +1,4 @@ +import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client'; import React, { FunctionComponent, useEffect, useRef } from 'react'; import cardBuilder from '../cardbuilder/cardBuilder'; @@ -17,7 +18,7 @@ const createScroller = ({ title = '' }) => ({ type SearchResultsRowProps = { title?: string; - items?: Array; // TODO: Should be Array once we have a typed API client + items?: BaseItemDto[]; cardOptions?: Record; } From e30a252c8aa30b9688a51a96e1b67bf711b121d7 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Fri, 18 Feb 2022 23:19:39 +0300 Subject: [PATCH 07/23] Fix type mismatch TypeScript error --- src/components/search/LiveTVSearchResults.tsx | 2 +- src/components/search/SearchResults.tsx | 2 +- src/components/search/SearchSuggestions.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/search/LiveTVSearchResults.tsx b/src/components/search/LiveTVSearchResults.tsx index 9f9874db2..6d43e59ef 100644 --- a/src/components/search/LiveTVSearchResults.tsx +++ b/src/components/search/LiveTVSearchResults.tsx @@ -27,7 +27,7 @@ type LiveTVSearchResultsProps = { /* * React component to display search result rows for live tv library search */ -const LiveTVSearchResults: FunctionComponent = ({ serverId, parentId, collectionType, query }: LiveTVSearchResultsProps) => { +const LiveTVSearchResults: FunctionComponent = ({ serverId = window.ApiClient.serverId(), parentId, collectionType, query }: LiveTVSearchResultsProps) => { const [ movies, setMovies ] = useState([]); const [ episodes, setEpisodes ] = useState([]); const [ sports, setSports ] = useState([]); diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index e814eba7f..67ff69501 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -15,7 +15,7 @@ type SearchResultsProps = { /* * React component to display search result rows for global search and non-live tv library search */ -const SearchResults: FunctionComponent = ({ serverId, parentId, collectionType, query }: SearchResultsProps) => { +const SearchResults: FunctionComponent = ({ serverId = window.ApiClient.serverId(), parentId, collectionType, query }: SearchResultsProps) => { const [ movies, setMovies ] = useState([]); const [ shows, setShows ] = useState([]); const [ episodes, setEpisodes ] = useState([]); diff --git a/src/components/search/SearchSuggestions.tsx b/src/components/search/SearchSuggestions.tsx index c13659c60..6f76d792e 100644 --- a/src/components/search/SearchSuggestions.tsx +++ b/src/components/search/SearchSuggestions.tsx @@ -23,7 +23,7 @@ type SearchSuggestionsProps = { parentId?: string; } -const SearchSuggestions: FunctionComponent = ({ serverId, parentId }: SearchSuggestionsProps) => { +const SearchSuggestions: FunctionComponent = ({ serverId = window.ApiClient.serverId(), parentId }: SearchSuggestionsProps) => { const [ suggestions, setSuggestions ] = useState([]); useEffect(() => { From 57895e724ca69b546bad826c33b0ff8ffbea9f45 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Fri, 18 Feb 2022 14:27:39 +0300 Subject: [PATCH 08/23] Fix implicit any TypeScript error --- .../dashboard/users/AccessScheduleList.tsx | 8 +++----- .../dashboard/users/BlockedTagList.tsx | 6 ++---- .../dashboard/users/ButtonElement.tsx | 2 +- .../dashboard/users/CheckBoxElement.tsx | 2 +- .../dashboard/users/CheckBoxListItem.tsx | 2 +- .../dashboard/users/InputElement.tsx | 2 +- .../users/LinkEditUserPreferences.tsx | 2 +- .../dashboard/users/SectionTabs.tsx | 6 ++---- .../users/SectionTitleButtonElement.tsx | 2 +- .../users/SectionTitleLinkElement.tsx | 2 +- .../dashboard/users/SelectElement.tsx | 2 +- .../users/SelectMaxParentalRating.tsx | 2 +- .../users/SelectSyncPlayAccessElement.tsx | 2 +- .../dashboard/users/UserCardBox.tsx | 5 +++-- src/components/pages/NewUserPage.tsx | 2 +- src/components/pages/UserEditPage.tsx | 15 ++++++++++++--- .../pages/UserLibraryAccessPage.tsx | 15 ++++++++++++--- src/components/pages/UserParentalControl.tsx | 19 ++++++++++++++----- src/components/pages/UserProfilesPage.tsx | 6 +++--- src/components/search/LiveTVSearchResults.tsx | 3 ++- src/components/search/SearchFields.tsx | 4 ++-- src/components/search/SearchResults.tsx | 7 ++++--- src/components/search/SearchSuggestions.tsx | 4 ++-- 23 files changed, 72 insertions(+), 48 deletions(-) diff --git a/src/components/dashboard/users/AccessScheduleList.tsx b/src/components/dashboard/users/AccessScheduleList.tsx index b46c6beeb..ef092c550 100644 --- a/src/components/dashboard/users/AccessScheduleList.tsx +++ b/src/components/dashboard/users/AccessScheduleList.tsx @@ -2,7 +2,7 @@ import React, { FunctionComponent } from 'react'; import datetime from '../../../scripts/datetime'; import globalize from '../../../scripts/globalize'; -const createButtonElement = ({index}) => ({ +const createButtonElement = (index: number) => ({ __html: `