From bdf2e7d9cc0b2fa5fb04bb9dc4f9f09f967cd9ea Mon Sep 17 00:00:00 2001 From: Benjamin G Date: Tue, 2 Aug 2016 21:23:42 +0200 Subject: [PATCH] Adapt code to new PGoApi Code (#2357) * 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 --- pokemongo_bot/__init__.py | 44 +++---- pokemongo_bot/api_wrapper.py | 108 +++++++++++------ .../cell_workers/collect_level_up_reward.py | 3 +- pokemongo_bot/cell_workers/evolve_all.py | 6 +- pokemongo_bot/cell_workers/handle_soft_ban.py | 1 - pokemongo_bot/cell_workers/incubate_eggs.py | 9 +- .../cell_workers/nickname_pokemon.py | 13 +-- .../cell_workers/pokemon_catch_worker.py | 62 ++++++---- pokemongo_bot/cell_workers/recycle_items.py | 8 +- pokemongo_bot/cell_workers/spin_fort.py | 13 ++- .../cell_workers/transfer_pokemon.py | 9 +- pokemongo_bot/cell_workers/utils.py | 6 +- pokemongo_bot/metrics.py | 7 +- requirements.txt | 2 +- tests/__init__.py | 18 +-- tests/api_wrapper_test.py | 110 +++++++++++------- 16 files changed, 242 insertions(+), 177 deletions(-) diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 811fd02245..4c21f87493 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -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() @@ -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 { @@ -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) @@ -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 @@ -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') @@ -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() @@ -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" @@ -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): @@ -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): @@ -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 diff --git a/pokemongo_bot/api_wrapper.py b/pokemongo_bot/api_wrapper.py index f861cb1636..0cb3f9c62f 100644 --- a/pokemongo_bot/api_wrapper.py +++ b/pokemongo_bot/api_wrapper.py @@ -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 @@ -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) @@ -79,15 +122,10 @@ 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 @@ -95,7 +133,7 @@ def throttle_sleep(self): 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) diff --git a/pokemongo_bot/cell_workers/collect_level_up_reward.py b/pokemongo_bot/cell_workers/collect_level_up_reward.py index 8398153386..912a97d197 100644 --- a/pokemongo_bot/cell_workers/collect_level_up_reward.py +++ b/pokemongo_bot/cell_workers/collect_level_up_reward.py @@ -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', {}) diff --git a/pokemongo_bot/cell_workers/evolve_all.py b/pokemongo_bot/cell_workers/evolve_all.py index 38a3a32d13..cdc6389344 100644 --- a/pokemongo_bot/cell_workers/evolve_all.py +++ b/pokemongo_bot/cell_workers/evolve_all.py @@ -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( @@ -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() diff --git a/pokemongo_bot/cell_workers/handle_soft_ban.py b/pokemongo_bot/cell_workers/handle_soft_ban.py index d13bd63eea..04e5178b50 100644 --- a/pokemongo_bot/cell_workers/handle_soft_ban.py +++ b/pokemongo_bot/cell_workers/handle_soft_ban.py @@ -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 diff --git a/pokemongo_bot/cell_workers/incubate_eggs.py b/pokemongo_bot/cell_workers/incubate_eggs.py index b8ef560bbb..95d497518a 100644 --- a/pokemongo_bot/cell_workers/incubate_eggs.py +++ b/pokemongo_bot/cell_workers/incubate_eggs.py @@ -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: @@ -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) diff --git a/pokemongo_bot/cell_workers/nickname_pokemon.py b/pokemongo_bot/cell_workers/nickname_pokemon.py index d626595d11..2c61d87a9f 100644 --- a/pokemongo_bot/cell_workers/nickname_pokemon.py +++ b/pokemongo_bot/cell_workers/nickname_pokemon.py @@ -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()) @@ -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: @@ -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 = "" @@ -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) diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index 6e90472a0c..3bcb8a6268 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -93,8 +93,11 @@ def work(self): break # Use the berry to catch - self.api.use_item_capture(item_id = berry_id,encounter_id = encounter_id,spawn_point_id = self.spawn_point_guid) - response_dict = self.api.call() + response_dict = self.api.use_item_capture( + item_id=berry_id, + encounter_id=encounter_id, + spawn_point_id=self.spawn_point_guid + ) if response_dict and response_dict['status_code'] is 1 and 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: for i in range(len(catch_rate)): if 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: @@ -126,8 +129,10 @@ def work(self): success_percentage = '{0:.2f}'.format(catch_rate[pokeball-1]*100) logger.log('Catch Rate with normal Pokeball is low ({}%). Thinking to throw a {}... ({} left!)'.format(success_percentage,self.item_list[str(berry_id)],berries_count-1)) - self.api.use_item_capture(item_id = berry_id,encounter_id = encounter_id,spawn_point_id = self.spawn_point_guid) - response_dict = self.api.call() + response_dict = self.api.use_item_capture(item_id=berry_id, + encounter_id=encounter_id, + spawn_point_id=self.spawn_point_guid + ) if response_dict and response_dict['status_code'] is 1 and 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: for i in range(len(catch_rate)): if 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: @@ -159,8 +164,10 @@ def work(self): success_percentage = '{0:.2f}'.format(catch_rate[pokeball-1]*100) logger.log('Catch Rate with normal Pokeball is low ({}%). Thinking to throw a {}... ({} left!)'.format(success_percentage,self.item_list[str(berry_id)],berries_count-1)) - self.api.use_item_capture(item_id = berry_id,encounter_id = encounter_id,spawn_point_id = self.spawn_point_guid) - response_dict = self.api.call() + response_dict = self.api.use_item_capture(item_id=berry_id, + encounter_id=encounter_id, + spawn_point_id=self.spawn_point_guid + ) if response_dict and response_dict['status_code'] is 1 and 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: for i in range(len(catch_rate)): if 'item_capture_mult' in response_dict['responses']['USE_ITEM_CAPTURE']: @@ -205,14 +212,15 @@ def work(self): reticle_size_parameter = normalized_reticle_size(self.config.catch_randomize_reticle_factor) spin_modifier_parameter = spin_modifier(self.config.catch_randomize_spin_factor) - self.api.catch_pokemon(encounter_id=encounter_id, - pokeball=pokeball, - normalized_reticle_size=reticle_size_parameter, - spawn_point_id=self.spawn_point_guid, - hit_pokemon=1, - spin_modifier=spin_modifier_parameter, - normalized_hit_position=1) - response_dict = self.api.call() + response_dict = self.api.catch_pokemon( + encounter_id=encounter_id, + pokeball=pokeball, + normalized_reticle_size=reticle_size_parameter, + spawn_point_id=self.spawn_point_guid, + hit_pokemon=1, + spin_modifier=spin_modifier_parameter, + normalized_hit_position=1 + ) if response_dict and \ 'responses' in response_dict and \ @@ -254,8 +262,7 @@ def work(self): if len(pokemon_to_transfer) == 0: raise RuntimeError( 'Trying to evolve 0 pokemons!') - self.api.evolve_pokemon(pokemon_id=pokemon_to_transfer[0]) - response_dict = self.api.call() + response_dict = self.api.evolve_pokemon(pokemon_id=pokemon_to_transfer[0]) status = response_dict['responses']['EVOLVE_POKEMON']['result'] if status == 1: logger.log( @@ -269,8 +276,7 @@ def work(self): def count_pokemon_inventory(self): # don't use cached bot.get_inventory() here # because we need to have actual information in capture logic - self.api.get_inventory() - response_dict = self.api.call() + response_dict = self.api.get_inventory() id_list = [] callback = lambda pokemon: id_list.append(pokemon['id']) @@ -360,22 +366,30 @@ def create_encounter_api_call(self): player_latitude = self.pokemon['latitude'] player_longitude = self.pokemon['longitude'] + request = self.api.create_request() if 'spawn_point_id' in self.pokemon: spawn_point_id = self.pokemon['spawn_point_id'] self.spawn_point_guid = spawn_point_id self.response_key = 'ENCOUNTER' self.response_status_key = 'status' - self.api.encounter(encounter_id=encounter_id, spawn_point_id=spawn_point_id, - player_latitude=player_latitude, player_longitude=player_longitude) + request.encounter( + encounter_id=encounter_id, + spawn_point_id=spawn_point_id, + player_latitude=player_latitude, + player_longitude=player_longitude + ) else: fort_id = self.pokemon['fort_id'] self.spawn_point_guid = fort_id self.response_key = 'DISK_ENCOUNTER' self.response_status_key = 'result' - self.api.disk_encounter(encounter_id=encounter_id, fort_id=fort_id, - player_latitude=player_latitude, player_longitude=player_longitude) - - return self.api.call() + request.disk_encounter( + encounter_id=encounter_id, + fort_id=fort_id, + player_latitude=player_latitude, + player_longitude=player_longitude + ) + return request.call() def check_vip_pokemon(self,pokemon, cp, iv): diff --git a/pokemongo_bot/cell_workers/recycle_items.py b/pokemongo_bot/cell_workers/recycle_items.py index 2ece62f95b..022a711f1a 100644 --- a/pokemongo_bot/cell_workers/recycle_items.py +++ b/pokemongo_bot/cell_workers/recycle_items.py @@ -44,9 +44,9 @@ def work(self): logger.log("-- Failed to discard " + item_name, 'red') def send_recycle_item_request(self, item_id, count): - self.bot.api.recycle_inventory_item(item_id=item_id, count=count) - inventory_req = self.bot.api.call() - # Example of good request response #{'responses': {'RECYCLE_INVENTORY_ITEM': {'result': 1, 'new_count': 46}}, 'status_code': 1, 'auth_ticket': {'expire_timestamp_ms': 1469306228058L, 'start': '/HycFyfrT4t2yB2Ij+yoi+on778aymMgxY6RQgvrGAfQlNzRuIjpcnDd5dAxmfoTqDQrbz1m2dGqAIhJ+eFapg==', 'end': 'f5NOZ95a843tgzprJo4W7Q=='}, 'request_id': 8145806132888207460L} - return inventory_req + return self.bot.api.recycle_inventory_item( + item_id=item_id, + count=count + ) diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index 6534731a86..f34fc2b8ef 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -33,12 +33,13 @@ def work(self): logger.log('Now at Pokestop: {0}'.format(fort_name), 'cyan') logger.log('Spinning ...', 'cyan') - self.bot.api.fort_search(fort_id=fort['id'], - fort_latitude=lat, - fort_longitude=lng, - player_latitude=f2i(self.bot.position[0]), - player_longitude=f2i(self.bot.position[1])) - response_dict = self.bot.api.call() + response_dict = self.bot.api.fort_search( + fort_id=fort['id'], + fort_latitude=lat, + fort_longitude=lng, + player_latitude=f2i(self.bot.position[0]), + player_longitude=f2i(self.bot.position[1]) + ) if 'responses' in response_dict and \ 'FORT_SEARCH' in response_dict['responses']: diff --git a/pokemongo_bot/cell_workers/transfer_pokemon.py b/pokemongo_bot/cell_workers/transfer_pokemon.py index 3cf40413f5..374ffa6a87 100644 --- a/pokemongo_bot/cell_workers/transfer_pokemon.py +++ b/pokemongo_bot/cell_workers/transfer_pokemon.py @@ -71,8 +71,10 @@ def work(self): def _release_pokemon_get_groups(self): pokemon_groups = {} - self.bot.api.get_player().get_inventory() - inventory_req = self.bot.api.call() + request = self.bot.api.create_request() + request.get_player() + request.get_inventory() + inventory_req = request.call() if inventory_req.get('responses', False) is False: return pokemon_groups @@ -182,8 +184,7 @@ def release_pokemon(self, pokemon_name, cp, iv, pokemon_id): logger.log('Exchanging {} [CP {}] [Potential {}] for candy!'.format(pokemon_name, cp, iv), 'green') - self.bot.api.release_pokemon(pokemon_id=pokemon_id) - response_dict = self.bot.api.call() + response_dict = self.bot.api.release_pokemon(pokemon_id=pokemon_id) action_delay(self.bot.config.action_wait_min, self.bot.config.action_wait_max) def _get_release_config_for(self, pokemon): diff --git a/pokemongo_bot/cell_workers/utils.py b/pokemongo_bot/cell_workers/utils.py index 253083bc0b..946c757b16 100644 --- a/pokemongo_bot/cell_workers/utils.py +++ b/pokemongo_bot/cell_workers/utils.py @@ -28,10 +28,10 @@ def fort_details(bot, fort_id, latitude, longitude): """ Lookup the fort details and cache the response for future use. """ - bot.api.fort_details(fort_id=fort_id, latitude=latitude, longitude=longitude) - + request = bot.api.create_request() + request.fort_details(fort_id=fort_id, latitude=latitude, longitude=longitude) try: - response_dict = bot.api.call() + response_dict = request.call() FORT_CACHE[fort_id] = response_dict['responses']['FORT_DETAILS'] except Exception: pass diff --git a/pokemongo_bot/metrics.py b/pokemongo_bot/metrics.py index 0ab7cb14dd..0ffeb39a6c 100644 --- a/pokemongo_bot/metrics.py +++ b/pokemongo_bot/metrics.py @@ -70,9 +70,10 @@ def released_pokemon(self, count=1): self.releases += count def capture_stats(self): - self.bot.api.get_inventory() - self.bot.api.get_player() - response_dict = self.bot.api.call() + request = self.bot.api.create_request() + request.get_inventory() + request.get_player() + response_dict = request.call() try: self.dust['latest'] = response_dict['responses']['GET_PLAYER']['player_data']['currencies'][1]['amount'] if self.dust['start'] is None: self.dust['start'] = self.dust['latest'] diff --git a/requirements.txt b/requirements.txt index e572c4e2c6..08f9407c81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ numpy==1.11.0 networkx==1.11 --e git+https://github.com/tejado/pgoapi.git@81e786cabf027a1c8fbd1e9a07e1c11aa3d8ee8b#egg=pgoapi +-e git+https://github.com/tejado/pgoapi.git@0811db23d639039f968a82e06c7aa15a0a5016b6#egg=pgoapi geopy==1.11.0 protobuf==3.0.0b4 requests==2.10.0 diff --git a/tests/__init__.py b/tests/__init__.py index 2db23ffd9d..5318e2d482 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,21 +1,15 @@ # __init__.py from mock import MagicMock -from pgoapi import PGoApi -from pokemongo_bot.api_wrapper import ApiWrapper +from pokemongo_bot.api_wrapper import ApiWrapper, ApiRequest from pokemongo_bot import PokemonGoBot class FakeApi(ApiWrapper): - def __init__(self, return_value=None): - super(FakeApi, self).__init__(PGoApi()) - self._api.call = MagicMock(return_value=return_value) - - def _can_call(self): - return True - - def setApiReturnValue(self, value): - self._api.call.return_value = value - + def create_request(self, return_value='mock return'): + request = ApiWrapper.create_request(self) + request.can_call = MagicMock(return_value=True) + request._call = MagicMock(return_value=return_value) + return request class FakeBot(PokemonGoBot): def __init__(self): diff --git a/tests/api_wrapper_test.py b/tests/api_wrapper_test.py index 0a835e0e20..335f04cbf2 100644 --- a/tests/api_wrapper_test.py +++ b/tests/api_wrapper_test.py @@ -5,43 +5,56 @@ from tests import FakeApi from pgoapi import PGoApi -from pgoapi.exceptions import NotLoggedInException, ServerBusyOrOfflineException +from pgoapi.exceptions import NotLoggedInException, ServerBusyOrOfflineException, NoPlayerPositionSetException, EmptySubrequestChainException from pokemongo_bot.api_wrapper import ApiWrapper class TestApiWrapper(unittest.TestCase): def test_raises_not_logged_in_exception(self): - api = ApiWrapper(PGoApi()) - api.get_inventory(test='awesome') + api = ApiWrapper() + api.set_position(*(42, 42, 0)) + request = api.create_request() + request.get_inventory(test='awesome') with self.assertRaises(NotLoggedInException): - api.call() + request.call() def test_api_call_with_no_requests_set(self): - api = ApiWrapper(PGoApi()) - with self.assertRaises(RuntimeError): - api.call() + request = ApiWrapper().create_request() + with self.assertRaises(EmptySubrequestChainException): + request.call() + + def test_api_wrong_request(self): + request = ApiWrapper().create_request() + with self.assertRaises(AttributeError): + request.wrong_request() + + def test_raises_no_player_position_set_exception(self): + request = ApiWrapper().create_request() + request.get_inventory(test='awesome') + with self.assertRaises(NoPlayerPositionSetException): + request.call() @patch('pokemongo_bot.api_wrapper.sleep') def test_api_server_is_unreachable_raises_server_busy_or_offline_exception(self, sleep): sleep.return_value = True # we don't need to really sleep - api = FakeApi('Wrong Value') - api.get_inventory(test='awesome') + request = FakeApi().create_request('Wrong Value') + request.get_inventory() # we expect an exception because the "server" isn't returning a valid response with self.assertRaises(ServerBusyOrOfflineException): - api.call() + request.call() def test_mocked_call(self): - api = FakeApi(True) - api._is_response_valid = MagicMock(return_value=True) - api.get_inventory(test='awesome') - result = api.call() + request = FakeApi().create_request(True) + request.is_response_valid = MagicMock(return_value=True) + request.get_inventory(test='awesome') + result = request.call() self.assertTrue(result) def test_return_value_is_not_valid(self): - - def returnApi(ret_value): - api = FakeApi(ret_value) - api.get_inventory(test='awesome') - return api + api = FakeApi() + def returnRequest(ret_value): + request = api.create_request(ret_value) + request.get_inventory(test='awesome') + return request wrong_return_values = [ None, @@ -52,52 +65,59 @@ def returnApi(ret_value): {'responses': {'GET_INVENTORY_OR_NOT': {}}, 'status_code': 0} ] for wrong in wrong_return_values: - api = returnApi(wrong) - request_callers = api._pop_request_callers() # we can pop because we do no call + request = returnRequest(wrong) + request_callers = request._pop_request_callers() # we can pop because we do no call - is_valid = api._is_response_valid(wrong, request_callers) + is_valid = request.is_response_valid(wrong, request_callers) self.assertFalse(is_valid, 'return value {} is valid somehow ?'.format(wrong)) def test_return_value_is_valid(self): - api = FakeApi() # we set the return value below - api.get_inventory(test='awesome') + request = FakeApi().create_request() # we set the return value below + request.get_inventory(test='awesome') - request = api.request_callers[0] # only one request - self.assertEqual(request.upper(), 'GET_INVENTORY') + request_caller = request.request_callers[0] # only one request + self.assertEqual(request_caller.upper(), 'GET_INVENTORY') - good_return_value = {'responses': {request.upper(): {}}, 'status_code': 0} - api.setApiReturnValue(good_return_value) + good_return_value = {'responses': {request_caller.upper(): {}}, 'status_code': 0} + request._call.return_value = good_return_value - result = api.call() + result = request.call() self.assertEqual(result, good_return_value) - self.assertEqual(len(api.request_callers), 0, 'request_callers must be empty') + self.assertEqual(len(request.request_callers), 0, 'request_callers must be empty') def test_multiple_requests(self): - api = FakeApi() - api.get_inventory(test='awesome') - api.fort_details() + request = FakeApi().create_request() + request.get_inventory(test='awesome') + request.fort_details() good_return_value = {'responses': {'GET_INVENTORY': {}, 'FORT_DETAILS': {}}, 'status_code': 0} - api.setApiReturnValue(good_return_value) + request._call.return_value = good_return_value - result = api.call() + result = request.call() self.assertEqual(result, good_return_value) @timeout(1) def test_api_call_throttle_should_pass(self): - api = FakeApi(True) - api._is_response_valid = MagicMock(return_value=True) - api.requests_per_seconds = 5 + request = FakeApi().create_request() + request.is_response_valid = MagicMock(return_value=True) + request.requests_per_seconds = 5 - for i in range(api.requests_per_seconds): - api.call() + for i in range(request.requests_per_seconds): + request.call() @timeout(1) # expects a timeout def test_api_call_throttle_should_fail(self): - api = FakeApi(True) - api._is_response_valid = MagicMock(return_value=True) - api.requests_per_seconds = 5 + request = FakeApi().create_request() + request.is_response_valid = MagicMock(return_value=True) + request.requests_per_seconds = 5 with self.assertRaises(TimeoutError): - for i in range(api.requests_per_seconds * 2): - api.call() + for i in range(request.requests_per_seconds * 2): + request.call() + + @patch('pokemongo_bot.api_wrapper.ApiRequest.is_response_valid') + def test_api_direct_call(self, mock_method): + mock_method.return_value = True + + result = FakeApi().get_inventory() + self.assertEqual(result, 'mock return')