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
+37 -2
View File
@@ -11,7 +11,7 @@ Sources:
Only patches within the past year (default 365 days from today) are kept,
descending. Per-patch detail keeps the raw ability_id / hero_id values; a
shared `lookup` resolves only the referenced ids to {key, name_loc} so the
read-only preview can render names + icons with no runtime network calls.
read-only Climperor web site can render names + icons with no runtime network calls.
Referenced item / ability icons are downloaded into assets/item_icons and
assets/ability_icons so the static export stays self-contained.
@@ -22,6 +22,7 @@ Usage:
python fetch_patches.py --since 2025-07-27
python fetch_patches.py --no-icons
python fetch_patches.py --force # refetch every patch detail
python fetch_patches.py --check # list vs cache; JSON on stdout
"""
from __future__ import annotations
@@ -58,7 +59,7 @@ DEFAULT_WINDOW_DAYS = 365
# Bundled shared badges — never fetched from CDN (many innate keys 404).
SKIP_ABILITY_ICON_KEYS = frozenset({"innate", "talent_tree"})
# Non-hero units that Valve places in patchnotes heroes[] under a pseudo hero_id
# absent from herolist. Resolve to a Chinese name + portrait key so the preview
# absent from herolist. Resolve to a Chinese name + portrait key so the web site
# never shows #1961. Portrait files live in assets/hero_portraits/<key>.png.
UNIT_NAMES = {1961: {"key": "spirit_bear", "name_loc": "熊灵"}}
@@ -372,6 +373,30 @@ def load_existing_details() -> dict[str, dict]:
return out
def check_for_new_patches(*, days: int, since: str | None) -> dict:
"""Compare Valve patch list to local details cache; do not write files.
Returns a JSON-serializable dict with has_new / new_versions / latest / …
"""
if since:
since_dt = datetime.strptime(since, "%Y-%m-%d").replace(tzinfo=timezone.utc)
else:
since_dt = datetime.now(tz=timezone.utc) - timedelta(days=days)
since_ts = int(since_dt.timestamp())
patches = fetch_patch_list(since_ts)
cached = load_existing_details()
new_versions = [p["version"] for p in patches if p["version"] not in cached]
latest = patches[0]["version"] if patches else None
return {
"has_new": bool(new_versions),
"new_versions": new_versions,
"latest": latest,
"window_count": len(patches),
"cached_details": len(cached),
"since": since_dt.date().isoformat(),
}
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--days", type=int, default=DEFAULT_WINDOW_DAYS, help="window in days from today")
@@ -379,8 +404,18 @@ def main() -> None:
ap.add_argument("--delay", type=float, default=0.3, help="seconds between detail/icon requests")
ap.add_argument("--no-icons", action="store_true", help="skip icon downloads")
ap.add_argument("--force", action="store_true", help="refetch every patch detail")
ap.add_argument(
"--check",
action="store_true",
help="only compare patch list to local details; print JSON to stdout",
)
args = ap.parse_args()
if args.check:
result = check_for_new_patches(days=args.days, since=args.since)
print(json.dumps(result, ensure_ascii=False), flush=True)
return
if args.since:
since_dt = datetime.strptime(args.since, "%Y-%m-%d").replace(tzinfo=timezone.utc)
else: