diff --git a/arlo.py b/arlo.py index b743c5e..cb1f914 100644 --- a/arlo.py +++ b/arlo.py @@ -26,7 +26,7 @@ from request import Request from eventstream import EventStream - + # Import all of the other stuff. from six import string_types, text_type from datetime import datetime @@ -136,7 +136,9 @@ def Login(self, username, password): """ self.username = username self.password = password - self.request = Request() + + # start login with curl_cffi to address cloudflare client fingerprinting + self.request = Request(impersonate=True) headers = { 'Access-Control-Request-Headers': 'content-type,source,x-user-device-id,x-user-device-name,x-user-device-type', @@ -146,7 +148,7 @@ def Login(self, username, password): 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_1_2 like Mac OS X) AppleWebKit/604.3.5 (KHTML, like Gecko) Mobile/15B202 NETGEAR/v1 (iOS Vuezone)', } self.request.options(f'https://{self.AUTH_URL}/api/auth', headers=headers) - + headers = { 'DNT': '1', 'schemaVersion': '1', @@ -171,6 +173,8 @@ def Login(self, username, password): ) headers['Authorization'] = body['token'] + # create a new Request object with a session that can be used with sseclient + self.request = Request(impersonate=False) self.request.session.headers.update(headers) self.user_id = body['userId'] @@ -180,7 +184,9 @@ def LoginMFA(self, username, password, google_credential_file): self.username = username self.password = password self.google_credentials = pickle.load(open(google_credential_file, 'rb')) - self.request = Request() + + # start login with curl_cffi to address cloudflare client fingerprinting + self.request = Request(impersonate=True) # request MFA token request_start_time = int(time.time()) @@ -285,6 +291,8 @@ def LoginMFA(self, username, password, google_credential_file): 'Authorization': finish_auth_body['data']['token'].encode('utf-8'), 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_1_2 like Mac OS X) AppleWebKit/604.3.5 (KHTML, like Gecko) Mobile/15B202 NETGEAR/v1 (iOS Vuezone)', } + # create a new Request object with a session that can be used with sseclient + self.request = Request(impersonate=False) self.request.session.headers.update(headers) self.BASE_URL = 'myapi.arlo.com' diff --git a/request.py b/request.py index f80afa3..4fe690c 100644 --- a/request.py +++ b/request.py @@ -16,6 +16,7 @@ import requests from requests.exceptions import HTTPError +from curl_cffi import requests as curl_cffi_requests #from requests_toolbelt.utils import dump #def print_raw_http(response): @@ -25,8 +26,15 @@ class Request(object): """HTTP helper class""" - def __init__(self): - self.session = requests.Session() + def __init__(self, impersonate=False): + # we create different sessions based on different technical needs: + # - curl_cffi is used to impersonate a browser and bypass client fingerprinting + # - requests is used to produce a feature-filled Session that can be compatible with sseclient + self.impersonate = impersonate + if impersonate: + self.session = curl_cffi_requests.Session(impersonate="chrome110") + else: + self.session = requests.Session() def _request(self, url, method='GET', params={}, headers={}, stream=False, raw=False): @@ -44,7 +52,11 @@ def _request(self, url, method='GET', params={}, headers={}, stream=False, raw=F if method == 'GET': #print('COOKIES: ', self.session.cookies.get_dict()) - r = self.session.get(url, params=params, headers=headers, stream=stream) + if self.impersonate: + # curl_cffi does no suppot the stream keyword + r = self.session.get(url, params=params, headers=headers) + else: + r = self.session.get(url, params=params, headers=headers, stream=stream) r.raise_for_status() if stream is True: return r diff --git a/setup.py b/setup.py index d2d0007..9120ed0 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ def readme(): url='https://github.com/jeffreydwalter/arlo', license='Apache Software License', include_package_data=True, - install_requires=['monotonic', 'requests', 'responses==0.10.15', 'urllib3==1.24', 'sseclient==0.0.22', 'PySocks', 'pickle-mixin', 'google-api-python-client', 'google-auth-oauthlib'], + install_requires=['monotonic', 'requests', 'responses==0.10.15', 'urllib3==1.24', 'sseclient==0.0.22', 'PySocks', 'pickle-mixin', 'google-api-python-client', 'google-auth-oauthlib', 'curl-cffi==0.5.7'], keywords=[ 'arlo', 'camera',