Files
climperor/fetch_matchups.py
T
voson a91789b72f v0.2.0: relations preview, item shop, abilities, overlay recommend, GSI enhancements
- Add relations/item/abilities preview (serve_relations.py + web/relations/)
- Add fetch scripts: hero_items, item_shop, items_meta, hero_abilities,
  ability_videos, patches, stratz, matchups, portraits
- Add overlay.py (role tags + Top-3 cyan marks), recommend.py
- Add http_utils.py, loc_format.py, hero_tags.py, item_fears.py
- GSI: full payload JSONL dump, foreground window detection
- Drop real template library; CDN-only matching
- Update docs: CHANGELOG 0.2.0, DESIGN config table, AGENTS module table
- .gitignore: exclude large regenerable assets (icons/portraits/videos)
2026-07-27 11:56:51 +08:00

83 lines
2.7 KiB
Python

"""Fetch OpenDota hero matchups into data/matchups.json (for audit_relations).
Usage:
python fetch_matchups.py
python fetch_matchups.py --delay 1.0
"""
from __future__ import annotations
import argparse
import json
import time
import urllib.error
from datetime import datetime, timezone
from pathlib import Path
from common import DATA
from grid import hero_table
from http_utils import http_json
API = "https://api.opendota.com/api/heroes/{hero_id}/matchups"
OUT = DATA / "matchups.json"
def fetch_one(hero_id: int, timeout: float = 30.0) -> list[dict]:
return http_json(API.format(hero_id=hero_id), timeout=int(timeout))
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--delay", type=float, default=1.0, help="seconds between requests")
ap.add_argument("--out", type=Path, default=OUT)
args = ap.parse_args()
table = hero_table()
ids = sorted({int(h["id"]) for h in table})
by_hero: dict[str, dict] = {}
if args.out.is_file():
try:
prev = json.loads(args.out.read_text(encoding="utf-8"))
by_hero = dict(prev.get("by_hero") or {})
print(f"resuming with {len(by_hero)} heroes already cached", flush=True)
except (OSError, json.JSONDecodeError):
pass
pending = [i for i in ids if str(i) not in by_hero]
print(f"fetching {len(pending)} / {len(ids)} heroes -> {args.out}", flush=True)
for n, hid in enumerate(pending, start=1):
try:
rows = fetch_one(hid)
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as e:
print(f" [{n}/{len(pending)}] hero {hid} failed: {e}", flush=True)
time.sleep(args.delay * 2)
continue
cell: dict[str, dict] = {}
for r in rows:
opp = r.get("hero_id")
games = int(r.get("games_played") or 0)
wins = int(r.get("wins") or 0)
if opp is None or games <= 0:
continue
cell[str(int(opp))] = {"games": games, "wins": wins}
by_hero[str(hid)] = cell
print(f" [{n}/{len(pending)}] hero {hid}: {len(cell)} matchups", flush=True)
args.out.parent.mkdir(parents=True, exist_ok=True)
payload = {
"fetched_at": datetime.now(timezone.utc).isoformat(),
"source": "opendota",
"attribution": "https://opendota.com",
"by_hero": by_hero,
}
args.out.write_text(
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
time.sleep(args.delay)
print(f"done: {len(by_hero)} heroes in {args.out}", flush=True)
if __name__ == "__main__":
main()