diff --git a/integrations/animeschedule/animeschedule.go b/integrations/animeschedule/animeschedule.go index f6eb549..66f2943 100644 --- a/integrations/animeschedule/animeschedule.go +++ b/integrations/animeschedule/animeschedule.go @@ -389,6 +389,32 @@ func fetchWeekAPI(ctx context.Context, httpClient *http.Client, token string, ye return WeekSchedule{}, err } + payload, err := fetchWeekAPIPayload(ctx, client, token, year, week, location) + if err != nil { + return WeekSchedule{}, err + } + + resolvedYear, resolvedWeek := resolveRequestedISOWeek(year, week) + out := WeekSchedule{ + Year: resolvedYear, + Week: resolvedWeek, + Days: map[time.Weekday][]Entry{}, + } + + for _, item := range payload { + entry, ok := weekEntryFromAPI(item, location) + if !ok { + continue + } + + out.Days[entry.Weekday] = append(out.Days[entry.Weekday], entry) + } + + return out, nil +} + +func fetchWeekAPIPayload(ctx context.Context, client *http.Client, token string, year int, week int, location *time.Location) ([]timetableAnimeAPI, error) { + u, _ := url.Parse("https://animeschedule.net/api/v3/timetables/sub") q := u.Query() if year > 0 && week > 0 { @@ -400,7 +426,7 @@ func fetchWeekAPI(ctx context.Context, httpClient *http.Client, token string, ye req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { - return WeekSchedule{}, fmt.Errorf("create api request: %w", err) + return nil, fmt.Errorf("create api request: %w", err) } req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("Accept", "application/json") @@ -408,13 +434,13 @@ func fetchWeekAPI(ctx context.Context, httpClient *http.Client, token string, ye res, err := client.Do(req) if err != nil { - return WeekSchedule{}, fmt.Errorf("api request failed: %w", err) + return nil, fmt.Errorf("api request failed: %w", err) } defer func() { _ = res.Body.Close() }() if res.StatusCode != http.StatusOK { body, _ := io.ReadAll(io.LimitReader(res.Body, netutil.Bytes512)) - return WeekSchedule{}, &HTTPStatusError{ + return nil, &HTTPStatusError{ StatusCode: res.StatusCode, URL: u.String(), ContentType: strings.TrimSpace(res.Header.Get("Content-Type")), @@ -424,72 +450,64 @@ func fetchWeekAPI(ctx context.Context, httpClient *http.Client, token string, ye var payload []timetableAnimeAPI if err := json.NewDecoder(res.Body).Decode(&payload); err != nil { - return WeekSchedule{}, fmt.Errorf("decode timetables api: %w", err) + return nil, fmt.Errorf("decode timetables api: %w", err) } - resolvedYear := year - resolvedWeek := week - if resolvedYear == 0 || resolvedWeek == 0 { - resolvedYear, resolvedWeek = time.Now().In(time.Local).ISOWeek() - } - - out := WeekSchedule{ - Year: resolvedYear, - Week: resolvedWeek, - Days: map[time.Weekday][]Entry{}, - } - - for _, item := range payload { - title := strings.TrimSpace(item.English) - if title == "" { - title = strings.TrimSpace(item.Title) - } - if title == "" { - continue - } - - episodeNumber := item.EpisodeNumber - subtracted := item.SubtractedEpisodeNumber - episodeText := "" - switch { - case subtracted > 0 && subtracted < episodeNumber: - episodeText = fmt.Sprintf("Ep %d-%d", subtracted, episodeNumber) - case episodeNumber > 0: - episodeText = fmt.Sprintf("Ep %d", episodeNumber) - default: - episodeText = "Ep ?" - } - - airType := AirType(strings.ToUpper(strings.TrimSpace(item.AirType))) - if airType != AirTypeSUB { - continue - } - - episodeTime := item.EpisodeDate.In(location) - weekday := episodeTime.Weekday() - localTime := episodeTime.Format("15:04") - - imageURL := "" - if strings.TrimSpace(item.ImageVersionRoute) != "" { - imageURL = "https://img.animeschedule.net/production/assets/public/img/" + strings.TrimLeft(strings.TrimSpace(item.ImageVersionRoute), "/") - } - - animeURL := "" - if strings.TrimSpace(item.Route) != "" { - animeURL = "https://animeschedule.net/anime/" + strings.TrimLeft(strings.TrimSpace(item.Route), "/") - } - - out.Days[weekday] = append(out.Days[weekday], Entry{ - Title: title, - AnimeURL: animeURL, - ImageURL: imageURL, - EpisodeText: episodeText, - AirType: airType, - AirsAt: episodeTime, - LocalTime: localTime, - Weekday: weekday, - }) - } - - return out, nil + return payload, nil +} + +func resolveRequestedISOWeek(year int, week int) (int, int) { + if year > 0 && week > 0 { + return year, week + } + + return time.Now().In(time.Local).ISOWeek() +} + +func weekEntryFromAPI(item timetableAnimeAPI, location *time.Location) (Entry, bool) { + title := strings.TrimSpace(item.English) + if title == "" { + title = strings.TrimSpace(item.Title) + } + if title == "" { + return Entry{}, false + } + + airType := AirType(strings.ToUpper(strings.TrimSpace(item.AirType))) + if airType != AirTypeSUB { + return Entry{}, false + } + + episodeTime := item.EpisodeDate.In(location) + + return Entry{ + Title: title, + AnimeURL: joinURLPath("https://animeschedule.net/anime/", item.Route), + ImageURL: joinURLPath("https://img.animeschedule.net/production/assets/public/img/", item.ImageVersionRoute), + EpisodeText: formatEpisodeText(item.EpisodeNumber, item.SubtractedEpisodeNumber), + AirType: airType, + AirsAt: episodeTime, + LocalTime: episodeTime.Format("15:04"), + Weekday: episodeTime.Weekday(), + }, true +} + +func formatEpisodeText(episodeNumber int, subtracted int) string { + switch { + case subtracted > 0 && subtracted < episodeNumber: + return fmt.Sprintf("Ep %d-%d", subtracted, episodeNumber) + case episodeNumber > 0: + return fmt.Sprintf("Ep %d", episodeNumber) + default: + return "Ep ?" + } +} + +func joinURLPath(base string, path string) string { + trimmed := strings.TrimSpace(path) + if trimmed == "" { + return "" + } + + return base + strings.TrimLeft(trimmed, "/") }