Example #1
0
class PGoApi:

    API_ENTRY = 'https://pgorelease.nianticlabs.com/plfe/rpc'

    def __init__(self):

        self.log = logging.getLogger(__name__)

        self._auth_provider = None
        self._api_endpoint = None

        self._position_lat = 0
        self._position_lng = 0
        self._position_alt = 0

        self._req_method_list = []

    def call(self):
        if not self._req_method_list:
            return False

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

        player_position = self.get_position()

        request = RpcApi(self._auth_provider)

        if self._api_endpoint:
            api_endpoint = self._api_endpoint
        else:
            api_endpoint = self.API_ENTRY

        self.log.info('Execution of RPC')
        response = None
        try:
            response = request.request(api_endpoint, self._req_method_list,
                                       player_position)
        except ServerBusyOrOfflineException as e:
            self.log.info('Server seems to be busy or offline - try again!')

        # cleanup after call execution
        self.log.info('Cleanup of request!')
        self._req_method_list = []

        return response

    def list_curr_methods(self):
        for i in self._req_method_list:
            print("{} ({})".format(RequestType.Name(i), i))

    def set_logger(self, logger):
        self._ = logger or logging.getLogger(__name__)

    def get_position(self):
        return (self._position_lat, self._position_lng, self._position_alt)

    def set_position(self, lat, lng, alt):
        self.log.debug('Set Position - Lat: %s Long: %s Alt: %s', lat, lng,
                       alt)

        self._position_lat = f2i(lat)
        self._position_lng = f2i(lng)
        self._position_alt = f2i(alt)

    def __getattr__(self, func):
        def function(**kwargs):

            if not self._req_method_list:
                self.log.info('Create new request...')

            name = func.upper()
            if kwargs:
                self._req_method_list.append({RequestType.Value(name): kwargs})
                self.log.info("Adding '%s' to RPC request including arguments",
                              name)
                self.log.debug("Arguments of '%s': \n\r%s", name, kwargs)
            else:
                self._req_method_list.append(RequestType.Value(name))
                self.log.info("Adding '%s' to RPC request", name)

            return self

        if func.upper() in RequestType.keys():
            return function
        else:
            raise AttributeError

    def login(self, provider, username, password):

        if not isinstance(username, str) or not isinstance(password, str):
            raise AuthException("Username/password not correctly specified")

        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        else:
            raise AuthException(
                "Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: %s', provider)

        if not self._auth_provider.login(username, password):
            self.log.info('Login process failed')
            return False

        self.log.info('Starting RPC login sequence (app simulation)')

        # making a standard call, like it is also done by the client
        self.get_player()
        self.get_hatched_eggs()
        self.get_inventory()
        self.check_awarded_badges()
        self.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")

        response = self.call()

        if not response:
            self.log.info('Login failed!')
            return False

        if 'api_url' in response:
            self._api_endpoint = ('https://{}/rpc'.format(response['api_url']))
            self.log.debug('Setting API endpoint to: %s', self._api_endpoint)
        else:
            self.log.error('Login failed - unexpected server response!')
            return False

        if 'auth_ticket' in response:
            self._auth_provider.set_ticket(response['auth_ticket'].values())

        self.log.info('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')

        return True
Example #2
0
class PGoApi:

    API_ENTRY = 'https://pgorelease.nianticlabs.com/plfe/rpc'

    def __init__(self):

        self.log = logging.getLogger(__name__)

        self._auth_provider = None
        self._api_endpoint = None

        self._position_lat = 0
        self._position_lng = 0
        self._position_alt = 0

        self._req_method_list = []

    def call(self):
        if not self._req_method_list:
            return False

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

        player_position = self.get_position()

        request = RpcApi(self._auth_provider)

        if self._api_endpoint:
            api_endpoint = self._api_endpoint
        else:
            api_endpoint = self.API_ENTRY

        self.log.info('Execution of RPC')
        response = None
        try:
            response = request.request(api_endpoint, self._req_method_list, player_position)
        except ServerBusyOrOfflineException as e:
            self.log.info('Server seems to be busy or offline - try again!')

        # cleanup after call execution
        self.log.info('Cleanup of request!')
        self._req_method_list = []

        return response

    def list_curr_methods(self):
        for i in self._req_method_list:
            print("{} ({})".format(RequestType.Name(i),i))

    def set_logger(self, logger):
        self._ = logger or logging.getLogger(__name__)

    def get_position(self):
        return (self._position_lat, self._position_lng, self._position_alt)

    def set_position(self, lat, lng, alt):
        self.log.debug('Set Position - Lat: %s Long: %s Alt: %s', lat, lng, alt)

        self._position_lat = lat
        self._position_lng = lng
        self._position_alt = alt

    def __getattr__(self, func):
        def function(**kwargs):

            if not self._req_method_list:
                self.log.info('Create new request...')

            name = func.upper()
            if kwargs:
                self._req_method_list.append( { RequestType.Value(name): kwargs } )
                self.log.info("Adding '%s' to RPC request including arguments", name)
                self.log.debug("Arguments of '%s': \n\r%s", name, kwargs)
            else:
                self._req_method_list.append( RequestType.Value(name) )
                self.log.info("Adding '%s' to RPC request", name)

            return self

        if func.upper() in RequestType.keys():
            return function
        else:
            raise AttributeError


    def login(self, provider, username, password):

        if not isinstance(username, six.string_types) or not isinstance(password, six.string_types):
            raise AuthException("Username/password not correctly specified")

        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        else:
            raise AuthException("Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: %s', provider)

        if not self._auth_provider.login(username, password):
            self.log.info('Login process failed')
            return False

        self.log.info('Starting RPC login sequence (app simulation)')

        # making a standard call, like it is also done by the client
        self.get_player()
        self.get_hatched_eggs()
        self.get_inventory()
        self.check_awarded_badges()
        self.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")

        response = self.call()

        if not response:
            self.log.info('Login failed!')
            return False

        if 'api_url' in response:
            self._api_endpoint = ('https://{}/rpc'.format(response['api_url']))
            self.log.debug('Setting API endpoint to: %s', self._api_endpoint)
        else:
            self.log.error('Login failed - unexpected server response!')
            return False

        if 'auth_ticket' in response:
            self._auth_provider.set_ticket(response['auth_ticket'].values())

        self.log.info('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')

        return True
Example #3
0
class PGoApi:

    API_ENTRY = 'https://pgorelease.nianticlabs.com/plfe/rpc'

    def __init__(self, config, pokemon_names):

        self.log = logging.getLogger(__name__)

        self._auth_provider = None
        self._api_endpoint = None
        self.config = config
        self._position_lat = 0
        self._position_lng = 0
        self._position_alt = 0
        self._posf = (0, 0, 0)
        self.MIN_KEEP_IV = config.get("MIN_KEEP_IV", 0)
        self.KEEP_CP_OVER = config.get("KEEP_CP_OVER", 0)
        self._req_method_list = []
        self._heartbeat_number = 5
        self.pokemon_names = pokemon_names
        self.AUTO_EVOLVE_POKEMON_IDS = config.get("AUTO_EVOLVE_POKEMON_IDS",
                                                  [])
        self.KEEP_BEST_POKEMON_ONLY = config.get("KEEP_BEST_POKEMON_ONLY",
                                                 False)
        self.KEEP_BEST_POKEMON_MIN_CP = config.get("KEEP_BEST_POKEMON_MIN_CP",
                                                   100)
        self.KEEP_BEST_POKEMON_MIN_IV = config.get("KEEP_BEST_POKEMON_MIN_IV",
                                                   20)

    def call(self):
        if not self._req_method_list:
            return False

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

        player_position = self.get_position()

        request = RpcApi(self._auth_provider)

        if self._api_endpoint:
            api_endpoint = self._api_endpoint
        else:
            api_endpoint = self.API_ENTRY

        self.log.debug('Execution of RPC')
        response = None
        try:
            response = request.request(api_endpoint, self._req_method_list,
                                       player_position)
        except ServerBusyOrOfflineException as e:
            self.log.info('Server seems to be busy or offline - try again!')

        # cleanup after call execution
        self.log.debug('Cleanup of request!')
        self._req_method_list = []

        return response

    def list_curr_methods(self):
        for i in self._req_method_list:
            print("{} ({})".format(RequestType.Name(i), i))

    def set_logger(self, logger):
        self._ = logger or logging.getLogger(__name__)

    def get_position(self):
        return (self._position_lat, self._position_lng, self._position_alt)

    def set_position(self, lat, lng, alt):
        self.log.debug('Set Position - Lat: %s Long: %s Alt: %s', lat, lng,
                       alt)
        self._posf = (lat, lng, alt)
        self._position_lat = f2i(lat)
        self._position_lng = f2i(lng)
        self._position_alt = f2i(alt)

    def __getattr__(self, func):
        def function(**kwargs):

            if not self._req_method_list:
                self.log.debug('Create new request...')

            name = func.upper()
            if kwargs:
                self._req_method_list.append({RequestType.Value(name): kwargs})
                self.log.debug(
                    "Adding '%s' to RPC request including arguments", name)
                self.log.debug("Arguments of '%s': \n\r%s", name, kwargs)
            else:
                self._req_method_list.append(RequestType.Value(name))
                self.log.debug("Adding '%s' to RPC request", name)

            return self

        if func.upper() in RequestType.keys():
            return function
        else:
            raise AttributeError

    def heartbeat(self):
        self.get_player()
        if self._heartbeat_number % 10 == 0:
            self.check_awarded_badges()
            self.get_inventory()
        res = self.call()
        if res.get("direction", -1) == 102:
            self.log.error(
                "There were a problem responses for api call: %s. Restarting!!!",
                res)
            raise AuthException("Token probably expired?")
        self.log.debug('Heartbeat dictionary: \n\r{}'.format(
            json.dumps(res, indent=2)))

        if self._heartbeat_number % 20 == 0:
            if 'GET_PLAYER' in res['responses']:
                player_data = res['responses'].get('GET_PLAYER',
                                                   {}).get('player_data', {})
                if os.path.isfile("accounts/%s.json" %
                                  self.config['username']):
                    with open("accounts/%s.json" % self.config['username'],
                              "r") as f:
                        file = f.read()
                        json_file = json.loads(file)
                    inventory_items = json_file.get('GET_INVENTORY', {}).get(
                        'inventory_delta', {}).get('inventory_items', [])
                    inventory_items_dict_list = map(
                        lambda x: x.get('inventory_item_data', {}),
                        inventory_items)
                    player_stats = filter(lambda x: 'player_stats' in x,
                                          inventory_items_dict_list)[0].get(
                                              'player_stats', {})
                else:
                    player_stats = {}
                currencies = player_data.get('currencies', [])
                currency_data = ",".join(
                    map(
                        lambda x: "{0}: {1}".format(x.get('name', 'NA'),
                                                    x.get('amount', 'NA')),
                        currencies))
                self.log.info(
                    "Username: %s, Lvl: %s, XP: %s/%s, Currencies: %s",
                    player_data.get('username', 'NA'),
                    player_stats.get('level', 'NA'),
                    player_stats.get('experience', 'NA'),
                    player_stats.get('next_level_xp', 'NA'), currency_data)

            if 'GET_INVENTORY' in res['responses']:
                with open("accounts/%s.json" % self.config['username'],
                          "w") as f:
                    res['responses']['lat'] = self._posf[0]
                    res['responses']['lng'] = self._posf[1]
                    f.write(json.dumps(res['responses'], indent=2))
                # self.log.info(get_inventory_data(res, self.pokemon_names))
                self.log.debug(
                    self.cleanup_inventory(
                        res['responses']['GET_INVENTORY']['inventory_delta']
                        ['inventory_items']))

        self._heartbeat_number += 1
        return res

    def walk_to(self, loc):
        try:
            steps = get_route(self._posf, loc,
                              self.config.get("USE_GOOGLE", False),
                              self.config.get("GMAPS_API_KEY_1", ""))
        except Exception as e:
            try:
                steps = get_route(self._posf, loc,
                                  self.config.get("USE_GOOGLE", False),
                                  self.config.get("GMAPS_API_KEY_1", ""))
            except Exception as e:
                steps = get_route(self._posf, loc,
                                  self.config.get("USE_GOOGLE", False),
                                  self.config.get("GMAPS_API_KEY_3", ""))
        for step in steps:
            for i, next_point in enumerate(
                    get_increments(self._posf, step,
                                   self.config.get("STEP_SIZE", 200))):
                self.set_position(*next_point)
                self.heartbeat()
                # self.log.info("Sleeping before next heartbeat")
                sleep(
                    2
                )  # If you want to make it faster, delete this line... would not recommend though
                while self.catch_near_pokemon():
                    sleep(
                        1
                    )  # If you want to make it faster, delete this line... would not recommend though

    def spin_near_fort(self):
        map_cells = self.nearby_map_objects(
        )['responses']['GET_MAP_OBJECTS']['map_cells']
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        destinations = filtered_forts(self._posf, forts)
        if destinations:
            fort = destinations[0]
            # self.log.info("Walking to fort at %s,%s", fort['latitude'], fort['longitude'])
            self.walk_to((fort['latitude'], fort['longitude']))
            position = self._posf  # FIXME ?
            res = self.fort_search(fort_id=fort['id'],
                                   fort_latitude=fort['latitude'],
                                   fort_longitude=fort['longitude'],
                                   player_latitude=position[0],
                                   player_longitude=position[1]).call(
                                   )['responses']['FORT_SEARCH']
            self.log.debug("Fort spinned: %s", res)
            if 'lure_info' in fort:
                encounter_id = fort['lure_info']['encounter_id']
                fort_id = fort['lure_info']['fort_id']
                position = self._posf
                resp = self.disk_encounter(encounter_id=encounter_id,
                                           fort_id=fort_id,
                                           player_latitude=position[0],
                                           player_longitude=position[1]).call(
                                           )['responses']['DISK_ENCOUNTER']
                self.disk_encounter_pokemon(fort['lure_info'])
            return True
        else:
            self.log.error("No fort to walk to!")
            return False

    def catch_near_pokemon(self):
        map_cells = self.nearby_map_objects(
        )['responses']['GET_MAP_OBJECTS']['map_cells']
        pokemons = PGoApi.flatmap(lambda c: c.get('catchable_pokemons', []),
                                  map_cells)

        # catch first pokemon:
        origin = (self._posf[0], self._posf[1])
        pokemon_distances = [
            (pokemon,
             distance_in_meters(origin,
                                (pokemon['latitude'], pokemon['longitude'])))
            for pokemon in pokemons
        ]
        self.log.debug("Nearby pokemon: : %s", pokemon_distances)
        for pokemon_distance in pokemon_distances:
            target = pokemon_distance
            self.log.debug("Catching pokemon: : %s, distance: %f meters",
                           target[0], target[1])
            self.log.info("Catching Pokemon: %s",
                          self.pokemon_names[str(target[0]['pokemon_id'])])
            return self.encounter_pokemon(target[0])
        return False

    def nearby_map_objects(self):
        position = self.get_position()
        neighbors = getNeighbors(self._posf)
        return self.get_map_objects(latitude=position[0],
                                    longitude=position[1],
                                    since_timestamp_ms=[0] * len(neighbors),
                                    cell_id=neighbors).call()

    def attempt_catch(self, encounter_id, spawn_point_guid, pokeball):
        try:
            r = self.catch_pokemon(
                normalized_reticle_size=1.950,
                pokeball=pokeball,
                spin_modifier=0.850,
                hit_pokemon=True,
                normalized_hit_position=1,
                encounter_id=encounter_id,
                spawn_point_guid=spawn_point_guid,
            ).call()['responses']['CATCH_POKEMON']
            if "status" in r:
                return r
        except Exception as e:
            return False

    def cleanup_inventory(self, inventory_items=None):
        if not inventory_items:
            inventory_items = self.get_inventory().call()['responses'][
                'GET_INVENTORY']['inventory_delta']['inventory_items']
        caught_pokemon = defaultdict(list)
        for inventory_item in inventory_items:
            if "pokemon_data" in inventory_item['inventory_item_data']:
                # is a pokemon:
                pokemon = inventory_item['inventory_item_data']['pokemon_data']
                if 'cp' in pokemon and "favorite" not in pokemon:
                    caught_pokemon[pokemon["pokemon_id"]].append(pokemon)
            elif "item" in inventory_item['inventory_item_data']:
                item = inventory_item['inventory_item_data']['item']
                if item['item_id'] in MIN_BAD_ITEM_COUNTS and "count" in item and item[
                        'count'] > MIN_BAD_ITEM_COUNTS[item['item_id']]:
                    recycle_count = item['count'] - MIN_BAD_ITEM_COUNTS[
                        item['item_id']]
                    self.log.info(
                        "Recycling Item_ID {0}, item count {1}".format(
                            item['item_id'], recycle_count))
                    self.recycle_inventory_item(item_id=item['item_id'],
                                                count=recycle_count)

        for pokemons in caught_pokemon.values():
            self.auto_evolve(pokemons, inventory_items)
            self.auto_release_pokemon(pokemons, self.KEEP_BEST_POKEMON_ONLY)

        return self.call()

    def release_single_pokemon(self, pokemon):
        self.log.debug("Releasing pokemon: %s", pokemon)
        self.log.info("Releasing pokemon: %s (CP: %s IV: %s)",
                      self.pokemon_names[str(pokemon['pokemon_id'])],
                      pokemon['cp'], pokemonIV(pokemon))
        self.release_pokemon(pokemon_id=pokemon["id"])
        return True

    def auto_release_pokemon(self, pokemons, keep_best_only=False):
        if len(pokemons) > MIN_SIMILAR_POKEMON:
            if keep_best_only:
                caught_pokemon_dict = {}
                for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                    if pokemon['pokemon_id'] not in caught_pokemon_dict:
                        caught_pokemon_dict[pokemon['pokemon_id']] = 0
                    if pokemonIV(pokemon) > caught_pokemon_dict[
                            pokemon['pokemon_id']]:
                        caught_pokemon_dict[pokemon['pokemon_id']] = pokemonIV(
                            pokemon)

                pokemons = sorted(
                    pokemons,
                    lambda x, y: cmp(x['pokemon_id'], y['pokemon_id']))
                for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                    if pokemonIV(pokemon) < caught_pokemon_dict[
                            pokemon['pokemon_id']] and (
                                pokemonIV(pokemon) <
                                self.KEEP_BEST_POKEMON_MIN_IV or
                                pokemon['cp'] < self.KEEP_BEST_POKEMON_MIN_CP):
                        self.release_single_pokemon(pokemon)
            else:
                pokemons = sorted(pokemons,
                                  lambda x, y: cmp(x['cp'], y['cp']),
                                  reverse=True)
                for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                    if 'cp' in pokemon and pokemonIVPercentage(
                            pokemon
                    ) < self.MIN_KEEP_IV and pokemon['cp'] < self.KEEP_CP_OVER:
                        self.release_single_pokemon(pokemon)
        return True

    def auto_evolve(self, pokemons, inventory_items):
        for pokemon in pokemons:
            if pokemon['pokemon_id'] in self.AUTO_EVOLVE_POKEMON_IDS:
                for inventory_item in inventory_items:
                    if "pokemon_family" in inventory_item[
                            'inventory_item_data'] and inventory_item[
                                'inventory_item_data']['pokemon_family'][
                                    'family_id'] == 16 and inventory_item[
                                        'inventory_item_data'][
                                            'pokemon_family']['candy'] > 11:
                        self.log.info(
                            "Evolving pokemon: %s",
                            self.pokemon_names[str(pokemon['pokemon_id'])])
                        self.evolve_pokemon(pokemon_id=pokemon['id'])
        return True

    def disk_encounter_pokemon(self, lureinfo):
        try:
            encounter_id = lureinfo['encounter_id']
            fort_id = lureinfo['fort_id']
            position = self._posf
            resp = self.disk_encounter(encounter_id=encounter_id,
                                       fort_id=fort_id,
                                       player_latitude=position[0],
                                       player_longitude=position[1]).call(
                                       )['responses']['DISK_ENCOUNTER']
            pokeball = 1
            if resp['result'] == 1:
                capture_status = -1
                while capture_status != 0 and capture_status != 3:
                    try:
                        catch_attempt = self.attempt_catch(
                            encounter_id, fort_id, pokeball)
                        capture_status = catch_attempt['status']
                        if capture_status == 1:
                            self.log.debug("Caught Pokemon: : %s",
                                           catch_attempt)
                            self.log.info(
                                "Caught Pokemon:  %s (CP: %s)",
                                self.pokemon_names[str(
                                    resp['pokemon_data']['pokemon_id'])],
                                resp['pokemon_data']['cp'])
                            sleep(
                                2
                            )  # If you want to make it faster, delete this line... would not recommend though
                            return catch_attempt
                        elif capture_status == 2:
                            pokeball += 1
                        elif capture_status != 2:
                            self.log.debug("Failed Catch: : %s", catch_attempt)
                            self.log.info(
                                "Failed to catch Pokemon:  %s (CP: %s)",
                                self.pokemon_names[str(
                                    resp['pokemon_data']['pokemon_id'])],
                                resp['pokemon_data']['cp'])
                    except Exception as e:
                        pokeball += 1
        except Exception as e:
            self.log.error("Error in disk encounter %s", e)
        return False
        sleep(
            2
        )  # If you want to make it faster, delete this line... would not recommend though

    def encounter_pokemon(self, pokemon):
        try:
            encounter_id = pokemon['encounter_id']
            spawn_point_id = pokemon['spawn_point_id']
            position = self._posf
            encounter = self.encounter(
                encounter_id=encounter_id,
                spawn_point_id=spawn_point_id,
                player_latitude=position[0],
                player_longitude=position[1]).call()['responses']['ENCOUNTER']
            self.log.debug("Started Encounter: %s", encounter)
            pokeball = 1
            if encounter['status'] == 1:
                capture_status = -1
                while capture_status != 0 and capture_status != 3:
                    try:
                        catch_attempt = self.attempt_catch(
                            encounter_id, spawn_point_id, pokeball)
                        capture_status = catch_attempt['status']
                        if capture_status == 1:
                            self.log.debug("Caught Pokemon: : %s",
                                           catch_attempt)
                            self.log.info(
                                "Caught Pokemon:  %s ",
                                self.pokemon_names[str(pokemon['pokemon_id'])])
                            sleep(
                                2
                            )  # If you want to make it faster, delete this line... would not recommend though
                            return catch_attempt
                        elif capture_status == 2:
                            pokeball += 1
                        elif capture_status != 2:
                            self.log.debug("Failed Catch: : %s", catch_attempt)
                            self.log.info(
                                "Failed to Catch Pokemon:  %s",
                                self.pokemon_names[str(pokemon['pokemon_id'])])
                        sleep(
                            2
                        )  # If you want to make it faster, delete this line... would not recommend though
                    except Exception as e:
                        pokeball += 1
        except Exception as e:
            self.log.error("Error in pokemon encounter %s", e)
        return False

    def login(self, provider, username, password, cached=False):
        if not isinstance(username, basestring) or not isinstance(
                password, basestring):
            raise AuthException("Username/password not correctly specified")

        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        else:
            raise AuthException(
                "Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: %s', provider)

        if not self._auth_provider.login(username, password):
            self.log.info('Login process failed')
            return False

        self.log.info('Starting RPC login sequence (app simulation)')
        self.get_player()
        self.get_hatched_eggs()
        self.get_inventory()
        self.check_awarded_badges()
        self.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")

        response = self.call()

        if not response:
            self.log.info('Login failed!')
        if os.path.isfile("auth_cache") and cached:
            response = pickle.load(open("auth_cache"))
        fname = "auth_cache_%s" % username
        if os.path.isfile(fname) and cached:
            response = pickle.load(open(fname))
        else:
            response = self.heartbeat()
            f = open(fname, "w")
            pickle.dump(response, f)
        if not response:
            self.log.info('Login failed!')
            return False

        if 'api_url' in response:
            self._api_endpoint = ('https://{}/rpc'.format(response['api_url']))
            self.log.debug('Setting API endpoint to: %s', self._api_endpoint)
        else:
            self.log.error('Login failed - unexpected server response!')
            return False

        if 'auth_ticket' in response:
            self._auth_provider.set_ticket(response['auth_ticket'].values())

        self.log.info('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')

        return True

    def main_loop(self):
        self.heartbeat()
        while True:
            self.heartbeat()
            sleep(
                1
            )  # If you want to make it faster, delete this line... would not recommend though
            self.spin_near_fort()
            while self.catch_near_pokemon():
                sleep(
                    4
                )  # If you want to make it faster, delete this line... would not recommend though
                pass

    @staticmethod
    def flatmap(f, items):
        return chain.from_iterable(imap(f, items))
class PGoApi:

    API_ENTRY = 'https://pgorelease.nianticlabs.com/plfe/rpc'

    def __init__(self, config, pokemon_names):

        self.log = logging.getLogger(__name__)

        self._auth_provider = None
        self._api_endpoint = None
        self.config = config
        self._position_lat = 0
        self._position_lng = 0
        self._position_alt = 0
        self._posf = (0,0,0)
        self.MIN_KEEP_IV = config.get("MIN_KEEP_IV", 0)
        self.KEEP_CP_OVER = config.get("KEEP_CP_OVER", 0)
        self.RELEASE_DUPLICATES = config.get("RELEASE_DUPLICATE", 0)
        self.DUPLICATE_CP_FORGIVENESS = config.get("DUPLICATE_CP_FOREGIVENESS", 0)
        self._req_method_list = []
        self._heartbeat_number = 5
        self.pokemon_names = pokemon_names

    def call(self):
        if not self._req_method_list:
            return False

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

        player_position = self.get_position()

        request = RpcApi(self._auth_provider)

        if self._api_endpoint:
            api_endpoint = self._api_endpoint
        else:
            api_endpoint = self.API_ENTRY

        self.log.debug('Execution of RPC')
        response = None
        try:
            response = request.request(api_endpoint, self._req_method_list, player_position)
        except ServerBusyOrOfflineException as e:
            self.log.info('Server seems to be busy or offline - try again!')

        # cleanup after call execution
        self.log.debug('Cleanup of request!')
        self._req_method_list = []

        return response

    def list_curr_methods(self):
        for i in self._req_method_list:
            print("{} ({})".format(RequestType.Name(i),i))
    def set_logger(self, logger):
        self._ = logger or logging.getLogger(__name__)

    def get_position(self):
        return (self._position_lat, self._position_lng, self._position_alt)
    def set_position(self, lat, lng, alt):
        self.log.debug('Set Position - Lat: %s Long: %s Alt: %s', lat, lng, alt)
        self._posf = (lat,lng,alt)
        self._position_lat = f2i(lat)
        self._position_lng = f2i(lng)
        self._position_alt = f2i(alt)

    def __getattr__(self, func):
        def function(**kwargs):

            if not self._req_method_list:
                self.log.debug('Create new request...')

            name = func.upper()
            if kwargs:
                self._req_method_list.append( { RequestType.Value(name): kwargs } )
                self.log.debug("Adding '%s' to RPC request including arguments", name)
                self.log.debug("Arguments of '%s': \n\r%s", name, kwargs)
            else:
                self._req_method_list.append( RequestType.Value(name) )
                self.log.debug("Adding '%s' to RPC request", name)

            return self

        if func.upper() in RequestType.keys():
            return function
        else:
            raise AttributeError
    def heartbeat(self):
        self.get_player()
        if self._heartbeat_number % 10 == 0:
            self.check_awarded_badges()
            self.get_inventory()
        res = self.call()
        if res.get("direction",-1) == 102:
            self.log.error("There were a problem responses for api call: %s. Restarting!!!", res)
            raise AuthException("Token probably expired?")
        self.log.debug('Heartbeat dictionary: \n\r{}'.format(json.dumps(res, indent=2)))

        if 'GET_PLAYER' in res['responses']:
            player_data = res['responses'].get('GET_PLAYER', {}).get('player_data', {})
            currencies = player_data.get('currencies', [])
            currency_data = ",".join(map(lambda x: "{0}: {1}".format(x.get('name', 'NA'), x.get('amount', 'NA')), currencies))
            self.log.info("Username: %s, Currencies: %s", player_data.get('username', 'NA'), currency_data)

        if 'GET_INVENTORY' in res['responses']:
            with open("accounts/%s.json" % self.config['username'], "w") as f:
                res['responses']['lat'] = self._posf[0]
                res['responses']['lng'] = self._posf[1]
                f.write(json.dumps(res['responses'], indent=2))
            self.log.info("List of Pokemon:\n" + get_inventory_data(res, self.pokemon_names) + "\nTotal Pokemon count: " + str(get_pokemon_num(res)))
            self.log.debug(self.cleanup_inventory(res['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']))

        self._heartbeat_number += 1
        return res
    def walk_to(self,loc):
        steps = get_route(self._posf, loc, self.config.get("USE_GOOGLE", False), self.config.get("GMAPS_API_KEY", ""))
        for step in steps:
            for i,next_point in enumerate(get_increments(self._posf,step,self.config.get("STEP_SIZE", 200))):
                self.set_position(*next_point)
                self.heartbeat()
                self.log.info("Sleeping before next heartbeat")
                sleep(2) # If you want to make it faster, delete this line... would not recommend though
                while self.catch_near_pokemon():
                    sleep(1) # If you want to make it faster, delete this line... would not recommend though



    def spin_near_fort(self):
        map_cells = self.nearby_map_objects()['responses']['GET_MAP_OBJECTS']['map_cells']
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        destinations = filtered_forts(self._posf,forts)
        if destinations:
            fort = destinations[0]
            self.log.info("Walking to fort at %s,%s", fort['latitude'], fort['longitude'])
            self.walk_to((fort['latitude'], fort['longitude']))
            position = self._posf # FIXME ?
            res = self.fort_search(fort_id = fort['id'], fort_latitude=fort['latitude'],fort_longitude=fort['longitude'],player_latitude=position[0],player_longitude=position[1]).call()['responses']['FORT_SEARCH']
            self.log.debug("Fort spinned: %s", res)
            if 'lure_info' in fort:
                encounter_id = fort['lure_info']['encounter_id']
                fort_id = fort['lure_info']['fort_id']
                position = self._posf
                resp = self.disk_encounter(encounter_id=encounter_id, fort_id=fort_id, player_latitude=position[0], player_longitude=position[1]).call()['responses']['DISK_ENCOUNTER']
                self.disk_encounter_pokemon(fort['lure_info'])
            return True
        else:
            self.log.error("No fort to walk to!")
            return False

    def catch_near_pokemon(self):
        map_cells = self.nearby_map_objects()['responses']['GET_MAP_OBJECTS']['map_cells']
        pokemons = PGoApi.flatmap(lambda c: c.get('catchable_pokemons', []), map_cells)

        # catch first pokemon:
        origin = (self._posf[0], self._posf[1])
        pokemon_distances = [(pokemon, distance_in_meters(origin,(pokemon['latitude'], pokemon['longitude']))) for pokemon in pokemons]
        self.log.debug("Nearby pokemon: : %s", pokemon_distances)
        for pokemon_distance in pokemon_distances:
            target = pokemon_distance
            self.log.debug("Catching pokemon: : %s, distance: %f meters", target[0], target[1])
            self.log.info("Catching Pokemon: %s", self.pokemon_names[str(target[0]['pokemon_id'])])
            return self.encounter_pokemon(target[0])
        return False

    def nearby_map_objects(self):
        position = self.get_position()
        neighbors = getNeighbors(self._posf)
        return self.get_map_objects(latitude=position[0], longitude=position[1], since_timestamp_ms=[0]*len(neighbors), cell_id=neighbors).call()
    
    def attempt_catch(self,encounter_id,spawn_point_guid): #Problem here... add 4 if you have master ball
        for i in range(1,3): # Range 1...4 iff you have master ball `range(1,4)`
            r = self.catch_pokemon(
                normalized_reticle_size= 1.950,
                pokeball = i,
                spin_modifier= 0.850,
                hit_pokemon=True,
                normalized_hit_position=1,
                encounter_id=encounter_id,
                spawn_point_guid=spawn_point_guid,
                ).call()['responses']['CATCH_POKEMON']
            if "status" in r:
                return r

    def cleanup_inventory(self, inventory_items=None):
        if not inventory_items:
            inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']

        all_actual_items = [xiq['inventory_item_data']["item"] for xiq in inventory_items if "item" in xiq['inventory_item_data']]
        all_actual_item_str = "List of items:\n"
        all_actual_item_count = 0
        all_actual_items = sorted([x for x in all_actual_items if "count" in x], key=lambda x: x["item_id"])
        for xiq in all_actual_items:
            true_item_name = INVENTORY_DICT[xiq["item_id"]]
            all_actual_item_str += "Item_ID "+str(xiq["item_id"])+"\titem count "+str(xiq["count"])+"\t("+true_item_name+")\n"
            all_actual_item_count += xiq["count"]
        all_actual_item_str += "Total item count: "+str(all_actual_item_count)
        self.log.info(all_actual_item_str)

        caught_pokemon = defaultdict(list)
        for inventory_item in inventory_items:
            if "pokemon_data" in  inventory_item['inventory_item_data']:
                # is a pokemon:
                pokemon = inventory_item['inventory_item_data']['pokemon_data']
                if 'cp' in pokemon and "favorite" not in pokemon:
                    caught_pokemon[pokemon["pokemon_id"]].append(pokemon)
            elif "item" in  inventory_item['inventory_item_data']:
                item = inventory_item['inventory_item_data']['item']
                if item['item_id'] in MIN_BAD_ITEM_COUNTS and "count" in item and item['count'] > MIN_BAD_ITEM_COUNTS[item['item_id']]:
                    recycle_count = item['count'] - MIN_BAD_ITEM_COUNTS[item['item_id']]
                    self.log.info("Recycling Item_ID {0}, item count {1}".format(item['item_id'], recycle_count))
                    self.recycle_inventory_item(item_id=item['item_id'], count=recycle_count)

        for pokemons in caught_pokemon.values():
            if len(pokemons) > MIN_SIMILAR_POKEMON:
                pokemons = sorted(pokemons, lambda x,y: cmp(x['cp'],y['cp']),reverse=True)
                for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                    if 'cp' in pokemon and pokemonIVPercentage(pokemon) < self.MIN_KEEP_IV and pokemon['cp'] < self.KEEP_CP_OVER:
                        if pokemon['pokemon_id'] == 16:
                            for inventory_item in inventory_items:
                                if "pokemon_family" in inventory_item['inventory_item_data'] and inventory_item['inventory_item_data']['pokemon_family']['family_id'] == 16 and inventory_item['inventory_item_data']['pokemon_family']['candy'] > 11:
                                  self.log.info("Evolving pokemon: %s", self.pokemon_names[str(pokemon['pokemon_id'])])
                                  self.evolve_pokemon(pokemon_id = pokemon['id'])
                        self.log.debug("Releasing pokemon: %s", pokemon)
                        self.log.info("Releasing pokemon: %s IV: %s", self.pokemon_names[str(pokemon['pokemon_id'])], pokemonIVPercentage(pokemon))
                        self.release_pokemon(pokemon_id = pokemon["id"])

        if self.RELEASE_DUPLICATES:
            for pokemons in caught_pokemon.values():
                if len(pokemons) > MIN_SIMILAR_POKEMON:
                    pokemons = sorted(pokemons, lambda x,y: cmp(self.pokemon_names[str(x['pokemon_id'])], self.pokemon_names[str(y['pokemon_id'])]))
                    last_pokemon = pokemons[0]
                    for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                        if self.pokemon_names[str(pokemon['pokemon_id'])] == self.pokemon_names[str(last_pokemon['pokemon_id'])]:
                            # two of the same pokemon, compare and release smaller of the two
                            if pokemon['cp'] > last_pokemon['cp']:
                                if pokemon['cp'] * self.DUPLICATE_CP_FORGIVENESS > last_pokemon['cp']:
                                    # release the lesser!
                                    self.log.debug("Releasing pokemon: %s", last_pokemon)
                                    self.log.info("Releasing pokemon: %s IV: %s", self.pokemon_names[str(last_pokemon['pokemon_id'])], pokemonIVPercentage(pokemon))
                                    self.release_pokemon(pokemon_id = last_pokemon["id"])
                                last_pokemon = pokemon
                            else:
                                if last_pokemon['cp'] * self.DUPLICATE_CP_FORGIVENESS > pokemon['cp']:
                                    # release the lesser!
                                    self.log.debug("Releasing pokemon: %s", pokemon)
                                    self.log.info("Releasing pokemon: %s IV: %s", self.pokemon_names[str(pokemon['pokemon_id'])], pokemonIVPercentage(pokemon))
                                    self.release_pokemon(pokemon_id = pokemon["id"])

                        else:
                            last_pokemon = pokemon


        return self.call()


    def disk_encounter_pokemon(self, lureinfo):
        try:
             encounter_id = lureinfo['encounter_id']
             fort_id = lureinfo['fort_id']
             position = self._posf
             resp = self.disk_encounter(encounter_id=encounter_id, fort_id=fort_id, player_latitude=position[0], player_longitude=position[1]).call()['responses']['DISK_ENCOUNTER']
             if resp['result'] == 1:
                 capture_status = -1
                 while capture_status != 0 and capture_status != 3:
                     catch_attempt = self.attempt_catch(encounter_id,fort_id)
                     capture_status = catch_attempt['status']
                     if capture_status == 1:
                         self.log.debug("Caught Pokemon: : %s", catch_attempt)
                         self.log.info("Caught Pokemon:  %s", self.pokemon_names[str(resp['pokemon_data']['pokemon_id'])])
                         sleep(2) # If you want to make it faster, delete this line... would not recommend though
                         return catch_attempt
                     elif capture_status != 2:
                         self.log.debug("Failed Catch: : %s", catch_attempt)
                         self.log.info("Failed to catch Pokemon:  %s", self.pokemon_names[str(resp['pokemon_data']['pokemon_id'])])
                         return False
                     sleep(2) # If you want to make it faster, delete this line... would not recommend though
        except Exception as e:
            self.log.error("Error in disk encounter %s", e)
            return False


    def encounter_pokemon(self,pokemon):
        encounter_id = pokemon['encounter_id']
        spawn_point_id = pokemon['spawn_point_id']
        position = self._posf
        encounter = self.encounter(encounter_id=encounter_id,spawn_point_id=spawn_point_id,player_latitude=position[0],player_longitude=position[1]).call()['responses']['ENCOUNTER']
        self.log.debug("Started Encounter: %s", encounter)
        if encounter['status'] == 1:
            capture_status = -1
            while capture_status != 0 and capture_status != 3:
                catch_attempt = self.attempt_catch(encounter_id,spawn_point_id)
                capture_status = catch_attempt['status']
                if capture_status == 1:
                    self.log.debug("Caught Pokemon: : %s", catch_attempt)
                    self.log.info("Caught Pokemon:  %s", self.pokemon_names[str(pokemon['pokemon_id'])])
                    sleep(2) # If you want to make it faster, delete this line... would not recommend though
                    return catch_attempt
                elif capture_status != 2:
                    self.log.debug("Failed Catch: : %s", catch_attempt)
                    self.log.info("Failed to Catch Pokemon:  %s", self.pokemon_names[str(pokemon['pokemon_id'])])
                return False
                sleep(2) # If you want to make it faster, delete this line... would not recommend though
        return False



    def login(self, provider, username, password,cached=False):
        if not isinstance(username, basestring) or not isinstance(password, basestring):
            raise AuthException("Username/password not correctly specified")

        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        else:
            raise AuthException("Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: %s', provider)

        if not self._auth_provider.login(username, password):
            self.log.info('Login process failed')
            return False

        self.log.info('Starting RPC login sequence (app simulation)')
        self.get_player()
        self.get_hatched_eggs()
        self.get_inventory()
        self.check_awarded_badges()
        self.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")

        response = self.call()

        if not response:
            self.log.info('Login failed!')
        if os.path.isfile("auth_cache") and cached:
            response = pickle.load(open("auth_cache"))
        fname = "auth_cache_%s" % username
        if os.path.isfile(fname) and cached:
            response = pickle.load(open(fname))
        else:
            response = self.heartbeat()
            f = open(fname,"w")
            pickle.dump(response, f)
        if not response:
            self.log.info('Login failed!')
            return False

        if 'api_url' in response:
            self._api_endpoint = ('https://{}/rpc'.format(response['api_url']))
            self.log.debug('Setting API endpoint to: %s', self._api_endpoint)
        else:
            self.log.error('Login failed - unexpected server response!')
            return False

        if 'auth_ticket' in response:
            self._auth_provider.set_ticket(response['auth_ticket'].values())

        self.log.info('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')

        return True

    def main_loop(self):
        self.heartbeat()
        while True:
            self.heartbeat()
            sleep(1) # If you want to make it faster, delete this line... would not recommend though
            self.spin_near_fort()
            while self.catch_near_pokemon():
                sleep(4) # If you want to make it faster, delete this line... would not recommend though
                pass

    @staticmethod
    def flatmap(f, items):
        return chain.from_iterable(imap(f, items))
Example #5
0
class PGoApi:
    API_ENTRY = 'https://pgorelease.nianticlabs.com/plfe/rpc'

    def __init__(self, config, pokemon_names):

        self.log = logging.getLogger(__name__)

        self._auth_provider = None
        self._api_endpoint = None
        self.config = config
        self._position_lat = 0  # int cooords
        self._position_lng = 0
        self._position_alt = 0
        self._posf = (0, 0, 0)  # this is floats
        self._origPosF = (0, 0, 0)  # this is original position in floats
        self._req_method_list = []
        self._heartbeat_number = 5
        self._firstRun = True
        self._last_egg_use_time = 0

        self.pokemon_caught = 0
        self.player = Player({})
        self.player_stats = PlayerStats({})
        self.inventory = Player_Inventory([])

        self.pokemon_names = pokemon_names

        self.start_time = time()
        self.exp_start = None
        self.exp_current = None

        self.MIN_ITEMS = {}
        for k, v in config.get("MIN_ITEMS", {}).items():
            self.MIN_ITEMS[getattr(Inventory, k)] = v

        self.POKEMON_EVOLUTION = {}
        self.POKEMON_EVOLUTION_FAMILY = {}
        for k, v in config.get("POKEMON_EVOLUTION", {}).items():
            self.POKEMON_EVOLUTION[getattr(Enums_pb2, k)] = v
            self.POKEMON_EVOLUTION_FAMILY[getattr(Enums_pb2, k)] = getattr(Enums_pb2, "FAMILY_" + k)

        self.KEEP_IV_OVER = config.get("KEEP_IV_OVER", 0)  # release anything under this
        self.KEEP_CP_OVER = config.get("KEEP_CP_OVER", 0)  # release anything under this

        self.MIN_SIMILAR_POKEMON = config.get("MIN_SIMILAR_POKEMON", 1)  # Keep atleast one of everything.
        self.STAY_WITHIN_PROXIMITY = config.get("STAY_WITHIN_PROXIMITY", 9999999)  # Stay within proximity

        self.LIST_POKEMON_BEFORE_CLEANUP = config.get("LIST_POKEMON_BEFORE_CLEANUP", True)  # list pokemon in console
        self.LIST_INVENTORY_BEFORE_CLEANUP = config.get("LIST_INVENTORY_BEFORE_CLEANUP", True)  # list inventory in console

        self.EGG_INCUBATION_ENABLED = config.get("EGG_INCUBATION", {}).get("ENABLE", True)
        self.USE_DISPOSABLE_INCUBATORS = config.get("EGG_INCUBATION", {}).get("USE_DISPOSABLE_INCUBATORS", False)
        self.INCUBATE_BIG_EGGS_FIRST = config.get("EGG_INCUBATION", {}).get("BIG_EGGS_FIRST", True)

        self.visited_forts = ExpiringDict(max_len=120, max_age_seconds=config.get("SKIP_VISITED_FORT_DURATION", 600))
        self.experimental = config.get("EXPERIMENTAL", False)
        self.spin_all_forts = config.get("SPIN_ALL_FORTS", False)
        self.keep_pokemon_ids = map(lambda x: getattr(Enums_pb2, x), config.get("KEEP_POKEMON_NAMES", []))
        self.throw_pokemon_ids = map(lambda x: getattr(Enums_pb2, x), config.get("THROW_POKEMON_NAMES", []))
        self.max_catch_attempts = config.get("MAX_CATCH_ATTEMPTS", 10)
        self.game_master = parse_game_master()
        self.should_catch_pokemon = config.get("CATCH_POKEMON", True)
        self.RELEASE_DUPLICATES = config.get("RELEASE_DUPLICATES", False)
        self.RELEASE_DUPLICATES_MAX_LV = config.get("RELEASE_DUPLICATES_MAX_LV", 0) # only release duplicates up to this lvl
        self.RELEASE_DUPLICATES_SCALER = config.get("RELEAES_DUPLICATES_SCALER", 1.0) # when comparing two pokemon's lvl, multiply larger by this
        self.DEFINE_POKEMON_LV = config.get("DEFINE_POKEMON_LV", "CP") # define a pokemon's lvl, options are CP, IV, CP*IV, CP+IV

    def call(self):
        if not self._req_method_list:
            return False

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

        player_position = self.get_position()

        request = RpcApi(self._auth_provider)

        if self._api_endpoint:
            api_endpoint = self._api_endpoint
        else:
            api_endpoint = self.API_ENTRY

        self.log.debug('Execution of RPC')
        response = None
        try:
            response = request.request(api_endpoint, self._req_method_list, player_position)
        except ServerBusyOrOfflineException as e:
            self.log.info('Server seems to be busy or offline - try again!')

        # cleanup after call execution
        self.log.debug('Cleanup of request!')
        self._req_method_list = []

        return response

    def list_curr_methods(self):
        for i in self._req_method_list:
            print("{} ({})".format(RequestType.Name(i), i))

    def set_logger(self, logger):
        self._ = logger or logging.getLogger(__name__)

    def get_position(self):
        return (self._position_lat, self._position_lng, self._position_alt)

    def set_position(self, lat, lng, alt):
        self.log.debug('Set Position - Lat: %s Long: %s Alt: %s', lat, lng, alt)
        self._posf = (lat, lng, alt)
        if self._firstRun:
            self._firstRun = False
            self._origPosF = self._posf
        self._position_lat = f2i(lat)
        self._position_lng = f2i(lng)
        self._position_alt = f2i(alt)

    def __getattr__(self, func):
        def function(**kwargs):

            if not self._req_method_list:
                self.log.debug('Create new request...')

            name = func.upper()
            if kwargs:
                self._req_method_list.append({RequestType.Value(name): kwargs})
                self.log.debug("Adding '%s' to RPC request including arguments", name)
                self.log.debug("Arguments of '%s': \n\r%s", name, kwargs)
            else:
                self._req_method_list.append(RequestType.Value(name))
                self.log.debug("Adding '%s' to RPC request", name)

            return self

        if func.upper() in RequestType.keys():
            return function
        else:
            raise AttributeError

    def hourly_exp(self, exp):
        if self.exp_start is None:
            self.exp_start = exp
        self.exp_current = exp

        run_time = time() - self.start_time
        run_time_hours = float(run_time/3600.00)
        exp_earned = float(self.exp_current - self.exp_start)
        exp_hour = float(exp_earned/run_time_hours)

        self.log.info("=== Exp/Hour: %s ===", round(exp_hour,2))

        return exp_hour

    def update_player_inventory(self):
        self.get_inventory()
        res = self.call()
        if 'GET_INVENTORY' in res['responses']:
            self.inventory = Player_Inventory(res['responses']['GET_INVENTORY']['inventory_delta']['inventory_items'])
        return res

    def heartbeat(self):
        # making a standard call to update position, etc
        self.get_player()
        if self._heartbeat_number % 10 == 0:
            self.check_awarded_badges()
            self.get_inventory()
        # self.download_settings(hash="4a2e9bc330dae60e7b74fc85b98868ab4700802e")
        res = self.call()
        if not res or res.get("direction", -1) == 102:
            self.log.error("There were a problem responses for api call: %s. Restarting!!!", res)
            raise AuthException("Token probably expired?")
        self.log.debug('Heartbeat dictionary: \n\r{}'.format(json.dumps(res, indent=2)))

        if 'GET_PLAYER' in res['responses']:
            self.player = Player(res['responses'].get('GET_PLAYER', {}).get('player_data', {}))
            self.log.info("Player Info: %s, Pokemon Caught in this run: %s", self.player, self.pokemon_caught)

        if 'GET_INVENTORY' in res['responses']:
            with open("data_dumps/%s.json" % self.config['username'], "w") as f:
                res['responses']['lat'] = self._posf[0]
                res['responses']['lng'] = self._posf[1]
                f.write(json.dumps(res['responses'], indent=2))

            self.inventory = Player_Inventory(res['responses']['GET_INVENTORY']['inventory_delta']['inventory_items'])
            for inventory_item in self.inventory.inventory_items:
                if "player_stats" in inventory_item['inventory_item_data']:
                    self.player_stats = PlayerStats(inventory_item['inventory_item_data']['player_stats'])
                    self.log.info("Player Stats: %s", self.player_stats)
                    self.hourly_exp(self.player_stats.experience)
            if self.LIST_INVENTORY_BEFORE_CLEANUP:
                self.log.info("Player Items Before Cleanup: %s", self.inventory)
            self.log.debug(self.cleanup_inventory(self.inventory.inventory_items))
            self.log.info("Player Inventory after cleanup: %s", self.inventory)
            if self.LIST_POKEMON_BEFORE_CLEANUP:
                self.log.info(get_inventory_data(res, self.pokemon_names))
            self.incubate_eggs()
            self.attempt_evolve(self.inventory.inventory_items)
            self.cleanup_pokemon(self.inventory.inventory_items)
        # Auto-use lucky-egg if applicable
            self.use_lucky_egg()
        self._heartbeat_number += 1
        return res

    def use_lucky_egg(self):
        if self.config.get("AUTO_USE_LUCKY_EGG", False) and self.inventory.has_lucky_egg() and time() - self._last_egg_use_time > 30*60:
            self.use_item_xp_boost(item_id=Inventory.ITEM_LUCKY_EGG)
            response = self.call()
            result = response.get('responses', {}).get('USE_ITEM_XP_BOOST', {}).get('result', -1)
            if result == 1:
                self.log.info("Ate a lucky egg! Yummy! :)")
                self.inventory.take_lucky_egg()
                self._last_egg_use_time = time()
                return True
            elif result == 3:
                self.log.info("Lucky egg already active")
                return False
            else:
                self.log.info("Lucky Egg couldn't be used, status code %s", result)
                return False
        else:
            return False

    def walk_to(self, loc, waypoints=[], directly=False):  # location in floats of course...
        steps = get_route(self._posf, loc, self.config.get("USE_GOOGLE", False), self.config.get("GMAPS_API_KEY", ""),
                          self.experimental and self.spin_all_forts, waypoints)
        catch_attempt = 0
        base_travel_link = "https://www.google.com/maps/dir/%s,%s/" % (self._posf[0], self._posf[1])
        step_size = self.config.get("STEP_SIZE", 200)
        total_distance_traveled = 0
        total_distance = distance_in_meters(self._posf, loc)
        new_loc = (loc[0], loc[1], 0)

        for step in steps:
            for i, next_point in enumerate(get_increments(self._posf, step, step_size)):
                # we are less than a step away, lets just go there!
                travel_remaining = total_distance - total_distance_traveled
                distance_to_point = distance_in_meters(self._posf, next_point)

                if travel_remaining < step_size or distance_to_point + total_distance_traveled > total_distance:
                    next_point = new_loc
                    distance_to_point = distance_in_meters(self._posf, next_point)

                total_distance_traveled += distance_to_point
                self.log.info('=================================')
                self.log.info(
                    "On my way to the next fort! :) Traveled %.2f meters of %.2f ",
                    total_distance_traveled,
                    total_distance,
                )

                travel_link = '%s%s,%s' % (base_travel_link, next_point[0], next_point[1])
                self.log.info("Travel Link: %s", travel_link)
                self.set_position(*next_point)
                self.heartbeat()

                if directly is False:
                    if self.experimental and self.spin_all_forts:
                        self.spin_nearest_fort()

                sleep(1)
                while self.catch_near_pokemon() and catch_attempt <= self.max_catch_attempts:
                    sleep(1)
                    catch_attempt += 1
                catch_attempt = 0

                # Don't continue with the steps if we've reached our location
                if next_point == new_loc:
                    self.log.info('=================================')
                    return

        self.log.info('=================================')

    def walk_back_to_origin(self):
        self.walk_to(self._origPosF)

    def spin_nearest_fort(self):
        map_cells = self.nearby_map_objects()['responses'].get('GET_MAP_OBJECTS', {}).get('map_cells', [])
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        destinations = filtered_forts(self._origPosF, self._posf, forts, self.STAY_WITHIN_PROXIMITY, self.visited_forts)
        if destinations:
            nearest_fort = destinations[0][0]
            nearest_fort_dis = destinations[0][1]
            self.log.info('Nearest fort distance is %s', nearest_fort_dis)

            # Fort is close enough to change our route and walk to
            if nearest_fort_dis > 40.00 and nearest_fort_dis <= 100:
                lat = nearest_fort['latitude']
                long = nearest_fort['longitude']
                self.walk_to_fort(destinations[0], directly=True)
                self.fort_search_pgoapi(nearest_fort, player_postion=self.get_position(),
                                        fort_distance=nearest_fort_dis)
            if nearest_fort_dis <= 40.00:
                self.fort_search_pgoapi(nearest_fort, player_postion=self.get_position(),
                                        fort_distance=nearest_fort_dis)
            if 'lure_info' in nearest_fort and self.should_catch_pokemon:
                self.disk_encounter_pokemon(nearest_fort['lure_info'])

        else:
            self.log.info('No spinnable forts within proximity. Or server returned no map objects.')

    def fort_search_pgoapi(self, fort, player_postion, fort_distance):
        res = self.fort_search(fort_id=fort['id'], fort_latitude=fort['latitude'],
                               fort_longitude=fort['longitude'],
                               player_latitude=player_postion[0],
                               player_longitude=player_postion[1]).call()['responses']['FORT_SEARCH']
        result = res.pop('result', -1)
        if result == 1 and res:
            self.log.info("Visiting fort... (http://maps.google.com/maps?q=%s,%s)", fort['latitude'], fort['longitude'])
            if "items_awarded" in res:
                items = defaultdict(int)
                for item in res['items_awarded']:
                    items[item['item_id']] += item['item_count']
                reward = 'XP +' + str(res['experience_awarded'])
                for item_id, amount in items.iteritems():
                    reward += ', ' + str(amount) + 'x ' + get_item_name(item_id)
                self.log.info("Fort spun, yielding: %s",
                              reward)
            else:
                self.log.info("Fort spun, but did not yield any rewards. Possible soft ban?")
            self.visited_forts[fort['id']] = fort
        elif result == 4:
            self.log.debug("For spinned but Your inventory is full : %s", res)
            self.log.info("For spinned but Your inventory is full.")
            self.visited_forts[fort['id']] = fort
        elif result == 2:
            self.log.debug("Could not spin fort -  fort not in range %s", res)
            self.log.info("Could not spin fort http://maps.google.com/maps?q=%s,%s, Not in Range %s", fort['latitude'],
                          fort['longitude'], fort_distance)
        else:
            self.log.debug("Could not spin fort %s", res)
            self.log.info("Could not spin fort http://maps.google.com/maps?q=%s,%s, Error id: %s", fort['latitude'],
                          fort['longitude'], result)
            return False
        return True

    def spin_all_forts_visible(self):
        res = self.nearby_map_objects()
        map_cells = res['responses'].get('GET_MAP_OBJECTS', {}).get('map_cells', [])
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        destinations = filtered_forts(self._origPosF, self._posf, forts, self.STAY_WITHIN_PROXIMITY, self.visited_forts,
                                      self.experimental)
        if not destinations:
            self.log.info("No fort to walk to! %s", res)
            self.log.info('No more spinnable forts within proximity. Or server error')
            self.walk_back_to_origin()
            return False
        if len(destinations) >= 20:
            destinations = destinations[:20]
        furthest_fort = destinations[0][0]
        self.log.info("Walking to fort at  http://maps.google.com/maps?q=%s,%s", furthest_fort['latitude'],
                      furthest_fort['longitude'])
        self.walk_to((furthest_fort['latitude'], furthest_fort['longitude']),
                     map(lambda x: "via:%f,%f" % (x[0]['latitude'], x[0]['longitude']), destinations[1:]))
        return True

    def return_to_start(self):
        self.set_position(*self._origPosF)

    def walk_to_fort(self, fort_data, directly=False):
        fort = fort_data[0]
        self.log.info("Walking to fort at  http://maps.google.com/maps?q=%s,%s", fort['latitude'],
                        fort['longitude'])
        self.walk_to((fort['latitude'], fort['longitude']), directly=directly)
        self.fort_search_pgoapi(fort, self.get_position(), fort_data[1])
        if 'lure_info' in fort:
            self.disk_encounter_pokemon(fort['lure_info'])

    def spin_near_fort(self):
        res = self.nearby_map_objects()
        map_cells = res['responses'].get('GET_MAP_OBJECTS', {}).get('map_cells', [])
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        destinations = filtered_forts(self._origPosF, self._posf, forts, self.STAY_WITHIN_PROXIMITY, self.visited_forts,
                                      self.experimental)
        if not destinations:
            self.log.debug("No fort to walk to! %s", res)
            self.log.info('No more spinnable forts within proximity. Returning back to origin')
            self.walk_back_to_origin()
            return False

        for fort_data in destinations:
            self.walk_to_fort(fort_data)

        return True

    def catch_near_pokemon(self):
        if self.should_catch_pokemon is False:
            return False

        map_cells = self.nearby_map_objects()['responses']['GET_MAP_OBJECTS']['map_cells']
        pokemons = PGoApi.flatmap(lambda c: c.get('catchable_pokemons', []), map_cells)

        # catch first pokemon:
        origin = (self._posf[0], self._posf[1])
        pokemon_distances = [(pokemon, distance_in_meters(origin, (pokemon['latitude'], pokemon['longitude']))) for
                             pokemon
                             in pokemons]
        if pokemons:
            self.log.debug("Nearby pokemon: : %s", pokemon_distances)
            self.log.info("Nearby Pokemon: %s",
                          ", ".join(map(lambda x: self.pokemon_names[str(x['pokemon_id'])], pokemons)))
        else:
            self.log.info("No nearby pokemon")
        catches_successful = False
        for pokemon_distance in pokemon_distances:
            target = pokemon_distance
            self.log.debug("Catching pokemon: : %s, distance: %f meters", target[0], target[1])
            catches_successful &= self.encounter_pokemon(target[0])
            sleep(random.randrange(4, 8))
        return catches_successful

    def nearby_map_objects(self):
        position = self.get_position()
        neighbors = getNeighbors(self._posf)
        return self.get_map_objects(latitude=position[0], longitude=position[1],
                                    since_timestamp_ms=[0] * len(neighbors),
                                    cell_id=neighbors).call()

    def attempt_catch(self, encounter_id, spawn_point_id, capture_probability=None):
        catch_status = -1
        catch_attempts = 1
        ret = {}
        if not capture_probability:
            capture_probability = {}
        # Max 4 attempts to catch pokemon
        while catch_status != 1 and self.inventory.can_attempt_catch() and catch_attempts < 11:
            pokeball = self.inventory.take_next_ball(capture_probability)
            self.log.info("Attempting catch with ball type {0}  at {1:.2f} % chance. Try Number: {2}".format(pokeball,
                          capture_probability.get(pokeball, 0.0) * 100, catch_attempts))
            r = self.catch_pokemon(
                normalized_reticle_size=1.950,
                pokeball=pokeball,
                spin_modifier=0.850,
                hit_pokemon=True,
                normalized_hit_position=1,
                encounter_id=encounter_id,
                spawn_point_id=spawn_point_id,
            ).call()['responses']['CATCH_POKEMON']
            catch_attempts += 1
            if "status" in r:
                catch_status = r['status']
                # fleed or error
                if catch_status == 3 or catch_status == 0:
                    break
            ret = r
            # Sleep between catch attempts
            sleep(3)
        # Sleep after the catch (the pokemon animation time)
        sleep(4)
        return ret

    def cleanup_inventory(self, inventory_items=None):
        if not inventory_items:
            inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta'][
                'inventory_items']
        item_count = 0
        for inventory_item in inventory_items:
            if "item" in inventory_item['inventory_item_data']:
                item = inventory_item['inventory_item_data']['item']
                if item['item_id'] in self.MIN_ITEMS and "count" in item and item['count'] > self.MIN_ITEMS[
                    item['item_id']]:
                    recycle_count = item['count'] - self.MIN_ITEMS[item['item_id']]
                    item_count += item['count'] - recycle_count
                    self.log.info("Recycling Item_ID {0}, item count {1}".format(item['item_id'], recycle_count))
                    res = self.recycle_inventory_item(item_id=item['item_id'], count=recycle_count).call()['responses'][
                        'RECYCLE_INVENTORY_ITEM']
                    response_code = res['result']
                    if response_code == 1:
                        self.log.info("Recycled Item %s, New Count: %s", item['item_id'], res.get('new_count', 0))
                    else:
                        self.log.info("Failed to recycle Item %s, Code: %s", item['item_id'], response_code)
                    sleep(2)
                elif "count" in item:
                    item_count += item['count']
        if item_count > 0:
            self.log.info('Intentory has %s/%s items', item_count, self.player.max_item_storage)
        return self.update_player_inventory()

    def get_caught_pokemons(self, inventory_items):
        caught_pokemon = defaultdict(list)
        for inventory_item in inventory_items:
            if "pokemon_data" in inventory_item['inventory_item_data']:
                # is a pokemon:
                pokemon = Pokemon(inventory_item['inventory_item_data']['pokemon_data'], self.pokemon_names)
                pokemon.pokemon_additional_data = self.game_master.get(pokemon.pokemon_id, PokemonData())
                if not pokemon.is_egg:
                    caught_pokemon[pokemon.pokemon_id].append(pokemon)
        return caught_pokemon

    def do_release_pokemon(self, pokemon):
        self.log.info("Releasing pokemon: %s", pokemon)
        self.release_pokemon(pokemon_id=pokemon.id)
        release_res = self.call()['responses']['RELEASE_POKEMON']
        status = release_res.get('result', -1)
        if status == 1:
            self.log.info("Successfully Released Pokemon %s", pokemon)
        else:
            self.log.debug("Failed to release pokemon %s, %s", pokemon, release_res)
            self.log.info("Failed to release Pokemon %s", pokemon)
        sleep(3)

    def cleanup_pokemon(self, inventory_items=None):
        if not inventory_items:
                inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta'][
                    'inventory_items']
        caught_pokemon = self.get_caught_pokemons(inventory_items)
        for pokemons in caught_pokemon.values():
            if len(pokemons) > self.MIN_SIMILAR_POKEMON:
                # highest lvl pokemon first
                sorted_pokemons = sorted(pokemons, key=self.pokemon_lvl, reverse=True)
                for pokemon in sorted_pokemons[self.MIN_SIMILAR_POKEMON:]:
                    if self.is_pokemon_eligible_for_transfer(pokemon, sorted_pokemons[0]):
                        self.do_release_pokemon(pokemon)

    def is_pokemon_eligible_for_transfer(self, pokemon, best_pokemon):
        # never release favorites and other defined pokemons
        if pokemon.is_favorite or pokemon.pokemon_id in self.keep_pokemon_ids:
            return False
        elif self.RELEASE_DUPLICATES and (
                    self.pokemon_lvl(best_pokemon) * self.RELEASE_DUPLICATES_SCALER > self.pokemon_lvl(
                    pokemon) and self.pokemon_lvl(pokemon) < self.RELEASE_DUPLICATES_MAX_LV):
            return True
        # release defined throwaway pokemons  but make sure we have kept at least 1 (dont throw away all of them)
        elif pokemon.pokemon_id in self.throw_pokemon_ids:
            return True
        # keep high-cp pokemons
        elif pokemon.cp > self.KEEP_CP_OVER or pokemon.iv > self.KEEP_IV_OVER:
            return False
        # if we haven't found a reason to keep it, transfer it
        else:
            return True

    def pokemon_lvl(self, pokemon):
        if self.DEFINE_POKEMON_LV == "CP":
            return pokemon.cp
        elif self.DEFINE_POKEMON_LV == "IV":
            return pokemon.iv
        elif self.DEFINE_POKEMON_LV == "CP*IV":
            return pokemon.cp * pokemon.iv
        elif self.DEFINE_POKEMON_LV == "CP+IV":
            return pokemon.cp + pokemon.iv

    def attempt_evolve(self, inventory_items=None):
        if not inventory_items:
            inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta'][
                'inventory_items']
        caught_pokemon = self.get_caught_pokemons(inventory_items)
        self.inventory = Player_Inventory(inventory_items)
        for pokemons in caught_pokemon.values():
            if len(pokemons) > self.MIN_SIMILAR_POKEMON:
                pokemons = sorted(pokemons, key=lambda x: (x.cp, x.iv), reverse=True)
                for pokemon in pokemons[self.MIN_SIMILAR_POKEMON:]:
                    # If we can't evolve this type of pokemon anymore, don't check others.
                    if not self.attempt_evolve_pokemon(pokemon):
                        break

    def attempt_evolve_pokemon(self, pokemon):
        if self.is_pokemon_eligible_for_evolution(pokemon=pokemon):
            self.log.info("Evolving pokemon: %s", pokemon)
            evo_res = self.evolve_pokemon(pokemon_id=pokemon.id).call()['responses']['EVOLVE_POKEMON']
            status = evo_res.get('result', -1)
            sleep(3)
            if status == 1:
                evolved_pokemon = Pokemon(evo_res.get('evolved_pokemon_data', {}), self.pokemon_names,
                                          self.game_master.get(str(pokemon.pokemon_id), PokemonData()))
                # I don' think we need additional stats for evolved pokemon. Since we do not do anything with it.
                # evolved_pokemon.pokemon_additional_data = self.game_master.get(pokemon.pokemon_id, PokemonData())
                self.log.info("Evolved to %s", evolved_pokemon)
                self.update_player_inventory()
                return True
            else:
                self.log.debug("Could not evolve Pokemon %s", evo_res)
                self.log.info("Could not evolve pokemon %s | Status %s", pokemon, status)
                self.update_player_inventory()
                return False
        else:
            return False

    def is_pokemon_eligible_for_evolution(self, pokemon):
        return self.inventory.pokemon_candy.get(self.POKEMON_EVOLUTION_FAMILY.get(pokemon.pokemon_id, None),
                                                -1) > self.POKEMON_EVOLUTION.get(pokemon.pokemon_id, None) \
               and pokemon.pokemon_id not in self.keep_pokemon_ids \
               and not pokemon.is_favorite \
               and pokemon.pokemon_id in self.POKEMON_EVOLUTION

    def disk_encounter_pokemon(self, lureinfo, retry=False):
        try:
            self.update_player_inventory()
            if not self.inventory.can_attempt_catch():
                self.log.info("No balls to catch %s, exiting disk encounter", self.inventory)
                return False
            encounter_id = lureinfo['encounter_id']
            fort_id = lureinfo['fort_id']
            position = self._posf
            self.log.debug("At Fort with lure %s".encode('utf-8', 'ignore'), lureinfo)
            self.log.info("At Fort with Lure AND Active Pokemon %s",
                          self.pokemon_names.get(str(lureinfo.get('active_pokemon_id', 0)), "NA"))
            resp = self.disk_encounter(encounter_id=encounter_id, fort_id=fort_id, player_latitude=position[0],
                                       player_longitude=position[1]).call()['responses']['DISK_ENCOUNTER']
            pokemon = Pokemon(resp.get('pokemon_data', {}), self.pokemon_names)
            result = resp.get('result', -1)
            capture_probability = create_capture_probability(resp.get('capture_probability', {}))
            self.log.debug("Attempt Encounter: %s", json.dumps(resp, indent=4, sort_keys=True))
            if result == 1:
                return self.do_catch_pokemon(encounter_id, fort_id, capture_probability, pokemon)
            elif result == 5:
                self.log.info("Couldn't catch %s Your pokemon bag was full, attempting to clear and re-try",
                              self.pokemon_names.get(str(lureinfo.get('active_pokemon_id', 0)), "NA"))
                self.cleanup_pokemon()
                if not retry:
                    return self.disk_encounter_pokemon(lureinfo, retry=True)
            else:
                self.log.info("Could not start Disk (lure) encounter for pokemon: %s",
                              self.pokemon_names.get(str(lureinfo.get('active_pokemon_id', 0)), "NA"))
        except Exception as e:
            self.log.error("Error in disk encounter %s", e)
            return False

    def do_catch_pokemon(self, encounter_id, spawn_point_id, capture_probability, pokemon):
        self.log.info("Catching Pokemon: %s", pokemon)
        catch_attempt = self.attempt_catch(encounter_id, spawn_point_id, capture_probability)
        capture_status = catch_attempt.get('status', -1)
        if capture_status == 1:
            self.log.debug("Caught Pokemon: : %s", catch_attempt)
            self.log.info("Caught Pokemon:  %s", pokemon)
            self.pokemon_caught += 1
            return True
        elif capture_status == 3:
            self.log.debug("Pokemon fleed : %s", catch_attempt)
            self.log.info("Pokemon fleed:  %s", pokemon)
            return False
        elif capture_status == 2:
            self.log.debug("Pokemon escaped: : %s", catch_attempt)
            self.log.info("Pokemon escaped:  %s", pokemon)
            return False
        elif capture_status == 4:
            self.log.debug("Catch Missed: : %s", catch_attempt)
            self.log.info("Catch Missed:  %s", pokemon)
            return False
        else:
            self.log.debug("Could not catch pokemon: %s", catch_attempt)
            self.log.info("Could not catch pokemon:  %s", pokemon)
            self.log.info("Could not catch pokemon:  %s, status: %s", pokemon, capture_status)
            return False

    def encounter_pokemon(self, pokemon_data, retry=False):  # take in a MapPokemon from MapCell.catchable_pokemons
        # Update Inventory to make sure we can catch this mon
        try:
            self.update_player_inventory()
            if not self.inventory.can_attempt_catch():
                self.log.info("No balls to catch %s, exiting encounter", self.inventory)
                return False
            encounter_id = pokemon_data['encounter_id']
            spawn_point_id = pokemon_data['spawn_point_id']
            # begin encounter_id
            position = self.get_position()
            self.log.info("Trying initiate catching Pokemon: %s", Pokemon(pokemon_data, self.pokemon_names))
            encounter = self.encounter(encounter_id=encounter_id,
                                       spawn_point_id=spawn_point_id,
                                       player_latitude=position[0],
                                       player_longitude=position[1]).call()['responses']['ENCOUNTER']
            self.log.debug("Attempting to Start Encounter: %s", encounter)
            pokemon = Pokemon(encounter.get('wild_pokemon', {}).get('pokemon_data', {}), self.pokemon_names)
            result = encounter.get('status', -1)
            capture_probability = create_capture_probability(encounter.get('capture_probability', {}))
            self.log.debug("Attempt Encounter Capture Probability: %s", json.dumps(encounter, indent=4, sort_keys=True))
            if result == 1:
                return self.do_catch_pokemon(encounter_id, spawn_point_id, capture_probability, pokemon)
            elif result == 7:
                self.log.info("Couldn't catch %s Your pokemon bag was full, attempting to clear and re-try", pokemon)
                self.cleanup_pokemon()
                if not retry:
                    return self.encounter_pokemon(pokemon, retry=True)
            else:
                self.log.info("Could not start encounter for pokemon: %s", pokemon)
            return False
        except Exception as e:
            self.log.error("Error in pokemon encounter %s", e)
            return False

    def incubate_eggs(self):
        if not self.EGG_INCUBATION_ENABLED:
            return
        if self.player_stats.km_walked > 0:
            for incubator in self.inventory.incubators_busy:
                incubator_egg_distance = incubator['target_km_walked'] - incubator['start_km_walked']
                incubator_distance_done = self.player_stats.km_walked - incubator['start_km_walked']
                if incubator_distance_done > incubator_egg_distance:
                    self.attempt_finish_incubation()
                    break
            for incubator in self.inventory.incubators_busy:
                incubator_egg_distance = incubator['target_km_walked'] - incubator['start_km_walked']
                incubator_distance_done = self.player_stats.km_walked - incubator['start_km_walked']
                self.log.info('Incubating %skm egg, %skm done', incubator_egg_distance, round(incubator_distance_done, 2))
        for incubator in self.inventory.incubators_available:
            if incubator['item_id'] == 901:  # unlimited use
                pass
            elif self.USE_DISPOSABLE_INCUBATORS and incubator['item_id'] == 902:  # limited use
                pass
            else:
                continue
            eggs_available = self.inventory.eggs_available
            eggs_available = sorted(eggs_available, key=lambda egg: egg['creation_time_ms'],
                                    reverse=False)  # oldest first
            eggs_available = sorted(eggs_available, key=lambda egg: egg['egg_km_walked_target'],
                                    reverse=self.INCUBATE_BIG_EGGS_FIRST)  # now sort as defined
            if not len(eggs_available) > 0 or not self.attempt_start_incubation(eggs_available[0], incubator):
                break

    def attempt_start_incubation(self, egg, incubator):
        self.log.info("Start incubating %skm egg", egg['egg_km_walked_target'])
        incubate_res = self.use_item_egg_incubator(item_id=incubator['id'], pokemon_id=egg['id']).call()['responses']['USE_ITEM_EGG_INCUBATOR']
        status = incubate_res.get('result', -1)
        sleep(3)
        if status == 1:
            self.log.info("Incubation started with %skm egg !", egg['egg_km_walked_target'])
            self.update_player_inventory()
            return True
        else:
            self.log.debug("Could not start incubating %s", incubate_res)
            self.log.info("Could not start incubating %s egg | Status %s", egg['egg_km_walked_target'], status)
            self.update_player_inventory()
            return False

    def attempt_finish_incubation(self):
        self.log.info("Checking for hatched eggs")
        hatch_res = self.get_hatched_eggs().call()['responses']['GET_HATCHED_EGGS']
        status = hatch_res.get('success', -1)
        sleep(3)
        if status == 1:
            self.update_player_inventory()
            i = 0
            for pokemon_id in hatch_res['pokemon_id']:
                pokemon = get_pokemon_by_long_id(pokemon_id, self.inventory.inventory_items,
                                                    self.pokemon_names)
                self.log.info("Egg Hatched! XP +%s, Candy +%s, Stardust +%s, %s",
                              hatch_res['experience_awarded'][i],
                              hatch_res['candy_awarded'][i],
                              hatch_res['stardust_awarded'][i],
                              pokemon)
                i += 1
            return True
        else:
            self.log.debug("Could not get hatched eggs %s", hatch_res)
            self.log.info("Could not get hatched eggs Status %s", status)
            self.update_player_inventory()
            return False

    def login(self, provider, username, password, cached=False):
        if not isinstance(username, basestring) or not isinstance(password, basestring):
            raise AuthException("Username/password not correctly specified")

        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        else:
            raise AuthException("Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: %s', provider)

        if not self._auth_provider.login(username, password):
            self.log.info('Login process failed')
            return False

        self.log.info('Starting RPC login sequence (app simulation)')
        # making a standard call, like it is also done by the client
        self.get_player()
        self.get_hatched_eggs()
        self.get_inventory()
        self.check_awarded_badges()
        self.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")

        response = self.call()

        if not response:
            self.log.info('Login failed!')
        if os.path.isfile("auth_cache") and cached:
            response = pickle.load(open("auth_cache"))
        fname = "auth_cache_%s" % username
        if os.path.isfile(fname) and cached:
            response = pickle.load(open(fname))
        else:
            response = self.heartbeat()
            f = open(fname, "w")
            pickle.dump(response, f)
        if not response:
            self.log.info('Login failed!')
            return False

        if 'api_url' in response:
            self._api_endpoint = ('https://{}/rpc'.format(response['api_url']))
            self.log.debug('Setting API endpoint to: %s', self._api_endpoint)
        else:
            self.log.error('Login failed - unexpected server response!')
            return False

        if 'auth_ticket' in response:
            self._auth_provider.set_ticket(response['auth_ticket'].values())

        self.log.info('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')

        return True

    def main_loop(self):
        catch_attempt = 0
        self.heartbeat()
        while True:
            self.heartbeat()
            sleep(1)

            if self.experimental and self.spin_all_forts:
                self.spin_all_forts_visible()
            else:
                self.spin_near_fort()
            # if catching fails 10 times, maybe you are sofbanned.
            while self.catch_near_pokemon() and catch_attempt <= self.max_catch_attempts:
                sleep(4)
                catch_attempt += 1
                pass
            if catch_attempt > self.max_catch_attempts:
                self.log.warn("Your account may be softbaned Or no Pokeballs. Failed to catch pokemon %s times",
                              catch_attempt)
            catch_attempt = 0

    @staticmethod
    def flatmap(f, items):
        return list(chain.from_iterable(imap(f, items)))
Example #6
0
class PGoApi:

    API_ENTRY = 'https://pgorelease.nianticlabs.com/plfe/rpc'

    def __init__(self, config, pokemon_names, start_pos):

        self.log = logging.getLogger(__name__)
        self._start_pos = start_pos
        self._walk_count = 1
        self._auth_provider = None
        self._api_endpoint = None
        self.config = config
        self.set_position(*start_pos)
        self._pokeball_type = 1
        self.MIN_KEEP_IV = config.get("MIN_KEEP_IV", 0)
        self.KEEP_CP_OVER = config.get("KEEP_CP_OVER", 0)
        self.RELEASE_DUPLICATES = config.get("RELEASE_DUPLICATE", 0)
        self.DUPLICATE_CP_FORGIVENESS = config.get("DUPLICATE_CP_FORGIVENESS", 0)
        self.MAX_BALL_TYPE = config.get("MAX_BALL_TYPE", 0)
        self._req_method_list = []
        self._heartbeat_number = 5
        self.pokemon_names = pokemon_names
        self.pokeballs = [0, 0, 0, 0]  # pokeball counts. set to 0 to force atleast one fort check  before trying to capture pokemon
        self.min_item_counts = dict(
            ((getattr(Inventory, key), value) for key, value in config.get('MIN_ITEM_COUNTS', {}).iteritems())
        )

    def call(self):
        if not self._req_method_list:
            return False

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

        player_position = self.get_position()

        request = RpcApi(self._auth_provider)

        if self._api_endpoint:
            api_endpoint = self._api_endpoint
        else:
            api_endpoint = self.API_ENTRY

        self.log.debug('Execution of RPC')
        response = None
        try:
            response = request.request(api_endpoint, self._req_method_list, player_position)
        except ServerBusyOrOfflineException:
            self.log.info('Server seems to be busy or offline - try again!')

        # cleanup after call execution
        self.log.debug('Cleanup of request!')
        self._req_method_list = []

        return response

    def list_curr_methods(self):
        for i in self._req_method_list:
            print("{} ({})".format(RequestType.Name(i), i))

    def set_logger(self, logger):
        self._ = logger or logging.getLogger(__name__)

    def get_position(self):
        return (self._position_lat, self._position_lng, self._position_alt)

    def set_position(self, lat, lng, alt):
        self.log.debug('Set Position - Lat: %s Long: %s Alt: %s', lat, lng, alt)
        self._posf = (lat, lng, alt)
        self._position_lat = f2i(lat)
        self._position_lng = f2i(lng)
        self._position_alt = f2i(alt)

    def __getattr__(self, func):
        def function(**kwargs):

            if not self._req_method_list:
                self.log.debug('Create new request...')

            name = func.upper()
            if kwargs:
                self._req_method_list.append({RequestType.Value(name): kwargs})
                self.log.debug("Adding '%s' to RPC request including arguments", name)
                self.log.debug("Arguments of '%s': \n\r%s", name, kwargs)
            else:
                self._req_method_list.append(RequestType.Value(name))
                self.log.debug("Adding '%s' to RPC request", name)

            return self

        if func.upper() in RequestType.keys():
            return function
        else:
            raise AttributeError

    def heartbeat(self):
        self.get_player()
        if self._heartbeat_number % 10 == 0:  # every 10 heartbeats do a inventory check
            self.check_awarded_badges()
            self.get_inventory()
        res = self.call()
        if res.get("direction", -1) == 102:
            self.log.error("There were a problem responses for api call: %s. Restarting!!!", res)
            raise AuthException("Token probably expired?")
        self.log.debug('Heartbeat dictionary: \n\r{}'.format(json.dumps(res, indent=2)))

        if 'GET_PLAYER' in res['responses']:
            player_data = res['responses'].get('GET_PLAYER', {}).get('player_data', {})
            if os.path.isfile("accounts/%s.json" % self.config['username']):
                with open("accounts/%s.json" % self.config['username'], "r") as f:
                    file = f.read()
                    json_file = json.loads(file)
                inventory_items = json_file.get('GET_INVENTORY', {}).get('inventory_delta', {}).get('inventory_items', [])
                inventory_items_dict_list = map(lambda x: x.get('inventory_item_data', {}), inventory_items)
                player_stats = filter(lambda x: 'player_stats' in x, inventory_items_dict_list)[0].get('player_stats', {})
            else:
                player_stats = {}
            currencies = player_data.get('currencies', [])
            currency_data = ",".join(map(lambda x: "{0}: {1}".format(x.get('name', 'NA'), x.get('amount', 'NA')), currencies))
            self.log.info("Username: %s, Lvl: %s, XP: %s/%s, Currencies: %s", player_data.get('username', 'NA'), player_stats.get('level', 'NA'), player_stats.get('experience', 'NA'), player_stats.get('next_level_xp', 'NA'), currency_data)  # display stats

        if 'GET_INVENTORY' in res['responses']:
            with open("accounts/%s.json" % self.config['username'], "w") as f:
                res['responses']['lat'] = self._posf[0]
                res['responses']['lng'] = self._posf[1]
                f.write(json.dumps(res['responses'], indent=2))
            self.log.info("List of Pokemon:\n" + get_inventory_data(res, self.pokemon_names) + "\nTotal Pokemon count: " + str(get_pokemon_num(res)) + "\nEgg Hatching status: " + get_incubators_stat(res))
            self.log.debug(self.cleanup_inventory(res['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']))

        self._heartbeat_number += 1
        return res

    def walk_to(self, loc):
        self._walk_count += 1
        steps = get_route(self._posf, loc, self.config.get("USE_GOOGLE", False), self.config.get("GMAPS_API_KEY", ""))
        for step in steps:
            for i, next_point in enumerate(get_increments(self._posf, step, self.config.get("STEP_SIZE", 200))):
                self.set_position(*next_point)
                self.heartbeat()
                self.log.info("Sleeping before next heartbeat")
                sleep(2)  # If you want to make it faster, delete this line... would not recommend though
                # make sure we have atleast 1 ball
                if sum(self.pokeballs) > 0:
                    while self.catch_near_pokemon():
                        sleep(1) # If you want to make it faster, delete this line... would not recommend though

    # this is in charge of spinning a pokestop
    def spin_near_fort(self):
        map_cells = self.nearby_map_objects().get('responses', {}).get('GET_MAP_OBJECTS', {}).get('map_cells', {})
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        if self._start_pos and self._walk_count % self.config.get("RETURN_START_INTERVAL") == 0:
            destinations = filtered_forts(self._start_pos, forts)
        else:
            destinations = filtered_forts(self._posf, forts)

        if destinations:
            destination_num = random.randint(0, min(5, len(destinations) - 1))
            fort = destinations[destination_num]
            self.log.info("Walking to fort at %s,%s", fort['latitude'], fort['longitude'])
            self.walk_to((fort['latitude'], fort['longitude']))
            position = self._posf # FIXME ?
            res = self.fort_search(fort_id=fort['id'], fort_latitude=fort['latitude'], fort_longitude=fort['longitude'], player_latitude=position[0], player_longitude=position[1]).call()['responses']['FORT_SEARCH']
            self.log.debug("Fort spinned: %s", res)
            if 'lure_info' in fort:
                encounter_id = fort['lure_info']['encounter_id']
                fort_id = fort['lure_info']['fort_id']
                position = self._posf
                resp = self.disk_encounter(encounter_id=encounter_id, fort_id=fort_id, player_latitude=position[0], player_longitude=position[1]).call()['responses']['DISK_ENCOUNTER']
                self.log.debug('Encounter response is: %s', resp)
                if self.pokeballs[1] > 9 and self.pokeballs[2] > 4 and self.pokeballs[3] > 4:
                    self.disk_encounter_pokemon(fort['lure_info'])
            return True
        else:
            self.log.error("No fort to walk to!")
            return False

    # this will catch any nearby pokemon
    def catch_near_pokemon(self):
        map_cells = self.nearby_map_objects().get('responses', {}).get('GET_MAP_OBJECTS', {}).get('map_cells', {})
        pokemons = PGoApi.flatmap(lambda c: c.get('catchable_pokemons', []), map_cells)

        # catch first pokemon:
        origin = (self._posf[0], self._posf[1])
        pokemon_distances = [(pokemon, distance_in_meters(origin, (pokemon['latitude'], pokemon['longitude']))) for pokemon in pokemons]
        self.log.debug("Nearby pokemon: : %s", pokemon_distances)
        for pokemon_distance in pokemon_distances:
            target = pokemon_distance
            self.log.debug("Catching pokemon: : %s, distance: %f meters", target[0], target[1])
            self.log.info("Catching Pokemon: %s", self.pokemon_names[str(target[0]['pokemon_id'])])
            return self.encounter_pokemon(target[0])
            if sum(self.pokeballs) == 0:
                self.spin_near_fort()
        return False

    def nearby_map_objects(self):
        position = self.get_position()
        neighbors = get_neighbors(self._posf)
        return self.get_map_objects(latitude=position[0], longitude=position[1], since_timestamp_ms=[0] * len(neighbors), cell_id=neighbors).call()

    def attempt_catch(self, encounter_id, spawn_point_guid, ball_type):
        r = self.catch_pokemon(
            normalized_reticle_size=1.950,
            pokeball=ball_type,
            spin_modifier=0.850,
            hit_pokemon=True,
            normalized_hit_position=1,
            encounter_id=encounter_id,
            spawn_point_guid=spawn_point_guid,
        ).call()['responses']['CATCH_POKEMON']
        self.log.info("Throwing pokeball type: %s", POKEBALLS[ball_type - 1]) # list the pokeball that was thrown
        if "status" in r:
            self.log.debug("Status: %d", r['status'])
            return r

    def cleanup_inventory(self, inventory_items=None):
        if not inventory_items:
            inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']

        all_actual_items = [xiq['inventory_item_data']["item"] for xiq in inventory_items if "item" in xiq['inventory_item_data']]
        all_actual_item_str = "List of items:\n"
        all_actual_item_count = 0
        all_actual_items = sorted([x for x in all_actual_items if "count" in x], key=lambda x: x["item_id"])
        for xiq in all_actual_items:
            if 1 <= xiq["item_id"] <= 4: # save counts of pokeballs
                self.pokeballs[xiq["item_id"]] = xiq["count"]
            true_item_name = INVENTORY_DICT[xiq["item_id"]]
            all_actual_item_str += "Item_ID " + str(xiq["item_id"]) + "\titem count " + str(xiq["count"]) + "\t(" + true_item_name + ")\n"
            all_actual_item_count += xiq["count"]
        all_actual_item_str += "Total item count: " + str(all_actual_item_count)
        self.log.info(all_actual_item_str)

        caught_pokemon = defaultdict(list)
        for inventory_item in inventory_items:
            if "pokemon_data" in inventory_item['inventory_item_data']:
                # is a pokemon:
                pokemon = inventory_item['inventory_item_data']['pokemon_data']
                if 'cp' in pokemon and "favorite" not in pokemon:
                    caught_pokemon[pokemon["pokemon_id"]].append(pokemon)
            elif "item" in inventory_item['inventory_item_data']:
                item = inventory_item['inventory_item_data']['item']
                if item['item_id'] in self.min_item_counts and "count" in item and item['count'] > self.min_item_counts[item['item_id']]:
                    recycle_count = item['count'] - self.min_item_counts[item['item_id']]
                    self.log.info("Recycling {0}, item count {1}".format(INVENTORY_DICT[item['item_id']], recycle_count))
                    self.recycle_inventory_item(item_id=item['item_id'], count=recycle_count)

        for pokemons in caught_pokemon.values():
            if len(pokemons) > MIN_SIMILAR_POKEMON:  # if you have more then same amount of pokemon do this
                pokemons = sorted(pokemons, lambda x, y: cmp(x['cp'], y['cp']), reverse=True)
                for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                    if 'cp' in pokemon and pokemon_iv_percentage(pokemon) > self.MIN_KEEP_IV and pokemon["cp"] > self.KEEP_CP_OVER:  # Keep only if the pokemon is over the IV and CP set up
                        if pokemon['pokemon_id'] in CANDY_NEEDED_TO_EVOLVE:
                            for inventory_item in inventory_items:
                                if "pokemon_family" in inventory_item['inventory_item_data'] and inventory_item['inventory_item_data']['pokemon_family']['family_id'] == pokemon['pokemon_id'] and inventory_item['inventory_item_data']['pokemon_family']['candy'] > CANDY_NEEDED_TO_EVOLVE[pokemon['pokemon_id']]:
                                    self.log.info("Evolving pokemon: %s", self.pokemon_names[str(pokemon['pokemon_id'])])
                                    self.evolve_pokemon(pokemon_id=pokemon['id'])
                    else:
                        if pokemon['pokemon_id'] in CANDY_NEEDED_TO_EVOLVE:
                            for inventory_item in inventory_items:
                                if "pokemon_family" in inventory_item['inventory_item_data'] and inventory_item['inventory_item_data']['pokemon_family']['family_id'] == pokemon['pokemon_id'] and inventory_item['inventory_item_data']['pokemon_family']['candy'] > CANDY_NEEDED_TO_EVOLVE[pokemon['pokemon_id']]:
                                    self.log.info("Evolving pokemon: %s", self.pokemon_names[str(pokemon['pokemon_id'])])
                                    self.evolve_pokemon(pokemon_id=pokemon['id'])
                        self.log.debug("Releasing pokemon: %s", pokemon)
                        self.log.info("Releasing pokemon: %s IV: %s", self.pokemon_names[str(pokemon['pokemon_id'])], pokemon_iv_percentage(pokemon))
                        self.release_pokemon(pokemon_id=pokemon["id"])

        if self.RELEASE_DUPLICATES:
            for pokemons in caught_pokemon.values():
                if len(pokemons) > MIN_SIMILAR_POKEMON:
                    pokemons = sorted(pokemons, lambda x, y: cmp(self.pokemon_names[str(x['pokemon_id'])], self.pokemon_names[str(y['pokemon_id'])]))
                    last_pokemon = pokemons[0]
                    for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                        if self.pokemon_names[str(pokemon['pokemon_id'])] == self.pokemon_names[str(last_pokemon['pokemon_id'])]:
                            # Compare two pokemon if the larger IV pokemon has less then DUPLICATE_CP_FORGIVENESS times CP keep it
                            if pokemon_iv_percentage(pokemon) > pokemon_iv_percentage(pokemon):
                                if pokemon['cp'] * self.DUPLICATE_CP_FORGIVENESS < last_pokemon['cp']:
                                    # release the lesser!
                                    self.log.debug("Releasing pokemon: %s", last_pokemon)
                                    self.log.info("Releasing pokemon: %s IV: %s", self.pokemon_names[str(last_pokemon['pokemon_id'])], pokemon_iv_percentage(last_pokemon))
                                    self.release_pokemon(pokemon_id=last_pokemon["id"])
                                last_pokemon = pokemon
                            else:
                                if last_pokemon['cp'] * self.DUPLICATE_CP_FORGIVENESS > pokemon['cp']:
                                    # release the lesser!
                                    self.log.debug("Releasing pokemon: %s", pokemon)
                                    self.log.info("Releasing pokemon: %s IV: %s", self.pokemon_names[str(pokemon['pokemon_id'])], pokemon_iv_percentage(pokemon))
                                    self.release_pokemon(pokemon_id=pokemon["id"])

                        else:
                            last_pokemon = pokemon

        return self.call()

    def disk_encounter_pokemon(self, lureinfo):
        try:
            encounter_id = lureinfo['encounter_id']
            fort_id = lureinfo['fort_id']
            position = self._posf
            resp = self.disk_encounter(encounter_id=encounter_id, fort_id=fort_id, player_latitude=position[0], player_longitude=position[1]).call()['responses']['DISK_ENCOUNTER']
            if resp['result'] == 1:
                capture_status = -1
                self._pokeball_type = 1
                while capture_status != 0 and capture_status != 3:
                    for balls in range(len(self.pokeballs)):
                        self._pokeball_type = balls
                        if self.pokeballs[balls] > 0:
                            catch_attempt = self.attempt_catch(encounter_id, fort_id, self._pokeball_type)
                            self.pokeballs[self._pokeball_type] -= 1
                            capture_status = catch_attempt['status']
                            if capture_status == 1:
                                self.log.debug("Caught Pokemon: : %s", catch_attempt)
                                self.log.info("Caught Pokemon:  %s", self.pokemon_names[str(resp['pokemon_data']['pokemon_id'])])
                                self._pokeball_type = 1
                                sleep(2) # If you want to make it faster, delete this line... would not recommend though
                                return catch_attempt
                            elif capture_status == 2:
                                self.log.info("Pokemon %s is too wild", self.pokemon_names[str(resp['pokemon_data']['pokemon_id'])])
                                if self._pokeball_type < self.MAX_BALL_TYPE:
                                    self._pokeball_type += 1
                            elif capture_status == 3:
                                self.log.debug("Failed Catch: : %s", catch_attempt)
                                self.log.info("Failed to Catch Pokemon:  %s", self.pokemon_names[str(resp['pokemon_data']['pokemon_id'])])
                                self._pokeball_type = 1
                    sleep(2) # If you want to make it faster, delete this line... would not recommend though
            return False
        except Exception as e:
            self.log.error("Error in disk encounter %s", e)
            self._pokeball_type = 1
            return False

    def encounter_pokemon(self, pokemon):
        encounter_id = pokemon['encounter_id']
        spawn_point_id = pokemon['spawn_point_id']
        position = self._posf
        encounter = self.encounter(
            encounter_id=encounter_id,
            spawn_point_id=spawn_point_id,
            player_latitude=position[0],
            player_longitude=position[1]
        ).call()['responses']['ENCOUNTER']
        # this cade catches pokemon
        self.log.debug("Started Encounter: %s", encounter)
        if encounter['status'] == 1:
            capture_status = -1
            self._pokeball_type = 1  # start with a pokeball
            while capture_status != 0 and capture_status != 3:
                for balls in range(len(self.pokeballs)):  # try with each ball type starting with weakest
                    self._pokeball_type = balls
                    if self.pokeballs[balls] > 0:  # if you have less then 1 ball do not attempt to catch em all
                        catch_attempt = self.attempt_catch(encounter_id, spawn_point_id, self._pokeball_type)  # actual catching code
                        self.pokeballs[self._pokeball_type] -= 1  # lowers the thrown ball code
                        capture_status = catch_attempt['status']
                        if capture_status == 1:
                            self.log.debug("Caught Pokemon: : %s", catch_attempt)  # you did it
                            self.log.info("Caught Pokemon:  %s", self.pokemon_names[str(pokemon['pokemon_id'])])
                            self._pokeball_type = 1
                            sleep(2) # If you want to make it faster, delete this line... would not recommend though
                            return catch_attempt
                        elif capture_status == 2:
                            self.log.info("Pokemon %s is too wild", self.pokemon_names[str(pokemon['pokemon_id'])])
                            if self._pokeball_type < self.MAX_BALL_TYPE:
                                self._pokeball_type += 1  # try with a stronger ball
                        elif capture_status == 3:
                            self.log.debug("Failed Catch: : %s", catch_attempt)  # potential soft ban or just a run away
                            self.log.info("Failed to Catch Pokemon:  %s", self.pokemon_names[str(pokemon['pokemon_id'])])
                            self._pokeball_type = 1
                sleep(2) # If you want to make it faster, delete this line... would not recommend though
        return False

    def login(self, provider, username, password, cached=False):
        if not isinstance(username, basestring) or not isinstance(password, basestring):
            raise AuthException("Username/password not correctly specified")

        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        else:
            raise AuthException("Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: %s', provider)

        if not self._auth_provider.login(username, password):
            self.log.info('Login process failed')
            return False

        self.log.info('Starting RPC login sequence (app simulation)')
        self.get_player()
        self.get_hatched_eggs()
        self.get_inventory()
        self.check_awarded_badges()
        self.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")  # not sure what this is but dont change it

        response = self.call()

        if not response:
            self.log.info('Login failed!')
        if os.path.isfile("auth_cache") and cached:
            response = pickle.load(open("auth_cache"))
        fname = "auth_cache_%s" % username
        if os.path.isfile(fname) and cached:
            response = pickle.load(open(fname))
        else:
            response = self.heartbeat()
            f = open(fname, "w")
            pickle.dump(response, f)
        if not response:
            self.log.info('Login failed!')
            return False

        if 'api_url' in response:
            self._api_endpoint = ('https://{}/rpc'.format(response['api_url']))
            self.log.debug('Setting API endpoint to: %s', self._api_endpoint)
        else:
            self.log.error('Login failed - unexpected server response!')
            return False

        if 'auth_ticket' in response:
            self._auth_provider.set_ticket(response['auth_ticket'].values())

        self.log.info('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')

        return True

    def main_loop(self):
        self.heartbeat()
        while True:
            self.heartbeat()
            sleep(1) # If you want to make it faster, delete this line... would not recommend though
            if sum(self.pokeballs) > 0:  # if you do not have any balls skip pokemon catching
                while self.catch_near_pokemon():
                    sleep(4) # If you want to make it faster, delete this line... would not recommend though
            else:
                self.log.info("Less than 1 Poke Balls: Entering pokestops only")
            self.spin_near_fort()  # check local pokestop

    @staticmethod
    def flatmap(f, items):
        return chain.from_iterable(imap(f, items))
Example #7
0
class PGoApi:

    API_ENTRY = 'https://pgorelease.nianticlabs.com/plfe/rpc'

    def __init__(self, config, pokemon_data, start_pos, print_info):

        self.log = logging.getLogger(__name__)
        self._start_pos = start_pos
        self._walk_count = 1
        self._auth_provider = None
        self._api_endpoint = None
        self.config = config
        self.set_position(*start_pos)
        self.RELEASE_DUPLICATES = config.get("RELEASE_DUPLICATE", 0)
        self._req_method_list = []
        self._heartbeat_number = 8
        self.pokemon_data = pokemon_data
        self.do_catch_pokemon = config.get("CATCH_POKEMON", True)
        self.min_probability_throw = config.get("MIN_PROBABILITY_THROW", 0.5)
        self.print_info = print_info
        self.MIN_KEEP_CP = config.get("MIN_KEEP_CP", 1000)

    def call(self):
        if not self._req_method_list:
            return False

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

        player_position = self.get_position()

        request = RpcApi(self._auth_provider)

        if self._api_endpoint:
            api_endpoint = self._api_endpoint
        else:
            api_endpoint = self.API_ENTRY

        self.log.debug('Execution of RPC')
        response = None
        try:
            response = request.request(api_endpoint, self._req_method_list, player_position)
        except ServerBusyOrOfflineException as e:
            self.log.info(colored('Server seems to be busy or offline - try again!', 'red'))

        # cleanup after call execution
        self.log.debug('Cleanup of request!')
        self._req_method_list = []

        return response

    def list_curr_methods(self):
        for i in self._req_method_list:
            print("{} ({})".format(RequestType.Name(i),i))

    def set_logger(self, logger):
        self._ = logger or logging.getLogger(__name__)

    def get_position(self):
        return (self._position_lat, self._position_lng, self._position_alt)

    def set_position(self, lat, lng, alt):
        self.log.debug('Set Position - Lat: %s Long: %s Alt: %s', lat, lng, alt)
        self._posf = (lat, lng, alt)
        self._position_lat = f2i(lat)
        self._position_lng = f2i(lng)
        self._position_alt = f2i(alt)

    def __getattr__(self, func):
        def function(**kwargs):

            if not self._req_method_list:
                self.log.debug('Create new request...')

            name = func.upper()
            if kwargs:
                self._req_method_list.append( { RequestType.Value(name): kwargs } )
                self.log.debug("Adding '%s' to RPC request including arguments", name)
                self.log.debug("Arguments of '%s': \n\r%s", name, kwargs)
            else:
                self._req_method_list.append( RequestType.Value(name) )
                self.log.debug("Adding '%s' to RPC request", name)

            return self

        if func.upper() in RequestType.keys():
            return function
        else:
            raise AttributeError

    def heartbeat(self):
        self.get_player()
        if self._heartbeat_number % 10 == 0:
            self.check_awarded_badges()
            self.get_inventory()

        res = self.call()

        if res.get("direction",-1) == 102:
            self.log.error("There were a problem responses for api call: %s. Restarting!!!", res)
            raise AuthException("Token probably expired?")

        self.log.debug('Heartbeat dictionary: \n\r{}'.format(json.dumps(res, indent=2)))

        if 'GET_PLAYER' in res['responses']:
            player_data = res['responses'].get('GET_PLAYER', {}).get('player_data', {})
            currencies = player_data.get('currencies', [])
            currency_data = ",".join(map(lambda x: "{0}: {1}".format(x.get('name', 'NA'), x.get('amount', 'NA')), currencies))
#            self.log.info("Username: %s, Currencies: %s", player_data.get('username', 'NA'), currency_data)

        if 'GET_INVENTORY' in res['responses']:
            with open("accounts/%s.json" % self.config['username'], "w") as f:
                res['responses']['lat'] = self._posf[0]
                res['responses']['lng'] = self._posf[1]
                res['responses']['alt'] = self._posf[2]
                f.write(json.dumps(res['responses'], indent=2))

            if self.print_info:
                self.log.info(get_inventory_data(res, self.pokemon_data))
                self.print_info = False

            self.log.debug(self.cleanup_inventory(res['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']))

        self._heartbeat_number += 1
        return res

    def walk_to(self,loc):
        self._walk_count += 1
        steps = get_route(self._posf, loc, self.config.get("USE_GOOGLE", False), self.config.get("GMAPS_API_KEY", ""))
        for step in steps:
            for i, next_point in enumerate(get_increments(self._posf, step, self.config.get("STEP_SIZE", 200))):
                self.set_position(*next_point)
                self.heartbeat()
#                self.log.info("Sleeping before next heartbeat")
                sleep(2) # If you want to make it faster, delete this line... would not recommend though
                while self.catch_near_pokemon():
                    sleep(1) # If you want to make it faster, delete this line... would not recommend though



    def spin_near_fort(self):
        map_cells = self.nearby_map_objects()['responses']['GET_MAP_OBJECTS']['map_cells']
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        if self._start_pos and self._walk_count % self.config.get("RETURN_START_INTERVAL") == 0:
            sleep_time = self.config.get("RETURN_START_INTERVAL") / self.config.get("STEP_SIZE", 200) * 100
            self.log.info(colored("Returning home and sleeping for %d seconds", "cyan"), sleep_time)
            sleep(sleep_time)
            destinations = filtered_forts(self._start_pos, forts)
        else:
            destinations = filtered_forts(self._posf,forts)

        if destinations:
            destinationNum = random.randint(0, min(5, len(destinations) - 1))
            fort = destinations[destinationNum]
            self.log.info("Walking to Pokestop at %s,%s", fort['latitude'], fort['longitude'])
            self.walk_to((fort['latitude'], fort['longitude']))
            position = self._posf # FIXME ?
            res = self.fort_search(fort_id = fort['id'], fort_latitude=fort['latitude'],fort_longitude=fort['longitude'],player_latitude=position[0],player_longitude=position[1]).call()['responses']['FORT_SEARCH']
            # self.log.info("Fort spinned: %s", res)
            if 'lure_info' in fort:
                encounter_id = fort['lure_info']['encounter_id']
                fort_id = fort['lure_info']['fort_id']
                position = self._posf
                resp = self.disk_encounter(encounter_id=encounter_id, fort_id=fort_id, player_latitude=position[0], player_longitude=position[1]).call()['responses']['DISK_ENCOUNTER']
                self.disk_encounter_pokemon(fort['lure_info'])
            return True
        else:
            self.log.error("No Pokestop to walk to!")
            return False

    def catch_near_pokemon(self):
        map_cells = self.nearby_map_objects()['responses']['GET_MAP_OBJECTS']['map_cells']
        pokemons = PGoApi.flatmap(lambda c: c.get('catchable_pokemons', []), map_cells)
        pokemons = [pokemon for pokemon in pokemons if pokemon['pokemon_id'] not in IGNORE_POKEMON]

        # catch first pokemon:
        origin = (self._posf[0], self._posf[1])
        pokemon_distances = [(pokemon, distance_in_meters(origin,(pokemon['latitude'], pokemon['longitude']))) for pokemon in pokemons]
        self.log.debug("Nearby pokemon: : %s", pokemon_distances)
        for pokemon_distance in pokemon_distances:
            target = pokemon_distance
            return self.encounter_pokemon(target[0])
        return False

    def nearby_map_objects(self):
        position = self.get_position()
        neighbors = getNeighbors(self._posf)
        data = self.get_map_objects(latitude=position[0], longitude=position[1], since_timestamp_ms=[0]*len(neighbors), cell_id=neighbors).call()
        return data

    def count_pokeballs(self):
        pokeballs = [0] * 4
        inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']
        for inventory_item in inventory_items:
            if 'inventory_item_data' in inventory_item:
                item = inventory_item['inventory_item_data']
                if 'item' in item:
                    item_id = item['item']['item_id']
                    if item_id < 5:
                        pokeballs[item_id-1] += item['item'].get('count') or 1

        return pokeballs

    def attempt_catch(self,encounter_id, spawn_point_guid, encounter):
        inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']
        pokeballs = self.count_pokeballs()
        probabilities = encounter['capture_probability']['capture_probability']
        # Throw up to 5 pokeballs at any given pokemon, then give up (our % is pretty damn high)
        for i in range(1,5):
            sleep(2)
            ball_type = 1;
            while (
                (pokeballs[ball_type - 1] == 0) or
                ((ball_type <= 4) and pokeballs[ball_type] and (probabilities[ball_type - 1] < self.min_probability_throw))
            ):
                ball_type += 1

            if pokeballs[ball_type - 1] == 0:
                self.log.info(colored("No pokeballs to throw, abandoning catch attempt!", "red"))
                return {'status': 2}

            pokeballs[ball_type - 1] -= 1
            self.log.info('Throwing %s', ['PokeBall', 'GreatBall', 'UltraBall', 'MasterBall'][ball_type - 1])
            r = self.catch_pokemon(
                normalized_reticle_size= 1.950,
                pokeball = ball_type,
                spin_modifier= 0.850,
                hit_pokemon=True,
                normalized_hit_position=1,
                encounter_id=encounter_id,
                spawn_point_guid=spawn_point_guid,
            ).call()['responses']

            if 'CATCH_POKEMON' in r:
                r = r['CATCH_POKEMON']
            else:
                self.log.info(colored("Error catching pokemon: %s", "red"), r)
                sleep(3)
                continue

            if "status" in r:
                return r

        # failed to catch
        return {'status': 3}

    def cleanup_inventory(self, inventory_items=None):
        if not inventory_items:
            inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta']['inventory_items']
        caught_pokemon = defaultdict(list)
        for inventory_item in inventory_items:
            if "pokemon_data" in inventory_item['inventory_item_data']:
                # is a pokemon:
                pokemon = inventory_item['inventory_item_data']['pokemon_data']
                if 'cp' in pokemon and "favorite" not in pokemon:
                    caught_pokemon[pokemon["pokemon_id"]].append(pokemon)
            elif "item" in inventory_item['inventory_item_data']:
                item = inventory_item['inventory_item_data']['item']
                if item['item_id'] in MIN_BAD_ITEM_COUNTS and "count" in item and item['count'] > MIN_BAD_ITEM_COUNTS[item['item_id']]:
                    recycle_count = item['count'] - MIN_BAD_ITEM_COUNTS[item['item_id']]
                    self.log.info("Recycling Item_ID {0}, item count {1}".format(item['item_id'], recycle_count))
                    self.recycle_inventory_item(item_id=item['item_id'], count=recycle_count)

        # evolve the strongest pokemon if possible
        for pokemons in caught_pokemon.values():
            if len(pokemons) > MIN_SIMILAR_POKEMON:
                pokemons = sorted(
                    pokemons,
                    lambda x,y: cmp(pokemonIVPercentage(x), pokemonIVPercentage(y)) or cmp(x['cp'], y['cp']),
                    reverse=True,
                )
                for pokemon in pokemons[:len(pokemons) - MIN_SIMILAR_POKEMON]:
                    poke_info = self.pokemon_data[str(pokemon['pokemon_id'])]
                    for inventory_item in inventory_items:
                        if ("pokemon_family" in inventory_item['inventory_item_data'] and
                          "evolves_to" in poke_info and
                          inventory_item['inventory_item_data']['pokemon_family']['family_id'] == poke_info['candy_id'] and
                          inventory_item['inventory_item_data']['pokemon_family']['candy'] > ((poke_info.get('max_evolve_candies') or poke_info.get('candies') or 0) + self.config.get("KEEP_CANDIES", 0))
                        ):
                          self.log.info(colored("Evolving pokemon:", "cyan") + " %s", self.pokemon_data[str(pokemon['pokemon_id'])]['name'])
                          self.evolve_pokemon(pokemon_id = pokemon['id'])
                          caught_pokemon[pokemon["pokemon_id"]].remove(pokemon)

        # sort and release all weaker pokemon
        if self.RELEASE_DUPLICATES:
            for pokemons in caught_pokemon.values():
                if len(pokemons) > MIN_SIMILAR_POKEMON:
                    pokemons = sorted(
                        pokemons,
                        lambda x,y: cmp(pokemonIVPercentage(x), pokemonIVPercentage(y)) or cmp(x['cp'], y['cp']),
                        reverse=True,
                    )
                    for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                        if pokemon['cp'] < self.MIN_KEEP_CP:
                            self.log.debug("Releasing pokemon: %s", pokemon)
                            self.log.info(colored("Releasing pokemon:", "cyan") + " %s CP: %s, IV: %s", self.pokemon_data[str(pokemon['pokemon_id'])]['name'], str(pokemon['cp']), pokemonIVPercentage(pokemon))
                            self.release_pokemon(pokemon_id = pokemon["id"])

        return self.call()

    def disk_encounter_pokemon(self, lureinfo):
        if lureinfo['active_pokemon_id'] in IGNORE_POKEMON:
            return False
        try:
             encounter_id = lureinfo['encounter_id']
             fort_id = lureinfo['fort_id']
             position = self._posf
             resp = self.disk_encounter(encounter_id=encounter_id, fort_id=fort_id, player_latitude=position[0], player_longitude=position[1]).call()['responses']['DISK_ENCOUNTER']
             if resp['result'] == 1:
                 capture_status = -1
                 while capture_status != 0 and capture_status != 3:
                     catch_attempt = self.attempt_catch(encounter_id, fort_id, resp)
                     capture_status = catch_attempt['status']
                     if capture_status == 1:
                         pokemon = resp['pokemon_data']
                         self.log.debug("Caught Pokemon: : %s", catch_attempt)
                         self.log.info(resp)
                         self.log.info(colored("Caught pokemon:", "green") + " %s CP: %s, IV: %s", self.pokemon_data[str(pokemon['pokemon_id'])]['name'], str(pokemon['cp']), pokemonIVPercentage(pokemon))
                         sleep(2) # If you want to make it faster, delete this line... would not recommend though
                         return catch_attempt
                     elif capture_status != 2:
                         self.log.debug("Failed Catch: : %s", catch_attempt)
                         self.log.info(colored("Failed to catch Pokemon:", "red") + " %s", self.pokemon_data[str(resp['pokemon_data']['pokemon_id'])]['name'])
                         return False
                     sleep(2) # If you want to make it faster, delete this line... would not recommend though
        except Exception as e:
            self.log.error("Error in disk encounter %s", e)
            return False


    def encounter_pokemon(self,pokemon):
        encounter_id = pokemon['encounter_id']
        spawn_point_id = pokemon['spawn_point_id']
        position = self._posf
        encounter = self.encounter(encounter_id=encounter_id,spawn_point_id=spawn_point_id,player_latitude=position[0],player_longitude=position[1]).call()['responses']['ENCOUNTER']
        pokemon = encounter['wild_pokemon']['pokemon_data']
        if encounter['status'] == 1:
            capture_status = -1
            while capture_status != 0 and capture_status != 3:
                catch_attempt = self.attempt_catch(encounter_id, spawn_point_id, encounter)

                capture_status = catch_attempt['status']
                if capture_status == 1:
                    self.log.debug("Caught Pokemon: : %s", catch_attempt)
                    self.log.info(colored("Caught pokemon:", "green") + " %s CP: %s, IV: %s", self.pokemon_data[str(pokemon['pokemon_id'])]['name'], str(pokemon['cp']), pokemonIVPercentage(pokemon))
                    sleep(2) # If you want to make it faster, delete this line... would not recommend though
                    return catch_attempt
                elif capture_status != 2:
                    self.log.debug("Failed Catch: : %s", catch_attempt)
                    self.log.info(colored("Failed to Catch Pokemon:", "red") + " %s", self.pokemon_data[str(pokemon['pokemon_id'])]['name'])
                return False
                sleep(2) # If you want to make it faster, delete this line... would not recommend though
        return False

    def login(self, provider, username, password, cached=False):
        if not isinstance(username, basestring) or not isinstance(password, basestring):
            raise AuthException("Username/password not correctly specified")

        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        else:
            raise AuthException("Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: %s', provider)

        if not self._auth_provider.login(username, password):
            self.log.info('Login process failed')
            return False

        self.log.info('Starting RPC login sequence (app simulation)')
        self.get_player()
        self.get_hatched_eggs()
        self.get_inventory()
        self.check_awarded_badges()
        self.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")

        response = self.call()

        if not response:
            self.log.info('Login failed!')

        if os.path.isfile("auth_cache") and cached:
            response = pickle.load(open("auth_cache"))

        fname = "auth_cache_%s" % username

        if os.path.isfile(fname) and cached:
            response = pickle.load(open(fname))
        else:
            response = self.heartbeat()
            f = open(fname, "w")
            pickle.dump(response, f)

        if not response:
            self.log.info('Login failed!')
            return False

        if 'api_url' in response:
            self._api_endpoint = ('https://{}/rpc'.format(response['api_url']))
            self.log.debug('Setting API endpoint to: %s', self._api_endpoint)
        else:
            self.log.error('Login failed - unexpected server response!')
            return False

        if 'auth_ticket' in response:
            self._auth_provider.set_ticket(response['auth_ticket'].values())

        if cached and os.path.isfile("accounts/%s.json" % username):
            with open("accounts/%s.json" % username, 'r') as file_contents:
                jsonstr = file_contents.read()
            account_json = json.loads(jsonstr)
            self.log.info('restoring location to %f, %f', account_json['lat'], account_json['lng'])
            self.set_position(account_json['lat'], account_json['lng'], account_json['alt'])

        self.log.info('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')

        return True

    def main_loop(self):
        self.heartbeat()
        while True:
            self.heartbeat()
            sleep(1) # If you want to make it faster, delete this line... would not recommend though
            self.spin_near_fort()
            while self.catch_near_pokemon():
                sleep(4) # If you want to make it faster, delete this line... would not recommend though
                pass

    @staticmethod
    def flatmap(f, items):
        return chain.from_iterable(imap(f, items))
Example #8
0
class PGoApi:
    API_ENTRY = 'https://pgorelease.nianticlabs.com/plfe/rpc'

    def __init__(self, config, pokemon_names):

        self.log = logging.getLogger(__name__)

        self._auth_provider = None
        self._api_endpoint = None
        self.config = config
        self._position_lat = 0  # int cooords
        self._position_lng = 0
        self._position_alt = 0
        self._posf = (0, 0, 0)  # this is floats
        self._origPosF = (0, 0, 0)  # this is original position in floats
        self._req_method_list = []
        self._heartbeat_number = 5
        self._firstRun = True

        self.pokemon_caught = 0
        self.inventory = Player_Inventory([])

        self.pokemon_names = pokemon_names

        self.MIN_ITEMS = {}
        for k, v in config.get("MIN_ITEMS", {}).items():
            self.MIN_ITEMS[getattr(Inventory, k)] = v

        self.POKEMON_EVOLUTION = {}
        self.POKEMON_EVOLUTION_FAMILY = {}
        for k, v in config.get("POKEMON_EVOLUTION", {}).items():
            self.POKEMON_EVOLUTION[getattr(Enums_pb2, k)] = v
            self.POKEMON_EVOLUTION_FAMILY[getattr(Enums_pb2, k)] = getattr(Enums_pb2, "FAMILY_" + k)

        self.MIN_KEEP_IV = config.get("MIN_KEEP_IV", 0)  # release anything under this if we don't have it already
        self.KEEP_CP_OVER = config.get("KEEP_CP_OVER", 0)  # release anything under this if we don't have it already
        self.MIN_SIMILAR_POKEMON = config.get("MIN_SIMILAR_POKEMON", 1)  # Keep atleast one of everything.
        self.STAY_WITHIN_PROXIMITY = config.get("STAY_WITHIN_PROXIMITY", False)  # Stay within proximity

        self.visited_forts = ExpiringDict(max_len=120, max_age_seconds=config.get("SKIP_VISITED_FORT_DURATION", 600))
        self.experimental = config.get("EXPERIMENTAL", False)
        self.spin_all_forts = config.get("SPIN_ALL_FORTS", False)
        self.keep_pokemon_ids = map(lambda x: getattr(Enums_pb2, x), config.get("KEEP_POKEMON_NAMES", []))
        self.throw_pokemon_ids = map(lambda x: getattr(Enums_pb2, x), config.get("THROW_POKEMON_NAMES", []))
        self.max_catch_attempts = config.get("MAX_CATCH_ATTEMPTS", 10)
        self.game_master = parse_game_master()

    def call(self):
        if not self._req_method_list:
            return False

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

        player_position = self.get_position()

        request = RpcApi(self._auth_provider)

        if self._api_endpoint:
            api_endpoint = self._api_endpoint
        else:
            api_endpoint = self.API_ENTRY

        self.log.debug('Execution of RPC')
        response = None
        try:
            response = request.request(api_endpoint, self._req_method_list, player_position)
        except ServerBusyOrOfflineException as e:
            self.log.info('Server seems to be busy or offline - try again!')

        # cleanup after call execution
        self.log.debug('Cleanup of request!')
        self._req_method_list = []

        return response

    def list_curr_methods(self):
        for i in self._req_method_list:
            print("{} ({})".format(RequestType.Name(i), i))

    def set_logger(self, logger):
        self._ = logger or logging.getLogger(__name__)

    def get_position(self):
        return (self._position_lat, self._position_lng, self._position_alt)

    def set_position(self, lat, lng, alt):
        self.log.debug('Set Position - Lat: %s Long: %s Alt: %s', lat, lng, alt)
        self._posf = (lat, lng, alt)
        if self._firstRun:
            self._firstRun = False
            self._origPosF = self._posf
        self._position_lat = f2i(lat)
        self._position_lng = f2i(lng)
        self._position_alt = f2i(alt)

    def __getattr__(self, func):
        def function(**kwargs):

            if not self._req_method_list:
                self.log.debug('Create new request...')

            name = func.upper()
            if kwargs:
                self._req_method_list.append({RequestType.Value(name): kwargs})
                self.log.debug("Adding '%s' to RPC request including arguments", name)
                self.log.debug("Arguments of '%s': \n\r%s", name, kwargs)
            else:
                self._req_method_list.append(RequestType.Value(name))
                self.log.debug("Adding '%s' to RPC request", name)

            return self

        if func.upper() in RequestType.keys():
            return function
        else:
            raise AttributeError

    def update_player_inventory(self):
        self.get_inventory()
        res = self.call()
        if 'GET_INVENTORY' in res['responses']:
            self.inventory = Player_Inventory(res['responses']['GET_INVENTORY']['inventory_delta']['inventory_items'])
        self.log.info("Player Items: %s", self.inventory)

    def heartbeat(self):
        # making a standard call to update position, etc
        self.get_player()
        if self._heartbeat_number % 10 == 0:
            self.check_awarded_badges()
            self.get_inventory()
        # self.download_settings(hash="4a2e9bc330dae60e7b74fc85b98868ab4700802e")
        res = self.call()
        if not res or res.get("direction", -1) == 102:
            self.log.error("There were a problem responses for api call: %s. Restarting!!!", res)
            raise AuthException("Token probably expired?")
        self.log.debug('Heartbeat dictionary: \n\r{}'.format(json.dumps(res, indent=2)))

        if 'GET_PLAYER' in res['responses']:
            player_data = res['responses'].get('GET_PLAYER', {}).get('player_data', {})
            currencies = player_data.get('currencies', [])
            currency_data = ",".join(
                map(lambda x: "{0}: {1}".format(x.get('name', 'NA'), x.get('amount', 'NA')), currencies))
            self.log.info("Username: %s, Currencies: %s, Pokemon Caught in this run: %s",
                          player_data.get('username', 'NA'), currency_data, self.pokemon_caught)

        if 'GET_INVENTORY' in res['responses']:
            with open("data_dumps/%s.json" % self.config['username'], "w") as f:
                res['responses']['lat'] = self._posf[0]
                res['responses']['lng'] = self._posf[1]
                f.write(json.dumps(res['responses'], indent=2))
            self.log.info(get_inventory_data(res, self.pokemon_names))
            self.log.info("Player Items: %s", self.inventory)
            self.inventory = Player_Inventory(res['responses']['GET_INVENTORY']['inventory_delta']['inventory_items'])
            self.log.debug(self.cleanup_inventory(self.inventory.inventory_items))
            self.attempt_evolve(self.inventory.inventory_items)
            self.cleanup_pokemon(self.inventory.inventory_items)

        self._heartbeat_number += 1
        return res

    def walk_to(self, loc, waypoints=[]):  # location in floats of course...
        steps = get_route(self._posf, loc, self.config.get("USE_GOOGLE", False), self.config.get("GMAPS_API_KEY", ""),
                          self.experimental and self.spin_all_forts, waypoints)
        catch_attempt = 0
        for step in steps:
            for i, next_point in enumerate(get_increments(self._posf, step, self.config.get("STEP_SIZE", 200))):
                self.set_position(*next_point)
                self.heartbeat()
                if self.experimental and self.spin_all_forts:
                    self.spin_nearest_fort()
                self.log.info("On my way to the next fort! :)")
                sleep(1)
                while self.catch_near_pokemon() and catch_attempt <= self.max_catch_attempts:
                    sleep(1)
                    catch_attempt += 1
                catch_attempt = 0

    def walk_back_to_origin(self):
        self.walk_to(self._origPosF)

    def spin_nearest_fort(self):
        map_cells = self.nearby_map_objects()['responses'].get('GET_MAP_OBJECTS', {}).get('map_cells', [])
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        destinations = filtered_forts(self._origPosF, self._posf, forts, self.STAY_WITHIN_PROXIMITY, self.visited_forts)
        if destinations:
            nearest_fort = destinations[0][0]
            nearest_fort_dis = destinations[0][1]
            if nearest_fort_dis <= 40.00:
                self.fort_search_pgoapi(nearest_fort, player_postion=self.get_position(),
                                        fort_distance=nearest_fort_dis)
                if 'lure_info' in nearest_fort:
                    self.disk_encounter_pokemon(nearest_fort['lure_info'])
        else:
            self.log.info('No spinnable forts within proximity. Or server returned no map objects.')

    def fort_search_pgoapi(self, fort, player_postion, fort_distance):
        res = self.fort_search(fort_id=fort['id'], fort_latitude=fort['latitude'],
                               fort_longitude=fort['longitude'],
                               player_latitude=player_postion[0],
                               player_longitude=player_postion[1]).call()['responses']['FORT_SEARCH']
        if res['result'] == 1:
            self.log.debug("Fort spinned: %s", res)
            self.log.info("Fort Spinned: http://maps.google.com/maps?q=%s,%s", fort['latitude'], fort['longitude'])
            self.visited_forts[fort['id']] = fort
        elif res['result'] == 4:
            self.log.debug("For spinned but Your inventory is full : %s", res)
            self.log.info("For spinned but Your inventory is full.")
            self.visited_forts[fort['id']] = fort
        elif res['result'] == 2:
            self.log.debug("Could not spin fort -  fort not in range %s", res)
            self.log.info("Could not spin fort http://maps.google.com/maps?q=%s,%s, Not in Range %s", fort['latitude'],
                          fort['longitude'], fort_distance)
        else:
            self.log.debug("Could not spin fort %s", res)
            self.log.info("Could not spin fort http://maps.google.com/maps?q=%s,%s, Error id: %s", fort['latitude'],
                          fort['longitude'], res['result'])
            return False
        return True

    def spin_all_forts_visible(self):
        res = self.nearby_map_objects()
        map_cells = res['responses'].get('GET_MAP_OBJECTS', {}).get('map_cells', [])
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        destinations = filtered_forts(self._origPosF, self._posf, forts, self.STAY_WITHIN_PROXIMITY, self.visited_forts,
                                      self.experimental)
        if not destinations:
            self.log.debug("No fort to walk to! %s", res)
            self.log.info('No more spinnable forts within proximity. Or server error')
            self.walk_back_to_origin()
            return False
        if len(destinations) >= 20:
            destinations = destinations[:20]
        furthest_fort = destinations[0][0]
        self.log.info("Walking to fort at  http://maps.google.com/maps?q=%s,%s", furthest_fort['latitude'],
                      furthest_fort['longitude'])
        self.walk_to((furthest_fort['latitude'], furthest_fort['longitude']),
                     map(lambda x: "via:%f,%f" % (x[0]['latitude'], x[0]['longitude']), destinations[1:]))
        return True

    def return_to_start(self):
        self.set_position(*self._origPosF)

    def spin_near_fort(self):
        res = self.nearby_map_objects()
        map_cells = res['responses'].get('GET_MAP_OBJECTS', {}).get('map_cells', [])
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        destinations = filtered_forts(self._origPosF, self._posf, forts, self.STAY_WITHIN_PROXIMITY, self.visited_forts,
                                      self.experimental)
        if not destinations:
            self.log.debug("No fort to walk to! %s", res)
            self.log.info('No more spinnable forts within proximity. Returning back to origin')
            self.walk_back_to_origin()
            return False
        for fort_data in destinations:
            fort = fort_data[0]
            self.log.info("Walking to fort at  http://maps.google.com/maps?q=%s,%s", fort['latitude'],
                          fort['longitude'])
            self.walk_to((fort['latitude'], fort['longitude']))
            self.fort_search_pgoapi(fort, self.get_position(), fort_data[1])
            if 'lure_info' in fort:
                self.disk_encounter_pokemon(fort['lure_info'])
        return True

    def catch_near_pokemon(self):
        map_cells = self.nearby_map_objects()['responses']['GET_MAP_OBJECTS']['map_cells']
        pokemons = PGoApi.flatmap(lambda c: c.get('catchable_pokemons', []), map_cells)

        # catch first pokemon:
        origin = (self._posf[0], self._posf[1])
        pokemon_distances = [(pokemon, distance_in_meters(origin, (pokemon['latitude'], pokemon['longitude']))) for
                             pokemon
                             in pokemons]
        if pokemons:
            self.log.debug("Nearby pokemon: : %s", pokemon_distances)
            self.log.info("Nearby Pokemon: %s",
                          ", ".join(map(lambda x: self.pokemon_names[str(x['pokemon_id'])], pokemons)))
        else:
            self.log.info("No nearby pokemon")
        catches_successful = False
        for pokemon_distance in pokemon_distances:
            target = pokemon_distance
            self.log.debug("Catching pokemon: : %s, distance: %f meters", target[0], target[1])
            self.log.info("Catching Pokemon: %s", self.pokemon_names[str(target[0]['pokemon_id'])])
            catches_successful &= self.encounter_pokemon(target[0])
            sleep(random.randrange(4, 8))
        return catches_successful

    def nearby_map_objects(self):
        position = self.get_position()
        neighbors = getNeighbors(self._posf)
        return self.get_map_objects(latitude=position[0], longitude=position[1],
                                    since_timestamp_ms=[0] * len(neighbors),
                                    cell_id=neighbors).call()

    def attempt_catch(self, encounter_id, spawn_point_guid):
        catch_status = -1
        catch_attempts = 0
        ret = {}
        # Max 4 attempts to catch pokemon
        while catch_status != 1 and self.inventory.can_attempt_catch() and catch_attempts < 5:
            pokeball = self.inventory.take_next_ball()
            r = self.catch_pokemon(
                normalized_reticle_size=1.950,
                pokeball=pokeball,
                spin_modifier=0.850,
                hit_pokemon=True,
                normalized_hit_position=1,
                encounter_id=encounter_id,
                spawn_point_guid=spawn_point_guid,
            ).call()['responses']['CATCH_POKEMON']
            catch_attempts += 1
            if "status" in r:
                catch_status = r['status']
                # fleed or error
                if catch_status == 3 or catch_status == 0:
                    break
            ret = r
        if 'status' in ret:
            return ret
        return {}

    def cleanup_inventory(self, inventory_items=None):
        if not inventory_items:
            inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta'][
                'inventory_items']
        for inventory_item in inventory_items:
            if "item" in inventory_item['inventory_item_data']:
                item = inventory_item['inventory_item_data']['item']
                if item['item_id'] in self.MIN_ITEMS and "count" in item and item['count'] > self.MIN_ITEMS[
                    item['item_id']]:
                    recycle_count = item['count'] - self.MIN_ITEMS[item['item_id']]
                    self.log.info("Recycling Item_ID {0}, item count {1}".format(item['item_id'], recycle_count))
                    res = self.recycle_inventory_item(item_id=item['item_id'], count=recycle_count).call()['responses'][
                        'RECYCLE_INVENTORY_ITEM']
                    response_code = res['result']
                    if response_code == 1:
                        self.log.info("Recycled Item %s, New Count: %s", item['item_id'], res.get('new_count', 0))
                    else:
                        self.log.info("Failed to recycle Item %s, Code: %s", item['item_id'], response_code)
                    sleep(2)
        return self.update_player_inventory()

    def get_caught_pokemons(self, inventory_items):
        caught_pokemon = defaultdict(list)
        for inventory_item in inventory_items:
            if "pokemon_data" in inventory_item['inventory_item_data']:
                # is a pokemon:
                pokemon = Pokemon(inventory_item['inventory_item_data']['pokemon_data'], self.pokemon_names)
                pokemon.pokemon_additional_data = self.game_master.get(pokemon.pokemon_id, PokemonData())
                if not pokemon.is_egg:
                    caught_pokemon[pokemon.pokemon_id].append(pokemon)
        return caught_pokemon

    def cleanup_pokemon(self, inventory_items=None):
        if not inventory_items:
                inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta'][
                    'inventory_items']
        caught_pokemon = self.get_caught_pokemons(inventory_items)

        for pokemons in caught_pokemon.values():
            # Only if we have more than MIN_SIMILAR_POKEMON
            if len(pokemons) > self.MIN_SIMILAR_POKEMON:
                pokemons = sorted(pokemons, key=lambda x: (x.cp, x.iv), reverse=True)
                # keep the first pokemon....
                for pokemon in pokemons[self.MIN_SIMILAR_POKEMON:]:
                    if self.is_pokemon_eligible_for_transfer(pokemon):
                        self.log.info("Releasing pokemon: %s", pokemon)
                        self.release_pokemon(pokemon_id=pokemon.id)
                        release_res = self.call()['responses']['RELEASE_POKEMON']
                        status = release_res.get('result', -1)
                        if status == 1:
                            self.log.info("Successfully Released Pokemon %s", pokemon)
                        else:
                            self.log.debug("Failed to release pokemon %s, %s", pokemon, release_res)
                            self.log.info("Failed to release Pokemon %s", pokemon)
                        sleep(3)

    def is_pokemon_eligible_for_transfer(self, pokemon):
        return (pokemon.pokemon_id in self.throw_pokemon_ids and not pokemon.is_favorite) \
               or (not pokemon.is_favorite and
                   pokemon.iv < self.MIN_KEEP_IV and
                   pokemon.cp < self.KEEP_CP_OVER and
                   pokemon.is_valid_pokemon() and
                   pokemon.pokemon_id not in self.keep_pokemon_ids)

    def attempt_evolve(self, inventory_items=None):
        if not inventory_items:
            inventory_items = self.get_inventory().call()['responses']['GET_INVENTORY']['inventory_delta'][
                'inventory_items']
        caught_pokemon = self.get_caught_pokemons(inventory_items)
        self.inventory = Player_Inventory(inventory_items)
        for pokemons in caught_pokemon.values():
            if len(pokemons) > self.MIN_SIMILAR_POKEMON:
                pokemons = sorted(pokemons, key=lambda x: (x.cp, x.iv), reverse=True)
                for pokemon in pokemons[self.MIN_SIMILAR_POKEMON:]:
                    # If we can't evolve this type of pokemon anymore, don't check others.
                    if not self.attempt_evolve_pokemon(pokemon):
                        break

        return False

    def attempt_evolve_pokemon(self, pokemon):
        if self.is_pokemon_eligible_for_evolution(pokemon=pokemon):
            self.log.info("Evolving pokemon: %s", pokemon)
            evo_res = self.evolve_pokemon(pokemon_id=pokemon.id).call()['responses']['EVOLVE_POKEMON']
            status = evo_res.get('result', -1)
            sleep(3)
            if status == 1:
                evolved_pokemon = Pokemon(evo_res.get('evolved_pokemon_data', {}), self.pokemon_names,
                                          self.game_master.get(str(pokemon.pokemon_id), PokemonData()))
                # I don' think we need additional stats for evolved pokemon. Since we do not do anything with it.
                # evolved_pokemon.pokemon_additional_data = self.game_master.get(pokemon.pokemon_id, PokemonData())
                self.log.info("Evolved to %s", evolved_pokemon)
                self.update_player_inventory()
                return True
            else:
                self.log.debug("Could not evolve Pokemon %s", evo_res)
                self.log.info("Could not evolve pokemon %s | Status %s", pokemon, status)
                self.update_player_inventory()
                return False
        else:
            return False

    def is_pokemon_eligible_for_evolution(self, pokemon):
        return self.inventory.pokemon_candy.get(self.POKEMON_EVOLUTION_FAMILY.get(pokemon.pokemon_id, None),
                                                -1) > self.POKEMON_EVOLUTION.get(pokemon.pokemon_id, None) \
               and pokemon.pokemon_id not in self.keep_pokemon_ids \
               and not pokemon.is_favorite \
               and pokemon.pokemon_id in self.POKEMON_EVOLUTION

    def disk_encounter_pokemon(self, lureinfo, retry=False):
        try:
            self.update_player_inventory()
            if not self.inventory.can_attempt_catch():
                self.log.info("No balls to catch %s, exiting disk encounter", self.inventory)
                return False
            encounter_id = lureinfo['encounter_id']
            fort_id = lureinfo['fort_id']
            position = self._posf
            self.log.debug("At Fort with lure %s".encode('ascii', 'ignore'), lureinfo)
            self.log.info("At Fort with Lure AND Active Pokemon %s",
                          self.pokemon_names.get(str(lureinfo.get('active_pokemon_id', 0)), "NA"))
            resp = self.disk_encounter(encounter_id=encounter_id, fort_id=fort_id, player_latitude=position[0],
                                       player_longitude=position[1]).call()['responses']['DISK_ENCOUNTER']
            if resp['result'] == 1:
                capture_status = -1
                while capture_status != 0 and capture_status != 3:
                    catch_attempt = self.attempt_catch(encounter_id, fort_id)
                    capture_status = catch_attempt['status']
                    if capture_status == 1:
                        self.log.debug("(LURE) Caught Pokemon: : %s", catch_attempt)
                        self.log.info("(LURE) Caught Pokemon:  %s",
                                      self.pokemon_names.get(str(lureinfo.get('active_pokemon_id', 0)), "NA"))
                        self.pokemon_caught += 1
                        sleep(2)
                        return True
                    elif capture_status != 2:
                        self.log.debug("(LURE) Failed Catch: : %s", catch_attempt)
                        self.log.info("(LURE) Failed to catch Pokemon:  %s",
                                      self.pokemon_names.get(str(lureinfo.get('active_pokemon_id', 0)), "NA"))
                        return False
                    sleep(2)
            elif resp['result'] == 5:
                self.log.info("Couldn't catch %s Your pokemon bag was full, attempting to clear and re-try",
                              self.pokemon_names.get(str(lureinfo.get('active_pokemon_id', 0)), "NA"))
                self.cleanup_pokemon()
                if not retry:
                    return self.disk_encounter_pokemon(lureinfo, retry=True)
            else:
                self.log.info("Could not start Disk (lure) encounter for pokemon: %s",
                              self.pokemon_names.get(str(lureinfo.get('active_pokemon_id', 0)), "NA"))
        except Exception as e:
            self.log.error("Error in disk encounter %s", e)
            return False

    def encounter_pokemon(self, pokemon, retry=False):  # take in a MapPokemon from MapCell.catchable_pokemons
        # Update Inventory to make sure we can catch this mon
        self.update_player_inventory()
        if not self.inventory.can_attempt_catch():
            self.log.info("No balls to catch %s, exiting encounter", self.inventory)
            return False
        encounter_id = pokemon['encounter_id']
        spawn_point_id = pokemon['spawn_point_id']
        # begin encounter_id
        position = self.get_position()
        encounter = self.encounter(encounter_id=encounter_id,
                                   spawn_point_id=spawn_point_id,
                                   player_latitude=position[0],
                                   player_longitude=position[1]).call()['responses']['ENCOUNTER']
        self.log.debug("Attempting to Start Encounter: %s", encounter)
        if encounter['status'] == 1:
            capture_status = -1
            # while capture_status != RpcEnum.CATCH_ERROR and capture_status != RpcEnum.CATCH_FLEE:
            while capture_status != 0 and capture_status != 3:
                catch_attempt = self.attempt_catch(encounter_id, spawn_point_id)
                capture_status = catch_attempt.get('status', -1)
                # if status == RpcEnum.CATCH_SUCCESS:
                if capture_status == 1:
                    self.log.debug("Caught Pokemon: : %s", catch_attempt)
                    self.log.info("Caught Pokemon:  %s", self.pokemon_names.get(str(pokemon['pokemon_id']), "NA"))
                    self.pokemon_caught += 1
                    sleep(2)
                    return True
                elif capture_status != 2:
                    self.log.debug("Failed Catch: : %s", catch_attempt)
                    self.log.info("Failed to Catch Pokemon:  %s",
                                  self.pokemon_names.get(str(pokemon['pokemon_id']), "NA"))
                    return False
                sleep(2)
        elif encounter['status'] == 7:
            self.log.info("Couldn't catch %s Your pokemon bag was full, attempting to clear and re-try",
                          self.pokemon_names.get(str(pokemon['pokemon_id']), "NA"))
            self.cleanup_pokemon()
            if not retry:
                return self.encounter_pokemon(pokemon, retry=True)
        else:
            self.log.info("Could not start encounter for pokemon: %s",
                          self.pokemon_names.get(str(pokemon['pokemon_id']), "NA"))
        return False

    def login(self, provider, username, password, cached=False):
        if not isinstance(username, basestring) or not isinstance(password, basestring):
            raise AuthException("Username/password not correctly specified")

        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        else:
            raise AuthException("Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: %s', provider)

        if not self._auth_provider.login(username, password):
            self.log.info('Login process failed')
            return False

        self.log.info('Starting RPC login sequence (app simulation)')
        # making a standard call, like it is also done by the client
        self.get_player()
        self.get_hatched_eggs()
        self.get_inventory()
        self.check_awarded_badges()
        self.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")

        response = self.call()

        if not response:
            self.log.info('Login failed!')
        if os.path.isfile("auth_cache") and cached:
            response = pickle.load(open("auth_cache"))
        fname = "auth_cache_%s" % username
        if os.path.isfile(fname) and cached:
            response = pickle.load(open(fname))
        else:
            response = self.heartbeat()
            f = open(fname, "w")
            pickle.dump(response, f)
        if not response:
            self.log.info('Login failed!')
            return False

        if 'api_url' in response:
            self._api_endpoint = ('https://{}/rpc'.format(response['api_url']))
            self.log.debug('Setting API endpoint to: %s', self._api_endpoint)
        else:
            self.log.error('Login failed - unexpected server response!')
            return False

        if 'auth_ticket' in response:
            self._auth_provider.set_ticket(response['auth_ticket'].values())

        self.log.info('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')

        return True

    def main_loop(self):
        catch_attempt = 0
        self.heartbeat()
        self.cleanup_inventory()
        while True:
            self.heartbeat()
            sleep(1)
            if self.experimental and self.spin_all_forts:
                self.spin_all_forts_visible()
            else:
                self.spin_near_fort()
            # if catching fails 10 times, maybe you are sofbanned.
            while self.catch_near_pokemon() and catch_attempt <= self.max_catch_attempts:
                sleep(4)
                catch_attempt += 1
                pass
            if catch_attempt > self.max_catch_attempts:
                self.log.warn("Your account may be softbaned Or no Pokeballs. Failed to catch pokemon %s times",
                              catch_attempt)
            catch_attempt = 0

    @staticmethod
    def flatmap(f, items):
        return list(chain.from_iterable(imap(f, items)))
Example #9
0
class PGoApi:

    API_ENTRY = 'https://pgorelease.nianticlabs.com/plfe/rpc'

    def __init__(self, config, pokemon_names, start_pos):

        self.log = logging.getLogger(__name__)
        self._start_pos = start_pos
        self._walk_count = 1
        self._auth_provider = None
        self._api_endpoint = None
        self.config = config
        self.set_position(*start_pos)
        self._pokeball_type = 1
        self.MIN_KEEP_IV = config.get("MIN_KEEP_IV", 0)
        self.KEEP_CP_OVER = config.get("KEEP_CP_OVER", 0)
        self.RELEASE_DUPLICATES = config.get("RELEASE_DUPLICATE", 0)
        self.DUPLICATE_CP_FORGIVENESS = config.get("DUPLICATE_CP_FORGIVENESS",
                                                   0)
        self.MAX_BALL_TYPE = config.get("MAX_BALL_TYPE", 0)
        self.RANDOM_SLEEP_TIME = config.get("RANDOM_SLEEP_TIME", 0)
        self._req_method_list = []
        self._heartbeat_number = 0
        self.pokemon_names = pokemon_names
        self.pokeballs = [
            0, 0, 0, 0
        ]  # pokeball counts. set to 0 to force atleast one fort check  before trying to capture pokemon
        self.map_cells = dict()
        self.min_item_counts = dict(
            ((getattr(Inventory, key), value)
             for key, value in config.get('MIN_ITEM_COUNTS', {}).iteritems()))

    def call(self):
        if not self._req_method_list:
            return False

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

        player_position = self.get_position()

        request = RpcApi(self._auth_provider)

        if self._api_endpoint:
            api_endpoint = self._api_endpoint
        else:
            api_endpoint = self.API_ENTRY

        self.log.debug('Execution of RPC')
        response = None
        try:
            response = request.request(api_endpoint, self._req_method_list,
                                       player_position)
        except ServerBusyOrOfflineException:
            self.log.info('Server seems to be busy or offline - try again!')

        # cleanup after call execution
        self.log.debug('Cleanup of request!')
        self._req_method_list = []

        return response

    def list_curr_methods(self):
        for i in self._req_method_list:
            print("{} ({})".format(RequestType.Name(i), i))

    def set_logger(self, logger):
        self._ = logger or logging.getLogger(__name__)

    def get_position(self):
        return (self._position_lat, self._position_lng, self._position_alt)

    def get_position_raw(self):
        return self._posf

    def set_position(self, lat, lng, alt):
        self.log.debug('Set Position - Lat: %s Long: %s Alt: %s', lat, lng,
                       alt)
        self._posf = (lat, lng, alt)
        self._position_lat = f2i(lat)
        self._position_lng = f2i(lng)
        self._position_alt = f2i(alt)

    def __getattr__(self, func):
        def function(**kwargs):

            if not self._req_method_list:
                self.log.debug('Create new request...')

            name = func.upper()
            if kwargs:
                self._req_method_list.append({RequestType.Value(name): kwargs})
                self.log.debug(
                    "Adding '%s' to RPC request including arguments", name)
                self.log.debug("Arguments of '%s': \n\r%s", name, kwargs)
            else:
                self._req_method_list.append(RequestType.Value(name))
                self.log.debug("Adding '%s' to RPC request", name)

            return self

        if func.upper() in RequestType.keys():
            return function
        else:
            raise AttributeError

    def heartbeat(self):
        self.get_player()
        if self._heartbeat_number % 10 == 0 or self._heartbeat_number == 0:  # every 10 heartbeats do a inventory check
            self.check_awarded_badges()
            self.get_inventory()
        res = self.call()
        if res.get("direction", -1) == 102:
            self.log.error(
                "There were a problem responses for api call: %s. Restarting!!!",
                res)
            raise AuthException("Token probably expired?")
        self.log.debug('Heartbeat dictionary: \n\r{}'.format(
            json.dumps(res, indent=2)))
        if 'GET_PLAYER' in res['responses']:
            player_data = res['responses'].get('GET_PLAYER',
                                               {}).get('player_data', {})
            if os.path.isfile("accounts/%s.json" % self.config['username']):
                with open("accounts/%s.json" % self.config['username'],
                          "r") as f:
                    file = f.read()
                    json_file = json.loads(file)
                inventory_items = json_file.get('GET_INVENTORY', {}).get(
                    'inventory_delta', {}).get('inventory_items', [])
                inventory_items_dict_list = map(
                    lambda x: x.get('inventory_item_data', {}),
                    inventory_items)
                player_stats = filter(lambda x: 'player_stats' in x,
                                      inventory_items_dict_list)[0].get(
                                          'player_stats', {})
            else:
                player_stats = {}
            currencies = player_data.get('currencies', [])
            currency_data = ",".join(
                map(
                    lambda x: "{0}: {1}".format(x.get('name', 'NA'),
                                                x.get('amount', 'NA')),
                    currencies))
        if 'GET_INVENTORY' in res['responses']:
            with open("accounts/%s.json" % self.config['username'], "w") as f:
                res['responses']['lat'] = self._posf[0]
                res['responses']['lng'] = self._posf[1]
                f.write(json.dumps(res['responses'], indent=2))
            self.log.info("\n List of Pokemon:\n" +
                          get_inventory_data(res, self.pokemon_names) +
                          "\nTotal Pokemon count: " +
                          str(get_pokemon_num(res)) +
                          "\nEgg Hatching status: " +
                          get_incubators_stat(res) + "\n")
            self.log.info(
                "\n Username: %s, Lvl: %s, XP: %s/%s \n Currencies: %s \n",
                player_data.get('username', 'NA'),
                player_stats.get('level', 'NA'),
                player_stats.get('experience', 'NA'),
                player_stats.get('next_level_xp', 'NA'), currency_data)
            self.log.debug(
                self.cleanup_inventory(res['responses']['GET_INVENTORY']
                                       ['inventory_delta']['inventory_items']))

        self._heartbeat_number += 1
        return res

    def walk_to(self, loc):
        self._walk_count += 1
        steps = get_route(self._posf, loc,
                          self.config.get("USE_GOOGLE", False),
                          self.config.get("GMAPS_API_KEY", ""))
        for step in steps:
            for i, next_point in enumerate(
                    get_increments(self._posf, step,
                                   self.config.get("STEP_SIZE", 200))):
                self.set_position(*next_point)
                self.heartbeat()
                self.log.info("Sleeping before next heartbeat")
                sleep(
                    self.RANDOM_SLEEP_TIME * random.random() + 2
                )  # If you want to make it faster, delete this line... would not recommend though
                # make sure we have atleast 1 ball
                if sum(self.pokeballs) > 0:
                    while self.catch_near_pokemon():
                        sleep(
                            self.RANDOM_SLEEP_TIME * random.random() + 1
                        )  # If you want to make it faster, delete this line... would not recommend though

    # this is in charge of spinning a pokestop
    def spin_near_fort(self):
        map_cells = self.nearby_map_objects().get('responses', {}).get(
            'GET_MAP_OBJECTS', {}).get('map_cells', {})
        forts = PGoApi.flatmap(lambda c: c.get('forts', []), map_cells)
        if self._start_pos and self._walk_count % self.config.get(
                "RETURN_START_INTERVAL") == 0:
            destinations = filtered_forts(self._start_pos, forts)
        else:
            destinations = filtered_forts(self._posf, forts)

        if destinations:
            destination_num = random.randint(0, min(5, len(destinations) - 1))
            fort = destinations[destination_num]
            self.log.info("Walking to fort at %s,%s", fort['latitude'],
                          fort['longitude'])
            self.walk_to((fort['latitude'], fort['longitude']))
            position = self._posf  # FIXME ?
            res = self.fort_search(fort_id=fort['id'],
                                   fort_latitude=fort['latitude'],
                                   fort_longitude=fort['longitude'],
                                   player_latitude=position[0],
                                   player_longitude=position[1]).call(
                                   )['responses']['FORT_SEARCH']
            self.log.debug("Fort spinned: %s", res)
            if 'lure_info' in fort:
                encounter_id = fort['lure_info']['encounter_id']
                fort_id = fort['lure_info']['fort_id']
                position = self._posf
                resp = self.disk_encounter(encounter_id=encounter_id,
                                           fort_id=fort_id,
                                           player_latitude=position[0],
                                           player_longitude=position[1]).call(
                                           )['responses']['DISK_ENCOUNTER']
                self.log.debug('Encounter response is: %s', resp)
                if self.pokeballs[1] > 9 and self.pokeballs[
                        2] > 4 and self.pokeballs[3] > 4:
                    self.disk_encounter_pokemon(fort['lure_info'])
            return True
        else:
            self.log.error("No fort to walk to!")
            return False

    # this will catch any nearby pokemon
    def catch_near_pokemon(self):
        map_cells = self.nearby_map_objects().get('responses', {}).get(
            'GET_MAP_OBJECTS', {}).get('map_cells', {})
        pokemons = PGoApi.flatmap(lambda c: c.get('catchable_pokemons', []),
                                  map_cells)

        # cache map cells for api
        self.map_cells = map_cells

        # catch first pokemon:
        origin = (self._posf[0], self._posf[1])
        pokemon_distances = [
            (pokemon,
             distance_in_meters(origin,
                                (pokemon['latitude'], pokemon['longitude'])))
            for pokemon in pokemons
        ]
        self.log.debug("Nearby pokemon: : %s", pokemon_distances)
        for pokemon_distance in pokemon_distances:
            target = pokemon_distance
            self.log.debug("Catching pokemon: : %s, distance: %f meters",
                           target[0], target[1])
            self.log.info("Catching Pokemon: %s",
                          self.pokemon_names[str(target[0]['pokemon_id'])])
            return self.encounter_pokemon(target[0])
            if sum(self.pokeballs) == 0:
                self.spin_near_fort()
        return False

    def nearby_map_objects(self):
        position = self.get_position()
        neighbors = get_neighbors(self._posf)
        return self.get_map_objects(latitude=position[0],
                                    longitude=position[1],
                                    since_timestamp_ms=[0] * len(neighbors),
                                    cell_id=neighbors).call()

    def attempt_catch(self, encounter_id, spawn_point_id, ball_type):
        r = self.catch_pokemon(
            normalized_reticle_size=1.950,
            pokeball=ball_type,
            spin_modifier=0.850,
            hit_pokemon=True,
            normalized_hit_position=1,
            encounter_id=encounter_id,
            spawn_point_id=spawn_point_id,
        ).call()['responses']['CATCH_POKEMON']
        self.log.info("Throwing pokeball type: %s",
                      POKEBALLS[ball_type -
                                1])  # list the pokeball that was thrown
        if "status" in r:
            self.log.debug("Status: %d", r['status'])
            return r

    def cleanup_inventory(self, inventory_items=None):
        if not inventory_items:
            inventory_items = self.get_inventory().call()['responses'][
                'GET_INVENTORY']['inventory_delta']['inventory_items']

        all_actual_items = [
            xiq['inventory_item_data']["item"] for xiq in inventory_items
            if "item" in xiq['inventory_item_data']
        ]
        all_actual_item_str = "List of items:\n"
        all_actual_item_count = 0
        all_actual_items = sorted(
            [x for x in all_actual_items if "count" in x],
            key=lambda x: x["item_id"])
        for xiq in all_actual_items:
            if 1 <= xiq["item_id"] <= 4:  # save counts of pokeballs
                self.pokeballs[xiq["item_id"]] = xiq["count"]
            true_item_name = INVENTORY_DICT[xiq["item_id"]]
            all_actual_item_str += "Item_ID " + str(
                xiq["item_id"]) + "\titem count " + str(
                    xiq["count"]) + "\t(" + true_item_name + ")\n"
            all_actual_item_count += xiq["count"]
        all_actual_item_str += "Total item count: " + str(
            all_actual_item_count)
        self.log.info(all_actual_item_str)

        caught_pokemon = defaultdict(list)
        for inventory_item in inventory_items:
            if "pokemon_data" in inventory_item['inventory_item_data']:
                # This code block checks to see if the inventory item is an item or pokemon
                pokemon = inventory_item['inventory_item_data']['pokemon_data']
                if 'cp' in pokemon and "favorite" not in pokemon:
                    caught_pokemon[pokemon["pokemon_id"]].append(pokemon)
            elif "item" in inventory_item['inventory_item_data']:
                item = inventory_item['inventory_item_data'][
                    'item']  # Check to see if your holding too many items and recycles them
                if item['item_id'] in self.min_item_counts and "count" in item and item[
                        'count'] > self.min_item_counts[item['item_id']]:
                    recycle_count = item['count'] - self.min_item_counts[
                        item['item_id']]
                    self.log.info("Recycling {0}, item count {1}".format(
                        INVENTORY_DICT[item['item_id']], recycle_count))
                    self.recycle_inventory_item(item_id=item['item_id'],
                                                count=recycle_count)

        for pokemons in caught_pokemon.values():
            if len(
                    pokemons
            ) > MIN_SIMILAR_POKEMON:  # if you have more than 1 of the same amount of pokemon do this
                pokemons = sorted(pokemons,
                                  lambda x, y: cmp(x['cp'], y['cp']),
                                  reverse=True)
                for pokemon in pokemons:
                    if pokemon['pokemon_id'] in CANDY_NEEDED_TO_EVOLVE:
                        for inventory_item in inventory_items:
                            if "pokemon_family" in inventory_item[
                                    'inventory_item_data'] and (
                                        inventory_item['inventory_item_data']
                                        ['pokemon_family']['family_id']
                                        == pokemon['pokemon_id'] or
                                        inventory_item['inventory_item_data']
                                        ['pokemon_family']['family_id']
                                        == (pokemon['pokemon_id'] - 1)
                                    ) and inventory_item['inventory_item_data'][
                                        'pokemon_family'][
                                            'candy'] > CANDY_NEEDED_TO_EVOLVE[
                                                pokemon[
                                                    'pokemon_id']]:  # Check to see if the pokemon is able to evolve or not, supports t2 evolutions
                                self.log.info(
                                    "Evolving pokemon: %s",
                                    self.pokemon_names[str(
                                        pokemon['pokemon_id'])])
                                self.evolve_pokemon(
                                    pokemon_id=pokemon['id']
                                )  # quick press ctrl + c to stop the evolution
                for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                    if 'cp' in pokemon and pokemon_iv_percentage(
                            pokemon
                    ) < self.MIN_KEEP_IV and pokemon[
                            "cp"] < self.KEEP_CP_OVER:  # remove only if the pokemon is under the IV and CP set up
                        self.log.debug("Releasing pokemon: %s", pokemon)
                        self.log.info(
                            "Releasing pokemon: %s IV: %s",
                            self.pokemon_names[str(pokemon['pokemon_id'])],
                            pokemon_iv_percentage(pokemon))
                        self.release_pokemon(pokemon_id=pokemon["id"]
                                             )  # release the unwanted pokemon

        if self.RELEASE_DUPLICATES:
            for pokemons in caught_pokemon.values():
                if len(pokemons) > MIN_SIMILAR_POKEMON:
                    pokemons = sorted(
                        pokemons, lambda x, y: cmp(
                            self.pokemon_names[str(x['pokemon_id'])], self.
                            pokemon_names[str(y['pokemon_id'])]))
                    last_pokemon = pokemons[0]
                    for pokemon in pokemons[MIN_SIMILAR_POKEMON:]:
                        if self.pokemon_names[str(
                                pokemon['pokemon_id'])] == self.pokemon_names[
                                    str(last_pokemon['pokemon_id'])]:
                            # Compare two pokemon if the larger IV pokemon has less then DUPLICATE_CP_FORGIVENESS times CP keep it
                            if pokemon_iv_percentage(
                                    pokemon) > pokemon_iv_percentage(
                                        last_pokemon):
                                if pokemon[
                                        'cp'] * self.DUPLICATE_CP_FORGIVENESS < last_pokemon[
                                            'cp']:
                                    # release the lesser!
                                    self.log.debug("Releasing pokemon: %s",
                                                   last_pokemon)
                                    self.log.info(
                                        "Releasing pokemon: %s IV: %s",
                                        self.pokemon_names[str(
                                            last_pokemon['pokemon_id'])],
                                        pokemon_iv_percentage(last_pokemon))
                                    self.release_pokemon(
                                        pokemon_id=last_pokemon["id"])
                                last_pokemon = pokemon
                            else:
                                if last_pokemon[
                                        'cp'] * self.DUPLICATE_CP_FORGIVENESS > pokemon[
                                            'cp']:
                                    # release the lesser!
                                    self.log.debug("Releasing pokemon: %s",
                                                   pokemon)
                                    self.log.info(
                                        "Releasing pokemon: %s IV: %s",
                                        self.pokemon_names[str(
                                            pokemon['pokemon_id'])],
                                        pokemon_iv_percentage(pokemon))
                                    self.release_pokemon(
                                        pokemon_id=pokemon["id"])
                                last_pokemon = pokemon

        return self.call()

    def disk_encounter_pokemon(self, lureinfo):
        try:
            encounter_id = lureinfo['encounter_id']
            fort_id = lureinfo['fort_id']
            position = self._posf
            resp = self.disk_encounter(encounter_id=encounter_id,
                                       fort_id=fort_id,
                                       player_latitude=position[0],
                                       player_longitude=position[1]).call(
                                       )['responses']['DISK_ENCOUNTER']
            if resp['result'] == 1:
                capture_status = -1
                self._pokeball_type = 1
                while capture_status != 0 and capture_status != 3:
                    for balls in range(len(self.pokeballs)):
                        self._pokeball_type = balls
                        if self.pokeballs[balls] > 0:
                            catch_attempt = self.attempt_catch(
                                encounter_id, fort_id, self._pokeball_type)
                            self.pokeballs[self._pokeball_type] -= 1
                            capture_status = catch_attempt['status']
                            if capture_status == 1:
                                self.log.debug("Caught Pokemon: : %s",
                                               catch_attempt)
                                self.log.info(
                                    "Caught Pokemon:  %s",
                                    self.pokemon_names[str(
                                        resp['pokemon_data']['pokemon_id'])])
                                self._pokeball_type = 1
                                sleep(
                                    self.RANDOM_SLEEP_TIME * random.random() +
                                    2
                                )  # If you want to make it faster, delete this line... would not recommend though
                                return catch_attempt
                            elif capture_status == 2:
                                self.log.info(
                                    "Pokemon %s is too wild",
                                    self.pokemon_names[str(
                                        resp['pokemon_data']['pokemon_id'])])
                                if self._pokeball_type < self.MAX_BALL_TYPE:
                                    self._pokeball_type += 1
                            elif capture_status == 3:
                                self.log.debug("Failed Catch: : %s",
                                               catch_attempt)
                                self.log.info(
                                    "Failed to Catch Pokemon:  %s",
                                    self.pokemon_names[str(
                                        resp['pokemon_data']['pokemon_id'])])
                                self._pokeball_type = 1
                    sleep(
                        self.RANDOM_SLEEP_TIME * random.random() + 2
                    )  # If you want to make it faster, delete this line... would not recommend though
            return False
        except Exception as e:
            self.log.error("Error in disk encounter %s", e)
            self._pokeball_type = 1
            return False

    def encounter_pokemon(self, pokemon):
        encounter_id = pokemon['encounter_id']
        spawn_point_id = pokemon['spawn_point_id']
        position = self._posf
        encounter = self.encounter(
            encounter_id=encounter_id,
            spawn_point_id=spawn_point_id,
            player_latitude=position[0],
            player_longitude=position[1]).call()['responses']['ENCOUNTER']
        # this cade catches pokemon
        self.log.debug("Started Encounter: %s", encounter)
        if encounter['status'] == 1:
            capture_status = -1
            self._pokeball_type = 1  # start with a pokeball
            while capture_status != 0 and capture_status != 3:
                for balls in range(
                        len(self.pokeballs)
                ):  # try with each ball type starting with weakest
                    self._pokeball_type = balls
                    if self.pokeballs[
                            balls] > 0:  # if you have less then 1 ball do not attempt to catch em all
                        catch_attempt = self.attempt_catch(
                            encounter_id, spawn_point_id,
                            self._pokeball_type)  # actual catching code
                        self.pokeballs[
                            self.
                            _pokeball_type] -= 1  # lowers the thrown ball code
                        capture_status = catch_attempt['status']
                        if capture_status == 1:
                            self.log.debug("Caught Pokemon: : %s",
                                           catch_attempt)  # you did it
                            self.log.info(
                                "Caught Pokemon:  %s",
                                self.pokemon_names[str(pokemon['pokemon_id'])])
                            self._pokeball_type = 1
                            sleep(
                                self.RANDOM_SLEEP_TIME * random.random() + 2
                            )  # If you want to make it faster, delete this line... would not recommend though
                            return catch_attempt
                        elif capture_status == 2:
                            self.log.info(
                                "Pokemon %s is too wild",
                                self.pokemon_names[str(pokemon['pokemon_id'])])
                            if self._pokeball_type < self.MAX_BALL_TYPE:
                                self._pokeball_type += 1  # try with a stronger ball
                        elif capture_status == 3:
                            self.log.debug(
                                "Failed Catch: : %s", catch_attempt
                            )  # potential soft ban or just a run away
                            self.log.info(
                                "Failed to Catch Pokemon:  %s",
                                self.pokemon_names[str(pokemon['pokemon_id'])])
                            self._pokeball_type = 1
                sleep(
                    self.RANDOM_SLEEP_TIME * random.random() + 2
                )  # If you want to make it faster, delete this line... would not recommend though
        return False

    def login(self, provider, username, password, cached=False):
        if not isinstance(username, basestring) or not isinstance(
                password, basestring):
            raise AuthException("Username/password not correctly specified")

        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        else:
            raise AuthException(
                "Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: %s', provider)

        if not self._auth_provider.login(username, password):
            self.log.info('Login process failed')
            return False

        self.log.info('Starting RPC login sequence (app simulation)')
        self.get_player()
        self.get_hatched_eggs()
        self.get_inventory()
        self.check_awarded_badges()
        self.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930"
                               )  # not sure what this is but dont change it

        response = self.call()

        if not response:
            self.log.info('Login failed!')
        if os.path.isfile("auth_cache") and cached:
            response = pickle.load(open("auth_cache"))
        fname = "auth_cache_%s" % username
        if os.path.isfile(fname) and cached:
            response = pickle.load(open(fname))
        else:
            response = self.heartbeat()
            f = open(fname, "w")
            pickle.dump(response, f)
        if not response:
            self.log.info('Login failed!')
            return False

        if 'api_url' in response:
            self._api_endpoint = ('https://{}/rpc'.format(response['api_url']))
            self.log.debug('Setting API endpoint to: %s', self._api_endpoint)
        else:
            self.log.error('Login failed - unexpected server response!')
            return False

        if 'auth_ticket' in response:
            self._auth_provider.set_ticket(response['auth_ticket'].values())

        self.log.info('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')

        return True

    def main_loop(self):
        while True:
            self.heartbeat()
            sleep(
                self.RANDOM_SLEEP_TIME * random.random() + 1
            )  # If you want to make it faster, delete this line... would not recommend though
            if sum(self.pokeballs
                   ) > 0:  # if you do not have any balls skip pokemon catching
                while self.catch_near_pokemon():
                    sleep(
                        self.RANDOM_SLEEP_TIME * random.random() + 4
                    )  # If you want to make it faster, delete this line... would not recommend though
            else:
                self.log.info(
                    "Less than 1 Poke Balls: Entering pokestops only")
            self.spin_near_fort()  # check local pokestop

    @staticmethod
    def flatmap(f, items):
        return chain.from_iterable(imap(f, items))