From 377c6a40a89e787cb21a54e19a1e2a25ecdc1da9 Mon Sep 17 00:00:00 2001 From: TOomaAh Date: Sun, 20 Oct 2024 16:17:42 +0200 Subject: [PATCH] fix: error during get episodes --- server/api/indexer/index.ts | 2 + server/api/indexer/themoviedb/index.ts | 10 ++ server/api/indexer/tvdb/index.ts | 129 ++++++++++++---------- server/lib/settings/index.ts | 12 ++ server/routes/settings/tvdb.ts | 2 +- server/routes/tv.ts | 35 ++---- src/components/Settings/SettingsTvdb.tsx | 11 +- src/components/TvDetails/Season/index.tsx | 2 +- 8 files changed, 113 insertions(+), 90 deletions(-) diff --git a/server/api/indexer/index.ts b/server/api/indexer/index.ts index aa59540b..23321b63 100644 --- a/server/api/indexer/index.ts +++ b/server/api/indexer/index.ts @@ -22,4 +22,6 @@ export interface TvShowIndexer { seasonNumber: number; language?: string; }): Promise; + + getSeasonIdentifier(req: any): number; } diff --git a/server/api/indexer/themoviedb/index.ts b/server/api/indexer/themoviedb/index.ts index 543ee69c..e162746a 100644 --- a/server/api/indexer/themoviedb/index.ts +++ b/server/api/indexer/themoviedb/index.ts @@ -198,6 +198,10 @@ class TheMovieDb extends ExternalAPI implements TvShowIndexer { } }; + getSeasonIdentifier(req: any): number { + return req.params.seasonNumber; + } + public searchTvShows = async ({ query, page = 1, @@ -342,6 +346,12 @@ class TheMovieDb extends ExternalAPI implements TvShowIndexer { } ); + data.episodes = data.episodes.map((episode) => { + if (episode.still_path !== '') { + episode.still_path = `https://image.tmdb.org/t/p/original/${episode.still_path}`; + } + return episode; + }); return data; } catch (e) { throw new Error(`[TMDB] Failed to fetch TV show details: ${e.message}`); diff --git a/server/api/indexer/tvdb/index.ts b/server/api/indexer/tvdb/index.ts index c657d548..f9689f5f 100644 --- a/server/api/indexer/tvdb/index.ts +++ b/server/api/indexer/tvdb/index.ts @@ -13,9 +13,9 @@ import cacheManager from '@server/lib/cache'; import logger from '@server/logger'; class Tvdb extends ExternalAPI implements TvShowIndexer { - static instance: Tvdb; + private tmdb: TheMovieDb = new TheMovieDb(); - private constructor() { + public constructor() { super( 'https://skyhook.sonarr.tv/v1/tvdb/shows', {}, @@ -29,18 +29,6 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { ); } - public static async getInstance() { - if (!this.instance) { - this.instance = new Tvdb(); - await this.instance.login(); - logger.info( - 'Tvdb instance created with token => ' + - this.instance.defaultHeaders.Authorization - ); - } - return this.instance; - } - async login() { try { return await this.get('/en/445009', {}); @@ -51,65 +39,66 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { public getTvShow = async ({ tvId, - language = 'en', }: { tvId: number; language?: string; }): Promise => { try { - const tmdb = new TheMovieDb(); - const tmdbTvShow = await tmdb.getTvShow({ tvId: tvId }); + const tmdbTvShow = await this.tmdb.getTvShow({ tvId: tvId }); + const tvdbId = this.getTvdbIdFromTmdb(tmdbTvShow); - const tvdbId = tmdbTvShow.external_ids.tvdb_id; - - if (!tvdbId) { + if (tvdbId === -1) { return tmdbTvShow; } - const data = await this.get( - `/${language}/${tvdbId}`, - {}, - 43200 - ); + try { + const data = await this.get( + `/en/${tvdbId}`, + {}, + 43200 + ); - const correctSeasons = data.seasons.filter((value) => { - return value.seasonNumber !== 0; - }); + const correctSeasons = data.seasons.filter((value) => { + return value.seasonNumber !== 0; + }); - tmdbTvShow.seasons = []; + tmdbTvShow.seasons = []; - for (const season of correctSeasons) { - if (season.seasonNumber) { - logger.info(`Fetching TV season ${season.seasonNumber}`); + for (const season of correctSeasons) { + if (season.seasonNumber) { + logger.info(`Fetching TV season ${season.seasonNumber}`); - try { - const seasonData = { - 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: '', - }; + try { + const seasonData = { + 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: '', + }; - tmdbTvShow.seasons.push(seasonData); - } catch (error) { - logger.error( - `Failed to get season ${season.seasonNumber} for TV show ${tvdbId}: ${error.message}`, - { - label: 'Tvdb', - message: `Failed to get season ${season.seasonNumber} for TV show ${tvdbId}`, - } - ); + tmdbTvShow.seasons.push(seasonData); + } catch (error) { + logger.error( + `Failed to get season ${season.seasonNumber} for TV show ${tvdbId}: ${error.message}`, + { + label: 'Tvdb', + message: `Failed to get season ${season.seasonNumber} for TV show ${tvdbId}`, + } + ); + } } } - } - return tmdbTvShow; + return tmdbTvShow; + } catch (e) { + return tmdbTvShow; + } } catch (error) { throw new Error( `[TVDB] Failed to fetch TV show details: ${error.message}` @@ -117,6 +106,14 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { } }; + private getTvdbIdFromTmdb(tmdbTvShow: TmdbTvDetails) { + try { + return tmdbTvShow.external_ids.tvdb_id || -1; + } catch (e) { + return -1; + } + } + public getTvSeason = async ({ tvId, seasonNumber, @@ -139,9 +136,16 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { season_number: 0, }; } + + const tmdbTvShow = await this.tmdb.getTvShow({ tvId: tvId }); + const tvdbId = this.getTvdbIdFromTmdb(tmdbTvShow); + + if (tvdbId === -1) { + return await this.tmdb.getTvSeason({ tvId, seasonNumber, language }); + } try { const tvdbSeason = await this.get( - `/en/${tvId}`, + `/en/${tvdbId}`, { lang: language }, 43200 ); @@ -150,11 +154,11 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { .filter((value) => { return value.seasonNumber === seasonNumber; }) - .map((episode) => ({ + .map((episode, index) => ({ id: episode.tvdbId, air_date: episode.airDate, episode_number: episode.episodeNumber, - name: episode.title || '', + name: episode.title || `Episode ${index + 1}`, overview: episode.overview || '', season_number: episode.seasonNumber, production_code: '', @@ -176,11 +180,16 @@ class Tvdb extends ExternalAPI implements TvShowIndexer { season_number: episodes.length, }; } catch (error) { - throw new Error( + logger.error( `[TVDB] Failed to fetch TV season details: ${error.message}` ); + return await this.tmdb.getTvSeason({ tvId, seasonNumber, language }); } }; + + getSeasonIdentifier(req: any): number { + return req.params.seasonId; + } } export default Tvdb; diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 5b7a2cee..fc11cf02 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -1,3 +1,6 @@ +import type { TvShowIndexer } from '@server/api/indexer'; +import TheMovieDb from '@server/api/indexer/themoviedb'; +import Tvdb from '@server/api/indexer/tvdb'; import { MediaServerType } from '@server/constants/server'; import { Permission } from '@server/lib/permissions'; import { runMigrations } from '@server/lib/settings/migrator'; @@ -787,4 +790,13 @@ export const getSettings = (initialSettings?: AllSettings): Settings => { return settings; }; +export const getIndexer = (): TvShowIndexer => { + const settings = getSettings(); + if (settings.tvdb.use) { + return new Tvdb(); + } else { + return new TheMovieDb(); + } +}; + export default Settings; diff --git a/server/routes/settings/tvdb.ts b/server/routes/settings/tvdb.ts index 7817a97c..fadedb13 100644 --- a/server/routes/settings/tvdb.ts +++ b/server/routes/settings/tvdb.ts @@ -28,7 +28,7 @@ tvdbRoutes.put('/', (req, res) => { tvdbRoutes.post('/test', async (req, res, next) => { try { - const tvdb = await Tvdb.getInstance(); + const tvdb = new Tvdb(); await tvdb.login(); return res.status(200).json({ message: 'Successfully connected to Tvdb' }); } catch (e) { diff --git a/server/routes/tv.ts b/server/routes/tv.ts index 32619738..3b15e01b 100644 --- a/server/routes/tv.ts +++ b/server/routes/tv.ts @@ -1,11 +1,10 @@ import TheMovieDb from '@server/api/indexer/themoviedb'; -import Tvdb from '@server/api/indexer/tvdb'; import RottenTomatoes from '@server/api/rating/rottentomatoes'; import { MediaType } from '@server/constants/media'; import { getRepository } from '@server/datasource'; import Media from '@server/entity/Media'; import { Watchlist } from '@server/entity/Watchlist'; -import { getSettings } from '@server/lib/settings'; +import { getIndexer } from '@server/lib/settings'; import logger from '@server/logger'; import { mapTvResult } from '@server/models/Search'; import { mapSeasonWithEpisodes, mapTvDetails } from '@server/models/Tv'; @@ -14,15 +13,10 @@ import { Router } from 'express'; const tvRoutes = Router(); tvRoutes.get('/:id', async (req, res, next) => { - const settings = getSettings(); - let tmdb; - if (settings.tvdb.use) { - tmdb = await Tvdb.getInstance(); - } else { - tmdb = new TheMovieDb(); - } + const indexer = getIndexer(); + try { - const tv = await tmdb.getTvShow({ + const tv = await indexer.getTvShow({ tvId: Number(req.params.id), language: (req.query.language as string) ?? req.locale, }); @@ -42,7 +36,9 @@ tvRoutes.get('/:id', async (req, res, next) => { // TMDB issue where it doesnt fallback to English when no overview is available in requested locale. if (!data.overview) { - const tvEnglish = await tmdb.getTvShow({ tvId: Number(req.params.id) }); + const tvEnglish = await indexer.getTvShow({ + tvId: Number(req.params.id), + }); data.overview = tvEnglish.overview; } @@ -62,20 +58,13 @@ tvRoutes.get('/:id', async (req, res, next) => { tvRoutes.get('/:id/season/:seasonNumber/:seasonId', async (req, res, next) => { try { - const settings = getSettings(); - let tmdb; - let seasonIdentifier; - if (settings.tvdb.use) { - tmdb = await Tvdb.getInstance(); - seasonIdentifier = req.params.seasonId; - } else { - tmdb = new TheMovieDb(); - seasonIdentifier = req.params.seasonNumber; - } + const indexer = getIndexer(); + const seasonIdentifier = indexer.getSeasonIdentifier(req); - const season = await tmdb.getTvSeason({ + const season = await indexer.getTvSeason({ tvId: Number(req.params.id), - seasonNumber: Number(seasonIdentifier), + seasonId: seasonIdentifier, + seasonNumber: Number(req.params.seasonNumber), }); return res.status(200).json(mapSeasonWithEpisodes(season)); diff --git a/src/components/Settings/SettingsTvdb.tsx b/src/components/Settings/SettingsTvdb.tsx index 094862cb..6574288d 100644 --- a/src/components/Settings/SettingsTvdb.tsx +++ b/src/components/Settings/SettingsTvdb.tsx @@ -1,6 +1,7 @@ import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import PageTitle from '@app/components/Common/PageTitle'; +import SettingsBadge from '@app/components/Settings/SettingsBadge'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; @@ -17,13 +18,11 @@ const messages = defineMessages('components.Settings', { apikey: 'API Key', pin: 'PIN', enable: 'Enable', - enableTip: 'Enable Tvdb (only for season and episode)', + enableTip: + 'Enable Tvdb (only for season and episode).' + + ' Due to a limitation of the api used, only English is available.', }); -/*interface SettingsTvdbProps { - onEdit: () => void; -}*/ - const SettingsTvdb = () => { const intl = useIntl(); const [isTesting, setIsTesting] = useState(false); @@ -111,6 +110,8 @@ const SettingsTvdb = () => { {intl.formatMessage(messages.enable)} + + {intl.formatMessage(messages.enableTip)} diff --git a/src/components/TvDetails/Season/index.tsx b/src/components/TvDetails/Season/index.tsx index 3823ca22..7a96d276 100644 --- a/src/components/TvDetails/Season/index.tsx +++ b/src/components/TvDetails/Season/index.tsx @@ -61,7 +61,7 @@ const Season = ({ seasonNumber, tvId, seasonId }: SeasonProps) => {