예제 #1
0
        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
예제 #2
0
    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()
예제 #3
0
    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()
예제 #4
0
 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()
예제 #5
0
 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()
예제 #6
0
    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
            ]
예제 #7
0
    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()
예제 #8
0
 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()
예제 #9
0
 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})
예제 #10
0
 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()
예제 #11
0
    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()
예제 #12
0
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))
예제 #13
0
 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()
예제 #15
0
        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
            )
예제 #16
0
    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()
예제 #19
0
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()
예제 #20
0
    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
예제 #22
0
    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
예제 #23
0
        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()
예제 #24
0
    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()
예제 #26
0
    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()
예제 #27
0
 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()
예제 #28
0
 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()
예제 #29
0
        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
예제 #30
0
 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")