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:
voson
2026-07-29 02:11:49 +08:00
co-authored by Cursor
parent 37769580f5
commit 3ec8007077
72 changed files with 20669 additions and 2105 deletions
+56 -6
View File
@@ -14,20 +14,70 @@ from pathlib import Path
UA = "climperor"
DEFAULT_TIMEOUT = 60
# OpenDota and similar APIs occasionally 429; back off before failing CI/refresh.
DEFAULT_RETRIES = 4
DEFAULT_RETRY_BACKOFF = 5.0
HEROES_URL = "https://www.dota2.com/datafeed/herolist?language={lang}"
ITEMLIST_URL = "https://www.dota2.com/datafeed/itemlist?language={lang}"
def http_json(url: str, *, timeout: int = DEFAULT_TIMEOUT) -> dict | list:
req = urllib.request.Request(url, headers={"User-Agent": UA})
with urllib.request.urlopen(req, timeout=timeout) as resp:
def _http_open(url: str, *, timeout: int, retries: int, backoff: float):
"""urlopen with retries on 429 / 5xx / transient network errors."""
last_err: BaseException | None = None
for attempt in range(retries + 1):
req = urllib.request.Request(url, headers={"User-Agent": UA})
try:
return urllib.request.urlopen(req, timeout=timeout)
except urllib.error.HTTPError as e:
last_err = e
if e.code not in (429, 500, 502, 503, 504) or attempt >= retries:
raise
sleep_s = backoff * (2**attempt)
retry_after = e.headers.get("Retry-After") if e.headers else None
if retry_after:
try:
sleep_s = max(sleep_s, float(retry_after))
except ValueError:
pass
print(
f"HTTP {e.code} {url} — retry {attempt + 1}/{retries} in {sleep_s:.0f}s",
flush=True,
)
time.sleep(sleep_s)
except (urllib.error.URLError, TimeoutError, OSError) as e:
last_err = e
if attempt >= retries:
raise
sleep_s = backoff * (2**attempt)
print(
f"HTTP error {e} {url} — retry {attempt + 1}/{retries} in {sleep_s:.0f}s",
flush=True,
)
time.sleep(sleep_s)
assert last_err is not None
raise last_err
def http_json(
url: str,
*,
timeout: int = DEFAULT_TIMEOUT,
retries: int = DEFAULT_RETRIES,
backoff: float = DEFAULT_RETRY_BACKOFF,
) -> dict | list:
with _http_open(url, timeout=timeout, retries=retries, backoff=backoff) as resp:
return json.loads(resp.read().decode())
def http_bytes(url: str, *, timeout: int = DEFAULT_TIMEOUT) -> bytes:
req = urllib.request.Request(url, headers={"User-Agent": UA})
with urllib.request.urlopen(req, timeout=timeout) as resp:
def http_bytes(
url: str,
*,
timeout: int = DEFAULT_TIMEOUT,
retries: int = DEFAULT_RETRIES,
backoff: float = DEFAULT_RETRY_BACKOFF,
) -> bytes:
with _http_open(url, timeout=timeout, retries=retries, backoff=backoff) as resp:
return resp.read()