v0.5.84: matches origin filter, mobile gate, refresh reliability.

Ship Web refresh cache/lock, mobile demand gate, matches 职业/国服 filter, and related site updates through 0.5.84.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
voson
2026-07-29 18:31:55 +08:00
co-authored by Cursor
parent 7681fdb069
commit b01552ee6e
50 changed files with 3406 additions and 487 deletions
+39
View File
@@ -18,6 +18,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import argparse
import json
import mimetypes
import threading
import webbrowser
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse
@@ -50,6 +51,32 @@ from shared.relations import DEFAULT_RELATIONS, load_relations
from mechanic_tags import QUERY_MECHANIC_ORDER, mechanic_query_payload
WEB_DIR = WEB_FRONTEND
MOBILE_DEMAND_PATH = ROOT / "web" / ".refresh" / "mobile_demand.json"
_MOBILE_DEMAND_LOCK = threading.Lock()
def _read_mobile_demand_count() -> int:
try:
if not MOBILE_DEMAND_PATH.is_file():
return 0
data = json.loads(MOBILE_DEMAND_PATH.read_text(encoding="utf-8"))
n = int(data.get("count", 0))
return n if n >= 0 else 0
except (OSError, ValueError, TypeError, json.JSONDecodeError):
return 0
def _inc_mobile_demand_count() -> int:
with _MOBILE_DEMAND_LOCK:
n = _read_mobile_demand_count() + 1
MOBILE_DEMAND_PATH.parent.mkdir(parents=True, exist_ok=True)
MOBILE_DEMAND_PATH.write_text(
json.dumps({"count": n}, ensure_ascii=False) + "\n",
encoding="utf-8",
)
return n
GRID_ORDER_PATH = DATA / "hero_grid_order.json"
HERO_ITEMS_PATH = DATA / "hero_items.json"
HERO_STATS_PATH = DATA / "hero_stats.json"
@@ -73,6 +100,8 @@ def _first_existing(*candidates: Path) -> Path | None:
if p.is_file():
return p
return None
ATTR_COLS = {"str": 6, "agi": 6, "int": 6, "all": 4}
ATTR_LABELS = {"str": "力量", "agi": "敏捷", "int": "智力", "all": "全才"}
ABILITY_ICON_URL = (
@@ -877,6 +906,9 @@ class Handler(BaseHTTPRequestHandler):
# production only); the empty map keeps the UI on data.json is_live.
self._json(200, {"probed_at": None, "ttl": 300, "streamers": {}})
return
if path == "/api/mobile-demand":
self._json(200, {"count": _read_mobile_demand_count()})
return
if path.startswith("/attr/"):
key = path[len("/attr/") :]
if key not in ("str.png", "agi.png", "int.png", "all.png"):
@@ -1057,6 +1089,13 @@ class Handler(BaseHTTPRequestHandler):
ctype = mimetypes.guess_type(str(candidate))[0] or "application/octet-stream"
self._send(200, candidate.read_bytes(), ctype)
def do_POST(self) -> None: # noqa: N802
path = urlparse(self.path).path
if path == "/api/mobile-demand":
self._json(200, {"count": _inc_mobile_demand_count(), "voted": True})
return
self.send_error(404)
def main() -> None:
ap = argparse.ArgumentParser(description="Preview qualitative hero relations (read-only)")