wave3: swap military feed to free adsb.lol (drop the paid RapidAPI dependency)
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 <noreply@anthropic.com>
This commit is contained in:
parent
bb09bcfae7
commit
96926091a6
16
js/config.js
16
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,
|
||||
// <half the cap). The browser polls faster only for display freshness (cheap
|
||||
// cache hits).
|
||||
// Military aircraft — REAL unfiltered military traffic (the OSINT prize OpenSky
|
||||
// hides: RCH/Reach airlift, tankers, the Area 51 "Janet" shuttle). Now on the
|
||||
// FREE, keyless adsb.lol /v2/mil feed via proxy/mil (same {ac:[...]} shape as
|
||||
// the old paid ADS-B Exchange feed, so the layer code is source-agnostic).
|
||||
// proxy/adsbx-mil still exists server-side as a paid fallback but is unused.
|
||||
adsbx: {
|
||||
proxyMil: 'proxy/adsbx-mil', // RELATIVE — works at "/" and "/godsigh/"
|
||||
pollMs: 300_000, // 5 min display refresh (server 10-min cache is the quota guard)
|
||||
proxyMil: 'proxy/mil', // RELATIVE — works at "/" and "/godsigh/"; free adsb.lol
|
||||
pollMs: 60_000, // 1 min — free feed, no quota (server 60s cache coalesces)
|
||||
},
|
||||
|
||||
// Optional live AIS. Leave blank to use the demo fleet only (roadmap feature).
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
// Military aircraft (REAL) — ADS-B Exchange /v2/mil/ feed via the paid RapidAPI
|
||||
// proxy. This is the unfiltered military traffic OpenSky/FR24 hide (C-17 Reach
|
||||
// flights, tankers, the Area 51 "Janet" shuttle, etc.) — the OSINT prize.
|
||||
// Military aircraft (REAL) — the free, keyless adsb.lol /v2/mil feed via
|
||||
// proxy/mil. This is the unfiltered military traffic OpenSky/FR24 hide (C-17
|
||||
// Reach flights, tankers, the Area 51 "Janet" shuttle, etc.) — the OSINT prize.
|
||||
//
|
||||
// BillboardCollection primitive like the civilian aircraft layer, but a distinct
|
||||
// red so it never blends in. Live-only (no history feed for this), polled every
|
||||
// 5 min to match the server-side cache and respect the ~10k/month quota.
|
||||
// minute (the feed is free/unmetered; a 60s server cache coalesces callers).
|
||||
|
||||
export default function create(ctx) {
|
||||
const { viewer, CONFIG, lib, ui, Cesium } = ctx;
|
||||
@ -19,7 +19,7 @@ export default function create(ctx) {
|
||||
let timer = null;
|
||||
let delay = A.pollMs;
|
||||
|
||||
ui.addLayer('military', 'Military (ADS-B Exchange)', true, (on) => {
|
||||
ui.addLayer('military', 'Military (adsb.lol)', true, (on) => {
|
||||
enabled = on;
|
||||
bc.show = on && isLive;
|
||||
if (on && isLive) kick();
|
||||
|
||||
32
serve.py
32
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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user