戦績 API 直叩き(ブラウザレス取得)
最終更新: 2026-06-13
戦績データ(battle_list / battle_detail / season 系)を Playwright のページ駆動なしに HTTP で直接取得する仕組みの設計ドキュメント。ログインは引き続きブラウザで行い、取得だけをブラウザレス化する「ハイブリッド方式」。
関連: Browser Automation Data(従来のページ駆動・傍受方式) / Electron Main
1. 背景:なぜ直叩きするか
従来は SPA(xzysteam.shengtiangames.com/gamerecords)をブラウザで開き、SPA が投げる API レスポンスを page.on('response') で傍受していた。試合終了のたびにブラウザを駆動するため重く・不安定だった。
裏 API は token ヘッダ 1 本のみ(Cookie 不要・署名不要)で叩けることを実機検証済みのため、ログイン済みブラウザから抜いたトークンで直接 fetch すれば、取得ホットパスからブラウザを外せる。
2. API 仕様(実機検証 2026-06-12)
- ホスト:
https://xzy.shengtiangames.com/mini-game/xzy/battle-record/<endpoint>- SPA ホスト
xzysteam.shengtiangames.comとは別ドメイン
- SPA ホスト
- 認証: HTTP ヘッダ
token: <値>のみ- Cookie 不要・署名(
signed)不要 - ヘッダ名は厳密に
token(Authorization/Bearerは不可)
- Cookie 不要・署名(
- 失効時:
{ code: 401, msg: "not login", data: "...未获取到token..." }
エンドポイント
| エンドポイント | クエリ | 用途 |
|---|---|---|
battle_list | season_code match_type time_start time_end page role_id | 戦績一覧(ページング) |
battle_detail2v2 | id=<BattleId> | 試合詳細(Winner/Loser Details) |
battle_seasonsummary | role_id season_code | シーズンサマリー(T1/T2 スコア) |
season_show | season_code role_id | ScoreMap / TopRoleList を含む詳細 |
season_list | (なし) | シーズン一覧(Code/StartTime/EndTime) |
レスポンスは全て { code: 0, msg: "success", data: ... } 形式。battle_detail2v2 の data は従来の傍受方式と同一構造のため、既存の変換関数 battleDetailFromResponse がそのまま使える。
3. トークンの在り処と供給
トークンはログイン済みブラウザの localStorage(origin xzysteam.shengtiangames.com)の token キーに入る。role_id は同 localStorage の selected_role_id。
Playwright の storageState.json(origins[].localStorage)にも保存されるため、ブラウザ未起動でもファイルから読める。
- storageState 実パス:
%APPDATA%/streaming-overlay-manager/browser-automation/storageState.json
CredentialResolver(live → file)
electron/services/battleRecordApi/credentialsProvider.ts
トークンは次の優先順で解決する:
- live: ブラウザ起動中なら
BrowserAutomationService.getLiveCredentials()がpage.evaluateで最新の localStorage を読む(storageState の 60s 保存遅延を回避) - file: 未起動なら
readCredentialsFromFile(storageState.json)
BattleRecordApiClient は同期 getter を期待するため、IPC 呼び出しの先頭で refresh() を await してキャッシュし、以降は get() で同期参照する。
4. モジュール構成
electron/services/battleRecordApi/
| ファイル | 役割 |
|---|---|
BattleRecordApiClient.ts | 各エンドポイントを token ヘッダ付きで叩く。fetchImpl 差し替えでテスト可能。401/not login は TokenExpiredError、その他非 0 は BattleRecordApiError |
tokenStore.ts | storageState から token / role_id を抽出(extractCredentials 純関数 + readCredentialsFromFile) |
credentialsProvider.ts | CredentialResolver(live→file 解決+キャッシュ) |
seasonResolver.ts | season_list から現シーズン(Code/StartTime/EndTime)を動的解決(pickCurrentSeason 純関数 + キャッシュ付き SeasonResolver) |
seasonConverter.ts | battle_seasonsummary / season_show の data → DB 保存型の純変換 |
directWorkflows.ts | 取得ワークフロー本体(後述) |
errors.ts | TokenExpiredError / BattleRecordApiError |
types.ts | ApiEnvelope / BattleRecordCredentials / BattleListParams |
DB 保存(saveBattleListItems / saveBattleDetails / saveSeason*)・matchId 解決(findMatchBy*)・battleDetailFromResponse は既存の DB / 変換関数を再利用する(ブラウザ非依存)。
5. ワークフロー(directWorkflows)
| 関数 | 取得対象 | 概要 |
|---|---|---|
directMatchEndWorkflow | battle_list(1p) → battle_detail2v2 | 試合終了直後。最新試合の詳細を保存 |
directFullWorkflow | battle_list(全ページ) → battle_detail2v2 | ページ送りで全件取得。スクロール制御が不要に |
directSeasonOnly | battle_seasonsummary + season_show | シーズン統計のみ |
directBattleListWithSeason | battle_list(1p) + season | 軽量な一覧+シーズン |
season_code / 取得期間の決定
battle_list 等は season_code と期間が必須。SeasonResolver が season_list API を権威として、現在時刻が属するシーズンの Code / StartTime / EndTime を動的に決める(ハードコードしない)。
6. IPC 配線(直叩き主・ブラウザ保険)
electron/ipc/BrowserAutomationHandlers.ts
4 つの workflow IPC(browser-automation-execute-match-end-workflow / -execute-full-workflow / -fetch-season-only / -fetch-battle-list-with-season)は次の方針で動く:
creds = await CredentialResolver.refresh() // live→file
outcome = runDirectWorkflow(!!creds, 直叩き)
├─ login-required (creds 無 / TokenExpiredError)
│ → handleLoginStatusUpdate({loggedIn:false}) // 再ログイン導線 + OS 通知
├─ ok → 成功
└─ fallback → ブラウザ起動中のみ従来のページ駆動 workflow へrunDirectWorkflow は実行結果を login-required / ok / fallback に分類する純粋ヘルパー(単体テスト可能)。
最大の利点: match-end はブラウザ未起動でも storageState のトークンがあれば取得できる。
401 → 再ログイン導線
TokenExpiredError(code:401 / not login)を検知したら handleLoginStatusUpdate({ loggedIn:false }) を呼び、renderer の LoginRequiredDialog 表示+OS 通知でブラウザログインを促す。ログイン後は最新トークンが live/file から取得され、自動的に直叩きが復活する。
7. ビルド時の注意(重要)
electron メインのビルド後処理(scripts/build/build-electron.cjs の「require パス修正」)は、ディレクトリ barrel(index.ts)への import を解決できない。
- ❌
import { ... } from '../services/battleRecordApi'- bundle 後に
require('../services/battleRecordApi.cjs')へ化け、起動時にCannot find moduleでウィンドウが開かず e2e スモークが落ちる(tsc / unit は通るので気付きにくい)
- bundle 後に
- ✅
import { BattleRecordApiClient } from '../services/battleRecordApi/BattleRecordApiClient'- 個別ファイルから import する
barrel index.ts はテスト・型参照用に残すが、electron メインのランタイム import では使わないこと。
8. テスト
| 種別 | 対象 |
|---|---|
| Unit | tokenStore / BattleRecordApiClient(fetch モック)/ seasonResolver / seasonConverter / directWorkflows(DB モック)/ credentialsProvider / runDirectWorkflow |
| Integration | モック HTTP サーバ(directWorkflows.integration.test.ts)で「API → client → workflow」の結線(URL / token ヘッダ / パース)を検証 |
| E2E スモーク | tests/e2e/scenarios/startup.e2e.ts が起動を担保(直叩き配線が dist 起動を壊さないこと) |
UI 出力は不変(内部データ経路の置換)のため、専用の Playwright E2E は追加していない。
9. 今後(Phase F)
直叩きが安定稼働したら、従来のページ駆動 fetch を fallback 専用に縮退、api_responses 生保存の要否を再評価する。