diff --git a/cmd/pastel/main.go b/cmd/pastel/main.go index b97ba82..1fc94e4 100644 --- a/cmd/pastel/main.go +++ b/cmd/pastel/main.go @@ -228,7 +228,7 @@ func main() { func populateInitialState(cfg *config.Config, db *database.DB) { // Record CheapShark deals without posting if cfg.HasSource("cheapshark") { - rawDeals, err := deals.FetchCheapSharkDeals(cfg.MaxPriceUSD, 10) + rawDeals, err := deals.FetchCheapSharkDeals(cfg.MaxPriceUSD, 60) if err != nil { slog.Warn("first run: cheapshark fetch failed", "error", err) } else { @@ -290,7 +290,7 @@ func checkCheapShark(cfg *config.Config, db *database.DB, mx *matrix.Client, con slog.Debug("checking cheapshark deals") conv.EnsureRates() - rawDeals, err := deals.FetchCheapSharkDeals(cfg.MaxPriceUSD, 10) + rawDeals, err := deals.FetchCheapSharkDeals(cfg.MaxPriceUSD, 60) if err != nil { slog.Error("cheapshark fetch failed", "error", err) return diff --git a/internal/deals/cheapshark.go b/internal/deals/cheapshark.go index 70290d9..3ff7d41 100644 --- a/internal/deals/cheapshark.go +++ b/internal/deals/cheapshark.go @@ -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 diff --git a/internal/deals/epic.go b/internal/deals/epic.go index f2b9e58..70b105c 100644 --- a/internal/deals/epic.go +++ b/internal/deals/epic.go @@ -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 { diff --git a/internal/deals/itad.go b/internal/deals/itad.go index fdc199b..eda3dc8 100644 --- a/internal/deals/itad.go +++ b/internal/deals/itad.go @@ -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"`