Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions seerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6257,6 +6257,94 @@ paths:
- $ref: '#/components/schemas/MovieResult'
- $ref: '#/components/schemas/TvResult'
- $ref: '#/components/schemas/PersonResult'
/discover/list/{listId}:
get:
summary: Get the contents of a TMDB list
description: >-
Returns a page of the movies and series contained in a public TMDB list,
in the order defined by the list owner, in a JSON object. Only the
requested page is fetched upstream; totalPages and totalResults describe
the whole list. The language is resolved server-side from the requesting
user's locale and cannot be overridden. When the list has been deleted or
is no longer public, the response is a 200 with an empty results array
and list.unavailable set to true.
tags:
- search
parameters:
- in: path
name: listId
required: true
description: Numeric ID of a public TMDB list.
schema:
type: string
pattern: '^[1-9]\d{0,11}$'
example: '8542986'
- in: query
name: page
schema:
type: integer
minimum: 1
example: 1
default: 1
responses:
'200':
description: Results
content:
application/json:
schema:
type: object
required:
- page
- totalPages
- totalResults
- list
- results
properties:
page:
type: number
example: 1
totalPages:
type: number
example: 20
totalResults:
type: number
example: 200
list:
type: object
required:
- providerId
- listId
properties:
providerId:
type: string
example: tmdb
listId:
type: string
example: '8542986'
name:
type: string
example: My Watchlist
description:
type: string
example: Movies and series I want to watch
unavailable:
type: boolean
description: >-
Present and true when the list itself could not be
read, which is distinct from a list with no entries.
example: true
results:
type: array
items:
anyOf:
- $ref: '#/components/schemas/MovieResult'
- $ref: '#/components/schemas/TvResult'
'400':
description: Invalid list ID or page
'403':
description: Unauthenticated
'500':
description: Unable to retrieve the list from the upstream provider
/discover/keyword/{keywordId}/movies:
get:
summary: Get movies from keyword
Expand Down
35 changes: 35 additions & 0 deletions server/api/themoviedb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
TmdbKeyword,
TmdbKeywordSearchResponse,
TmdbLanguage,
TmdbListResponse,
TmdbMovieDetails,
TmdbNetwork,
TmdbPersonCombinedCredits,
Expand Down Expand Up @@ -1063,6 +1064,40 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
}
}

/**
* Fetches a single page of a TMDB list.
*
* The v3 `/list/{listId}` endpoint reads both classic v3 lists and lists
* created through the v4 API (as long as they are public), so no v4 read
* token is required. Returns `null` when the list does not exist or is not
* publicly readable.
*/
public async getList({
listId,
page = 1,
language = this.locale,
}: {
listId: number;
page?: number;
language?: string;
}): Promise<TmdbListResponse | null> {
try {
const data = await this.get<TmdbListResponse>(`/list/${listId}`, {
params: { page, language },
});

return data;
} catch (e) {
// 401 is returned for private lists, 404 for lists that do not exist
if (e.response?.status === 401 || e.response?.status === 404) {
return null;
}
throw new Error(`[TMDB] Failed to fetch list: ${e.message}`, {
cause: e,
});
}
}

public async getMovieGenres({
language = this.locale,
}: {
Expand Down
27 changes: 27 additions & 0 deletions server/api/themoviedb/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,33 @@ export interface TmdbExternalIdResponse {
person_results: TmdbPersonResult[];
}

/**
* A single entry of a TMDB list.
*
* v4 lists can hold both movies and series, so their items carry their own
* `media_type`. Classic v3 lists are movie-only and omit the field entirely,
* which is why it is optional here: a missing `media_type` means a movie.
*/
export type TmdbListItem =
| TmdbTvResult
| (Omit<TmdbMovieResult, 'media_type'> & { media_type?: 'movie' });

/**
* Response shape of `GET /3/list/{listId}`.
*
* The v3 endpoint serves both classic v3 lists and the newer v4 lists.
*/
export interface TmdbListResponse extends TmdbPaginatedResponse {
id: number;
name: string;
description?: string;
created_by?: string;
iso_639_1?: string;
poster_path?: string;
item_count: number;
items: TmdbListItem[];
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

export interface TmdbCreditCast {
cast_id: number;
character: string;
Expand Down
5 changes: 5 additions & 0 deletions server/constants/discover.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type DiscoverSlider from '@server/entity/DiscoverSlider';

/**
* These values are persisted in the database. Only ever append new members to
* this enum; never reorder or remove existing ones.
*/
export enum DiscoverSliderType {
RECENTLY_ADDED = 1,
RECENT_REQUESTS,
Expand All @@ -22,6 +26,7 @@ export enum DiscoverSliderType {
TMDB_NETWORK,
TMDB_MOVIE_STREAMING_SERVICES,
TMDB_TV_STREAMING_SERVICES,
TMDB_LIST,
}

export const defaultSliders: Partial<DiscoverSlider>[] = [
Expand Down
11 changes: 10 additions & 1 deletion server/lib/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export type AvailableCacheIds =
| 'plexguid'
| 'plextv'
| 'plexwatchlist'
| 'tvdb';
| 'tvdb'
| 'medialist';

const DEFAULT_TTL = 300;
const DEFAULT_CHECK_PERIOD = 120;
Expand Down Expand Up @@ -75,6 +76,14 @@ class CacheManager {
stdTtl: 21600,
checkPeriod: 60 * 30,
}),
// Holds one entry per list page. Entries stay resident for 48 hours so that
// a stale page can still be served when the upstream provider is
// unreachable. Freshness, single-flight and the entry ceiling are handled
// by the media list service.
medialist: new Cache('medialist', 'Media Lists', {
stdTtl: 86400 * 2,
checkPeriod: 60 * 30,
}),
};

public getCache(id: AvailableCacheIds): Cache {
Expand Down
Loading