Skip to content

fix(shoko): resolve series episode/movie cross-references by anime id - #1453

Merged
revam merged 1 commit into
ShokoAnime:masterfrom
Baine:fix/series-crossref-anime-scope
Oct 10, 2026
Merged

revam merged 1 commit into
ShokoAnime:masterfrom
Baine:fix/series-crossref-anime-scope

Conversation

@Baine

@Baine Baine commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Summary

AnimeSeries — the ISeries implementation backed by a Shoko series — exposes its episode-level and film-level cross-references by calling the per-episode lookups with the series' AniDB anime id:

// Shoko.Server/Models/Shoko/AnimeSeries.cs:465-469
IReadOnlyList<IMetadataEpisodeCrossReference> ISeries.MetadataEpisodeCrossReferences
    => ...GetService<IMetadataService>()?.GetEpisodeCrossReferences(AniDB_ID, null) ?? [];      // AniDB_ID is an anime id

IReadOnlyList<IMetadataMovieCrossReference> ISeries.MetadataMovieCrossReferences
    => ...GetService<IMetadataService>()?.GetMovieCrossReferences(AniDB_ID, null) ?? [];         // ditto

GetEpisodeCrossReferences / GetMovieCrossReferences are keyed by an AniDB episode id, so passing an anime id returns essentially nothing — or, worse, cross-references belonging to the unrelated AniDB episode whose id happens to equal this anime id.

The sibling MetadataSeriesCrossReferences and MetadataSeasonCrossReferences properties correctly use the series-scoped lookups (GetSeriesCrossReferences / GetSeasonCrossReferences), and AniDB_Anime already uses GetEpisodeCrossReferencesForSeries. Only these two lines use the wrong scope.

Root cause

A regression introduced in afe0455 ("refactor: my third most evilest commit to date"), which added the ISeries.Metadata*CrossReferences surface and gave AnimeSeries the per-episode overload instead of the per-anime one.

Impact

Every consumer of ISeries.MetadataEpisodeCrossReferences / MetadataMovieCrossReferences on a Shoko series:

  • Relocation / rename providers. LuaRenamer's TmdbToModel (ModelProducers.cs) reads series.GetEpisodeCrossReferences(MetadataSource.TMDB) to map TMDB episodes back to the AniDB episodes of the series. With this returning empty, each TMDB episode's anidbepisodeids is empty; the rename script loses the episode↔AniDB link and falls back to seasonnumber or 1. A "season 2" episode of a sequel anime is therefore renamed and filed into Season 01. (The series folder is still correct, because it comes from the series/anime id — only the season level is wrong, which is why it is so easy to miss.)
  • The API surface, e.g. GET /api/v3/Metadata/Shoko/Series/{id}/Episode/CrossReferences and .../Movie/CrossReferences.

Evidence

Reproduced on a live server (alpha.102, matching 6b4bfca9d) with Saga of Tanya the Evil — AniDB S1 = Shoko series 1243 / anime 11905, S2 = series 19964 / anime 16392, both linking TMDB show 69346:

Request Result
GET /api/v3/Series/19964/Metadata/TMDB/CrossReferences/Episode (via GetEpisodeCrossReferencesForSeries) 24 links, e.g. AniDB ep 312884 ↔ TMDB ep 7358247, SeasonNumber = 2
GET /api/v3/Metadata/Shoko/Series/19964/Episode/CrossReferences (via the broken property) {"Total": 0, "List": []}
GET /api/v3/Metadata/Shoko/Series/1243/Episode/CrossReferences 1 bogus entry — anime id 11905 reused as an episode id resolves to a TMDB episode of a different show

A relocation-preview debug of the S2 file logged:

tmdb.episodes[1] { id = 7358247, showid = 69346, season = 2, anidbepisodeids = [] }

The TMDB link is correct (season 2), but the AniDB episode list is empty — exactly what drives the renamer to Season 01.

The fix

Use the series-scoped overloads:

GetEpisodeCrossReferencesForSeries(AniDB_ID, null)
GetMovieCrossReferencesForSeries(AniDB_ID, null)

This matches:

  • the documented contract of the property — "The episode-level cross-references Shoko made for every episode of this entry, flattened across the series.";
  • how MetadataSeasonCrossReferences is already built (from the series' episode links);
  • the implementation AniDB_Anime already uses;
  • the summary of GetMovieCrossReferencesForSeries itself — "Looks up the film-level cross-references across a whole anime."

Behaviour for a provider-side series (where AniDB_ID is 0) is unchanged: both methods guard anidbAnimeID <= 0 and return an empty list.

Scope of change

Two lines in Shoko.Server/Models/Shoko/AnimeSeries.cs. No abstractions or public API signatures change; AnimeSeason and AnimeEpisode are unaffected (they build their references from the per-episode ones, which is correct).

Testing

  • dotnet build Shoko.Server/Shoko.Server.csproj — succeeds with the change.
  • Manual (against the live data in the table above): after the fix, GET /api/v3/Metadata/Shoko/Series/19964/Episode/CrossReferences returns the same 24 links as the TMDB route, and a relocation preview of the S2 file resolves to Season 02 / S02E01 instead of Season 01 / S01E01.

AnimeSeries exposed ISeries.MetadataEpisodeCrossReferences and
ISeries.MetadataMovieCrossReferences by calling the per-episode lookups
(GetEpisodeCrossReferences / GetMovieCrossReferences) with the series'
AniDB *anime* id. Those methods are keyed by AniDB episode id, so the
result was empty (or, when an unrelated AniDB episode happens to share
the id, wrong).

The sibling MetadataSeriesCrossReferences / MetadataSeasonCrossReferences
properties and AniDB_Anime already use the series-scoped overloads; do
the same here with GetEpisodeCrossReferencesForSeries /
GetMovieCrossReferencesForSeries.

This restores the episode<->AniDB mapping consumed by relocation/rename
providers (e.g. LuaRenamer's TmdbToModel), which otherwise lose the link
and fall back to season 1 for sequel anime.

Regression introduced in afe0455.
@revam
revam merged commit 2629c66 into ShokoAnime:master Oct 10, 2026
5 checks passed
@sonarqubecloud

Copy link
Copy Markdown

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.

2 participants