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:
@@ -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)")
|
||||
|
||||
Reference in New Issue
Block a user