Add PC post-match player pages with opt-in public OSS sync.

Generate /players/{account_id}[/{match_id}] locally after POST_GAME via OpenDota; publish to OSS only when public_share is enabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
voson
2026-07-31 11:01:32 +08:00
co-authored by Cursor
parent d6f7c3f0f5
commit 4a61aeeb26
16 changed files with 1684 additions and 23 deletions
+19
View File
@@ -37,6 +37,7 @@ from capture import append_gsi_payload, grab_frame, is_dota_foreground, raw_dir_
from common import ROOT, load_config, load_template_library
from draft_session import HERO_SELECTION, DraftSession, describe, gsi_slot, loc
from overlay import DraftOverlay
from player_pages import POST_GAME, schedule_post_game
from recognize import recognize_image
from roles import detect_roles
@@ -81,6 +82,8 @@ class Watcher:
except Exception as e: # noqa: BLE001
print(f"[draft] overlay disabled: {e}", flush=True)
self.overlay = None
# Dedup post-game player-page jobs per match (in addition to module lock).
self._player_page_matches: set[str] = set()
def on_payload(self, payload: dict) -> None:
if not self.connected:
@@ -123,7 +126,11 @@ class Watcher:
print(f"[gsi] map keys: {sorted(m.keys())}", flush=True)
print(f"[gsi] player keys: {sorted(p.keys())}", flush=True)
print(f"[gsi] self: {self.self_info} -> top-bar slot {gsi_slot(self.self_info)}", flush=True)
prev = self.last_state
self.last_state = state
# First transition into POST_GAME → build local /players pages.
if state == POST_GAME and prev != POST_GAME:
self._maybe_schedule_player_page(match_id)
if state not in self.trigger_states:
return
@@ -144,6 +151,18 @@ class Watcher:
except OSError as e:
print(f"[gsi] dump failed: {e}", flush=True)
def _maybe_schedule_player_page(self, match_id: str) -> None:
if not match_id or match_id == "no-match":
return
if match_id in self._player_page_matches:
return
self._player_page_matches.add(match_id)
schedule_post_game(
self.cfg,
match_id=match_id,
account_id=self.self_info.get("accountid"),
)
def track(self, match_id: str, state: str, key: str) -> dict | None:
"""Follow the draft from here to the end, recording every reveal."""
if not self.busy.acquire(blocking=False):