From b88606a1df4b0ac7a1f2e2d3bf517fcc5e7635a6 Mon Sep 17 00:00:00 2001 From: TOomaAh Date: Sun, 20 Oct 2024 01:46:08 +0200 Subject: [PATCH] refactor(tvdb): replace tvdb api by skyhook --- jellyseerr-api.yml | 21 --- server/api/indexer/themoviedb/interfaces.ts | 2 +- server/api/indexer/tvdb/index.ts | 134 ++++++--------- server/api/indexer/tvdb/interfaces.ts | 177 +++++++------------- server/lib/settings/index.ts | 2 - server/models/Tv.ts | 2 +- server/routes/settings/tvdb.ts | 2 - src/components/Settings/SettingsTvdb.tsx | 93 +--------- 8 files changed, 111 insertions(+), 322 deletions(-) diff --git a/jellyseerr-api.yml b/jellyseerr-api.yml index 20e170d1f..bf4553670 100644 --- a/jellyseerr-api.yml +++ b/jellyseerr-api.yml @@ -522,12 +522,6 @@ components: TvdbSettings: type: object properties: - apiKey: - type: string - example: 'apikey' - pin: - type: string - example: 'ABCDEFGH' use: type: boolean example: true @@ -2617,21 +2611,6 @@ paths: description: Tests if the TVDB configuration is valid. Returns a list of available languages on success. tags: - settings - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - apiKey: - type: string - example: yourapikey - pin: - type: string - example: yourpin - required: - - apiKey responses: '200': description: Succesfully connected to TVDB diff --git a/server/api/indexer/themoviedb/interfaces.ts b/server/api/indexer/themoviedb/interfaces.ts index 775a89765..65ba18f80 100644 --- a/server/api/indexer/themoviedb/interfaces.ts +++ b/server/api/indexer/themoviedb/interfaces.ts @@ -220,7 +220,7 @@ export interface TmdbTvEpisodeResult { show_id: number; still_path: string; vote_average: number; - vote_cuont: number; + vote_count: number; } export interface TmdbTvSeasonResult { diff --git a/server/api/indexer/tvdb/index.ts b/server/api/indexer/tvdb/index.ts index 8d9bd82bd..c657d5488 100644 --- a/server/api/indexer/tvdb/index.ts +++ b/server/api/indexer/tvdb/index.ts @@ -6,26 +6,19 @@ import type { TmdbTvDetails, } from '@server/api/indexer/themoviedb/interfaces'; import type { - TvdbEpisodeTranslation, TvdbLoginResponse, - TvdbSeasonDetails, - TvdbTvDetails, + TvdbTvShowDetail, } from '@server/api/indexer/tvdb/interfaces'; import cacheManager from '@server/lib/cache'; -import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; class Tvdb extends ExternalAPI implements TvShowIndexer { static instance: Tvdb; - private dateTokenExpires?: Date; - private pin?: string; - private constructor(apiKey: string, pin?: string) { + private constructor() { super( - 'https://api4.thetvdb.com/v4', - { - apiKey: apiKey, - }, + 'https://skyhook.sonarr.tv/v1/tvdb/shows', + {}, { nodeCache: cacheManager.getCache('tvdb').data, rateLimit: { @@ -34,16 +27,11 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { }, } ); - this.pin = pin; } public static async getInstance() { if (!this.instance) { - const settings = getSettings(); - if (!settings.tvdb.apiKey) { - throw new Error('TVDB API key is not set'); - } - this.instance = new Tvdb(settings.tvdb.apiKey, settings.tvdb.pin); + this.instance = new Tvdb(); await this.instance.login(); logger.info( 'Tvdb instance created with token => ' + @@ -55,14 +43,7 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { async login() { try { - const res = await this.post('/login', { - apiKey: this.params.apiKey, - pin: this.pin, - }); - this.defaultHeaders.Authorization = `Bearer ${res.data.token}`; - this.dateTokenExpires = new Date(); - this.dateTokenExpires.setMonth(this.dateTokenExpires.getMonth() + 1); - return res; + return await this.get('/en/445009', {}); } catch (error) { throw new Error(`[TVDB] Login failed: ${error.message}`); } @@ -85,49 +66,43 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { return tmdbTvShow; } - const data = await this.get( - `/series/${tvdbId}/extended`, - { - short: 'true', - }, + const data = await this.get( + `/${language}/${tvdbId}`, + {}, 43200 ); - const correctSeasons = data.data.seasons.filter( - (season: TvdbSeasonDetails) => - season.id && season.number > 0 && season.type.name === 'Aired Order' - ); + const correctSeasons = data.seasons.filter((value) => { + return value.seasonNumber !== 0; + }); tmdbTvShow.seasons = []; for (const season of correctSeasons) { - if (season.id) { - logger.info(`Fetching TV season ${season.id}`); + if (season.seasonNumber) { + logger.info(`Fetching TV season ${season.seasonNumber}`); try { - const tvdbSeason = await this.getTvSeason({ - tvId: tvdbId, - seasonNumber: season.id, - language, - }); const seasonData = { - id: season.id, - episode_count: tvdbSeason.episodes.length, - name: tvdbSeason.name, - overview: tvdbSeason.overview, - season_number: season.number, + id: tvdbId, + episode_count: data.episodes.filter((value) => { + return value.seasonNumber === season.seasonNumber; + }).length, + name: `${season.seasonNumber}`, + overview: '', + season_number: season.seasonNumber, poster_path: '', air_date: '', - image: tvdbSeason.poster_path, + image: '', }; tmdbTvShow.seasons.push(seasonData); } catch (error) { logger.error( - `Failed to get season ${season.id} for TV show ${tvdbId}: ${error.message}`, + `Failed to get season ${season.seasonNumber} for TV show ${tvdbId}: ${error.message}`, { label: 'Tvdb', - message: `Failed to get season ${season.id} for TV show ${tvdbId}`, + message: `Failed to get season ${season.seasonNumber} for TV show ${tvdbId}`, } ); } @@ -142,25 +117,6 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { } }; - getEpisode = async ( - episodeId: number, - language: string - ): Promise => { - try { - const tvdbEpisode = await this.get( - `/episodes/${episodeId}/translations/${language}`, - {}, - 43200 - ); - - return tvdbEpisode; - } catch (error) { - throw new Error( - `[TVDB] Failed to fetch TV episode details: ${error.message}` - ); - } - }; - public getTvSeason = async ({ tvId, seasonNumber, @@ -184,36 +140,40 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { }; } try { - const tvdbSeason = await this.get( - `/seasons/${seasonNumber}/extended`, + const tvdbSeason = await this.get( + `/en/${tvId}`, { lang: language }, 43200 ); - const episodes = tvdbSeason.data.episodes.map((episode) => ({ - id: episode.id, - air_date: episode.aired, - episode_number: episode.number, - name: episode.name, - overview: episode.overview || '', - season_number: episode.seasonNumber, - production_code: '', - show_id: tvId, - still_path: episode.image, - vote_average: 1, - vote_cuont: 1, - })); + const episodes = tvdbSeason.episodes + .filter((value) => { + return value.seasonNumber === seasonNumber; + }) + .map((episode) => ({ + id: episode.tvdbId, + air_date: episode.airDate, + episode_number: episode.episodeNumber, + name: episode.title || '', + overview: episode.overview || '', + season_number: episode.seasonNumber, + production_code: '', + show_id: tvId, + still_path: episode.image || '', + vote_average: 1, + vote_count: 1, + })); return { episodes: episodes, external_ids: { - tvdb_id: tvdbSeason.seriesId, + tvdb_id: tvdbSeason.tvdbId, }, name: '', overview: '', - id: tvdbSeason.id, - air_date: tvdbSeason.year, - season_number: tvdbSeason.number, + id: tvdbSeason.tvdbId, + air_date: tvdbSeason.firstAired, + season_number: episodes.length, }; } catch (error) { throw new Error( diff --git a/server/api/indexer/tvdb/interfaces.ts b/server/api/indexer/tvdb/interfaces.ts index c45e47a97..3e9007e2d 100644 --- a/server/api/indexer/tvdb/interfaces.ts +++ b/server/api/indexer/tvdb/interfaces.ts @@ -1,138 +1,75 @@ export interface TvdbBaseResponse { data: T; - errors: any; + errors: string; } export interface TvdbLoginResponse extends TvdbBaseResponse<{ token: string }> { data: { token: string }; } -interface TvDetailsAliases { - language: string; - name: string; -} - -interface TvDetailsStatus { - id: number; - name: string; - recordType: string; - keepUpdated: boolean; -} - -export interface TvdbTvDetails extends TvdbBaseResponse { - id: number; - name: string; +export interface TvdbTvShowDetail { + tvdbId: number; + title: string; + overview: string; slug: string; - image: string; - nameTranslations: string[]; - overwiewTranslations: string[]; - aliases: TvDetailsAliases[]; - firstAired: Date; - lastAired: Date; - nextAired: Date | string; - score: number; - status: TvDetailsStatus; originalCountry: string; originalLanguage: string; - defaultSeasonType: string; - isOrderRandomized: boolean; - lastUpdated: Date; - averageRuntime: number; - seasons: TvdbSeasonDetails[]; -} - -interface TvdbCompanyType { - companyTypeId: number; - companyTypeName: string; -} - -interface TvdbParentCompany { - id?: number; - name?: string; - relation?: { - id?: number; - typeName?: string; - }; -} - -interface TvdbCompany { - id: number; - name: string; - slug: string; - nameTranslations?: string[]; - overviewTranslations?: string[]; - aliases?: string[]; - country: string; - primaryCompanyType: number; - activeDate: string; - inactiveDate?: string; - companyType: TvdbCompanyType; - parentCompany: TvdbParentCompany; - tagOptions?: string[]; -} - -interface TvdbType { - id: number; - name: string; - type: string; - alternateName?: string; -} - -interface TvdbArtwork { - id: number; - image: string; - thumbnail: string; language: string; - type: number; - score: number; - width: number; - height: number; - includesText: boolean; -} - -interface TvdbEpisode { - id: number; - seriesId: number; - name: string; - aired: string; + firstAired: string; + lastAired: string; + tvMazeId: number; + tmdbId: number; + imdbId: string; + lastUpdated: string; + status: string; runtime: number; - nameTranslations: string[]; - overview?: string; - overviewTranslations: string[]; - image: string; - imageType: number; - isMovie: number; - seasons?: string[]; - number: number; - absoluteNumber: number; - seasonNumber: number; - lastUpdated: string; - finaleType?: string; - year: string; + timeOfDay: TvdbTimeOfDay; + originalNetwork: string; + network: string; + genres: string[]; + alternativeTitles: TvdbAlternativeTitle[]; + actors: TvdbActor[]; + images: TvdbImage[]; + seasons: TvdbSeason[]; + episodes: TvdbEpisode[]; } -export interface TvdbSeasonDetails extends TvdbBaseResponse { - id: number; - seriesId: number; - type: TvdbType; - number: number; - nameTranslations: string[]; - overviewTranslations: string[]; - image: string; - imageType: number; - companies: { - studio: TvdbCompany[]; - network: TvdbCompany[]; - production: TvdbCompany[]; - distributor: TvdbCompany[]; - special_effects: TvdbCompany[]; - }; - lastUpdated: string; - year: string; - episodes: TvdbEpisode[]; - trailers: string[]; - artwork: TvdbArtwork[]; - tagOptions?: string[]; +export interface TvdbTimeOfDay { + hours: number; + minutes: number; +} + +export interface TvdbAlternativeTitle { + title: string; +} + +export interface TvdbActor { + name: string; + character: string; + image?: string; +} + +export interface TvdbImage { + coverType: string; + url: string; +} + +export interface TvdbSeason { + seasonNumber: number; +} + +export interface TvdbEpisode { + tvdbShowId: number; + tvdbId: number; + seasonNumber: number; + episodeNumber: number; + absoluteEpisodeNumber: number; + title?: string; + airDate: string; + airDateUtc: string; + runtime?: number; + overview?: string; + image?: string; } export interface TvdbEpisodeTranslation diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index deb29ccf5..5b7a2cee5 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -80,8 +80,6 @@ export interface DVRSettings { } export interface TvdbSettings { - apiKey?: string; - pin?: string; use: boolean; } diff --git a/server/models/Tv.ts b/server/models/Tv.ts index b2703e08a..3a75db39b 100644 --- a/server/models/Tv.ts +++ b/server/models/Tv.ts @@ -124,7 +124,7 @@ const mapEpisodeResult = (episode: TmdbTvEpisodeResult): Episode => ({ seasonNumber: episode.season_number, showId: episode.show_id, voteAverage: episode.vote_average, - voteCount: episode.vote_cuont, + voteCount: episode.vote_count, stillPath: episode.still_path, }); diff --git a/server/routes/settings/tvdb.ts b/server/routes/settings/tvdb.ts index d3b6f3410..7817a97c9 100644 --- a/server/routes/settings/tvdb.ts +++ b/server/routes/settings/tvdb.ts @@ -18,8 +18,6 @@ tvdbRoutes.put('/', (req, res) => { const newTvdb = req.body as TvdbSettings; const tvdb = settings.tvdb; - tvdb.apiKey = newTvdb.apiKey; - tvdb.pin = newTvdb.pin; tvdb.use = newTvdb.use; settings.tvdb = tvdb; diff --git a/src/components/Settings/SettingsTvdb.tsx b/src/components/Settings/SettingsTvdb.tsx index b2149e6fc..094862cb0 100644 --- a/src/components/Settings/SettingsTvdb.tsx +++ b/src/components/Settings/SettingsTvdb.tsx @@ -1,12 +1,9 @@ import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import PageTitle from '@app/components/Common/PageTitle'; -import SensitiveInput from '@app/components/Common/SensitiveInput'; -import CopyButton from '@app/components/Settings/CopyButton'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; -import { ArrowPathIcon } from '@heroicons/react/24/solid'; import type { TvdbSettings } from '@server/lib/settings'; import { Field, Form, Formik } from 'formik'; import { useState } from 'react'; @@ -33,13 +30,12 @@ const SettingsTvdb = () => { const { addToast } = useToasts(); - const testConnection = async (apiKey: string | undefined, pin?: string) => { + const testConnection = async () => { const response = await fetch('/api/v1/settings/tvdb/test', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ apiKey, pin }), }); if (!response.ok) { @@ -82,19 +78,12 @@ const SettingsTvdb = () => {
{ - if (values.enable && values.apiKey === '') { - addToast('Please enter an API key', { appearance: 'error' }); - return; - } - try { setIsTesting(true); - await testConnection(values.apiKey, values.pin); + await testConnection(); setIsTesting(false); } catch (e) { addToast('Tvdb connection error, check your credentials', { @@ -105,8 +94,6 @@ const SettingsTvdb = () => { try { await saveSettings({ - apiKey: values.apiKey, - pin: values.pin, use: values.enable || false, }); } catch (e) { @@ -116,75 +103,9 @@ const SettingsTvdb = () => { addToast('Tvdb settings saved', { appearance: 'success' }); }} > - {({ - errors, - touched, - isSubmitting, - isValid, - values, - setFieldValue, - }) => { + {({ isSubmitting, isValid, values, setFieldValue }) => { return (
-
- -
-
- { - setFieldValue('apiKey', e.target.value); - }} - /> - - -
-
-
- -
- -
-
- { - values.pin = e.target.value; - }} - /> - - -
-
-
-
- {errors.apiKey && - touched.apiKey && - typeof errors.apiKey === 'string' && ( -
{errors.apiKey}
- )} +
@@ -224,7 +141,7 @@ const SettingsTvdb = () => { onClick={async () => { setIsTesting(true); try { - await testConnection(values.apiKey, values.pin); + await testConnection(); addToast('Tvdb connection successful', { appearance: 'success', });