mijiaAPI 2.0.0__tar.gz → 2.0.1__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.
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/PKG-INFO +1 -1
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/mijiaAPI/__main__.py +24 -24
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/mijiaAPI/apis.py +24 -15
- mijiaapi-2.0.0/mijiaAPI/urls.py → mijiaapi-2.0.1/mijiaAPI/consts.py +2 -0
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/mijiaAPI/devices.py +40 -40
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/mijiaAPI/login.py +21 -22
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/mijiaAPI/utils.py +2 -3
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/pyproject.toml +2 -2
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/LICENSE +0 -0
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/README.md +0 -0
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/mijiaAPI/__init__.py +0 -0
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/mijiaAPI/code.py +0 -0
- {mijiaapi-2.0.0 → mijiaapi-2.0.1}/mijiaAPI/logger.py +0 -0
|
@@ -133,7 +133,7 @@ def init_api(auth_path: str) -> mijiaAPI:
|
|
|
133
133
|
auth = json.load(f)
|
|
134
134
|
api = mijiaAPI(auth_data=auth)
|
|
135
135
|
if not api.available:
|
|
136
|
-
raise ValueError("
|
|
136
|
+
raise ValueError("认证信息已过期")
|
|
137
137
|
except (json.JSONDecodeError, ValueError):
|
|
138
138
|
api = mijiaLogin(save_path=auth_path)
|
|
139
139
|
auth = api.QRlogin()
|
|
@@ -147,7 +147,7 @@ def init_api(auth_path: str) -> mijiaAPI:
|
|
|
147
147
|
def get_devices_list(api: mijiaAPI, verbose: bool = True) -> dict:
|
|
148
148
|
devices = api.get_devices_list()
|
|
149
149
|
if verbose:
|
|
150
|
-
print("
|
|
150
|
+
print("设备列表:")
|
|
151
151
|
for device in devices:
|
|
152
152
|
print(f" - {device['name']}\n"
|
|
153
153
|
f" did: {device['did']}\n"
|
|
@@ -162,14 +162,14 @@ def get_homes_list(api: mijiaAPI, verbose: bool = True, device_mapping: Optional
|
|
|
162
162
|
device_mapping = get_devices_list(api, verbose=False)
|
|
163
163
|
homes = api.get_homes_list()
|
|
164
164
|
if verbose:
|
|
165
|
-
print("
|
|
165
|
+
print("家庭列表:")
|
|
166
166
|
for home in homes:
|
|
167
167
|
print(f" - {home['name']}\n"
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
print("
|
|
168
|
+
f" ID: {home['id']}\n"
|
|
169
|
+
f" 地址: {home['address']}\n"
|
|
170
|
+
f" 房间数量: {len(home['roomlist'])}\n"
|
|
171
|
+
f" 创建时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(home['create_time']))}")
|
|
172
|
+
print( " 房间列表:")
|
|
173
173
|
for room in home['roomlist']:
|
|
174
174
|
devices_name = []
|
|
175
175
|
if room['dids']:
|
|
@@ -180,9 +180,9 @@ def get_homes_list(api: mijiaAPI, verbose: bool = True, device_mapping: Optional
|
|
|
180
180
|
devices_name.append(did)
|
|
181
181
|
dids = ', '.join(devices_name)
|
|
182
182
|
print(f" - {room['name']}\n"
|
|
183
|
-
f"
|
|
184
|
-
f"
|
|
185
|
-
f"
|
|
183
|
+
f" ID: {room['id']}\n"
|
|
184
|
+
f" 设备列表: {dids}\n"
|
|
185
|
+
f" 创建时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(room['create_time']))}")
|
|
186
186
|
home_mapping = {home['id']: home for home in homes}
|
|
187
187
|
return home_mapping
|
|
188
188
|
|
|
@@ -193,11 +193,11 @@ def get_scenes_list(api: mijiaAPI, verbose: bool = True, home_mapping: Optional[
|
|
|
193
193
|
for home_id, home in home_mapping.items():
|
|
194
194
|
scenes = api.get_scenes_list(home_id)
|
|
195
195
|
if scenes and verbose:
|
|
196
|
-
print(f"
|
|
196
|
+
print(f"{home['name']} ({home_id}) 中的场景:")
|
|
197
197
|
for scene in scenes:
|
|
198
198
|
print(f" - {scene['name']}\n"
|
|
199
|
-
f"
|
|
200
|
-
f"
|
|
199
|
+
f" ID: {scene['scene_id']}\n"
|
|
200
|
+
f" 创建时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(scene['create_time'])))}\n"
|
|
201
201
|
f" update time: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(scene['update_time'])))}")
|
|
202
202
|
scene_mapping.update({scene['scene_id']: scene for scene in scenes})
|
|
203
203
|
return scene_mapping
|
|
@@ -207,11 +207,11 @@ def get_consumable_items(api: mijiaAPI, home_mapping: Optional[dict] = None):
|
|
|
207
207
|
home_mapping = get_homes_list(api, verbose=False)
|
|
208
208
|
for home_id, home in home_mapping.items():
|
|
209
209
|
items = api.get_consumable_items(home_id, home['uid'])
|
|
210
|
-
print(f"
|
|
210
|
+
print(f"{home['name']} ({home_id}) 中的耗材:")
|
|
211
211
|
for item in items:
|
|
212
212
|
for consumes_data in item['consumes_data']:
|
|
213
|
-
print(f" - {consumes_data['details'][0]['description']}
|
|
214
|
-
|
|
213
|
+
print(f" - {consumes_data['details'][0]['description']} 在 {consumes_data['name']}({consumes_data['did']})\n"
|
|
214
|
+
f" 值: {consumes_data['details'][0]['value']}")
|
|
215
215
|
|
|
216
216
|
def run_scene(api: mijiaAPI, scene_id: str, scene_mapping: Optional[dict] = None) -> bool:
|
|
217
217
|
if scene_mapping is None:
|
|
@@ -225,15 +225,15 @@ def run_scene(api: mijiaAPI, scene_id: str, scene_mapping: Optional[dict] = None
|
|
|
225
225
|
found = True
|
|
226
226
|
break
|
|
227
227
|
if not found:
|
|
228
|
-
print(f"
|
|
228
|
+
print(f"场景 {scene_name_to_find} 未找到")
|
|
229
229
|
return False
|
|
230
230
|
scene_name = scene_mapping[scene_id]['name']
|
|
231
231
|
ret = api.run_scene(scene_id)
|
|
232
232
|
if ret:
|
|
233
|
-
print(f"
|
|
233
|
+
print(f"场景 {scene_name}({scene_id}) 运行成功")
|
|
234
234
|
return True
|
|
235
235
|
else:
|
|
236
|
-
print(f"
|
|
236
|
+
print(f"运行场景 {scene_name}({scene_id}) 失败")
|
|
237
237
|
return False
|
|
238
238
|
|
|
239
239
|
def get(args):
|
|
@@ -241,7 +241,7 @@ def get(args):
|
|
|
241
241
|
device = mijiaDevice(api, dev_name=args.dev_name)
|
|
242
242
|
value = device.get(args.prop_name)
|
|
243
243
|
unit = device.prop_list[args.prop_name].unit
|
|
244
|
-
print(f"
|
|
244
|
+
print(f"{args.dev_name} 的 {args.prop_name} 值为 {value} {unit if unit else ''}")
|
|
245
245
|
|
|
246
246
|
def set(args):
|
|
247
247
|
api = init_api(args.auth_path)
|
|
@@ -249,9 +249,9 @@ def set(args):
|
|
|
249
249
|
ret = device.set(args.prop_name, args.value)
|
|
250
250
|
unit = device.prop_list[args.prop_name].unit
|
|
251
251
|
if ret:
|
|
252
|
-
print(f"
|
|
252
|
+
print(f"{args.dev_name} 的 {args.prop_name} 值已设置为 {args.value} {unit if unit else ''}")
|
|
253
253
|
else:
|
|
254
|
-
print(f"
|
|
254
|
+
print(f"设置 {args.dev_name} 的 {args.prop_name} 值为 {args.value} 失败")
|
|
255
255
|
|
|
256
256
|
|
|
257
257
|
def main(args):
|
|
@@ -295,7 +295,7 @@ def main(args):
|
|
|
295
295
|
wifispeaker = mijiaDevice(api, dev_name=device['name'])
|
|
296
296
|
break
|
|
297
297
|
if wifispeaker is None:
|
|
298
|
-
raise ValueError("
|
|
298
|
+
raise ValueError("未找到小爱音箱设备")
|
|
299
299
|
else:
|
|
300
300
|
wifispeaker = mijiaDevice(api, dev_name=args.wifispeaker_name)
|
|
301
301
|
wifispeaker.run_action('execute-text-directive', _in=[args.run, args.quiet])
|
|
@@ -4,7 +4,8 @@ from typing import Union, Optional
|
|
|
4
4
|
import requests
|
|
5
5
|
import requests.cookies
|
|
6
6
|
|
|
7
|
-
from .
|
|
7
|
+
from .consts import defaultUA
|
|
8
|
+
from .utils import post_data
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class mijiaAPI(object):
|
|
@@ -19,7 +20,7 @@ class mijiaAPI(object):
|
|
|
19
20
|
Exception: 当授权数据不完整时抛出异常。
|
|
20
21
|
"""
|
|
21
22
|
if any(k not in auth_data for k in ['userId', 'deviceId', 'ssecurity', 'serviceToken']):
|
|
22
|
-
raise Exception('
|
|
23
|
+
raise Exception('授权数据无效')
|
|
23
24
|
self.userId = auth_data['userId']
|
|
24
25
|
self.ssecurity = auth_data['ssecurity']
|
|
25
26
|
self.session = requests.Session()
|
|
@@ -35,7 +36,7 @@ class mijiaAPI(object):
|
|
|
35
36
|
@staticmethod
|
|
36
37
|
def _post_process(data: dict) -> Union[list, bool]:
|
|
37
38
|
if data['code'] != 0:
|
|
38
|
-
raise Exception(f'
|
|
39
|
+
raise Exception(f'获取数据失败, {data["message"]}')
|
|
39
40
|
return data['result']
|
|
40
41
|
|
|
41
42
|
@property
|
|
@@ -64,18 +65,26 @@ class mijiaAPI(object):
|
|
|
64
65
|
home_list = self.get_homes_list()
|
|
65
66
|
devices = []
|
|
66
67
|
for home in home_list:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
start_did = ''
|
|
69
|
+
has_more = True
|
|
70
|
+
while has_more:
|
|
71
|
+
data = {
|
|
72
|
+
"home_owner": home['uid'],
|
|
73
|
+
"home_id": int(home['id']),
|
|
74
|
+
"limit": 200,
|
|
75
|
+
"start_did": start_did,
|
|
76
|
+
"get_split_device": True,
|
|
77
|
+
"support_smart_home": True,
|
|
78
|
+
"get_cariot_device": True,
|
|
79
|
+
"get_third_device": True
|
|
80
|
+
}
|
|
81
|
+
ret = self._post_process(post_data(self.session, self.ssecurity, uri, data))
|
|
82
|
+
if ret and ret.get('device_info'):
|
|
83
|
+
devices.extend(ret['device_info'])
|
|
84
|
+
start_did = ret.get('max_did', '')
|
|
85
|
+
has_more = ret.get('has_more', False) and start_did != ''
|
|
86
|
+
else:
|
|
87
|
+
has_more = False
|
|
79
88
|
return devices
|
|
80
89
|
|
|
81
90
|
def get_homes_list(self) -> list:
|
|
@@ -5,3 +5,5 @@ qrURL = 'https://account.xiaomi.com/longPolling/loginUrl'
|
|
|
5
5
|
apiURL = 'https://api.io.mi.com/app'
|
|
6
6
|
deviceURL = 'https://home.miot-spec.com/spec/'
|
|
7
7
|
accountURL = 'https://account.xiaomi.com/pass2/profile/home?bizFlag=&userId='
|
|
8
|
+
|
|
9
|
+
defaultUA = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36 Edg/126.0.0.0'
|
|
@@ -6,7 +6,7 @@ import requests
|
|
|
6
6
|
from time import sleep
|
|
7
7
|
from .apis import mijiaAPI
|
|
8
8
|
from .code import ERROR_CODE
|
|
9
|
-
from .
|
|
9
|
+
from .consts import deviceURL
|
|
10
10
|
from .logger import get_logger
|
|
11
11
|
|
|
12
12
|
logger = get_logger(__name__)
|
|
@@ -26,7 +26,7 @@ class DevProp(object):
|
|
|
26
26
|
self.desc = prop_dict['description']
|
|
27
27
|
self.type = prop_dict['type']
|
|
28
28
|
if self.type not in ['bool', 'int', 'uint', 'float', 'string']:
|
|
29
|
-
raise ValueError(f'
|
|
29
|
+
raise ValueError(f'不支持的类型: {self.type}, 可选类型: bool, int, uint, float, string')
|
|
30
30
|
self.rw = prop_dict['rw']
|
|
31
31
|
self.unit = prop_dict['unit']
|
|
32
32
|
self.range = prop_dict['range']
|
|
@@ -106,18 +106,18 @@ class mijiaDevice(object):
|
|
|
106
106
|
- 如果只提供了dev_info,则直接使用该信息。
|
|
107
107
|
"""
|
|
108
108
|
if dev_info is None and dev_name is None:
|
|
109
|
-
raise RuntimeError("
|
|
109
|
+
raise RuntimeError("必须提供 'dev_info' 或 'dev_name' 中的一个参数。")
|
|
110
110
|
if dev_info is not None and dev_name is not None:
|
|
111
|
-
logger.warning("
|
|
111
|
+
logger.warning("同时提供了 'dev_info' 和 'dev_name'。将使用 'dev_info' 进行初始化。")
|
|
112
112
|
|
|
113
113
|
self.api = api
|
|
114
114
|
if dev_info is None:
|
|
115
115
|
devices_list = self.api.get_devices_list()
|
|
116
116
|
matches = [device for device in devices_list if device['name'] == dev_name]
|
|
117
117
|
if not matches:
|
|
118
|
-
raise ValueError(f"
|
|
118
|
+
raise ValueError(f"未找到设备 {dev_name}")
|
|
119
119
|
elif len(matches) > 1:
|
|
120
|
-
raise ValueError(f"
|
|
120
|
+
raise ValueError(f"找到多个名为 {dev_name} 的设备")
|
|
121
121
|
else:
|
|
122
122
|
dev_info = get_device_info(matches[0]['model'])
|
|
123
123
|
did = matches[0]['did']
|
|
@@ -171,15 +171,15 @@ class mijiaDevice(object):
|
|
|
171
171
|
if did is None:
|
|
172
172
|
did = self.did
|
|
173
173
|
if did is None:
|
|
174
|
-
raise ValueError('
|
|
174
|
+
raise ValueError('请指定设备ID (did)')
|
|
175
175
|
if name not in self.prop_list:
|
|
176
|
-
raise ValueError(f'
|
|
176
|
+
raise ValueError(f'不支持的属性: {name}, 可用属性: {list(self.prop_list.keys())}')
|
|
177
177
|
prop = self.prop_list[name]
|
|
178
178
|
if 'w' not in prop.rw:
|
|
179
|
-
raise ValueError(f'
|
|
179
|
+
raise ValueError(f'属性 {name} 不可写入')
|
|
180
180
|
if prop.value_list:
|
|
181
181
|
if value not in [item['value'] for item in prop.value_list]:
|
|
182
|
-
raise ValueError(f'
|
|
182
|
+
raise ValueError(f'无效值: {value}, 请使用 {prop.value_list}')
|
|
183
183
|
if prop.type == 'bool':
|
|
184
184
|
if isinstance(value, str):
|
|
185
185
|
if value.lower() == 'true':
|
|
@@ -189,51 +189,51 @@ class mijiaDevice(object):
|
|
|
189
189
|
elif value in ['0', '1']:
|
|
190
190
|
value = bool(int(value))
|
|
191
191
|
else:
|
|
192
|
-
raise ValueError(f'
|
|
192
|
+
raise ValueError(f'无效布尔值: {value}')
|
|
193
193
|
elif isinstance(value, int):
|
|
194
194
|
if value == 0:
|
|
195
195
|
value = False
|
|
196
196
|
elif value == 1:
|
|
197
197
|
value = True
|
|
198
198
|
else:
|
|
199
|
-
raise ValueError(f'
|
|
199
|
+
raise ValueError(f'无效布尔值: {value}')
|
|
200
200
|
elif not isinstance(value, bool):
|
|
201
|
-
raise ValueError(f'
|
|
201
|
+
raise ValueError(f'无效布尔值: {value}')
|
|
202
202
|
elif prop.type in ['int', 'uint']:
|
|
203
203
|
value = int(value)
|
|
204
204
|
if prop.range:
|
|
205
205
|
if value < prop.range[0] or value > prop.range[1]:
|
|
206
|
-
raise ValueError(f'
|
|
206
|
+
raise ValueError(f'{value} 超出数值范围, 应该在 {prop.range[:2]} 之间')
|
|
207
207
|
if len(prop.range) >= 3 and prop.range[2] != 1:
|
|
208
208
|
if (value - prop.range[0]) % prop.range[2] != 0:
|
|
209
209
|
raise ValueError(
|
|
210
|
-
f'
|
|
210
|
+
f'无效的值: {value}, 应该在范围 {prop.range[:2]} 内且步长为 {prop.range[2]}')
|
|
211
211
|
elif prop.type == 'float':
|
|
212
212
|
value = float(value)
|
|
213
213
|
if prop.range:
|
|
214
214
|
if value < prop.range[0] or value > prop.range[1]:
|
|
215
|
-
raise ValueError(f'
|
|
215
|
+
raise ValueError(f'{value} 超出数值范围, 应该在 {prop.range[:2]} 之间')
|
|
216
216
|
if len(prop.range) >= 3 and isinstance(prop.range[2], int):
|
|
217
217
|
if int(value - prop.range[0]) % prop.range[2] != 0:
|
|
218
218
|
raise ValueError(
|
|
219
|
-
f'
|
|
219
|
+
f'无效的值: {value}, 应该在范围 {prop.range[:2]} 内且步长为 {prop.range[2]}')
|
|
220
220
|
elif prop.type == 'string':
|
|
221
221
|
if not isinstance(value, str):
|
|
222
|
-
raise ValueError(f'
|
|
222
|
+
raise ValueError(f'无效字符串值: {value}')
|
|
223
223
|
else:
|
|
224
|
-
raise ValueError(f'
|
|
224
|
+
raise ValueError(f'不支持的类型: {prop.type}, 可用类型: bool, int, uint, float, string')
|
|
225
225
|
method = prop.method.copy()
|
|
226
226
|
method['did'] = did
|
|
227
227
|
method['value'] = value
|
|
228
228
|
result = self.api.set_devices_prop([method])[0]
|
|
229
229
|
if result['code'] != 0:
|
|
230
230
|
raise RuntimeError(
|
|
231
|
-
f"
|
|
232
|
-
f"
|
|
233
|
-
f"
|
|
231
|
+
f"设置属性 {name} 失败, "
|
|
232
|
+
f"错误码: {result['code']}, "
|
|
233
|
+
f"错误信息: {ERROR_CODE.get(str(result['code']), '未知错误')}"
|
|
234
234
|
)
|
|
235
235
|
sleep(self.sleep_time)
|
|
236
|
-
logger.debug(f"
|
|
236
|
+
logger.debug(f"设置属性: {self.name} -> {name}, 值: {value}, 结果: {result}")
|
|
237
237
|
return result['code'] == 0
|
|
238
238
|
|
|
239
239
|
def get(self, name: str, did: Optional[str] = None) -> Union[bool, int, float, str]:
|
|
@@ -254,23 +254,23 @@ class mijiaDevice(object):
|
|
|
254
254
|
if did is None:
|
|
255
255
|
did = self.did
|
|
256
256
|
if did is None:
|
|
257
|
-
raise ValueError('
|
|
257
|
+
raise ValueError('请指定设备ID (did)')
|
|
258
258
|
if name not in self.prop_list:
|
|
259
|
-
raise ValueError(f'
|
|
259
|
+
raise ValueError(f'不支持的属性: {name}, 可用属性: {list(self.prop_list.keys())}')
|
|
260
260
|
prop = self.prop_list[name]
|
|
261
261
|
if 'r' not in prop.rw:
|
|
262
|
-
raise ValueError(f'
|
|
262
|
+
raise ValueError(f'属性 {name} 不可读取')
|
|
263
263
|
method = prop.method.copy()
|
|
264
264
|
method['did'] = did
|
|
265
265
|
result = self.api.get_devices_prop([method])[0]
|
|
266
266
|
if result['code'] != 0:
|
|
267
267
|
raise RuntimeError(
|
|
268
|
-
f"
|
|
269
|
-
f"
|
|
270
|
-
f"
|
|
268
|
+
f"获取属性 {name} 失败, "
|
|
269
|
+
f"错误码: {result['code']}, "
|
|
270
|
+
f"错误信息: {ERROR_CODE.get(str(result['code']), '未知错误')}"
|
|
271
271
|
)
|
|
272
272
|
sleep(self.sleep_time)
|
|
273
|
-
logger.debug(f"
|
|
273
|
+
logger.debug(f"获取属性: {self.name} -> {name}, 结果: {result}")
|
|
274
274
|
return result['value']
|
|
275
275
|
|
|
276
276
|
def __setattr__(self, name: str, value: Union[bool, int, float, str]) -> None:
|
|
@@ -286,7 +286,7 @@ class mijiaDevice(object):
|
|
|
286
286
|
"""
|
|
287
287
|
if 'prop_list' in self.__dict__ and name in self.prop_list:
|
|
288
288
|
if not self.set(name, value):
|
|
289
|
-
raise RuntimeError(f'
|
|
289
|
+
raise RuntimeError(f'设置属性 {name} 失败')
|
|
290
290
|
else:
|
|
291
291
|
super().__setattr__(name, value)
|
|
292
292
|
|
|
@@ -331,9 +331,9 @@ class mijiaDevice(object):
|
|
|
331
331
|
if did is None:
|
|
332
332
|
did = self.did
|
|
333
333
|
if did is None:
|
|
334
|
-
raise ValueError('
|
|
334
|
+
raise ValueError('请指定设备ID (did)')
|
|
335
335
|
if name not in self.action_list:
|
|
336
|
-
raise ValueError(f'
|
|
336
|
+
raise ValueError(f'不支持的动作: {name}, 可用动作: {list(self.action_list.keys())}')
|
|
337
337
|
act = self.action_list[name]
|
|
338
338
|
method = act.method.copy()
|
|
339
339
|
method['did'] = did
|
|
@@ -344,17 +344,17 @@ class mijiaDevice(object):
|
|
|
344
344
|
if k.startswith("_"):
|
|
345
345
|
k = k[1:]
|
|
346
346
|
if k in method:
|
|
347
|
-
raise ValueError(f'
|
|
347
|
+
raise ValueError(f'无效的参数: {k}. 请勿使用以下参数 ({", ".join(method.keys())})')
|
|
348
348
|
method[k] = v
|
|
349
349
|
result = self.api.run_action(method)
|
|
350
350
|
if result['code'] != 0:
|
|
351
351
|
raise RuntimeError(
|
|
352
|
-
f"
|
|
353
|
-
f"
|
|
354
|
-
f"
|
|
352
|
+
f"执行动作 {name} 失败, "
|
|
353
|
+
f"错误码: {result['code']}, "
|
|
354
|
+
f"错误信息: {ERROR_CODE.get(str(result['code']), '未知错误')}"
|
|
355
355
|
)
|
|
356
356
|
sleep(self.sleep_time)
|
|
357
|
-
logger.debug(f"
|
|
357
|
+
logger.debug(f"执行动作: {self.name} -> {name}, 结果: {result}")
|
|
358
358
|
return result['code'] == 0
|
|
359
359
|
|
|
360
360
|
|
|
@@ -379,10 +379,10 @@ def get_device_info(device_model: str, cache_path: Optional[str] = os.path.join(
|
|
|
379
379
|
return json.load(f)
|
|
380
380
|
response = requests.get(deviceURL + device_model)
|
|
381
381
|
if response.status_code != 200:
|
|
382
|
-
raise RuntimeError(f'
|
|
382
|
+
raise RuntimeError(f'获取设备信息失败')
|
|
383
383
|
content = re.search(r'data-page="(.*?)">', response.text)
|
|
384
384
|
if content is None:
|
|
385
|
-
raise RuntimeError(f'
|
|
385
|
+
raise RuntimeError(f'获取设备信息失败')
|
|
386
386
|
content = content.group(1)
|
|
387
387
|
content = json.loads(content.replace('"', '"'))
|
|
388
388
|
|
|
@@ -13,8 +13,7 @@ import requests
|
|
|
13
13
|
from qrcode import QRCode
|
|
14
14
|
|
|
15
15
|
from .logger import get_logger
|
|
16
|
-
from .
|
|
17
|
-
from .utils import defaultUA
|
|
16
|
+
from .consts import msgURL, loginURL, qrURL, accountURL, defaultUA
|
|
18
17
|
|
|
19
18
|
logger = get_logger(__name__)
|
|
20
19
|
|
|
@@ -65,7 +64,7 @@ class mijiaLogin(object):
|
|
|
65
64
|
"""
|
|
66
65
|
ret = self.session.get(msgURL)
|
|
67
66
|
if ret.status_code != 200:
|
|
68
|
-
raise LoginError(ret.status_code, f'
|
|
67
|
+
raise LoginError(ret.status_code, f'获取索引页失败, {ret.text}')
|
|
69
68
|
ret_data = json.loads(ret.text[11:])
|
|
70
69
|
data = {'deviceId': self.deviceId}
|
|
71
70
|
data.update({
|
|
@@ -90,7 +89,7 @@ class mijiaLogin(object):
|
|
|
90
89
|
try:
|
|
91
90
|
ret = self.session.get(accountURL + str(user_id))
|
|
92
91
|
if ret.status_code != 200:
|
|
93
|
-
raise LoginError(ret.status_code, f'
|
|
92
|
+
raise LoginError(ret.status_code, f'获取账户页面失败, {ret.text}')
|
|
94
93
|
data = json.loads(ret.text[11:])['data']
|
|
95
94
|
except (KeyError, json.JSONDecodeError) as e:
|
|
96
95
|
data = {}
|
|
@@ -117,7 +116,7 @@ class mijiaLogin(object):
|
|
|
117
116
|
]
|
|
118
117
|
|
|
119
118
|
if not gmt_time_keys:
|
|
120
|
-
raise LoginError(-1, '
|
|
119
|
+
raise LoginError(-1, '在cookie中未找到GMT时间键')
|
|
121
120
|
parsed_times = [datetime.strptime(k, '%d-%b-%Y %H:%M:%S GMT') for k in gmt_time_keys]
|
|
122
121
|
latest_utc_time = max(parsed_times)
|
|
123
122
|
china_time = latest_utc_time + timedelta(hours=8)
|
|
@@ -136,14 +135,14 @@ class mijiaLogin(object):
|
|
|
136
135
|
if not os.path.isabs(self.save_path):
|
|
137
136
|
self.save_path = os.path.abspath(self.save_path)
|
|
138
137
|
if os.path.exists(self.save_path) and not os.path.isfile(self.save_path):
|
|
139
|
-
raise ValueError(f'
|
|
138
|
+
raise ValueError(f'[{self.save_path}] 不是文件')
|
|
140
139
|
if not os.path.exists(os.path.dirname(self.save_path)):
|
|
141
140
|
os.makedirs(os.path.dirname(self.save_path))
|
|
142
141
|
with open(self.save_path, 'w') as f:
|
|
143
142
|
json.dump(self.auth_data, f, indent=2)
|
|
144
|
-
logger.info(f'
|
|
143
|
+
logger.info(f'认证文件已保存到 [{self.save_path}]')
|
|
145
144
|
else:
|
|
146
|
-
logger.info('
|
|
145
|
+
logger.info('认证文件未保存')
|
|
147
146
|
|
|
148
147
|
def login(self, username: str, password: str) -> dict:
|
|
149
148
|
"""
|
|
@@ -159,7 +158,7 @@ class mijiaLogin(object):
|
|
|
159
158
|
Raises:
|
|
160
159
|
LoginError: 登录失败时抛出。
|
|
161
160
|
"""
|
|
162
|
-
logger.warning('
|
|
161
|
+
logger.warning('使用账号密码登录很可能需要验证码。请尝试使用 `QRlogin` 方法。')
|
|
163
162
|
data = self._get_index()
|
|
164
163
|
post_data = {
|
|
165
164
|
'qs': data['qs'],
|
|
@@ -172,17 +171,17 @@ class mijiaLogin(object):
|
|
|
172
171
|
}
|
|
173
172
|
ret = self.session.post(loginURL, data=post_data)
|
|
174
173
|
if ret.status_code != 200:
|
|
175
|
-
raise LoginError(ret.status_code, f'
|
|
174
|
+
raise LoginError(ret.status_code, f'登录页面提交失败, {ret.text}')
|
|
176
175
|
ret_data = json.loads(ret.text[11:])
|
|
177
176
|
if ret_data['code'] != 0:
|
|
178
177
|
raise LoginError(ret_data['code'], ret_data['desc'])
|
|
179
178
|
if 'location' not in ret_data:
|
|
180
|
-
raise LoginError(-1, '
|
|
179
|
+
raise LoginError(-1, '获取跳转位置失败')
|
|
181
180
|
if 'notificationUrl' in ret_data:
|
|
182
|
-
raise LoginError(-1, '
|
|
181
|
+
raise LoginError(-1, '需要验证码,请尝试使用 `QRlogin` 方法')
|
|
183
182
|
ret = self.session.get(ret_data['location'])
|
|
184
183
|
if ret.status_code != 200:
|
|
185
|
-
raise LoginError(ret.status_code, f'
|
|
184
|
+
raise LoginError(ret.status_code, f'获取跳转位置失败, {ret.text}')
|
|
186
185
|
cookies = self.session.cookies.get_dict()
|
|
187
186
|
|
|
188
187
|
self.auth_data = {
|
|
@@ -207,7 +206,7 @@ class mijiaLogin(object):
|
|
|
207
206
|
loginurl (str): 包含登录信息的URL。
|
|
208
207
|
box_size (int, optional): 二维码大小。默认为10。
|
|
209
208
|
"""
|
|
210
|
-
logger.info('
|
|
209
|
+
logger.info('请使用米家APP扫描下方二维码')
|
|
211
210
|
qr = QRCode(border=1, box_size=box_size)
|
|
212
211
|
qr.add_data(loginurl)
|
|
213
212
|
qr.make_image().save('qr.png')
|
|
@@ -215,10 +214,10 @@ class mijiaLogin(object):
|
|
|
215
214
|
qr.print_ascii(invert=True, tty=True)
|
|
216
215
|
except OSError:
|
|
217
216
|
qr.print_ascii(invert=True, tty=False)
|
|
218
|
-
logger.info('
|
|
219
|
-
'
|
|
220
|
-
'
|
|
221
|
-
'
|
|
217
|
+
logger.info('如果无法扫描二维码,'
|
|
218
|
+
'请更改终端字体,'
|
|
219
|
+
'如"Maple Mono"、"Fira Code"等。\n'
|
|
220
|
+
'或者直接使用当前目录下的qr.png文件。')
|
|
222
221
|
|
|
223
222
|
def QRlogin(self) -> dict:
|
|
224
223
|
"""
|
|
@@ -251,7 +250,7 @@ class mijiaLogin(object):
|
|
|
251
250
|
url = qrURL + '?' + parse.urlencode(params)
|
|
252
251
|
ret = self.session.get(url)
|
|
253
252
|
if ret.status_code != 200:
|
|
254
|
-
raise LoginError(ret.status_code, f'
|
|
253
|
+
raise LoginError(ret.status_code, f'获取二维码URL失败, {ret.text}')
|
|
255
254
|
ret_data = json.loads(ret.text[11:])
|
|
256
255
|
if ret_data['code'] != 0:
|
|
257
256
|
raise LoginError(ret_data['code'], ret_data['desc'])
|
|
@@ -260,15 +259,15 @@ class mijiaLogin(object):
|
|
|
260
259
|
try:
|
|
261
260
|
ret = self.session.get(ret_data['lp'], timeout=60, headers={'Connection': 'keep-alive'})
|
|
262
261
|
except requests.exceptions.Timeout:
|
|
263
|
-
raise LoginError(-1, '
|
|
262
|
+
raise LoginError(-1, '超时,请重试')
|
|
264
263
|
if ret.status_code != 200:
|
|
265
|
-
raise LoginError(ret.status_code, f'
|
|
264
|
+
raise LoginError(ret.status_code, f'等待登录失败, {ret.text}')
|
|
266
265
|
ret_data = json.loads(ret.text[11:])
|
|
267
266
|
if ret_data['code'] != 0:
|
|
268
267
|
raise LoginError(ret_data['code'], ret_data['desc'])
|
|
269
268
|
ret = self.session.get(ret_data['location'])
|
|
270
269
|
if ret.status_code != 200:
|
|
271
|
-
raise LoginError(ret.status_code, f'
|
|
270
|
+
raise LoginError(ret.status_code, f'获取跳转位置失败, {ret.text}')
|
|
272
271
|
cookies = self.session.cookies.get_dict()
|
|
273
272
|
|
|
274
273
|
self.auth_data = {
|
|
@@ -6,9 +6,8 @@ import string
|
|
|
6
6
|
|
|
7
7
|
import requests
|
|
8
8
|
|
|
9
|
-
from .
|
|
9
|
+
from .consts import apiURL
|
|
10
10
|
|
|
11
|
-
defaultUA = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36 Edg/126.0.0.0'
|
|
12
11
|
|
|
13
12
|
class PostDataError(Exception):
|
|
14
13
|
def __init__(self, code: int, message: str):
|
|
@@ -38,6 +37,6 @@ def post_data(session: requests.Session, ssecurity: str, uri: str, data: dict) -
|
|
|
38
37
|
post_data = {'_nonce': nonce, 'data': data, 'signature': signature}
|
|
39
38
|
ret = session.post(apiURL + uri, data=post_data)
|
|
40
39
|
if ret.status_code != 200:
|
|
41
|
-
raise PostDataError(ret.status_code, f'
|
|
40
|
+
raise PostDataError(ret.status_code, f'发送数据失败, {ret.text}')
|
|
42
41
|
ret_data = ret.json()
|
|
43
42
|
return ret_data
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mijiaAPI"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.1"
|
|
4
4
|
description = "A Python API for Xiaomi Mijia"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.9,<4.0"
|
|
@@ -15,7 +15,7 @@ mijiaAPI = "mijiaAPI.__main__:cli"
|
|
|
15
15
|
|
|
16
16
|
[tool.poetry]
|
|
17
17
|
name = "mijiaAPI"
|
|
18
|
-
version = "2.0.
|
|
18
|
+
version = "2.0.1"
|
|
19
19
|
description = "A Python API for Xiaomi Mijia"
|
|
20
20
|
authors = ["Do1e <dpj.email@qq.com>"]
|
|
21
21
|
license = "GPLv3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|