diff options
| author | Bobby <[email protected]> | 2025-05-09 03:55:48 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-05-09 03:55:48 +0530 |
| commit | 6869a86c26793622f6018b0cf49e1e6b046dffc4 (patch) | |
| tree | 37d1fbbed7b673b9169366bedc73a4fc24a6802c /utils | |
| parent | cb41c834529f696c2f83adbb41e6b592c89495f3 (diff) | |
| download | metachan-6869a86c26793622f6018b0cf49e1e6b046dffc4.tar.xz metachan-6869a86c26793622f6018b0cf49e1e6b046dffc4.zip | |
updated tmdb for simple retrying
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/api/tmdb/tmdb.go | 175 |
1 files changed, 99 insertions, 76 deletions
diff --git a/utils/api/tmdb/tmdb.go b/utils/api/tmdb/tmdb.go index 1c5a87b..eda82fa 100644 --- a/utils/api/tmdb/tmdb.go +++ b/utils/api/tmdb/tmdb.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "math" - "math/rand" "metachan/config" "metachan/types" "metachan/utils/logger" @@ -13,74 +12,59 @@ import ( "time" ) -// makeRequestWithRetries executes an HTTP request with retries for handling temporary network failures -func makeRequestWithRetries(req *http.Request, maxRetries int) (*http.Response, error) { +// makeSimpleRequest executes a simple HTTP request with retries for both connection and rate limit errors +func makeSimpleRequest(req *http.Request) (*http.Response, error) { + // Create a simple HTTP client with a short timeout client := &http.Client{ - Timeout: 10 * time.Second, + Timeout: 5 * time.Second, // Reduced timeout for faster failure } + // Do retries for up to 3 attempts var lastErr error - for attempt := 0; attempt <= maxRetries; attempt++ { - if attempt > 0 { - // Exponential backoff with jitter for retries - backoffTime := time.Duration(math.Pow(1.5, float64(attempt))) * time.Second + var resp *http.Response - // Updated jitter calculation without using deprecated rand.Seed - jitter := time.Duration(rand.Int31n(500)) * time.Millisecond + for attempt := 0; attempt < 3; attempt++ { + // Execute the request + resp, lastErr = client.Do(req) - sleepTime := backoffTime + jitter + // If successful, check for rate limiting + if lastErr == nil { + // If we got rate limited (429), wait and retry + if resp.StatusCode == http.StatusTooManyRequests { + resp.Body.Close() - logger.Log(fmt.Sprintf("TMDB request retry %d/%d after %v due to: %v", - attempt, maxRetries, sleepTime, lastErr), logger.LogOptions{ - Level: logger.Debug, - Prefix: "TMDB", - }) - - time.Sleep(sleepTime) - - // Create a fresh request to avoid any issues with reusing the same request - newReq, err := http.NewRequest(req.Method, req.URL.String(), nil) - if err != nil { - return nil, fmt.Errorf("failed to create new request for retry: %w", err) - } - - // Copy all headers from the original request - for key, values := range req.Header { - for _, value := range values { - newReq.Header.Add(key, value) - } - } - - // Set the new retry request as our active request - req = newReq - } + logger.Log(fmt.Sprintf("TMDB rate limited (attempt %d/3): waiting 5 seconds", attempt+1), logger.LogOptions{ + Level: logger.Warn, + Prefix: "TMDB", + }) - resp, err := client.Do(req) - if err != nil { - lastErr = err - // Check if this is a network error that might be temporary - if strings.Contains(err.Error(), "connection reset by peer") || - strings.Contains(err.Error(), "EOF") || - strings.Contains(err.Error(), "connection refused") || - strings.Contains(err.Error(), "timeout") { - // These are retryable errors + // Wait for 5 seconds before retrying for rate limits + time.Sleep(5 * time.Second) continue } - // Other errors are not retryable - return nil, err + + // Any other status code (including success) should be returned + return resp, nil } - // If we got a server error (5xx), retry - if resp.StatusCode >= 500 && resp.StatusCode < 600 { - lastErr = fmt.Errorf("server error: %s", resp.Status) - resp.Body.Close() // Make sure we close the body before we retry + // Check if this is a connection reset error for immediate retry + if strings.Contains(lastErr.Error(), "connection reset") { + logger.Log(fmt.Sprintf("TMDB connection reset (attempt %d/3): retrying immediately", attempt+1), logger.LogOptions{ + Level: logger.Debug, + Prefix: "TMDB", + }) continue } - return resp, nil + // Log the error + logger.Log(fmt.Sprintf("TMDB request error (attempt %d/3): %v", attempt+1, lastErr), logger.LogOptions{ + Level: logger.Debug, + Prefix: "TMDB", + }) } - return nil, fmt.Errorf("failed after %d retries: %w", maxRetries, lastErr) + // All attempts failed, return the last error + return nil, fmt.Errorf("failed after 3 retry attempts: %w", lastErr) } // normalizeTitle cleans up the anime title for better matching with TMDB @@ -136,6 +120,7 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c Prefix: "TMDB", }) + // Create request apiURL := "https://api.themoviedb.org/3/search/tv" req, err := http.NewRequest("GET", apiURL, nil) if err != nil { @@ -151,8 +136,8 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", config.Config.TMDB.ReadAccessToken)) req.Header.Add("Accept", "application/json") - // Use our retry mechanism (3 retries) - resp, err := makeRequestWithRetries(req, 3) + // Make the simple request + resp, err := makeSimpleRequest(req) if err != nil { return nil, fmt.Errorf("failed to search TV shows: %w", err) } @@ -168,11 +153,9 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c return nil, fmt.Errorf("failed to decode response: %w", err) } - results := searchResponse.Results - // Filter results if needed var filteredResults []TMDBShowResult - for _, show := range results { + for _, show := range searchResponse.Results { if (isAdult && show.Adult) || (!isAdult && !show.Adult) { filteredResults = append(filteredResults, show) } @@ -234,8 +217,8 @@ func getTVShowDetails(showID int) (*TMDBShowDetails, error) { req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", config.Config.TMDB.ReadAccessToken)) req.Header.Add("Accept", "application/json") - // Use our retry mechanism (3 retries) - resp, err := makeRequestWithRetries(req, 5) + // Make the simple request + resp, err := makeSimpleRequest(req) if err != nil { return nil, fmt.Errorf("failed to get TV show details: %w", err) } @@ -246,12 +229,12 @@ func getTVShowDetails(showID int) (*TMDBShowDetails, error) { } // Parse response - var showDetails TMDBShowDetails - if err := json.NewDecoder(resp.Body).Decode(&showDetails); err != nil { + details := &TMDBShowDetails{} + if err := json.NewDecoder(resp.Body).Decode(details); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } - return &showDetails, nil + return details, nil } // getSeasonDetails gets details for a TV season from TMDB @@ -270,8 +253,8 @@ func getSeasonDetails(showID, seasonNumber int) (*TMDBSeasonDetails, error) { req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", config.Config.TMDB.ReadAccessToken)) req.Header.Add("Accept", "application/json") - // Use our retry mechanism (3 retries) - resp, err := makeRequestWithRetries(req, 3) + // Make the simple request + resp, err := makeSimpleRequest(req) if err != nil { return nil, fmt.Errorf("failed to get season details: %w", err) } @@ -282,12 +265,12 @@ func getSeasonDetails(showID, seasonNumber int) (*TMDBSeasonDetails, error) { } // Parse response - var seasonDetails TMDBSeasonDetails - if err := json.NewDecoder(resp.Body).Decode(&seasonDetails); err != nil { + details := &TMDBSeasonDetails{} + if err := json.NewDecoder(resp.Body).Decode(details); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } - return &seasonDetails, nil + return details, nil } // findBestSeason finds the best matching season for an anime @@ -338,17 +321,17 @@ func findBestSeason(shows []TMDBShowResult, title string, episodeCount int, airD } // AttachEpisodeDescriptions enriches anime episodes with descriptions and thumbnails from TMDB -func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode, alternativeTitle string, tmdbID int) []types.AnimeSingleEpisode { +func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode, alternativeTitle string, tmdbID int) ([]types.AnimeSingleEpisode, error) { if config.Config.TMDB.ReadAccessToken == "" { logger.Log("TMDB is not configured, skipping episode description enrichment", logger.LogOptions{ Level: logger.Warn, Prefix: "TMDB", }) - return episodes + return episodes, fmt.Errorf("TMDB is not configured") } if len(episodes) == 0 { - return episodes + return episodes, nil } logger.Log(fmt.Sprintf("Enriching episodes for: %s", title), logger.LogOptions{ @@ -360,10 +343,23 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode var seasonNumber int var err error + // Use a short timeout for the entire operation + startTime := time.Now() + maxDuration := 10 * time.Second + // If we have a TMDB ID, use it directly if tmdbID > 0 { showID = tmdbID + // Check if we've exceeded the timeout + if time.Since(startTime) > maxDuration { + logger.Log("TMDB enrichment timed out", logger.LogOptions{ + Level: logger.Warn, + Prefix: "TMDB", + }) + return episodes, fmt.Errorf("TMDB enrichment timed out") + } + // Try to get show details and find the best season showDetails, err := getTVShowDetails(showID) if err != nil { @@ -371,7 +367,7 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode Level: logger.Warn, Prefix: "TMDB", }) - return episodes + return episodes, fmt.Errorf("failed to get TMDB show details: %w", err) } // Find the best matching season - prefer the first season if we can't determine @@ -410,6 +406,15 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode Prefix: "TMDB", }) } else { + // Check if we've exceeded the timeout + if time.Since(startTime) > maxDuration { + logger.Log("TMDB enrichment timed out", logger.LogOptions{ + Level: logger.Warn, + Prefix: "TMDB", + }) + return episodes, fmt.Errorf("TMDB enrichment timed out") + } + // Search for the TV show on TMDB if we don't have a direct ID shows, err := searchTVShowsByTitle(title, alternativeTitle, false, "JP") if err != nil { @@ -417,7 +422,7 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode Level: logger.Warn, Prefix: "TMDB", }) - return episodes + return episodes, fmt.Errorf("failed to search TMDB shows: %w", err) } if len(shows) == 0 { @@ -425,7 +430,7 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode Level: logger.Warn, Prefix: "TMDB", }) - return episodes + return episodes, fmt.Errorf("no TMDB shows found for: %s", title) } // Find the best matching season @@ -434,16 +439,34 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode airDate = episodes[0].Aired } + // Check if we've exceeded the timeout + if time.Since(startTime) > maxDuration { + logger.Log("TMDB enrichment timed out", logger.LogOptions{ + Level: logger.Warn, + Prefix: "TMDB", + }) + return episodes, fmt.Errorf("TMDB enrichment timed out") + } + showID, seasonNumber, err = findBestSeason(shows, title, len(episodes), airDate) if err != nil { logger.Log(fmt.Sprintf("Failed to find best season: %v", err), logger.LogOptions{ Level: logger.Warn, Prefix: "TMDB", }) - return episodes + return episodes, fmt.Errorf("failed to find best season: %w", err) } } + // Check if we've exceeded the timeout + if time.Since(startTime) > maxDuration { + logger.Log("TMDB enrichment timed out", logger.LogOptions{ + Level: logger.Warn, + Prefix: "TMDB", + }) + return episodes, fmt.Errorf("TMDB enrichment timed out") + } + // Get season details with episode information seasonDetails, err := getSeasonDetails(showID, seasonNumber) if err != nil { @@ -451,7 +474,7 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode Level: logger.Warn, Prefix: "TMDB", }) - return episodes + return episodes, fmt.Errorf("failed to get season details: %w", err) } // Enrich episodes with descriptions and thumbnails @@ -494,5 +517,5 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode Prefix: "TMDB", }) - return enrichedEpisodes + return enrichedEpisodes, nil } |
