Skip to content

Commit

Permalink
aiohttp 2.0 support, refactoring
Browse files Browse the repository at this point in the history
Use aiohttp 2.0 exceptions, only try to import SocksError in exceptions
and import from there, PTC auth refactoring, update TimedConnector to
match aiohttp 2.0, use new aiohttp json request parameter, some PGoApi,
PGoApiRequest, and RpcApi refactoring, add more headers to RPCs, add
optimal json loads and json dumps functions to __init__.
  • Loading branch information
Noctem committed Mar 29, 2017
1 parent 15e5625 commit 6e5f5f4
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 453 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ install:
- pip install -r requirements.txt
- python setup.py install
script:
- python -c 'import aiopogo'
- python -c 'from aiopogo import auth_google, auth_ptc, auth, connector, exceptions, hash_server, pgoapi, rpc_api, session, utilities'
25 changes: 15 additions & 10 deletions aiopogo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
from .exceptions import PleaseInstallProtobufVersion3

import logging

__title__ = 'aiopogo'
__version__ = '1.5.2'
__version__ = '1.6.0b1'
__author__ = 'David Christenson'
__license__ = 'MIT License'
__copyright__ = 'Copyright (c) 2017 David Christenson <https://github.com/Noctem>'

protobuf_exist = False
protobuf_version = 0
try:
from google import protobuf
protobuf_version = protobuf.__version__
protobuf_exist = True
from google import protobuf as _protobuf
protobuf_version = _protobuf.__version__
except ImportError:
raise PleaseInstallProtobufVersion3('Protobuf not found, install it.')

if int(protobuf_version[:1]) < 3:
raise PleaseInstallProtobufVersion3('Protobuf 3 needed, you have {}'.format(protobuf_version))

from functools import partial as _partial

try:
from ujson import dumps as _dumps, loads as json_loads
json_dumps = _partial(_dumps, escape_forward_slashes=False)
except ImportError:
from json import dumps as _dumps, loads as json_loads
from .utilities import JSONByteEncoder
json_dumps = _partial(_dumps, cls=JSONByteEncoder)

from .pgoapi import PGoApi
from .rpc_api import RpcApi, RPC_SESSIONS
from .auth import Auth
from .rpc_api import RpcApi
from .hash_server import HashServer

def close_sessions():
RPC_SESSIONS.close()
RpcApi.sessions.close()
HashServer.close_session()

def activate_hash_server(hash_token, conn_limit=300):
Expand Down
248 changes: 99 additions & 149 deletions aiopogo/auth_ptc.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,39 @@
from urllib.parse import parse_qs, urlsplit
from asyncio import get_event_loop, TimeoutError, CancelledError
from asyncio import get_event_loop, TimeoutError
from time import time
from functools import partial

from aiohttp import TCPConnector, ClientSession, ClientError, DisconnectedError, HttpProcessingError
try:
from aiosocks.errors import SocksError
except ImportError:
class SocksError(Exception): pass
from aiohttp import TCPConnector, ClientSession, ClientError, ClientHttpProxyError, ClientProxyConnectionError, ClientResponseError, ServerTimeoutError

from .session import socks_connector, CONN_TIMEOUT
from . import json_loads
from .session import socks_connector
from .auth import Auth
from .exceptions import ActivationRequiredException, AuthConnectionException, AuthException, AuthTimeoutException, InvalidCredentialsException, ProxyException, TimeoutException

try:
import ujson as json

jexc = ValueError
except ImportError:
import json

jexc = jexc = (json.JSONDecodeError, ValueError)
from .exceptions import ActivationRequiredException, AuthConnectionException, AuthException, AuthTimeoutException, InvalidCredentialsException, ProxyException, SocksError, TimeoutException

class AuthPtc(Auth):

PTC_LOGIN_URL = 'https://sso.pokemon.com/sso/login?service=https%3A%2F%2Fsso.pokemon.com%2Fsso%2Foauth2.0%2FcallbackAuthorize'
PTC_LOGIN_OAUTH = 'https://sso.pokemon.com/sso/oauth2.0/accessToken'
PTC_LOGIN_CLIENT_SECRET = 'w8ScCUXJQc6kXKw8FiOhd8Fixzht18Dq3PEVkUCP5ZPxtgyWsbTvWHFLm2wNY0JR'
loop = get_event_loop()

def __init__(self, username=None, password=None, proxy=None, user_agent=None, timeout=None):
Auth.__init__(self)

self._auth_provider = 'ptc'
self._session = None

self._username = username
self._password = password
self.user_agent = user_agent or 'pokemongo/0 CFNetwork/758.5.3 Darwin/15.6.0'
self.timeout = timeout or 10

