Skip to content

feat(discover): implement multi-region discovery and filtering#2260

Closed
Valtora wants to merge 3 commits intoseerr-team:developfrom
Valtora:feature-multi-region-discovery
Closed

feat(discover): implement multi-region discovery and filtering#2260
Valtora wants to merge 3 commits intoseerr-team:developfrom
Valtora:feature-multi-region-discovery

Conversation

@Valtora
Copy link

@Valtora Valtora commented Dec 28, 2025

feat(discover): implement multi-region discovery and filtering

  • Update RegionSelector component to support multi-selection strings (e.g., 'US|GB')
  • Enhance RegionSelector UI to display specific flags and names for multiple selections
  • Update TheMovieDb API to use with_origin_country when multiple regions are selected
  • Implement filterResults helper to manually filter Trending/Upcoming results by both Origin Country and Original Language
  • Fix streamingRegion persistence issue in SettingsMain

Description

This PR implements Multi-Region Discovery and Filtering, allowing users to select multiple countries in the "Discover Region" setting. This ensures that content from all selected regions is visible in Discover, Trending, and Popular feeds.

AI Disclosure

AI was used (Gemini 3 Pro, High, via Antigravity) for code generation and refactoring with heavy human supervision throughout and manual verification of all logic.

Q: Why do I think this was okay in this context?
A: The changes made are relatively simple to the frontend and API logic without interfering with other systems.

Key Changes

  • Frontend RegionSelector:

    • Refactored to support multi-selection (stored as pipe-separated strings, e.g., US|GB).
    • Updated UI to display specific flags and country names for selected regions (e.g., "🇺🇸 United States, 🇬🇧 United Kingdom") instead of a generic count.
    • Implemented logic to handle exclusivity for "All Regions" and "Default" options.
  • Backend TheMovieDb:

    • Updated getDiscoverMovies and getDiscoverTv to use the with_origin_country parameter when multiple regions are selected.
    • Implemented a manual filterResults helper for endpoints that don't support simultaneous region/language filtering (like Trending).
    • "Safe Filter" Logic:
      • TV Shows: Strictly filters by origin country.
      • Movies: Includes movies if they match the origin country OR if the origin country data is missing (common in TMDb for newer entries), preventing over-filtering.
    • Language Filtering: Now manually filters Trending results to respect the originalLanguage setting (e.g., hiding Chinese content when English is selected).
  • Fixes:

    • Settings Persistence: Fixed a bug in SettingsMain where streamingRegion would revert to 'US' on refresh if "All Regions" (empty string) was selected. Changed logical OR (||) to nullish coalescing (??) to correctly handle the empty string value.

How Has This Been Tested?

I have tested this manually in a local development environment and verified with static analysis tools.

Manual Verification

  1. Multi-Selection:
    • Selected "United States" and "United Kingdom" in Settings -> General.
    • Verified that the UI correctly displays "🇺🇸 United States, 🇬🇧 United Kingdom".
    • Reloaded the page to ensure settings persistence (saved as US|GB).
  2. Discover Feeds:
    • Navigated to Discover -> Movies/TV.
    • Confirmed that content from both the US and UK appears.
  3. Trending Logic:
    • Verified that the "Trending" rows on the homepage respect the selected regions.
    • Edge Case: Confirmed that when "Discover Language" is set to "English", foreign content (like Chinese donghua) is correctly filtered out from the Trending list, even if it appears in the raw API response.
  4. Streaming Region Default:
    • Cleared the "Streaming Region" (setting it to "All Regions").
    • Refreshed the page.
    • Verified that it remained "All Regions" instead of reverting to "United States".

Automated Checks

  • Passed pnpm typecheck (Client & Server)
  • Passed pnpm lint

Screenshots / Logs (if applicable)

Screenshot 2025-12-28 125643

Checklist:

  • I have read and followed the contribution guidelines.
  • Disclosed any use of AI (see our policy)
    • Note: AI was used for code generation and refactoring, with heavy human supervision and manual verification of all logic.
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract
  • Database migration (if required) - N/A - Not Required

Summary by CodeRabbit

  • New Features

    • Multi-region content discovery: searches across multiple regions, merging, deduplicating and re-sorting combined movie and TV results.
    • Enhanced region selector: true multi-select UI showing multiple regions and "+ N more".
  • Bug Fixes

    • Clarified trending error messages to distinguish movies vs TV shows.
  • Documentation

    • Updated region selector text: "Default" label and pluralized "Regions Selected".

@Valtora Valtora requested a review from a team as a code owner December 28, 2025 14:09
@Valtora Valtora force-pushed the feature-multi-region-discovery branch from fabb4b5 to d1a7105 Compare December 30, 2025 20:47
@Valtora
Copy link
Author

