From f746eb14b9ea686d7837b98e8d6c0786051e22e9 Mon Sep 17 00:00:00 2001 From: FriendsOfGalaxy Date: Fri, 15 Nov 2019 06:11:03 -0800 Subject: [PATCH] version 0.31 --- src/backend.py | 62 ++++++++++++++++ src/plugin.py | 35 +++++++-- src/version.py | 2 +- tests/conftest.py | 2 + tests/integration_test.py | 1 + tests/test_authentication.py | 4 +- tests/test_game_library_settings.py | 107 ++++++++++++++++++++++++++++ 7 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 tests/test_game_library_settings.py diff --git a/src/backend.py b/src/backend.py index 608d877..584f7a6 100755 --- a/src/backend.py +++ b/src/backend.py @@ -383,3 +383,65 @@ def parse_timestamp(product_info_xml) -> Timestamp: except (ET.ParseError, AttributeError, ValueError): logging.exception("Can not parse backend response: %s", await response.text()) raise UnknownBackendResponse() + + async def get_favorite_games(self, user_id): + response = await self._http_client.get("{base_api}/atom/users/{user_id}/privacySettings/FAVORITEGAMES".format( + base_api=self._get_api_host(), + user_id=user_id + )) + + ''' + + + + 1008620950926 + FAVORITEGAMES + OFB-EAST:48217;OFB-EAST:109552409;DR:119971300 + + + ''' + + try: + content = await response.text() + payload_xml = ET.ElementTree(ET.fromstring(content)).find("privacySetting/payload") + if payload_xml is None or payload_xml.text is None: + # No games tagged, if on object evaluates to false + return [] + + favorite_games = set(payload_xml.text.split(';')) + + return favorite_games + except (ET.ParseError, AttributeError, ValueError): + logging.exception("Can not parse backend response: %s", await response.text()) + raise UnknownBackendResponse() + + async def get_hidden_games(self, user_id): + response = await self._http_client.get("{base_api}/atom/users/{user_id}/privacySettings/HIDDENGAMES".format( + base_api=self._get_api_host(), + user_id=user_id + )) + + ''' + + + + 1008620950926 + HIDDENGAMES + 1.0|OFB-EAST:109552409;OFB-EAST:109552409 + + + ''' + + try: + content = await response.text() + payload_xml = ET.ElementTree(ET.fromstring(content)).find("privacySetting/payload") + if payload_xml is None or payload_xml.text is None: + # No games tagged, if on object evaluates to false + return [] + payload_text = payload_xml.text.replace('1.0|', '') + hidden_games = set(payload_text.split(';')) + + return hidden_games + except (ET.ParseError, AttributeError, ValueError): + logging.exception("Can not parse backend response: %s", await response.text()) + raise UnknownBackendResponse() diff --git a/src/plugin.py b/src/plugin.py index 51347a8..ddda294 100755 --- a/src/plugin.py +++ b/src/plugin.py @@ -15,13 +15,13 @@ AccessDenied, AuthenticationRequired, InvalidCredentials, UnknownBackendResponse, UnknownError ) from galaxy.api.plugin import create_and_run_plugin, Plugin -from galaxy.api.types import Achievement, Authentication, FriendInfo, Game, GameTime, LicenseInfo, NextStep +from galaxy.api.types import Achievement, Authentication, FriendInfo, Game, GameTime, LicenseInfo, NextStep, GameLibrarySettings from backend import AuthenticatedHttpClient, MasterTitleId, OfferId, OriginBackendClient, Timestamp from local_games import get_local_content_path, LocalGames from uri_scheme_handler import is_uri_handler_installed from version import __version__ - +import re def is_windows(): return platform.system().lower() == "windows" @@ -38,6 +38,14 @@ def is_windows(): "&redirect_uri=https://www.origin.com/views/login.html", "end_uri_regex": r"^https://www\.origin\.com/views/login\.html.*" } +def regex_pattern(regex): + return ".*" + re.escape(regex) + ".*" + +JS = {regex_pattern(r"originX/login?execution"): [ +r''' + document.getElementById("rememberMe").click(); +''' +]} MultiplayerId = NewType("MultiplayerId", str) AchievementsImportContext = namedtuple("AchievementsImportContext", ["owned_games", "achievements"]) @@ -97,7 +105,7 @@ async def authenticate(self, stored_credentials=None): stored_cookies = stored_credentials.get("cookies") if stored_credentials else None if not stored_cookies: - return NextStep("web_session", AUTH_PARAMS) + return NextStep("web_session", AUTH_PARAMS,js=JS) return await self._do_authenticate(stored_cookies) @@ -111,7 +119,6 @@ async def get_owned_games(self): self._check_authenticated() owned_offers = await self._get_owned_offers() - games = [] for offer in owned_offers: game = Game( @@ -304,6 +311,26 @@ async def get_game_time(self, game_id: OfferId, last_played_games: Any) -> GameT logging.exception("Failed to import game times") raise UnknownBackendResponse(str(e)) + async def prepare_game_library_settings_context(self, game_ids: List[str]) -> Any: + self._check_authenticated() + hidden_games = await self._backend_client.get_hidden_games(self._user_id) + favorite_games = await self._backend_client.get_favorite_games(self._user_id) + + library_context = {} + for game_id in game_ids: + library_context[game_id] = {'hidden': game_id in hidden_games, 'favorite': game_id in favorite_games} + return library_context + + async def get_game_library_settings(self, game_id: str, context: Any) -> GameLibrarySettings: + if not context: + # Unable to retrieve context + return GameLibrarySettings(game_id, None, None) + game_library_settings = context.get(game_id) + if game_library_settings is None: + # Able to retrieve context but game is not in its values -> It doesnt have any tags or hidden status set + return GameLibrarySettings(game_id, [], False) + return GameLibrarySettings(game_id, ['favorite'] if game_library_settings['favorite'] else [], game_library_settings['hidden']) + def game_times_import_complete(self): if self._persistent_cache_updated: self.push_cache() diff --git a/src/version.py b/src/version.py index fd12277..98195c0 100755 --- a/src/version.py +++ b/src/version.py @@ -1 +1 @@ -__version__ = "0.30" +__version__ = "0.31" diff --git a/tests/conftest.py b/tests/conftest.py index acc2dd1..ad2ab77 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -69,6 +69,8 @@ def backend_client(): mock.get_owned_games = AsyncMock() mock.get_friends = AsyncMock() mock.get_lastplayed_games = MagicMock() + mock.get_hidden_games = AsyncMock() + mock.get_favorite_games = AsyncMock() return mock diff --git a/tests/integration_test.py b/tests/integration_test.py index 037b882..2513619 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -56,6 +56,7 @@ def test_integration(): "ImportOwnedGames", "ImportAchievements", "ImportInstalledGames", + "ImportGameLibrarySettings", "LaunchGame", "InstallGame", "ShutdownPlatformClient", diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 85f5363..64f7f2d 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -4,7 +4,7 @@ from galaxy.api.types import Authentication, NextStep -from plugin import AUTH_PARAMS +from plugin import AUTH_PARAMS, JS def test_no_stored_credentials(plugin, http_client, backend_client): @@ -22,7 +22,7 @@ def test_no_stored_credentials(plugin, http_client, backend_client): with patch.object(plugin, "store_credentials") as store_credentials: result = loop.run_until_complete(plugin.authenticate()) - assert result == NextStep("web_session", AUTH_PARAMS) + assert result == NextStep("web_session", AUTH_PARAMS, js=JS) credentials = { "cookies": cookies, diff --git a/tests/test_game_library_settings.py b/tests/test_game_library_settings.py new file mode 100644 index 0000000..3fcc7fc --- /dev/null +++ b/tests/test_game_library_settings.py @@ -0,0 +1,107 @@ +import pytest +from galaxy.api.errors import AuthenticationRequired +from galaxy.api.types import GameLibrarySettings +from galaxy.unittest.mock import async_return_value + +GAME_IDS = ['DR:119971300', 'OFB-EAST:48217', 'OFB-EAST:109552409', 'Origin.OFR.50.0002694'] + +BACKEND_HIDDEN_RESPONSE = ''' + + + + 1008620950926 + HIDDENGAMES + Origin.OFR.50.0002694;OFB-EAST:109552409 + + +''' + +BACKEND_FAVORITES_RESPONSE = ''' + + + + 1008620950926 + FAVORITEGAMES + OFB-EAST:48217;OFB-EAST:109552409;DR:119971300 + + + ''' + +FAVORITE_GAMES = {'OFB-EAST:48217', 'OFB-EAST:109552409', 'DR:119971300'} +HIDDEN_GAMES = {'Origin.OFR.50.0002694', 'OFB-EAST:109552409'} + +GAME_LIBRARY_CONTEXT = { + 'OFB-EAST:48217': { + 'hidden': False, + 'favorite': True + }, + 'OFB-EAST:109552409': { + 'hidden': True, + 'favorite': True + }, + 'DR:119971300': { + 'hidden': False, + 'favorite': True + }, + 'Origin.OFR.50.0002694': { + 'hidden': True, + 'favorite': False + } +} + +GAME_LIBRARY_SETTINGS = GameLibrarySettings('OFB-EAST:48217', ['favorite'], False) + + +@pytest.mark.asyncio +async def test_not_authenticated(plugin, http_client): + http_client.is_authenticated.return_value = False + with pytest.raises(AuthenticationRequired): + await plugin.prepare_game_library_settings_context([]) + + +@pytest.mark.asyncio +async def test_prepare_library_settings_context( + authenticated_plugin, + backend_client, + user_id, + +): + backend_client.get_favorite_games.return_value = FAVORITE_GAMES + backend_client.get_hidden_games.return_value = HIDDEN_GAMES + + assert GAME_LIBRARY_CONTEXT == await authenticated_plugin.prepare_game_library_settings_context(GAME_IDS) + + backend_client.get_favorite_games.assert_called_once_with(user_id) + backend_client.get_hidden_games.assert_called_once_with(user_id) + + +@pytest.mark.asyncio +async def test_get_favorite_games( + backend_client, + user_id, + http_client, +): + http_client.get.return_value = async_return_value(BACKEND_FAVORITES_RESPONSE) + + assert FAVORITE_GAMES == await backend_client.get_favorite_games(user_id) + + +@pytest.mark.asyncio +async def test_get_hidden_games( + backend_client, + user_id, + http_client, +): + http_client.get.return_value = async_return_value(BACKEND_HIDDEN_RESPONSE) + + assert HIDDEN_GAMES == await backend_client.get_hidden_games(user_id) + + +@pytest.mark.asyncio +async def test_get_game_library_settings( + authenticated_plugin, +): + assert GAME_LIBRARY_SETTINGS == await authenticated_plugin.get_game_library_settings('OFB-EAST:48217', GAME_LIBRARY_CONTEXT) + + +