python-roborock 2.17.0__tar.gz → 2.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {python_roborock-2.17.0 → python_roborock-2.18.0}/PKG-INFO +2 -1
  2. {python_roborock-2.17.0 → python_roborock-2.18.0}/pyproject.toml +2 -1
  3. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/exceptions.py +4 -0
  4. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/web_api.py +43 -0
  5. {python_roborock-2.17.0 → python_roborock-2.18.0}/LICENSE +0 -0
  6. {python_roborock-2.17.0 → python_roborock-2.18.0}/README.md +0 -0
  7. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/__init__.py +0 -0
  8. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/api.py +0 -0
  9. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/cli.py +0 -0
  10. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/cloud_api.py +0 -0
  11. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/code_mappings.py +0 -0
  12. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/command_cache.py +0 -0
  13. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/const.py +0 -0
  14. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/containers.py +0 -0
  15. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/local_api.py +0 -0
  16. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/protocol.py +0 -0
  17. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/py.typed +0 -0
  18. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/roborock_future.py +0 -0
  19. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/roborock_message.py +0 -0
  20. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/roborock_typing.py +0 -0
  21. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/util.py +0 -0
  22. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/version_1_apis/__init__.py +0 -0
  23. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
  24. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
  25. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
  26. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/version_a01_apis/__init__.py +0 -0
  27. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  28. {python_roborock-2.17.0 → python_roborock-2.18.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-roborock
3
- Version: 2.17.0
3
+ Version: 2.18.0
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Home-page: https://github.com/humbertogontijo/python-roborock
6
6
  License: GPL-3.0-only
@@ -24,6 +24,7 @@ Requires-Dist: construct (>=2.10.57,<3.0.0)
24
24
  Requires-Dist: paho-mqtt (>=1.6.1,<3.0.0)
25
25
  Requires-Dist: pycryptodome (>=3.18,<4.0)
26
26
  Requires-Dist: pycryptodomex (>=3.18,<4.0) ; sys_platform == "darwin"
27
+ Requires-Dist: pyrate-limiter (>=3.7.0,<4.0.0)
27
28
  Requires-Dist: vacuum-map-parser-roborock
28
29
  Project-URL: Documentation, https://python-roborock.readthedocs.io/
29
30
  Project-URL: Repository, https://github.com/humbertogontijo/python-roborock
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-roborock"
3
- version = "2.17.0"
3
+ version = "2.18.0"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = ["humbertogontijo <humbertogontijo@users.noreply.github.com>"]
6
6
  license = "GPL-3.0-only"
@@ -30,6 +30,7 @@ pycryptodomex = {version = "^3.18", markers = "sys_platform == 'darwin'"}
30
30
  paho-mqtt = ">=1.6.1,<3.0.0"
31
31
  construct = "^2.10.57"
32
32
  vacuum-map-parser-roborock = "*"
33
+ pyrate-limiter = "^3.7.0"
33
34
 
34
35
 
35
36
  [build-system]
@@ -72,3 +72,7 @@ class RoborockMissingParameters(RoborockException):
72
72
 
73
73
  class RoborockTooManyRequest(RoborockException):
74
74
  """Class for Roborock too many request exceptions."""
75
+
76
+
77
+ class RoborockRateLimit(RoborockException):
78
+ """Class for our rate limits exceptions."""
@@ -10,6 +10,7 @@ import time
10
10
 
11
11
  import aiohttp
12
12
  from aiohttp import ContentTypeError, FormData
13
+ from pyrate_limiter import BucketFullException, Duration, Limiter, Rate
13
14
 
14
15
  from roborock.containers import HomeData, HomeDataRoom, HomeDataScene, ProductResponse, RRiot, UserData
15
16
  from roborock.exceptions import (
@@ -21,6 +22,7 @@ from roborock.exceptions import (
21
22
  RoborockInvalidUserAgreement,
22
23
  RoborockMissingParameters,
23
24
  RoborockNoUserAgreement,
25
+ RoborockRateLimit,
24
26
  RoborockTooFrequentCodeRequests,
25
27
  RoborockTooManyRequest,
26
28
  RoborockUrlException,
@@ -30,6 +32,22 @@ _LOGGER = logging.getLogger(__name__)
30
32
 
31
33
 
32
34
  class RoborockApiClient:
35
+ _LOGIN_RATES = [
36
+ Rate(1, Duration.SECOND),
37
+ Rate(3, Duration.MINUTE),
38
+ Rate(10, Duration.HOUR),
39
+ Rate(20, Duration.DAY),
40
+ ]
41
+ _HOME_DATA_RATES = [
42
+ Rate(1, Duration.SECOND),
43
+ Rate(5, Duration.MINUTE),
44
+ Rate(15, Duration.HOUR),
45
+ Rate(40, Duration.DAY),
46
+ ]
47
+
48
+ _login_limiter = Limiter(_LOGIN_RATES)
49
+ _home_data_limiter = Limiter(_HOME_DATA_RATES)
50
+
33
51
  def __init__(self, username: str, base_url=None, session: aiohttp.ClientSession | None = None) -> None:
34
52
  """Sample API Client."""
35
53
  self._username = username
@@ -175,6 +193,11 @@ class RoborockApiClient:
175
193
  return add_device_response["result"]
176
194
 
177
195
  async def request_code(self) -> None:
196
+ try:
197
+ self._login_limiter.try_acquire("login")
198
+ except BucketFullException as ex:
199
+ _LOGGER.info(ex.meta_info)
200
+ raise RoborockRateLimit("Reached maximum requests for login. Please try again later.") from ex
178
201
  base_url = await self._get_base_url()
179
202
  header_clientid = self._get_header_client_id()
180
203
  code_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
@@ -199,6 +222,11 @@ class RoborockApiClient:
199
222
  raise RoborockException(f"{code_response.get('msg')} - response code: {code_response.get('code')}")
200
223
 
201
224
  async def pass_login(self, password: str) -> UserData:
225
+ try:
226
+ self._login_limiter.try_acquire("login")
227
+ except BucketFullException as ex:
228
+ _LOGGER.info(ex.meta_info)
229
+ raise RoborockRateLimit("Reached maximum requests for login. Please try again later.") from ex
202
230
  base_url = await self._get_base_url()
203
231
  header_clientid = self._get_header_client_id()
204
232
 
@@ -289,6 +317,11 @@ class RoborockApiClient:
289
317
  return home_id_response["data"]["rrHomeId"]
290
318
 
291
319
  async def get_home_data(self, user_data: UserData) -> HomeData:
320
+ try:
321
+ self._home_data_limiter.try_acquire("home_data")
322
+ except BucketFullException as ex:
323
+ _LOGGER.info(ex.meta_info)
324
+ raise RoborockRateLimit("Reached maximum requests for home data. Please try again later.") from ex
292
325
  rriot = user_data.rriot
293
326
  if rriot is None:
294
327
  raise RoborockException("rriot is none")
@@ -313,6 +346,11 @@ class RoborockApiClient:
313
346
 
314
347
  async def get_home_data_v2(self, user_data: UserData) -> HomeData:
315
348
  """This is the same as get_home_data, but uses a different endpoint and includes non-robotic vacuums."""
349
+ try:
350
+ self._home_data_limiter.try_acquire("home_data")
351
+ except BucketFullException as ex:
352
+ _LOGGER.info(ex.meta_info)
353
+ raise RoborockRateLimit("Reached maximum requests for home data. Please try again later.") from ex
316
354
  rriot = user_data.rriot
317
355
  if rriot is None:
318
356
  raise RoborockException("rriot is none")
@@ -337,6 +375,11 @@ class RoborockApiClient:
337
375
 
338
376
  async def get_home_data_v3(self, user_data: UserData) -> HomeData:
339
377
  """This is the same as get_home_data, but uses a different endpoint and includes non-robotic vacuums."""
378
+ try:
379
+ self._home_data_limiter.try_acquire("home_data")
380
+ except BucketFullException as ex:
381
+ _LOGGER.info(ex.meta_info)
382
+ raise RoborockRateLimit("Reached maximum requests for home data. Please try again later.") from ex
340
383
  rriot = user_data.rriot
341
384
  home_id = await self._get_home_id(user_data)
342
385
  if rriot.r.a is None: