from datetime import datetime, timezone from typing import Optional, Tuple, Union import pandas as pd import pytz try: from curl_cffi import requests as http_requests USE_IMPERSONATE = True except Exception: import requests as http_requests USE_IMPERSONATE = False SPORT_API_MAP = { "NASCAR": "NAS", "GOLF": "GOLF", "MLB": "MLB", "NBA": "NBA", "NHL": "NHL", "NFL": "NFL", "MMA": "MMA", } # MLB / NBA / NHL: only list contests whose slate date is "today" in US/Eastern (excludes next-day slates). _TODAY_SLATE_SPORTS = frozenset({"MLB", "NBA", "NHL"}) _EASTERN = pytz.timezone("US/Eastern") def _parse_sd_to_eastern_date_strings(start_raw) -> Tuple[Optional[str], Optional[str]]: """Return (YYYYMMDD, display YYYY-MM-DD) in US/Eastern from DK `sd` field, or (None, None).""" if start_raw is None: return None, None try: ts_ms = int(str(start_raw)[6:-2]) dt_utc = datetime.fromtimestamp(ts_ms / 1000.0, tz=timezone.utc) dt_eastern = dt_utc.astimezone(_EASTERN) return dt_eastern.strftime("%Y%m%d"), dt_eastern.strftime("%Y-%m-%d") except Exception: return None, None def _today_yyyymmdd_eastern() -> str: return datetime.now(_EASTERN).strftime("%Y%m%d") def _contest_type_matches(game_type: str, type_var: str) -> bool: if type_var == "Showdown": return game_type in {"Showdown", "Showdown Captain Mode"} return game_type in {"Classic", "Single Match", "Short Slate"} def _clean_contest_name(name: str) -> str: name = name or "" return name.strip() def _http_get_json(url: str, timeout: int = 20) -> dict: if USE_IMPERSONATE: response = http_requests.get(url, impersonate="chrome", timeout=timeout) else: response = http_requests.get(url, timeout=timeout) response.raise_for_status() return response.json() def fetch_contests_for_selection(sport_var: str, type_var: str) -> list[dict]: sport_code = SPORT_API_MAP.get(sport_var, sport_var) data = _http_get_json(f"https://www.draftkings.com/lobby/getcontests?sport={sport_code}") contests = data.get("Contests", []) filtered = [] for contest in contests: name = _clean_contest_name(contest.get("n")) game_type = contest.get("gameType", "") if not name: continue if "-Player" in name or "50-50" in name or "Winner Take All" in name: continue if not _contest_type_matches(game_type, type_var): continue start_raw = contest.get("sd") contest_date, contest_date_display = _parse_sd_to_eastern_date_strings(start_raw) if sport_var in _TODAY_SLATE_SPORTS: if not contest_date or contest_date != _today_yyyymmdd_eastern(): continue filtered.append( { "contest_name": name, "contest_id": contest.get("id"), "contest_date": contest_date or "", "contest_date_display": contest_date_display or "", "game_type": game_type, "prize_pool": contest.get("po", 0), } ) filtered.sort(key=lambda x: x.get("prize_pool", 0), reverse=True) return filtered def fetch_pricing_for_contest(contest_id: Union[int, str]) -> pd.DataFrame: contest_data = _http_get_json( f"https://api.draftkings.com/contests/v1/contests/{contest_id}?format=json" ) contest_detail = contest_data.get("contestDetail", {}) draft_group_id = contest_detail.get("draftGroupId") if draft_group_id is None: raise ValueError("No draftGroupId returned for selected contest.") draftables_data = _http_get_json( f"https://api.draftkings.com/draftgroups/v1/draftgroups/{draft_group_id}/draftables" ).get("draftables", []) rows = [] for draftable in draftables_data: rows.append( { "Name": draftable.get("displayName"), "ID": draftable.get("draftableId"), "Roster Position": draftable.get("position"), "Salary": draftable.get("salary"), "Team": draftable.get("teamAbbreviation"), } ) pricing_df = pd.DataFrame(rows) if pricing_df.empty: return pricing_df pricing_df = pricing_df.dropna(subset=["Name", "ID", "Roster Position", "Salary"]) pricing_df = pricing_df.drop_duplicates(subset=["Name", "ID"]) return pricing_df.reset_index(drop=True)