#!/usr/bin/env python3 """Schreibt badge-data.json aus der Releases-API (OmniNX-Pack + NX_Firmware-Tag, Gitea/GitHub-kompatibel).""" from __future__ import annotations import json import os import sys import urllib.error import urllib.request BADGE_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "badge-data.json") def api_get(url: str, token: str) -> bytes: req = urllib.request.Request(url) if token: req.add_header("Authorization", f"Bearer {token}") req.add_header("Accept", "application/json") with urllib.request.urlopen(req, timeout=120) as resp: return resp.read() def fetch_releases(api_base: str, repo: str, token: str) -> list: out: list = [] page = 1 while True: url = f"{api_base}/repos/{repo}/releases?limit=100&page={page}" batch = json.loads(api_get(url, token)) if not batch: break out.extend(batch) if len(batch) < 100: break page += 1 return out def _token_attempt_order(pack_token: str) -> list[str]: """Repos/branches the job token cannot see on Gitea often return 404; public /releases works without auth.""" pat = os.environ.get("FIRMWARE_API_TOKEN", "").strip() order = [pat, "", pack_token] if pat else ["", pack_token] seen: set[str] = set() out: list[str] = [] for t in order: if t in seen: continue seen.add(t) out.append(t) return out def fetch_releases_with_token_fallback( api_base: str, repo: str, pack_token: str ) -> tuple[list | None, urllib.error.HTTPError | None]: last_err: urllib.error.HTTPError | None = None for t in _token_attempt_order(pack_token): try: return fetch_releases(api_base, repo, t), None except urllib.error.HTTPError as e: last_err = e if e.code in (401, 403, 404): continue raise return None, last_err def sum_zip_downloads(releases: list) -> int: total = 0 for rel in releases: for asset in rel.get("assets") or []: name = str(asset.get("name", "")) if name.endswith(".zip"): total += int(asset.get("download_count") or 0) return total def first_non_draft_tag(releases: list) -> str | None: for rel in releases: if rel.get("draft"): continue tag = str(rel.get("tag_name") or "").strip() if tag: return tag return None def load_existing_firmware() -> str | None: if not os.path.isfile(BADGE_PATH): return None try: with open(BADGE_PATH, encoding="utf-8") as f: return json.load(f).get("switch_firmware") except (OSError, json.JSONDecodeError, TypeError): return None def main() -> int: api_base = os.environ.get("GITHUB_API_URL", "").rstrip("/") repo = os.environ.get("GITHUB_REPOSITORY", "") fw_repo = os.environ.get("FIRMWARE_REPOSITORY", "OmniNX/NX_Firmware").strip() token = os.environ.get("GITHUB_TOKEN", "") if not api_base or not repo: print("GITHUB_API_URL und GITHUB_REPOSITORY werden benötigt.", file=sys.stderr) return 1 try: omninx_releases = fetch_releases(api_base, repo, token) except urllib.error.HTTPError as e: print(f"API: HTTP {e.code} — {e.reason}", file=sys.stderr) return 1 if not omninx_releases: print("Keine Releases gefunden.", file=sys.stderr) return 1 omninx_tag = first_non_draft_tag(omninx_releases) if not omninx_tag: print("Kein nicht-Draft-Release.", file=sys.stderr) return 1 fw: str | None = None fw_releases, fw_err = fetch_releases_with_token_fallback(api_base, fw_repo, token) if fw_releases is not None: fw = first_non_draft_tag(fw_releases) elif fw_err is not None: print(f"Firmware-Repo ({fw_repo}): HTTP {fw_err.code} — {fw_err.reason}", file=sys.stderr) if not fw: fw = load_existing_firmware() or "unknown" data = { "switch_firmware": fw, "release_zip_downloads_total": sum_zip_downloads(omninx_releases), "omninx_pack_version": omninx_tag, } with open(BADGE_PATH, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write("\n") print(json.dumps(data, indent=2)) return 0 if __name__ == "__main__": sys.exit(main())