|
9 | 9 | import time
|
10 | 10 |
|
11 | 11 | import aiohttp
|
12 |
| -from aiohttp import ContentTypeError |
| 12 | +from aiohttp import ContentTypeError, FormData |
13 | 13 |
|
14 | 14 | from roborock.containers import HomeData, HomeDataRoom, ProductResponse, RRiot, UserData
|
15 | 15 | from roborock.exceptions import (
|
@@ -67,22 +67,108 @@ def _get_header_client_id(self):
|
67 | 67 | md5.update(self._device_identifier.encode())
|
68 | 68 | return base64.b64encode(md5.digest()).decode()
|
69 | 69 |
|
70 |
| - def _get_hawk_authentication(self, rriot: RRiot, url: str) -> str: |
| 70 | + def _process_extra_hawk_values(self, values: dict | None) -> str: |
| 71 | + if values is None: |
| 72 | + return "" |
| 73 | + else: |
| 74 | + sorted_keys = sorted(values.keys()) |
| 75 | + result = [] |
| 76 | + for key in sorted_keys: |
| 77 | + value = values.get(key) |
| 78 | + result.append(f"{key}={value}") |
| 79 | + return hashlib.md5("&".join(result).encode()).hexdigest() |
| 80 | + |
| 81 | + def _get_hawk_authentication( |
| 82 | + self, rriot: RRiot, url: str, formdata: dict | None = None, params: dict | None = None |
| 83 | + ) -> str: |
71 | 84 | timestamp = math.floor(time.time())
|
72 | 85 | nonce = secrets.token_urlsafe(6)
|
| 86 | + formdata_str = self._process_extra_hawk_values(formdata) |
| 87 | + params_str = self._process_extra_hawk_values(params) |
| 88 | + |
73 | 89 | prestr = ":".join(
|
74 | 90 | [
|
75 | 91 | rriot.u,
|
76 | 92 | rriot.s,
|
77 | 93 | nonce,
|
78 | 94 | str(timestamp),
|
79 | 95 | hashlib.md5(url.encode()).hexdigest(),
|
80 |
| - "", |
81 |
| - "", |
| 96 | + params_str, |
| 97 | + formdata_str, |
82 | 98 | ]
|
83 | 99 | )
|
84 | 100 | mac = base64.b64encode(hmac.new(rriot.h.encode(), prestr.encode(), hashlib.sha256).digest()).decode()
|
85 |
| - return f'Hawk id="{rriot.u}", s="{rriot.s}", ts="{timestamp}", nonce="{nonce}", mac="{mac}"' |
| 101 | + return f'Hawk id="{rriot.u}",s="{rriot.s}",ts="{timestamp}",nonce="{nonce}",mac="{mac}"' |
| 102 | + |
| 103 | + async def nc_prepare(self, user_data: UserData, timezone: str) -> dict: |
| 104 | + """This gets a few critical parameters for adding a device to your account.""" |
| 105 | + if ( |
| 106 | + user_data.rriot is None |
| 107 | + or user_data.rriot.r is None |
| 108 | + or user_data.rriot.u is None |
| 109 | + or user_data.rriot.r.a is None |
| 110 | + ): |
| 111 | + raise RoborockException("Your userdata is missing critical attributes.") |
| 112 | + base_url = user_data.rriot.r.a |
| 113 | + prepare_request = PreparedRequest(base_url) |
| 114 | + hid = await self._get_home_id(user_data) |
| 115 | + |
| 116 | + data = FormData() |
| 117 | + data.add_field("hid", hid) |
| 118 | + data.add_field("tzid", timezone) |
| 119 | + |
| 120 | + prepare_response = await prepare_request.request( |
| 121 | + "post", |
| 122 | + "/nc/prepare", |
| 123 | + headers={ |
| 124 | + "Authorization": self._get_hawk_authentication( |
| 125 | + user_data.rriot, "/nc/prepare", {"hid": hid, "tzid": timezone} |
| 126 | + ), |
| 127 | + }, |
| 128 | + data=data, |
| 129 | + ) |
| 130 | + |
| 131 | + if prepare_response is None: |
| 132 | + raise RoborockException("prepare_response is None") |
| 133 | + if not prepare_response.get("success"): |
| 134 | + raise RoborockException(f"{prepare_response.get('msg')} - response code: {prepare_response.get('code')}") |
| 135 | + |
| 136 | + return prepare_response["result"] |
| 137 | + |
| 138 | + async def add_device(self, user_data: UserData, s: str, t: str) -> dict: |
| 139 | + """This will add a new device to your account |
| 140 | + it is recommended to only use this during a pairing cycle with a device. |
| 141 | + Please see here: https://github.com/Python-roborock/Roborockmitmproxy/blob/main/handshake_protocol.md |
| 142 | + """ |
| 143 | + if ( |
| 144 | + user_data.rriot is None |
| 145 | + or user_data.rriot.r is None |
| 146 | + or user_data.rriot.u is None |
| 147 | + or user_data.rriot.r.a is None |
| 148 | + ): |
| 149 | + raise RoborockException("Your userdata is missing critical attributes.") |
| 150 | + base_url = user_data.rriot.r.a |
| 151 | + add_device_request = PreparedRequest(base_url) |
| 152 | + |
| 153 | + add_device_response = await add_device_request.request( |
| 154 | + "GET", |
| 155 | + "/user/devices/newadd", |
| 156 | + headers={ |
| 157 | + "Authorization": self._get_hawk_authentication( |
| 158 | + user_data.rriot, "/user/devices/newadd", params={"s": s, "t": t} |
| 159 | + ), |
| 160 | + }, |
| 161 | + params={"s": s, "t": t}, |
| 162 | + ) |
| 163 | + |
| 164 | + if add_device_response is None: |
| 165 | + raise RoborockException("add_device is None") |
| 166 | + if not add_device_response.get("success"): |
| 167 | + raise RoborockException( |
| 168 | + f"{add_device_response.get('msg')} - response code: {add_device_response.get('code')}" |
| 169 | + ) |
| 170 | + |
| 171 | + return add_device_response["result"] |
86 | 172 |
|
87 | 173 | async def request_code(self) -> None:
|
88 | 174 | base_url = await self._get_base_url()
|
|
0 commit comments