From 21a1965fddbb4c674e09627a7002eed2bcfa0ecb Mon Sep 17 00:00:00 2001 From: mkelvers Date: Sat, 13 Jun 2026 22:24:02 +0200 Subject: [PATCH] extract: add availability parsing --- .../playback/allanime/availability.go | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 integrations/playback/allanime/availability.go diff --git a/integrations/playback/allanime/availability.go b/integrations/playback/allanime/availability.go new file mode 100644 index 0000000..d12f64b --- /dev/null +++ b/integrations/playback/allanime/availability.go @@ -0,0 +1,102 @@ +package allanime + +import ( + "context" + "fmt" + "mal/internal/domain" + "strconv" + "strings" +) + +type AvailableEpisodes struct { + Sub []string + Dub []string + Raw []string +} + +func (c *AllAnimeProvider) GetEpisodeAvailability(ctx context.Context, animeID int, titleCandidates []string) (domain.EpisodeAvailability, error) { + showID, err := c.ResolveEpisodeProviderID(ctx, animeID, titleCandidates) + if err != nil { + return domain.EpisodeAvailability{}, err + } + return c.GetEpisodeAvailabilityByProviderID(ctx, showID) +} + +func (c *AllAnimeProvider) GetEpisodeAvailabilityByProviderID(ctx context.Context, showID string) (domain.EpisodeAvailability, error) { + available, err := c.GetAvailableEpisodes(ctx, showID) + if err != nil { + return domain.EpisodeAvailability{}, err + } + + sub := parseEpisodeNumbers(append(available.Sub, available.Raw...)) + dub := parseEpisodeNumbers(available.Dub) + return domain.EpisodeAvailability{Sub: sub, Dub: dub}, nil +} + +func (c *AllAnimeProvider) GetAvailableEpisodes(ctx context.Context, showID string) (AvailableEpisodes, error) { + graphqlQuery := `query($showId: String!) { + show(_id: $showId) { + availableEpisodesDetail + lastEpisodeInfo + } + }` + + result, err := c.graphqlRequest(ctx, graphqlQuery, map[string]any{"showId": showID}) + if err != nil { + return AvailableEpisodes{}, err + } + + data, ok := result["data"].(map[string]any) + if !ok { + return AvailableEpisodes{}, fmt.Errorf("invalid response") + } + + show, ok := data["show"].(map[string]any) + if !ok || show == nil { + return AvailableEpisodes{}, fmt.Errorf("show not found") + } + + detail, ok := show["availableEpisodesDetail"].(map[string]any) + if !ok { + return AvailableEpisodes{}, fmt.Errorf("invalid detail") + } + + return AvailableEpisodes{ + Sub: stringSliceFromAny(detail["sub"]), + Dub: stringSliceFromAny(detail["dub"]), + Raw: stringSliceFromAny(detail["raw"]), + }, nil +} + +func parseEpisodeNumbers(raw []string) []int { + seen := make(map[int]bool, len(raw)) + out := make([]int, 0, len(raw)) + for _, value := range raw { + n, err := strconv.Atoi(strings.TrimSpace(value)) + if err != nil || n <= 0 || seen[n] { + continue + } + seen[n] = true + out = append(out, n) + } + return out +} + +func stringSliceFromAny(value any) []string { + items, ok := value.([]any) + if !ok { + return nil + } + + values := make([]string, 0, len(items)) + for _, item := range items { + str, ok := item.(string) + if !ok { + continue + } + + values = append(values, str) + } + + return values +}