diff --git a/overseerr-api.yml b/overseerr-api.yml index 551f7dd91..c6611f79a 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1031,6 +1031,13 @@ components: type: array items: $ref: '#/components/schemas/WatchProviders' + TvSeasons: + type: object + properties: + seasons: + type: array + items: + $ref: '#/components/schemas/Season' MediaRequest: type: object properties: @@ -5082,6 +5089,37 @@ paths: application/json: schema: $ref: '#/components/schemas/TvDetails' + /tv/{tvId}/seasons: + get: + summary: Get TV seasons + description: Returns TV seasons based on the provided tvId in a JSON object. + tags: + - tv + parameters: + - in: path + name: tvId + required: true + schema: + type: number + example: 76479 + - in: query + name: page + schema: + type: number + example: 1 + default: 1 + - in: query + name: language + schema: + type: string + example: en + responses: + '200': + description: TV seasons + content: + application/json: + schema: + $ref: '#/components/schemas/TvSeasons' /tv/{tvId}/season/{seasonId}: get: summary: Get season details and episode list diff --git a/server/api/themoviedb/index.ts b/server/api/themoviedb/index.ts index b5060c030..bef79ce50 100644 --- a/server/api/themoviedb/index.ts +++ b/server/api/themoviedb/index.ts @@ -18,6 +18,7 @@ import { TmdbSearchTvResponse, TmdbSeasonWithEpisodes, TmdbTvDetails, + TmdbTvSeasonResult, TmdbUpcomingMoviesResponse, } from './interfaces'; @@ -271,6 +272,32 @@ class TheMovieDb extends ExternalAPI { } }; + public getTvSeasons = async ({ + tvId, + page = 1, + language = 'en', + }: { + tvId: number; + page: number; + language?: string; + }): Promise => { + try { + const data = await this.get( + `/tv/${tvId}`, + { + params: { + page, + language, + }, + }, + 43200 + ); + return data.seasons; + } catch (e) { + throw new Error(`[TMDb] Failed to fetch TV show seasons: ${e.message}`); + } + }; + public getTvSeason = async ({ tvId, seasonNumber, diff --git a/server/models/Tv.ts b/server/models/Tv.ts index b596b1d2b..8599ac342 100644 --- a/server/models/Tv.ts +++ b/server/models/Tv.ts @@ -48,6 +48,10 @@ interface Season { seasonNumber: number; } +export interface TvSeasons { + seasons: Season[]; +} + export interface SeasonWithEpisodes extends Season { episodes: Episode[]; externalIds: ExternalIds; @@ -223,3 +227,7 @@ export const mapTvDetails = ( mediaInfo: media, watchProviders: mapWatchProviders(show['watch/providers']?.results ?? {}), }); + +export const mapTvSeasons = (seasons: TmdbTvSeasonResult[]): TvSeasons => ({ + seasons: seasons.map(mapSeasonResult), +}); diff --git a/server/routes/tv.ts b/server/routes/tv.ts index 201e7afe3..7083aa9b3 100644 --- a/server/routes/tv.ts +++ b/server/routes/tv.ts @@ -5,7 +5,11 @@ import { MediaType } from '../constants/media'; import Media from '../entity/Media'; import logger from '../logger'; import { mapTvResult } from '../models/Search'; -import { mapSeasonWithEpisodes, mapTvDetails } from '../models/Tv'; +import { + mapSeasonWithEpisodes, + mapTvDetails, + mapTvSeasons, +} from '../models/Tv'; const tvRoutes = Router(); @@ -33,6 +37,29 @@ tvRoutes.get('/:id', async (req, res, next) => { } }); +tvRoutes.get('/:id/seasons', async (req, res, next) => { + const tmdb = new TheMovieDb(); + try { + const seasons = await tmdb.getTvSeasons({ + tvId: Number(req.params.id), + page: Number(req.query.page), + language: req.locale ?? (req.query.language as string), + }); + + return res.status(200).json(mapTvSeasons(seasons)); + } catch (e) { + logger.debug('Something went wrong retrieving seasons', { + label: 'API', + errorMessage: e.message, + tvId: req.params.id, + }); + return next({ + status: 500, + message: 'Unable to retrieve seasons.', + }); + } +}); + tvRoutes.get('/:id/season/:seasonNumber', async (req, res, next) => { const tmdb = new TheMovieDb(); diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index a4d6bcef1..f11eabb8e 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -672,6 +672,14 @@ const TvDetails: React.FC = ({ tv }) => { + + {data.credits.cast.length > 0 && ( <>