feat: settings password change, migrate desktop client with hub activation/report
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""与 gold_monitor 中控通信:激活、成交上报、K 线上报。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from typing import Any, Optional
|
||||
|
||||
from device_store import load_device, save_device
|
||||
|
||||
|
||||
class HubError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _url(server: str, path: str) -> str:
|
||||
base = (server or "").rstrip("/")
|
||||
if not base:
|
||||
raise HubError("未配置服务器地址")
|
||||
if not path.startswith("/"):
|
||||
path = "/" + path
|
||||
return base + path
|
||||
|
||||
|
||||
def _request(
|
||||
method: str,
|
||||
server: str,
|
||||
path: str,
|
||||
body: Optional[dict] = None,
|
||||
token: str = "",
|
||||
timeout: float = 15.0,
|
||||
) -> dict[str, Any]:
|
||||
data = None
|
||||
headers = {"Accept": "application/json"}
|
||||
if body is not None:
|
||||
raw = json.dumps(body, ensure_ascii=False).encode("utf-8")
|
||||
data = raw
|
||||
headers["Content-Type"] = "application/json"
|
||||
if token:
|
||||
headers["X-Device-Token"] = token
|
||||
req = urllib.request.Request(_url(server, path), data=data, headers=headers, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
text = resp.read().decode("utf-8", errors="replace")
|
||||
return json.loads(text) if text else {}
|
||||
except urllib.error.HTTPError as exc:
|
||||
try:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
payload = json.loads(detail) if detail else {}
|
||||
raise HubError(payload.get("error") or detail or str(exc)) from exc
|
||||
except HubError:
|
||||
raise
|
||||
except Exception:
|
||||
raise HubError(f"HTTP {exc.code}") from exc
|
||||
except urllib.error.URLError as exc:
|
||||
raise HubError(f"无法连接服务器:{exc.reason}") from exc
|
||||
|
||||
|
||||
def activate(server: str, code: str, fingerprint: str, machine_name: str) -> dict[str, Any]:
|
||||
result = _request(
|
||||
"POST",
|
||||
server,
|
||||
"/api/v1/activate",
|
||||
{
|
||||
"code": code.strip(),
|
||||
"fingerprint": fingerprint,
|
||||
"machine_name": machine_name,
|
||||
},
|
||||
)
|
||||
if not result.get("ok"):
|
||||
raise HubError(result.get("error") or "激活失败")
|
||||
device = load_device()
|
||||
device["server"] = server.rstrip("/")
|
||||
device["token"] = result["token"]
|
||||
device["account_id"] = result.get("account_id")
|
||||
device["machine_id"] = result.get("machine_id")
|
||||
device["fingerprint"] = fingerprint
|
||||
device["machine_name"] = machine_name
|
||||
save_device(device)
|
||||
return result
|
||||
|
||||
|
||||
def ping() -> bool:
|
||||
d = load_device()
|
||||
if not d.get("token") or not d.get("server"):
|
||||
return False
|
||||
try:
|
||||
r = _request("GET", d["server"], "/api/v1/ping", token=d["token"], timeout=8)
|
||||
return bool(r.get("ok"))
|
||||
except HubError:
|
||||
return False
|
||||
|
||||
|
||||
def report_trade(payload: dict[str, Any]) -> Optional[int]:
|
||||
d = load_device()
|
||||
if not d.get("token") or not d.get("server"):
|
||||
return None
|
||||
try:
|
||||
r = _request("POST", d["server"], "/api/v1/trades", body=payload, token=d["token"])
|
||||
if r.get("ok"):
|
||||
return int(r.get("id") or 0)
|
||||
except HubError:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def report_bars(symbol: str, timeframe: str, bars: list[dict]) -> int:
|
||||
d = load_device()
|
||||
if not d.get("token") or not d.get("server") or not bars:
|
||||
return 0
|
||||
try:
|
||||
r = _request(
|
||||
"POST",
|
||||
d["server"],
|
||||
"/api/v1/bars",
|
||||
body={"symbol": symbol, "timeframe": timeframe, "bars": bars},
|
||||
token=d["token"],
|
||||
timeout=30,
|
||||
)
|
||||
return int(r.get("count") or 0)
|
||||
except HubError:
|
||||
return 0
|
||||
Reference in New Issue
Block a user