Files
pigway-frame-flow/pigway_frame_flow/integrations/kokonna_official/rpc.py
T

107 lines
3.9 KiB
Python
Raw Normal View History

"""Strict account RPC allowlist. Never falls back to a device key or retries."""
import time
import requests
from pigway_frame_flow.errors import ErrorCode as E
from pigway_frame_flow.errors import FlowError
from pigway_frame_flow.integrations.kokonna_official.capabilities import READ_OPERATIONS
class OfficialRPC:
def __init__(self, token: str = ""):
self._token = token
@property
def authorized(self) -> bool:
return bool(self._token)
def _call(self, method: str, params: dict, *, allow_write=False) -> dict:
if method == "appFrame.setFrameCover" and not allow_write:
raise FlowError(
E.UNSUPPORTED_CAPABILITY, "Use the explicit widget operation", 422
)
if method not in READ_OPERATIONS and method not in (
"app.login",
"appFrame.setFrameCover",
):
raise FlowError(
E.UNSUPPORTED_CAPABILITY, "Official operation is not enabled", 422
)
if method != "app.login" and not self._token:
raise FlowError(
E.CONFIG_REQUIRED, "Official account authorization required", 503
)
try:
response = requests.post(
"https://robot.galaxyguide.cn/api/rpc",
headers={"Authorization": "Bearer " + self._token}
if method != "app.login"
else {},
json={
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": int(time.time() * 1000),
},
timeout=15,
allow_redirects=False,
)
if response.status_code in (401, 403):
self.disconnect()
raise FlowError(
E.OFFICIAL_AUTH_REQUIRED,
"Official account authentication required",
401,
)
payload = response.json()
error = payload.get("error")
if isinstance(error, dict) and error.get("message") == "请重新登陆":
self.disconnect()
raise FlowError(
E.OFFICIAL_AUTH_REQUIRED, "Official account session expired", 401
)
response.raise_for_status()
if error or not isinstance(payload.get("result"), dict):
raise ValueError()
return payload["result"]
except FlowError:
raise
except (requests.RequestException, ValueError, AttributeError):
if method == "appFrame.setFrameCover":
raise FlowError(
E.WRITE_RESULT_UNKNOWN, "Official widget write result unknown", 409
) from None
raise FlowError(
E.CLOUD_UNAVAILABLE, "Official service query failed", 502
) from None
def login(self, username: str, password: str) -> None:
if not username or not password:
raise FlowError(E.INVALID_REQUEST, "Official account credentials required")
result = self._call("app.login", {"username": username, "password": password})
token = result.get("token")
if not isinstance(token, str) or not token:
raise FlowError(E.UNAUTHORIZED, "Official account login failed", 401)
self._token = token
def disconnect(self) -> None:
self._token = ""
def devices(self) -> dict:
return self._call("app.getRobots", {})
def device(self, robot_id: int) -> dict:
return self._call("appFrame.getFrame", {"id": robot_id})
def widgets(self, robot_id: int) -> dict:
return self._call("appFrame.getFrameCovers", {"robotId": robot_id})
def set_widget(self, robot_id, cover_id, args):
return self._call(
"appFrame.setFrameCover",
{"robotId": robot_id, "coverId": cover_id, "args": args},
allow_write=True,
)