pyezvizapi 1.0.3.6__py3-none-any.whl → 1.0.3.7__py3-none-any.whl

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.

Potentially problematic release.


This version of pyezvizapi might be problematic. Click here for more details.

pyezvizapi/__main__.py CHANGED
@@ -421,7 +421,7 @@ def _handle_devices_light(args: argparse.Namespace, client: EzvizClient) -> int:
421
421
 
422
422
  def _handle_pagelist(client: EzvizClient) -> int:
423
423
  """Output full pagelist (raw JSON) for exploration in editors like Notepad++."""
424
- data = client._get_page_list() # noqa: SLF001
424
+ data = client.get_page_list()
425
425
  _write_json(data)
426
426
  return 0
427
427
 
@@ -611,7 +611,7 @@ def main(argv: list[str] | None = None) -> int:
611
611
  return 2
612
612
  finally:
613
613
  if args.save_token and args.token_file:
614
- _save_token_file(args.token_file, cast(dict[str, Any], client._token)) # noqa: SLF001
614
+ _save_token_file(args.token_file, client.export_token())
615
615
  client.close_session()
616
616
 
617
617
 
pyezvizapi/client.py CHANGED
@@ -4398,6 +4398,16 @@ class EzvizClient:
4398
4398
  json_key=None,
4399
4399
  )
4400
4400
 
4401
+ def get_page_list(self) -> Any:
4402
+ """Return the full pagelist payload without filtering."""
4403
+
4404
+ return self._get_page_list()
4405
+
4406
+ def export_token(self) -> dict[str, Any]:
4407
+ """Return a shallow copy of the current authentication token."""
4408
+
4409
+ return dict(self._token)
4410
+
4401
4411
  def get_device(self) -> Any:
4402
4412
  """Get ezviz devices filter."""
4403
4413
  return self._api_get_pagelist(page_filter="CLOUD", json_key="deviceInfos")
pyezvizapi/constants.py CHANGED
@@ -13,6 +13,7 @@ FEATURE_CODE = generate_unique_code()
13
13
  XOR_KEY = b"\x0c\x0eJ^X\x15@Rr"
14
14
  DEFAULT_TIMEOUT = 25
15
15
  MAX_RETRIES = 3
