import logging from dataclasses import dataclass import httpx logger = logging.getLogger(__name__) BASE_URL = "https://www.cheapshark.com/api/1.0" # CheapShark store IDs for PC/digital storefronts STORE_IDS = { "1": "Steam", "7": "GOG", "11": "Humble Store", "23": "GreenManGaming", } @dataclass class CheapSharkDeal: deal_id: str game_id: str title: str sale_price: str normal_price: str savings: float # percentage off deal_rating: float store_id: str last_change: int # unix timestamp steam_app_id: str | None @property def store_name(self) -> str: return STORE_IDS.get(self.store_id, f"Store {self.store_id}") @property def deal_url(self) -> str: return f"https://www.cheapshark.com/redirect?dealID={self.deal_id}" @property def dedup_id(self) -> str: return f"cheapshark-{self.game_id}-{self.last_change}" async def fetch_deals( client: httpx.AsyncClient, *, max_price: float = 20, min_rating: int = 80, min_discount: int = 50, page_size: int = 10, ) -> list[CheapSharkDeal]: """Fetch top deals from CheapShark across configured stores.""" store_ids = ",".join(STORE_IDS.keys()) params = { "storeID": store_ids, "upperPrice": str(int(max_price)), "sortBy": "Deal Rating", "desc": "1", "pageSize": str(page_size), } try: resp = await client.get(f"{BASE_URL}/deals", params=params) resp.raise_for_status() raw_deals = resp.json() except (httpx.HTTPError, ValueError) as exc: logger.error("CheapShark API error: %s", exc) return [] deals = [] for d in raw_deals: savings = float(d.get("savings", 0)) rating = float(d.get("dealRating", 0)) if savings < min_discount: continue if rating < min_rating: continue steam_app_id = d.get("steamAppID") or None if steam_app_id == "0": steam_app_id = None deals.append( CheapSharkDeal( deal_id=d["dealID"], game_id=d["gameID"], title=d["title"], sale_price=d["salePrice"], normal_price=d["normalPrice"], savings=savings, deal_rating=rating, store_id=d["storeID"], last_change=int(d.get("lastChange", 0)), steam_app_id=steam_app_id, ) ) logger.info("CheapShark returned %d deals after filtering", len(deals)) return deals