Improve error handling across deal sources and increase CheapShark page size
Add proper error handling for price parsing, date parsing, and HTTP status codes in CheapShark, Epic, and ITAD integrations. Fix Epic dedup IDs to distinguish current vs upcoming offers. Bump CheapShark fetch page size from 10 to 60. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,12 +49,12 @@ type cheapSharkRaw struct {
|
||||
|
||||
// FetchCheapSharkDeals fetches deals from CheapShark API.
|
||||
func FetchCheapSharkDeals(maxPrice float64, pageSize int) ([]CheapSharkDeal, error) {
|
||||
url := fmt.Sprintf(
|
||||
reqURL := fmt.Sprintf(
|
||||
"https://www.cheapshark.com/api/1.0/deals?storeID=1,7,11,23&upperPrice=%d&sortBy=recent&desc=1&pageSize=%d",
|
||||
int(maxPrice), pageSize,
|
||||
)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
resp, err := http.Get(reqURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cheapshark request failed: %w", err)
|
||||
}
|
||||
@@ -71,11 +71,17 @@ func FetchCheapSharkDeals(maxPrice float64, pageSize int) ([]CheapSharkDeal, err
|
||||
|
||||
var deals []CheapSharkDeal
|
||||
for _, d := range raw {
|
||||
sale, _ := strconv.ParseFloat(d.SalePrice, 64)
|
||||
normal, _ := strconv.ParseFloat(d.NormalPrice, 64)
|
||||
savings, _ := strconv.ParseFloat(d.Savings, 64)
|
||||
sale, err1 := strconv.ParseFloat(d.SalePrice, 64)
|
||||
normal, err2 := strconv.ParseFloat(d.NormalPrice, 64)
|
||||
savings, err3 := strconv.ParseFloat(d.Savings, 64)
|
||||
rating, _ := strconv.ParseFloat(d.DealRating, 64)
|
||||
|
||||
if err1 != nil || err2 != nil || err3 != nil {
|
||||
slog.Warn("cheapshark: skipping deal with unparseable prices", "title", d.Title,
|
||||
"salePrice", d.SalePrice, "normalPrice", d.NormalPrice, "savings", d.Savings)
|
||||
continue
|
||||
}
|
||||
|
||||
storeName := storeNames[d.StoreID]
|
||||
if storeName == "" {
|
||||
storeName = "Unknown"
|
||||
@@ -103,12 +109,12 @@ func FetchCheapSharkDeals(maxPrice float64, pageSize int) ([]CheapSharkDeal, err
|
||||
|
||||
// SearchCheapSharkDeals searches for current deals matching a title query.
|
||||
func SearchCheapSharkDeals(query string, maxResults int) ([]CheapSharkDeal, error) {
|
||||
url := fmt.Sprintf(
|
||||
reqURL := fmt.Sprintf(
|
||||
"https://www.cheapshark.com/api/1.0/deals?storeID=1,7,11,23&sortBy=savings&desc=1&pageSize=%d&title=%s",
|
||||
maxResults, url.QueryEscape(query),
|
||||
)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
resp, err := http.Get(reqURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cheapshark search failed: %w", err)
|
||||
}
|
||||
@@ -125,9 +131,14 @@ func SearchCheapSharkDeals(query string, maxResults int) ([]CheapSharkDeal, erro
|
||||
|
||||
var results []CheapSharkDeal
|
||||
for _, d := range raw {
|
||||
sale, _ := strconv.ParseFloat(d.SalePrice, 64)
|
||||
normal, _ := strconv.ParseFloat(d.NormalPrice, 64)
|
||||
savings, _ := strconv.ParseFloat(d.Savings, 64)
|
||||
sale, err1 := strconv.ParseFloat(d.SalePrice, 64)
|
||||
normal, err2 := strconv.ParseFloat(d.NormalPrice, 64)
|
||||
savings, err3 := strconv.ParseFloat(d.Savings, 64)
|
||||
|
||||
if err1 != nil || err2 != nil || err3 != nil {
|
||||
slog.Warn("cheapshark: skipping search result with unparseable prices", "title", d.Title)
|
||||
continue
|
||||
}
|
||||
|
||||
if savings <= 0 {
|
||||
continue
|
||||
|
||||
@@ -3,6 +3,7 @@ package deals
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@@ -88,6 +89,10 @@ func FetchEpicFreeGames() ([]EpicFreeGame, error) {
|
||||
if slug == "" && len(elem.CatalogNs.Mappings) > 0 {
|
||||
slug = elem.CatalogNs.Mappings[0].PageSlug
|
||||
}
|
||||
if slug == "" {
|
||||
slog.Warn("epic: skipping game with no slug", "title", elem.Title, "id", elem.ID)
|
||||
continue
|
||||
}
|
||||
storeURL := fmt.Sprintf("https://store.epicgames.com/en-US/p/%s", slug)
|
||||
|
||||
// Check current free offers
|
||||
@@ -96,8 +101,12 @@ func FetchEpicFreeGames() ([]EpicFreeGame, error) {
|
||||
if offer.DiscountSetting.DiscountPercentage != 0 {
|
||||
continue
|
||||
}
|
||||
start, _ := time.Parse(time.RFC3339, offer.StartDate)
|
||||
end, _ := time.Parse(time.RFC3339, offer.EndDate)
|
||||
start, errS := time.Parse(time.RFC3339, offer.StartDate)
|
||||
end, errE := time.Parse(time.RFC3339, offer.EndDate)
|
||||
if errS != nil || errE != nil {
|
||||
slog.Warn("epic: failed to parse offer dates", "title", elem.Title, "start", offer.StartDate, "end", offer.EndDate)
|
||||
continue
|
||||
}
|
||||
|
||||
if start.After(now) || end.Before(now) {
|
||||
continue
|
||||
@@ -109,7 +118,7 @@ func FetchEpicFreeGames() ([]EpicFreeGame, error) {
|
||||
Desc: elem.Description,
|
||||
URL: storeURL,
|
||||
Upcoming: false,
|
||||
DedupID: fmt.Sprintf("epic-%s", elem.ID),
|
||||
DedupID: fmt.Sprintf("epic-current-%s", elem.ID),
|
||||
}
|
||||
if !end.IsZero() {
|
||||
game.EndDate = &end
|
||||
@@ -131,7 +140,7 @@ func FetchEpicFreeGames() ([]EpicFreeGame, error) {
|
||||
Desc: elem.Description,
|
||||
URL: storeURL,
|
||||
Upcoming: true,
|
||||
DedupID: fmt.Sprintf("epic-%s", elem.ID),
|
||||
DedupID: fmt.Sprintf("epic-upcoming-%s", elem.ID),
|
||||
}
|
||||
if offer.EndDate != "" {
|
||||
if end, err := time.Parse(time.RFC3339, offer.EndDate); err == nil {
|
||||
|
||||
@@ -166,6 +166,12 @@ func LookupHistoricalLows(apiKey string, steamAppIDs []string) (map[string]bool,
|
||||
continue
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
slog.Warn("itad lookup returned non-200", "appid", appID, "status", resp.StatusCode)
|
||||
resp.Body.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
var lookup struct {
|
||||
Found bool `json:"found"`
|
||||
Game struct {
|
||||
@@ -195,7 +201,10 @@ func LookupHistoricalLows(apiKey string, steamAppIDs []string) (map[string]bool,
|
||||
itadToSteam[itadID] = steamID
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(gameIDs)
|
||||
body, err := json.Marshal(gameIDs)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("itad overview marshal failed: %w", err)
|
||||
}
|
||||
url := fmt.Sprintf("https://api.isthereanydeal.com/games/overview/v2?key=%s", apiKey)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
@@ -203,6 +212,10 @@ func LookupHistoricalLows(apiKey string, steamAppIDs []string) (map[string]bool,
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return result, fmt.Errorf("itad overview returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var overview struct {
|
||||
Prices []struct {
|
||||
ID string `json:"id"`
|
||||
|
||||
Reference in New Issue
Block a user