diff --git a/requirements/dev.txt b/requirements/dev.txt index 31cd5d7..f19937e 100755 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -5,4 +5,4 @@ pytest-asyncio==0.10.0 pytest-mock==1.10.3 pytest-flakes==4.0.0 pytest-pythonpath==0.7.3 -pip-tools==4.3.0 +pip-tools==4.5.1 diff --git a/src/backend.py b/src/backend.py index dd6f813..fa7d886 100755 --- a/src/backend.py +++ b/src/backend.py @@ -1,4 +1,5 @@ import logging +import time import random import xml.etree.ElementTree as ET from dataclasses import dataclass @@ -107,9 +108,25 @@ async def _get_access_token(self): try: data = await response.json(content_type=None) self._access_token = data["access_token"] - except (ValueError, KeyError) as e: - logging.exception("Can not parse access token from backend response %s", repr(e)) - raise UnknownBackendResponse() + except (TypeError, ValueError, KeyError) as e: + self._log_session_duration() + try: + if data.get("error") == 'login_required': + raise AuthenticationRequired + else: + raise UnknownBackendResponse(data) + except AttributeError: + logging.exception(f"Error parsing access token: {repr(e)}, data: {data}") + raise UnknownBackendResponse + + def _log_session_duration(self): + try: + utag_main_cookie = next(filter(lambda c: c.key == 'utag_main', self._cookie_jar)) + utag_main = {i.split(':')[0]: i.split(':')[1] for i in utag_main_cookie.value.split('$')} + seconds_ago = int(time.time()) - int(utag_main['_st'][:10]) + logging.info('Session created %s hours ago', str(seconds_ago // 3600)) + except Exception as e: + logging.warning('Failed to get session duration: %s', repr(e)) class OriginBackendClient: @@ -363,26 +380,32 @@ async def get_lastplayed_games(self, user_id) -> Dict[MasterTitleId, Timestamp]: ''' - try: - def parse_title_id(product_info_xml) -> MasterTitleId: - return product_info_xml.find("masterTitleId").text + def parse_title_id(product_info_xml) -> MasterTitleId: + return product_info_xml.find("masterTitleId").text - def parse_timestamp(product_info_xml) -> Timestamp: - return Timestamp(int( - ( - datetime.strptime(product_info_xml.find("timestamp").text, "%Y-%m-%dT%H:%M:%S.%fZ") - - datetime(1970, 1, 1) - ).total_seconds() - )) + def parse_timestamp(product_info_xml) -> Timestamp: + formats = ( + "%Y-%m-%dT%H:%M:%S.%fZ", + "%Y-%m-%dT%H:%M:%SZ" # no microseconds + ) + td = product_info_xml.find("timestamp").text + for date_format in formats: + try: + time_delta = datetime.strptime(td, date_format) - datetime(1970, 1, 1) + except ValueError: + continue + return Timestamp(int(time_delta.total_seconds())) + raise ValueError(f"time data '{td}' does not match known formats") + try: content = await response.text() return { parse_title_id(product_info_xml): parse_timestamp(product_info_xml) for product_info_xml in ET.ElementTree(ET.fromstring(content)).iter("lastPlayed") } - except (ET.ParseError, AttributeError, ValueError): + except (ET.ParseError, AttributeError, ValueError) as e: logging.exception("Can not parse backend response: %s", await response.text()) - raise UnknownBackendResponse() + raise UnknownBackendResponse(e) async def get_favorite_games(self, user_id): response = await self._http_client.get("{base_api}/atom/users/{user_id}/privacySettings/FAVORITEGAMES".format( diff --git a/src/plugin.py b/src/plugin.py index e270e3f..8fbbb97 100755 --- a/src/plugin.py +++ b/src/plugin.py @@ -97,7 +97,7 @@ async def _do_authenticate(self, cookies): self._user_id, self._persona_id, user_name = await self._backend_client.get_identity() return Authentication(self._user_id, user_name) - except (AccessDenied, InvalidCredentials) as e: + except (AccessDenied, InvalidCredentials, AuthenticationRequired) as e: logging.exception("Failed to authenticate %s", repr(e)) raise InvalidCredentials() diff --git a/src/version.py b/src/version.py index ec6f2f3..0fd7496 100755 --- a/src/version.py +++ b/src/version.py @@ -1 +1,9 @@ -__version__ = "0.33" \ No newline at end of file +__version__ = "0.34" + +__changelog__ = { + "0.34": + """ + - fix rare bug while parsing game times (#16) + - fix handling status 400 with "login_error": go to "Credentials Lost" instead of "Offline. Retry" + """ +} \ No newline at end of file diff --git a/tests/test_game_times.py b/tests/test_game_times.py index a11a091..975a06f 100644 --- a/tests/test_game_times.py +++ b/tests/test_game_times.py @@ -110,7 +110,7 @@ async def test_not_authenticated(plugin, http_client): ''' + MASTER_TITLE_IDS[2] + ''' - 2019-02-27T14:55:30.135Z + 2019-02-27T14:55:30Z ''' # intentionally missing MASTER_TITLE_IDS[3]