Skip to content

Jellyfin [ALL]: Fix search for generated season titles#585

Open
macsla wants to merge 2 commits into
yuzono:masterfrom
macsla:fix/jellyfin-season-search
Open

Jellyfin [ALL]: Fix search for generated season titles#585
macsla wants to merge 2 commits into
yuzono:masterfrom
macsla:fix/jellyfin-season-search

Conversation

@macsla

@macsla macsla commented Jul 3, 2026

Copy link
Copy Markdown

Checklist:

  • Updated extVersionCode value in build.gradle for individual extensions
  • Updated overrideVersionCode or baseVersionCode as needed for all multisrc extensions
  • Referenced all related issues in the PR body (e.g. "Closes #xyz")
  • Added the isNsfw = true flag in build.gradle when appropriate
  • Have not changed source names
  • Have explicitly kept the id if a source's name or language were changed
  • Have tested the modifications by compiling and running the extension through Android Studio
  • Have removed web_hi_res_512.png when adding a new extension
  • This PR is AI-assisted, I have reviewed the changes manually and confirmed they are not slop
  • Have made sure all the icons are in png format

Add a 👍 reaction to pull requests you find important.

Summary by Sourcery

Improve Jellyfin search handling to correctly find series when users search using generated season titles.

Bug Fixes:

  • Fix search results so queries including generated season titles (e.g., "Series Name Season 1" or "Specials") can still match the underlying series.
  • Ensure pagination metadata is accurate when search results are narrowed to a specific matching season title.

Enhancements:

  • Normalize and strip season/specials suffixes from search terms to better align user queries with Jellyfin series titles.
  • Prioritize exact title matches when a requested season title differs from the base series name.

Build:

  • Bump Jellyfin extension version code to 31.

@sourcery-ai

sourcery-ai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adjusts Jellyfin search behavior to better handle season-style titles by normalizing search terms, optionally re-querying without season suffixes, and then post-filtering results, while also bumping the extension version code.

Sequence diagram for updated Jellyfin season title search behavior

sequenceDiagram
    actor User
    participant Jellyfin as Jellyfin.searchAnime
    participant Client as client

    User->>Jellyfin: searchAnime(query, page)
    Jellyfin->>Jellyfin: checkPreferences()
    Jellyfin->>Jellyfin: getSearchUrl(searchQuery)
    Jellyfin->>Client: get(url)
    Client-->>Jellyfin: parseAs_ItemListDto(json) as items

    Jellyfin->>Jellyfin: seriesQuery = searchQuery.replace(SEASON_TITLE_SUFFIX_REGEX, "")
    alt [seriesQuery != searchQuery && items.items.none { it.name.equals(searchQuery, ignoreCase = true) }]
        Jellyfin->>Jellyfin: getSearchUrl(seriesQuery)
        Jellyfin->>Client: get(url)
        Client-->>Jellyfin: parseAs_ItemListDto(json) as items
        Jellyfin->>Jellyfin: requestedSeasonTitle = searchQuery
    else [no season requery]
        Jellyfin->>Jellyfin: requestedSeasonTitle = null
    end

    Jellyfin->>Jellyfin: items.items.parallelFlatMap { ... } as animeList

    alt [requestedSeasonTitle != null]
        Jellyfin->>Jellyfin: matchingAnime = animeList.filter { it.title.equals(requestedSeasonTitle, ignoreCase = true) }
        Jellyfin->>Jellyfin: hasNextPage = false
    else
        Jellyfin->>Jellyfin: matchingAnime = animeList
        Jellyfin->>Jellyfin: hasNextPage = SERIES_FETCH_LIMIT * page < items.totalRecordCount
    end

    Jellyfin-->>User: AnimesPage(matchingAnime, hasNextPage)
Loading

File-Level Changes

Change Details Files
Refine search logic to handle generated season titles (e.g., "Season 1", "Specials") by normalizing the user query and conditionally re-running the search.
  • Wrap the existing items URL builder into a reusable getSearchUrl(searchTerm) helper that injects the SearchTerm parameter.
  • Trim the user query and perform the initial search with the cleaned search string.
  • Derive a series-level query by stripping season/specials suffixes via SEASON_TITLE_SUFFIX_REGEX, falling back to the original query when stripping yields blank.
  • If a season-like query was used and the first search results contain no exact name match, issue a second search using the series query and remember the requested season title.
  • Add SEASON_TITLE_SUFFIX_REGEX to match whitespace plus "Season N" or "Specials" suffixes case-insensitively.
src/all/jellyfin/src/eu/kanade/tachiyomi/animeextension/all/jellyfin/Jellyfin.kt
Post-filter search results to only return exact matches for the requested season title when applicable and adjust pagination accordingly.
  • Use requestedSeasonTitle to filter the built anime list down to exact title matches when a specific generated season title was requested.
  • Disable paging (hasNextPage = false) when searching by a specific season title to avoid misleading next-page indicators.
  • Keep original paging behavior when no season-specific post-filtering is applied.
