Skip to content

feat: move to TheIntroDB API v4 with episode-order aware lookups - #35

Merged
Pasithea0 merged 2 commits into
mainfrom
v4
Oct 17, 2026
Merged

Pasithea0 merged 2 commits into
mainfrom
v4

Conversation

@Pasithea0

@Pasithea0 Pasithea0 commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Moves the plugin to TheIntroDB API v4 and closes the episode-mapping gap reported in TheIntroDB/jellyfin-plugin#31.

Why

/v4 keys every response by tvdb_id and treats TVDB as the media's identity. Two things followed from that:

  1. Episode numbers were being read in the wrong order. The plugin preferred tmdb_id, and Emby numbers episodes in the series' display order. A tmdb_id request is always read as TMDB numbering, so for a series set to DVD or absolute order the API answered a different episode — silently, and usually plausibly (the credits template matches). Measured on the Jellyfin side of the same API, Futurama under alternate answered a 14.6 s intro where the file's episode has a 29.3 s one.
  2. Key validation had gone dark. v2 and v3 are frozen at GET /media, so /v3/user/stats now 404s and the settings page silently stopped validating keys.

What changed

  • Identity: address by tvdb_id first, then tmdb_id, then imdb_id (the API's own priority). MediaResponse reads tvdb_id instead of tmdb_id.
  • Episode order: Series.DisplayOrder is sent as mapping. Emby's is an enum (Aired, Dvd, Absolute) rather than Jellyfin's free-form string, so Aired maps to no preference (the default order, costing no conversion) and Dvd/Absolute map to their TVDB order names. Emby cannot express alternate or regional ordering at all.
  • Episode id: the episode's own TVDB id is sent as tvdb_episode_id when it differs from the series id. Emby stores provider ids per item and some fetchers repeat the series id on episodes — the old code passed that through as tvdb_id, which the API reads as a series id and would fail to resolve. tmdb_id/tvdb_id for an episode now come from the series, with IMDb still allowed from either.
  • Fail closed: a series with a non-default order but no TVDB id is skipped and logged once per series, rather than answered with another order's data.
  • 404 cache: keyed on TVDB first and includes the order (episode:tvdb:71663:1:1:dvd), so one order's miss can't answer for another's.
  • Rate limits: v4 raised /media to 120 req / 10 s and 100,000 lookups/day for signed-in callers (was 30 and 1,000), and 5,000/day anonymous. Pacing is now tier-aware: 400 ms anonymous (unchanged), 100 ms with a key.
  • Key probe: /v4/user/stats.
  • Docs: README and the settings page describe the TVDB-first behaviour and the signed-in limits.

Verification

  • dotnet build: succeeded, 0 errors. dotnet test (net8.0 via the SDK 8 toolchain): 56 passed, including new V4MigrationTests asserting the exact query strings the client builds and the enum→order mapping.
  • InternalsVisibleTo added to the plugin project so the tests exercise the real members rather than reflecting over private fields.
  • Live, unauthenticated, the API answers the URLs this client now builds (?tvdb_id=…&season=…&episode=…&mapping=dvd&tvdb_episode_id=…, ?tvdb_id=169 for a movie, 400 invalid mapping for an unknown order), and /v3/user/stats returns 404 where /v4/user/stats returns 401 without a key.

Note for review

tvdb_episode_id overrides season/episode per the v4 docs (confirmed live: sending one episode's id with another's numbers returns the id's episode). We only send it when it is distinct from the series id, so a fetcher that repeats the series id on episodes cannot make the API look for a non-existent episode. If you'd rather send only mapping and drop the pin entirely, that is a one-line change.

No version bump: that's yours.

Summary by CodeRabbit

  • New Features
    • Episode matching now supports TVDB display orders and episode IDs, with TVDB preferred over TMDB and IMDb.
    • Authenticated API keys receive higher request and daily lookup limits.
  • Updates
    • The integration now uses API v4. Media identity can be provided by TVDB, TMDB, or IMDb.
  • Bug Fixes
    • Non-default episode orders without a valid TVDB series ID are skipped, with a warning logged once per series.

The API's v4 surface keys every response by tvdb_id and treats TVDB as the
media's identity. Address by tvdb_id first (tmdb_id and imdb_id still resolve),
and read the identity from the response DTO instead of tmdb_id.

Episode lookups now carry the series' display order, so a series whose episodes
are numbered in DVD or absolute order is resolved by the numbers Emby actually
has. Previously a TMDB-addressed series with a non-default order was answered
with another episode's segments, silently (Jellyfin watched this happen on
Futurama under 'alternate': S07E01 answered default S06E01, whose intro ends at
29.3s instead of 14.6s). Emby's Series.DisplayOrder is an enum rather than
Jellyfin's free-form string, so Aired maps to no preference and Dvd/Absolute map
to their TVDB order names. Emby cannot express alternate or regional ordering.

The episode's own TVDB id is sent as tvdb_episode_id when it differs from the
series id. Emby populates provider ids per item and some fetchers repeat the
series id on episodes, which the API would read as a non-existent episode; the
episode id used to be passed as tvdb_id in that case, which the API reads as a
series id.

A series with a non-default order but no TVDB id is skipped and logged once - no
other id can express the order.

Rate limits went up with v4: /media is 120 requests / 10s and 100,000 lookups
per day for signed-in callers (was 30 and 1,000) and 5,000 lookups per day
anonymous. Pacing now follows the tier in use instead of always assuming the
anonymous ceiling.

The API key probe moves to /v4/user/stats: v2 and v3 are frozen at GET /media
and /v3/user/stats now 404s, which silently disabled key validation.
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3052098a-792c-4d98-a3df-24e821f542b3
📥 Commits

Reviewing files that changed from the base of the PR and between 1b56720 and 2d96af6.

📒 Files selected for processing: 8
  • README.md
  • TheIntroDB.Tests/V4MigrationTests.cs
  • TheIntroDB/Api/MediaResponse.cs
  • TheIntroDB/Api/TheIntroDbApiKeyValidationService.cs
  • TheIntroDB/Api/TheIntroDbClient.cs
  • TheIntroDB/Configuration/configPage.html
  • TheIntroDB/Providers/TheIntroDbSegmentProvider.cs
  • TheIntroDB/TheIntroDB.csproj

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The client now targets API v4, prioritizes TVDB identifiers, and supports separate request pacing for authenticated and anonymous callers. Episode lookups include display-order details when available, and not-found cache keys distinguish episode order and pinned TVDB episode IDs.

Changes: TVDB episode lookup

Layer / File(s) Summary
API v4 client requests
TheIntroDB/Api/*, TheIntroDB.Tests/V4MigrationTests.cs, TheIntroDB/Configuration/configPage.html, TheIntroDB/TheIntroDB.csproj
The client sends requests to API v4 and selects a single identifier in TVDB, TMDB, then IMDb order. TVDB episode requests can include mapping and a positive episode ID. Request pacing uses separate authenticated and anonymous intervals. The response identity property and user-stats URI were also updated.
Episode display-order lookup
TheIntroDB/Providers/TheIntroDbSegmentProvider.cs, TheIntroDB.Tests/V4MigrationTests.cs, README.md
The provider derives episode IDs and display-order mapping from series and episode metadata. It skips non-default-order lookups without a positive series TVDB ID, logs the skip once per series, and includes mapping and episode ID in cache keys and requests.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant TheIntroDbSegmentProvider
  participant TheIntroDbClient
  participant TheIntroDBAPI
  TheIntroDbSegmentProvider->>TheIntroDbClient: GetMediaAsync with mapping and TVDB episode ID
  TheIntroDbClient->>TheIntroDBAPI: Request media using selected identifier and episode parameters
Loading
Merge Risk: ⚪ Minimal · up to 2d96a

No actionable issue is established by the available evidence. The change is mergeable after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 22 functions across 5 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely identifies the main changes: migration to TheIntroDB API v4 and episode-order-aware lookups.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 22 functions across 5 files. (3 skipped: 3 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

- Key the order-skip warning by the series id, not the episode id, so a large
  non-default-order series logs one warning instead of one per episode.
- Include the pinned TVDB episode id in the not-found cache key: the id
  outranks season/episode when sent, so a 404 recorded against one id could
  suppress a later lookup whose id had been corrected within the TTL.

The unsupported-display-order finding does not apply here: Emby's
Series.DisplayOrder is an enum (Aired, Dvd, Absolute), so no value can reach
the API under a name it does not know.
@Pasithea0

Copy link
Copy Markdown
Member Author

All three findings were valid — fixed in 1de959d:

  1. Skip warning keyed by episode id → now keyed by the series id (and the series name), falling back to the item when there is no series. The dictionary was accumulating one entry per episode, so a big non-default-order show logged a warning per episode.
  2. Pinned episode id missing from the cache key → the key now carries it (episode:tvdb:73871:7:1:alternate:ep1051911). Correct: the pin outranks season/episode when sent, so a 404 recorded against one id must not suppress a later lookup whose id has been corrected.
  3. Unsupported display orders → good catch, and the TMDB episode-group vocabulary is real: Jellyfin's TmdbSeriesProvider.MapDisplayOrderToTvGroupType fills Series.DisplayOrder with originalAirDate, absolute, dvd, digital, storyArc, production, tv. GetDisplayOrder now returns either a name the API knows (dvd, absolute, alternate, regional, plus the Jellyfin spellings which are canonicalized) or reports the order as unsupported. Unsupported orders take the skip path even when a TVDB id exists, and get their own log message listing the supported orders, since the old text claimed the series had no TVDB id.

BuildNotFoundKey is internal now so the cache-key shape is asserted in V4MigrationTests alongside the order classification. dotnet build clean, dotnet test 50 passed.

The same two applicable findings are fixed on the Emby PR (#35, commit 2d96af6). The third does not apply there: Emby's Series.DisplayOrder is an enum (Aired, Dvd, Absolute), so no value can reach the API under a name it does not know.

@Pasithea0
Pasithea0 merged commit b28d466 into main Oct 17, 2026
3 checks passed
@Pasithea0
Pasithea0 deleted the v4 branch October 17, 2026 20:03
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