Valtora commented Jan 2, 2026

Please review and let me know if I need to do anything else, thank you for taking the time to do so.

@fallenbagel
Copy link
Collaborator

Please review and let me know if I need to do anything else, thank you for taking the time to do so.

Seerr is on a feature freeze until next release. New features will be reviewed after release.

@Valtora
Copy link
Author

Valtora commented Jan 3, 2026

@fallenbagel Understood, thank you. I'll leave the PR here for now and come back after release. Good luck to you and team in the meantime, thank you for putting in the work to bring us seerr.

@github-actions github-actions bot added the merge conflict Cannot merge due to merge conflicts label Jan 29, 2026
@github-actions
Copy link

This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.

@github-actions github-actions bot removed the merge conflict Cannot merge due to merge conflicts label Feb 16, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

Adds multi-region support: RegionSelector changed to multi-select and emits pipe-delimited regions, TMDB API updated to request and aggregate multi-region discovery/trending results using with_origin_country + post-fetch filterResults, minor Settings nullish-coalescing change, and two i18n key updates.

Changes

Cohort / File(s) Summary
Multi-Region API Filtering
server/api/themoviedb/index.ts
Adds uniqBy import and a private filterResults<T>(results: T[]): T[]. Implements multi-region handling: builds per-region requests (using with_origin_country for multi-region), fetches in parallel, merges/deduplicates/sorts results, and applies filtering to discover and trending endpoints; updates trending error messages to distinguish movies vs TV.
Region Selector UI
src/components/RegionSelector/index.tsx
Converts single-region selector to multi-select (selectedRegionsList), introduces defaultRegion, isRegionSelected, multi-item rendering with “+ N more”, Listbox multiselect wiring, and onChange semantics emitting 'all', '' (default) or pipe-delimited region codes.
Settings & Translations
src/components/Settings/SettingsMain/index.tsx, src/i18n/locale/en.json
Settings: use ?? for streamingRegion defaulting. i18n: change components.RegionSelector.regionServerDefault to "Default" and add components.RegionSelector.regionsSelected pluralized key.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as RegionSelector UI
    participant State as Component State
    participant Form as Settings/Form
    participant API as TMDB API Layer
    participant TMDB as TMDB Service
    participant Filter as filterResults

    User->>UI: Select multiple regions
    UI->>State: Update selectedRegionsList
    UI->>Form: onChange(pipe-delimited regions)
    Form->>API: Request discover/trending (isMultiRegion)
    API->>TMDB: Parallel fetches using with_origin_country (per region)
    TMDB-->>API: Return results per region
    API->>Filter: Merge, dedupe (uniqBy), filterResults by origin_language/country
    Filter-->>API: Return combined paginated results
    API-->>Form: Return filtered data
    Form->>UI: Render filtered content
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰
I hopped through lists and pipes galore,
Collected flags and stitched them to the core,
I filtered, deduped, and sorted with glee,
Now many regions frolic — thanks to me! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(discover): implement multi-region discovery and filtering' directly and accurately summarizes the main change: implementing multi-region discovery with filtering across both frontend and backend components.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/RegionSelector/index.tsx (1)

160-234: ⚠️ Potential issue | 🔴 Critical

Remove stale selectedRegion rendering block.

Line 225-233 still references selectedRegion, which no longer exists after the refactor. This will not compile and also duplicates label rendering.

🧹 Proposed fix
-                  {selectedRegion && selectedRegion.iso_3166_1 !== 'all'
-                    ? regionName(selectedRegion.iso_3166_1)
-                    : isUserSetting && selectedRegion?.iso_3166_1 !== 'all'
-                      ? intl.formatMessage(messages.regionServerDefault, {
-                          region: regionValue
-                            ? regionName(regionValue)
-                            : intl.formatMessage(messages.regionDefault),
-                        })
-                      : intl.formatMessage(messages.regionDefault)}
server/api/themoviedb/index.ts (1)

724-729: ⚠️ Potential issue | 🟠 Major

Guard region when multi-region is selected.

/movie/upcoming expects an ISO 3166-1 country code and will not accept pipe-delimited values. /trending/all does not support the region parameter and ignores it. Apply the same multi-region guard already used in /discover/movie and /discover/tv to maintain consistency and ensure proper behavior:

