diff --git a/pyezviz/__init__.py b/pyezvizapi/__init__.py similarity index 98% rename from pyezviz/__init__.py rename to pyezvizapi/__init__.py index 54b937a..6bb33e2 100644 --- a/pyezviz/__init__.py +++ b/pyezvizapi/__init__.py @@ -1,4 +1,4 @@ -"""init pyezviz.""" +"""init pyezvizapi.""" from .camera import EzvizCamera from .cas import EzvizCAS from .client import EzvizClient diff --git a/pyezviz/__main__.py b/pyezvizapi/__main__.py similarity index 99% rename from pyezviz/__main__.py rename to pyezvizapi/__main__.py index 1b572f5..58f0aff 100644 --- a/pyezviz/__main__.py +++ b/pyezvizapi/__main__.py @@ -1,4 +1,4 @@ -"""pyezviz command line.""" +"""pyezvizapi command line.""" import argparse import json import logging @@ -17,7 +17,7 @@ def main() -> Any: """Initiate arg parser.""" - parser = argparse.ArgumentParser(prog="pyezviz") + parser = argparse.ArgumentParser(prog="pyezvizapi") parser.add_argument("-u", "--username", required=True, help="Ezviz username") parser.add_argument("-p", "--password", required=True, help="Ezviz Password") parser.add_argument( diff --git a/pyezviz/api_endpoints.py b/pyezvizapi/api_endpoints.py similarity index 88% rename from pyezviz/api_endpoints.py rename to pyezvizapi/api_endpoints.py index e8c7008..aee8afa 100644 --- a/pyezviz/api_endpoints.py +++ b/pyezvizapi/api_endpoints.py @@ -7,30 +7,36 @@ API_ENDPOINT_LOGOUT = "/v3/users/logout/v2" API_ENDPOINT_REFRESH_SESSION_ID = "/v3/apigateway/login" API_ENDPOINT_SERVER_INFO = "/v3/configurations/system/info" + API_ENDPOINT_USER_ID = "/v3/userdevices/v1/token" API_ENDPOINT_GROUP_DEFENCE_MODE = "/v3/userdevices/v1/group/defenceMode" +API_ENDPOINT_PAGELIST = "/v3/userdevices/v1/resources/pagelist" +API_ENDPOINT_SWITCH_DEFENCE_MODE = "/v3/userdevices/v1/group/switchDefenceMode" API_ENDPOINT_PANORAMIC_DEVICES_OPERATION = "/v3/panoramicDevices/operation" API_ENDPOINT_UPGRADE_DEVICE = "/v3/upgrades/v1/devices/" API_ENDPOINT_SEND_CODE = "/v3/sms/nologin/checkcode" +API_ENDPOINT_UNIFIEDMSG_LIST_GET = "/v3/unifiedmsg/list" +API_ENDPOINT_IOT_FEATURE = "/v3/iot-feature/feature/" +API_ENDPOINT_CALLING_NOTIFY = "/v3/calling/" API_ENDPOINT_ALARMINFO_GET = "/v3/alarms/v2/advanced" -API_ENDPOINT_UNIFIEDMSG_LIST_GET = "/v3/unifiedmsg/list" API_ENDPOINT_V3_ALARMS = "/v3/alarms/" -API_ENDPOINT_SET_LUMINANCE = "/v3/alarms/device/alarmLight" +API_ENDPOINT_SET_LUMINANCE = "/v3/alarms/device/alarmLight/" -API_ENDPOINT_PAGELIST = "/v3/userdevices/v1/resources/pagelist" -API_ENDPOINT_SWITCH_DEFENCE_MODE = "/v3/userdevices/v1/group/switchDefenceMode" +API_ENDPOINT_DEVCONFIG_BY_KEY = "/v3/devconfig/v1/keyValue/" +API_ENDPOINT_CAM_AUTH_CODE = "/v3/devconfig/authcode/query/" API_ENDPOINT_DETECTION_SENSIBILITY = "/api/device/configAlgorithm" API_ENDPOINT_DETECTION_SENSIBILITY_GET = "/api/device/queryAlgorithmConfig" API_ENDPOINT_SET_DEFENCE_SCHEDULE = "/api/device/defence/plan2" API_ENDPOINT_CAM_ENCRYPTKEY = "/api/device/query/encryptkey" +API_ENDPOINT_OFFLINE_NOTIFY = "/api/device/notify/switch" API_ENDPOINT_CANCEL_ALARM = "/api/device/cancelAlarm" API_ENDPOINT_DEVICE_SYS_OPERATION = "/api/device/v2/sysOper/" API_ENDPOINT_DEVICE_STORAGE_STATUS = "/api/device/queryStorageStatus" -API_ENDPOINT_DEVCONFIG_BY_KEY = "/v3/devconfig/v1/keyValue/" -API_ENDPOINT_IOT_FEATURE = "/v3/iot-feature/feature/" +API_ENDPOINT_CREATE_PANORAMIC = "/api/panoramic/devices/pics/collect" +API_ENDPOINT_RETURN_PANORAMIC = "/api/panoramic/devices/pics" # Videogo DeviceApi API_ENDPOINT_DEVICES = "/v3/devices/" @@ -41,10 +47,7 @@ API_ENDPOINT_SWITCH_SOUND_ALARM = "/sendAlarm" API_ENDPOINT_DO_NOT_DISTURB = "/nodisturb" API_ENDPOINT_VIDEO_ENCRYPT = "encryptedInfo/risk" -API_ENDPOINT_CHANGE_DEFENCE_STATUS = "changeDefenceStatusReq" - -API_ENDPOINT_CREATE_PANORAMIC = "/api/panoramic/devices/pics/collect" -API_ENDPOINT_RETURN_PANORAMIC = "/api/panoramic/devices/pics" +API_ENDPOINT_CHANGE_DEFENCE_STATUS = "/changeDefenceStatusReq" # MQTT API_ENDPOINT_REGISTER_MQTT = "/v1/getClientId" diff --git a/pyezviz/camera.py b/pyezvizapi/camera.py similarity index 97% rename from pyezviz/camera.py rename to pyezvizapi/camera.py index 19e33e1..7dc7458 100644 --- a/pyezviz/camera.py +++ b/pyezvizapi/camera.py @@ -1,4 +1,5 @@ -"""pyezviz camera api.""" +"""pyezvizapi camera api.""" + from __future__ import annotations import datetime @@ -43,7 +44,7 @@ def _alarm_list(self) -> None: if fetch_nested_value(_alarmlist, ["page", "totalResults"], 0) > 0: self._last_alarm = _alarmlist["alarms"][0] - return self._motion_trigger() + self._motion_trigger() def _local_ip(self) -> Any: """Fix empty ip value for certain cameras.""" @@ -123,6 +124,8 @@ def status(self) -> dict[Any, Any]: "local_ip": self._local_ip(), "wan_ip": self.fetch_key(["CONNECTION", "netIp"]), "mac_address": self.fetch_key(["deviceInfos", "mac"]), + "offline_notify": bool(self.fetch_key(["deviceInfos", "offlineNotify"])), + "last_offline_time": self.fetch_key(["deviceInfos", "offlineTime"]), "local_rtsp_port": self.fetch_key(["CONNECTION", "localRtspPort"], "554") if self.fetch_key(["CONNECTION", "localRtspPort"], "554") != 0 else "554", diff --git a/pyezviz/cas.py b/pyezvizapi/cas.py similarity index 99% rename from pyezviz/cas.py rename to pyezvizapi/cas.py index 075d703..055352f 100644 --- a/pyezviz/cas.py +++ b/pyezvizapi/cas.py @@ -1,4 +1,4 @@ -"""Ezviz CAS API Functions.""" +"""pyezvizapi CAS API Functions.""" from io import BytesIO from itertools import cycle diff --git a/pyezviz/client.py b/pyezvizapi/client.py similarity index 88% rename from pyezviz/client.py rename to pyezvizapi/client.py index e08581f..383217a 100644 --- a/pyezviz/client.py +++ b/pyezvizapi/client.py @@ -15,6 +15,8 @@ from .api_endpoints import ( API_ENDPOINT_ALARM_SOUND, API_ENDPOINT_ALARMINFO_GET, + API_ENDPOINT_CALLING_NOTIFY, + API_ENDPOINT_CAM_AUTH_CODE, API_ENDPOINT_CAM_ENCRYPTKEY, API_ENDPOINT_CANCEL_ALARM, API_ENDPOINT_CHANGE_DEFENCE_STATUS, @@ -30,6 +32,7 @@ API_ENDPOINT_IOT_FEATURE, API_ENDPOINT_LOGIN, API_ENDPOINT_LOGOUT, + API_ENDPOINT_OFFLINE_NOTIFY, API_ENDPOINT_PAGELIST, API_ENDPOINT_PANORAMIC_DEVICES_OPERATION, API_ENDPOINT_PTZCONTROL, @@ -122,7 +125,7 @@ def _login(self, smscode: int | None = None) -> dict[Any, Any]: try: req = self._session.post( - "https://" + self._token["api_url"] + API_ENDPOINT_LOGIN, + url=f"https://{self._token['api_url']}{API_ENDPOINT_LOGIN}", allow_redirects=False, data=payload, timeout=self._timeout, @@ -192,7 +195,7 @@ def send_mfa_code(self) -> bool: """Send verification code.""" try: req = self._session.post( - "https://" + self._token["api_url"] + API_ENDPOINT_SEND_CODE, + url=f"https://{self._token['api_url']}{API_ENDPOINT_SEND_CODE}", data={ "from": self.account, "bizType": "TERMINAL_BIND", @@ -229,7 +232,7 @@ def get_service_urls(self) -> Any: try: req = self._session.get( - f"https://{self._token['api_url']}{API_ENDPOINT_SERVER_INFO}", + url=f"https://{self._token['api_url']}{API_ENDPOINT_SERVER_INFO}", timeout=self._timeout, ) req.raise_for_status() @@ -285,7 +288,7 @@ def _api_get_pagelist( try: req = self._session.get( - "https://" + self._token["api_url"] + API_ENDPOINT_PAGELIST, + url=f"https://{self._token['api_url']}{API_ENDPOINT_PAGELIST}", params=params, timeout=self._timeout, ) @@ -340,7 +343,6 @@ def _api_get_pagelist( return data - def get_alarminfo(self, serial: str, limit: int = 1, max_retries: int = 0) -> dict: """Get data from alarm info API for camera serial.""" if max_retries > MAX_RETRIES: @@ -355,7 +357,7 @@ def get_alarminfo(self, serial: str, limit: int = 1, max_retries: int = 0) -> di try: req = self._session.get( - "https://" + self._token["api_url"] + API_ENDPOINT_ALARMINFO_GET, + url=f"https://{self._token['api_url']}{API_ENDPOINT_ALARMINFO_GET}", params=params, timeout=self._timeout, ) @@ -418,7 +420,7 @@ def get_device_messages_list( try: req = self._session.get( - "https://" + self._token["api_url"] + API_ENDPOINT_UNIFIEDMSG_LIST_GET, + url=f"https://{self._token['api_url']}{API_ENDPOINT_UNIFIEDMSG_LIST_GET}", params=params, timeout=self._timeout, ) @@ -570,7 +572,7 @@ def set_camera_defence( try: req = self._session.put( - url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICES}{serial}/{channel_no}/{API_ENDPOINT_CHANGE_DEFENCE_STATUS}", + url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICES}{serial}/{channel_no}{API_ENDPOINT_CHANGE_DEFENCE_STATUS}", timeout=self._timeout, data={ "type": arm_type, @@ -771,11 +773,7 @@ def upgrade_device(self, serial: str, max_retries: int = 0) -> bool: try: req = self._session.put( - "https://" - + self._token["api_url"] - + API_ENDPOINT_UPGRADE_DEVICE - + serial - + "/0/upgrade", + url=f"https://{self._token['api_url']}{API_ENDPOINT_UPGRADE_DEVICE}{serial}/0/upgrade", timeout=self._timeout, ) @@ -862,12 +860,7 @@ def sound_alarm(self, serial: str, enable: int = 1, max_retries: int = 0) -> boo try: req = self._session.put( - "https://" - + self._token["api_url"] - + API_ENDPOINT_DEVICES - + serial - + "/0" - + API_ENDPOINT_SWITCH_SOUND_ALARM, + url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICES}{serial}/0{API_ENDPOINT_SWITCH_SOUND_ALARM}", data={ "enable": enable, }, @@ -908,7 +901,7 @@ def get_user_id(self, max_retries: int = 0) -> Any: try: req = self._session.get( - f"https://{self._token['api_url']}{API_ENDPOINT_USER_ID}", + url=f"https://{self._token['api_url']}{API_ENDPOINT_USER_ID}", timeout=self._timeout, ) req.raise_for_status() @@ -950,32 +943,24 @@ def set_video_enc( if max_retries > MAX_RETRIES: raise PyEzvizError("Can't gather proper data. Max retries exceeded.") - device_token_info = self.get_user_id() - cookies = { - "clientType": "3", - "clientVersion": "5.12.1.0517", - "userId": device_token_info["userId"], - "ASG_DisplayName": "home", - "C_SS": self._session.headers["sessionId"], - "lang": "en_US", - } + if enable == 2 and not old_password: + raise PyEzvizError("Old password is required when changing password.") + + if new_password and not enable == 2: + raise PyEzvizError("New password is only required when changing password.") try: req = self._session.put( - "https://" - + self._token["api_url"] - + API_ENDPOINT_DEVICES - + API_ENDPOINT_VIDEO_ENCRYPT, + url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICES}{API_ENDPOINT_VIDEO_ENCRYPT}", data={ "deviceSerial": serial, - "isEncrypt": enable, - "oldPassword": old_password, + "isEncrypt": enable, # 1 = enable, 0 = disable, 2 = change password + "oldPassword": old_password, # required if changing. "password": new_password, "featureCode": FEATURE_CODE, "validateCode": camera_verification_code, "msgType": -1, }, - cookies=cookies, timeout=self._timeout, ) @@ -1073,6 +1058,69 @@ def reboot_camera( return True + def set_offline_notification( + self, + serial: str, + enable: int = 1, + req_type: int = 1, + max_retries: int = 0, + ) -> bool: + """Set offline notification.""" + if max_retries > MAX_RETRIES: + raise PyEzvizError("Can't gather proper data. Max retries exceeded.") + + try: + req = self._session.post( + url=f'https://{self._token["api_url"]}{API_ENDPOINT_OFFLINE_NOTIFY}', + data={ + "reqType": req_type, + "serial": serial, + "status": enable, + }, + timeout=self._timeout, + ) + + req.raise_for_status() + + except requests.HTTPError as err: + if err.response.status_code == 401: + # session is wrong, need to relogin + self.login() + return self.set_offline_notification( + serial, + enable, + req_type, + max_retries + 1, + ) + + raise HTTPError from err + + try: + json_output = req.json() + + except ValueError as err: + raise PyEzvizError( + "Impossible to decode response: " + + str(err) + + "\nResponse was: " + + str(req.text) + ) from err + + if json_output["resultCode"] != "0": + if json_output["resultCode"] == "-1": + _LOGGER.warning( + "Unable to set offline notification, camera %s is unreachable, retrying %s of %s", + serial, + max_retries, + MAX_RETRIES, + ) + return self.set_offline_notification( + serial, enable, req_type, max_retries + 1 + ) + raise PyEzvizError(f"Could not set offline notification {json_output})") + + return True + def get_group_defence_mode(self, max_retries: int = 0) -> Any: """Get group arm status. The alarm arm/disarm concept on 1st page of app.""" @@ -1081,7 +1129,7 @@ def get_group_defence_mode(self, max_retries: int = 0) -> Any: try: req = self._session.get( - "https://" + self._token["api_url"] + API_ENDPOINT_GROUP_DEFENCE_MODE, + url=f"https://{self._token['api_url']}{API_ENDPOINT_GROUP_DEFENCE_MODE}", params={ "groupId": -1, }, @@ -1124,7 +1172,7 @@ def cancel_alarm_device(self, serial: str, max_retries: int = 0) -> bool: try: req = self._session.post( - "https://" + self._token["api_url"] + API_ENDPOINT_CANCEL_ALARM, + url=f"https://{self._token['api_url']}{API_ENDPOINT_CANCEL_ALARM}", data={"subSerial": serial}, timeout=self._timeout, ) @@ -1270,15 +1318,11 @@ def ptz_control( try: req = self._session.put( - "https://" - + self._token["api_url"] - + API_ENDPOINT_DEVICES - + serial - + API_ENDPOINT_PTZCONTROL, + url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICES}{serial}{API_ENDPOINT_PTZCONTROL}", data={ "command": command, "action": action, - "channelNo": "1", + "channelNo": 1, "speed": speed, "uuid": str(uuid4()), "serial": serial, @@ -1316,7 +1360,7 @@ def get_cam_key( try: req = self._session.post( - "https://" + self._token["api_url"] + API_ENDPOINT_CAM_ENCRYPTKEY, + url=f"https://{self._token['api_url']}{API_ENDPOINT_CAM_ENCRYPTKEY}", data={ "checkcode": smscode, "serial": serial, @@ -1368,6 +1412,61 @@ def get_cam_key( return json_output["encryptkey"] + def get_cam_auth_code( + self, + serial: str, + encrypt_pwd: str | None = None, + msg_auth_code: int | None = None, + max_retries: int = 0, + ) -> Any: + """Get Camera auth code. This is the verification code on the camera sticker.""" + + if max_retries > MAX_RETRIES: + raise PyEzvizError("Can't gather proper data. Max retries exceeded.") + + params: dict[str, int | str | None] = { + "encrptPwd": encrypt_pwd, + "msgAuthCode": msg_auth_code, + "senderType": 0, + } + + try: + req = self._session.get( + url=f"https://{self._token['api_url']}{API_ENDPOINT_CAM_AUTH_CODE}{serial}", + params=params, + timeout=self._timeout, + ) + + req.raise_for_status() + + except requests.HTTPError as err: + if err.response.status_code == 401: + # session is wrong, need to relogin + self.login() + return self.get_cam_auth_code( + serial, encrypt_pwd, msg_auth_code, max_retries + 1 + ) + + raise HTTPError from err + + try: + json_output = req.json() + + except ValueError as err: + raise PyEzvizError( + "Impossible to decode response: " + + str(err) + + "\nResponse was: " + + str(req.text) + ) from err + + if json_output["meta"]["code"] != 200: + raise PyEzvizError( + f"Could not get camera verification key: Got {json_output})" + ) + + return json_output["devAuthCode"] + def create_panoramic(self, serial: str, max_retries: int = 0) -> Any: """Create panoramic image.""" @@ -1376,7 +1475,7 @@ def create_panoramic(self, serial: str, max_retries: int = 0) -> Any: try: req = self._session.post( - "https://" + self._token["api_url"] + API_ENDPOINT_CREATE_PANORAMIC, + url=f"https://{self._token['api_url']}{API_ENDPOINT_CREATE_PANORAMIC}", data={"deviceSerial": serial}, timeout=self._timeout, ) @@ -1424,7 +1523,7 @@ def return_panoramic(self, serial: str, max_retries: int = 0) -> Any: try: req = self._session.post( - "https://" + self._token["api_url"] + API_ENDPOINT_RETURN_PANORAMIC, + url=f"https://{self._token['api_url']}{API_ENDPOINT_RETURN_PANORAMIC}", data={"deviceSerial": serial}, timeout=self._timeout, ) @@ -1479,9 +1578,7 @@ def ptz_control_coordinates( try: req = self._session.post( - "https://" - + self._token["api_url"] - + API_ENDPOINT_PANORAMIC_DEVICES_OPERATION, + url=f"https://{self._token['api_url']}{API_ENDPOINT_PANORAMIC_DEVICES_OPERATION}", data={ "x": f"{x_axis:.6f}", "y": f"{y_axis:.6f}", @@ -1515,9 +1612,7 @@ def login(self, sms_code: int | None = None) -> dict[Any, Any]: if self._token["session_id"] and self._token["rf_session_id"]: try: req = self._session.put( - "https://" - + self._token["api_url"] - + API_ENDPOINT_REFRESH_SESSION_ID, + url=f"https://{self._token['api_url']}{API_ENDPOINT_REFRESH_SESSION_ID}", data={ "refreshSessionId": self._token["rf_session_id"], "featureCode": FEATURE_CODE, @@ -1579,7 +1674,7 @@ def logout(self) -> bool: """Close Ezviz session and remove login session from ezviz servers.""" try: req = self._session.delete( - "https://" + self._token["api_url"] + API_ENDPOINT_LOGOUT, + url=f"https://{self._token['api_url']}{API_ENDPOINT_LOGOUT}", timeout=self._timeout, ) req.raise_for_status() @@ -1630,7 +1725,7 @@ def api_set_defence_schedule( ) try: req = self._session.post( - "https://" + self._token["api_url"] + API_ENDPOINT_SET_DEFENCE_SCHEDULE, + url=f"https://{self._token['api_url']}{API_ENDPOINT_SET_DEFENCE_SCHEDULE}", data={ "devTimingPlan": schedulestring, }, @@ -1679,10 +1774,9 @@ def api_set_defence_mode(self, mode: DefenseModeType, max_retries: int = 0) -> b """Set defence mode for all devices. The alarm panel from main page is used.""" if max_retries > MAX_RETRIES: raise PyEzvizError("Can't gather proper data. Max retries exceeded.") - try: req = self._session.post( - "https://" + self._token["api_url"] + API_ENDPOINT_SWITCH_DEFENCE_MODE, + url=f"https://{self._token['api_url']}{API_ENDPOINT_SWITCH_DEFENCE_MODE}", data={ "groupId": -1, "mode": mode, @@ -1729,14 +1823,8 @@ def do_not_disturb( try: req = self._session.put( - "https://" - + self._token["api_url"] - + API_ENDPOINT_V3_ALARMS - + serial - + "/" - + channelno - + API_ENDPOINT_DO_NOT_DISTURB, - data={"enable": enable, "channelNo": channelno, "deviceSerial": serial}, + url=f"https://{self._token['api_url']}{API_ENDPOINT_V3_ALARMS}{serial}/{channelno}{API_ENDPOINT_DO_NOT_DISTURB}", + data={"enable": enable}, timeout=self._timeout, ) req.raise_for_status() @@ -1760,11 +1848,47 @@ def do_not_disturb( return True + def set_answer_call( + self, + serial: str, + enable: int = 1, + max_retries: int = 0, + ) -> bool: + """Set answer call on camera with specified serial.""" + if max_retries > MAX_RETRIES: + raise PyEzvizError("Can't gather proper data. Max retries exceeded.") + try: + req = self._session.put( + url=f"https://{self._token['api_url']}{API_ENDPOINT_CALLING_NOTIFY}{serial}{API_ENDPOINT_DO_NOT_DISTURB}", + data={"deviceSerial": serial, "switchStatus": enable}, + timeout=self._timeout, + ) + req.raise_for_status() + + except requests.HTTPError as err: + if err.response.status_code == 401: + # session is wrong, need to re-log-in + self.login() + return self.set_answer_call(serial, enable, max_retries + 1) + + raise HTTPError from err + + try: + json_output = req.json() + + except ValueError as err: + raise PyEzvizError("Could not decode response:" + str(err)) from err + + if json_output["meta"]["code"] != 200: + raise PyEzvizError(f"Could not set answer call: Got {json_output})") + + return True + def set_floodlight_brightness( self, serial: str, luminance: int = 50, - channelno: str = "1", + channelno: int = 1, max_retries: int = 0, ) -> bool | str: """Set brightness on camera with adjustable light.""" @@ -1778,13 +1902,7 @@ def set_floodlight_brightness( try: req = self._session.post( - "https://" - + self._token["api_url"] - + API_ENDPOINT_SET_LUMINANCE - + "/" - + serial - + "/" - + channelno, + url=f"https://{self._token['api_url']}{API_ENDPOINT_SET_LUMINANCE}{serial}/{channelno}", data={ "luminance": luminance, }, @@ -1818,7 +1936,7 @@ def set_brightness( self, serial: str, luminance: int = 50, - channelno: str = "1", + channelno: int = 1, max_retries: int = 0, ) -> bool | str: """Facade that changes the brightness to light bulbs or cameras' light.""" @@ -1869,16 +1987,13 @@ def detection_sensibility( raise PyEzvizError( "Unproper sensibility for type 0 (should be within 1 to 6)." ) - try: req = self._session.post( - "https://" - + self._token["api_url"] - + API_ENDPOINT_DETECTION_SENSIBILITY, + url=f"https://{self._token['api_url']}{API_ENDPOINT_DETECTION_SENSIBILITY}", data={ "subSerial": serial, "type": type_value, - "channelNo": "1", + "channelNo": 1, "value": sensibility, }, timeout=self._timeout, @@ -1925,12 +2040,9 @@ def get_detection_sensibility( """Get detection sensibility notifications.""" if max_retries > MAX_RETRIES: raise PyEzvizError("Can't gather proper data. Max retries exceeded.") - try: req = self._session.post( - "https://" - + self._token["api_url"] - + API_ENDPOINT_DETECTION_SENSIBILITY_GET, + url=f"https://{self._token['api_url']}{API_ENDPOINT_DETECTION_SENSIBILITY_GET}", data={ "subSerial": serial, }, @@ -1992,11 +2104,7 @@ def alarm_sound( try: req = self._session.put( - "https://" - + self._token["api_url"] - + API_ENDPOINT_DEVICES - + serial - + API_ENDPOINT_ALARM_SOUND, + url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICES}{serial}{API_ENDPOINT_ALARM_SOUND}", data={ "enable": enable, "soundType": sound_type, @@ -2044,9 +2152,7 @@ def get_device(self) -> Any: def get_connection(self) -> Any: """Get ezviz connection infos filter.""" - return self._api_get_pagelist( - page_filter="CONNECTION", json_key="CONNECTION" - ) + return self._api_get_pagelist(page_filter="CONNECTION", json_key="CONNECTION") def _get_status(self) -> Any: """Get ezviz status infos filter.""" @@ -2054,9 +2160,7 @@ def _get_status(self) -> Any: def get_switch(self) -> Any: """Get ezviz switch infos filter.""" - return self._api_get_pagelist( - page_filter="SWITCH", json_key="SWITCH" - ) + return self._api_get_pagelist(page_filter="SWITCH", json_key="SWITCH") def _get_wifi(self) -> Any: """Get ezviz wifi infos filter.""" @@ -2064,9 +2168,7 @@ def _get_wifi(self) -> Any: def _get_nodisturb(self) -> Any: """Get ezviz nodisturb infos filter.""" - return self._api_get_pagelist( - page_filter="NODISTURB", json_key="NODISTURB" - ) + return self._api_get_pagelist(page_filter="NODISTURB", json_key="NODISTURB") def _get_p2p(self) -> Any: """Get ezviz P2P infos filter.""" diff --git a/pyezviz/constants.py b/pyezvizapi/constants.py similarity index 100% rename from pyezviz/constants.py rename to pyezvizapi/constants.py diff --git a/pyezviz/exceptions.py b/pyezvizapi/exceptions.py similarity index 100% rename from pyezviz/exceptions.py rename to pyezvizapi/exceptions.py diff --git a/pyezviz/light_bulb.py b/pyezvizapi/light_bulb.py similarity index 98% rename from pyezviz/light_bulb.py rename to pyezvizapi/light_bulb.py index 0fa4974..200c8ec 100644 --- a/pyezviz/light_bulb.py +++ b/pyezvizapi/light_bulb.py @@ -66,7 +66,7 @@ def get_feature_json(self) -> Any: return json_output - def get_feature_item(self, key: str, default_value: Any = None) -> Any: + def get_feature_item(self, key: str, default_value: Any = { "dataValue" : "" }) -> Any: """Get items fron FEATURE.""" items = self._feature_json["featureItemDtos"] for item in items: diff --git a/pyezviz/mqtt.py b/pyezvizapi/mqtt.py similarity index 100% rename from pyezviz/mqtt.py rename to pyezvizapi/mqtt.py diff --git a/pyezviz/test_cam_rtsp.py b/pyezvizapi/test_cam_rtsp.py similarity index 100% rename from pyezviz/test_cam_rtsp.py rename to pyezvizapi/test_cam_rtsp.py diff --git a/pyezviz/utils.py b/pyezvizapi/utils.py similarity index 92% rename from pyezviz/utils.py rename to pyezvizapi/utils.py index d2baab3..bf9c17a 100644 --- a/pyezviz/utils.py +++ b/pyezvizapi/utils.py @@ -1,4 +1,5 @@ """Decrypt camera images.""" + from __future__ import annotations from hashlib import md5 @@ -85,13 +86,11 @@ def decrypt_image(input_data: bytes, password: str) -> bytes: # check header if input_data[:16] != b"hikencodepicture": - _LOGGER.warning("Image header doesn't contain 'hikencodepicture'") + _LOGGER.debug("Image header doesn't contain 'hikencodepicture'") return input_data file_hash = input_data[16:48] - passwd_hash = ( - md5(str.encode(md5(str.encode(password)).digest().hex())).digest().hex() - ) + passwd_hash = md5(str.encode(md5(str.encode(password)).hexdigest())).hexdigest() if file_hash != str.encode(passwd_hash): raise PyEzvizError("Invalid password") @@ -114,7 +113,13 @@ def decrypt_image(input_data: bytes, password: str) -> bytes: i += chunk_size return output_data -def deep_merge(dict1, dict2): + +def return_password_hash(password: str) -> str: + """Return the password hash.""" + return md5(str.encode(md5(str.encode(password)).hexdigest())).hexdigest() + + +def deep_merge(dict1: Any, dict2: Any) -> Any: """Recursively merges two dictionaries, handling lists as well. Args: @@ -157,4 +162,3 @@ def deep_merge(dict1, dict2): merged[key] = dict2[key] return merged - diff --git a/requirements.txt b/requirements.txt index deda5f2..1d8d6a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -pycryptodome==3.20.0 -pandas==2.2.2 +pycryptodome==3.21.0 +pandas==2.2.3 requests==2.32.3 -paho-mqtt==1.6.1 -xmltodict==0.13.0 \ No newline at end of file +paho-mqtt==2.1.0 +xmltodict==0.14.2 \ No newline at end of file diff --git a/setup.py b/setup.py index 2e4e653..9e328b1 100644 --- a/setup.py +++ b/setup.py @@ -4,14 +4,14 @@ long_description = fh.read() setuptools.setup( - name='pyezviz', - version="0.2.2.3", + name='pyezvizapi', + version="1.0.0.6", license='Apache Software License 2.0', - author='Pierre Ourdouille', - author_email='baqs@users.github.com', + author='Renier Moorcroft', + author_email='RenierM26@users.github.com', description='Pilot your Ezviz cameras', long_description="Pilot your Ezviz cameras with this module. Please view readme on github", - url='http://github.com/baqs/pyEzviz/', + url='https://github.com/RenierM26/pyEzvizApi/', packages=setuptools.find_packages(), setup_requires=[ 'requests', @@ -25,7 +25,7 @@ 'pycryptodome' ], entry_points={ - 'console_scripts': ['pyezviz = pyezviz.__main__:main'] + 'console_scripts': ['pyezvizapi = pyezvizapi.__main__:main'] }, python_requires = '>=3.6' )