Isolate official account RPC capabilities from API key device driver

This commit is contained in:
Pigway
2026-10-03 07:03:35 +08:00
parent 142d771ffa
commit 5aa0e9f762
15 changed files with 434 additions and 126 deletions
-1
View File
@@ -4,7 +4,6 @@ from pigway_frame_flow.models import Device, GalleryImage
class FrameDeviceDriver(Protocol):
def widgets(self): ...
def invalidate(self) -> None: ...
def device(self, refresh: bool = False) -> Device: ...
def get_current_image(self) -> int | None: ...
+1 -5
View File
@@ -8,9 +8,8 @@ from pigway_frame_flow.models import (
class KokonnaDriver:
def __init__(self, client, device_id, sanitize=lambda v: v, widgets=None):
def __init__(self, client, device_id, sanitize=lambda v: v):
self.client, self.device_id, self.sanitize = client, device_id, sanitize
self.widget_reader = widgets
self.cached: Device | None = None
self.cached_at = 0.0
@@ -63,9 +62,6 @@ class KokonnaDriver:
self.cached_at = time.monotonic()
return result
def widgets(self):
return self.widget_reader.read()
def get_current_image(self):
return self.device().current_content.id
@@ -1,862 +0,0 @@
{
"observed_at": "2026-10-02",
"transport": "https://robot.galaxyguide.cn/api/rpc",
"method": "appFrame.getFrameCovers",
"catalog": [
{
"id": 188,
"name": "大字体日历",
"tags": [
"Weather",
"feature",
"Calendar"
],
"type": null,
"arg_keys": [
"城市"
]
},
{
"id": 183,
"name": "天气日历-黑",
"tags": [
"Weather",
"feature",
"Calendar"
],
"type": null,
"arg_keys": [
"城市"
]
},
{
"id": 182,
"name": "天气日历-白",
"tags": [
"Weather",
"feature",
"Calendar"
],
"type": null,
"arg_keys": [
"城市"
]
},
{
"id": 190,
"name": "文学金句2",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 30,
"name": "文学金句",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 191,
"name": "名人名言",
"tags": [
"DailyRandom",
"Calendar",
"feature",
"Lunar"
],
"type": null,
"arg_keys": []
},
{
"id": 29,
"name": "古诗词名句",
"tags": [
"DailyRandom",
"Calendar",
"feature",
"Lunar"
],
"type": null,
"arg_keys": []
},
{
"id": 2,
"name": "电影台词",
"tags": [
"Weather",
"Calendar",
"DailyRandom"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 13,
"name": "一周天气",
"tags": [
"Weather",
"feature",
"Calendar",
"DailyRandom"
],
"type": null,
"arg_keys": [
"城市"
]
},
{
"id": 27,
"name": "世界名画推荐",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 7,
"name": "项目倒计时",
"tags": [
"Countdown"
],
"type": null,
"arg_keys": [
"项目名称",
"截止时间"
]
},
{
"id": 8,
"name": "结婚纪念日",
"tags": [
"Stopwatch"
],
"type": null,
"arg_keys": [
"新郎姓名",
"新娘姓名",
"结婚日期"
]
},
{
"id": 28,
"name": "乐器知识",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 193,
"name": "作品名称和描述",
"tags": [
"DailyRandom"
],
"type": null,
"arg_keys": []
},
{
"id": 192,
"name": "作品名称",
"tags": [
"DailyRandom"
],
"type": null,
"arg_keys": []
},
{
"id": 31,
"name": "旅行目的地推荐",
"tags": [
"DailyRandom",
"Calendar",
"Weather"
],
"type": null,
"arg_keys": [
"城市"
]
},
{
"id": 32,
"name": "动物科普",
"tags": [
"DailyRandom",
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 33,
"name": "植物科普",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 42,
"name": "海洋生物科普",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 43,
"name": "昆虫科普",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 36,
"name": "佛经片段",
"tags": [
"DailyRandom",
"Calendar",
"feature",
"Lunar"
],
"type": null,
"arg_keys": []
},
{
"id": 44,
"name": "美食推荐",
"tags": [
"DailyRandom",
"Calendar",
"feature",
"Lunar"
],
"type": null,
"arg_keys": []
},
{
"id": 45,
"name": "博物馆文物推荐",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 46,
"name": "职场毒鸡汤",
"tags": [
"DailyRandom",
"Calendar",
"feature",
"Lunar"
],
"type": null,
"arg_keys": []
},
{
"id": 37,
"name": "每日学单词",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 11,
"name": "生活建议",
"tags": [
"Weather",
"Calendar",
"feature"
],
"type": null,
"arg_keys": [
"城市"
]
},
{
"id": 34,
"name": "历史上的今天",
"tags": [
"DailyRandom",
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 5,
"name": "冷知识",
"tags": [
"Weather",
"Calendar",
"DailyRandom"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 1,
"name": "心灵鸡汤",
"tags": [
"feature",
"Weather",
"Calendar",
"DailyRandom"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 189,
"name": "待办和天气",
"tags": [
"feature",
"DailyRandom",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市",
"待办事项"
]
},
{
"id": 24,
"name": "待办事项",
"tags": [
"feature",
"DailyRandom",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市",
"待办事项"
]
},
{
"id": 39,
"name": "情侣间的365件事",
"tags": [
"Calendar",
"feature",
"Lunar"
],
"type": null,
"arg_keys": []
},
{
"id": 25,
"name": "星座今日运势",
"tags": [
"feature",
"DailyRandom",
"Calendar"
],
"type": null,
"arg_keys": [
"生日"
]
},
{
"id": 26,
"name": "生肖今日运势",
"tags": [
"feature",
"DailyRandom",
"Lunar",
"Calendar"
],
"type": null,
"arg_keys": [
"生日"
]
},
{
"id": 16,
"name": "娱乐新闻",
"tags": [
"feature",
"DailyRandom",
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 21,
"name": "政治新闻",
"tags": [
"feature",
"DailyRandom",
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 12,
"name": "科技新闻",
"tags": [
"feature",
"DailyRandom",
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 15,
"name": "财经新闻",
"tags": [
"feature",
"DailyRandom",
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 48,
"name": "简约月历3",
"tags": [
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 47,
"name": "简约月历2",
"tags": [
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 41,
"name": "简约月历1",
"tags": [
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 23,
"name": "黄历",
"tags": [
"feature",
"DailyRandom",
"Lunar"
],
"type": null,
"arg_keys": []
},
{
"id": 19,
"name": "农历阳历",
"tags": [
"Lunar",
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 18,
"name": "简约日历3",
"tags": [
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 22,
"name": "简约日历2",
"tags": [
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 3,
"name": "简约日历1",
"tags": [
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 54,
"name": "极简日历5",
"tags": [
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 40,
"name": "极简日历4",
"tags": [
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 53,
"name": "KoKonna极简日历",
"tags": [
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 20,
"name": "极简日历3",
"tags": [
"Calendar",
"feature"
],
"type": null,
"arg_keys": []
},
{
"id": 4,
"name": "极简日历2",
"tags": [
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 14,
"name": "极简日历1",
"tags": [
"Calendar"
],
"type": null,
"arg_keys": []
},
{
"id": 228,
"name": "月历卡片",
"tags": [
"Weather",
"Calendar"
],
"type": null,
"arg_keys": [
"城市"
]
},
{
"id": 209,
"name": "卡片天气日历15",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 208,
"name": "卡片天气日历14",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 207,
"name": "卡片天气日历13",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 206,
"name": "卡片天气日历12",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 205,
"name": "卡片天气日历11",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 204,
"name": "卡片天气日历10",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 203,
"name": "卡片天气日历9",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 202,
"name": "卡片天气日历8",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 201,
"name": "卡片天气日历7",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 200,
"name": "卡片天气日历6",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 199,
"name": "卡片天气日历5",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 198,
"name": "卡片天气日历4",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 197,
"name": "卡片天气日历3",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 196,
"name": "卡片天气日历2",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 195,
"name": "卡片天气日历1",
"tags": [
"feature",
"Weather"
],
"type": null,
"arg_keys": [
"所在城市"
]
},
{
"id": 57,
"name": "自定义二维码",
"tags": [
"DailyRandom"
],
"type": null,
"arg_keys": [
"二维码内容",
"备注"
]
},
{
"id": 56,
"name": "画框分享二维码",
"tags": [
"DailyRandom"
],
"type": null,
"arg_keys": []
},
{
"id": 55,
"name": "画框状态",
"tags": [
"DailyRandom"
],
"type": null,
"arg_keys": []
},
{
"id": 52,
"name": "黑色艺术字贴纸2",
"tags": [
"DailyRandom",
"feature"
],
"type": null,
"arg_keys": [
"内容"
]
},
{
"id": 50,
"name": "黑色艺术字贴纸",
"tags": [
"DailyRandom",
"feature"
],
"type": null,
"arg_keys": [
"内容"
]
},
{
"id": 49,
"name": "白色艺术字贴纸",
"tags": [
"DailyRandom",
"feature"
],
"type": null,
"arg_keys": [
"内容"
]
},
{
"id": 17,
"name": "红色艺术字贴纸",
"tags": [
"DailyRandom",
"feature"
],
"type": null,
"arg_keys": [
"内容"
]
}
],
"notes": "Observed directory only; no user tokens, device IDs, photos or configuration values retained."
}
+3 -116
View File
@@ -1,118 +1,5 @@
"""Read-only official widget RPC; separate from the device OpenAPI credential."""
"""Compatibility import; official capabilities live outside device drivers."""
import json
import time
from pathlib import Path
from pigway_frame_flow.integrations.kokonna_official.widgets import WidgetReader
import requests
from pigway_frame_flow.errors import ErrorCode as E
from pigway_frame_flow.errors import FlowError
from pigway_frame_flow.models import Widget, WidgetState
class WidgetReader:
def __init__(self, token="", robot_id=None, mock=False):
self._token = token
self._robot_id = robot_id
self.mock = mock
self._cached = None
self._cached_at = 0.0
def read(self) -> WidgetState:
if self._cached is not None and time.monotonic() - self._cached_at < 60:
return self._cached
snapshot = json.loads(
Path(__file__).with_name("widget_catalog.json").read_text()
)
catalog = [
Widget(**{k: row[k] for k in ("id", "name", "tags", "arg_keys")})
for row in snapshot["catalog"]
]
if self.mock:
return WidgetState(
source="mock",
configured=True,
catalog=catalog,
current=catalog[0],
current_verified=True,
)
if not self._token or self._robot_id is None:
return WidgetState(
source="snapshot",
configured=False,
catalog=catalog,
observed_at=snapshot["observed_at"],
status="AUTH_REQUIRED",
)
try:
response = requests.post(
"https://robot.galaxyguide.cn/api/rpc",
headers={"Authorization": f"Bearer {self._token}"},
json={
"jsonrpc": "2.0",
"method": "appFrame.getFrameCovers",
"params": {"robotId": self._robot_id},
"id": int(time.time() * 1000),
},
timeout=15,
allow_redirects=False,
)
if response.status_code in (401, 403):
raise FlowError(E.UNAUTHORIZED, "Official widget session expired", 401)
response.raise_for_status()
payload = response.json()
if payload.get("error"):
raise FlowError(
E.CLOUD_UNAVAILABLE, "Official widget query rejected", 502
)
data = payload["result"]
rows = data["rows"]
catalog = [
Widget(
id=row["id"],
name=row["name"],
tags=row.get("tags") or [],
arg_keys=list((row.get("args") or {}).keys())
if isinstance(row.get("args"), dict)
else [],
)
for row in rows
]
selected = data.get("frameCoverRobot") or {}
cover = selected.get("frameCover") or {}
current_id = cover.get("id", selected.get("coverId"))
current = next((row for row in catalog if row.id == current_id), None)
robot = data.get("robot") or {}
no_widget = (
"frameCoverRobot" in data
and data["frameCoverRobot"] is None
and "frameCoverRobotId" in robot
and robot["frameCoverRobotId"] is None
)
result = WidgetState(
source="live",
configured=True,
catalog=catalog,
current=current,
current_verified=current is not None or no_widget,
status="OK"
if current
else "NO_WIDGET"
if no_widget
else "CURRENT_UNVERIFIED",
)
except FlowError:
raise
except (
requests.RequestException,
ValueError,
KeyError,
TypeError,
AttributeError,
):
raise FlowError(
E.CLOUD_UNAVAILABLE, "Official widget query unavailable", 502
) from None
self._cached, self._cached_at = result, time.monotonic()
return result
__all__ = ["WidgetReader"]