From b6691174607a66959f4d9046dffb4cd4e782695d Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Thu, 9 Jan 2025 10:19:57 -0500 Subject: [PATCH] feat: add product v4 and downloading code (#267) * feat: add product v4 and downloading code * fix: remove got message --- roborock/containers.py | 42 ++++++++++--------- roborock/version_1_apis/roborock_client_v1.py | 6 +++ roborock/web_api.py | 40 +++++++++++++----- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/roborock/containers.py b/roborock/containers.py index 4a5cd21..7a30c79 100644 --- a/roborock/containers.py +++ b/roborock/containers.py @@ -893,26 +893,28 @@ class RoborockProductSpec(RoborockBase): @dataclass class RoborockProduct(RoborockBase): - id: int - name: str - model: str - packagename: str - ssid: str - picurl: str - cardpicurl: str - medium_cardpicurl: str - resetwifipicurl: str - resetwifitext: dict - tuyaid: str - status: int - rriotid: str - cardspec: str - pictures: list - nc_mode: str - scope: None - product_tags: list - agreements: list - plugin_pic_url: None + id: int | None = None + name: str | None = None + model: str | None = None + packagename: str | None = None + ssid: str | None = None + picurl: str | None = None + cardpicurl: str | None = None + mediumCardpicurl: str | None = None + resetwifipicurl: str | None = None + configPicUrl: str | None = None + pluginPicUrl: str | None = None + resetwifitext: dict | None = None + tuyaid: str | None = None + status: int | None = None + rriotid: str | None = None + pictures: list | None = None + ncMode: str | None = None + scope: str | None = None + product_tags: list | None = None + agreements: list | None = None + cardspec: str | None = None + plugin_pic_url: str | None = None products_specification: RoborockProductSpec | None = None def __post_init__(self): diff --git a/roborock/version_1_apis/roborock_client_v1.py b/roborock/version_1_apis/roborock_client_v1.py index 5cb9493..4e493a9 100644 --- a/roborock/version_1_apis/roborock_client_v1.py +++ b/roborock/version_1_apis/roborock_client_v1.py @@ -421,6 +421,12 @@ def on_message_received(self, messages: list[RoborockMessage]) -> None: consumable = Consumable.from_dict(value) for listener in self.listener_model.protocol_handlers.get(data_protocol, []): listener(consumable) + else: + self._logger.warning( + f"Unknown data protocol {data_point_number}, please create an " + f"issue on the python-roborock repository" + ) + self._logger.info(data) return except ValueError: self._logger.warning( diff --git a/roborock/web_api.py b/roborock/web_api.py index 44646f6..d79fc7d 100644 --- a/roborock/web_api.py +++ b/roborock/web_api.py @@ -54,7 +54,7 @@ async def _get_base_url(self) -> str: raise RoborockMissingParameters( "You are missing parameters for this request, are you sure you " "entered your username?" ) - raise RoborockUrlException(response.get("error")) + raise RoborockUrlException(f"error code: {response_code} msg: {response.get('error')}") response_data = response.get("data") if response_data is None: raise RoborockUrlException("response does not have 'data'") @@ -276,7 +276,7 @@ async def get_products(self, user_data: UserData) -> ProductResponse: product_request = PreparedRequest(base_url, {"header_clientid": header_clientid}) product_response = await product_request.request( "get", - "/api/v3/product", + "/api/v4/product", headers={"Authorization": user_data.token}, ) if product_response is None: @@ -288,24 +288,44 @@ async def get_products(self, user_data: UserData) -> ProductResponse: return ProductResponse.from_dict(result) raise RoborockException("product result was an unexpected type") + async def download_code(self, user_data: UserData, product_id: int): + base_url = await self._get_base_url() + header_clientid = self._get_header_client_id() + product_request = PreparedRequest(base_url, {"header_clientid": header_clientid}) + request = {"apilevel": 99999, "productids": [product_id], "type": 2} + response = await product_request.request( + "post", + "/api/v1/appplugin", + json=request, + headers={"Authorization": user_data.token, "Content-Type": "application/json"}, + ) + return response["data"][0]["url"] + + async def download_category_code(self, user_data: UserData): + base_url = await self._get_base_url() + header_clientid = self._get_header_client_id() + product_request = PreparedRequest(base_url, {"header_clientid": header_clientid}) + response = await product_request.request( + "get", + "api/v1/plugins?apiLevel=99999&type=2", + headers={ + "Authorization": user_data.token, + }, + ) + return {r["category"]: r["url"] for r in response["data"]["categoryPluginList"]} + class PreparedRequest: def __init__(self, base_url: str, base_headers: dict | None = None) -> None: self.base_url = base_url self.base_headers = base_headers or {} - async def request(self, method: str, url: str, params=None, data=None, headers=None) -> dict: + async def request(self, method: str, url: str, params=None, data=None, headers=None, json=None) -> dict: _url = "/".join(s.strip("/") for s in [self.base_url, url]) _headers = {**self.base_headers, **(headers or {})} async with aiohttp.ClientSession() as session: try: - async with session.request( - method, - _url, - params=params, - data=data, - headers=_headers, - ) as resp: + async with session.request(method, _url, params=params, data=data, headers=_headers, json=json) as resp: return await resp.json() except ContentTypeError as err: """If we get an error, lets log everything for debugging."""