Initial commit
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import httpx
|
||||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Launch Library 2 API — completely free, no API key required
|
||||
UPCOMING_URL = "https://ll.thespacedevs.com/2.2.0/launch/upcoming/?limit=25&format=json"
|
||||
RECENT_URL = "https://ll.thespacedevs.com/2.2.0/launch/previous/?limit=10&format=json"
|
||||
|
||||
|
||||
def _parse_launch(item: dict) -> dict | None:
|
||||
"""Parse a single launch object from the LL2 API response."""
|
||||
pad = item.get("pad") or {}
|
||||
location = pad.get("location") or {}
|
||||
|
||||
try:
|
||||
lat = float(pad.get("latitude", ""))
|
||||
lon = float(pad.get("longitude", ""))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
if not (-90 <= lat <= 90) or not (-180 <= lon <= 180):
|
||||
return None
|
||||
|
||||
provider = (item.get("launch_service_provider") or {}).get("name", "Unknown")
|
||||
rocket = ((item.get("rocket") or {}).get("configuration") or {}).get("name", "Unknown")
|
||||
mission = item.get("mission") or {}
|
||||
status = (item.get("status") or {}).get("name", "Unknown")
|
||||
|
||||
return {
|
||||
"id": item.get("id", ""),
|
||||
"name": item.get("name", "Unknown Launch"),
|
||||
"status": status,
|
||||
"net": item.get("net", ""),
|
||||
"provider": provider,
|
||||
"country": location.get("country_code", ""),
|
||||
"pad": pad.get("name", "Unknown Pad"),
|
||||
"lat": lat,
|
||||
"lon": lon,
|
||||
"image": item.get("image", ""),
|
||||
"rocket": rocket,
|
||||
"mission": mission.get("name") or "",
|
||||
"mission_description": mission.get("description") or "",
|
||||
"type": "launch",
|
||||
}
|
||||
|
||||
|
||||
async def fetch_launches() -> list:
|
||||
"""
|
||||
Fetches real rocket launch data from Launch Library 2.
|
||||
Returns combined upcoming + recent launches, sorted by date.
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
upcoming_resp, recent_resp = await asyncio.gather(
|
||||
client.get(UPCOMING_URL, timeout=15.0),
|
||||
client.get(RECENT_URL, timeout=15.0),
|
||||
)
|
||||
|
||||
upcoming = []
|
||||
if upcoming_resp.status_code == 200:
|
||||
for item in upcoming_resp.json().get("results", []):
|
||||
parsed = _parse_launch(item)
|
||||
if parsed:
|
||||
upcoming.append(parsed)
|
||||
else:
|
||||
print(f"[LAUNCHES] Upcoming API returned {upcoming_resp.status_code}")
|
||||
|
||||
recent = []
|
||||
if recent_resp.status_code == 200:
|
||||
for item in recent_resp.json().get("results", []):
|
||||
parsed = _parse_launch(item)
|
||||
if parsed:
|
||||
recent.append(parsed)
|
||||
else:
|
||||
print(f"[LAUNCHES] Recent API returned {recent_resp.status_code}")
|
||||
|
||||
combined = upcoming + recent
|
||||
|
||||
# Sort by NET date (earliest first), unknown dates go last
|
||||
def sort_key(launch):
|
||||
try:
|
||||
return datetime.fromisoformat(launch["net"].replace("Z", "+00:00"))
|
||||
except Exception:
|
||||
return datetime.max.replace(tzinfo=timezone.utc)
|
||||
|
||||
combined.sort(key=sort_key)
|
||||
|
||||
print(f"[LAUNCHES] {len(upcoming)} upcoming, {len(recent)} recent launches")
|
||||
return combined
|
||||
|
||||
except Exception as e:
|
||||
print(f"[LAUNCHES] Fetch error: {e}")
|
||||
return []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = asyncio.run(fetch_launches())
|
||||
print(f"Total launches: {len(result)}")
|
||||
for launch in result[:5]:
|
||||
print(f" {launch['net'][:16]} {launch['status']:15s} {launch['name']}")
|
||||
Reference in New Issue
Block a user