地圖官方 WebSocket 客戶端實現
概述
在保留油猴指令碼轉發相容模式的同時,專案內整合終末地官方地圖的 wss://ws.skland.com/ws/v1/game/endfield/map WebSocket 客戶端。
每個賬號(玩家角色)需要獨立的 hg/check credential 才能建立連線。
憑證可填寫在任務直接輸入(content 欄位)或儲存在賬號配置頁(map_contents 中),專案自動執行 OAuth 換取流程。
憑證輸入方式
方式一:任務直接輸入
ItemNavigatorTask.default_config['content'] —— 直接填入 hg/check 介面返回的 data.content 字串。
方式二:賬號配置頁(推薦)
AccountConfigTab 中的"地圖同步 content"區域,每個賬號儲存一份 data.content。
資料持久化在 configs/account_scoped_overrides.json 的 map_contents 欄位中。
憑證解析優先順序
- 任務
content非空 → 直接使用 - 任務
地图账号非空 → 從map_contents讀取該賬號的 content - 任務當前登入賬號 context → 從
map_contents讀取;觸發任務自身沒有 context 時,還會讀取 executor 當前執行任務的current_account_id/current_user
不帶憑證時的回退
content 和 地图账号 均為空時,自動啟動舊的本地 WS 服務端模式(監聽 ws://127.0.0.1:3001),相容油猴指令碼或其他外部來源。
OAuth → WebSocket 登入全鏈路
flowchart TD
A[hg/check data.content] --> B[POST Hypergryph OAuth grant]
B --> C[取得 oauth code]
C --> D[POST zonai.skland.com/web/v1/user/auth/generate_cred_by_code]
D --> E[取得 cred / sign token / userId]
E --> F[GET user 和 player binding]
F --> G[解析默认终末地角色]
G --> H[GET websocket token]
H --> I[连接官方地图 WS endpoint]
I --> J[发送 type=1 token 鉴权]
HTTP 簽名演算法
headers = {
"platform": "3",
"vName": "1.0.0",
"timestamp": str(timestamp),
"dId": device_id or "",
}
sign_payload = path + (query if GET else body) + timestamp
compact_headers = {"platform":"3","timestamp":"...","dId":"...","vName":"1.0.0"}
sign_payload += json.dumps(compact_headers, separators=(",", ":"))
digest = hmac.new(sign_token.encode(), sign_payload.encode(), sha256).hexdigest()
sign = md5(digest.encode()).hexdigest()
headers["sign"] = sign
timestamp 處理
clientTime = 換取 cred 時的本地時間戳;serverTime = 換取 cred 時的伺服器響應 timestamp。
之後每次簽名:adjusted = serverTime + (now - clientTime),確保時間戳隨流逝時間同步推進。
WebSocket 協議
sequenceDiagram
participant C as ok-ef WS Client
participant S as skland WS
C->>S: type=1 token 鉴权
S-->>C: type=2 auth 成功
loop 每 10 秒
C->>S: type=3 心跳
end
loop 鉴权后每 5 秒
C->>S: type=1011 roleId/serverId 初始化/刷新
S-->>C: type=1012 pos/mapId/levelId
end
S-->>C: type=6 token 过期
C->>S: type=1 新 token 鉴权
| type | 方向 | 說明 |
|---|---|---|
| 1 | C→S | token 鑑權:{token: wss_token} |
| 2 | S→C | auth 成功確認 |
| 3 | C→S | 心跳(每 10s) |
| 6 | S→C | token 過期(code=10002),需重新獲取 ws token 後發 type=1 |
| 1011 | C→S | 初始化/重新整理:{roleId, serverId}(鑑權後每 5s 傳送一次) |
| 1012 | S→C | 位置資料:{data: {pos: {x,y,z}, mapId, levelId}} |
客戶端收到的位置資料通過 _push_ws_payload() 放入統一佇列,與本地 WS 服務端模式共用同一套消費邏輯。
角色解析規則
- 先請求
/web/v1/user;再請求/api/v1/game/player/binding。若後者失敗且 cred 響應帶userId,則以uid=userId重試。 - 優先讀取
data.gameMap.endfield;缺失時在data.list中查詢appCode == "endfield"。 bindingList優先選擇isDefault項,否則取第一項。- 角色優先取該項的
defaultRole,否則取roles[0];最終必須同時有roleId與serverId。
多賬號架構
flowchart TD
A[AccountConfigTab 保存地图同步 content] --> B[account_scope_store.set_account_map_content]
B --> C[configs/account_scoped_overrides.json]
C --> D[map_contents account_id -> content]
E[ItemNavigatorTask 地图账号配置] --> F[get_account_map_content]
G[当前任务账号上下文] --> F
D --> F
F --> H[官方地图 WS 凭证解析]
資料流
flowchart TD
A[account_registry] --> B[account_id]
C[accounts] --> B
D[map_contents] --> B
B --> E[读取账号任务覆盖]
B --> F[读取地图同步 content]
configs/account_scoped_overrides.json
├── map_contents ← 账号 → hg/check content 映射
│ ├── "acc_xxx": "data.content string"
│ └── ...
├── accounts ← 任务级覆盖(已有)
└── account_registry ← 账号 ID 注册表
相關 API (account_scope_store.py)
get_account_map_content(account, account_name="")→ strset_account_map_content(account, content)→ None- 內部
_resolve_account_id_for_read/write()處理 ID/使用者名稱解析
UI (AccountConfigTab.py)
- 重新構建賬戶下拉時,從
map_contents鍵集合中也拉入賬號列表 - 地圖 content 編輯區:單行
LineEdit,隨當前賬號配置統一儲存
安全退出機制
1. 遊戲視窗退出檢測
_is_game_window_alive() 檢查 win32gui.IsWindow(hwnd) && win32gui.IsWindowVisible(hwnd)。
- WS 客戶端主迴圈每輪收包前檢查,視窗不存在則
return退出協程,不會自動重連。 ItemNavigatorTask.run()第一行也檢查,失效時呼叫_cleanup_navigator_runtime()清理所有 WS 資源和箭頭。
2. 消費者空閒超時
導航任務每次通過 _recv_ws_position_payload() 或 _recv_ws_position_payload_or_cached() 讀取位置時,更新 _map_ws_last_consume_at = time.time()。
WS 客戶端執行緒檢查 _map_ws_should_stop_for_idle_consumer(),若超過 _map_ws_consumer_idle_timeout(初始化預設 10s)未被讀取,則主動退出。每次從佇列取得新位置,或在佇列為空時返回有效快取位置,都會重新整理消費時間。
解決 executor disable 任務後 WS 執行緒繼續空轉的問題。
ItemNavigatorTask 關鍵變更
移除的功能
support_multi_account標記 —— 不再參與多賬號覆蓋 UI- 舊版 JSON/Cookie/
key=value格式憑證解析
新增配置項
| 配置鍵 | 型別 | 說明 |
|---|---|---|
content |
str | 可選。直接填寫 hg/check data.content |
地图账号 |
dropdown | 可選。從賬號配置頁選擇已儲存 content 的賬號 |
run() 流程
flowchart TD
A[ItemNavigatorTask.run] --> B{游戏窗口是否存在}
B -->|否| C[清理 WS 和箭头]
B -->|是| D[读取 content 或地图账号 content]
D --> E{有凭证}
E -->|是| F[停止本地 WS 服务]
F --> G[启动官方地图 WS 客户端]
E -->|否| H[停止官方 WS 客户端]
H --> I[启动本地 WS 服务]
G --> J[读取位置 payload 或缓存]
I --> J
J --> K[匹配物品点位并绘制箭头]
K --> L[处理标记按键]
L --> M[延迟保存 marked_points.json]
相關檔案
| 檔案 | 職責 |
|---|---|
src/tasks/mixin/ws_position_mixin.py |
WS 客戶端核心:OAuth 換取、HTTP 簽名、WS 協議、退出控制 |
src/tasks/trigger/ItemNavigatorTask.py |
導航任務:憑證解析、位置消費、箭頭渲染、標記邏輯 |
src/tasks/account/account_scope_store.py |
持久化:map_contents 欄位的讀寫、賬號解析 |
src/gui/AccountConfigTab.py |
UI:賬號配置頁,包含地圖 content 編輯 |