def login(self, credentials=None): """Perform account login""" try: # First we get the authentication url without logging in, required for login API call react_context = website.extract_json(self.get('login'), 'reactContext') auth_url = website.extract_api_data(react_context)['auth_url'] LOG.debug('Logging in...') login_response = self.post( 'login', headers={'Accept-Language': _get_accept_language_string(react_context)}, data=_login_payload(credentials or common.get_credentials(), auth_url, react_context)) website.extract_session_data(login_response, validate=True, update_profiles=True) if credentials: # Save credentials only when login has succeeded common.set_credentials(credentials) LOG.info('Login successful') ui.show_notification(common.get_local_string(30109)) cookies.save(self.account_hash, self.session.cookies) return True except LoginValidateError as exc: self.session.cookies.clear() common.purge_credentials() raise_from(LoginError(unicode(exc)), exc) except (MbrStatusNeverMemberError, MbrStatusFormerMemberError) as exc: self.session.cookies.clear() LOG.warn('Membership status {} not valid for login', exc) raise_from(LoginError(common.get_local_string(30180)), exc) except Exception: # pylint: disable=broad-except self.session.cookies.clear() import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) raise
def login_auth_data(self, data=None, password=None): """Perform account login with authentication data""" from requests import exceptions LOG.debug('Logging in with authentication data') # Add the cookies to the session self.session.cookies.clear() for cookie in data['cookies']: self.session.cookies.set(cookie[0], cookie[1], **cookie[2]) cookies.log_cookie(self.session.cookies) # Try access to website try: website.extract_session_data(self.get('browse'), validate=True, update_profiles=True) except MbrStatusAnonymousError: # Access not valid return False # Get the account e-mail page_response = self.get('your_account').decode('utf-8') email_match = re.search(r'account-email[^<]+>([^<]+@[^</]+)</', page_response) email = email_match.group(1).strip() if email_match else None if not email: raise WebsiteParsingError('E-mail field not found') # Verify the password (with parental control api) try: response = self.post_safe('profile_hub', data={ 'destination': 'contentRestrictions', 'guid': G.LOCAL_DB.get_active_profile_guid(), 'password': password, 'task': 'auth' }) if response.get('status') != 'ok': raise LoginError(common.get_local_string( 12344)) # 12344=Passwords entered did not match. except exceptions.HTTPError as exc: if exc.response.status_code == 500: # This endpoint raise HTTP error 500 when the password is wrong raise LoginError(common.get_local_string(12344)) from exc raise common.set_credentials({'email': email, 'password': password}) LOG.info('Login successful') ui.show_notification(common.get_local_string(30109)) cookies.save(self.session.cookies) return True
def __init__(self): super().__init__() # Slot allocation for IPC self.slots = [ self.get_safe, self.post_safe, self.login, self.login_auth_data, self.logout, self.path_request, self.perpetual_path_request, self.callpath_request, self.fetch_initial_page, self.refresh_session_data, self.activate_profile, self.parental_control_data, self.get_metadata, self.update_loco_context, self.update_videoid_bookmark, self.get_videoid_info ] # Share the activate profile function to SessionBase class self.external_func_activate_profile = self.activate_profile self.dt_initial_page_prefetch = None # Try prefetch login if self.prefetch_login(): try: # Try prefetch initial page response = self.get_safe('browse') api_data = website.extract_session_data(response, update_profiles=True) self.auth_url = api_data['auth_url'] self.dt_initial_page_prefetch = datetime.now() except Exception as exc: # pylint: disable=broad-except LOG.warn('Prefetch initial page failed: {}', exc)
def activate_profile(self, guid): """Set the profile identified by guid as active""" LOG.debug('Switching to profile {}', guid) current_active_guid = G.LOCAL_DB.get_active_profile_guid() if guid == current_active_guid: LOG.info('The profile guid {} is already set, activation not needed.', guid) return if xbmc.Player().isPlayingVideo(): # Change the current profile while a video is playing can cause problems with outgoing HTTP requests # (MSL/NFSession) causing a failure in the HTTP request or sending data on the wrong profile raise Warning('It is not possible select a profile while a video is playing.') timestamp = time.time() LOG.info('Activating profile {}', guid) # 20/05/2020 - The method 1 not more working for switching PIN locked profiles # INIT Method 1 - HTTP mode # response = self._get('switch_profile', params={'tkn': guid}) # self.nfsession.auth_url = self.website_extract_session_data(response)['auth_url'] # END Method 1 # INIT Method 2 - API mode try: self.get_safe(endpoint='activate_profile', params={'switchProfileGuid': guid, '_': int(timestamp * 1000), 'authURL': self.auth_url}) except HttpError401 as exc: # Profile guid not more valid raise InvalidProfilesError('Unable to access to the selected profile.') from exc # Retrieve browse page to update authURL response = self.get_safe('browse') self.auth_url = website.extract_session_data(response)['auth_url'] # END Method 2 G.LOCAL_DB.switch_active_profile(guid) G.CACHE_MANAGEMENT.identifier_prefix = guid cookies.save(self.session.cookies)
def website_extract_session_data(self, content, **kwargs): """Extract session data and handle errors""" try: return website.extract_session_data(content, **kwargs) except WebsiteParsingError as exc: LOG.error('An error occurs in extract session data: {}', exc) raise except (LoginValidateError, MbrStatusAnonymousError) as exc: LOG.warn('The session data is not more valid ({})', type(exc).__name__) common.purge_credentials() self.session.cookies.clear() common.send_signal(signal=common.Signals.CLEAR_USER_ID_TOKENS) raise_from(NotLoggedInError, exc)
def login(self, modal_error_message=True): """Perform account login""" try: # First we get the authentication url without logging in, required for login API call react_context = website.extract_json(self.get('login'), 'reactContext') auth_url = website.extract_api_data(react_context)['auth_url'] LOG.debug('Logging in...') login_response = self.post('login', data=_login_payload( common.get_credentials(), auth_url)) try: website.extract_session_data(login_response, validate=True, update_profiles=True) LOG.info('Login successful') ui.show_notification(common.get_local_string(30109)) cookies.save(self.account_hash, self.session.cookies) return True except LoginValidateError as exc: self.session.cookies.clear() common.purge_credentials() if not modal_error_message: raise ui.show_ok_dialog(common.get_local_string(30008), unicode(exc)) except (MbrStatusNeverMemberError, MbrStatusFormerMemberError): if not modal_error_message: raise ui.show_error_info(common.get_local_string(30008), common.get_local_string(30180), False, True) except Exception: # pylint: disable=broad-except import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) self.session.cookies.clear() raise return False
def website_extract_session_data(self, content, **kwargs): """Extract session data and handle errors""" try: return website.extract_session_data(content, **kwargs) except WebsiteParsingError as exc: LOG.error('An error occurs in extract session data: {}', exc) raise except (LoginValidateError, MbrStatusAnonymousError) as exc: LOG.warn('The session data is not more valid ({})', type(exc).__name__) common.purge_credentials() self.session.cookies.clear() # Clear the user ID tokens are tied to the credentials self.msl_handler.clear_user_id_tokens() raise NotLoggedInError from exc
def try_refresh_session_data(self, raise_exception=False): """Refresh session data from the Netflix website""" from requests import exceptions try: self.auth_url = website.extract_session_data( self.get('browse'))['auth_url'] cookies.save(self.session.cookies) LOG.debug('Successfully refreshed session data') return True except MbrStatusError: raise except (WebsiteParsingError, MbrStatusAnonymousError) as exc: import traceback LOG.warn( 'Failed to refresh session data, login can be expired or the password has been changed ({})', type(exc).__name__) LOG.debug(G.py2_decode(traceback.format_exc(), 'latin-1')) self.session.cookies.clear() if isinstance(exc, MbrStatusAnonymousError): # This prevent the MSL error: No entity association record found for the user common.send_signal(signal=common.Signals.CLEAR_USER_ID_TOKENS) # Needed to do a new login common.purge_credentials() ui.show_notification(common.get_local_string(30008)) raise_from(NotLoggedInError, exc) except exceptions.RequestException: import traceback LOG.warn( 'Failed to refresh session data, request error (RequestException)' ) LOG.warn(G.py2_decode(traceback.format_exc(), 'latin-1')) if raise_exception: raise except Exception: # pylint: disable=broad-except import traceback LOG.warn( 'Failed to refresh session data, login expired (Exception)') LOG.debug(G.py2_decode(traceback.format_exc(), 'latin-1')) self.session.cookies.clear() if raise_exception: raise return False
def try_refresh_session_data(self, raise_exception=False): """Refresh session data from the Netflix website""" try: self.auth_url = website.extract_session_data( self.get('browse'))['auth_url'] cookies.save(self.session.cookies.jar) LOG.debug('Successfully refreshed session data') return True except MbrStatusError: raise except (WebsiteParsingError, MbrStatusAnonymousError) as exc: import traceback LOG.warn( 'Failed to refresh session data, login can be expired or the password has been changed ({})', type(exc).__name__) LOG.debug(traceback.format_exc()) self.session.cookies.clear() if isinstance(exc, MbrStatusAnonymousError): # This prevent the MSL error: No entity association record found for the user self.msl_handler.clear_user_id_tokens() # Needed to do a new login common.purge_credentials() ui.show_notification(common.get_local_string(30008)) raise NotLoggedInError from exc except httpx.RequestError: import traceback LOG.warn( 'Failed to refresh session data, request error (RequestError)') LOG.warn(traceback.format_exc()) if raise_exception: raise except Exception: # pylint: disable=broad-except import traceback LOG.warn( 'Failed to refresh session data, login expired (Exception)') LOG.debug(traceback.format_exc()) self.session.cookies.clear() if raise_exception: raise return False
def activate_profile(self, guid): """Set the profile identified by guid as active""" LOG.debug('Switching to profile {}', guid) current_active_guid = G.LOCAL_DB.get_active_profile_guid() if guid == current_active_guid: LOG.info( 'The profile guid {} is already set, activation not needed.', guid) return timestamp = time.time() LOG.info('Activating profile {}', guid) # 20/05/2020 - The method 1 not more working for switching PIN locked profiles # INIT Method 1 - HTTP mode # response = self._get('switch_profile', params={'tkn': guid}) # self.nfsession.auth_url = self.website_extract_session_data(response)['auth_url'] # END Method 1 # INIT Method 2 - API mode try: self.get_safe(endpoint='activate_profile', params={ 'switchProfileGuid': guid, '_': int(timestamp * 1000), 'authURL': self.auth_url }) except HttpError401 as exc: # Profile guid not more valid raise_from( InvalidProfilesError( 'Unable to access to the selected profile.'), exc) # Retrieve browse page to update authURL response = self.get_safe('browse') self.auth_url = website.extract_session_data(response)['auth_url'] # END Method 2 G.LOCAL_DB.switch_active_profile(guid) G.CACHE_MANAGEMENT.identifier_prefix = guid cookies.save(self.session.cookies)
def login_auth_data(self, data=None, password=None): """Perform account login with authentication data""" LOG.debug('Logging in with authentication data') # Add the cookies to the session self.session.cookies.clear() for cookie in data['cookies']: # The code below has been adapted from httpx.Cookies.set() method kwargs = { 'version': 0, 'name': cookie['name'], 'value': cookie['value'], 'port': None, 'port_specified': False, 'domain': cookie['domain'], 'domain_specified': bool(cookie['domain']), 'domain_initial_dot': cookie['domain'].startswith('.'), 'path': cookie['path'], 'path_specified': bool(cookie['path']), 'secure': cookie['secure'], 'expires': cookie['expires'], 'discard': True, 'comment': None, 'comment_url': None, 'rest': cookie['rest'], 'rfc2109': False, } cookie = Cookie(**kwargs) self.session.cookies.jar.set_cookie(cookie) cookies.log_cookie(self.session.cookies.jar) # Try access to website try: website.extract_session_data(self.get('browse'), validate=True, update_profiles=True) except MbrStatusAnonymousError: # Access not valid return False # Get the account e-mail page_response = self.get('your_account').decode('utf-8') email_match = re.search(r'account-email[^<]+>([^<]+@[^</]+)</', page_response) email = email_match.group(1).strip() if email_match else None if not email: raise WebsiteParsingError('E-mail field not found') # Verify the password (with parental control api) try: response = self.post_safe('profile_hub', data={ 'destination': 'contentRestrictions', 'guid': G.LOCAL_DB.get_active_profile_guid(), 'password': password, 'task': 'auth' }) if response.get('status') != 'ok': raise LoginError(common.get_local_string( 12344)) # 12344=Passwords entered did not match. except httpx.HTTPStatusError as exc: if exc.response.status_code == 500: # This endpoint raise HTTP error 500 when the password is wrong raise LoginError(common.get_local_string(12344)) from exc raise common.set_credentials({'email': email, 'password': password}) LOG.info('Login successful') ui.show_notification(common.get_local_string(30109)) cookies.save(self.session.cookies.jar) return True