Files
p42.f-w/backend/app/tracking/seventeen.py
T
guyg1474andClaude Opus 5.5 dd58b3d1f8 P42 Dashboard: erster Stand
FastAPI-Backend und React-Frontend mit Konten, Sendungsverfolgung,
Projekt-Artikeln, Vorrat & Einkauf mit Rezeptabgleich, Reise-Weltkarte,
Kanban-Projekten mit Priorität, Server-Monitoring und den Designs
Matrix und Blackwall. Betrieb per Docker hinter nginx (siehe DEPLOY.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:08:11 +02:00

81 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""17TRACK Tracking API v2.2 – https://api.17track.net
Nummern müssen erst registriert werden, danach lassen sich die Daten abrufen.
Bei neu registrierten Nummern dauert es oft ein paar Sekunden, bis Daten da sind.
"""
import httpx
API = "https://api.17track.net/track/v2.2"
ALREADY_REGISTERED = -18019901
STATUS = {
"NotFound": "notfound",
"InfoReceived": "inforeceived",
"InTransit": "intransit",
"Expired": "expired",
"AvailableForPickup": "pickup",
"OutForDelivery": "outfordelivery",
"DeliveryFailure": "failed",
"Delivered": "delivered",
"Exception": "exception",
}
async def _call(client: httpx.AsyncClient, endpoint: str, key: str, body: list) -> dict:
res = await client.post(f"{API}/{endpoint}", headers={"17token": key}, json=body)
res.raise_for_status()
data = res.json()
if data.get("code") != 0:
raise RuntimeError(f"API-Code {data.get('code')}")
return data["data"]
def _location(event: dict) -> str | None:
if event.get("location"):
return event["location"]
addr = event.get("address") or {}
return ", ".join(filter(None, [addr.get("city"), addr.get("country")])) or None
async def track_17(number: str, key: str) -> dict:
async with httpx.AsyncClient(timeout=15) as client:
data = await _call(client, "gettrackinfo", key, [{"number": number}])
if not data.get("accepted"):
# Noch nicht registriert -> registrieren (17TRACK erkennt den Paketdienst selbst)
reg = await _call(client, "register", key, [{"number": number}])
rejected = (reg.get("rejected") or [None])[0]
if rejected and (rejected.get("error") or {}).get("code") != ALREADY_REGISTERED:
raise RuntimeError((rejected.get("error") or {}).get("message") or "Registrierung abgelehnt")
data = await _call(client, "gettrackinfo", key, [{"number": number}])
item = (data.get("accepted") or [None])[0]
if not item or not item.get("track_info"):
return {"source": "17TRACK", "status": "pending"}
info = item["track_info"]
provider = ((info.get("tracking") or {}).get("providers") or [{}])[0]
events = [
{
"time": e.get("time_iso") or e.get("time_utc"),
"description": e.get("description"),
"location": _location(e),
}
for e in provider.get("events") or []
]
latest = info.get("latest_status") or {}
eta = (info.get("time_metrics") or {}).get("estimated_delivery_date") or {}
shipping = info.get("shipping_info") or {}
return {
"source": "17TRACK",
"carrier": (provider.get("provider") or {}).get("name"),
"status": STATUS.get(latest.get("status"), "unknown"),
"statusDetail": latest.get("sub_status"),
"eta": {"from": eta.get("from"), "to": eta.get("to")} if eta.get("from") or eta.get("to") else None,
"deliveredAt": events[0]["time"] if latest.get("status") == "Delivered" and events else None,
"origin": (shipping.get("shipper_address") or {}).get("country"),
"destination": (shipping.get("recipient_address") or {}).get("country"),
"events": events,
}