Skip to content

Conversation

@AintGotNoLoveToday
Copy link

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)

vlcsnap-2025-09-08-16h03m00s532 vlcsnap-2025-09-08-16h03m38s987 vlcsnap-2025-09-08-16h04m18s656

To-Dos

  • Successful build pnpm build
  • Translation keys extracted with pnpm i18n:extract
    (New keys such as tmdbList and providetmdblistid were added and need to be present in the i18n schema.)

Issues Fixed or Closed

@Gauvino
Copy link
Contributor

Gauvino commented Sep 8, 2025

Can a preview be done this ?

@AintGotNoLoveToday
Copy link
Author

Can a preview be done this ?

I think you would like to ask if I can show you a little preview? :)
Here is a screerecord:
https://github.com/user-attachments/assets/a314822b-7d2b-472d-bd01-6a5d7d73c8b0

If you wanna build this with docker and try this on your own:

git clone [email protected]:AintGotNoLoveToday/jellyseerr-slider-tmdb-lists.git ./jellyseerr-tmdb-lists-feat/
cd jellyseerr-tmdb-lists-feat
git checkout feature-tmdb-list-slider

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

@Gauvino
Copy link
Contributor

Gauvino commented Sep 8, 2025

Can a preview be done this ?

I think you would like to ask if I can show you a little preview? :) Here is a screerecord: user-attachments/assets/a314822b-7d2b-472d-bd01-6a5d7d73c8b0

If you wanna build this with docker and try this on your own:

git clone [email protected]:AintGotNoLoveToday/jellyseerr-slider-tmdb-lists.git ./jellyseerr-tmdb-lists-feat/
cd jellyseerr-tmdb-lists-feat
git checkout feature-tmdb-list-slider

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

@Gylesie
Copy link
Contributor

Gylesie commented Sep 8, 2025

Beware, low-effort AI slop.

@AintGotNoLoveToday
Copy link
Author

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.

@gauthier-th
Copy link
Member

We are merging PRs almost every day, but every review takes time, we can't merge everything at the same time.
Don't worry, we will review it and come back to you, but we are always prioritizing bug fixes and overall QoL features before merging new features.

@github-actions github-actions bot added the merge conflict Cannot merge due to merge conflicts label Oct 6, 2025
@github-actions
Copy link

github-actions bot commented Oct 6, 2025

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
@AintGotNoLoveToday AintGotNoLoveToday requested a review from a team as a code owner December 7, 2025 19:10
@github-actions github-actions bot removed the merge conflict Cannot merge due to merge conflicts label Dec 7, 2025
@AintGotNoLoveToday
Copy link
Author

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.
All actual code changes, refactoring work, and the full conflict resolution were written, reviewed, and tested by myself.

In addition, I thoroughly tested the feature.
The TMDB list integration works reliably, including under the updated Seerr project structure.

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 👍

Copy link
Collaborator

@fallenbagel fallenbagel left a 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.

@AintGotNoLoveToday
Copy link
Author

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 👍

@AintGotNoLoveToday
Copy link
Author

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.

okay, I think its done :D Just my commits are now showed up instead of the 121....

Copy link
Contributor

Copilot AI left a 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_LIST slider type added to the discovery system
  • Backend endpoint /api/v1/discover/list/:listId supporting 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.

Comment on lines +948 to +952
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.

)[],
};
} catch (e) {
throw new Error(`[TMDB] Failed to fetch list: ${e.message}`);
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1009 to +1027
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
)
)
Copy link

Copilot AI Jan 5, 2026

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.

Suggested change
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)

Copilot uses AI. Check for mistakes.
return res.status(200).json({
page: v4.page ?? 1,
totalPages: v4.total_pages ?? 1,
totalResults: mappedResults.length,
Copy link

Copilot AI Jan 5, 2026

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.

Suggested change
totalResults: mappedResults.length,
totalResults: v4.total_results ?? 0,

Copilot uses AI. Check for mistakes.
Comment on lines +399 to +408
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;
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
Copy link
Member

@gauthier-th gauthier-th left a 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?

Comment on lines +789 to +803
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
)[]);
Copy link
Member

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?

Comment on lines +809 to +814
results: items as (
| TmdbMovieResult
| TmdbTvResult
| TmdbPersonResult
| TmdbCollectionResult
)[],
Copy link
Member

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.

Comment on lines +207 to +210
if (seasonNumber === 0) {
return this.createEmptySeasonResponse(tvId);
}

Copy link
Member

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) {
Copy link
Member

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;
Copy link
Member

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.

Comment on lines +952 to +961
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 }[];
};
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Support for Custom Sliders Based on External or Dynamic Sources (e.g. RSS, TMDB Lists, Keywords)

5 participants