""" Scrape Eurostar timetable via httpx. The route-specific timetable pages are Next.js SSR — all departure data is embedded in ', html, re.DOTALL) if not m: return [] data = json.loads(m.group(1)) departures = data['props']['pageProps']['pageData']['liveDepartures'] services = [] for dep in departures: dep_time = _hhmm(dep['origin']['model']['scheduledDepartureDateTime']) arr_time = _hhmm(dep['destination']['model']['scheduledArrivalDateTime']) if dep_time and arr_time: services.append({ 'depart_st_pancras': dep_time, 'arrive_destination': arr_time, 'destination': destination, }) return sorted(services, key=lambda s: s['depart_st_pancras']) async def fetch(destination: str, travel_date: str, user_agent: str = DEFAULT_UA) -> list[dict]: url = ROUTE_URLS[destination] headers = { 'User-Agent': user_agent, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-GB,en;q=0.9', } async with httpx.AsyncClient(headers=headers, follow_redirects=True, timeout=20) as client: r = await client.get(url, params={'date': travel_date}) r.raise_for_status() return _parse(r.text, destination) def get_eurostar_times(destination: str, travel_date: str, user_agent: str = DEFAULT_UA) -> list[dict]: """Synchronous wrapper for CLI/testing.""" return asyncio.run(fetch(destination, travel_date, user_agent))