Ejemplo n.º 1
0
    def set_authentication(self,
                           provider=None,
                           oauth2_refresh_token=None,
                           username=None,
                           password=None):
        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        elif provider is None:
            self._auth_provider = None
        else:
            raise AuthException(
                "Invalid authentication provider - only ptc/google available.")

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

        if oauth2_refresh_token is not None:
            self._auth_provider.set_refresh_token(oauth2_refresh_token)
        elif username is not None and password is not None:
            self._auth_provider.user_login(username, password)
        else:
            raise AuthException(
                "Invalid Credential Input - Please provide username/password or an oauth2 refresh token"
            )
Ejemplo n.º 2
0
    def set_authentication(self,
                           provider=None,
                           oauth2_refresh_token=None,
                           username=None,
                           password=None,
                           proxy_config=None,
                           user_agent=None,
                           timeout=None):
        if provider == 'ptc':
            self._auth_provider = AuthPtc(user_agent=user_agent,
                                          timeout=timeout)
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        elif provider is None:
            self._auth_provider = None
        else:
            raise InvalidCredentialsException(
                "Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: {}'.format(provider))

        if proxy_config:
            self._auth_provider.set_proxy(proxy_config)

        if oauth2_refresh_token is not None:
            self._auth_provider.set_refresh_token(oauth2_refresh_token)
        elif username and password:
            if not self._auth_provider.user_login(username, password):
                raise AuthException("User login failed!")
        else:
            raise InvalidCredentialsException(
                "Invalid Credential Input - Please provide username/password or an oauth2 refresh token"
            )
Ejemplo n.º 3
0
    def login(self, provider, username, password, lat = None, lng = None, alt = None, app_simulation = True):

        if (lat is not None) and (lng is not None) and (alt is not None):
            self._position_lat = lat
            self._position_lng = lng
            self._position_alt = alt

        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

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

            # making a standard call, like it is also done by the client
            request = self.create_request()

            request.get_player()
            request.get_hatched_eggs()
            request.get_inventory()
            request.check_awarded_badges()
            request.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")

            response = request.call()
        else:
            self.log.info('Starting minimal RPC login sequence')
            response = self.get_player()

        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 app_simulation:
            self.log.info('Finished RPC login sequence (app simulation)')
        else:
            self.log.info('Finished minimal RPC login sequence')

        self.log.info('Login process completed')

        return True
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
    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.debug('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
        sleep(3 * random.random() + 5)
        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.debug('Finished RPC login sequence (app simulation)')
        self.log.info('Login process completed')
        if self.SLOW_BUT_STEALTH:
            sleep(3 * random.random() + 10)

        return True
Ejemplo n.º 6
0
    def login(self, provider, username, password, auth_token=None):

        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 auth_token is None:
            if not self._auth_provider.login(username, password):
                self.log.info('Login process failed')
                return False
            with open("token.txt", "w") as f:
                f.write(self._auth_provider.get_token())
                self.log.info('Token saved')
        else:
            self.log.info('Reuse token')
            self._auth_provider._login = True
            self._auth_provider.set_token(auth_token)

        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
Ejemplo n.º 7
0
    def set_authentication(self, provider=None, oauth2_refresh_token=None, username=None, password=None):
        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        elif provider is None:
            self._auth_provider = None
        else:
            raise AuthException("Invalid authentication provider - only ptc/google available.")

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

        if oauth2_refresh_token is not None:
            self._auth_provider.set_refresh_token(oauth2_refresh_token)
        elif username is not None and password is not None:
            self._auth_provider.user_login(username, password)
        else:
            raise AuthException("Invalid Credential Input - Please provide username/password or an oauth2 refresh token")
Ejemplo n.º 8
0
    def set_authentication(self, provider=None, oauth2_refresh_token=None, username=None, password=None, proxy_config=None, user_agent=None, timeout=None):
        if provider == 'ptc':
            self._auth_provider = AuthPtc(user_agent=user_agent, timeout=timeout)
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        elif provider is None:
            self._auth_provider = None
        else:
            raise InvalidCredentialsException("Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: {}'.format(provider))

        if proxy_config:
            self._auth_provider.set_proxy(proxy_config)

        if oauth2_refresh_token is not None:
            self._auth_provider.set_refresh_token(oauth2_refresh_token)
        elif username and password:
            if not self._auth_provider.user_login(username, password):
                raise AuthException("User login failed!")
        else:
            raise InvalidCredentialsException("Invalid Credential Input - Please provide username/password or an oauth2 refresh token")
Ejemplo n.º 9
0
class PGoApi:

    def __init__(self, provider=None, oauth2_refresh_token=None, username=None, password=None, position_lat=None, position_lng=None, position_alt=None, proxy_config=None, device_info=None):
        self.set_logger()
        self.log.info('%s v%s - %s', __title__, __version__, __copyright__)

        self._auth_provider = None
        if provider is not None and ((username is not None and password is not None) or (oauth2_refresh_token is not None)):
            self.set_authentication(provider, oauth2_refresh_token, username, password, proxy_config)

        self.set_api_endpoint("pgorelease.nianticlabs.com/plfe")

        self._position_lat = position_lat
        self._position_lng = position_lng
        self._position_alt = position_alt

        self._hash_server_token = None

        self._session = requests.session()
        self._session.headers.update({'User-Agent': 'Niantic App'})
        self._session.verify = True

        if proxy_config is not None:
            self._session.proxies = proxy_config

        self.device_info = device_info

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

    @staticmethod
    def get_api_version():
        return 9100

    def set_authentication(self, provider=None, oauth2_refresh_token=None, username=None, password=None, proxy_config=None, user_agent=None, timeout=None):
        if provider == 'ptc':
            self._auth_provider = AuthPtc(user_agent=user_agent, timeout=timeout)
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        elif provider is None:
            self._auth_provider = None
        else:
            raise InvalidCredentialsException("Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: {}'.format(provider))

        if proxy_config:
            self._auth_provider.set_proxy(proxy_config)

        if oauth2_refresh_token is not None:
            self._auth_provider.set_refresh_token(oauth2_refresh_token)
        elif username and password:
            if not self._auth_provider.user_login(username, password):
                raise AuthException("User login failed!")
        else:
            raise InvalidCredentialsException("Invalid Credential Input - Please provide username/password or an oauth2 refresh token")

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

    def set_position(self, lat, lng, alt=None):
        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 set_proxy(self, proxy_config):
        self._session.proxies = proxy_config

    def get_api_endpoint(self):
        return self._api_endpoint

    def set_api_endpoint(self, api_url):
        if api_url.startswith("https"):
            self._api_endpoint = api_url
        else:
            self._api_endpoint = parse_api_endpoint(api_url)

    def get_auth_provider(self):
        return self._auth_provider

    def create_request(self):
        request = PGoApiRequest(self, self._position_lat, self._position_lng,
                                self._position_alt, self.device_info)
        return request

    def activate_hash_server(self, hash_server_token):
        self._hash_server_token = hash_server_token

    def get_hash_server_token(self):
        return self._hash_server_token

    def __getattr__(self, func):
        def function(**kwargs):
            request = self.create_request()
            getattr(request, func)(_call_direct=True, **kwargs )
            return request.call()

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

    def app_simulation_login(self):
        self.log.info('Starting RPC login sequence (iOS app simulation)')

        # Send empty initial request
        request = self.create_request()
        response = request.call()
        
        time.sleep(1.5)
        
        # Send GET_PLAYER only
        request = self.create_request()
        request.get_player(player_locale = {'country': 'US', 'language': 'en', 'timezone': 'America/Chicago'})
        response = request.call()

        if response.get('responses', {}).get('GET_PLAYER', {}).get('banned', False):
            raise BannedAccountException

        time.sleep(1.5)

        request = self.create_request()
        request.download_remote_config_version(platform=1, app_version=self.get_api_version())
        request.check_challenge()
        request.get_hatched_eggs()
        request.get_inventory()
        request.check_awarded_badges()
        request.download_settings()
        response = request.call()

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

        return response

    """
    The login function is not needed anymore but still in the code for backward compatibility"
    """
    def login(self, provider, username, password, lat=None, lng=None, alt=None, app_simulation=True):

        if lat and lng:
            self._position_lat = lat
            self._position_lng = lng
        if alt:
            self._position_alt = alt

        try:
            self.set_authentication(provider, username=username, password=password)
        except AuthException as e:
            self.log.error('Login process failed: %s', e)
            return False

        if app_simulation:
            response = self.app_simulation_login()
        else:
            self.log.info('Starting minimal RPC login sequence')
            response = self.get_player()
            self.log.info('Finished minimal RPC login sequence')

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

        self.log.info('Login process completed')

        return True
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))
Ejemplo n.º 11
0
class PGoApi:
    def __init__(self,
                 provider=None,
                 oauth2_refresh_token=None,
                 username=None,
                 password=None,
                 position_lat=None,
                 position_lng=None,
                 position_alt=None,
                 proxy_config=None,
                 device_info=None):
        self.set_logger()
        self.log.info('%s v%s - %s', __title__, __version__, __copyright__)

        self._auth_provider = None
        if provider is not None and (
            (username is not None and password is not None) or
            (oauth2_refresh_token is not None)):
            self.set_authentication(provider, oauth2_refresh_token, username,
                                    password, proxy_config)

        self.set_api_endpoint("pgorelease.nianticlabs.com/plfe")

        self._position_lat = position_lat
        self._position_lng = position_lng
        self._position_alt = position_alt

        self._hash_server_token = None

        self._session = requests.session()
        self._session.headers.update({'User-Agent': 'Niantic App'})
        self._session.verify = True

        if proxy_config is not None:
            self._session.proxies = proxy_config

        self.device_info = device_info

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

    @staticmethod
    def get_api_version():
        return 9100

    def set_authentication(self,
                           provider=None,
                           oauth2_refresh_token=None,
                           username=None,
                           password=None,
                           proxy_config=None,
                           user_agent=None,
                           timeout=None):
        if provider == 'ptc':
            self._auth_provider = AuthPtc(user_agent=user_agent,
                                          timeout=timeout)
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        elif provider is None:
            self._auth_provider = None
        else:
            raise InvalidCredentialsException(
                "Invalid authentication provider - only ptc/google available.")

        self.log.debug('Auth provider: {}'.format(provider))

        if proxy_config:
            self._auth_provider.set_proxy(proxy_config)

        if oauth2_refresh_token is not None:
            self._auth_provider.set_refresh_token(oauth2_refresh_token)
        elif username and password:
            if not self._auth_provider.user_login(username, password):
                raise AuthException("User login failed!")
        else:
            raise InvalidCredentialsException(
                "Invalid Credential Input - Please provide username/password or an oauth2 refresh token"
            )

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

    def set_position(self, lat, lng, alt=None):
        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 set_proxy(self, proxy_config):
        self._session.proxies = proxy_config

    def get_api_endpoint(self):
        return self._api_endpoint

    def set_api_endpoint(self, api_url):
        if api_url.startswith("https"):
            self._api_endpoint = api_url
        else:
            self._api_endpoint = parse_api_endpoint(api_url)

    def get_auth_provider(self):
        return self._auth_provider

    def create_request(self):
        request = PGoApiRequest(self, self._position_lat, self._position_lng,
                                self._position_alt, self.device_info)
        return request

    def activate_hash_server(self, hash_server_token):
        self._hash_server_token = hash_server_token

    def get_hash_server_token(self):
        return self._hash_server_token

    def __getattr__(self, func):
        def function(**kwargs):
            request = self.create_request()
            getattr(request, func)(_call_direct=True, **kwargs)
            return request.call()

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

    def app_simulation_login(self):
        self.log.info('Starting RPC login sequence (iOS app simulation)')

        # Send empty initial request
        request = self.create_request()
        response = request.call()

        time.sleep(1.5)

        # Send GET_PLAYER only
        request = self.create_request()
        request.get_player(player_locale={
            'country': 'US',
            'language': 'en',
            'timezone': 'America/Chicago'
        })
        response = request.call()

        if response.get('responses', {}).get('GET_PLAYER',
                                             {}).get('banned', False):
            raise BannedAccountException

        time.sleep(1.5)

        request = self.create_request()
        request.download_remote_config_version(
            platform=1, app_version=self.get_api_version())
        request.check_challenge()
        request.get_hatched_eggs()
        request.get_inventory()
        request.check_awarded_badges()
        request.download_settings()
        response = request.call()

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

        return response

    """
    The login function is not needed anymore but still in the code for backward compatibility"
    """

    def login(self,
              provider,
              username,
              password,
              lat=None,
              lng=None,
              alt=None,
              app_simulation=True):

        if lat and lng:
            self._position_lat = lat
            self._position_lng = lng
        if alt:
            self._position_alt = alt

        try:
            self.set_authentication(provider,
                                    username=username,
                                    password=password)
        except AuthException as e:
            self.log.error('Login process failed: %s', e)
            return False

        if app_simulation:
            response = self.app_simulation_login()
        else:
            self.log.info('Starting minimal RPC login sequence')
            response = self.get_player()
            self.log.info('Finished minimal RPC login sequence')

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

        self.log.info('Login process completed')

        return True
Ejemplo n.º 12
0
class PGoApi:

    def __init__(self):

        self.set_logger()

        self._auth_provider = None
        self._api_endpoint = 'https://pgorelease.nianticlabs.com/plfe/rpc'

        self._position_lat = None
        self._position_lng = None
        self._position_alt = None

        self.log.info('%s v%s - %s', __title__, __version__, __copyright__ )

    def set_logger(self, logger = None):
        self.log = logger or logging.getLogger(__name__)
        
    def get_api_endpoint(self):
        return self._api_endpoint

    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 create_request(self):    
        request = PGoApiRequest(self._api_endpoint, self._auth_provider, self._position_lat, self._position_lng, self._position_alt)
        return request

    def __getattr__(self, func):
    
        def function(**kwargs):
            request = self.create_request()
            getattr(request, func)( _call_direct = True, **kwargs )
            return request.call()

        if func.upper() in  RequestType.keys():
            return function
        else:
            raise AttributeError
        
    def login(self, provider, username, password, lat = None, lng = None, alt = None, app_simulation = True):

        if (lat is not None) and (lng is not None) and (alt is not None):
            self._position_lat = lat
            self._position_lng = lng
            self._position_alt = alt

        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

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

            # making a standard call, like it is also done by the client
            request = self.create_request()

            request.get_player()
            request.get_hatched_eggs()
            request.get_inventory()
            request.check_awarded_badges()
            request.download_settings(hash="05daf51635c82611d1aac95c0b051d3ec088a930")

            response = request.call()
        else:
            self.log.info('Starting minimal RPC login sequence')
            response = self.get_player()

        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 app_simulation:
            self.log.info('Finished RPC login sequence (app simulation)')
        else:
            self.log.info('Finished minimal RPC login sequence')

        self.log.info('Login process completed')

        return True
Ejemplo n.º 13
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)))
Ejemplo n.º 14
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))
    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