16
+ HIK_ENCRYPTION_HEADER = b"hikencodepicture"
16
17
  REQUEST_HEADER = {
17
18
  "featureCode": FEATURE_CODE,
18
19
  "clientType": "3",
pyezvizapi/feature.py CHANGED
@@ -5,22 +5,18 @@ from __future__ import annotations
5
5
  from collections.abc import Iterable, Iterator, Mapping, MutableMapping
6
6
  from typing import Any, cast
7
7
 
8
- from .utils import coerce_int, decode_json
8
+ from .utils import WILDCARD_STEP, coerce_int, decode_json, first_nested
9
9
 
10
10
 
11
11
  def _feature_video_section(camera_data: Mapping[str, Any]) -> dict[str, Any]:
12
12
  """Return the nested Video feature section from feature info payload."""
13
13
 
14
- feature = camera_data.get("FEATURE_INFO")
15
- if not isinstance(feature, Mapping):
16
- return {}
17
-
18
- for group in feature.values():
19
- if isinstance(group, Mapping):
20
- video = group.get("Video")
21
- if isinstance(video, MutableMapping):
22
- return cast(dict[str, Any], video)
23
-
14
+ video = first_nested(
15
+ camera_data,
16
+ ("FEATURE_INFO", WILDCARD_STEP, "Video"),
17
+ )
18
+ if isinstance(video, MutableMapping):
19
+ return cast(dict[str, Any], video)
24
20
  return {}
25
21
 
26
22
 
pyezvizapi/mqtt.py CHANGED
@@ -247,7 +247,7 @@ class MQTTClient:
247
247
  # Stop background thread and disconnect
248
248
  self.mqtt_client.loop_stop()
249
249
  self.mqtt_client.disconnect()
250
- except Exception as err: # noqa: BLE001
250
+ except (OSError, ValueError, RuntimeError) as err:
251
251
  _LOGGER.debug("MQTT disconnect failed: %s", err)
252
252
  # Always attempt to stop push on server side
253
253
  self._stop_ezviz_push()
pyezvizapi/test_mqtt.py CHANGED
@@ -125,7 +125,7 @@ def main(argv: list[str] | None = None) -> int:
125
125
  print("Stopped.")
126
126
 
127
127
  if args.save_token and args.token_file:
128
- _save_token_file(args.token_file, cast(dict[str, Any], client._token)) # noqa: SLF001
128
+ _save_token_file(args.token_file, client.export_token())
129
129
 
130
130
  return 0
131
131
 
pyezvizapi/utils.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Iterable, Iterator
5
6
  import datetime
6
7
  from hashlib import md5
7
8
  import json
@@ -13,6 +14,7 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
13
14
 
14
15
  from Crypto.Cipher import AES
15
16
 
17
+ from .constants import HIK_ENCRYPTION_HEADER
16
18
  from .exceptions import PyEzvizError
17
19
 
18
20
  _LOGGER = logging.getLogger(__name__)
@@ -74,6 +76,49 @@ def string_to_list(data: Any, separator: str = ",") -> Any:
74
76
  return data
75
77
 
76
78
 
79
+ PathComponent = str | int
80
+ WILDCARD_STEP = "*"
81
+ _MISSING = object()
82
+
83
+
84
+ def iter_nested(data: Any, path: Iterable[PathComponent]) -> Iterator[Any]:
85
+ """Yield values reachable by following a dotted path with optional wildcards."""
86
+
87
+ current: list[Any] = [data]
88
+
89
+ for step in path:
90
+ next_level: list[Any] = []
91
+ for candidate in current:
92
+ if step == WILDCARD_STEP:
93
+ if isinstance(candidate, dict):
94
+ next_level.extend(candidate.values())
95
+ elif isinstance(candidate, (list, tuple)):
96
+ next_level.extend(candidate)
97
+ continue
98
+
99
+ if isinstance(candidate, dict) and step in candidate:
100
+ next_level.append(candidate[step])
101
+ continue
102
+
103
+ if isinstance(candidate, (list, tuple)) and isinstance(step, int):
104
+ if -len(candidate) <= step < len(candidate):
105
+ next_level.append(candidate[step])
106
+
107
+ current = next_level
108
+ if not current:
109
+ break
110
+
111
+ yield from current
112
+
113
+
114
+ def first_nested(
115
+ data: Any, path: Iterable[PathComponent], default: Any = None
116
+ ) -> Any:
117
+ """Return the first value produced by iter_nested or ``default``."""
118
+
119
+ return next(iter_nested(data, path), default)
120
+
121
+
77
122
  def fetch_nested_value(data: Any, keys: list, default_value: Any = None) -> Any:
78
123
  """Fetch the value corresponding to the given nested keys in a dictionary.
79
124
 
@@ -88,14 +133,8 @@ def fetch_nested_value(data: Any, keys: list, default_value: Any = None) -> Any:
88
133
  The value corresponding to the nested keys or the default value.
89
134
 
90
135
  """
91
- try:
92
- for key in keys:
93
- data = data[key]
94
-
95
- except (KeyError, TypeError):
96
- return default_value
97
-
98
- return data
136
+ value = first_nested(data, keys, _MISSING)
137
+ return default_value if value is _MISSING else value
99
138
 
100
139
 
101
140
  def decrypt_image(input_data: bytes, password: str) -> bytes:
@@ -116,8 +155,10 @@ def decrypt_image(input_data: bytes, password: str) -> bytes:
116
155
  raise PyEzvizError("Invalid image data")
117
156
 
118
157
  # check header
119
- if input_data[:16] != b"hikencodepicture":
120
- _LOGGER.debug("Image header doesn't contain 'hikencodepicture'")
158
+ header_len = len(HIK_ENCRYPTION_HEADER)
159
+
160
+ if input_data[:header_len] != HIK_ENCRYPTION_HEADER:
161
+ _LOGGER.debug("Image header doesn't contain %s", HIK_ENCRYPTION_HEADER)
121
162
  return input_data
122
163
 
123
164
  file_hash = input_data[16:48]
@@ -132,7 +173,7 @@ def decrypt_image(input_data: bytes, password: str) -> bytes:
132
173
  next_chunk = b""
133
174
  output_data = b""
134
175
  finished = False
135
- i = 48 # offset hikencodepicture + hash
176
+ i = 48 # offset HIK header + hash
136
177
  chunk_size = 1024 * AES.block_size
137
178
  while not finished:
138
179
  chunk, next_chunk = next_chunk, cipher.decrypt(input_data[i : i + chunk_size])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyezvizapi
3
- Version: 1.0.3.6
3
+ Version: 1.0.3.7
4
4
  Summary: Pilot your Ezviz cameras
5
5
  Home-page: https://github.com/RenierM26/pyEzvizApi/
6
6
  Author: Renier Moorcroft
@@ -0,0 +1,22 @@
1
+ pyezvizapi/__init__.py,sha256=-OxHqxA9h0JZQW__GoZd1aByGquIHawCnNdeNlIg2Qo,3382
2
+ pyezvizapi/__main__.py,sha256=6vFvkh8gCD-Mo4CXd1deZfDeaaHR-Kc8pOu9vkUjQf4,20878
3
+ pyezvizapi/api_endpoints.py,sha256=2M5Vs4YB1VWZGcowT-4Fj2hhRNjFh976LT3jtRrqvrc,5754
4
+ pyezvizapi/camera.py,sha256=Pl5oIEdrFcv1Hz5sQI1IyyJIDCMjOjQdtExgKzmLoK8,22102
5
+ pyezvizapi/cas.py,sha256=3zHe-_a0KchCmGeAj1of-pV6oMPRUmSCIiDqBFsTK8A,6025
6
+ pyezvizapi/client.py,sha256=CKSK7hdQFmJAD0p1GPgEaqw7QAtqyhqLN8soR_VmSeA,142146
7
+ pyezvizapi/constants.py,sha256=4aoo2jKV88quLIPRwRAH0xW7yCuQfsU9tNtSxq7Xnnk,12854
8
+ pyezvizapi/exceptions.py,sha256=8rmxEUQdrziqMe-M1SeeRd0HtP2IDQ2xpJVj7wvOQyo,976
9
+ pyezvizapi/feature.py,sha256=TwQDAVFXqrpe5s0rXyJSqsHCnQsw1MNSTsUGhb61EzI,15886
10
+ pyezvizapi/light_bulb.py,sha256=9wgycG3dTvBbrsxQjQnXal-GA8VXPsIN1m-CTtRh8i0,7797
11
+ pyezvizapi/models.py,sha256=NQzwTP0yEe2IWU-Vc6nAn87xulpTuo0MX2Rcf0WxifA,4176
12
+ pyezvizapi/mqtt.py,sha256=_JHklUsqqwrgKk3k8RyBgo3bp1utEOr5KSBe8Oz92qg,22375
13
+ pyezvizapi/test_cam_rtsp.py,sha256=O9NHh-vcNFfnzNw8jbuhM9a_5TWfNZIMXaJP7Lmkaj4,5162
14
+ pyezvizapi/test_mqtt.py,sha256=-0L621V4nzm6x0oLgurAgkJZopVZ5Os7n1hLVMh3qIc,4117
15
+ pyezvizapi/utils.py,sha256=Gmn6Ljuo7SUSt1nFN-JmuoWAJGKqUYqur4SBESlhjKI,14153
16
+ pyezvizapi-1.0.3.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
+ pyezvizapi-1.0.3.7.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
+ pyezvizapi-1.0.3.7.dist-info/METADATA,sha256=UYwabH3pFMA4UryJZ8h9Pu-alu2TuG1ee-enZQ2j8bU,695
19
+ pyezvizapi-1.0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ pyezvizapi-1.0.3.7.dist-info/entry_points.txt,sha256=_BSJ3eNb2H_AZkRdsv1s4mojqWn3N7m503ujvg1SudA,56
21
+ pyezvizapi-1.0.3.7.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
22
+ pyezvizapi-1.0.3.7.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- pyezvizapi/__init__.py,sha256=-OxHqxA9h0JZQW__GoZd1aByGquIHawCnNdeNlIg2Qo,3382
2
- pyezvizapi/__main__.py,sha256=9uttTuOfO22tzyomJIV8ebFJ-G-YUNDYOadZ_0AgdNA,20925
3
- pyezvizapi/api_endpoints.py,sha256=2M5Vs4YB1VWZGcowT-4Fj2hhRNjFh976LT3jtRrqvrc,5754
4
- pyezvizapi/camera.py,sha256=Pl5oIEdrFcv1Hz5sQI1IyyJIDCMjOjQdtExgKzmLoK8,22102
5
- pyezvizapi/cas.py,sha256=3zHe-_a0KchCmGeAj1of-pV6oMPRUmSCIiDqBFsTK8A,6025
6
- pyezvizapi/client.py,sha256=LroaR4h_wYu8HB74l4_X6Vjq7rTiL88Idf18elUBj4A,141851
7
- pyezvizapi/constants.py,sha256=5AxJYfof6NvebBcFvPkoKI6xinpkwmCnaauUvhvBMDY,12810
8
- pyezvizapi/exceptions.py,sha256=8rmxEUQdrziqMe-M1SeeRd0HtP2IDQ2xpJVj7wvOQyo,976
9
- pyezvizapi/feature.py,sha256=inqqk14Jgo3HOzml2GRUgz-hxbne4kT5iJ3L6SJhU8s,15990
10
- pyezvizapi/light_bulb.py,sha256=9wgycG3dTvBbrsxQjQnXal-GA8VXPsIN1m-CTtRh8i0,7797
11
- pyezvizapi/models.py,sha256=NQzwTP0yEe2IWU-Vc6nAn87xulpTuo0MX2Rcf0WxifA,4176
12
- pyezvizapi/mqtt.py,sha256=aOL-gexZgYvCCaNQ03M4vZan91d5p2Fl_qsFykn9NW4,22365
13
- pyezvizapi/test_cam_rtsp.py,sha256=O9NHh-vcNFfnzNw8jbuhM9a_5TWfNZIMXaJP7Lmkaj4,5162
14
- pyezvizapi/test_mqtt.py,sha256=Orn-fwZPJIE4G5KROMX0MRAkLwU6nLb9LUtXyb2ZCQs,4147
15
- pyezvizapi/utils.py,sha256=ozGncEyaIJJ8VYw8f-xfM2OBmqR8eNYLq728FFvbvr8,12757
16
- pyezvizapi-1.0.3.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
- pyezvizapi-1.0.3.6.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
- pyezvizapi-1.0.3.6.dist-info/METADATA,sha256=jp0jUC6j-BihBfzY79Qo_bdmk_yT6Db5wiJdlTHolmQ,695
19
- pyezvizapi-1.0.3.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- pyezvizapi-1.0.3.6.dist-info/entry_points.txt,sha256=_BSJ3eNb2H_AZkRdsv1s4mojqWn3N7m503ujvg1SudA,56
21
- pyezvizapi-1.0.3.6.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
22
- pyezvizapi-1.0.3.6.dist-info/RECORD,,