src/all/jellyfin/src/eu/kanade/tachiyomi/animeextension/all/jellyfin/Jellyfin.kt
Bump Jellyfin extension version code to reflect the behavior change.
  • Increment extVersionCode from 30 to 31.
src/all/jellyfin/build.gradle

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The second parseAs call for the series search (client.get(getSearchUrl(seriesQuery)).parseAs(json)) drops the explicit ItemListDto type parameter; consider restoring it to avoid relying on type inference and potential mismatches.
  • The hasNextPage logic now depends on requestedSeasonTitle being null; double-check if this behavior (disabling pagination when a season title was requested) is intentional for all search scenarios, or if it should still respect items.totalRecordCount.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The second `parseAs` call for the series search (`client.get(getSearchUrl(seriesQuery)).parseAs(json)`) drops the explicit `ItemListDto` type parameter; consider restoring it to avoid relying on type inference and potential mismatches.
- The `hasNextPage` logic now depends on `requestedSeasonTitle` being null; double-check if this behavior (disabling pagination when a season title was requested) is intentional for all search scenarios, or if it should still respect `items.totalRecordCount`.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request improves the Jellyfin extension's search behavior when searching for specific seasons. If a search for a specific season fails, it strips the season suffix to search for the base series instead, and then filters the results to match the requested season. The review feedback points out that disabling pagination during season searches limits results to the first page (5 items), which may prevent finding the target series if it appears on later pages. Additionally, the reviewer suggests normalizing season titles (e.g., handling zero-padded season numbers like "Season 01" vs "Season 1") to make the matching more robust.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

val matchingAnime = requestedSeasonTitle?.let { title ->
animeList.filter { it.title.equals(title, ignoreCase = true) }
} ?: animeList
val hasNextPage = requestedSeasonTitle == null && SERIES_FETCH_LIMIT * page < items.totalRecordCount

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Disabling pagination when requestedSeasonTitle != null limits the search results to only the first page of series (up to 5 items, since SERIES_FETCH_LIMIT is 5). If the target series is not within the first 5 results, the user will never be able to find the requested season.

Removing requestedSeasonTitle == null && allows pagination to work correctly for season searches, as the next pages of series will be fetched and filtered as expected.

Suggested change
val hasNextPage = requestedSeasonTitle == null && SERIES_FETCH_LIMIT * page < items.totalRecordCount
val hasNextPage = SERIES_FETCH_LIMIT * page < items.totalRecordCount

Comment on lines +598 to +601
private val SEASON_TITLE_SUFFIX_REGEX = Regex(
"""\s+(?:Season\s+\d+|Specials)\s*$""",
RegexOption.IGNORE_CASE,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Add a regex to normalize season titles (e.g., stripping leading zeros from season numbers) to ensure robust matching during season searches.

        private val SEASON_TITLE_SUFFIX_REGEX = Regex(
            """\s+(?:Season\s+\d+|Specials)\s*$""",
            RegexOption.IGNORE_CASE,
        )
        private val SEASON_NORMALIZE_REGEX = Regex(
            """\bSeason\s+0*(\d+)\b""",
            RegexOption.IGNORE_CASE,
        )

Comment on lines +229 to +231
val matchingAnime = requestedSeasonTitle?.let { title ->
animeList.filter { it.title.equals(title, ignoreCase = true) }
} ?: animeList

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When searching for a specific season, the search query might contain a different zero-padding for the season number compared to how it is named in Jellyfin (e.g., Season 01 vs Season 1). To make the matching robust against such differences, we can normalize the season numbers by stripping leading zeros before comparing the titles.

Suggested change
val matchingAnime = requestedSeasonTitle?.let { title ->
animeList.filter { it.title.equals(title, ignoreCase = true) }
} ?: animeList
val matchingAnime = requestedSeasonTitle?.let { title ->
val normalizedTitle = title.replace(SEASON_NORMALIZE_REGEX, "Season $1")
animeList.filter {
it.title.replace(SEASON_NORMALIZE_REGEX, "Season $1").equals(normalizedTitle, ignoreCase = true)
}
} ?: animeList

@macsla

macsla commented Jul 3, 2026

Copy link
Copy Markdown
Author

Some context for this change: Jellyfin season entries are displayed as <series> Season N or <series> Specials, while the search targets parent series names. This causes exact-title searches during migration to return nothing unless the suffix is manually removed. It is particularly inconvenient after changing a Jellyfin server address or restoring a backup to a fresh app installation, when affected entries need to be migrated.

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.

1 participant