Ejemplo n.º 16
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))
Ejemplo n.º 17
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
Ejemplo n.º 18
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)))
Ejemplo n.º 19
0
class PGoApi:
    def __init__(self,
                 provider=None,
                 oauth2_refresh_token=None,
                 username=None,
                 password=None,
                 position_lat=None,
                 position_lng=None,
                 position_alt=None):
        self.set_logger()
        self.log.info('%s v%s - %s', __title__, __version__, __copyright__)

        self._auth_provider = None
        if provider is not None and (
            (username is not None and password is not None) or
            (oauth2_refresh_token is not None)):
            self.set_authentication(provider, oauth2_refresh_token, username,
                                    password)

        self.set_api_endpoint("pgorelease.nianticlabs.com/plfe")

        self._position_lat = position_lat
        self._position_lng = position_lng
        self._position_alt = position_alt

        self._signature_lib = None

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

    def set_authentication(self,
                           provider=None,
                           oauth2_refresh_token=None,
                           username=None,
                           password=None):
        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        elif provider is None:
            self._auth_provider = None
        else:
            raise AuthException(
                "Invalid authentication provider - only ptc/google available.")

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

        if oauth2_refresh_token is not None:
            self._auth_provider.set_refresh_token(oauth2_refresh_token)
        elif username is not None and password is not None:
            self._auth_provider.user_login(username, password)
        else:
            raise AuthException(
                "Invalid Credential Input - Please provide username/password or an oauth2 refresh token"
            )

    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 get_api_endpoint(self):
        return self._api_endpoint

    def set_api_endpoint(self, api_url):
        if api_url.startswith("https"):
            self._api_endpoint = api_url
        else:
            self._api_endpoint = parse_api_endpoint(api_url)

    def get_auth_provider(self):
        return self._auth_provider

    def create_request(self):
        request = PGoApiRequest(self, self._position_lat, self._position_lng,
                                self._position_alt)
        return request

    def activate_signature(self, lib_path):
        self._signature_lib = lib_path

    def get_signature_lib(self):
        return self._signature_lib

    def __getattr__(self, func):
        def function(**kwargs):
            request = self.create_request()
            getattr(request, func)(_call_direct=True, **kwargs)
            return request.call()

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

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

        # making a standard call, like it is also done by the client
        request = self.create_request()

        request.get_player()
        request.get_hatched_eggs()
        request.get_inventory()
        request.check_awarded_badges()
        request.download_settings(
            hash="54b359c97e46900f87211ef6e6dd0b7f2a3ea1f5")

        response = request.call()

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

        return response

    """
    The login function is not needed anymore but still in the code for backward compatibility"
    """

    def login(self,
              provider,
              username,
              password,
              lat=None,
              lng=None,
              alt=None,
              app_simulation=True):

        if lat is not None and lng is not None and alt is not None:
            self._position_lat = lat
            self._position_lng = lng
            self._position_alt = alt

        try:
            self.set_authentication(provider,
                                    username=username,
                                    password=password)
        except AuthException as e:
            self.log.error('Login process failed: %s', e)
            return False

        if app_simulation:
            response = self.app_simulation_login()
        else:
            self.log.info('Starting minimal RPC login sequence')
            response = self.get_player()
            self.log.info('Finished minimal RPC login sequence')

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

        self.log.info('Login process completed')

        return True
