def parse(text, user_profile_url): html = HTML(html=text) # find persona_name div = html.find("div.profile_header_centered_persona", first=True) if not div: logger.error("Can not parse backend response - no div.profile_header_centered_persona") raise UnknownBackendResponse() span = div.find("span.actual_persona_name", first=True) if not span: logger.error("Can not parse backend response - no span.actual_persona_name") raise UnknownBackendResponse() persona_name = span.text # find steam id variable = 'g_steamID = "' start = text.find(variable) if start == -1: logger.error("Can not parse backend response - no g_steamID variable") raise UnknownBackendResponse() start += len(variable) end = text.find('";', start) steam_id = text[start:end] # find miniprofile id profile_link = f'{user_profile_url}" data-miniprofile="' start = text.find(profile_link) if start == -1: logger.error("Can not parse backend response - no steam profile href") raise UnknownBackendResponse() start += len(profile_link) end = text.find('">', start) miniprofile_id = text[start:end] return steam_id, miniprofile_id, persona_name
async def get_unlocked_achievements( self, game_id: str, context: AchievementsImportContext) -> List[Achievement]: try: achievements_set = context.owned_games[game_id] except KeyError: logging.exception( "Game '{}' not found amongst owned".format(game_id)) raise UnknownBackendResponse() if not achievements_set: return [] try: # for some games(e.g.: ApexLegends) achievement set is not present in "all". have to fetch it explicitly achievements = context.achievements.get(achievements_set) if achievements is not None: return achievements return (await self._backend_client.get_achievements( self._persona_id, achievements_set))[achievements_set] except KeyError: logging.exception( "Failed to parse achievements for game {}".format(game_id)) raise UnknownBackendResponse()
async def fetch_paginated_data( self, parser, url, counter_name, limit=DEFAULT_LIMIT, *args, **kwargs ): response = await self._http_client.get(paginate_url(url=url, limit=limit), *args, **kwargs) if not response: return [] try: total = int(response.get(counter_name, 0)) except ValueError: raise UnknownBackendResponse() responses = [response] + await asyncio.gather(*[ self._http_client.get(paginate_url(url=url, limit=limit, offset=offset), *args, **kwargs) for offset in range(limit, total, limit) ]) try: return [rec for res in responses for rec in parser(res)] except Exception: logging.exception("Cannot parse data") raise UnknownBackendResponse()
def parse(text): html = HTML(html=text) profile_url = html.find("a.user_avatar", first=True) if not profile_url: raise UnknownBackendResponse() try: return profile_url.attrs["href"] except KeyError: return UnknownBackendResponse()
def parse(text): html = HTML(html=text) profile_url = html.find("a.user_avatar", first=True) if not profile_url: logging.error("Can not parse backend response - no a.user_avatar") raise UnknownBackendResponse() try: return profile_url.attrs["href"] except KeyError: logging.exception("Can not parse backend response") return UnknownBackendResponse()
async def import_games_achievements(self, _game_ids): game_ids = set(_game_ids) error = UnknownError() try: achievement_sets = { } # 'offerId' to 'achievementSet' names mapping for offer_id, achievement_set in ( await self._backend_client.get_achievements_sets(self._persona_id )).items(): if not achievement_set: self.game_achievements_import_success(offer_id, []) game_ids.remove(offer_id) else: achievement_sets[offer_id] = achievement_set if not achievement_sets: return for offer_id, achievements in ( await self._backend_client.get_achievements( self._persona_id, achievement_sets)).items(): try: self.game_achievements_import_success( offer_id, [ Achievement(achievement_id=key, achievement_name=value["name"], unlock_time=value["u"]) for key, value in achievements.items() if value["complete"] ]) except KeyError: self.game_achievements_import_failure( offer_id, UnknownBackendResponse()) except ApplicationError as error: self.game_achievements_import_failure(offer_id, error) finally: game_ids.remove(offer_id) except KeyError: error = UnknownBackendResponse() except ApplicationError as _error: error = _error except Exception: pass # handled below finally: # any other exceptions or not answered game_ids are responded with an error [ self.game_achievements_import_failure(game_id, error) for game_id in game_ids ]
async def get_game_time(self, pid, master_title_id, multiplayer_id): url = "{}/atom/users/{}/games/{}/usage".format( self._get_api_host(), pid, master_title_id ) # 'multiPlayerId' must be used if exists, otherwise '**/lastplayed' backend returns zero headers = {} if multiplayer_id: headers["Multiplayerid"] = multiplayer_id response = await self._http_client.get(url, headers=headers) """ response looks like following: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <usage> <gameId>192140</gameId> <total>30292</total> <MultiplayerId>1024390</MultiplayerId> <lastSession>9</lastSession> <lastSessionEndTimeStamp>1497190184759</lastSessionEndTimeStamp> </usage> """ try: content = await response.text() xml_response = ET.fromstring(content) total_play_time = round(int(xml_response.find("total").text)/60) # response is in seconds last_session_end_time = round(int(xml_response.find("lastSessionEndTimeStamp").text)/1000) # response is in miliseconds return total_play_time, last_session_end_time except (ET.ParseError, AttributeError, ValueError): logging.exception("Can not parse backend response") raise UnknownBackendResponse()
async def get_access_token(self, refresh_token=None, url=OAUTH_TOKEN_URL, cookies=None): response = None if cookies is None: cookies = {"npsso": refresh_token} try: response = await super().request("GET", url=url, cookies=cookies, allow_redirects=False) location_params = urlsplit(response.headers["Location"]) self._validate_auth_response(location_params) fragment = dict(parse_qsl(location_params.fragment)) if 'access_token' not in fragment: return await self.get_access_token( url=response.headers['Location'], cookies=response.cookies) self._store_new_npsso(cookies) return fragment["access_token"] except AuthenticationRequired as e: raise InvalidCredentials(e.data) except (KeyError, IndexError): raise UnknownBackendResponse(str(response.headers)) finally: if response: response.close()
async def _authenticate(self, username=None, password=None, two_factor=None): if two_factor: return await self._steam_client.communication_queues['websocket'].put({'password': password, 'two_factor': two_factor}) if not username or not password: raise UnknownBackendResponse() self._user_info_cache.account_username = username await self._steam_client.communication_queues['websocket'].put({'password': password})
async def get_friends(self, user_id): response = await self._http_client.get( "{base_api}/atom/users/{user_id}/other/{other_user_id}/friends?page={page}" .format(base_api=self._get_api_host(), user_id=user_id, other_user_id=user_id, page=0)) """ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <users> <user> <userId>1003118773678</userId> <personaId>1781965055</personaId> <EAID>martinaurtica</EAID> </user> <user> <userId>1008880909879</userId> <personaId>1004303509879</personaId> <EAID>testerg976</EAID> </user> </users> """ try: content = await response.text() return { user_xml.find("userId").text: user_xml.find("EAID").text for user_xml in ET.ElementTree(ET.fromstring(content)).iter( "user") } except (ET.ParseError, AttributeError, ValueError): logging.exception("Can not parse backend response: %s", await response.text()) raise UnknownBackendResponse()
async def _get_access_token(self): url = "https://accounts.ea.com/connect/auth" params = { "client_id": "ORIGIN_JS_SDK", "response_type": "token", "redirect_uri": "nucleus:rest", "prompt": "none" } response = await super().request("GET", url, params=params) try: data = await response.json(content_type=None) self._access_token = data["access_token"] except (TypeError, ValueError, KeyError) as e: self._log_session_details() 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 else: self._save_lats()
def handle_exception(): """ Context manager translating network related exceptions to custom :mod:`~galaxy.api.errors`. """ try: yield except asyncio.TimeoutError: raise BackendTimeout() except aiohttp.ServerDisconnectedError: raise BackendNotAvailable() except aiohttp.ClientConnectionError: raise NetworkError() except aiohttp.ContentTypeError as error: raise UnknownBackendResponse(error.message) except aiohttp.ClientResponseError as error: if error.status == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired(error.message) if error.status == HTTPStatus.FORBIDDEN: raise AccessDenied(error.message) if error.status == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable(error.message) if error.status == HTTPStatus.TOO_MANY_REQUESTS: raise TooManyRequests(error.message) if error.status >= 500: raise BackendError(error.message) if error.status >= 400: logger.warning("Got status %d while performing %s request for %s", error.status, error.request_info.method, str(error.request_info.url)) raise UnknownError(error.message) except aiohttp.ClientError as e: logger.exception("Caught exception while performing request") raise UnknownError(repr(e))
def _parse_catalog_item(items): try: item = list(items.values())[0] categories = [category["path"] for category in item["categories"]] return CatalogItem(item["id"], item["title"], categories) except (IndexError, KeyError): raise UnknownBackendResponse()
async def get_favorite_games(self, user_id) -> Set[OfferId]: 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)) ''' <?xml version="1.0" encoding="UTF-8"?> <privacySettings> <privacySetting> <userId>1008620950926</userId> <category>FAVORITEGAMES</category> <payload>OFB-EAST:48217;OFB-EAST:109552409;DR:119971300</payload> </privacySetting> </privacySettings> ''' 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 set() favorite_games = set(OfferId(payload_xml.text.split(';'))) return favorite_games except (ET.ParseError, AttributeError, ValueError): logger.exception("Can not parse backend response: %s", await response.text()) raise UnknownBackendResponse()
def achievement_parser(achievement_tag: Optional[AchievementTag]) -> Achievement: name_tag = achievement_tag.h2 if not name_tag: raise UnknownBackendResponse("Cannot find achievement name tag") achievement_name = achievement_tag.h2.get_text() if not achievement_name: raise UnknownBackendResponse("Failed to parse achievement name") return Achievement( unlock_time=self._achievements_cache.setdefault( achievement_name , Timestamp(int(datetime.utcnow().timestamp())) ) , achievement_name=achievement_name )
async def _authenticate(self, grant_type, secret): headers = { "Authorization": "basic " + self._BASIC_AUTH_CREDENTIALS, "User-Agent": self.LAUNCHER_USER_AGENT } data = { "grant_type": grant_type, "token_type": "eg1" } data[grant_type] = secret try: response = await self._request("POST", self._OAUTH_URL, headers=headers, data=data) except Exception as e: logging.exception(f"Authentication failed, grant_type: {grant_type}") raise e result = await response.json() try: self._access_token = result["access_token"] self._refresh_token = result["refresh_token"] self._account_id = result["account_id"] credentials = {"refresh_token": self._refresh_token} self._store_credentials(credentials) except KeyError: logging.exception("Can not parse backend response") raise UnknownBackendResponse()
async def test_prepare_achievements_context_error( authenticated_plugin, mock_get_game_communication_ids, mock_async_get_earned_trophies): mock_get_game_communication_ids.side_effect = UnknownBackendResponse() with pytest.raises(UnknownBackendResponse): await authenticated_plugin.prepare_achievements_context([GAME_ID])
async def _get_active_subscription( self, subscription_uri) -> Optional[SubscriptionDetails]: def parse_timestamp(timestamp: str) -> Timestamp: return Timestamp( int((datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S") - datetime(1970, 1, 1)).total_seconds())) response = await self._http_client.get(subscription_uri) try: data = await response.json() sub_status = self._get_subscription_status(data) if data and sub_status == 'enabled': return SubscriptionDetails( tier=data['Subscription']['subscriptionLevel'].lower(), end_time=parse_timestamp( data['Subscription']['nextBillingDate'])) else: logger.debug( f"Cannot get data from response or subscription status is not 'ENABLED': {data}" ) return None except (ValueError, KeyError) as e: logger.exception( "Can not parse backend response while getting subs details: %s, error %s", await response.text(), repr(e)) raise UnknownBackendResponse()
async def test_invalid_stored_credentials(backend_client, plugin, stored_credentials, mocker): backend_client.get_profile.side_effect = UnknownBackendResponse() lost_authentication = mocker.patch.object(plugin, "lost_authentication") with pytest.raises(InvalidCredentials): await plugin.authenticate(stored_credentials) lost_authentication.assert_not_called()
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)) ''' <?xml version="1.0" encoding="UTF-8"?> <privacySettings> <privacySetting> <userId>1008620950926</userId> <category>HIDDENGAMES</category> <payload>1.0|OFB-EAST:109552409;OFB-EAST:109552409</payload> </privacySetting> </privacySettings> ''' 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()
async def request(self, method, url, *args, **kwargs): try: response = await self._session.request(method, url, *args, **kwargs) except asyncio.TimeoutError: raise BackendTimeout() except aiohttp.ServerDisconnectedError: raise BackendNotAvailable() except aiohttp.ClientConnectionError: raise NetworkError() except aiohttp.ContentTypeError: raise UnknownBackendResponse() except aiohttp.ClientError: logging.exception( "Caught exception while running {} request for {}".format( method, url)) raise UnknownError() if response.status == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if response.status == HTTPStatus.FORBIDDEN: raise AccessDenied() if response.status == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if response.status == HTTPStatus.TOO_MANY_REQUESTS: raise TooManyRequests() if response.status >= 500: raise BackendError() if response.status >= 400: logging.warning( "Got status {} while running {} request for {}".format( response.status, method, url)) raise UnknownError() return response
async def get_steam_sharing_games(self, owngames: List[str]) -> List[Game]: profiles = list( filter( lambda x: x.user_name.endswith(FRIEND_SHARING_END_PATTERN + "*" ), self._own_friends)) newgames: List[Game] = [] self._family_sharing_games = [] for i in profiles: othergames = await self._client.get_games(i.user_id) try: for game in othergames: hasit = any(f == str(game["appid"]) for f in owngames) or any( f.game_id == str(game["appid"]) for f in newgames) if not hasit: self._family_sharing_games.append(str(game["appid"])) newgame = Game( str(game["appid"]), game["name"], [], LicenseInfo(LicenseType.OtherUserLicense, i.user_name)) newgames.append(newgame) except (KeyError, ValueError): logging.exception("Can not parse backend response") raise UnknownBackendResponse() return newgames
def parse_response(text): def parse_id(profile): return profile.attrs["data-steamid"] def parse_name(profile): return HTML(html=profile.html).find( ".friend_block_content", first=True).text.split("\n")[0] def parse_avatar(profile): avatar_html = HTML(html=profile.html).find(".player_avatar", first=True).html return HTML(html=avatar_html).find("img")[0].attrs.get("src") def parse_url(profile): return HTML(html=profile.html).find( ".selectable_overlay", first=True).attrs.get('href') try: search_results = HTML(html=text).find("#search_results", first=True).html return [ UserInfo(user_id=parse_id(profile), user_name=parse_name(profile), avatar_url=parse_avatar(profile), profile_url=parse_url(profile)) for profile in HTML( html=search_results).find(".friend_block_v2") ] except (AttributeError, ValueError, TypeError): logger.exception("Can not parse backend response") raise UnknownBackendResponse()
async def get_owned_games(self): if self._steam_id is None: raise AuthenticationRequired() games = await self._client.get_games(self._steam_id) owned_games = [] try: for game in games: owned_games.append( Game(str(game["appid"]), game["name"], [], LicenseInfo(LicenseType.SinglePurchase, None))) except (KeyError, ValueError): logging.exception("Can not parse backend response") raise UnknownBackendResponse() self._own_games = games game_ids = list(map(lambda x: x.game_id, owned_games)) other_games = await self.get_steam_sharing_games(game_ids) for i in other_games: owned_games.append(i) return owned_games
def _get_subscription_status(self, response_data: Dict) -> Optional[str]: try: return response_data['Subscription']['status'].lower( ) if response_data else None except (ValueError, KeyError) as e: logger.exception("No 'status' key in response", response_data, repr(e)) raise UnknownBackendResponse()
async def fetch_data(self, parser, *args, **kwargs): response = await self._http_client.get(*args, **kwargs) try: return parser(response) except Exception: logging.exception("Cannot parse data") raise UnknownBackendResponse()
async def get_servers(self) -> List[str]: url = "http://api.steampowered.com/ISteamDirectory/GetCMList/v1/?cellid=0" response = await self._http_client.get(url) try: data = await response.json() return data['response']['serverlist_websockets'] except (ValueError, KeyError): logging.exception("Can not parse backend response") raise UnknownBackendResponse()
async def get_authentication_data(self) -> Tuple[int, str, str]: url = "https://steamcommunity.com/chat/clientjstoken" response = await self._http_client.get(url) try: data = await response.json() return int(data["steamid"]), data["account_name"], data["token"] except (ValueError, KeyError): logger.exception("Can not parse backend response") raise UnknownBackendResponse()
def parse(text): html = HTML(html=text) # find login pulldown = html.find("#account_pulldown", first=True) if not pulldown: raise UnknownBackendResponse() login = pulldown.text # find steam id variable = 'g_steamID = "' start = text.find(variable) if start == -1: raise UnknownBackendResponse() start += len(variable) end = text.find('";', start) steam_id = text[start:end] return steam_id, login
async def authenticate(self, refresh_token): self._refresh_token = refresh_token try: self._access_token = await self.get_access_token( self._refresh_token) finally: self.can_refresh.set() if not self._access_token: raise UnknownBackendResponse("Empty access token")