feat: estimate released episode count for airing anime
This commit is contained in:
@@ -20,6 +20,23 @@ const (
|
||||
audioLookupTimeout = 8 * time.Second
|
||||
)
|
||||
|
||||
func releasedEpisodeCount(anime domain.Anime, now time.Time) int {
|
||||
if !anime.Airing || anime.Aired.From == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
firstAired, err := time.Parse(time.RFC3339, anime.Aired.From)
|
||||
if err != nil || now.Before(firstAired) {
|
||||
return 0
|
||||
}
|
||||
|
||||
count := int(now.Sub(firstAired)/(7*24*time.Hour)) + 1
|
||||
if anime.Episodes > 0 && count > anime.Episodes {
|
||||
return anime.Episodes
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func animeAudioAvailabilityLabel(episodes []domain.CanonicalEpisode) string {
|
||||
hasKnownSub := false
|
||||
for _, episode := range episodes {
|
||||
@@ -105,6 +122,7 @@ func (h *AnimeHandler) HandleAnimeDetails(c *gin.Context) {
|
||||
}
|
||||
|
||||
audioAvailability := h.animeAudioAvailability(c.Request.Context(), anime)
|
||||
episodesCount := releasedEpisodeCount(anime, time.Now())
|
||||
|
||||
c.HTML(http.StatusOK, "anime.gohtml", gin.H{
|
||||
"Anime": anime,
|
||||
@@ -115,6 +133,7 @@ func (h *AnimeHandler) HandleAnimeDetails(c *gin.Context) {
|
||||
"WatchlistIDs": watchlistIDs,
|
||||
"ContinueWatchingEp": ep,
|
||||
"ContinueWatchingTime": cwSeconds,
|
||||
"EpisodesCount": episodesCount,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"mal/integrations/jikan"
|
||||
"mal/internal/domain"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stubEpisodeService struct {
|
||||
@@ -26,6 +27,92 @@ func (s *stubEpisodeService) RefreshTrackedDue(ctx context.Context, limit int) e
|
||||
return nil
|
||||
}
|
||||
|
||||
type releasedCountTest struct {
|
||||
name string
|
||||
anime domain.Anime
|
||||
now time.Time
|
||||
want int
|
||||
}
|
||||
|
||||
var releasedCountTests = []releasedCountTest{
|
||||
{
|
||||
name: "weekly airing count",
|
||||
anime: domain.Anime{Anime: jikan.Anime{
|
||||
Airing: true,
|
||||
Episodes: 24,
|
||||
Aired: jikan.Aired{From: "2026-04-04T15:00:00+00:00"},
|
||||
}},
|
||||
now: time.Date(2026, time.June, 13, 15, 0, 0, 0, time.UTC),
|
||||
want: 11,
|
||||
},
|
||||
{
|
||||
name: "before first release",
|
||||
anime: domain.Anime{Anime: jikan.Anime{
|
||||
Airing: true,
|
||||
Aired: jikan.Aired{From: "2026-04-04T15:00:00+00:00"},
|
||||
}},
|
||||
now: time.Date(2026, time.April, 4, 14, 59, 0, 0, time.UTC),
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "first release counts as one",
|
||||
anime: domain.Anime{Anime: jikan.Anime{
|
||||
Airing: true,
|
||||
Aired: jikan.Aired{From: "2026-04-04T15:00:00+00:00"},
|
||||
}},
|
||||
now: time.Date(2026, time.April, 4, 15, 0, 0, 0, time.UTC),
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "caps at total episode count",
|
||||
anime: domain.Anime{Anime: jikan.Anime{
|
||||
Airing: true,
|
||||
Episodes: 12,
|
||||
Aired: jikan.Aired{From: "2026-04-04T15:00:00+00:00"},
|
||||
}},
|
||||
now: time.Date(2026, time.December, 1, 15, 0, 0, 0, time.UTC),
|
||||
want: 12,
|
||||
},
|
||||
{
|
||||
name: "unknown total still estimates current count",
|
||||
anime: domain.Anime{Anime: jikan.Anime{
|
||||
Airing: true,
|
||||
Aired: jikan.Aired{From: "2026-04-04T15:00:00+00:00"},
|
||||
}},
|
||||
now: time.Date(2026, time.April, 18, 15, 0, 0, 0, time.UTC),
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "non airing anime is not estimated",
|
||||
anime: domain.Anime{Anime: jikan.Anime{
|
||||
Airing: false,
|
||||
Aired: jikan.Aired{From: "2026-04-04T15:00:00+00:00"},
|
||||
}},
|
||||
now: time.Date(2026, time.April, 18, 15, 0, 0, 0, time.UTC),
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "invalid aired date is ignored",
|
||||
anime: domain.Anime{Anime: jikan.Anime{
|
||||
Airing: true,
|
||||
Aired: jikan.Aired{From: "not-a-date"},
|
||||
}},
|
||||
now: time.Date(2026, time.April, 18, 15, 0, 0, 0, time.UTC),
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
|
||||
func TestReleasedEpisodeCount(t *testing.T) {
|
||||
for _, tt := range releasedCountTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := releasedEpisodeCount(tt.anime, tt.now)
|
||||
if got != tt.want {
|
||||
t.Fatalf("releasedEpisodeCount() = %d, want %d", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnimeAudioAvailabilityLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user