Ejemplo n.º 20
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))
Ejemplo n.º 21
0
class PGoApi:

    def __init__(self, provider=None, oauth2_refresh_token=None, username=None, password=None, position_lat=None, position_lng=None, position_alt=None, proxy_config=None):
        self.set_logger()
        self.log.info('%s v%s - %s', __title__, __version__, __copyright__)

        self._auth_provider = None
        if provider is not None and ((username is not None and password is not None) or (oauth2_refresh_token is not None)):
            self.set_authentication(provider, oauth2_refresh_token, username, password)

        self.set_api_endpoint("pgorelease.nianticlabs.com/plfe")

        self._position_lat = position_lat
        self._position_lng = position_lng
        self._position_alt = position_alt

        self._signature_lib = None

        self._session = requests.session()
        self._session.headers.update({'User-Agent': 'Niantic App'})
        self._session.verify = True

        if proxy_config is not None:
            self._session.proxies = proxy_config

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

    def set_authentication(self, provider=None, oauth2_refresh_token=None, username=None, password=None):
        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        elif provider is None:
            self._auth_provider = None
        else:
            raise AuthException("Invalid authentication provider - only ptc/google available.")

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

        if oauth2_refresh_token is not None:
            self._auth_provider.set_refresh_token(oauth2_refresh_token)
        elif username is not None and password is not None:
            self._auth_provider.user_login(username, password)
        else:
            raise AuthException("Invalid Credential Input - Please provide username/password or an oauth2 refresh token")

    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 set_proxy(self, proxy_config):
        self._session.proxies = proxy_config

    def get_api_endpoint(self):
        return self._api_endpoint

    def set_api_endpoint(self, api_url):
        if api_url.startswith("https"):
            self._api_endpoint = api_url
        else:
            self._api_endpoint = parse_api_endpoint(api_url)

    def get_auth_provider(self):
        return self._auth_provider

    def create_request(self):
        request = PGoApiRequest(self, self._position_lat, self._position_lng,
                                self._position_alt)
        return request

    def activate_signature(self, lib_path):
        self._signature_lib = lib_path

    def get_signature_lib(self):
        return self._signature_lib

    def __getattr__(self, func):
        def function(**kwargs):
            request = self.create_request()
            getattr(request, func)(_call_direct=True, **kwargs )
            return request.call()

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

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

        # making a standard call, like it is also done by the client
        request = self.create_request()

        request.get_player()
        request.get_hatched_eggs()
        request.get_inventory()
        request.check_awarded_badges()
        request.download_settings(hash="54b359c97e46900f87211ef6e6dd0b7f2a3ea1f5")

        response = request.call()

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

        return response

    """
    The login function is not needed anymore but still in the code for backward compatibility"
    """
    def login(self, provider, username, password, lat=None, lng=None, alt=None, app_simulation=True):

        if lat is not None and lng is not None and alt is not None:
            self._position_lat = lat
            self._position_lng = lng
            self._position_alt = alt

        try:
            self.set_authentication(provider, username=username, password=password)
        except AuthException as e:
            self.log.error('Login process failed: %s', e)
            return False

        if app_simulation:
            response = self.app_simulation_login()
        else:
            self.log.info('Starting minimal RPC login sequence')
            response = self.get_player()
            self.log.info('Finished minimal RPC login sequence')

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

        self.log.info('Login process completed')

        return True
