import asyncio import requests import functools import logging logging.getLogger("urllib3").setLevel(logging.WARNING) from http import HTTPStatus from galaxy.api.errors import ( AccessDenied, AuthenticationRequired, BackendTimeout, BackendNotAvailable, BackendError, NetworkError, UnknownError ) from consts import FIREFOX_AGENT class AccessTokenExpired(Exception): pass class BackendClient(object): def __init__(self, plugin, authentication_client): self._plugin = plugin self._authentication_client = authentication_client async def _authenticated_request(self, method, url, data=None, json=True, headers=None, ignore_failure=False): try: return await self.do_request(method, url, data, json, headers, ignore_failure) except (AccessDenied, AuthenticationRequired): logging.info('Refreshing credentials') try: await self.refresh_cookies() self._authentication_client.refresh_credentials() except AuthenticationRequired: self._plugin.lost_authentication() raise except Exception as e: logging.error(repr(e)) raise return await self.do_request(method, url, data, json, headers, ignore_failure) @staticmethod def handle_status_code(status_code): logging.debug(f'Status code: {status_code}') if status_code == HTTPStatus.UNAUTHORIZED: raise AuthenticationRequired() if status_code == HTTPStatus.FORBIDDEN: raise AccessDenied() if status_code == HTTPStatus.SERVICE_UNAVAILABLE: raise BackendNotAvailable() if status_code >= 500: raise BackendError() if status_code >= 400: raise UnknownError() async def do_request(self, method, url, data=None, json=True, headers=None, ignore_failure=False): loop = asyncio.get_event_loop() if not headers: headers = self._authentication_client.session.headers try: if data is None: data = {} params = { "method": method, "url": url, "data": data, "timeout": self._authentication_client.timeout, "headers": headers } try: response = await loop.run_in_executor(None, functools.partial(self._authentication_client.session.request, **params)) except (requests.Timeout, requests.ConnectTimeout, requests.ReadTimeout): raise BackendTimeout() except requests.ConnectionError: raise NetworkError if not ignore_failure: self.handle_status_code(response.status_code) if json: return response.json() else: return response except Exception as e: raise e async def refresh_cookies(self): headers = { 'User-Agent': FIREFOX_AGENT } r = await self.do_request('GET', f"{self._authentication_client.blizzard_accounts_url}/games", json=False, headers=headers, ignore_failure=True) # verbose log responses in this function due to large probability of failure headers = { 'User-Agent': FIREFOX_AGENT, "Referer": f"{self._authentication_client.blizzard_accounts_url}/games", } r = await self.do_request("GET", f"{self._authentication_client.blizzard_accounts_url}/api/games-and-subs", json=False, headers=headers, ignore_failure=True) if r.status_code != 401: return headers = { 'User-Agent': FIREFOX_AGENT, "Referer": f"{self._authentication_client.blizzard_accounts_url}/api/" } r = await self.do_request("GET", f"{self._authentication_client.blizzard_accounts_url}:443/oauth2/authorization/account-settings", json=False, headers=headers) headers = { 'User-Agent': FIREFOX_AGENT } r = await self.do_request("GET", f"{self._authentication_client.blizzard_accounts_url}/api/games-and-subs", json=False, headers=headers) return async def get_user_info(self): url = f"{self._authentication_client.blizzard_oauth_url}/userinfo" return await self._authenticated_request("GET", url) async def get_account_details(self): details_url = f"{self._authentication_client.blizzard_accounts_url}/api/details" return await self.do_request("GET", details_url) async def get_owned_games(self): games_url = f"{self._authentication_client.blizzard_accounts_url}/api/games-and-subs" return await self._authenticated_request("GET", games_url) async def get_owned_classic_games(self): games_url = f"{self._authentication_client.blizzard_accounts_url}/api/classic-games" return await self._authenticated_request("GET", games_url) async def validate_access_token(self, access_token): # this is inconsistent with the documentation https://develop.battle.net/documentation/api-reference/oauth-api token_url = f"{self._authentication_client.blizzard_oauth_url}/check_token" # return await self.do_request()("POST", token_url, data={"token": access_token}) return await self.do_request("POST", token_url, data={"token": access_token}, ignore_failure=True) async def get_sc2_player_data(self, account_id): url = f"{self._authentication_client.blizzard_api_url}/sc2/player/{account_id}" return await self._authenticated_request("GET", url) async def get_sc2_profile_data(self, region_id, realm_id, player_id): url = f"{self._authentication_client.blizzard_api_url}/sc2/profile/{region_id}/{realm_id}/{player_id}" return await self._authenticated_request("GET", url) async def get_wow_character_data(self): url = f"{self._authentication_client.blizzard_api_url}/wow/user/characters" return await self._authenticated_request("GET", url) async def get_wow_character_achievements(self, realm, character_name): url = f"{self._authentication_client.blizzard_api_url}/wow/character/{realm.lower()}/{character_name}?fields=achievements" return await self.do_request("GET", url) async def get_ow_player_data(self): player_name = self._authentication_client.user_details['battletag'] url = f"https://owapi.io/profile/pc/{self._authentication_client.region}/{player_name.replace('#', '-')}" return await self.do_request('GET', url)