if proxy and proxy.startswith('socks'):
self.socks_proxy = proxy
self.conn = partial(socks_connector, proxy=proxy, loop=self.loop)
self.proxy = None
else:
self.socks_proxy = None
self.conn = partial(TCPConnector, loop=self.loop, verify_ssl=False)
self.proxy = proxy

def activate_session(self):
if self._session and not self._session.closed:
return
if self.socks_proxy:
conn = socks_connector(self.socks_proxy, loop=self.loop)
else:
conn = TCPConnector(loop=self.loop, verify_ssl=False, conn_timeout=CONN_TIMEOUT)
self._session = ClientSession(connector=conn,
loop=self.loop,
headers={'User-Agent': self.user_agent})

def close_session(self):
if self._session.closed:
return
self._session.close()
self.session = partial(
ClientSession,
loop=self.loop,
headers=(('User-Agent', user_agent or 'pokemongo/0 CFNetwork/758.5.3 Darwin/15.6.0'),),
raise_for_status=True,
conn_timeout=5.0,
read_timeout=timeout or 10.0)

async def user_login(self, username=None, password=None, retry=True):
self._username = username or self._username
Expand All @@ -70,130 +44,106 @@ async def user_login(self, username=None, password=None, retry=True):

self.log.info('PTC User Login for: {}'.format(self._username))
self._access_token = None
self.activate_session()
try:
now = time()
async with self._session.get(self.PTC_LOGIN_URL, timeout=self.timeout, proxy=self.proxy) as resp:
resp.raise_for_status()
data = await resp.json(loads=json.loads)
login_url = 'https://sso.pokemon.com/sso/oauth2.0/authorize?client_id=mobile-app_pokemon-go&redirect_uri=https%3A%2F%2Fwww.nianticlabs.com%2Fpokemongo%2Ferror&locale=en'
async with self.session(connector=self.conn()) as session:
async with session.get(login_url, proxy=self.proxy) as resp:
data = await resp.json(encoding='utf-8', loads=json_loads, content_type=None)

try:
assert 'lt' in data
data['_eventId'] = 'submit'
data['username'] = self._username
data['password'] = self._password
except TypeError as e:
raise AuthException('Invalid initial JSON response.') from e

async with self._session.post(self.PTC_LOGIN_URL, data=data, timeout=self.timeout, proxy=self.proxy, allow_redirects=False) as resp:
resp.raise_for_status()
try:
qs = parse_qs(urlsplit(resp.headers['Location'])[3])
self._refresh_token = qs['ticket'][0]
self._access_token = resp.cookies['CASTGC'].value
except KeyError:
try:
j = await resp.json(loads=json.loads)
except jexc as e:
raise AuthException('Unable to decode second response.') from e

login_url = 'https://sso.pokemon.com/sso/login?service=https%3A%2F%2Fsso.pokemon.com%2Fsso%2Foauth2.0%2FcallbackAuthorize&locale=en'
async with session.post(login_url, data=data, proxy=self.proxy, allow_redirects=False) as resp:
try:
if j.get('error_code') == 'users.login.activation_required':
raise ActivationRequiredException('Account email not verified.')
error = j['errors'][0]
raise AuthException(error)
except (AttributeError, KeyError, IndexError) as e:
raise AuthException('Unable to login or get error information.') from e

if self._access_token:
self._login = True
self._access_token_expiry = now + 7200.0
self.log.info('PTC User Login successful.')
elif self._refresh_token and retry:
return await self.get_access_token()
return self._access_token
except HttpProcessingError as e:
qs = parse_qs(urlsplit(resp.headers['Location'])[3])
self._refresh_token = qs['ticket'][0]
self._access_token = resp.cookies['CASTGC'].value
except (KeyError, AttributeError, TypeError, IndexError):
try:
j = await resp.json(encoding='utf-8', loads=json_loads, content_type=None)
except ValueError as e:
raise AuthException('Unable to decode second response.') from e
try:
if j.get('error_code') == 'users.login.activation_required':
raise ActivationRequiredException('Account email not verified.')
error = j['errors'][0]
raise AuthException(error)
except (KeyError, AttributeError, TypeError, IndexError) as e:
raise AuthException('Unable to login or get error information.') from e
except (ClientHttpProxyError, ClientProxyConnectionError, SocksError) as e:
raise ProxyException('Proxy connection error during user_login.') from e
except ClientResponseError as e:
raise AuthConnectionException('Error {} during user_login: {}'.format(e.code, e.message))
except (TimeoutError, TimeoutException) as e:
except (TimeoutError, ServerTimeoutError) as e:
raise AuthTimeoutException('user_login timeout.') from e
except (ProxyException, SocksError) as e:
raise ProxyException('Proxy connection error during user_login.') from e
except jexc as e:
raise AuthException('Unable to parse user_login response.') from e
except (ClientError, DisconnectedError) as e:
err = e.__cause__ or e
raise AuthConnectionException('{} during user_login.'.format(err.__class__.__name__)) from e
except (AuthException, CancelledError):
raise
except Exception as e:
raise AuthException('{} during user_login.'.format(e.__class__.__name__)) from e
finally:
self.close_session()

