Skip to content

Commit daeade7

Browse files
committed
Fix lint errors
1 parent 5b9821f commit daeade7

File tree

9 files changed

+76
-44
lines changed

9 files changed

+76
-44
lines changed

karcher/cli.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ def default(self, o):
2525
return super().default(o)
2626

2727
class GlobalContextObject:
28-
def __init__(self, debug: int = 0, output: str = "json", region: Region = Region.EU):
28+
def __init__(self,
29+
debug: int = 0,
30+
output: str = "json",
31+
region: Region = Region.EU
32+
):
2933
self.debug = debug
3034
self.output = output
3135
self.region = region
@@ -104,17 +108,17 @@ def devices(ctx: click.Context, username: str, password: str, token: str):
104108

105109
kh = KarcherHome(region=ctx.obj.region)
106110
sess = None
107-
if token != None:
111+
if token is not None:
108112
sess = Session.from_token(token, '')
109-
elif username != None and password != None:
113+
elif username is not None and password is not None:
110114
sess = kh.login(username, password)
111115
else:
112116
raise click.BadParameter('Must provide either token or username and password.')
113117

114118
devices = kh.get_devices(sess)
115119

116120
# Logout if we used a username and password
117-
if token == None:
121+
if token is None:
118122
kh.logout(sess)
119123

120124
ctx.obj.print(devices)

karcher/consts.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from enum import Enum
77

8+
89
class Region(str, Enum):
910
"""Region enum.
1011
@@ -14,6 +15,7 @@ class Region(str, Enum):
1415
US = 'us'
1516
CN = 'cn'
1617

18+
1719
class Language(int, Enum):
1820
"""Language enum.
1921
@@ -57,13 +59,15 @@ def __str__(self):
5759
else:
5860
return 'en'
5961

62+
6063
class Product(str, Enum):
6164
"""Product model enum."""
6265

6366
RCV3 = '1528986273083777024'
6467
RCV5 = '1540149850806333440'
6568
RCF5 = '1599715149861306368'
6669

