"""Eksterne tjenester: geoip, static-assets-version, random-proxy status, Matomo, DiscoPanel.""" import httpx import cache from config import (ASSETS_VERSION_URL, DISCO_API_TOKEN, DISCO_BASE_URL, GEOIP_DB, MATOMO_BASE, MATOMO_SITE, MATOMO_TOKEN, RP_API_KEY, RP_BASE_URL) _geoip_reader = None def geoip_country(ip): global _geoip_reader try: if _geoip_reader is None: import maxminddb _geoip_reader = maxminddb.open_database(GEOIP_DB) rec = _geoip_reader.get(ip) or {} c = rec.get("country") or {} iso = c.get("iso_code") or "" name = (c.get("names") or {}).get("en") or "" flag = "".join(chr(0x1F1E6 + ord(ch) - 65) for ch in iso) if len(iso) == 2 and iso.isalpha() else "" return {"iso": iso, "name": name, "flag": flag} except Exception: return {"iso": "", "name": "", "flag": ""} async def assets_version(): try: async with httpx.AsyncClient(timeout=6) as c: r = await c.get(ASSETS_VERSION_URL) return r.text.strip()[:40] except Exception: return None async def rp_status(): if not RP_API_KEY: return {"error": "ingen RP_API_KEY konfigureret"} try: async with httpx.AsyncClient(timeout=6) as c: r = await c.get(RP_BASE_URL + "/status", headers={"X-WEIRCON-RANDOM-IP": RP_API_KEY}) d = r.json() for p in d.get("proxies", []): p["geo"] = geoip_country(p.get("egress_ip") or "") rot = d.get("rotation") or {} if rot.get("next_egress_ip"): rot["geo"] = geoip_country(rot["next_egress_ip"]) d["inflight_total"] = sum(int(p.get("inflight") or 0) for p in d.get("proxies", [])) return d except Exception as exc: return {"error": str(exc)[:200]} _MC_STATUS = { # DiscoPanel ServerStatus → (dansk label, css-klasse) "SERVER_STATUS_RUNNING": ("Kører", "run"), "SERVER_STATUS_STARTING": ("Starter…", "warn"), "SERVER_STATUS_RESTARTING": ("Genstarter…", "warn"), "SERVER_STATUS_CREATING": ("Oprettes…", "warn"), "SERVER_STATUS_STOPPING": ("Stopper…", "warn"), "SERVER_STATUS_STOPPED": ("Stoppet", "off"), "SERVER_STATUS_ERROR": ("Fejl", "err"), "SERVER_STATUS_UNHEALTHY": ("Usund", "err"), } async def minecraft_servers(): """Serverliste fra DiscoPanel (CT 137, Connect-RPC). None = ikke konfigureret/utilgængelig.""" if not DISCO_API_TOKEN: return None hit = cache.get("mc:servers") if hit is not None: return hit try: async with httpx.AsyncClient(timeout=6) as c: r = await c.post(f"{DISCO_BASE_URL}/discopanel.v1.ServerService/ListServers", json={}, headers={"Authorization": f"Bearer {DISCO_API_TOKEN}", "Content-Type": "application/json"}) r.raise_for_status() data = r.json() servers = [] for s in data.get("servers", []): label, css = _MC_STATUS.get(s.get("status"), ("Ukendt", "off")) servers.append({ "name": s.get("name") or "?", "status": label, "css": css, "online": s.get("status") == "SERVER_STATUS_RUNNING", "players": int(s.get("playersOnline") or 0), "max_players": int(s.get("maxPlayers") or 0), "version": s.get("serverVersion") or s.get("mcVersion") or ""}) servers.sort(key=lambda x: (not x["online"], x["name"].lower())) cache.put("mc:servers", servers, 15) return servers except Exception: return None async def matomo_summary(): if not MATOMO_TOKEN: return None params = {"module": "API", "method": "VisitsSummary.get", "idSite": MATOMO_SITE, "period": "day", "date": "last7", "format": "JSON", "token_auth": MATOMO_TOKEN} try: async with httpx.AsyncClient(timeout=10) as c: r = await c.get(f"{MATOMO_BASE}/index.php", params=params) data = r.json() days = list(data.values()) if isinstance(data, dict) else [] return {"visits_7d": sum(int(d.get("nb_visits", 0) or 0) for d in days if isinstance(d, dict)), "actions_7d": sum(int(d.get("nb_actions", 0) or 0) for d in days if isinstance(d, dict))} except Exception: return None