diff --git a/aerodatabox.py b/aerodatabox.py index 8ba4e12..e60f4ce 100644 --- a/aerodatabox.py +++ b/aerodatabox.py @@ -100,8 +100,16 @@ def lookup_gate_times(flight_number: str, flight_date: date) -> dict | None: f"{BASE_URL}/flights/number/{cleaned}/{flight_date.isoformat()}" "?dateLocalRole=Both&withAircraftImage=false&withLocation=false" ) + # A default urllib request (no User-Agent) gets HTTP 403 from AeroDataBox's + # Cloudflare-fronted gateway even with a perfectly valid, subscribed key - + # confirmed live, side by side, same key/URL: bare urllib -> 403, same + # request + this UA -> 200 with real data. See config.BROWSER_USER_AGENT. req = urllib.request.Request( - url, headers={"x-rapidapi-key": config.AERODATABOX_API_KEY} + url, + headers={ + "x-rapidapi-key": config.AERODATABOX_API_KEY, + "User-Agent": config.BROWSER_USER_AGENT, + }, ) try: with urllib.request.urlopen(req, timeout=20) as resp: diff --git a/boarding-pass-aerodatabox-403-fix.patch b/boarding-pass-aerodatabox-403-fix.patch new file mode 100644 index 0000000..c897c48 --- /dev/null +++ b/boarding-pass-aerodatabox-403-fix.patch @@ -0,0 +1,44 @@ +diff --git a/aerodatabox.py b/aerodatabox.py +index 8ba4e12..e60f4ce 100644 +--- a/aerodatabox.py ++++ b/aerodatabox.py +@@ -100,8 +100,16 @@ def lookup_gate_times(flight_number: str, flight_date: date) -> dict | None: + f"{BASE_URL}/flights/number/{cleaned}/{flight_date.isoformat()}" + "?dateLocalRole=Both&withAircraftImage=false&withLocation=false" + ) ++ # A default urllib request (no User-Agent) gets HTTP 403 from AeroDataBox's ++ # Cloudflare-fronted gateway even with a perfectly valid, subscribed key - ++ # confirmed live, side by side, same key/URL: bare urllib -> 403, same ++ # request + this UA -> 200 with real data. See config.BROWSER_USER_AGENT. + req = urllib.request.Request( +- url, headers={"x-rapidapi-key": config.AERODATABOX_API_KEY} ++ url, ++ headers={ ++ "x-rapidapi-key": config.AERODATABOX_API_KEY, ++ "User-Agent": config.BROWSER_USER_AGENT, ++ }, + ) + try: + with urllib.request.urlopen(req, timeout=20) as resp: +diff --git a/config.py b/config.py +index 25f93bc..4056f19 100644 +--- a/config.py ++++ b/config.py +@@ -19,10 +19,16 @@ AIRTRAIL_API_KEY = os.environ.get("AIRTRAIL_API_KEY", "") + # manual Search to fill in, same as today. + AERODATABOX_API_KEY = os.environ.get("AERODATABOX_API_KEY", "") + +-FLIGHTAWARE_USER_AGENT = ( ++# Shared browser UA for every outbound scrape/API call this pipeline makes. ++# Both FlightAware and (as of the AeroDataBox 403 debugging session) the ++# AeroDataBox/RapidAPI gateway sit behind Cloudflare and reject requests ++# carrying Python urllib's default User-Agent - confirmed live: the exact ++# same request/key/URL got HTTP 403 with no UA and HTTP 200 with this one. ++BROWSER_USER_AGENT = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/124.0 Safari/537.36" + ) ++FLIGHTAWARE_USER_AGENT = BROWSER_USER_AGENT # kept for anything still importing the old name + + MAX_TRACK_POINTS = 2000 + MAX_FETCH_ATTEMPTS = 5 diff --git a/config.py b/config.py index 25f93bc..4056f19 100644 --- a/config.py +++ b/config.py @@ -19,10 +19,16 @@ AIRTRAIL_API_KEY = os.environ.get("AIRTRAIL_API_KEY", "") # manual Search to fill in, same as today. AERODATABOX_API_KEY = os.environ.get("AERODATABOX_API_KEY", "") -FLIGHTAWARE_USER_AGENT = ( +# Shared browser UA for every outbound scrape/API call this pipeline makes. +# Both FlightAware and (as of the AeroDataBox 403 debugging session) the +# AeroDataBox/RapidAPI gateway sit behind Cloudflare and reject requests +# carrying Python urllib's default User-Agent - confirmed live: the exact +# same request/key/URL got HTTP 403 with no UA and HTTP 200 with this one. +BROWSER_USER_AGENT = ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/124.0 Safari/537.36" ) +FLIGHTAWARE_USER_AGENT = BROWSER_USER_AGENT # kept for anything still importing the old name MAX_TRACK_POINTS = 2000 MAX_FETCH_ATTEMPTS = 5