
    i$9                       d dl mZ d dlZd dlZd dlZd dlmZmZ d dlmZ d dl	m
Z
 d dlmZ d dlmZ  ed      Zd	Z G d
 d      ZddZdddZddZddZddZddddddddddd
	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZy)    )annotationsN)datetimetimezone)Path)Any)ZoneInfo)webzAsia/Shanghaiu%  <!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
  <title>LoL Signal</title>
  <style>
    :root {
      color-scheme: dark;
      --bg: #0f1419;
      --panel: #171d24;
      --line: #28313b;
      --text: #f2f5f8;
      --muted: #98a6b3;
      --green: #2fb66d;
      --red: #e24b50;
      --blue: #4d83ff;
      --amber: #d99b2b;
    }
    * { box-sizing: border-box; }
    body {
      margin: 0;
      background: var(--bg);
      color: var(--text);
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      font-size: 14px;
    }
    main {
      max-width: 760px;
      margin: 0 auto;
      padding: 12px;
      padding-bottom: calc(18px + env(safe-area-inset-bottom));
    }
    .top {
      display: grid;
      grid-template-columns: minmax(0, 1fr) 108px;
      gap: 8px;
      align-items: end;
      margin-bottom: 10px;
    }
    label {
      display: block;
      color: var(--muted);
      font-size: 12px;
      margin-bottom: 4px;
    }
    input, select {
      width: 100%;
      height: 38px;
      border: 1px solid var(--line);
      border-radius: 6px;
      background: #10161d;
      color: var(--text);
      padding: 0 10px;
      font-size: 14px;
      outline: none;
    }
    .panel {
      border: 1px solid var(--line);
      border-radius: 8px;
      background: var(--panel);
      padding: 10px;
      margin-bottom: 10px;
    }
    .teams {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 8px;
    }
    .team-title {
      font-weight: 700;
      margin: 0 0 8px;
      font-size: 15px;
    }
    .team-token {
      margin: -4px 0 8px;
      color: var(--muted);
      font-size: 10px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    }
    .grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 8px;
    }
    button {
      width: 100%;
      min-height: 58px;
      border: 0;
      border-radius: 8px;
      color: white;
      font-weight: 800;
      font-size: 15px;
      touch-action: manipulation;
      user-select: none;
    }
    button:active { transform: translateY(1px); filter: brightness(1.08); }
    .a button { background: var(--blue); }
    .b button { background: var(--red); }
    .neutral { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
    .neutral button { min-height: 48px; background: #3c4652; }
    .neutral button.warn { background: var(--amber); }
    .status {
      min-height: 72px;
      white-space: pre-wrap;
      line-height: 1.45;
      color: var(--muted);
      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
      font-size: 12px;
    }
    .ok { color: #68d391; }
    .bad { color: #ff7a7a; }
    @media (max-width: 520px) {
      .top { grid-template-columns: 1fr; }
      main { padding: 8px; }
      button { min-height: 64px; font-size: 14px; }
      .grid { grid-template-columns: 1fr; }
    }
  </style>
</head>
<body>
  <main>
    <div class="top">
      <div>
        <label>Event</label>
        <input id="eventSlug" placeholder="lol-wb-we-2026-05-07" readonly />
      </div>
      <div>
        <label>Market</label>
        <select id="market"></select>
      </div>
    </div>
    <input id="token" type="hidden" />

    <div class="teams">
      <section class="panel a">
        <p class="team-title" id="titleA">A</p>
        <p class="team-token" id="tokenA"></p>
        <div class="grid">
          <button data-team="A" data-signal="first_blood">一血</button>
          <button data-team="A" data-signal="dragon">小龙</button>
          <button data-team="A" data-signal="baron">大龙</button>
          <button data-team="A" data-signal="teamfight_win">团战胜利</button>
          <button data-team="A" data-signal="tower">推塔</button>
          <button data-team="A" data-signal="manual">手动利好</button>
        </div>
      </section>
      <section class="panel b">
        <p class="team-title" id="titleB">B</p>
        <p class="team-token" id="tokenB"></p>
        <div class="grid">
          <button data-team="B" data-signal="first_blood">一血</button>
          <button data-team="B" data-signal="dragon">小龙</button>
          <button data-team="B" data-signal="baron">大龙</button>
          <button data-team="B" data-signal="teamfight_win">团战胜利</button>
          <button data-team="B" data-signal="tower">推塔</button>
          <button data-team="B" data-signal="manual">手动利好</button>
        </div>
      </section>
    </div>

    <section class="panel neutral">
      <button data-team="" data-signal="pause">暂停</button>
      <button data-team="" data-signal="resume">恢复</button>
      <button class="warn" data-team="" data-signal="cancel">取消/撤单</button>
    </section>

    <section class="panel">
      <div id="status" class="status">ready</div>
    </section>
  </main>

  <script>
    const CONFIG = __CONFIG__;
    const els = {
      eventSlug: document.getElementById("eventSlug"),
      market: document.getElementById("market"),
      teamA: document.getElementById("teamA"),
      teamB: document.getElementById("teamB"),
      token: document.getElementById("token"),
      titleA: document.getElementById("titleA"),
      titleB: document.getElementById("titleB"),
      tokenA: document.getElementById("tokenA"),
      tokenB: document.getElementById("tokenB"),
      status: document.getElementById("status"),
    };

    function load() {
      const marketKeys = Object.keys(CONFIG.markets || {});
      els.market.innerHTML = marketKeys.map((key) => `<option value="${key}">${key}</option>`).join("");
      if (CONFIG.event_slug) els.eventSlug.value = CONFIG.event_slug;
      if (CONFIG.default_market && marketKeys.includes(CONFIG.default_market)) els.market.value = CONFIG.default_market;
      else if (marketKeys.length) els.market.value = marketKeys[0];
      if (CONFIG.token) els.token.value = CONFIG.token;
      for (const key of ["market"]) {
        const value = localStorage.getItem("lol_signal_" + key);
        if (value) els[key].value = value;
      }
      updateTitles();
    }

    function save() {
      for (const key of ["market"]) {
        localStorage.setItem("lol_signal_" + key, els[key].value);
      }
    }

    function selectedMarketConfig() {
      return (CONFIG.markets && CONFIG.markets[els.market.value]) || CONFIG.markets.moneyline || {};
    }

    function shortToken(token) {
      if (!token) return "-";
      return token.length > 14 ? token.slice(0, 6) + "..." + token.slice(-6) : token;
    }

    function updateTitles() {
      const cfg = selectedMarketConfig();
      const a = (cfg.teams && cfg.teams[0]) || {};
      const b = (cfg.teams && cfg.teams[1]) || {};
      els.titleA.textContent = a.name || "A";
      els.titleB.textContent = b.name || "B";
      els.tokenA.textContent = shortToken(a.token_id || "");
      els.tokenB.textContent = shortToken(b.token_id || "");
    }

    function nowIso() {
      return new Date().toISOString();
    }

    async function sendSignal(teamKey, signal) {
      save();
      const clickAt = performance.now();
      const cfg = selectedMarketConfig();
      const a = (cfg.teams && cfg.teams[0]) || {};
      const b = (cfg.teams && cfg.teams[1]) || {};
      const teamName = teamKey === "A" ? a.name : teamKey === "B" ? b.name : "";
      const teamToken = teamKey === "A" ? a.token_id : teamKey === "B" ? b.token_id : "";
      const body = {
        event_slug: els.eventSlug.value.trim(),
        market: els.market.value,
        team_key: teamKey,
        team: teamName,
        team_token_id: teamToken || "",
        signal,
        client_ts: nowIso(),
        nonce: crypto.randomUUID ? crypto.randomUUID() : String(Date.now()) + Math.random(),
      };
      if (!body.event_slug) {
        els.status.innerHTML = '<span class="bad">missing event slug</span>';
        return;
      }
      els.status.textContent = "sending " + signal + " " + (teamName || teamKey);
      try {
        const resp = await fetch("/api/signal", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-Signal-Token": els.token.value.trim(),
          },
          body: JSON.stringify(body),
        });
        const data = await resp.json();
        const rtt = performance.now() - clickAt;
        if (!resp.ok || !data.ok) {
          throw new Error(data.error || resp.statusText);
        }
        els.status.innerHTML = '<span class="ok">sent</span>\n'
          + "signal: " + signal + "\n"
          + "team: " + (teamName || "-") + "\n"
          + "server: " + data.received_at_wall + "\n"
          + "rtt_ms: " + Math.round(rtt) + "\n"
          + "seq: " + data.sequence;
      } catch (err) {
        els.status.innerHTML = '<span class="bad">failed</span>\n' + String(err.message || err);
      }
    }

    document.querySelectorAll("button[data-signal]").forEach((button) => {
      button.addEventListener("click", () => sendSignal(button.dataset.team, button.dataset.signal));
    });
    ["market"].forEach((key) => {
      els[key].addEventListener("change", save);
      els[key].addEventListener("input", () => { save(); updateTitles(); });
    });
    load();
  </script>
</body>
</html>
c                      e Zd ZddZddZy)SignalStorec                     || _         d| _        y )Nr   )output_rootsequence)selfr   s     'analysis/lpl_orderbook/signal_server.py__init__zSignalStore.__init__9  s    &    c                z   dj                  d |D              }|sd}| j                  |z  dz  }|j                  j                  dd       |j	                  dd	      5 }|j                  t        j                  |d
d             |j                  d       |j                          d d d        y # 1 sw Y   y xY w)N c              3  J   K   | ]  }|j                         s|d v s|  yw)>   _-N)isalnum).0chs     r   	<genexpr>z$SignalStore.write.<locals>.<genexpr>>  s     X2rzz|rZGWBXs   ##unknownzsignals.jsonlT)parentsexist_okazutf-8)encodingF)ensure_ascii	sort_keys
)	joinr   parentmkdiropenwritejsondumpsflush)r   
event_slugrow	safe_slugpathfs         r   r(   zSignalStore.write=  s    GGXXX	!I)+o=$6YYsWY- 	GGDJJs$GHGGDMGGI	 	 	s   A	B11B:N)r   r   returnNone)r,   strr-   zdict[str, Any]r1   r2   )__name__
__module____qualname__r   r(    r   r   r   r   8  s    	r   r   c                 H    t        j                  t        j                        S )N)r   nowr   utcr7   r   r   _utc_nowr;   I  s    <<%%r   c                6    t        j                  d| d|      S )NF)okerrorstatus)r	   json_response)messager@   s     r   _json_errorrC   M  s    EG<VLLr   c                   K   | j                   d   }t        j                  dt        j                  |dd            }t        j                  |d      S w)Npage_config
__CONFIG__F),:)r!   
separatorsz	text/html)textcontent_type)appSIGNAL_PAGEreplacer)   r*   r	   Response)_requestconfigpages      r   indexrS   Q  sJ     \\-(F

6*ED <<T<<s   AAc                  K   t        j                  dt               j                         t	        | j
                  d   j                        | j
                  d   j                         D ci c]  \  }}|dk7  r|| c}}d      S c c}}w w)NTstorerE   token)r=   server_timer   rE   )r	   rA   r;   	isoformatr3   rL   r   items)requestkeyvalues      r   healthr]   Z  s     #://1w{{73??@ #*++m"<"B"B"DC'> U
			
 
