From 71dfeaebe9795697b7699acecd4ba9b96e433f08 Mon Sep 17 00:00:00 2001 From: partisan Date: Tue, 1 Jul 2025 22:06:49 +0200 Subject: [PATCH] Added fallback for currency instant answers --- ia-currency.go | 106 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 34 deletions(-) diff --git a/ia-currency.go b/ia-currency.go index 2f1de37..8618055 100644 --- a/ia-currency.go +++ b/ia-currency.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" "regexp" "strconv" @@ -17,6 +16,7 @@ import ( var ( exchangeRates = make(map[string]float64) nextUpdateTime time.Time + lastUpdateTime time.Time exchangeCacheMutex sync.RWMutex allCurrencies []string ) @@ -26,47 +26,83 @@ type CurrencyAPIResponse struct { Rates map[string]float64 `json:"rates"` } -// UpdateExchangeRates fetches and caches currency rates +var primaryURL = "https://open.er-api.com/v6/latest/USD" +var backupURL = "https://api.frankfurter.app/latest?base=USD" + +func fetchRates(url string) ([]byte, error) { + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + return io.ReadAll(resp.Body) +} + func UpdateExchangeRates() error { exchangeCacheMutex.Lock() defer exchangeCacheMutex.Unlock() - // Use a reliable free API with good rate limits - resp, err := http.Get("https://open.er-api.com/v6/latest/USD") - if err != nil { - return err - } - defer resp.Body.Close() + var ( + rates map[string]float64 + nextTime time.Time + fallback bool + ) - body, err := io.ReadAll(resp.Body) - if err != nil { - return err + // Try primary API + body, err := fetchRates(primaryURL) + if err == nil { + var res struct { + Result string `json:"result"` + Rates map[string]float64 `json:"rates"` + TimeNextUpdateUnix int64 `json:"time_next_update_unix"` + } + if err := json.Unmarshal(body, &res); err == nil && res.Result == "success" && len(res.Rates) > 0 { + rates = res.Rates + nextTime = time.Unix(res.TimeNextUpdateUnix, 0) + } else { + printWarn("Primary API response invalid or empty, falling back to backup") + fallback = true + } + } else { + printWarn("Primary API fetch failed: %v", err) + fallback = true } - var apiResponse struct { - Result string `json:"result"` - Rates map[string]float64 `json:"rates"` - TimeNextUpdateUnix int64 `json:"time_next_update_unix"` - } - if err := json.Unmarshal(body, &apiResponse); err != nil { - return err + // Try backup API if needed + if fallback { + body, err := fetchRates(backupURL) + if err != nil { + return fmt.Errorf("both rate fetches failed: %v", err) + } + + var res struct { + Base string `json:"base"` + Date string `json:"date"` + Rates map[string]float64 `json:"rates"` + } + if err := json.Unmarshal(body, &res); err != nil { + return fmt.Errorf("backup API response unmarshal failed: %v", err) + } + if len(res.Rates) == 0 { + return fmt.Errorf("backup API returned empty rates") + } + + rates = res.Rates + nextTime = time.Now().Add(6 * time.Hour) } - if apiResponse.Result != "success" { - return fmt.Errorf("API error: %s", apiResponse.Result) - } + // Finalize + exchangeRates = rates + nextUpdateTime = nextTime + lastUpdateTime = time.Now() - // Cache the rates - exchangeRates = apiResponse.Rates - nextUpdateTime = time.Unix(apiResponse.TimeNextUpdateUnix, 0) - - // Update list of all currencies allCurrencies = make([]string, 0, len(exchangeRates)) - for currency := range exchangeRates { - allCurrencies = append(allCurrencies, currency) + for c := range exchangeRates { + allCurrencies = append(allCurrencies, c) } printDebug("Updated currency rates: %d currencies cached", len(allCurrencies)) + printDebug("Next currency update at: %s", nextUpdateTime.Format(time.RFC1123)) return nil } @@ -76,7 +112,7 @@ func PrecacheAllCurrencyPairs() { defer exchangeCacheMutex.RUnlock() if len(exchangeRates) == 0 { - log.Println("Skipping precache - no rates available") + printWarn("Skipping precache: no currency rates available") return } @@ -99,11 +135,13 @@ func PrecacheAllCurrencyPairs() { func GetExchangeRate(from, to string) (float64, bool) { // Auto-update cache if expired if time.Now().After(nextUpdateTime) { - err := UpdateExchangeRates() - if err != nil { - printWarn("Currency update failed: %v", err) - // Postpone next attempt to avoid hammering API - nextUpdateTime = time.Now().Add(5 * time.Minute) + // Avoid excessive updates within 1 min + if time.Since(lastUpdateTime) > time.Minute { + err := UpdateExchangeRates() + if err != nil { + printWarn("Currency update failed: %v", err) + nextUpdateTime = time.Now().Add(5 * time.Minute) + } } }