-
-
Notifications
You must be signed in to change notification settings - Fork 548
feat(discover): add TMDB lists as custom-slider #1896
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
feat(discover): add TMDB lists as custom-slider #1896
Conversation
|
Can a preview be done this ? |
I think you would like to ask if I can show you a little preview? :) If you wanna build this with docker and try this on your own: Then you can use a compose.yaml like this: services:
jellyseerr:
build:
#THIS MUST BE THE PATH WHERE YOU CLONE MY FORK INTO
context: ./jellyseerr-tmdb-lists-feat/
dockerfile: Dockerfile.local
args:
COMMIT_TAG: local
image: jellyseerr-fork:dev
container_name: jellyseerr-fork
environment:
- LOG_LEVEL=debug
- TZ=Europe/London
- PORT=5055 # optional
- COMMIT_TAG=local # <<< important: you need this Build-ARG
network_mode: host
ports:
- 5055:5055
restart: no |
Thank you for responding, but the message was addressed to the dev team cause they can launch a GitHub CI to make a Docker image of your PR so we can test it |
|
Beware, low-effort AI slop. |
|
Is there anything else I can do? I don't want to bother anyone, but every PR takes time, and it's always nice when something comes out of it. I also see that there are a lot of PRs at the moment. As I said, if there's anything else I can do to help, please let me know. |
|
We are merging PRs almost every day, but every review takes time, we can't merge everything at the same time. |
|
This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged. |
support creating discover sliders from public tmdb lists
|
So, I finally managed to complete this 👍 Just a brief note regarding your new contribution guidelines: For this update, I used an AI tool purely as a supportive tool — mainly to better understand certain parts of the codebase and to streamline the development and merge process. In addition, I thoroughly tested the feature. I believe this feature is a nice value-add for Seerr. If any adjustments or further tests are needed, feel free to let me know 👍 |
fallenbagel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this PR cannot be reviewed nor merged as it is. It has picked up all commits from develop, showing 121 commits instead of just your TMDB list changes.
To fix this, you'll need to rebase your branch on the latest develop or create a fresh branch with only your original commits, then force push. Happy to review once the PR shows only your changes.
oh, upsi. I will check this. Thanks for your time 👍 |
okay, I think its done :D Just my commits are now showed up instead of the 121.... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces support for TMDB lists as a new custom slider type, allowing administrators to configure public TMDB list IDs and display their items in the discovery interface. The implementation includes both v3 and v4 TMDB API fallbacks, frontend UI components for creating and editing these sliders, and comprehensive API documentation. Additionally, unrelated changes to TVDB API handling filter out season 0 content.
- New
TMDB_LISTslider type added to the discovery system - Backend endpoint
/api/v1/discover/list/:listIdsupporting both TMDB API v3 and v4 - Frontend components for creating, editing, and displaying TMDB list sliders with numeric input validation
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| server/constants/discover.ts | Adds TMDB_LIST enum value to DiscoverSliderType |
| src/components/Discover/constants.ts | Adds translation key for TMDB list slider title |
| src/components/Discover/index.tsx | Implements rendering logic for TMDB list slider type |
| src/components/Discover/DiscoverSliderEdit/index.tsx | Adds display name handling for TMDB list slider in edit mode |
| src/components/Discover/CreateSlider/index.tsx | Adds UI for creating/editing TMDB list sliders with numeric input field |
| server/routes/discover.ts | Implements new API endpoint with v3/v4 fallback logic; fixes error handling type safety |
| server/api/themoviedb/index.ts | Adds getList method for fetching TMDB list data via v3 API |
| server/api/tvdb/index.ts | Filters out season 0 content and improves season number validation (unrelated to main feature) |
| seerr-api.yml | Documents new /discover/list/{listId} endpoint with parameters and response schema |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const axiosInstance: any = (tmdb as any).axios; | ||
| const v4Url = `https://api.themoviedb.org/4/list/${listId}`; | ||
|
|
||
| try { | ||
| const resp = await axiosInstance.get(v4Url, { |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
| )[], | ||
| }; | ||
| } catch (e) { | ||
| throw new Error(`[TMDB] Failed to fetch list: ${e.message}`); |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error property access should be type-safe. Use (e as Error).message instead of e.message to match the updated pattern elsewhere in this PR (line 715) and ensure TypeScript type safety.
| const mappedResults = data.results.map((result) => | ||
| isMovie(result) | ||
| ? mapMovieResult( | ||
| result, | ||
| media.find( | ||
| (m) => | ||
| m.tmdbId === result.id && m.mediaType === MediaType.MOVIE | ||
| ) | ||
| ) | ||
| : isPerson(result) | ||
| ? mapPersonResult(result) | ||
| : isCollection(result) | ||
| ? mapCollectionResult(result) | ||
| : mapTvResult( | ||
| result, | ||
| media.find( | ||
| (m) => m.tmdbId === result.id && m.mediaType === MediaType.TV | ||
| ) | ||
| ) |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's significant code duplication in mapping results between v3 (lines 1009-1028) and v4 (lines 968-989) list processing. The mapping logic for movies, TV shows, persons, and collections is nearly identical. Consider extracting this into a shared helper function to improve maintainability.
| const mappedResults = data.results.map((result) => | |
| isMovie(result) | |
| ? mapMovieResult( | |
| result, | |
| media.find( | |
| (m) => | |
| m.tmdbId === result.id && m.mediaType === MediaType.MOVIE | |
| ) | |
| ) | |
| : isPerson(result) | |
| ? mapPersonResult(result) | |
| : isCollection(result) | |
| ? mapCollectionResult(result) | |
| : mapTvResult( | |
| result, | |
| media.find( | |
| (m) => m.tmdbId === result.id && m.mediaType === MediaType.TV | |
| ) | |
| ) | |
| const mapSearchResultWithMedia = ( | |
| result: any, | |
| mediaItems: Media[] | |
| ) => { | |
| if (isMovie(result)) { | |
| return mapMovieResult( | |
| result, | |
| mediaItems.find( | |
| (m) => | |
| m.tmdbId === result.id && | |
| m.mediaType === MediaType.MOVIE | |
| ) | |
| ); | |
| } | |
| if (isPerson(result)) { | |
| return mapPersonResult(result); | |
| } | |
| if (isCollection(result)) { | |
| return mapCollectionResult(result); | |
| } | |
| return mapTvResult( | |
| result, | |
| mediaItems.find( | |
| (m) => | |
| m.tmdbId === result.id && m.mediaType === MediaType.TV | |
| ) | |
| ); | |
| }; | |
| const mappedResults = data.results.map((result) => | |
| mapSearchResultWithMedia(result, media) |
| return res.status(200).json({ | ||
| page: v4.page ?? 1, | ||
| totalPages: v4.total_pages ?? 1, | ||
| totalResults: mappedResults.length, |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The totalResults field should use the TMDB API's total_results value (v4.total_results ?? 0) instead of mappedResults.length. Using mappedResults.length only reflects the current page's result count, not the total number of results across all pages. This breaks pagination logic in the UI.
| totalResults: mappedResults.length, | |
| totalResults: v4.total_results ?? 0, |
| case DiscoverSliderType.TMDB_LIST: | ||
| sliderComponent = ( | ||
| <MediaSlider | ||
| sliderKey={`custom-slider-${slider.id}`} | ||
| title={slider.title ?? ''} | ||
| url={`/api/v1/discover/list/${slider.data}`} | ||
| // linkUrl intentionally omitted | ||
| /> | ||
| ); | ||
| break; |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new TMDB_LIST slider type lacks test coverage. Consider adding an E2E test case in cypress/e2e/settings/discover-customization.cy.ts or cypress/e2e/discover.cy.ts to verify that TMDB list sliders can be created, display correctly, and handle both valid and invalid list IDs appropriately.
gauthier-th
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please also add Cypress tests?
| const data = await this.get<any>(`/list/${listId}`, { | ||
| params: { | ||
| language, | ||
| }, | ||
| }); | ||
|
|
||
| // The API does not provide pagination on lists so we normalise here. | ||
| const items = | ||
| data?.items ?? | ||
| ([] as ( | ||
| | TmdbMovieResult | ||
| | TmdbTvResult | ||
| | TmdbPersonResult | ||
| | TmdbCollectionResult | ||
| )[]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you move the type definition of data.items inside the API call with this.get<T> instead of using any?
| results: items as ( | ||
| | TmdbMovieResult | ||
| | TmdbTvResult | ||
| | TmdbPersonResult | ||
| | TmdbCollectionResult | ||
| )[], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Useless type conversion? Same type as before.
| if (seasonNumber === 0) { | ||
| return this.createEmptySeasonResponse(tvId); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? A season with index 0 is considered as a special season, not as an empty one.
| ): TmdbTvSeasonResult { | ||
| const seasonNumber = season.number ?? -1; | ||
| if (seasonNumber < 0) { | ||
| if (!season.number) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as before, 0 is a valid season number, used for special seasons.
| const page = req.query.page ? Number(req.query.page) || 1 : 1; | ||
|
|
||
| try { | ||
| let data: { results: any[] } | null = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the right type for the TMDB list instead of any.
| const resp = await axiosInstance.get(v4Url, { | ||
| params: { page, language }, | ||
| }); | ||
|
|
||
| const v4 = resp.data as { | ||
| page?: number; | ||
| total_pages?: number; | ||
| total_results?: number; | ||
| results?: { id: number; media_type: string }[]; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as before, move the type definition of resp.data inside the API call with axiosInstance.get<T> instead of using any
Description
This PR introduces a new discovery slider type (
TMDB_LIST) that allows administrators to configure a public TMDB list ID and display its items.Built and tested locally with Docker (
Dockerfile.local), slider works as expected.Screenshot (if UI-related)
To-Dos
pnpm buildpnpm i18n:extract(New keys such as
tmdbListandprovidetmdblistidwere added and need to be present in the i18n schema.)Issues Fixed or Closed