v0.5.59: item counter evidence for fears, catch up Web features to site version.
Ship OpenDota counter-stats reordering for feared items, finalize SITE_VERSION/docs for rankings/streamers/trends/matches/mechanics and draft archetypes, and ignore regenerable Web data caches. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""Fetch Valve Immortal regional leaderboards into data/leaderboards.json.
|
||||
|
||||
Pulls the official division boards (americas / europe / se_asia / china) and
|
||||
keeps Top 100 per region for the Climperor web「排行」page.
|
||||
|
||||
Valve does not expose MMR or account_id on this endpoint — only rank, name,
|
||||
and optional team/country. Divisions use separate MMR scales (no global board).
|
||||
|
||||
Preview only — do not merge into relations/heroes or recommend.
|
||||
|
||||
Usage:
|
||||
python fetch_leaderboards.py
|
||||
python fetch_leaderboards.py --out data/leaderboards.json
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from common import DATA
|
||||
from http_utils import http_json
|
||||
|
||||
OUT = DATA / "leaderboards.json"
|
||||
API = (
|
||||
"https://www.dota2.com/webapi/ILeaderboard/GetDivisionLeaderboard/v0001"
|
||||
"?division={division}&leaderboard=0"
|
||||
)
|
||||
|
||||
# Display order for the web rankings tab (China first for CN audience).
|
||||
REGION_ORDER = ("china", "europe", "americas", "se_asia")
|
||||
REGION_LABELS = {
|
||||
"china": "中国",
|
||||
"europe": "欧洲",
|
||||
"americas": "美洲",
|
||||
"se_asia": "东南亚",
|
||||
}
|
||||
TOP_N = 100
|
||||
|
||||
|
||||
def slim_entry(row: dict) -> dict:
|
||||
out: dict = {
|
||||
"rank": int(row.get("rank") or 0),
|
||||
"name": str(row.get("name") or ""),
|
||||
}
|
||||
team = row.get("team_tag")
|
||||
if isinstance(team, str) and team.strip():
|
||||
out["team_tag"] = team.strip()
|
||||
country = row.get("country")
|
||||
if isinstance(country, str) and country.strip():
|
||||
out["country"] = country.strip().lower()
|
||||
return out
|
||||
|
||||
|
||||
def fetch_division(division: str) -> dict:
|
||||
url = API.format(division=division)
|
||||
print(f"fetching {division} ...", flush=True)
|
||||
raw = http_json(url)
|
||||
if not isinstance(raw, dict):
|
||||
raise SystemExit(f"{division}: unexpected payload type {type(raw).__name__}")
|
||||
lb = raw.get("leaderboard")
|
||||
if not isinstance(lb, list):
|
||||
raise SystemExit(f"{division}: missing leaderboard array")
|
||||
top = [slim_entry(e) for e in lb[:TOP_N] if isinstance(e, dict)]
|
||||
return {
|
||||
"division": division,
|
||||
"label_zh": REGION_LABELS.get(division, division),
|
||||
"time_posted": raw.get("time_posted"),
|
||||
"count": len(lb),
|
||||
"top100": top,
|
||||
}
|
||||
|
||||
|
||||
def build_payload(regions: dict[str, dict]) -> dict:
|
||||
return {
|
||||
"fetched_at": datetime.now(timezone.utc).isoformat(),
|
||||
"source": "valve",
|
||||
"attribution": "https://www.dota2.com/leaderboards",
|
||||
"note": (
|
||||
"Official Immortal division boards; no MMR/account_id in payload; "
|
||||
"region MMR scales are not comparable across divisions"
|
||||
),
|
||||
"default_region": "china",
|
||||
"region_order": list(REGION_ORDER),
|
||||
"regions": regions,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--out", type=Path, default=OUT)
|
||||
args = ap.parse_args()
|
||||
|
||||
regions: dict[str, dict] = {}
|
||||
for div in REGION_ORDER:
|
||||
regions[div] = fetch_division(div)
|
||||
|
||||
payload = build_payload(regions)
|
||||
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.out.write_text(
|
||||
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
parts = [
|
||||
f"{div}={len((regions[div].get('top100') or []))}" for div in REGION_ORDER
|
||||
]
|
||||
print(f"done: {', '.join(parts)} -> {args.out}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user