Skip to content

Commit 9ae9b3a

Browse files
committed
reorganize structure
1 parent 113d376 commit 9ae9b3a

73 files changed

Lines changed: 5179 additions & 2360 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/roborock_local_server/backend.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def _resolve_backend_path() -> Path:
1919
sys.path.insert(0, backend_str)
2020

2121
from https_server.endpoint_rules import default_endpoint_rules, resolve_route # type: ignore # noqa: E402
22+
from https_server.routes.plugin.dispatch import PluginZipDispatchError, dispatch_plugin_zip_request # type: ignore # noqa: E402
2223
from import_web_inventory_from_cloud import ( # type: ignore # noqa: E402
2324
_build_inventory,
2425
_fetch_additional_web_cache,
@@ -59,7 +60,9 @@ def _resolve_backend_path() -> Path:
5960
"_normalize_value_map",
6061
"append_jsonl",
6162
"classify_host",
63+
"dispatch_plugin_zip_request",
6264
"default_endpoint_rules",
65+
"PluginZipDispatchError",
6366
"resolve_route",
6467
"setup_file_logger",
6568
"start_broker",

src/roborock_local_server/bundled_backend/https_server/endpoint_rules.py

Lines changed: 152 additions & 2221 deletions
Large diffs are not rendered by default.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"""URL-tree route modules for HTTPS endpoint handling."""
2+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"""API route namespaces."""
2+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Shared app-config data for v1/v2 appconfig endpoints."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
APP_CONFIG_COMMON_DATA: dict[str, Any] = {
8+
"version": "4.41.04",
9+
"url": "itms-apps://itunes.apple.com/cn/app/id1462875428?mt=8",
10+
"required": 0,
11+
"description": "<p><br></p>",
12+
"minimumVersion": "0.0.0",
13+
"mainPictureInfoList": [
14+
{
15+
"darkModePic": "https://files.roborock.com/iot/doc/bbecbae006b940eab34d200ba809bb72.png",
16+
"lightModePic": "https://files.roborock.com/iot/doc/a35d5dfcd6eb41e69dab160fe12f059f.png",
17+
},
18+
{
19+
"darkModePic": "https://files.roborock.com/iot/doc/588414a901d645058c9dce93b5f891c3.png",
20+
"lightModePic": "https://files.roborock.com/iot/doc/e38de51323a743628f094358611c6b0d.png",
21+
},
22+
{
23+
"darkModePic": "https://files.roborock.com/iot/doc/576cfc12bed843c9b45bffabceb07259.png",
24+
"lightModePic": "https://files.roborock.com/iot/doc/de2f035553f24795a5699279193d312a.png",
25+
},
26+
],
27+
}
28+
29+
30+
def app_config_common_payload() -> dict[str, Any]:
31+
return dict(APP_CONFIG_COMMON_DATA)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""Shared product-state services for /api/v4/product and /api/v5/product."""
2+
3+
from __future__ import annotations
4+
5+
import hashlib
6+
from typing import Any
7+
8+
from shared.context import ServerContext
9+
10+
from ..user.homes.service import home_payload
11+
12+
_MODEL_PRODUCT_ID_OVERRIDES = {
13+
"roborock.vacuum.a87": 110,
14+
"roborock.vacuum.a15": 23,
15+
"roborock.vacuum.sc05": 10001,
16+
}
17+
18+
19+
def _as_int(value: Any, default: int) -> int:
20+
try:
21+
return int(value)
22+
except (TypeError, ValueError):
23+
return default
24+
25+
26+
def _default_product_name(model: str) -> str:
27+
short_model = model.split(".")[-1].upper() if model else "VACUUM"
28+
return f"Roborock {short_model}"
29+
30+
31+
def _stable_int(seed: str) -> int:
32+
return int(hashlib.sha256(seed.encode("utf-8")).hexdigest()[:12], 16)
33+
34+
35+
def build_product_response(ctx: ServerContext) -> dict[str, Any]:
36+
home_data = home_payload(ctx)
37+
products_value = home_data.get("products")
38+
products = products_value if isinstance(products_value, list) else []
39+
categories: dict[str, dict[str, Any]] = {}
40+
for product in products:
41+
if not isinstance(product, dict):
42+
continue
43+
raw_product_id = str(product.get("id") or "")
44+
category_name = str(product.get("category") or "robot.vacuum.cleaner")
45+
if category_name not in categories:
46+
category_id = len(categories) + 1
47+
categories[category_name] = {
48+
"category": {
49+
"id": category_id,
50+
"displayName": category_name,
51+
"iconUrl": "",
52+
},
53+
"productList": [],
54+
}
55+
model = str(product.get("model") or "roborock.vacuum.a117")
56+
model_key = model.strip().lower()
57+
product_id = _MODEL_PRODUCT_ID_OVERRIDES.get(
58+
model_key,
59+
_as_int(raw_product_id, _stable_int(raw_product_id or category_name) % 1_000_000),
60+
)
61+
product_entry = {
62+
"id": product_id,
63+
"name": str(product.get("name") or _default_product_name(model)),
64+
"model": model,
65+
"packagename": f"com.roborock.{model.split('.')[-1]}",
66+
"ncMode": "global",
67+
"status": 10,
68+
}
69+
icon_url = product.get("iconUrl")
70+
if isinstance(icon_url, str) and icon_url:
71+
product_entry["picurl"] = icon_url
72+
product_entry["cardPicUrl"] = icon_url
73+
product_entry["pluginPicUrl"] = icon_url
74+
categories[category_name]["productList"].append(product_entry)
75+
return {"categoryDetailList": list(categories.values())}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"""API v1 route modules."""
2+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Route handler for /api/v1/appconfig."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from shared.context import ServerContext
8+
9+
from ...auth.service import ok
10+
from ..appconfig_service import app_config_common_payload
11+
12+
13+
def match(path: str) -> bool:
14+
return path.rstrip("/") == "/api/v1/appconfig"
15+
16+
17+
def build(
18+
ctx: ServerContext,
19+
query_params: dict[str, list[str]],
20+
body_params: dict[str, list[str]],
21+
clean_path: str,
22+
) -> dict[str, Any]:
23+
_ = ctx, query_params, body_params, clean_path
24+
return ok(app_config_common_payload())
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Route handler for /api/v1/appfeatureplugin."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from shared.context import ServerContext
8+
9+
from ...plugin.common import APP_FEATURE_PLUGIN_LIST, proxied_plugin_records
10+
11+
12+
def _ok(data: Any) -> dict[str, Any]:
13+
return {"code": 200, "msg": "success", "data": data}
14+
15+
16+
def match(path: str) -> bool:
17+
return path.rstrip("/") == "/api/v1/appfeatureplugin"
18+
19+
20+
def build(
21+
ctx: ServerContext,
22+
query_params: dict[str, list[str]],
23+
body_params: dict[str, list[str]],
24+
clean_path: str,
25+
) -> dict[str, Any]:
26+
_ = query_params, body_params, clean_path
27+
return _ok({"plugins": proxied_plugin_records(ctx, APP_FEATURE_PLUGIN_LIST)})
28+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Route handler for /api/v1/appplugin."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from shared.context import ServerContext
8+
9+
from ...plugin.common import APPPLUGIN_LIST, proxied_plugin_records
10+
11+
12+
def _ok(data: Any) -> dict[str, Any]:
13+
return {"code": 200, "msg": "success", "data": data}
14+
15+
16+
def match(path: str) -> bool:
17+
return path.rstrip("/") == "/api/v1/appplugin"
18+
19+
20+
def build(
21+
ctx: ServerContext,
22+
query_params: dict[str, list[str]],
23+
body_params: dict[str, list[str]],
24+
clean_path: str,
25+
) -> dict[str, Any]:
26+
_ = query_params, body_params, clean_path
27+
return _ok(proxied_plugin_records(ctx, APPPLUGIN_LIST))
28+

0 commit comments

Comments
 (0)