Schedule: AnimeFlow's AniList episode number wins outright (JoJo S6E4 no longer claims next week's ep 4)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-10 20:42:28 +03:00
1 parent e621b392af
commit 473c82ec0a
2 files changed
+28 -4

No files matched your search

@@ -140,17 +140,24 @@ List<ScheduleEntry> matchSchedule(
final id = s.anilistId; final id = s.anilistId;
if (id != null) refs[id] ??= _SeriesRef(s.seriesId, s.jellyfinSeriesId, s.followed); if (id != null) refs[id] ??= _SeriesRef(s.seriesId, s.jellyfinSeriesId, s.followed);
} }
// AnimeFlow's AniList episode number wins outright: TVDB numbering runs
// on across split entries (JoJo S6E4 is "2nd & 3rd STAGE" ep 3), so a TVDB
// or absolute number, or an air day, is only a guess for AniList entries
// AnimeFlow gives no AniList numbers for.
final byAni = <String, AnimeFlowScheduleItem>{};
final byEp = <String, AnimeFlowScheduleItem>{}; final byEp = <String, AnimeFlowScheduleItem>{};
final byAbs = <String, AnimeFlowScheduleItem>{}; final byAbs = <String, AnimeFlowScheduleItem>{};
final byDay = <String, AnimeFlowScheduleItem>{}; final byDay = <String, AnimeFlowScheduleItem>{};
final numbered = <int>{};
for (final it in schedule) { for (final it in schedule) {
final e = it.episode, id = e.anilistId; final e = it.episode, id = e.anilistId;
if (id == null) continue; if (id == null) continue;
final ref = refs[id] ??= _SeriesRef(it.seriesId, it.jellyfinSeriesId, it.followed); final ref = refs[id] ??= _SeriesRef(it.seriesId, it.jellyfinSeriesId, it.followed);
if (it.followed) ref.followed = true; if (it.followed) ref.followed = true;
// AnimeFlow's AniList episode number first: TVDB numbering can run on if (e.anilistEpisode != null) {
// across split cours. numbered.add(id);
if (e.anilistEpisode != null) byEp.putIfAbsent('$id#${e.anilistEpisode}', () => it); byAni.putIfAbsent('$id#${e.anilistEpisode}', () => it);
}
byEp.putIfAbsent('$id#${e.episode}', () => it); byEp.putIfAbsent('$id#${e.episode}', () => it);
if (e.absolute != null) byAbs.putIfAbsent('$id#${e.absolute}', () => it); if (e.absolute != null) byAbs.putIfAbsent('$id#${e.absolute}', () => it);
if (e.airDate != null) byDay.putIfAbsent('$id@${_day(e.airDate!)}', () => it); if (e.airDate != null) byDay.putIfAbsent('$id@${_day(e.airDate!)}', () => it);
@@ -160,7 +167,9 @@ List<ScheduleEntry> matchSchedule(
() { () {
final id = a.anilistId; final id = a.anilistId;
final ref = refs[id]; final ref = refs[id];
final it = byEp['$id#${a.episode}'] ?? byAbs['$id#${a.episode}'] ?? byDay['$id@${_day(a.airingAt)}']; final it = numbered.contains(id)
? byAni['$id#${a.episode}']
: byEp['$id#${a.episode}'] ?? byAbs['$id#${a.episode}'] ?? byDay['$id@${_day(a.airingAt)}'];
if (it != null) { if (it != null) {
final e = it.episode; final e = it.episode;
return ScheduleEntry( return ScheduleEntry(
@@ -129,6 +129,21 @@ void main() {
expect(out.single.season, 2); expect(out.single.season, 2);
}); });
test('a TVDB number never claims another AniList episode (JoJo S6E4 = 2nd STAGE ep 3)', () {
AnimeFlowScheduleItem row(int tvdbEp, int aniEp, EpisodeStatus st) => AnimeFlowScheduleItem(
episode: AnimeFlowEpisode(season: 6, episode: tvdbEp, anilistId: 210482, anilistEpisode: aniEp, status: st),
seriesId: 40,
title: 'JoJo',
jellyfinSeriesId: 'jfJ',
);
final out = matchSchedule(
[airing(210482, 3, DateTime(2026, 10, 9, 11)), airing(210482, 4, DateTime(2026, 10, 16, 11))],
schedule: [row(4, 3, EpisodeStatus.available), row(5, 4, EpisodeStatus.upcoming)],
now: now,
);
expect(out.map((e) => e.status), [ScheduleStatus.available, ScheduleStatus.upcoming]);
});
test('by AniList id + episode, per season ids', () { test('by AniList id + episode, per season ids', () {
// One AnimeFlow series (7) spans two AniList entries: S1 = 100, S2 = 200. // One AnimeFlow series (7) spans two AniList entries: S1 = 100, S2 = 200.
final out = matchSchedule( final out = matchSchedule(