s   A)B+B=Bc                  K   | j                   d   }|r.| j                  j                  dd      }||k7  rt        dd      S 	 | j	                          d {   }t        |t              st        d      S t        |j                  d	      xs d      j                         }t        |j                  d
      xs d      j                         }|st        d      S |st        d      S | j                   d   }|xj                  dz  c_
        t               }| j                  j                  d      xs | j                  xs d}|j                  |t        |j                  d      xs d      t        |j                  d      xs d      t        |j                  d      xs d      t        |j                  d      xs d      |t        |j                  d      xs d      |j                         |j                  t              j                         t!        j"                         t        |j                  d      xs t%        j&                               || j                  j                  dd      |d}	|j)                  ||	       t+        j,                  d|j                  |	d   |	d   d      S 7 Z# t        j
                  $ r t        d      cY S w xY ww)NrV   zX-Signal-Tokenr   unauthorizedi  r?   zinvalid jsonzpayload must be objectr,   signalzevent_slug is requiredzsignal is requiredrU      zX-Forwarded-Formarketteam_keyteamteam_token_id	client_tsnoncez
User-Agent)r   r,   rb   rc   rd   re   r`   rf   received_at_wallreceived_at_wall_cnreceived_at_monotonic_nsrg   source
user_agentraw_jsonTrh   ri   )r=   r   rh   ri   )rL   headersgetrC   r)   JSONDecodeError
isinstancedictr3   stripr   r;   remoterX   
astimezoneSHANGHAI_TZtimemonotonic_nsuuiduuid4r(   r	   rA   )
rZ   rV   providedpayloadr,   signal_namerU   received_atrk   r-   s
             r   r`   r`   i  sd    KK E??&&'7<u~c::+& gt$344W[[.4"5;;=Jgkk(+1r288:K344/00 W-E	NNaN*K__  !23Kw~~KFNN gkk(+1r2J/526GKK'-2.W[[9?R@[17R8'113*55kBLLN$($5$5$7W[[)9TZZ\:oo)),;C" 
KK
C  #$6 7#&'<#=		
 G ' +>**+s=   A KJ3 J0J3 IK0J3 3KKKKr   ABgame1T)
rV   r,   team_ateam_bteam_a_token_idteam_b_token_idmarketsdefault_marketlock_configexpose_tokenc                |   t        j                  d      }t        |       |d<   ||d<   ||||||xs d|||d||dgdi||
r|nd|	t        |xr |
      d	
|d
<   |j                  j                  dt               |j                  j                  dt               |j                  j                  dt               |S )Ni   )client_max_sizerU   rV   	moneyline)nametoken_id)slugteamsr   )
r,   r   r   r   r   r   r   rV   r   
hide_tokenrE   /z/healthz/api/signal)
r	   Applicationr   boolrouteradd_getrS   r]   add_postr`   )r   rV   r,   r   r   r   r   r   r   r   r   rL   s               r   make_appr     s     //+
6C{+CLCL ** 	
"#A#A
 )&B"51\2'C* JJsE"JJy&)JJv.Jr   )r1   r   )i  )rB   r3   r@   intr1   web.Response)rP   web.Requestr1   r   )rZ   r   r1   r   )r   r   rV   r3   r,   r3   r   r3   r   r3   r   r3   r   r3   r   zdict[str, Any] | Noner   r3   r   r   r   r   r1   zweb.Application)
__future__r   r)   rw   ry   r   r   pathlibr   typingr   zoneinfor   aiohttpr	   rv   rM   r   r;   rC   rS   r]   r`   r   r7   r   r   <module>r      s    "    '     'dN	 "&M=1n %)!)) ) 	)
 ) ) ) ) #) ) ) ) )r   