def set_refresh_token(self, refresh_token):
self.log.info('PTC Refresh Token provided by user')
self._refresh_token = refresh_token
except ClientError as e:
raise AuthConnectionException('{} during user_login.'.format(e.__class__.__name__)) from e
except (AssertionError, TypeError, ValueError) as e:
raise AuthException('Invalid initial JSON response.') from e

if self._access_token:
self._login = True
self._access_token_expiry = now + 7200.0
self.log.info('PTC User Login successful.')
elif self._refresh_token and retry:
return await self.get_access_token()
return self._access_token

async def get_access_token(self, force_refresh=False):
if force_refresh is False and self.check_access_token():
if not force_refresh and self.check_access_token():
self.log.debug('Using cached PTC Access Token')
return self._access_token
elif self._refresh_token is None:
return await self.user_login()
else:
self.activate_session()
self._login = False
self._access_token = None
self.log.info('Request PTC Access Token...')

data = {
'code': self._refresh_token,
'grant_type': 'refresh_token',
'client_secret': 'w8ScCUXJQc6kXKw8FiOhd8Fixzht18Dq3PEVkUCP5ZPxtgyWsbTvWHFLm2wNY0JR',
'redirect_uri': 'https://www.nianticlabs.com/pokemongo/error',
'client_id': 'mobile-app_pokemon-go'
}

try:
self._login = False
self._access_token = None
if force_refresh:
self.log.info('Forced request of PTC Access Token!')
else:
self.log.info('Request PTC Access Token...')

data = {
'client_id': 'mobile-app_pokemon-go',
'redirect_uri': 'https://www.nianticlabs.com/pokemongo/error',
'client_secret': self.PTC_LOGIN_CLIENT_SECRET,
'grant_type': 'refresh_token',
'code': self._refresh_token
}

async with self._session.post(self.PTC_LOGIN_OAUTH, data=data, timeout=self.timeout, proxy=self.proxy) as resp:
self._refresh_token = None
resp.raise_for_status()
qs = await resp.text()
token_data = parse_qs(qs)
try:
self._access_token = token_data['access_token'][0]
except (KeyError, IndexError):
return await self.user_login(retry=False)

if self._access_token is not None:
# set expiration to an hour less than value received because Pokemon OAuth
# login servers return an access token with an explicit expiry time of
# three hours, however, the token stops being valid after two hours.
# See issue #86
try:
self._access_token_expiry = token_data['expires'][0] - 3600 + time()
except (KeyError, IndexError, TypeError):
self._access_token_expiry = 0

self._login = True

self.log.info('PTC Access Token successfully retrieved.')
return self._access_token
else:
self.log.info('Authenticating with refresh token failed, using credentials instead.')
return await self.user_login(retry=False)
except HttpProcessingError as e:
async with self.session(connector=self.conn()) as session:
async with session.post('https://sso.pokemon.com/sso/oauth2.0/accessToken', data=data, proxy=self.proxy) as resp:
self._refresh_token = None
qs = await resp.text()
except (ClientHttpProxyError, ClientProxyConnectionError, SocksError) as e:
raise ProxyException('Proxy connection error while fetching access token.') from e
except ClientResponseError as e:
raise AuthConnectionException('Error {} while fetching access token: {}'.format(e.code, e.message))
except (TimeoutError, TimeoutException) as e:
except (TimeoutError, ServerTimeoutError) as e:
raise AuthTimeoutException('Access token request timed out.') from e
except (ProxyException, SocksError) as e:
raise ProxyException('Proxy connection error while fetching access token.') from e
except (ClientError, DisconnectedError) as e:
except ClientError as e:
raise AuthConnectionException('{} while fetching access token.'.format(e.__class__.__name__)) from e
except (AuthException, CancelledError):
raise
except Exception as e:
raise AuthException('{} while fetching access token.'.format(e.__class__.__name__)) from e
finally:
self.close_session()

token_data = parse_qs(qs)
try:
self._access_token = token_data['access_token'][0]
assert self._access_token is not None
except (KeyError, IndexError, AssertionError):
self.log.info('Authenticating with refresh token failed, using credentials instead.')
return await self.user_login(retry=False)

# set expiration to an hour less than value received because Pokemon OAuth
# login servers return an access token with an explicit expiry time of
# three hours, however, the token stops being valid after two hours.
# See issue #86
try:
self._access_token_expiry = token_data['expires'][0] - 3600 + time()
except (KeyError, IndexError, TypeError):
self._access_token_expiry = 0

self._login = True

self.log.info('PTC Access Token successfully retrieved.')
return self._access_token
Loading

0 comments on commit 6e5f5f4

Please sign in to comment.