From cd293408aad03063376c9e55865ce62d39c964b3 Mon Sep 17 00:00:00 2001 From: alBronko Date: Mon, 14 Aug 2023 10:05:24 +0300 Subject: [PATCH] Update sonoff.py I updated the app key from here: https://github.com/AlexxIT/SonoffLAN/blob/master/custom_components/sonoff/core/ewelink/cloud.py and used the code from here: https://github.com/lucien2k/sonoff-python/issues/27#issuecomment-720433887 (correcting some spacing in the file) --- sonoff/sonoff.py | 151 ++++++++++++++++++++++++++--------------------- 1 file changed, 85 insertions(+), 66 deletions(-) diff --git a/sonoff/sonoff.py b/sonoff/sonoff.py index 5ba9e8f..84359ec 100644 --- a/sonoff/sonoff.py +++ b/sonoff/sonoff.py @@ -1,11 +1,11 @@ # The domain of your component. Should be equal to the name of your component. -import logging, time, hmac, hashlib, random, base64, json, socket, requests, re, uuid +import logging, time, hmac, hashlib, random, base64, json, socket, requests, re, string from datetime import timedelta SCAN_INTERVAL = timedelta(seconds=60) HTTP_MOVED_PERMANENTLY, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED, HTTP_NOT_FOUND = 301,400,401,404 -_LOGGER = logging.getLogger(__name__) +#_LOGGER = logging.getLogger(__name__) def gen_nonce(length=8): @@ -28,14 +28,7 @@ def __init__(self, username, password, api_region, user_apikey=None, bearer_toke self._bearer_token = bearer_token self._devices = [] self._ws = None - - # app details - self._app_version = '3.5.3' - self._appid = 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq' - self._model = 'iPhone10,6' - self._os = 'iOS' - self._rom_version = '11.1.2' - self._version = '6' + self.appid = 'R8Oq3y0eSZSYdKccHlrQzT1ACCOUT9Gv' if user_apikey and bearer_token: self.do_reconnect() @@ -58,29 +51,42 @@ def do_reconnect(self): self.do_login() def do_login(self): + import uuid # reset the grace period self._skipped_login = 0 app_details = { 'password' : self._password, - 'version' : self._version, + 'version' : '8', 'ts' : int(time.time()), 'nonce' : gen_nonce(15), - 'appid' : self._appid, + #'appid' : 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq', + 'appid' : 'R8Oq3y0eSZSYdKccHlrQzT1ACCOUT9Gv', 'imei' : str(uuid.uuid4()), - 'os' : self._os, - 'model' : self._model, - 'romVersion': self._rom_version, - 'appVersion': self._app_version + 'os' : 'iOS', + 'model' : 'iPhone10,6', + 'romVersion': '11.1.2', + 'appVersion': '3.5.3' } + self._model = 'iPhone' + random.choice(['6,1', '6,2', '7,1', '7,2', '8,1', '8,2', '8,4', '9,1', '9,2', '9,3', '9,4', '10,1', '10,2', '10,3', '10,4', '10,5', '10,6', '11,2', '11,4', '11,6', '11,8']) + self._romVersion = random.choice([ + '10.0', '10.0.2', '10.0.3', '10.1', '10.1.1', '10.2', '10.2.1', '10.3', '10.3.1', '10.3.2', '10.3.3', '10.3.4', + '11.0', '11.0.1', '11.0.2', '11.0.3', '11.1', '11.1.1', '11.1.2', '11.2', '11.2.1', '11.2.2', '11.2.3', '11.2.4', '11.2.5', '11.2.6', '11.3', '11.3.1', '11.4', '11.4.1', + '12.0', '12.0.1', '12.1', '12.1.1', '12.1.2', '12.1.3', '12.1.4', '12.2', '12.3', '12.3.1', '12.3.2', '12.4', '12.4.1', '12.4.2', + '13.0', '13.1', '13.1.1', '13.1.2', '13.2' + ]) + self._appVersion = random.choice(['3.5.3', '3.5.4', '3.5.6', '3.5.8', '3.5.10', '3.5.12', '3.6.0', '3.6.1', '3.7.0', '3.8.0', '3.9.0', '3.9.1', '3.10.0', '3.11.0']) + self._imei = str(uuid.uuid4()) + if re.match(r'[^@]+@[^@]+\.[^@]+', self._username): app_details['email'] = self._username else: app_details['phoneNumber'] = self._username - decryptedAppSecret = b'6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM' + #decryptedAppSecret = b'6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM' + decryptedAppSecret = b'1ve5Qk9GXfUhKAn1svnKwpAlxXkMarru' hex_dig = hmac.new( decryptedAppSecret, @@ -94,16 +100,19 @@ def do_login(self): 'Content-Type' : 'application/json;charset=UTF-8' } - r = requests.post('https://{}-api.coolkit.cc:8080/api/user/login'.format(self._api_region), +# r = requests.post('https://{}-api.coolkit.cc:8080/api/user/login'.format(self._api_region), +# headers=self._headers, json=app_details, verify=False) + r = requests.post('https://{}-api.coolkit.cc:8080/api/user/login'.format(self._api_region), headers=self._headers, json=app_details) + resp = r.json() # get a new region to login if 'error' in resp and 'region' in resp and resp['error'] == HTTP_MOVED_PERMANENTLY: self._api_region = resp['region'] - _LOGGER.warning("found new region: >>> %s <<< (you should change api_region option to this value in configuration.yaml)", self._api_region) + print("found new region: >>> %s <<< (you should change api_region option to this value in configuration.yaml)", self._api_region) # re-login using the new localized endpoint self.do_login() @@ -116,7 +125,7 @@ def do_login(self): self.do_login() else: - _LOGGER.error("Couldn't authenticate using the provided credentials!") + print("Couldn't authenticate using the provided credentials!") return @@ -131,12 +140,13 @@ def do_login(self): self.update_devices() # to get the devices list def set_wshost(self): - r = requests.post('https://%s-disp.coolkit.cc:8080/dispatch/app' % self._api_region, headers=self._headers) +# r = requests.post('https://%s-disp.coolkit.cc:8080/dispatch/app' % self._api_region, headers=self._headers, verify=False) + r = requests.post('https://%s-disp.coolkit.cc:8080/dispatch/app' % self._api_region, headers=self._headers) resp = r.json() if 'error' in resp and resp['error'] == 0 and 'domain' in resp: self._wshost = resp['domain'] - _LOGGER.info("Found websocket address: %s", self._wshost) + #print("Found websocket address: %s", self._wshost) else: raise Exception('No websocket domain') @@ -157,39 +167,31 @@ def update_devices(self): # we are in the grace period, no updates to the devices if self._skipped_login and self.is_grace_period(): - _LOGGER.info("Grace period active") + print("Grace period active, no updates to the devices") return self._devices - query_params = { - 'lang': 'en', - 'version': self._version, - 'ts': int(time.time()), - 'nonce': gen_nonce(15), - 'appid': self._appid, - 'imei': str(uuid.uuid4()), - 'os': self._os, - 'model': self._model, - 'romVersion': self._rom_version, - 'appVersion': self._app_version - } - r = requests.get('https://{}-api.coolkit.cc:8080/api/user/device'.format(self._api_region), - params=query_params, - headers=self._headers) - + #r = requests.get('https://{}-api.coolkit.cc:8080/api/user/device'.format(self._api_region), + # headers=self._headers) + r = requests.get('https://{}-api.coolkit.cc:8080/api/user/device?lang=en&apiKey={}&getTags=1&version=6&ts=%s&nonce=%s&appid=Uw83EKZFxdif7XFXEsrpduz5YyjP7nTl&imei=%s&os=iOS&model=%s&romVersion=%s&appVersion=%s'.format( + self._api_region, self.get_user_apikey(), str(int(time.time())), ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(8)), self._imei, self._model, self._romVersion, self._appVersion + ), headers=self._headers) resp = r.json() + #print ("response :",resp) + #print (r.status_code) + #print (r.content) if 'error' in resp and resp['error'] in [HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED]: # @IMPROVE add maybe a service call / switch to deactivate sonoff component if self.is_grace_period(): - _LOGGER.warning("Grace period activated!") + print("Grace period activated!") # return the current (and possible old) state of devices # in this period any change made with the mobile app (on/off) won't be shown in HA return self._devices - _LOGGER.info("Re-login component") + print("Re-login component") self.do_login() - - self._devices = resp.get('devicelist', []) + devices = r.json() + self._devices = devices['devicelist'] return self._devices def get_devices(self, force_update = False): @@ -213,6 +215,8 @@ def get_user_apikey(self): return self._user_apikey def _get_ws(self): + import ssl + import websocket """Check if the websocket is setup and connected.""" try: create_connection @@ -221,29 +225,44 @@ def _get_ws(self): if self._ws is None: try: + #self._ws = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE}) + #self._ws.connect('wss://{}:8080/api/ws'.format(self._wshost)) self._ws = create_connection(('wss://{}:8080/api/ws'.format(self._wshost)), timeout=10) - + ts = time.time() +# payload = { +# 'action' : "userOnline", +# 'userAgent' : 'app', +# 'version' : 8, +# 'nonce' : gen_nonce(15), +# 'apkVesrion': "1.8", +# 'os' : 'ios', +# 'at' : self.get_bearer_token(), +# 'apikey' : self.get_user_apikey(), +# 'ts' : str(int(time.time())), +# 'model' : 'iPhone10,6', +# 'romVersion': '11.1.2', +# 'sequence' : str(time.time()).replace('.','') +# } payload = { - 'action' : "userOnline", - 'userAgent' : 'app', - 'version' : 6, - 'nonce' : gen_nonce(15), - 'apkVesrion': "1.8", - 'os' : 'ios', - 'at' : self.get_bearer_token(), - 'apikey' : self.get_user_apikey(), - 'ts' : str(int(time.time())), - 'model' : 'iPhone10,6', - 'romVersion': '11.1.2', - 'sequence' : str(time.time()).replace('.','') + 'action': 'userOnline', + 'at': self.get_bearer_token(), + 'apikey': self.get_user_apikey(), + 'userAgent': 'app', + 'appid': self.appid, + 'nonce': str(int(ts / 100)), + 'ts': int(ts), + 'version': 8, + 'sequence': str(int(ts * 1000)) } self._ws.send(json.dumps(payload)) wsresp = self._ws.recv() - # _LOGGER.error("open socket: %s", wsresp) + #print (wsresp) + #("open socket: %s", wsresp) - except (socket.timeout, ConnectionRefusedError, ConnectionResetError): - _LOGGER.error('failed to create the websocket') +# except (socket.timeout, ConnectionRefusedError, ConnectionResetError): + except (socket.timeout): + print('failed to create the websocket') self._ws = None return self._ws @@ -253,13 +272,13 @@ def switch(self, new_state, deviceid, outlet=None): # we're in the grace period, no state change if self._skipped_login: - _LOGGER.info("Grace period, no state change") + print("Grace period, no state change") return (not new_state) self._ws = self._get_ws() if not self._ws: - _LOGGER.warning('invalid websocket, state cannot be changed') + print('invalid websocket, state cannot be changed') return (not new_state) # convert from True/False to on/off @@ -269,13 +288,13 @@ def switch(self, new_state, deviceid, outlet=None): device = self.get_device(deviceid) if outlet is not None: - _LOGGER.debug("Switching `%s - %s` on outlet %d to state: %s", \ + print("Switching `%s - %s` on outlet %d to state: %s", \ device['deviceid'], device['name'] , (outlet+1) , new_state) else: - _LOGGER.debug("Switching `%s` to state: %s", deviceid, new_state) + print ("Switching ", device['name'], " to state: ", new_state) if not device: - _LOGGER.error('unknown device to be updated') + print('unknown device to be updated') return False # the payload rule is like this: @@ -310,8 +329,8 @@ def switch(self, new_state, deviceid, outlet=None): self._ws.send(json.dumps(payload)) wsresp = self._ws.recv() - # _LOGGER.debug("switch socket: %s", wsresp) - + #_LOGGER.debug("switch socket: %s", wsresp) + #print (wsresp) self._ws.close() # no need to keep websocket open (for now) self._ws = None