Ejemplo n.º 22
0
class PGoApi:

    def __init__(self, provider=None, oauth2_refresh_token=None, username=None, password=None, position_lat=None, position_lng=None, position_alt=None, proxy_config=None, device_info=None):
        self.set_logger()
        self.log.info('%s v%s - %s', __title__, __version__, __copyright__)

        self._auth_provider = None
        if provider is not None and ((username is not None and password is not None) or (oauth2_refresh_token is not None)):
            self.set_authentication(provider, oauth2_refresh_token, username, password, proxy_config)

        self.set_api_endpoint("pgorelease.nianticlabs.com/plfe")

        self._position_lat = position_lat
        self._position_lng = position_lng
        self._position_alt = position_alt

        self._signature_lib = None
        self._hash_lib = None

        self._session = requests.session()
        self._session.headers.update({'User-Agent': 'Niantic App'})
        self._session.verify = True

        if proxy_config is not None:
            self._session.proxies = proxy_config

        self.device_info = device_info

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

    def set_authentication(self, provider=None, oauth2_refresh_token=None, username=None, password=None, proxy_config=None):
        if provider == 'ptc':
            self._auth_provider = AuthPtc()
        elif provider == 'google':
            self._auth_provider = AuthGoogle()
        elif provider is None:
            self._auth_provider = None
        else:
            raise AuthException("Invalid authentication provider - only ptc/google available.")

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

        if proxy_config is not None:
            self._auth_provider.set_proxy(proxy_config)

        if oauth2_refresh_token is not None:
            self._auth_provider.set_refresh_token(oauth2_refresh_token)
        elif username is not None and password is not None:
            if not self._auth_provider.user_login(username, password):
                raise AuthException("User login failed!")
        else:
            raise AuthException("Invalid Credential Input - Please provide username/password or an oauth2 refresh token")

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

    def set_position(self, lat, lng, alt=None):
        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 set_proxy(self, proxy_config):
        self._session.proxies = proxy_config

    def get_api_endpoint(self):
        return self._api_endpoint

    def set_api_endpoint(self, api_url):
        if api_url.startswith("https"):
            self._api_endpoint = api_url
        else:
            self._api_endpoint = parse_api_endpoint(api_url)

    def get_auth_provider(self):
        return self._auth_provider

    def create_request(self):
        request = PGoApiRequest(self, self._position_lat, self._position_lng,
                                self._position_alt, self.device_info)
        return request

    def activate_signature(self, signature_lib_path=None, hash_lib_path=None):
        if signature_lib_path: self.set_signature_lib(signature_lib_path)
        if hash_lib_path: self.set_hash_lib(hash_lib_path)

    def set_signature_lib(self, signature_lib_path):
        self._signature_lib = signature_lib_path

    def set_hash_lib(self, hash_lib_path):
        self._hash_lib = hash_lib_path

    def get_signature_lib(self):
        return self._signature_lib

    def get_hash_lib(self):
        return self._hash_lib

    def __getattr__(self, func):
        def function(**kwargs):
            request = self.create_request()
            getattr(request, func)(_call_direct=True, **kwargs )
            return request.call()

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

    def app_simulation_login(self):
        self.log.info('Starting RPC login sequence (iOS app simulation)')

        # Send empty initial request
        request = self.create_request()
        response = request.call()
        time.sleep(1.172)

        request = self.create_request()
        response = request.call()
        time.sleep(1.304)
        

        # Send GET_PLAYER only
        request = self.create_request()
        request.get_player(player_locale = {'country': 'US', 'language': 'en', 'timezone': 'America/Denver'})
        response = request.call()
        
        if response.get('responses', {}).get('GET_PLAYER', {}).get('banned', False):
            raise BannedAccount()

        time.sleep(1.356)

        request = self.create_request()
        request.download_remote_config_version(platform=1, app_version=4500)
        request.check_challenge()
        request.get_hatched_eggs()
        request.get_inventory()
        request.check_awarded_badges()
        request.download_settings()
        response = request.call()
        time.sleep(1.072)

        responses = response.get('responses', {})
        download_hash = responses.get('DOWNLOAD_SETTINGS', {}).get('hash')
        inventory = responses.get('GET_INVENTORY', {}).get('inventory_delta', {})
        timestamp = inventory.get('new_timestamp_ms')
        player_level = None
        for item in inventory.get('inventory_items', []):
            player_stats = item.get('inventory_item_data', {}).get('player_stats', {})
            if player_stats:
                player_level = player_stats.get('level')
                break

        request = self.create_request()
        request.get_asset_digest(platform=1, app_version=4500)
        request.check_challenge()
        request.get_hatched_eggs()
        request.get_inventory(last_timestamp_ms=timestamp)
        request.check_awarded_badges()
        request.download_settings(hash=download_hash)
        response = request.call()
        time.sleep(1.709)

        timestamp = response.get('responses', {}).get('GET_INVENTORY', {}).get('inventory_delta', {}).get('new_timestamp_ms')
        request = self.create_request()
        request.get_player_profile()
        request.check_challenge()
        request.get_hatched_eggs()
        request.get_inventory(last_timestamp_ms=timestamp)
        request.check_awarded_badges()
        request.download_settings(hash=download_hash)
        request.get_buddy_walked()
        response = request.call()
        time.sleep(1.326)

        timestamp = response.get('responses', {}).get('GET_INVENTORY', {}).get('inventory_delta', {}).get('new_timestamp_ms')
        request = self.create_request()
        request.level_up_rewards(level=player_level)
        request.check_challenge()
        request.get_hatched_eggs()
        request.get_inventory(last_timestamp_ms=timestamp)
        request.check_awarded_badges()
        request.download_settings(hash=download_hash)
        request.get_buddy_walked()
        response = request.call()

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

        return response

    """
    The login function is not needed anymore but still in the code for backward compatibility"
    """
    def login(self, provider, username, password, lat=None, lng=None, alt=None, app_simulation=True):

        if lat and lng:
            self._position_lat = lat
            self._position_lng = lng
        if alt:
            self._position_alt = alt

        try:
            self.set_authentication(provider, username=username, password=password, proxy_config=self._session.proxies)
        except AuthException as e:
            self.log.error('Login process failed: %s', e)
            return False

        if app_simulation:
            response = self.app_simulation_login()
        else:
            self.log.info('Starting minimal RPC login sequence')
            response = self.get_player()
            self.log.info('Finished minimal RPC login sequence')

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

        challenge_url = response.get('responses', {}).get('CHECK_CHALLENGE', {}).get('challenge_url', ' ')
        if challenge_url != ' ':
            self.log.error('CAPCHA required: ' + challenge_url)
            return challenge_url

        self.log.info('Login process completed')

        return True
Ejemplo n.º 23
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
Ejemplo n.º 24
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))