70+
6771
REGION_URLS = {
6872
Region.EU: "https://eu-appaiot.3irobotix.net",
6973
Region.US: "https://us-appaiot.3irobotix.net",

karcher/device.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
from .consts import Product
1212
from .utils import snake_case
1313

14+
1415
class DeviceStatus(int, Enum):
1516
"""Device status enum."""
1617
Offline = 0
1718
Online = 1
1819

20+
1921
@dataclass(init=False)
2022
class DeviceVersion:
2123
"""Device version class.
@@ -34,6 +36,7 @@ def __init__(self, **kwargs):
3436
if k in names:
3537
setattr(self, k, v)
3638

39+
3740
@dataclass(init=False)
3841
class Device:
3942
"""Device class.

karcher/exception.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,17 @@ def __init__(self, message):
2828
self.message = message
2929
super().__init__(608, self.message)
3030

31+
3132
def handle_error_code(code, message):
3233
if code == 608:
3334
raise KarcherHomeAccessDenied('Forbidden')
3435
elif code == 609:
35-
raise KarcherHomeAccessDenied('Unauthorized or authorization expired, please log in again')
36+
raise KarcherHomeAccessDenied(
37+
'Unauthorized or authorization expired, please log in again')
3638
elif code == 613:
3739
raise KarcherHomeException(613, 'Invalid token')
3840
elif code == 620:
39-
raise KarcherHomeException(620, 'The username or password is incorrect')
41+
raise KarcherHomeException(
42+
620, 'The username or password is incorrect')
4043
else:
4144
raise KarcherHomeException(code, message)

karcher/identifiers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""Compat layer for homeassistant."""
77
from enum import Enum, auto
88

9+
910
class VacuumState(Enum):
1011
"""Vacuum state enum.
1112

karcher/karcher.py

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@
99
import urllib.parse
1010

1111
from .auth import Domains, Session
12-
from .consts import APP_VERSION_CODE, APP_VERSION_NAME, PROJECT_TYPE, PROTOCOL_VERSION, REGION_URLS, TENANT_ID, Region, Language
12+
from .consts import APP_VERSION_CODE, APP_VERSION_NAME, PROJECT_TYPE, \
13+
PROTOCOL_VERSION, REGION_URLS, TENANT_ID, \
14+
Region, Language
1315
from .device import Device
1416
from .exception import KarcherHomeAccessDenied, KarcherHomeException, handle_error_code
1517
from .map import Map
16-
from .utils import decrypt, decrypt_map, encrypt, get_nonce, get_random_string, get_timestamp, is_email, md5
18+
from .utils import decrypt, decrypt_map, encrypt, get_nonce, get_random_string, \
19+
get_timestamp, is_email, md5
20+
1721

1822
class KarcherHome:
1923
"""Main class to access Karcher Home Robots API"""
@@ -94,13 +98,15 @@ def _download(self, url):
9498

9599
resp = session.get(url, headers=headers)
96100
if resp.status_code != 200:
97-
raise KarcherHomeException(-1, 'HTTP error: ' + str(resp.status_code))
101+
raise KarcherHomeException(-1,
102+
'HTTP error: ' + str(resp.status_code))
98103

99104
return resp.content
100105

101-
def _process_response(self, resp, prop = None):
106+
def _process_response(self, resp, prop=None):
102107
if resp.status_code != 200:
103-
raise KarcherHomeException(-1, 'HTTP error: ' + str(resp.status_code))
108+
raise KarcherHomeException(-1,
109+
'HTTP error: ' + str(resp.status_code))
104110
data = resp.json()
105111
# Check for error response
106112
if data['code'] != 0:
@@ -112,7 +118,7 @@ def _process_response(self, resp, prop = None):
112118
result = data['result']
113119
if type(result) == str:
114120
raise KarcherHomeException(-2, 'Invalid response: ' + result)
115-
if prop != None:
121+
if prop is not None:
116122
return json.loads(decrypt(result[prop]))
117123
return result
118124

@@ -128,7 +134,7 @@ def get_urls(self):
128134
d = self._process_response(resp, 'domain')
129135
return Domains(**d)
130136

131-
def login(self, username, password, register_id = None):
137+
def login(self, username, password, register_id=None):
132138
"""Login using provided credentials."""
133139

134140
if register_id is None or register_id == '':
@@ -164,14 +170,15 @@ def login(self, username, password, register_id = None):
164170

165171
def logout(self, sess: Session):
166172
"""End current session.
167-
173+
168174
This will also reset the session object.
169175
"""
170176
if sess.auth_token == '' or sess.user_id == '':
171177
sess.reset()
172178
return
173-
174-
self._process_response(self._request(sess, 'POST', '/user-center/auth/logout'))
179+
180+
self._process_response(self._request(
181+
sess, 'POST', '/user-center/auth/logout'))
175182
sess.reset()
176183

177184
def get_devices(self, sess: Session):
@@ -180,21 +187,26 @@ def get_devices(self, sess: Session):
180187
if sess is None or sess.auth_token == '' or sess.user_id == '':
181188
raise KarcherHomeAccessDenied('Not authorized')
182189

183-
resp = self._request(sess, 'GET', '/smart-home-service/smartHome/user/getDeviceInfoByUserId/' + sess.user_id)
190+
resp = self._request(
191+
sess, 'GET',
192+
'/smart-home-service/smartHome/user/getDeviceInfoByUserId/' + sess.user_id)
184193

185194
return [Device(**d) for d in self._process_response(resp)]
186195

187196
def get_map_data(self, sess: Session, dev: Device, map: int = 1):
188197
# <tenantId>/<modeType>/<deviceSn>/01-01-2022/map/temp/0046690461_<deviceSn>_1
189198
mapDir = TENANT_ID + '/' + dev.product_mode_code + '/' +\
190-
dev.sn + '/01-01-2022/map/temp/0046690461_' + dev.sn + '_' + str(map)
191-
192-
resp = self._request(sess, 'POST', '/storage-management/storage/aws/getAccessUrl', json={
193-
'dir': mapDir,
194-
'countryCode': sess.get_country_code(),
195-
'serviceType': 2,
196-
'tenantId': TENANT_ID,
197-
})
199+
dev.sn + '/01-01-2022/map/temp/0046690461_' + \
200+
dev.sn + '_' + str(map)
201+
202+
resp = self._request(sess, 'POST',
203+
'/storage-management/storage/aws/getAccessUrl',
204+
json={
205+
'dir': mapDir,
206+
'countryCode': sess.get_country_code(),
207+
'serviceType': 2,
208+
'tenantId': TENANT_ID,
209+
})
198210

199211
d = self._process_response(resp)
200212
downloadUrl = d['url']
@@ -207,21 +219,3 @@ def get_map_data(self, sess: Session, dev: Device, map: int = 1):
207219
return Map.parse(data)
208220
else:
209221
return json.loads(data)
210-
211-
def get_families(self, sess: Session):
212-
213-
if sess is None or sess.auth_token == '' or sess.user_id == '':
214-
raise KarcherHomeAccessDenied('Not authorized')
215-
216-
resp = self._request(sess, 'GET', '/smart-home-service/smartHome/familyInfo/list/' + sess.user_id)
217-
218-
return self._process_response(resp)
219-
220-
def get_consumables(self, sess: Session, familyID: str):
221-
222-
if sess is None or sess.auth_token == '' or sess.user_id == '':
223-
raise KarcherHomeAccessDenied('Not authorized')
224-
225-
resp = self._request(sess, 'GET', '/smart-home-service/smartHome/consumablesInfo/getConsumablesInfoByFamilyId/' + familyID)
226-
227-
return self._process_response(resp)

karcher/map.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
# -----------------------------------------------------------
2+
# Copyright (c) 2023 Lauris BH
3+
# SPDX-License-Identifier: MIT
4+
# -----------------------------------------------------------
5+
16
from google.protobuf.json_format import MessageToDict
27

38
from . import mapdata_pb2
49
from .utils import snake_case, snake_case_fields
510

11+
612
class Map:
713
@staticmethod
814
def parse(data: bytes):

karcher/utils.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,44 @@
1414

1515
from .consts import TENANT_ID, Product
1616

17+
18+
EMAIL_REGEX = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"
19+
1720
def get_random_string(length):
1821
letters = string.digits + string.ascii_lowercase + string.ascii_uppercase
1922
result_str = ''.join(random.choice(letters) for i in range(length))
2023
return result_str
2124

25+
2226
def get_nonce():
2327
return get_random_string(32)
2428

29+
2530
def get_timestamp():
2631
return int(time.time())
2732

33+
2834
def get_enc_key():
2935
m = hashlib.md5()
3036
m.update(bytes(TENANT_ID, 'utf-8'))
3137
h = m.hexdigest()
3238
return bytes(h[8:24], 'utf-8')
3339

40+
3441
def decrypt(data):
3542
cipher = AES.new(get_enc_key(), AES.MODE_ECB)
3643
buf = cipher.decrypt(base64.b64decode(data))
3744
return str(buf[:-ord(buf[-1:])], 'utf-8')
3845

46+
3947
def encrypt(data):
4048
cipher = AES.new(get_enc_key(), AES.MODE_ECB)
4149
buf = bytes(data, 'utf-8')
4250
pad_len = cipher.block_size - (len(buf) % cipher.block_size)
4351
buf = buf + bytes([pad_len]) * pad_len
4452
return base64.b64encode(cipher.encrypt(buf)).decode()
4553

54+
4655
def get_map_enc_key(sn: str, mac: str, product_id: Product):
4756
sub_key = mac.replace(':', '').lower() + str(product_id.value)
4857
cipher = AES.new(bytes(sub_key[0:16], 'utf-8'), AES.MODE_ECB)
@@ -57,6 +66,7 @@ def get_map_enc_key(sn: str, mac: str, product_id: Product):
5766
h = m.hexdigest()
5867
return bytes(h[8:24], 'utf-8')
5968

69+
6070
def decrypt_map(sn: str, mac: str, product_id: Product, data: bytes):
6171
cipher = AES.new(get_map_enc_key(sn, mac, product_id), AES.MODE_ECB)
6272
buf = cipher.decrypt(base64.b64decode(data))
@@ -65,18 +75,22 @@ def decrypt_map(sn: str, mac: str, product_id: Product, data: bytes):
6575
except Exception:
6676
return bytes.fromhex(str(buf[:-ord(buf[-1:])], 'utf-8'))
6777

78+
6879
def md5(data):
6980
m = hashlib.md5()
7081
m.update(bytes(data, 'utf-8'))
7182
return m.hexdigest()
7283

84+
7385
def is_email(email):
74-
return re.search("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$", email) is not None
86+
return re.search(EMAIL_REGEX, email) is not None
87+
7588

7689
def snake_case(value):
7790
first_underscore = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', value)
7891
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', first_underscore).lower()
7992

93+
8094
def snake_case_fields(data):
8195
if type(data) is dict:
8296
n = {}

ruff.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
extend-exclude = [
22
"*_pb2.py*"
33
]
4+
5+
[per-file-ignores]
6+
"tests/test_*.py" = ["E501"]

0 commit comments

Comments
 (0)