💡 Suggested guard for multi-region
   public getUpcomingMovies = async ({
     page = 1,
     language = this.locale,
   }: {
     page: number;
     language: string;
   }): Promise<TmdbUpcomingMoviesResponse> => {
     try {
+      const isMultiRegion = this.discoverRegion?.includes('|');
       const data = await this.get<TmdbUpcomingMoviesResponse>(
         '/movie/upcoming',
         {
           params: {
             page,
             language,
-            region: this.discoverRegion,
+            region: isMultiRegion ? undefined : this.discoverRegion,
             originalLanguage: this.originalLanguage,
           },
         }
       );
   public getAllTrending = async ({
     page = 1,
     timeWindow = 'day',
     language = this.locale,
   }: {
     page?: number;
     timeWindow?: 'day' | 'week';
     language?: string;
   } = {}): Promise<TmdbSearchMultiResponse> => {
     try {
+      const isMultiRegion = this.discoverRegion?.includes('|');
       const data = await this.get<TmdbSearchMultiResponse>(
         `/trending/all/${timeWindow}`,
         {
           params: {
             page,
             language,
-            region: this.discoverRegion,
+            region: isMultiRegion ? undefined : this.discoverRegion,
           },
         }
       );

Also applies to: 754-758

🤖 Fix all issues with AI agents
In `@src/components/Settings/SettingsMain/index.tsx`:
- Around line 171-176: The initialValues object contains duplicate and
misspelled keys: remove the duplicate streamingRegion entry and keep a single
assignment using nullish coalescing (streamingRegion: data?.streamingRegion ??
'US') so empty-string values are preserved, and standardize the tag keys to
match the form (replace blacklistedTags and blacklistedTagsLimit with
blocklistedTags and blocklistedTagsLimit), using nullish coalescing for limits
as well (e.g., blocklistedTagsLimit: data?.blocklistedTagsLimit ?? 50); update
the initialValues in the SettingsMain component accordingly.

Comment on lines 171 to 176
streamingRegion: data?.streamingRegion ?? 'US',
blacklistedTags: data?.blacklistedTags,
blacklistedTagsLimit: data?.blacklistedTagsLimit || 50,
streamingRegion: data?.streamingRegion || 'US',
blocklistedTags: data?.blocklistedTags,
blocklistedTagsLimit: data?.blocklistedTagsLimit || 50,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Duplicate initialValues keys override the intended nullish default.

Line 171-176 defines streamingRegion twice; the later || overwrites the earlier ??, so empty-string selections still collapse to 'US'. Also, blacklistedTags* appears to be a typo and diverges from the blocklistedTags* fields actually used by the form. Please keep a single, correct set of keys.

🛠️ Proposed fix
-            streamingRegion: data?.streamingRegion ?? 'US',
-            blacklistedTags: data?.blacklistedTags,
-            blacklistedTagsLimit: data?.blacklistedTagsLimit || 50,
-            streamingRegion: data?.streamingRegion || 'US',
-            blocklistedTags: data?.blocklistedTags,
-            blocklistedTagsLimit: data?.blocklistedTagsLimit || 50,
+            streamingRegion: data?.streamingRegion ?? 'US',
+            blocklistedTags: data?.blocklistedTags,
+            blocklistedTagsLimit: data?.blocklistedTagsLimit || 50,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
streamingRegion: data?.streamingRegion ?? 'US',
blacklistedTags: data?.blacklistedTags,
blacklistedTagsLimit: data?.blacklistedTagsLimit || 50,
streamingRegion: data?.streamingRegion || 'US',
blocklistedTags: data?.blocklistedTags,
blocklistedTagsLimit: data?.blocklistedTagsLimit || 50,
streamingRegion: data?.streamingRegion ?? 'US',
blocklistedTags: data?.blocklistedTags,
blocklistedTagsLimit: data?.blocklistedTagsLimit || 50,
🧰 Tools
🪛 Biome (2.3.14)

[error] 171-171: This property is later overwritten by an object member with the same name.

Overwritten with this property.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property.

(lint/suspicious/noDuplicateObjectKeys)

🪛 GitHub Check: CodeQL

[failure] 171-171: Overwritten property
This property is overwritten by another property in the same object literal.

🤖 Prompt for AI Agents
In `@src/components/Settings/SettingsMain/index.tsx` around lines 171 - 176, The
initialValues object contains duplicate and misspelled keys: remove the
duplicate streamingRegion entry and keep a single assignment using nullish
coalescing (streamingRegion: data?.streamingRegion ?? 'US') so empty-string
values are preserved, and standardize the tag keys to match the form (replace
blacklistedTags and blacklistedTagsLimit with blocklistedTags and
blocklistedTagsLimit), using nullish coalescing for limits as well (e.g.,
blocklistedTagsLimit: data?.blocklistedTagsLimit ?? 50); update the
initialValues in the SettingsMain component accordingly.

@0xSysR3ll
Copy link
Contributor

Did you rebase ? There are still backlist leftovers. We renamed blacklist to blocklist as part of seerr release.

@Valtora
Copy link
Author

Valtora commented Feb 16, 2026

Hi, I'm currently in the process of bringing everything up to date so please review again in a day or two.

@Valtora Valtora force-pushed the feature-multi-region-discovery branch from 1142db1 to 5e01596 Compare February 16, 2026 14:31
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/RegionSelector/index.tsx (1)

1-12: ⚠️ Potential issue | 🟡 Minor

Prettier check failed for this file.

Pipeline reports a formatting failure; please run Prettier on this file before merge.

server/api/themoviedb/index.ts (1)

721-734: ⚠️ Potential issue | 🟠 Major

Omit the region parameter when discoverRegion contains multiple regions.

Lines 727 and 757 pass region: this.discoverRegion directly to TMDB endpoints. TMDB's region parameter only accepts a single ISO 3166-1 country code—not multi-region strings like US|GB. When discoverRegion contains |, passing it violates the API contract and may cause errors or empty results.

Other methods in this file (e.g., discover at line 572, searchMulti at line 661) already handle this correctly: they check discoverRegion?.includes('|') and omit the region parameter when multi-region is detected, relying on filterResults for post-filtering instead.

Apply the same pattern to getUpcomingMovies and getAllTrending:

Suggested changes
   public getUpcomingMovies = async ({
     page = 1,
     language = this.locale,
   }: {
     page: number;
     language: string;
   }): Promise<TmdbUpcomingMoviesResponse> => {
     try {
+      const isMultiRegion = this.discoverRegion?.includes('|');
       const data = await this.get<TmdbUpcomingMoviesResponse>(
         '/movie/upcoming',
         {
           params: {
             page,
             language,
-            region: this.discoverRegion,
+            region: isMultiRegion ? undefined : this.discoverRegion,
             originalLanguage: this.originalLanguage,
           },
         }
       );
   public getAllTrending = async ({
     page = 1,
     timeWindow = 'day',
     language = this.locale,
   }: {
     page?: number;
     timeWindow?: 'day' | 'week';
     language?: string;
   } = {}): Promise<TmdbSearchMultiResponse> => {
     try {
+      const isMultiRegion = this.discoverRegion?.includes('|');
       const data = await this.get<TmdbSearchMultiResponse>(
         `/trending/all/${timeWindow}`,
         {
           params: {
             page,
             language,
-            region: this.discoverRegion,
+            region: isMultiRegion ? undefined : this.discoverRegion,
           },
         }
       );
🤖 Fix all issues with AI agents
In `@server/api/themoviedb/index.ts`:
- Around line 572-583: The code currently passes pipe-delimited
this.discoverRegion into the TMDB with_origin_country param which only accepts a
single ISO country; update the logic where
get<TmdbSearchMovieResponse>('/discover/movie', { params: { ..., region:
isMultiRegion ? undefined : this.discoverRegion, with_origin_country:
isMultiRegion ? this.discoverRegion : undefined } }) is built: detect if
this.discoverRegion.includes('|') and, for multi-region, split on '|' and
perform one get(...) request per country with with_origin_country set to a
single code, then merge and dedupe results (e.g., by movie id) before returning;
otherwise keep the single request path; ensure pagination/total handling is
reconciled when merging or fall back to using region or post-filtering if
merging pages is unacceptable.

- Fixes 'with_origin_country' pipe-delimiter limitation by fetching regions in parallel.
- Fixes formatting in RegionSelector.
@Valtora Valtora force-pushed the feature-multi-region-discovery branch from b66cc3a to cc00b36 Compare February 16, 2026 15:01
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/api/themoviedb/index.ts (1)

1-5: ⚠️ Potential issue | 🟡 Minor

Resolve the Prettier warning before merge.

CI reports Prettier issues; please re-run the formatter to keep this file consistent.

🧹 Nitpick comments (1)
server/api/themoviedb/index.ts (1)

572-667: Consider bounding multi-region fan‑out to protect TMDB rate limits.

Promise.all scales linearly with selected regions; large selections can spike concurrency and fail the whole request. Consider capping region count or batching/limiting concurrency.

Also applies to: 712-804

@Valtora Valtora closed this Feb 16, 2026
@Valtora Valtora deleted the feature-multi-region-discovery branch February 16, 2026 15:10
@Valtora
Copy link
Author

Valtora commented Feb 16, 2026

This needs to be implemented properly as its slightly more complicated in scope than I initially realised. The API rate limit needs to be handled carefully and the changes are not only to the frontend but also the backend. I may revisit manually at a later time if there is enough demand.

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.

3 participants