From 96926091a6ebefd854d944bf97049f4e6dfc50d7 Mon Sep 17 00:00:00 2001 From: jing Date: Tue, 14 Jul 2026 00:15:28 +1000 Subject: [PATCH] wave3: swap military feed to free adsb.lol (drop the paid RapidAPI dependency) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit proxy/mil forwards api.adsb.lol/v2/mil — free, keyless, identical {ac:[...]} shape to ADS-B Exchange, so the layer is unchanged bar its label. 60s server cache for politeness (no quota to guard). proxy/adsbx-mil stays server-side as an unused paid fallback; John can cancel the RapidAPI sub once it lapses. Co-Authored-By: Claude Fable 5 --- js/config.js | 16 +++++++--------- js/layers/military.js | 10 +++++----- serve.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/js/config.js b/js/config.js index 5121bbf..bf21cb8 100644 --- a/js/config.js +++ b/js/config.js @@ -88,16 +88,14 @@ export const CONFIG = { cap: 500, }, - // Military aircraft — ADS-B Exchange /v2/mil/ (REAL unfiltered military, the - // OSINT prize OpenSky hides). Paid RapidAPI feed with OVERAGE billing over - // 10k/month (no hard block), so the same-origin proxy injects the key - // server-side AND single-flight-caches /v2/mil/ for 10 min: the *cache TTL* - // (not this poll) governs the upstream quota → max ~144 calls/day (~4.4k/mo, - // { + ui.addLayer('military', 'Military (adsb.lol)', true, (on) => { enabled = on; bc.show = on && isLive; if (on && isLive) kick(); diff --git a/serve.py b/serve.py index bc241c8..e1bee53 100644 --- a/serve.py +++ b/serve.py @@ -51,6 +51,15 @@ _opensky_token = {"value": None, "exp": 0.0} HISTORY_DB = Path(__file__).resolve().parent / "data" / "history.db" HISTORY_TOLERANCE_SEC = 600 +# ---- Military ADS-B via adsb.lol (FREE, no key, community feed) -------------- +# proxy/mil forwards api.adsb.lol/v2/mil (no CORS header, so it needs a same-origin +# proxy) with a short in-memory cache. Free and unmetered — the cache is just +# politeness/coalescing, not a quota guard. Same {ac:[...]} shape as ADS-B +# Exchange, so js/layers/military.js is source-agnostic. +ADSBLOL_MIL_URL = "https://api.adsb.lol/v2/mil" +ADSBLOL_CACHE_SEC = 60 +_adsblol_cache = {"body": None, "ts": 0.0} + # ---- ADS-B Exchange military feed (paid RapidAPI, ~10k req/month) ------------ # proxy/adsbx-mil injects the RapidAPI key server-side (never in the browser) and # HARD-caches the /v2/mil/ response — the quota guard. The $10 BASIC tier bills @@ -165,6 +174,8 @@ class Handler(SimpleHTTPRequestHandler): if not self.path.startswith("/proxy/"): return super().do_GET() name, _, query = self.path[len("/proxy/"):].partition("?") + if name == "mil": + return self._serve_mil() if name == "adsbx-mil": return self._serve_adsbx_mil() base = UPSTREAMS.get(name) @@ -267,6 +278,27 @@ class Handler(SimpleHTTPRequestHandler): out.write_bytes(png) return self._send_json(200, {"ok": True, "path": f"docs/{name}.png", "bytes": len(png)}) + def _serve_mil(self): + # Free adsb.lol military feed; short in-memory cache (politeness, not quota). + now = time.time() + if _adsblol_cache["body"] is not None and now - _adsblol_cache["ts"] < ADSBLOL_CACHE_SEC: + return self._send(200, "application/json", _adsblol_cache["body"], {"X-Godsigh-Cache": "hit"}) + try: + req = urllib.request.Request(ADSBLOL_MIL_URL, headers={"User-Agent": "godsigh/1.0 (partly.party)"}) + with urllib.request.urlopen(req, timeout=30) as r: + body = r.read() + _adsblol_cache["body"] = body + _adsblol_cache["ts"] = now + return self._send(200, "application/json", body, {"X-Godsigh-Cache": "miss"}) + except urllib.error.HTTPError as e: + if _adsblol_cache["body"] is not None: + return self._send(200, "application/json", _adsblol_cache["body"], {"X-Godsigh-Cache": "stale"}) + return self._send_json(e.code, {"error": f"adsb.lol upstream {e.code}"}) + except Exception as e: + if _adsblol_cache["body"] is not None: + return self._send(200, "application/json", _adsblol_cache["body"], {"X-Godsigh-Cache": "stale"}) + return self._send_json(502, {"error": str(e)}) + def _serve_adsbx_mil(self): host, key = load_adsbx_creds() if not host or not key: