Skip to content

Commit

Permalink
Adapt code to new PGoApi Code (#2357)
Browse files Browse the repository at this point in the history
* wip: fixing imports and tests

* tests are passing

* add tests, and modify calls to api accordingly

* fix login bug

* fix rebase

* fix import error

* Handle ThrottlingException

* fix relogging errors
  • Loading branch information
DayBr3ak authored and douglascamata committed Aug 2, 2016
1 parent 9b1fdef commit bdf2e7d
Show file tree
Hide file tree
Showing 16 changed files with 242 additions and 177 deletions.
44 changes: 22 additions & 22 deletions pokemongo_bot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class PokemonGoBot(object):
def position(self):
return self.api._position_lat, self.api._position_lng, 0

@position.setter
def position(self, position_tuple):
self.api._position_lat, self.api._position_lng, self.api._position_alt = position_tuple

def __init__(self, config):
self.config = config
self.fort_timeouts = dict()
Expand Down Expand Up @@ -103,7 +107,7 @@ def get_meta_cell(self):
wild_pokemons += cell["wild_pokemons"]
if "catchable_pokemons" in cell and len(cell["catchable_pokemons"]):
catchable_pokemons += cell["catchable_pokemons"]

# If there are forts present in the cells sent from the server or we don't yet have any cell data, return all data retrieved
if len(forts) > 1 or not self.cell:
return {
Expand Down Expand Up @@ -138,14 +142,13 @@ def update_web_location(self, cells=[], lat=None, lng=None, alt=None):
if 'forts' in cell:
for fort in cell['forts']:
if fort.get('type') != 1:
self.api.get_gym_details(
response_gym_details = self.api.get_gym_details(
gym_id=fort.get('id'),
player_latitude=lng,
player_longitude=lat,
gym_latitude=fort.get('latitude'),
gym_longitude=fort.get('longitude')
)
response_gym_details = self.api.call()
fort['gym_details'] = response_gym_details.get(
'responses', {}
).get('GET_GYM_DETAILS', None)
Expand Down Expand Up @@ -239,6 +242,9 @@ def check_session(self, position):

if remaining_time < 60:
logger.log("Session stale, re-logging in", 'yellow')
position = self.position
self.api = ApiWrapper()
self.position = position
self.login()

@staticmethod
Expand All @@ -251,13 +257,13 @@ def is_numeric(s):

def login(self):
logger.log('Attempting login to Pokemon Go.', 'white')
self.api.reset_auth()
lat, lng = self.position[0:2]
self.api.set_position(lat, lng, 0)

while not self.api.login(self.config.auth_service,
str(self.config.username),
str(self.config.password)):
while not self.api.login(
self.config.auth_service,
str(self.config.username),
str(self.config.password)):

logger.log('[X] Login Error, server busy', 'red')
logger.log('[X] Waiting 10 seconds to try again', 'red')
Expand All @@ -267,7 +273,7 @@ def login(self):

def _setup_api(self):
# instantiate pgoapi
self.api = ApiWrapper(PGoApi())
self.api = ApiWrapper()

# provide player position on the earth
self._set_starting_position()
Expand All @@ -286,8 +292,7 @@ def _setup_api(self):
def _print_character_info(self):
# get player profile call
# ----------------------
self.api.get_player()
response_dict = self.api.call()
response_dict = self.api.get_player()
# print('Response dictionary: \n\r{}'.format(json.dumps(response_dict, indent=2)))
currency_1 = "0"
currency_2 = "0"
Expand Down Expand Up @@ -368,15 +373,11 @@ def _print_character_info(self):
logger.log('')

def use_lucky_egg(self):
self.api.use_item_xp_boost(item_id=301)
inventory_req = self.api.call()
return inventory_req
return self.api.use_item_xp_boost(item_id=301)

def get_inventory(self):
if self.latest_inventory is None:
self.api.get_inventory()
response = self.api.call()
self.latest_inventory = response
self.latest_inventory = self.api.get_inventory()
return self.latest_inventory

def update_inventory(self):
Expand Down Expand Up @@ -538,9 +539,10 @@ def heartbeat(self):
self.fort_timeouts = {id: timeout for id, timeout
in self.fort_timeouts.iteritems()
if timeout >= time.time() * 1000}
self.api.get_player()
self.api.check_awarded_badges()
self.api.call()
request = self.api.create_request()
request.get_player()
request.check_awarded_badges()
request.call()
self.update_web_location() # updates every tick

def get_inventory_count(self, what):
Expand Down Expand Up @@ -620,14 +622,12 @@ def get_map_objects(self, lat, lng, timestamp, cellid):
if time.time() - self.last_time_map_object < self.config.map_object_cache_time:
return self.last_map_object

self.api.get_map_objects(
self.last_map_object = self.api.get_map_objects(
latitude=f2i(lat),
longitude=f2i(lng),
since_timestamp_ms=timestamp,
cell_id=cellid
)

self.last_map_object = self.api.call()
self.last_time_map_object = time.time()

return self.last_map_object
108 changes: 73 additions & 35 deletions pokemongo_bot/api_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,70 @@
import time

from pgoapi.exceptions import (NotLoggedInException,
ServerBusyOrOfflineException)
from pgoapi.exceptions import ServerSideRequestThrottlingException, NotLoggedInException, ServerBusyOrOfflineException, NoPlayerPositionSetException, EmptySubrequestChainException
from pgoapi.pgoapi import PGoApi, PGoApiRequest, RpcApi
from pgoapi.protos.POGOProtos.Networking.Requests_pb2 import RequestType

import logger
import pokemongo_bot.logger as logger
from human_behaviour import sleep


class ApiWrapper(object):
def __init__(self, api):
self._api = api
class ApiWrapper(PGoApi):
def __init__(self):
PGoApi.__init__(self)
self.useVanillaRequest = False

def create_request(self):
RequestClass = ApiRequest
if self.useVanillaRequest:
RequestClass = PGoApiRequest

return RequestClass(
self._api_endpoint,
self._auth_provider,
self._position_lat,
self._position_lng,
self._position_alt
)

def login(self, *args):
# login needs base class "create_request"
self.useVanillaRequest = True
try:
ret_value = PGoApi.login(self, *args)
finally:
# cleanup code
self.useVanillaRequest = False
return ret_value


class ApiRequest(PGoApiRequest):
def __init__(self, *args):
PGoApiRequest.__init__(self, *args)
self.request_callers = []
self.reset_auth()
self.last_api_request_time = None
self.requests_per_seconds = 2

def reset_auth(self):
self._api._auth_token = None
self._api._auth_provider = None
self._api._api_endpoint = None

# wrap api methods
def _can_call(self):
if not self._api._req_method_list or len(self._api._req_method_list) == 0:
raise RuntimeError('Trying to call the api without setting any request')
if self._api._auth_provider is None or not self._api._auth_provider.is_login():
logger.log('Not logged in!', 'red')
def can_call(self):
if not self._req_method_list:
raise EmptySubrequestChainException()

if (self._position_lat is None) or (self._position_lng is None) or (self._position_alt is None):
raise NoPlayerPositionSetException()

if self._auth_provider is None or not self._auth_provider.is_login():
self.log.info('Not logged in')
raise NotLoggedInException()

return True

def _call(self):
return PGoApiRequest.call(self)

def _pop_request_callers(self):
r = self.request_callers
self.request_callers = []
return [i.upper() for i in r]

def _is_response_valid(self, result, request_callers):
def is_response_valid(self, result, request_callers):
if not result or result is None or not isinstance(result, dict):
return False

Expand All @@ -54,22 +84,35 @@ def _is_response_valid(self, result, request_callers):

def call(self, max_retry=5):
request_callers = self._pop_request_callers()
if not self._can_call():
if not self.can_call():
return False # currently this is never ran, exceptions are raised before

request_timestamp = None

api_req_method_list = self._api._req_method_list
api_req_method_list = self._req_method_list
result = None
try_cnt = 0
throttling_retry = 0
while True:
request_timestamp = self.throttle_sleep()
self._api._req_method_list = [req_method for req_method in api_req_method_list] # api internally clear this field after a call
result = self._api.call()
if not self._is_response_valid(result, request_callers):
# self._call internally clear this field, so save it
self._req_method_list = [req_method for req_method in api_req_method_list]
try:
result = self._call()
should_retry = False
except ServerSideRequestThrottlingException:
should_retry = True

if should_retry:
throttling_retry += 1
logger.log("Server is throttling, let's slow down a bit")
if throttling_retry >= max_retry:
raise ServerSideRequestThrottlingException('Server throttled too many times')
sleep(1) # huge sleep ?
continue # skip response checking

if not self.is_response_valid(result, request_callers):
try_cnt += 1
logger.log('Server seems to be busy or offline - try again - {}/{}'.format(try_cnt, max_retry), 'red')

if try_cnt >= max_retry:
raise ServerBusyOrOfflineException()
sleep(1)
Expand All @@ -79,23 +122,18 @@ def call(self, max_retry=5):
self.last_api_request_time = request_timestamp
return result

def login(self, provider, username, password):
return self._api.login(provider, username, password)

# fallback
def __getattr__(self, func):
DEFAULT_ATTRS = ['_position_lat', '_position_lng', '_auth_provider', '_api_endpoint', 'set_position', 'get_position']
if func not in DEFAULT_ATTRS:
if func.upper() in RequestType.keys():
self.request_callers.append(func)
return getattr(self._api, func)
return PGoApiRequest.__getattr__(self, func)

def throttle_sleep(self):
now_milliseconds = time.time() * 1000
required_delay_between_requests = 1000 / self.requests_per_seconds

difference = now_milliseconds - (self.last_api_request_time if self.last_api_request_time else 0)

if (self.last_api_request_time != None and difference < required_delay_between_requests):
if self.last_api_request_time != None and difference < required_delay_between_requests:
sleep_time = required_delay_between_requests - difference
time.sleep(sleep_time / 1000)

Expand Down
3 changes: 1 addition & 2 deletions pokemongo_bot/cell_workers/collect_level_up_reward.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ def work(self):
self.previous_level = self.current_level

def _collect_level_reward(self):
self.bot.api.level_up_rewards(level=self.current_level)
response_dict = self.bot.api.call()
response_dict = self.bot.api.level_up_rewards(level=self.current_level)
if 'status_code' in response_dict and response_dict['status_code'] == 1:
data = (response_dict
.get('responses', {})
Expand Down
6 changes: 2 additions & 4 deletions pokemongo_bot/cell_workers/evolve_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,7 @@ def _execute_pokemon_evolve(self, pokemon, cache):
if pokemon_name in cache:
return

self.bot.api.evolve_pokemon(pokemon_id=pokemon_id)
response_dict = self.bot.api.call()
response_dict = self.bot.api.evolve_pokemon(pokemon_id=pokemon_id)
status = response_dict['responses']['EVOLVE_POKEMON']['result']
if status == 1:
logger.log('[#] Successfully evolved {} with {} CP and {} IV!'.format(
Expand All @@ -174,8 +173,7 @@ def _execute_pokemon_evolve(self, pokemon, cache):

# TODO: move to utils. These methods are shared with other workers.
def transfer_pokemon(self, pid):
self.bot.api.release_pokemon(pokemon_id=pid)
response_dict = self.bot.api.call()
response_dict = self.bot.api.release_pokemon(pokemon_id=pid)

def count_pokemon_inventory(self):
response_dict = self.bot.get_inventory()
Expand Down
1 change: 0 additions & 1 deletion pokemongo_bot/cell_workers/handle_soft_ban.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ def spin_fort(self, fort):
player_latitude=f2i(self.bot.position[0]),
player_longitude=f2i(self.bot.position[1])
)
self.bot.api.call()

def should_run(self):
return self.bot.softban
9 changes: 5 additions & 4 deletions pokemongo_bot/cell_workers/incubate_eggs.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ def _apply_incubators(self):
continue
if self.bot.config.debug:
logger.log('[x] Attempting to apply incubator {} to egg {}'.format(incubator['id'], egg['id']))
self.bot.api.use_item_egg_incubator(item_id=incubator["id"], pokemon_id=egg["id"])
ret = self.bot.api.call()
ret = self.bot.api.use_item_egg_incubator(
item_id=incubator["id"],
pokemon_id=egg["id"]
)
if ret:
code = ret.get("responses", {}).get("USE_ITEM_EGG_INCUBATOR", {}).get("result", 0)
if code == 1:
Expand Down Expand Up @@ -125,8 +127,7 @@ def _check_inventory(self, lookup_ids=[]):
return matched_pokemon

def _hatch_eggs(self):
self.bot.api.get_hatched_eggs()
response_dict = self.bot.api.call()
response_dict = self.bot.api.get_hatched_eggs()
log_color = 'green'
try:
result = reduce(dict.__getitem__, ["responses", "GET_HATCHED_EGGS"], response_dict)
Expand Down
13 changes: 6 additions & 7 deletions pokemongo_bot/cell_workers/nickname_pokemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def initialize(self):
self.template = self.config.get('nickname_template','').lower().strip()
if self.template == "{name}":
self.template = ""

def work(self):
try:
inventory = reduce(dict.__getitem__, ["responses", "GET_INVENTORY", "inventory_delta", "inventory_items"], self.bot.get_inventory())
Expand All @@ -16,8 +16,8 @@ def work(self):
else:
pokemon_data = self._get_inventory_pokemon(inventory)
for pokemon in pokemon_data:
self._nickname_pokemon(pokemon)
self._nickname_pokemon(pokemon)

def _get_inventory_pokemon(self,inventory_dict):
pokemon_data = []
for inv_data in inventory_dict:
Expand All @@ -29,7 +29,7 @@ def _get_inventory_pokemon(self,inventory_dict):
if not pokemon.get('is_egg',False):
pokemon_data.append(pokemon)
return pokemon_data

def _nickname_pokemon(self,pokemon):
"""This requies a pokemon object containing all the standard fields: id, ivs, cp, etc"""
new_name = ""
Expand Down Expand Up @@ -62,10 +62,9 @@ def _nickname_pokemon(self,pokemon):
logger.log("Unable to nickname {} due to bad template ({})".format(name,bad_key),log_color)
if pokemon.get('nickname', "") == new_name:
return
self.bot.api.nickname_pokemon(pokemon_id=instance_id,nickname=new_name)
response = self.bot.api.call()
response = self.bot.api.nickname_pokemon(pokemon_id=instance_id,nickname=new_name)
sleep(1.2)
try:
try:
result = reduce(dict.__getitem__, ["responses", "NICKNAME_POKEMON"], response)
except KeyError:
logger.log("Attempt to nickname received bad response from server.",log_color)
Expand Down
Loading

0 comments on commit bdf2e7d

Please sign in to comment.