def entry(): args = parse_arguments(sys.argv[1:]) api = PGoApi() prog = re.compile("^(\-?\d+\.\d+),?\s?(\-?\d+\.\d+)$") res = prog.match(args.location) if res: print('Using the following coordinates: {}'.format(args.location)) position = (float(res.group(1)), float(res.group(2)), 0) else: print(('Failed to parse the supplied coordinates ({}).' + ' Please try again.').format(args.location)) return if args.hash_key: print "Using hash key: {}.".format(args.hash_key) api.activate_hash_server(args.hash_key) with open(str(args.file)) as f: credentials = [x.strip().split(',')[0:] for x in f.readlines()] for provider, username, password in credentials: try: if check_account(provider, username, password, position, api): # Success! csv_format = provider + ',' + username + ',' + password appendFile(csv_format, 'working.csv') except ServerSideRequestThrottlingException: print('Server side throttling, waiting for 10 seconds.') time.sleep(10) check_account(provider, username, password, position, api) except NotLoggedInException: print('Could not login, waiting for 10 seconds.') time.sleep(10) check_account(provider, username, password, position, api)
def perform_scout(p): global api, last_scout_timestamp, encounter_cache if not args.scout_account_username: return { "msg": "No scout account configured." } pokemon_name = get_pokemon_name(p.pokemon_id) # Check cache once in a non-blocking way if p.encounter_id in encounter_cache: result = encounter_cache[p.encounter_id] log.info(u"Cached scout-result: level {} {} with CP {}.".format(result["level"], pokemon_name, result["cp"])) return result scoutLock.acquire() try: # Check cache again after mutually exclusive access if p.encounter_id in encounter_cache: result = encounter_cache[p.encounter_id] log.info(u"Cached scout-result: level {} {} with CP {}.".format(result["level"], pokemon_name, result["cp"])) return result # Delay scouting now = time.time() if last_scout_timestamp is not None and now < last_scout_timestamp + scout_delay_seconds: wait_secs = last_scout_timestamp + scout_delay_seconds - now log.info("Waiting {} more seconds before next scout use.".format(wait_secs)) time.sleep(wait_secs) log.info(u"Scouting a {} at {}, {}".format(pokemon_name, p.latitude, p.longitude)) step_location = jitterLocation([p.latitude, p.longitude, 42]) if api is None: # instantiate pgoapi api = PGoApi() api.set_position(*step_location) api.set_proxy({'http': args.scout_account_proxy, 'https': args.scout_account_proxy}) account = { "auth_service": args.scout_account_auth, "username": args.scout_account_username, "password": args.scout_account_password, } check_login(args, account, api, None, False) if args.hash_key: key = key_scheduler.next() log.debug('Using key {} for this scout use.'.format(key)) api.activate_hash_server(key) request_result = encounter_request(long(b64decode(p.encounter_id)), p.spawnpoint_id, p.latitude, p.longitude) # Update last timestamp last_scout_timestamp = time.time() finally: scoutLock.release() return parse_scout_result(request_result, p.encounter_id, pokemon_name)
def checkAccounts(): api = PGoApi() api.activate_hash_server('') bannedAccounts = [] goodAccounts = [] with open('accounts.json', 'r') as accountlist: accounts = json.load(accountlist) for account in accounts: print(f"Checking {account['username']}...") try: api.set_authentication(provider=account['type'], username=account['username'], password=account['password']) api.set_position(40.707259, -73.520977, 144.3) request = api.create_request() request.call(request) time.sleep(1) request = api.create_request() request.get_player(player_locale={ 'country': 'US', 'language': 'en', 'timezone': 'America/New_York' }) request.call(request) time.sleep(1) request = api.create_request() request.download_remote_config_version(platform=1, app_version=7903) request.check_challenge() request.get_hatched_eggs() request.get_inventory(last_timestamp_ms=0) request.check_awarded_badges() request.download_settings() resp = request.call(request) # print(resp) if not resp['responses'].get('GET_INVENTORY'): bannedAccounts.append(account) print("\rbanned.") else: goodAccounts.append(account) print("Good") except AuthException: bannedAccounts.append(account) time.sleep(10) print( f"found {len(bannedAccounts)} banned accounts and {len(goodAccounts)} unbanned accounts." ) with open('banned.json', 'w') as bf: json.dump(bannedAccounts, bf) with open('good.json', 'w') as gf: json.dump(goodAccounts, gf)
class Renamer(object): def __init__(self): self.pokemon = [] self.api = None self.config = None self.pokemon_list = None def init_config(self): parser = argparse.ArgumentParser() parser.add_argument('-a', '--auth-service') parser.add_argument('-u', '--username') parser.add_argument('-p', '--password') parser.add_argument('--hash-key', required=True) parser.add_argument('--clear', action='store_true', default=False) parser.add_argument('--rename', action='store_true', default=False) parser.add_argument('--format', default='%percent% %atk %def %sta') parser.add_argument('--transfer', action='store_true', default=False) parser.add_argument('--locale', default='en') parser.add_argument('--location', required=True) parser.add_argument('--min-delay', type=int, default=10) parser.add_argument('--max-delay', type=int, default=20) parser.add_argument('--iv', type=int, default=75) self.config = parser.parse_args() self.config.overwrite = True def start(self): self.init_config() try: self.pokemon_list = json.load( open('locales/pokemon.' + self.config.locale + '.json')) except IOError: print 'The selected language is currently not supported' exit(0) self.setup_api() self.get_pokemon() self.print_pokemon() if self.config.clear: self.clear_pokemon() elif self.config.rename: self.rename_pokemon() elif self.config.transfer: self.transfer_pokemon() def get_elevation_for_position(self): try: url = 'https://maps.googleapis.com/maps/api/elevation/json?locations={},{}'.format( str(self.position[0]), str(self.position[1])) altitude = requests.get(url).json( )[u'results'][0][u'elevation'] + random.uniform(0.9, 1.7) print "Local altitude is: {0}m".format(altitude) self.position = (self.position[0], self.position[1], altitude) except requests.exceptions.RequestException: print "Unable to retrieve altitude from Google APIs; setting to 0" def get_location(self): # use lat/lng directly if matches such a pattern prog = re.compile("^(\-?\d+\.\d+),?\s?(\-?\d+\.\d+)$") res = prog.match(self.config.location) if res: print "Using coordinates from CLI directly..." self.position = (float(res.group(1)), float(res.group(2)), 0) else: print "Looking up coordinates using API..." self.position = util.get_pos_by_name(self.config.location) self.get_elevation_for_position() def setup_api(self): self.api = PGoApi() self.api.activate_hash_server(self.config.hash_key) self.get_location() print u'Signing in…' if not self.api.login( self.config.auth_service, self.config.username, self.config.password, self.position[0], # latitude self.position[1], # longitude self.position[2] # altitude ): print 'Login error' exit(0) def wait_randomly(self): random_delay = uniform(self.config.min_delay, self.config.max_delay) print u'Waiting %.3f seconds…' % random_delay time.sleep(random_delay) def get_pokemon(self): print u'Getting Pokémon list…' response_dict = self.api.get_inventory() self.pokemon = [] self.candy = {i: 0 for i in range(1, 251 + 1)} inventory_items = (response_dict.get('responses', {}).get( 'GET_INVENTORY', {}).get('inventory_delta', {}).get('inventory_items', {})) for item in inventory_items: try: reduce(dict.__getitem__, ['inventory_item_data', 'candy'], item) except KeyError: pass else: candy = item['inventory_item_data']['candy'] pokedex_number = candy['family_id'] self.candy[pokedex_number] = candy.get('candy', 0) for item in inventory_items: try: reduce(dict.__getitem__, ['inventory_item_data', 'pokemon_data'], item) except KeyError: pass else: try: pokemon = item['inventory_item_data']['pokemon_data'] pid = pokemon['id'] pokedex_number = pokemon['pokemon_id'] name = self.pokemon_list[str(pokedex_number)] attack = pokemon.get('individual_attack', 0) defense = pokemon.get('individual_defense', 0) stamina = pokemon.get('individual_stamina', 0) iv_percent = int( round((attack + defense + stamina) / 45.0 * 100.0)) is_favorite = pokemon.get('favorite', 0) > 0 nickname = pokemon.get('nickname', 'NONE') combat_power = pokemon.get('cp', 0) cpm = pokemon['cp_multiplier'] + pokemon.get( 'additional_cp_multiplier', 0) level = cpm2level(cpm) # https://github.com/AeonLucid/POGOProtos/blob/master/src/POGOProtos/Enums/Gender.proto GENDERS = { 0: '', # unset 1: u'♂', # male 2: u'♀', # female 3: '' # genderless } gender = GENDERS.get( pokemon.get('pokemon_display', {}).get('gender', 0), '') # https://github.com/AeonLucid/POGOProtos/blob/master/src/POGOProtos/Enums/Costume.proto has_costume = pokemon.get('pokemon_display', {}).get( 'costume', 0) > 0 is_shiny = pokemon.get('pokemon_display', {}).get( 'shiny', 0) > 0 self.pokemon.append({ 'id': pid, 'pokedex_number': pokedex_number, 'name': name, 'nickname': nickname, 'level': level, 'cp': combat_power, 'attack': attack, 'defense': defense, 'stamina': stamina, 'iv_percent': iv_percent, 'is_favorite': is_favorite, 'move_1': pokemon['move_1'], 'move_2': pokemon['move_2'], 'gender': gender, 'has_costume': has_costume, 'is_shiny': is_shiny, }) except KeyError: pass # Sort the way the in-game `Number` option would, i.e. by Pokedex number # in ascending order and then by CP in descending order. self.pokemon.sort(key=lambda k: (k['pokedex_number'], -k['cp'])) def print_pokemon(self): sorted_mons = sorted(self.pokemon, key=lambda k: (k['pokedex_number'], -k['iv_percent'])) groups = groupby(sorted_mons, key=lambda k: k['pokedex_number']) table_data = [[ u'Pokémon', 'Level', 'CP', 'IV %', 'ATK', 'DEF', 'STA', 'Candy', 'Recommendation' ]] total_evolutions = 0 total_transfers = 0 print u'%d Pokémon found.' % len(sorted_mons) for key, group in groups: group = list(group) pokemon_name = self.pokemon_list[str(key)] best_iv_pokemon = max(group, key=lambda k: k['iv_percent']) best_iv_pokemon['best_iv'] = True candy_count = self.candy[key] result = pogotransfercalc.calculate(pokemon_count=len(group), candy_count=candy_count, pokedex_number=key) evolutions = result['evolutions'] total_evolutions += evolutions if evolutions: for pokemon in group[:evolutions]: pokemon['message'] = 'evolve' transfers = result['transfers'] transfer_count = 0 if transfers: for pokemon in reversed(group[evolutions:]): if pokemon['is_favorite']: pokemon['message'] = u'keep (★)' continue if pokemon['has_costume']: pokemon['message'] = u'keep (costume)' continue if pokemon['is_shiny']: pokemon['message'] = u'keep (shiny)' continue if pokemon['iv_percent'] < self.config.iv: pokemon['message'] = 'transfer' pokemon['transfer'] = True transfer_count += 1 total_transfers += 1 if transfer_count == transfers: break continue for pokemon in group: if pokemon['iv_percent'] >= self.config.iv: iv_msg = u'(IV ≥ %d%%)' % self.config.iv if 'message' in pokemon: pokemon['message'] += ' %s' % iv_msg else: pokemon['message'] = 'keep %s' % iv_msg row_data = [ pokemon_name + pokemon['gender'] + (u'✨' if pokemon['is_shiny'] else '') + (u'☃' if pokemon['has_costume'] else '') + (u'★' if pokemon['is_favorite'] else ''), pokemon['level'], pokemon['cp'], '{0:.0f}%'.format(pokemon['iv_percent']), pokemon['attack'], pokemon['defense'], pokemon['stamina'], candy_count, pokemon.get('message', '') ] table_data.append(row_data) table = SingleTable(table_data) table.justify_columns = { 0: 'left', 1: 'right', 2: 'right', 3: 'right', 4: 'right', 5: 'right', 6: 'right', 7: 'right' } print table.table table = SingleTable([ ['Total suggested transfers', format_number(total_transfers)], ['Total evolutions', format_number(total_evolutions)], [ 'Total XP from evolutions', format_number(total_evolutions * 500) ], [ 'Total XP from evolutions with lucky egg', format_number(total_evolutions * 1000) ], ]) table.inner_heading_row_border = False table.justify_columns = {0: 'left', 1: 'right'} print table.table def rename_pokemon(self): already_renamed = 0 renamed = 0 # After logging in, wait a while before starting to rename Pokémon, like a # human player would. self.wait_randomly() for pokemon in self.pokemon: individual_value = pokemon['attack'] + pokemon[ 'defense'] + pokemon['stamina'] iv_percent = pokemon['iv_percent'] if individual_value < 10: individual_value = '0' + str(individual_value) pokedex_number = pokemon['pokedex_number'] pokemon_name = self.pokemon_list[str(pokedex_number)] name = self.config.format name = name.replace('%id', str(pokedex_number)) name = name.replace('%ivsum', str(individual_value)) name = name.replace('%atk', str(pokemon['attack'])) name = name.replace('%def', str(pokemon['defense'])) name = name.replace('%sta', str(pokemon['stamina'])) name = name.replace('%percent', str(iv_percent)) name = name.replace('%cp', str(pokemon['cp'])) name = name.replace('%name', pokemon_name) name = name[:12] if (pokemon['nickname'] == 'NONE' \ or pokemon['nickname'] == pokemon_name \ or (pokemon['nickname'] != name and self.config.overwrite)) \ and iv_percent >= self.config.iv: response = self.api.nickname_pokemon(pokemon_id=pokemon['id'], nickname=name) result = response['responses']['NICKNAME_POKEMON']['result'] if result == 1: print 'Renaming ' + pokemon_name + ' (CP ' + str( pokemon['cp']) + ') to ' + name else: print 'Something went wrong with renaming ' + pokemon_name + ' (CP ' + str( pokemon['cp'] ) + ') to ' + name + '. Error code: ' + str(result) self.wait_randomly() renamed += 1 else: already_renamed += 1 print str(renamed) + ' Pokémon renamed.' print str(already_renamed) + ' Pokémon already renamed.' def clear_pokemon(self): # Reset all Pokémon names to the original name. cleared = 0 # After logging in, wait a while before starting to rename Pokémon, like a # human player would. self.wait_randomly() for pokemon in self.pokemon: pokedex_number = pokemon['pokedex_number'] name_original = self.pokemon_list[str(pokedex_number)] if pokemon['nickname'] != 'NONE' and pokemon[ 'nickname'] != name_original: response = self.api.nickname_pokemon(pokemon_id=pokemon['id'], nickname=name_original) result = response['responses']['NICKNAME_POKEMON']['result'] if result == 1: print 'Reset ' + pokemon[ 'nickname'] + ' to ' + name_original else: print 'Something went wrong with resetting ' + pokemon[ 'nickname'] + ' to ' + name_original + '. Error code: ' + str( result) self.wait_randomly() cleared += 1 print 'Cleared ' + str(cleared) + ' nicknames' def transfer_pokemon(self): pokemon_list = [ p for p in self.pokemon if p.get('transfer', False) and not p['is_favorite'] and not p['is_shiny'] and not p['has_costume'] ] total_transfers = len(pokemon_list) transfers_completed = 0 if not pokemon_list: print u'No Pokémon scheduled to transfer.' return table_data = [[u'Pokémon', 'CP', 'IV %', 'ATK', 'DEF', 'STA']] print 'About to transfer %d Pokémon…' % total_transfers transfer_list = [] for pokemon in pokemon_list: # Remove the Pokémon from the list, so that we don’t try to rename # it later. self.pokemon.remove(pokemon) pokedex_number = pokemon['pokedex_number'] pokemon_name = self.pokemon_list[str(pokedex_number)] table_data.append([ pokemon_name, pokemon['cp'], '{0:.0f}%'.format(pokemon['iv_percent']), pokemon['attack'], pokemon['defense'], pokemon['stamina'] ]) transfer_list.append(pokemon['id']) table = SingleTable(table_data) table.justify_columns = { 0: 'left', 1: 'right', 2: 'right', 3: 'right', 4: 'right', 5: 'right', 6: 'left' } print u'The following Pokémon are about to be transferred:' print table.table # After logging in, wait a while before starting to rename Pokémon, like a # human player would. self.config.min_delay = total_transfers * 2 self.config.max_delay = total_transfers * 4 self.wait_randomly() response = self.api.release_pokemon(pokemon_ids=transfer_list) try: result = response['responses']['RELEASE_POKEMON']['result'] except KeyError: print 'Failed:' print response status = 'error' pass else: if result == 1: status = 'success' print 'Transfer successful.' else: status = 'error' print 'Transfer failed. Error code: %s' % str(result)
def perform_scout(p): global api, last_scout_timestamp, encounter_cache if not args.scout: return {"msg": "Scouting disabled"} if len(accounts) == 0: return {"msg": "No scout account configured."} pokemon_name = get_pokemon_name(p.pokemon_id) # Check cache once in a non-blocking way if p.encounter_id in encounter_cache: result = encounter_cache[p.encounter_id] log.info(u"Cached scout-result: level {} {} with CP {}.".format( result["level"], pokemon_name, result["cp"])) return result step_location = [] scoutLock.acquire() try: now = time.time() account = None wait_secs = args.scout_cooldown_delay for acc in accounts: if account is None: last_scout_timestamp = acc["last_used"] if acc['in_use']: continue elif last_scout_timestamp is not None \ and now < last_scout_timestamp + args.scout_cooldown_delay: wait_secs = min( last_scout_timestamp + args.scout_cooldown_delay - now, wait_secs) else: account = acc account["last_used"] = now acc['in_use'] = True finally: scoutLock.release() if account is None: log.info( "Waiting {} more seconds before next scout use.".format(wait_secs)) # time.sleep(wait_secs) request_result = {} request_result["wait"] = wait_secs else: # Check cache again after mutually exclusive access if p.encounter_id in encounter_cache: result = encounter_cache[p.encounter_id] log.info(u"Cached scout-result: level {} {} with CP {}.".format( result["level"], pokemon_name, result["cp"])) return result # Delay scouting if last_scout_timestamp is not None and now < last_scout_timestamp + args.scout_cooldown_delay: wait_secs = last_scout_timestamp + args.scout_cooldown_delay - now log.info("Waiting {} more seconds before next scout use.".format( wait_secs)) # time.sleep(wait_secs) request_result = {} request_result["wait"] = wait_secs else: log.info(u"Scouting a {} at {}, {}".format(pokemon_name, p.latitude, p.longitude)) step_location = jitter_location([p.latitude, p.longitude, 42]) if api is None: # instantiate pgoapi api = PGoApi() api.set_position(*step_location) proxy_url = False if args.proxy is not None and len(args.proxy) > 0: proxy_num, proxy_url = get_new_proxy(args) if proxy_url: log.debug('Using proxy %s', proxy_url) api.set_proxy({'http': proxy_url, 'https': proxy_url}) try: check_login(args, account, api, None, proxy_url) if args.hash_key: key = key_scheduler.next() log.debug('Using key {} for this scout use.'.format(key)) api.activate_hash_server(key) request_result = encounter_request( long(b64decode(p.encounter_id)), p.spawnpoint_id, p.latitude, p.longitude) # Update last timestamp account['last_used'] = time.time() except TooManyLoginAttempts: log.error("{} failed to login, going to sleep for 600 seconds". format(account['username'])) account['last_used'] = time.mktime( (datetime.datetime.now() + datetime.timedelta(seconds=600)).timetuple()) account['in_use'] = False return {"msg": "Scout can't login"} finally: account['in_use'] = False return parse_scout_result(request_result, p.encounter_id, pokemon_name, step_location, account)
class Renamer(object): """Main renamer class object""" def __init__(self): self.pokemon = [] self.api = None self.config = None self.position = None self.pokemon_list = None def init_config(self): """Gets configuration from command line arguments""" parser = argparse.ArgumentParser() parser.add_argument("-a", "--auth_service") parser.add_argument("-u", "--username") parser.add_argument("-p", "--password") parser.add_argument("-l", "--location") parser.add_argument('--hash-key', required=True) parser.add_argument("--clear", action='store_true', default=False) parser.add_argument("-lo", "--list_only", action='store_true', default=False) parser.add_argument("--format", default="%ivsum, %atk/%def/%sta") parser.add_argument("-L", "--locale", default="en") parser.add_argument("--min_delay", type=int, default=10) parser.add_argument("--max_delay", type=int, default=20) parser.add_argument("--iv", type=int, default=0) parser.add_argument("--cp", type=int, default=0) self.config = parser.parse_args() self.config.overwrite = True #self.config.skip_favorite = True #self.config.only_favorite = False def start(self): """Start renamer""" print "Start renamer" self.init_config() try: self.pokemon_list = json.load(open('locales/pokemon.' + self.config.locale + '.json')) except IOError: print "The selected language is currently not supported" exit(0) self.setup_api() self.get_pokemon() self.print_pokemon() if self.config.list_only: pass elif self.config.clear: self.clear_pokemon() else: self.rename_pokemon() def setup_api(self): """Prepare and sign in to API""" self.api = PGoApi() self.api.activate_hash_server(self.config.hash_key) self.get_location() if not self.api.login(self.config.auth_service, str(self.config.username), str(self.config.password), self.position[0], self.position[1], self.position[2]): print "Login error" exit(0) print "Signed in" def get_pokemon(self): """Fetch Pokemon from server and store in array""" print "Getting Pokemon list" self.random_sleep() response_dict = self.api.get_inventory() self.pokemon = [] inventory_items = (response_dict .get('responses', {}) .get('GET_INVENTORY', {}) .get('inventory_delta', {}) .get('inventory_items', {})) for item in inventory_items: try: reduce(dict.__getitem__, ["inventory_item_data", "pokemon_data"], item) except KeyError: pass else: try: pokemon = item['inventory_item_data']['pokemon_data'] pid = pokemon['id'] num = pokemon['pokemon_id'] name = self.pokemon_list[str(num)] attack = pokemon.get('individual_attack', 0) defense = pokemon.get('individual_defense', 0) stamina = pokemon.get('individual_stamina', 0) iv_percent = (float(attack + defense + stamina) / 45.0) * 100.0 nickname = pokemon.get('nickname', 'NONE') combat_power = pokemon.get('cp', 0) self.pokemon.append({ 'id': pid, 'num': num, 'name': name, 'nickname': nickname, 'cp': combat_power, 'attack': attack, 'defense': defense, 'stamina': stamina, 'iv_percent': iv_percent, }) except KeyError: pass # Sort the way the in-game `Number` option would, i.e. by Pokedex number # in ascending order and then by CP in descending order. self.pokemon.sort(key=lambda k: (k['num'], -k['cp'])) def print_pokemon(self): """Print Pokemon and their stats""" sorted_mons = sorted(self.pokemon, key=lambda k: (k['num'], -k['iv_percent'])) groups = groupby(sorted_mons, key=lambda k: k['num']) table_data = [ ['Pokemon', 'CP', 'IV %', 'ATK', 'DEF', 'STA'] ] for key, group in groups: group = list(group) pokemon_name = self.pokemon_list[str(key)].replace(u'\N{MALE SIGN}', '(M)').replace(u'\N{FEMALE SIGN}', '(F)') best_iv_pokemon = max(group, key=lambda k: k['iv_percent']) best_iv_pokemon['best_iv'] = True for pokemon in group: row_data = [ pokemon_name, pokemon['cp'], "{0:.0f}%".format(pokemon['iv_percent']), pokemon['attack'], pokemon['defense'], pokemon['stamina'] ] table_data.append(row_data) # if pokemon.get('best_iv', False) and len(group) > 1: # row_data = [Colors.OKGREEN + str(cell) + Colors.ENDC for cell in row_data] table = AsciiTable(table_data) table.justify_columns[0] = 'left' table.justify_columns[1] = 'right' table.justify_columns[2] = 'right' table.justify_columns[3] = 'right' table.justify_columns[4] = 'right' table.justify_columns[5] = 'right' print table.table def rename_pokemon(self): """Renames Pokemon according to configuration""" already_renamed = 0 renamed = 0 for pokemon in self.pokemon: individual_value = pokemon['attack'] + pokemon['defense'] + pokemon['stamina'] iv_percent = int(pokemon['iv_percent']) if individual_value < 10: individual_value = "0" + str(individual_value) num = pokemon['num'] pokemon_name = self.pokemon_list[str(num)] name = self.config.format name = name.replace("%id", str(num)) name = name.replace("%ivsum", str(individual_value)) name = name.replace("%atk", str(pokemon['attack'])) name = name.replace("%def", str(pokemon['defense'])) name = name.replace("%sta", str(pokemon['stamina'])) name = name.replace("%percent", str(iv_percent)) name = name.replace("%cp", str(pokemon['cp'])) name = name.replace("%name", pokemon_name) name = name[:12] if (pokemon['nickname'] == "NONE" \ or pokemon['nickname'] == pokemon_name \ or (pokemon['nickname'] != name and self.config.overwrite)) \ and iv_percent >= self.config.iv \ and pokemon['cp'] >= self.config.cp: self.random_sleep() response = self.api.nickname_pokemon(pokemon_id=pokemon['id'], nickname=name) result = response['responses']['NICKNAME_POKEMON']['result'] if result == 1: print "Renaming " + pokemon_name.replace(u'\N{MALE SIGN}', '(M)').replace(u'\N{FEMALE SIGN}', '(F)') + " (CP " + str(pokemon['cp']) + ") to " + name else: print "Something went wrong with renaming " + pokemon_name.replace(u'\N{MALE SIGN}', '(M)').replace(u'\N{FEMALE SIGN}', '(F)') + " (CP " + str(pokemon['cp']) + ") to " + name + ". Error code: " + str(result) renamed += 1 else: already_renamed += 1 print str(renamed) + " Pokemon renamed." print str(already_renamed) + " Pokemon already renamed." def clear_pokemon(self): """Resets all Pokemon names to the original""" cleared = 0 for pokemon in self.pokemon: num = int(pokemon['num']) name_original = self.pokemon_list[str(num)] if pokemon['nickname'] != "NONE" and pokemon['nickname'] != name_original: response = self.api.nickname_pokemon(pokemon_id=pokemon['id'], nickname=name_original) result = response['responses']['NICKNAME_POKEMON']['result'] if result == 1: print "Resetted " + pokemon['nickname'] + " to " + name_original.replace(u'\N{MALE SIGN}', '(M)').replace(u'\N{FEMALE SIGN}', '(F)') else: print "Something went wrong with resetting " + pokemon['nickname'] + " to " + name_original.replace(u'\N{MALE SIGN}', '(M)').replace(u'\N{FEMALE SIGN}', '(F)') + ". Error code: " + str(result) random_delay = randint(self.config.min_delay, self.config.max_delay) time.sleep(random_delay) cleared += 1 print "Cleared " + str(cleared) + " names" def get_elevation_for_position(self): try: url = 'https://maps.googleapis.com/maps/api/elevation/json?locations={},{}'.format( str(self.position[0]), str(self.position[1])) altitude = requests.get(url).json()[u'results'][0][u'elevation'] + random.uniform(0.9, 1.7) print "Local altitude is: {0}m".format(altitude) self.position = (self.position[0], self.position[1], altitude) except requests.exceptions.RequestException: print "Unable to retrieve altitude from Google APIs; setting to 0" def get_location(self): # use lat/lng directly if matches such a pattern prog = re.compile("^(\-?\d+\.\d+),?\s?(\-?\d+\.\d+)$") res = prog.match(self.config.location) if res: print "Using coordinates from CLI directly" self.position = (float(res.group(1)), float(res.group(2)), 0) else: print "Looking up coordinates in API" self.position = util.get_pos_by_name(self.config.location) self.get_elevation_for_position() def random_sleep(self): random_delay = randint(self.config.min_delay, self.config.max_delay) print "Will sleep for " + str(random_delay) + " seconds to slow down and not stress out the api..." time.sleep(random_delay)
class Renamer(object): def __init__(self): self.pokemon = [] self.api = None self.config = None self.pokemon_list = None def init_config(self): parser = argparse.ArgumentParser() parser.add_argument('-a', '--auth-service') parser.add_argument('-u', '--username') parser.add_argument('-p', '--password') parser.add_argument('--hash-key', required=True) parser.add_argument('--clear', action='store_true', default=False) parser.add_argument('--rename', action='store_true', default=False) parser.add_argument('--format', default='%percent% %atk %def %sta') parser.add_argument('--transfer', action='store_true', default=False) parser.add_argument('--locale', default='en') parser.add_argument('--location', required=True) parser.add_argument('--min-delay', type=int, default=10) parser.add_argument('--max-delay', type=int, default=20) parser.add_argument('--iv', type=int, default=75) self.config = parser.parse_args() self.config.overwrite = True def start(self): self.init_config() try: self.pokemon_list = json.load(open('locales/pokemon.' + self.config.locale + '.json')) except IOError: print 'The selected language is currently not supported' exit(0) self.setup_api() self.get_pokemon() self.print_pokemon() if self.config.clear: self.clear_pokemon() elif self.config.rename: self.rename_pokemon() elif self.config.transfer: self.transfer_pokemon() def get_elevation_for_position(self): try: url = 'https://maps.googleapis.com/maps/api/elevation/json?locations={},{}'.format( str(self.position[0]), str(self.position[1])) altitude = requests.get(url).json()[u'results'][0][u'elevation'] + random.uniform(0.9, 1.7) print "Local altitude is: {0}m".format(altitude) self.position = (self.position[0], self.position[1], altitude) except requests.exceptions.RequestException: print "Unable to retrieve altitude from Google APIs; setting to 0" def get_location(self): # use lat/lng directly if matches such a pattern prog = re.compile("^(\-?\d+\.\d+),?\s?(\-?\d+\.\d+)$") res = prog.match(self.config.location) if res: print "Using coordinates from CLI directly..." self.position = (float(res.group(1)), float(res.group(2)), 0) else: print "Looking up coordinates using API..." self.position = util.get_pos_by_name(self.config.location) self.get_elevation_for_position() def setup_api(self): self.api = PGoApi() self.api.activate_hash_server(self.config.hash_key) self.get_location() print u'Signing in…' if not self.api.login( self.config.auth_service, self.config.username, self.config.password, self.position[0], # latitude self.position[1], # longitude self.position[2] # altitude ): print 'Login error' exit(0) def wait_randomly(self): random_delay = uniform(self.config.min_delay, self.config.max_delay) print u'Waiting %.3f seconds…' % random_delay time.sleep(random_delay) def get_pokemon(self): print u'Getting Pokémon list…' response_dict = self.api.get_inventory() self.pokemon = [] self.candy = { i: 0 for i in range(1, 251 + 1) } inventory_items = (response_dict .get('responses', {}) .get('GET_INVENTORY', {}) .get('inventory_delta', {}) .get('inventory_items', {})) for item in inventory_items: try: reduce(dict.__getitem__, ['inventory_item_data', 'candy'], item) except KeyError: pass else: candy = item['inventory_item_data']['candy'] pokedex_number = candy['family_id'] self.candy[pokedex_number] = candy.get('candy', 0) for item in inventory_items: try: reduce(dict.__getitem__, ['inventory_item_data', 'pokemon_data'], item) except KeyError: pass else: try: pokemon = item['inventory_item_data']['pokemon_data'] pid = pokemon['id'] pokedex_number = pokemon['pokemon_id'] name = self.pokemon_list[str(pokedex_number)] attack = pokemon.get('individual_attack', 0) defense = pokemon.get('individual_defense', 0) stamina = pokemon.get('individual_stamina', 0) iv_percent = int(round((attack + defense + stamina) / 45.0 * 100.0)) is_favorite = pokemon.get('favorite', 0) > 0 nickname = pokemon.get('nickname', 'NONE') combat_power = pokemon.get('cp', 0) cpm = pokemon['cp_multiplier'] + pokemon.get('additional_cp_multiplier', 0) level = cpm2level(cpm) # https://github.com/AeonLucid/POGOProtos/blob/master/src/POGOProtos/Enums/Gender.proto GENDERS = { 0: '', # unset 1: u'♂', # male 2: u'♀', # female 3: '' # genderless } gender = GENDERS.get( pokemon.get('pokemon_display', {}).get('gender', 0), '' ) # https://github.com/AeonLucid/POGOProtos/blob/master/src/POGOProtos/Enums/Costume.proto has_costume = pokemon.get('pokemon_display', {}).get('costume', 0) > 0 is_shiny = pokemon.get('pokemon_display', {}).get('shiny', 0) > 0 self.pokemon.append({ 'id': pid, 'pokedex_number': pokedex_number, 'name': name, 'nickname': nickname, 'level': level, 'cp': combat_power, 'attack': attack, 'defense': defense, 'stamina': stamina, 'iv_percent': iv_percent, 'is_favorite': is_favorite, 'move_1': pokemon['move_1'], 'move_2': pokemon['move_2'], 'gender': gender, 'has_costume': has_costume, 'is_shiny': is_shiny, }) except KeyError: pass # Sort the way the in-game `Number` option would, i.e. by Pokedex number # in ascending order and then by CP in descending order. self.pokemon.sort(key=lambda k: (k['pokedex_number'], -k['cp'])) def print_pokemon(self): sorted_mons = sorted(self.pokemon, key=lambda k: (k['pokedex_number'], -k['iv_percent'])) groups = groupby(sorted_mons, key=lambda k: k['pokedex_number']) table_data = [ [u'Pokémon', 'Level', 'CP', 'IV %', 'ATK', 'DEF', 'STA', 'Candy', 'Recommendation'] ] total_evolutions = 0 total_transfers = 0 print u'%d Pokémon found.' % len(sorted_mons) for key, group in groups: group = list(group) pokemon_name = self.pokemon_list[str(key)] best_iv_pokemon = max(group, key=lambda k: k['iv_percent']) best_iv_pokemon['best_iv'] = True candy_count=self.candy[key] result = pogotransfercalc.calculate( pokemon_count=len(group), candy_count=candy_count, pokedex_number=key) evolutions = result['evolutions'] total_evolutions += evolutions if evolutions: for pokemon in group[:evolutions]: pokemon['message'] = 'evolve' transfers = result['transfers'] transfer_count = 0 if transfers: for pokemon in reversed(group[evolutions:]): if pokemon['is_favorite']: pokemon['message'] = u'keep (★)' continue if pokemon['has_costume']: pokemon['message'] = u'keep (costume)' continue if pokemon['is_shiny']: pokemon['message'] = u'keep (shiny)' continue if pokemon['iv_percent'] < self.config.iv: pokemon['message'] = 'transfer' pokemon['transfer'] = True transfer_count += 1 total_transfers += 1 if transfer_count == transfers: break continue for pokemon in group: if pokemon['iv_percent'] >= self.config.iv: iv_msg = u'(IV ≥ %d%%)' % self.config.iv if 'message' in pokemon: pokemon['message'] += ' %s' % iv_msg else: pokemon['message'] = 'keep %s' % iv_msg row_data = [ pokemon_name + pokemon['gender'] + (u'✨' if pokemon['is_shiny'] else '') + (u'☃' if pokemon['has_costume'] else '') + (u'★' if pokemon['is_favorite'] else ''), pokemon['level'], pokemon['cp'], '{0:.0f}%'.format(pokemon['iv_percent']), pokemon['attack'], pokemon['defense'], pokemon['stamina'], candy_count, pokemon.get('message', '') ] table_data.append(row_data) table = SingleTable(table_data) table.justify_columns = { 0: 'left', 1: 'right', 2: 'right', 3: 'right', 4: 'right', 5: 'right', 6: 'right', 7: 'right' } print table.table table = SingleTable([ ['Total suggested transfers', format_number(total_transfers)], ['Total evolutions', format_number(total_evolutions)], ['Total XP from evolutions', format_number(total_evolutions * 500)], ['Total XP from evolutions with lucky egg', format_number(total_evolutions * 1000)], ]) table.inner_heading_row_border = False table.justify_columns = { 0: 'left', 1: 'right' } print table.table def rename_pokemon(self): already_renamed = 0 renamed = 0 # After logging in, wait a while before starting to rename Pokémon, like a # human player would. self.wait_randomly() for pokemon in self.pokemon: individual_value = pokemon['attack'] + pokemon['defense'] + pokemon['stamina'] iv_percent = pokemon['iv_percent'] if individual_value < 10: individual_value = '0' + str(individual_value) pokedex_number = pokemon['pokedex_number'] pokemon_name = self.pokemon_list[str(pokedex_number)] name = self.config.format name = name.replace('%id', str(pokedex_number)) name = name.replace('%ivsum', str(individual_value)) name = name.replace('%atk', str(pokemon['attack'])) name = name.replace('%def', str(pokemon['defense'])) name = name.replace('%sta', str(pokemon['stamina'])) name = name.replace('%percent', str(iv_percent)) name = name.replace('%cp', str(pokemon['cp'])) name = name.replace('%name', pokemon_name) name = name[:12] if (pokemon['nickname'] == 'NONE' \ or pokemon['nickname'] == pokemon_name \ or (pokemon['nickname'] != name and self.config.overwrite)) \ and iv_percent >= self.config.iv: response = self.api.nickname_pokemon(pokemon_id=pokemon['id'], nickname=name) result = response['responses']['NICKNAME_POKEMON']['result'] if result == 1: print 'Renaming ' + pokemon_name + ' (CP ' + str(pokemon['cp']) + ') to ' + name else: print 'Something went wrong with renaming ' + pokemon_name + ' (CP ' + str(pokemon['cp']) + ') to ' + name + '. Error code: ' + str(result) self.wait_randomly() renamed += 1 else: already_renamed += 1 print str(renamed) + ' Pokémon renamed.' print str(already_renamed) + ' Pokémon already renamed.' def clear_pokemon(self): # Reset all Pokémon names to the original name. cleared = 0 # After logging in, wait a while before starting to rename Pokémon, like a # human player would. self.wait_randomly() for pokemon in self.pokemon: pokedex_number = pokemon['pokedex_number'] name_original = self.pokemon_list[str(pokedex_number)] if pokemon['nickname'] != 'NONE' and pokemon['nickname'] != name_original: response = self.api.nickname_pokemon(pokemon_id=pokemon['id'], nickname=name_original) result = response['responses']['NICKNAME_POKEMON']['result'] if result == 1: print 'Reset ' + pokemon['nickname'] + ' to ' + name_original else: print 'Something went wrong with resetting ' + pokemon['nickname'] + ' to ' + name_original + '. Error code: ' + str(result) self.wait_randomly() cleared += 1 print 'Cleared ' + str(cleared) + ' nicknames' def transfer_pokemon(self): pokemon_list = [p for p in self.pokemon if p.get('transfer', False) and not p['is_favorite'] and not p['is_shiny'] and not p['has_costume']] total_transfers = len(pokemon_list) transfers_completed = 0 if not pokemon_list: print u'No Pokémon scheduled to transfer.' return table_data = [ [u'Pokémon', 'CP', 'IV %', 'ATK', 'DEF', 'STA'] ] print 'About to transfer %d Pokémon…' % total_transfers transfer_list = [] for pokemon in pokemon_list: # Remove the Pokémon from the list, so that we don’t try to rename # it later. self.pokemon.remove(pokemon) pokedex_number = pokemon['pokedex_number'] pokemon_name = self.pokemon_list[str(pokedex_number)] table_data.append([ pokemon_name, pokemon['cp'], '{0:.0f}%'.format(pokemon['iv_percent']), pokemon['attack'], pokemon['defense'], pokemon['stamina'] ]) transfer_list.append(pokemon['id']) table = SingleTable(table_data) table.justify_columns = { 0: 'left', 1: 'right', 2: 'right', 3: 'right', 4: 'right', 5: 'right', 6: 'left' } print u'The following Pokémon are about to be transferred:' print table.table # After logging in, wait a while before starting to rename Pokémon, like a # human player would. self.config.min_delay = total_transfers * 2 self.config.max_delay = total_transfers * 4 self.wait_randomly() response = self.api.release_pokemon(pokemon_ids=transfer_list) try: result = response['responses']['RELEASE_POKEMON']['result'] except KeyError: print 'Failed:' print response status = 'error' pass else: if result == 1: status = 'success' print 'Transfer successful.' else: status = 'error' print 'Transfer failed. Error code: %s' % str(result)
class Worker: """Single worker walking on the map""" network_executor = ThreadPoolExecutor(config.NETWORK_THREADS) download_hash = "11dcdeb848ed224924f8a8e14d94d620c8966d44" g = {'seen': 0, 'captchas': 0} db_processor = DatabaseProcessor() spawns = db_processor.spawns accounts = load_accounts() if config.CACHE_CELLS: cell_ids = load_pickle('cells') or {} loop = get_event_loop() login_semaphore = Semaphore(config.SIMULTANEOUS_LOGINS) if config.CAPTCHA_KEY: session = requests.Session() proxies = None proxy = None if config.PROXIES: if len(config.PROXIES) == 1: proxy = config.PROXIES.pop() else: proxies = config.PROXIES.copy() if config.NOTIFY: notifier = Notifier(spawns) g['sent'] = 0 def __init__(self, worker_no): self.worker_no = worker_no self.logger = getLogger('worker-{}'.format(worker_no)) # account information try: self.account = self.extra_queue.get_nowait() except Empty as e: raise ValueError( "You don't have enough accounts for the number of workers specified in GRID." ) from e self.username = self.account['username'] self.location = self.account.get('location', get_start_coords(worker_no)) self.inventory_timestamp = self.account.get('inventory_timestamp') # last time of any request self.last_request = self.account.get('time', 0) # last time of a request that requires user interaction in the game self.last_action = self.last_request # last time of a GetMapObjects request self.last_gmo = self.last_request self.items = self.account.get('items', {}) self.num_captchas = 0 self.eggs = {} self.unused_incubators = [] # API setup if self.proxies: self.new_proxy(set_api=False) self.initialize_api() # State variables self.busy = BusyLock() self.killed = False # Other variables self.after_spawn = None self.speed = 0 self.account_start = None self.total_seen = 0 self.error_code = 'INIT' self.item_capacity = 350 self.visits = 0 self.pokestops = config.SPIN_POKESTOPS self.next_spin = 0 def initialize_api(self): device_info = get_device_info(self.account) self.logged_in = False self.ever_authenticated = False self.empty_visits = 0 self.account_seen = 0 self.api = PGoApi(device_info=device_info) if config.HASH_KEY: self.api.activate_hash_server(config.HASH_KEY) self.api.set_position(*self.location) if self.proxy: self.api.set_proxy({'http': self.proxy, 'https': self.proxy}) self.api.set_logger(self.logger) if self.account.get('provider') == 'ptc' and self.account.get( 'refresh'): self.api._auth_provider = AuthPtc( username=self.username, password=self.account['password'], timeout=config.LOGIN_TIMEOUT) self.api._auth_provider.set_refresh_token( self.account.get('refresh')) self.api._auth_provider._access_token = self.account.get('auth') self.api._auth_provider._access_token_expiry = self.account.get( 'expiry') if self.api._auth_provider.check_access_token(): self.api._auth_provider._login = True self.logged_in = True self.ever_authenticated = True def new_proxy(self, set_api=True): self.proxy = self.proxies.pop() if not self.proxies: self.proxies.update(config.PROXIES) if set_api: self.api.set_proxy({'http': self.proxy, 'https': self.proxy}) def swap_circuit(self, reason=''): time_passed = monotonic() - CIRCUIT_TIME[self.proxy] if time_passed > 180: socket = config.CONTROL_SOCKS[self.proxy] with Controller.from_socket_file(path=socket) as controller: controller.authenticate() controller.signal(Signal.NEWNYM) CIRCUIT_TIME[self.proxy] = monotonic() CIRCUIT_FAILURES[self.proxy] = 0 self.logger.warning('Changed circuit on {p} due to {r}.'.format( p=self.proxy, r=reason)) else: self.logger.info('Skipped changing circuit on {p} because it was ' 'changed {s} seconds ago.'.format(p=self.proxy, s=time_passed)) async def login(self): """Logs worker in and prepares for scanning""" self.logger.info('Trying to log in') self.error_code = '^' async with self.login_semaphore: if self.killed: return False self.error_code = 'LOGIN' for attempt in range(-1, config.MAX_RETRIES): try: await self.loop.run_in_executor( self.network_executor, partial(self.api.set_authentication, username=self.username, password=self.account['password'], provider=self.account.get('provider', 'ptc'), timeout=config.LOGIN_TIMEOUT)) except ex.AuthTimeoutException: if attempt >= config.MAX_RETRIES - 1: raise else: self.logger.warning('Login attempt timed out.') else: break if not self.ever_authenticated: if config.APP_SIMULATION: await self.app_simulation_login() else: # do one startup request instead of the whole login flow # will receive the full inventory and the download_hash request = self.api.create_request() request.download_remote_config_version(platform=1, app_version=5301) await self.call(request, stamp=False, buddy=False, settings=True, dl_hash=False) await random_sleep(.1, .462) self.ever_authenticated = True self.logged_in = True self.error_code = None self.account_start = time() return True async def app_simulation_login(self): self.error_code = 'APP SIMULATION' self.logger.info('Starting RPC login sequence (iOS app simulation)') # empty request 1 request = self.api.create_request() await self.call(request, chain=False) await random_sleep(0.3, 0.4) # empty request 2 request = self.api.create_request() await self.call(request, chain=False) await random_sleep(.3, .4) # request 1: get_player request = self.api.create_request() request.get_player(player_locale=config.PLAYER_LOCALE) responses = await self.call(request, chain=False) get_player = responses.get('GET_PLAYER', {}) tutorial_state = get_player.get('player_data', {}).get('tutorial_state', []) self.item_capacity = get_player.get('player_data', {}).get('max_item_storage', 350) if get_player.get('banned', False): raise ex.BannedAccountException await random_sleep(.7, 1.2) version = 5301 # request 2: download_remote_config_version request = self.api.create_request() request.download_remote_config_version(platform=1, app_version=version) responses = await self.call(request, stamp=False, buddy=False, settings=True, dl_hash=False) inventory_items = responses.get('GET_INVENTORY', {}).get('inventory_delta', {}).get('inventory_items', []) player_level = None for item in inventory_items: player_stats = item.get('inventory_item_data', {}).get('player_stats', {}) if player_stats: player_level = player_stats.get('level') break await random_sleep(.78, .95) # request 3: get_asset_digest request = self.api.create_request() request.get_asset_digest(platform=1, app_version=version) await self.call(request, buddy=False, settings=True) await random_sleep(.9, 3.4) if (config.COMPLETE_TUTORIAL and tutorial_state is not None and not all(x in tutorial_state for x in (0, 1, 3, 4, 7))): self.logger.warning('Starting tutorial') await self.complete_tutorial(tutorial_state) else: # request 4: get_player_profile request = self.api.create_request() request.get_player_profile() await self.call(request, settings=True) await random_sleep(.3, .5) if player_level: # request 5: level_up_rewards request = self.api.create_request() request.level_up_rewards(level=player_level) await self.call(request, settings=True) await random_sleep(.45, .7) else: self.logger.warning('No player level') # request 6: register_background_device request = self.api.create_request() request.register_background_device(device_type='apple_watch') await self.call(request, action=0.1) self.logger.info('Finished RPC login sequence (iOS app simulation)') self.error_code = None return True async def complete_tutorial(self, tutorial_state): self.error_code = 'TUTORIAL' if 0 not in tutorial_state: await random_sleep(1, 5) request = self.api.create_request() request.mark_tutorial_complete(tutorials_completed=0) await self.call(request, buddy=False) if 1 not in tutorial_state: await random_sleep(5, 12) request = self.api.create_request() request.set_avatar( player_avatar={ 'hair': randint(1, 5), 'shirt': randint(1, 3), 'pants': randint(1, 2), 'shoes': randint(1, 6), 'gender': randint(0, 1), 'eyes': randint(1, 4), 'backpack': randint(1, 5) }) await self.call(request, buddy=False) await random_sleep(.3, .5) request = self.api.create_request() request.mark_tutorial_complete(tutorials_completed=1) await self.call(request, buddy=False, action=1) await random_sleep(.5, .6) request = self.api.create_request() request.get_player_profile() await self.call(request) starter_id = None if 3 not in tutorial_state: await random_sleep(1, 1.5) request = self.api.create_request() request.get_download_urls(asset_id=[ '1a3c2816-65fa-4b97-90eb-0b301c064b7a/1477084786906000', 'aa8f7687-a022-4773-b900-3a8c170e9aea/1477084794890000', 'e89109b0-9a54-40fe-8431-12f7826c8194/1477084802881000' ]) await self.call(request) await random_sleep(1, 1.6) request = self.api.create_request() await self.call(request, chain=False) await random_sleep(6, 13) request = self.api.create_request() starter = choice((1, 4, 7)) request.encounter_tutorial_complete(pokemon_id=starter) await self.call(request, action=1) await random_sleep(.5, .6) request = self.api.create_request() request.get_player(player_locale=config.PLAYER_LOCALE) responses = await self.call(request) inventory = responses.get('GET_INVENTORY', {}).get('inventory_delta', {}).get('inventory_items', []) for item in inventory: pokemon = item.get('inventory_item_data', {}).get('pokemon_data') if pokemon: starter_id = pokemon.get('id') if 4 not in tutorial_state: await random_sleep(5, 12) request = self.api.create_request() request.claim_codename(codename=self.username) await self.call(request, action=1) await random_sleep(1, 1.3) request = self.api.create_request() request.mark_tutorial_complete(tutorials_completed=4) await self.call(request, buddy=False) await sleep(.1) request = self.api.create_request() request.get_player(player_locale=config.PLAYER_LOCALE) await self.call(request) if 7 not in tutorial_state: await random_sleep(4, 10) request = self.api.create_request() request.mark_tutorial_complete(tutorials_completed=7) await self.call(request) if starter_id: await random_sleep(3, 5) request = self.api.create_request() request.set_buddy_pokemon(pokemon_id=starter_id) await self.call(request, action=1) await random_sleep(.8, 1.8) await sleep(.2) return True def update_inventory(self, inventory_items): for thing in inventory_items: obj = thing.get('inventory_item_data', {}) if 'item' in obj: item = obj['item'] item_id = item.get('item_id') self.items[item_id] = item.get('count', 0) elif config.INCUBATE_EGGS: if ('pokemon_data' in obj and obj['pokemon_data'].get('is_egg')): egg = obj['pokemon_data'] egg_id = egg.get('id') self.eggs[egg_id] = egg elif 'egg_incubators' in obj: self.unused_incubators = [] for item in obj['egg_incubators'].get('egg_incubator', []): if 'pokemon_id' in item: continue if item.get('item_id') == 901: self.unused_incubators.append(item) else: self.unused_incubators.insert(0, item) async def call(self, request, chain=True, stamp=True, buddy=True, settings=False, dl_hash=True, action=None): if chain: request.check_challenge() request.get_hatched_eggs() if stamp and self.inventory_timestamp: request.get_inventory( last_timestamp_ms=self.inventory_timestamp) else: request.get_inventory() request.check_awarded_badges() if settings: if dl_hash: request.download_settings(hash=self.download_hash) else: request.download_settings() if buddy: request.get_buddy_walked() try: refresh = HashServer.status.get('period') while HashServer.status.get('remaining') < 5 and time() < refresh: self.error_code = 'HASH WAITING' wait = refresh - time() + 1 await sleep(wait) refresh = HashServer.status.get('period') except TypeError: pass now = time() if action: # wait for the time required, or at least a half-second if self.last_action > now + .5: await sleep(self.last_action - now) else: await sleep(0.5) for _ in range(-1, config.MAX_RETRIES): try: response = await self.loop.run_in_executor( self.network_executor, request.call) if response: break else: raise ex.MalformedResponseException('empty response') except ex.HashingOfflineException: self.logger.warning('Hashing server busy or offline.') self.error_code = 'HASHING OFFLINE' await sleep(7.5) except ex.NianticOfflineException: self.logger.warning('Niantic busy or offline.') self.error_code = 'NIANTIC OFFLINE' await random_sleep() except ex.HashingQuotaExceededException: self.logger.warning('Exceeded your hashing quota, sleeping.') self.error_code = 'QUOTA EXCEEDED' refresh = HashServer.status.get('period') now = time() if refresh: if refresh > now: await sleep(refresh - now + 1) else: await sleep(5) else: await sleep(30) except ex.NianticThrottlingException: self.logger.warning('Server throttling - sleeping for a bit') self.error_code = 'THROTTLE' await random_sleep(11, 22, 12) except (ex.MalformedResponseException, ex.UnexpectedResponseException) as e: self.logger.warning(e) self.error_code = 'MALFORMED RESPONSE' await random_sleep(10, 14, 11) if not response: raise MaxRetriesException self.last_request = time() if action: # pad for time that action would require self.last_action = self.last_request + action responses = response.get('responses') if chain: delta = responses.get('GET_INVENTORY', {}).get('inventory_delta', {}) timestamp = delta.get('new_timestamp_ms') inventory_items = delta.get('inventory_items', []) if inventory_items: self.update_inventory(inventory_items) self.inventory_timestamp = timestamp or self.inventory_timestamp d_hash = responses.get('DOWNLOAD_SETTINGS', {}).get('hash') self.download_hash = d_hash or self.download_hash if self.check_captcha(responses): self.logger.warning( '{} has encountered a CAPTCHA, trying to solve'.format( self.username)) self.g['captchas'] += 1 await self.handle_captcha(responses) return responses def travel_speed(self, point): '''Fast calculation of travel speed to point''' if self.busy.locked(): return None time_diff = max(time() - self.last_request, config.SCAN_DELAY) if time_diff > 60: self.error_code = None distance = get_distance(self.location, point) # conversion from meters/second to miles/hour speed = (distance / time_diff) * 2.236936 return speed async def bootstrap_visit(self, point): for _ in range(0, 3): if await self.visit(point, bootstrap=True): return True self.error_code = '∞' self.simulate_jitter(0.00005) return False async def visit(self, point, bootstrap=False): """Wrapper for self.visit_point - runs it a few times before giving up Also is capable of restarting in case an error occurs. """ visited = False try: altitude = self.spawns.get_altitude(point) altitude = uniform(altitude - 1, altitude + 1) self.location = point + [altitude] self.api.set_position(*self.location) if not self.logged_in: if not await self.login(): return False return await self.visit_point(point, bootstrap=bootstrap) except (ex.AuthException, ex.NotLoggedInException): self.logger.warning('{} is not authenticated.'.format( self.username)) self.error_code = 'NOT AUTHENTICATED' await sleep(1) await self.swap_account(reason='login failed') except CaptchaException: self.error_code = 'CAPTCHA' self.g['captchas'] += 1 await sleep(1) await self.bench_account() except CaptchaSolveException: self.error_code = 'CAPTCHA' await sleep(1) await self.swap_account(reason='solving CAPTCHA failed') except MaxRetriesException: self.logger.warning('Hit the maximum number of attempt retries.') self.error_code = 'MAX RETRIES' except ex.TempHashingBanException: self.error_code = 'HASHING BAN' self.logger.error( 'Temporarily banned from hashing server for using invalid keys.' ) await sleep(185) except ex.BannedAccountException: self.error_code = 'BANNED' self.logger.warning('{} is banned'.format(self.username)) await sleep(1) await self.remove_account() except ex.NianticIPBannedException: self.error_code = 'IP BANNED' if config.CONTROL_SOCKS: self.swap_circuit('IP ban') await random_sleep(minimum=25, maximum=35) elif self.proxies: self.logger.warning('Swapping out {} due to IP ban.'.format( self.proxy)) proxy = self.proxy while proxy == self.proxy: self.new_proxy() await random_sleep(minimum=12, maximum=20) else: self.logger.error('IP banned.') await sleep(150) except ex.HashServerException as e: self.logger.warning(e) self.error_code = 'HASHING ERROR' except ex.PgoapiError as e: self.logger.exception('pgoapi error') self.error_code = 'PGOAPI ERROR' except Exception: self.logger.exception('A wild exception appeared!') self.error_code = 'EXCEPTION' await sleep(1) return False async def visit_point(self, point, bootstrap=False): if bootstrap: self.error_code = '∞' else: self.error_code = '!' latitude, longitude = point self.logger.info('Visiting {0[0]:.4f},{0[1]:.4f}'.format(point)) start = time() rounded = round_coords(point, precision=4) if config.CACHE_CELLS and rounded in self.cell_ids: cell_ids = list(self.cell_ids[rounded]) else: cell_ids = get_cell_ids(*rounded, radius=500) if config.CACHE_CELLS: try: self.cell_ids[rounded] = array('L', cell_ids) except OverflowError: self.cell_ids[rounded] = tuple(cell_ids) since_timestamp_ms = [0] * len(cell_ids) request = self.api.create_request() request.get_map_objects(cell_id=cell_ids, since_timestamp_ms=since_timestamp_ms, latitude=latitude, longitude=longitude) diff = self.last_gmo + config.SCAN_DELAY - time() if diff > 0: await sleep(diff + .25) responses = await self.call(request) self.last_gmo = time() map_objects = responses.get('GET_MAP_OBJECTS', {}) sent = False pokemon_seen = 0 forts_seen = 0 points_seen = 0 if map_objects.get('status') == 3: raise ex.BannedAccountException('GMO code 3') elif map_objects.get('status') != 1: self.logger.warning('MapObjects code: {}'.format( map_objects.get('status'))) self.empty_visits += 1 if self.empty_visits > 3: reason = '{} empty visits'.format(self.empty_visits) await self.swap_account(reason) raise ex.UnexpectedResponseException time_of_day = map_objects.get('time_of_day', 0) if config.ITEM_LIMITS and self.bag_full(): await self.clean_bag() for map_cell in map_objects['map_cells']: request_time_ms = map_cell['current_timestamp_ms'] for pokemon in map_cell.get('wild_pokemons', []): pokemon_seen += 1 # Accurate times only provided in the last 90 seconds invalid_tth = (pokemon['time_till_hidden_ms'] < 0 or pokemon['time_till_hidden_ms'] > 90000) normalized = self.normalize_pokemon(pokemon, request_time_ms) if invalid_tth: despawn_time = self.spawns.get_despawn_time( normalized['spawn_id'], normalized['seen']) if despawn_time: normalized['expire_timestamp'] = despawn_time normalized['time_till_hidden_ms'] = ( despawn_time * 1000) - request_time_ms normalized['valid'] = 'fixed' else: normalized['valid'] = False else: normalized['valid'] = True if config.NOTIFY and self.notifier.eligible(normalized): if config.ENCOUNTER: normalized.update(await self.encounter(pokemon)) sent = self.notify(normalized, time_of_day) if (normalized not in SIGHTING_CACHE and normalized not in MYSTERY_CACHE): self.account_seen += 1 if (config.ENCOUNTER == 'all' and 'individual_attack' not in normalized): try: normalized.update(await self.encounter(pokemon)) except Exception: self.logger.exception( 'Exception during encounter.') self.db_processor.add(normalized) for fort in map_cell.get('forts', []): if not fort.get('enabled'): continue forts_seen += 1 if fort.get('type') == 1: # pokestops if 'lure_info' in fort: norm = self.normalize_lured(fort, request_time_ms) pokemon_seen += 1 if norm not in SIGHTING_CACHE: self.account_seen += 1 self.db_processor.add(norm) pokestop = self.normalize_pokestop(fort) self.db_processor.add(pokestop) if self.pokestops and not self.bag_full( ) and time() > self.next_spin: cooldown = fort.get('cooldown_complete_timestamp_ms') if not cooldown or time() > cooldown / 1000: await self.spin_pokestop(pokestop) else: self.db_processor.add(self.normalize_gym(fort)) if config.MORE_POINTS or bootstrap: for point in map_cell.get('spawn_points', []): points_seen += 1 try: p = (point['latitude'], point['longitude']) if p in self.spawns.known_points or not Bounds.contain( p): continue self.spawns.add_mystery(p) except (KeyError, TypeError): self.logger.warning( 'Spawn point exception ignored. {}'.format(point)) pass if config.INCUBATE_EGGS and len(self.unused_incubators) > 0 and len( self.eggs) > 0: await self.incubate_eggs() if pokemon_seen > 0: self.error_code = ':' self.total_seen += pokemon_seen self.g['seen'] += pokemon_seen self.empty_visits = 0 if CIRCUIT_FAILURES: CIRCUIT_FAILURES[self.proxy] = 0 else: self.empty_visits += 1 if forts_seen == 0: self.error_code = '0 SEEN' else: self.error_code = ',' if self.empty_visits > 3: reason = '{} empty visits'.format(self.empty_visits) await self.swap_account(reason) if CIRCUIT_FAILURES: CIRCUIT_FAILURES[self.proxy] += 1 if CIRCUIT_FAILURES[self.proxy] > 20: reason = '{} empty visits'.format( CIRCUIT_FAILURES[self.proxy]) self.swap_circuit(reason) self.visits += 1 if config.MAP_WORKERS: self.worker_dict.update([ (self.worker_no, ((latitude, longitude), start, self.speed, self.total_seen, self.visits, pokemon_seen, sent)) ]) self.logger.info( 'Point processed, %d Pokemon and %d forts seen!', pokemon_seen, forts_seen, ) self.update_accounts_dict(auth=False) return pokemon_seen + forts_seen + points_seen async def spin_pokestop(self, pokestop): self.error_code = '$' pokestop_location = pokestop['lat'], pokestop['lon'] distance = get_distance(self.location, pokestop_location) # permitted interaction distance - 2 (for some jitter leeway) # estimation of spinning speed limit if distance > 38 or self.speed > 22: return False # randomize location up to ~1.4 meters self.simulate_jitter(amount=0.00001) request = self.api.create_request() request.fort_details(fort_id=pokestop['external_id'], latitude=pokestop['lat'], longitude=pokestop['lon']) responses = await self.call(request, action=1.5) name = responses.get('FORT_DETAILS', {}).get('name') request = self.api.create_request() request.fort_search(fort_id=pokestop['external_id'], player_latitude=self.location[0], player_longitude=self.location[1], fort_latitude=pokestop['lat'], fort_longitude=pokestop['lon']) responses = await self.call(request, action=1) result = responses.get('FORT_SEARCH', {}).get('result', 0) if result == 1: self.logger.info('Spun {}.'.format(name)) elif result == 2: self.logger.info( 'The server said {n} was out of spinning range. {d:.1f}m {s:.1f}MPH' .format(n=name, d=distance, s=self.speed)) elif result == 3: self.logger.warning('{} was in the cooldown period.'.format(name)) elif result == 4: self.logger.warning( 'Could not spin {n} because inventory was full. {s}'.format( n=name, s=sum(self.items.values()))) elif result == 5: self.logger.warning( 'Could not spin {} because the daily limit was reached.'. format(name)) self.pokestops = False else: self.logger.warning('Failed spinning {n}: {r}'.format(n=name, r=result)) self.next_spin = time() + config.SPIN_COOLDOWN self.error_code = '!' return responses async def encounter(self, pokemon): pokemon_point = pokemon['latitude'], pokemon['longitude'] distance_to_pokemon = get_distance(self.location, pokemon_point) self.error_code = '~' if distance_to_pokemon > 47: percent = 1 - (46 / distance_to_pokemon) lat_change = (self.location[0] - pokemon['latitude']) * percent lon_change = (self.location[1] - pokemon['longitude']) * percent self.location = [ self.location[0] - lat_change, self.location[1] - lon_change, uniform(self.location[2] - 3, self.location[2] + 3) ] self.api.set_position(*self.location) delay_required = (distance_to_pokemon * percent) / 8 if delay_required < 1.5: delay_required = triangular(1.25, 4, 2) else: self.simulate_jitter() delay_required = triangular(1.25, 4, 2) if time() - self.last_request < delay_required: await sleep(delay_required) request = self.api.create_request() request = request.encounter(encounter_id=pokemon['encounter_id'], spawn_point_id=pokemon['spawn_point_id'], player_latitude=self.location[0], player_longitude=self.location[1]) responses = await self.call(request, action=2.25) response = responses.get('ENCOUNTER', {}) pokemon_data = response.get('wild_pokemon', {}).get('pokemon_data', {}) if 'cp' in pokemon_data: for iv in ('individual_attack', 'individual_defense', 'individual_stamina'): if iv not in pokemon_data: pokemon_data[iv] = 0 pokemon_data['probability'] = response.get( 'capture_probability', {}).get('capture_probability') self.error_code = '!' return pokemon_data def bag_full(self): return sum(self.items.values()) >= self.item_capacity async def clean_bag(self): self.error_code = '|' rec_items = {} limits = config.ITEM_LIMITS for item, count in self.items.items(): if item in limits and count > limits[item]: discard = count - limits[item] if discard > 50: rec_items[item] = randint(50, discard) else: rec_items[item] = discard removed = 0 for item, count in rec_items.items(): request = self.api.create_request() request.recycle_inventory_item(item_id=item, count=count) responses = await self.call(request, action=2) if responses.get('RECYCLE_INVENTORY_ITEM', {}).get('result', 0) != 1: self.logger.warning("Failed to remove item %d", item) else: removed += count self.logger.info("Removed %d items", removed) self.error_code = '!' async def incubate_eggs(self): # copy the list, as self.call could modify it as it updates the inventory incubators = self.unused_incubators.copy() for egg in sorted(self.eggs.values(), key=lambda x: x.get('egg_km_walked_target')): if egg.get('egg_incubator_id'): continue if not incubators: break inc = incubators.pop() if inc.get('item_id') == 901 or egg.get('egg_km_walked_target', 0) > 9: request = self.api.create_request() request.use_item_egg_incubator(item_id=inc.get('id'), pokemon_id=egg.get('id')) responses = await self.call(request, action=5) ret = responses.get('USE_ITEM_EGG_INCUBATOR', {}).get('result', 0) if ret == 4: self.logger.warning( "Failed to use incubator because it was already in use." ) elif ret != 1: self.logger.warning( "Failed to apply incubator {} on {}, code: {}".format( inc.get('id', 0), egg.get('id', 0), ret)) async def handle_captcha(self, responses): if self.num_captchas >= config.CAPTCHAS_ALLOWED: self.logger.error( "{} encountered too many CAPTCHAs, removing.".format( self.username)) raise CaptchaException self.error_code = 'C' self.num_captchas += 1 url = responses.get('CHECK_CHALLENGE', {}).get('challenge_url') site_key = '6LeeTScTAAAAADqvhqVMhPpr_vB9D364Ia-1dSgK' try: requrl = "http://2captcha.com/in.php?key={}&method=userrecaptcha&googlekey={}&pageurl={}" response = self.session.post(requrl.format(config.CAPTCHA_KEY, site_key, url), timeout=5).text except Exception as e: self.logger.error('Got an error while trying to solve CAPTCHA. ' 'Check your API Key and account balance.') raise CaptchaSolveException from e if 'ERROR_ZERO_BALANCE' in response: config.CAPTCHA_KEY = None self.logger.error( "Error: 2Captcha reported zero balance, disabling CAPTCHA solving" ) raise CaptchaException if not response.startswith('OK|'): self.logger.error( "Failed to submit CAPTCHA for solving: {}".format(response)) raise CaptchaSolveException captcha_id = str(response.split('|')[1]) try: # Get the response, retry every 5 seconds if it's not ready while True: recaptcha_response = self.session.get( "http://2captcha.com/res.php?key={}&action=get&id={}". format(config.CAPTCHA_KEY, captcha_id), timeout=20).text if 'CAPCHA_NOT_READY' not in recaptcha_response: break await sleep(5) except Exception as e: self.logger.error('Got an error while trying to solve CAPTCHA. ' 'Check your API Key and account balance.') raise CaptchaSolveException from e if not recaptcha_response.startswith('OK|'): self.logger.error("Failed to get CAPTCHA response: {}".format( recaptcha_response)) raise CaptchaSolveException token = str(recaptcha_response.split('|')[1]) request = self.api.create_request() request.verify_challenge(token=token) try: responses = await self.call(request) self.update_accounts_dict() self.logger.warning("Successfully solved CAPTCHA") except CaptchaException: self.logger.warning( "CAPTCHA #{} for {} was not solved correctly, trying again". format(captcha_id, self.username)) # try again await self.handle_captcha(responses) def simulate_jitter(self, amount=0.00002): '''Slightly randomize location, by up to ~2.8 meters by default.''' self.location = [ uniform(self.location[0] - amount, self.location[0] + amount), uniform(self.location[1] - amount, self.location[1] + amount), uniform(self.location[2] - 1, self.location[2] + 1) ] self.api.set_position(*self.location) def notify(self, norm, time_of_day): self.error_code = '*' notified = self.notifier.notify(norm, time_of_day) if notified: self.g['sent'] += 1 self.error_code = '!' return notified def update_accounts_dict(self, captcha=False, banned=False, auth=True): self.account['captcha'] = captcha self.account['banned'] = banned self.account['location'] = self.location self.account['time'] = self.last_request self.account['inventory_timestamp'] = self.inventory_timestamp self.account['items'] = self.items if auth and self.api._auth_provider: self.account['refresh'] = self.api._auth_provider._refresh_token if self.api._auth_provider.check_access_token(): self.account['auth'] = self.api._auth_provider._access_token self.account[ 'expiry'] = self.api._auth_provider._access_token_expiry else: self.account['auth'], self.account['expiry'] = None, None self.accounts[self.username] = self.account async def remove_account(self): self.error_code = 'REMOVING' self.logger.warning('Removing {} due to ban.'.format(self.username)) self.update_accounts_dict(banned=True) await self.new_account() async def bench_account(self): self.error_code = 'BENCHING' self.logger.warning('Swapping {} due to CAPTCHA.'.format( self.username)) self.update_accounts_dict(captcha=True) self.captcha_queue.put(self.account) await self.new_account() async def swap_account(self, reason='', lock=False): self.error_code = 'SWAPPING' self.logger.warning('Swapping out {u} because {r}.'.format( u=self.username, r=reason)) if lock: await self.busy.acquire() self.update_accounts_dict() while self.extra_queue.empty(): if self.killed: return False await sleep(15) self.extra_queue.put(self.account) await self.new_account(lock) async def new_account(self, lock=False): while self.extra_queue.empty(): if self.killed: return False await sleep(15) self.account = self.extra_queue.get() self.username = self.account.get('username') self.location = self.account.get('location', (0, 0, 0)) self.inventory_timestamp = self.account.get('inventory_timestamp') self.last_request = self.account.get('time', 0) self.last_action = self.last_request self.last_gmo = self.last_request self.items = self.account.get('items', {}) self.num_captchas = 0 self.pokestops = config.SPIN_POKESTOPS self.eggs = {} self.unused_incubators = [] self.initialize_api() self.error_code = None if lock: self.busy.release() def seen_per_second(self, now): try: seconds_active = now - self.account_start if seconds_active < 120: return None return self.account_seen / seconds_active except TypeError: return None def kill(self): """Marks worker as killed Killed worker won't be restarted. """ self.error_code = 'KILLED' self.killed = True if self.ever_authenticated: self.update_accounts_dict() @staticmethod def normalize_pokemon(raw, now): """Normalizes data coming from API into something acceptable by db""" return { 'type': 'pokemon', 'encounter_id': raw['encounter_id'], 'pokemon_id': raw['pokemon_data']['pokemon_id'], 'expire_timestamp': round( (now + raw['time_till_hidden_ms']) / 1000), 'lat': raw['latitude'], 'lon': raw['longitude'], 'spawn_id': get_spawn_id(raw), 'time_till_hidden_ms': raw['time_till_hidden_ms'], 'seen': round(raw['last_modified_timestamp_ms'] / 1000) } @staticmethod def normalize_lured(raw, now): return { 'type': 'pokemon', 'encounter_id': raw['lure_info']['encounter_id'], 'pokemon_id': raw['lure_info']['active_pokemon_id'], 'expire_timestamp': raw['lure_info']['lure_expires_timestamp_ms'] / 1000, 'lat': raw['latitude'], 'lon': raw['longitude'], 'spawn_id': -1, 'time_till_hidden_ms': raw['lure_info']['lure_expires_timestamp_ms'] - now, 'valid': 'pokestop' } @staticmethod def normalize_gym(raw): return { 'type': 'fort', 'external_id': raw['id'], 'lat': raw['latitude'], 'lon': raw['longitude'], 'team': raw.get('owned_by_team', 0), 'prestige': raw.get('gym_points', 0), 'guard_pokemon_id': raw.get('guard_pokemon_id', 0), 'last_modified': round(raw['last_modified_timestamp_ms'] / 1000), } @staticmethod def normalize_pokestop(raw): return { 'type': 'pokestop', 'external_id': raw['id'], 'lat': raw['latitude'], 'lon': raw['longitude'] } @staticmethod def check_captcha(responses): challenge_url = responses.get('CHECK_CHALLENGE', {}).get('challenge_url', ' ') verify = responses.get('VERIFY_CHALLENGE', {}) success = verify.get('success') if challenge_url != ' ' and not success: if config.CAPTCHA_KEY and not verify: return True else: raise CaptchaException else: return False @property def status(self): """Returns status message to be displayed in status screen""" if self.error_code: msg = self.error_code else: msg = 'P{seen}'.format(seen=self.total_seen) return '[W{worker_no}: {msg}]'.format(worker_no=self.worker_no, msg=msg)
class Scout(object): def __init__(self, auth, username, password, job_queue): self.auth = auth self.username = username self.password = password self.job_queue = job_queue # Stats self.last_request = None self.previous_encounter = None self.last_msg = "" self.total_encounters = 0 # Things needed for requests self.inventory_timestamp = None # Collects the last few pauses between encounters to measure a "encounters per hour" value self.past_pauses = deque() self.encounters_per_hour = float(0) # instantiate pgoapi self.api = PGoApi() self.api.activate_hash_server(cfg_get('hash_key')) if have_proxies(): self.proxy = get_new_proxy() self.log_info("Using Proxy: {}".format(self.proxy)) self.api.set_proxy({ 'http': self.proxy, 'https': self.proxy }) def run(self): self.log_info("Waiting for job...") while True: job = self.job_queue.get() try: self.log_info(u"Scouting a {} at {}, {}".format(job.pokemon_name, job.lat, job.lng)) # Initialize API (lat, lng, alt) = jitter_location([job.lat, job.lng, job.altitude]) self.api.set_position(lat, lng, alt) self.check_login() if job.encounter_id and job.spawn_point_id: job.result = self.scout_by_encounter_id(job) else: if self.find_pokemon(job): job.result = self.scout_by_encounter_id(job) else: job.result = self.scout_error("Could not determine encounter_id for {} at {}, {}".format(job.pokemon_name, job.lat, job.lng)) except: job.result = self.scout_error(repr(sys.exc_info())) finally: job.processed = True self.update_history() def log_info(self, msg): self.last_msg = msg log.info(msg) def log_debug(self, msg): self.last_msg = msg log.debug(msg) def log_warning(self, msg): self.last_msg = msg log.warning(msg) def log_error(self, msg): self.last_msg = msg log.error(msg) def update_history(self): if self.previous_encounter: # Determine current pause now = time.time() pause = now - self.previous_encounter self.past_pauses.append(pause) if len(self.past_pauses) > NUM_PAUSE_SAMPLES: self.past_pauses.popleft() avg_pause = reduce(lambda x, y: x + y, self.past_pauses) / len( self.past_pauses) self.encounters_per_hour = 3600 / avg_pause self.total_encounters += 1 self.previous_encounter = time.time() def parse_wild_pokemon(self, response): wild_pokemon = [] cells = response.get('responses', {}).get('GET_MAP_OBJECTS', {}).get('map_cells', []) for cell in cells: wild_pokemon += cell.get('wild_pokemons', []) return wild_pokemon def find_pokemon(self, job): tries = 0 max_tries = 3 wild_pokemon = [] while tries < max_tries: tries += 1 try: (lat, lng, alt) = self.jittered_location(job) self.log_info("Looking for {} at {}, {} - try {}".format(job.pokemon_name, lat, lng, tries)) cell_ids = get_cell_ids(lat, lng) timestamps = [0, ] * len(cell_ids) req = self.api.create_request() req.get_map_objects(latitude=f2i(lat), longitude=f2i(lng), since_timestamp_ms=timestamps, cell_id=cell_ids) response = self.perform_request(req) wild_pokemon = self.parse_wild_pokemon(response) if len(wild_pokemon) > 0: break except Exception as e: self.log_error('Exception on GMO try {}: {}'.format(tries, repr(e))) if len(wild_pokemon) == 0: self.log_info("Still no wild Pokemon found. Giving up.") return False # find all pokemon with desired id candidates = filter(lambda pkm: pkm['pokemon_data']['pokemon_id'] == job.pokemon_id, wild_pokemon) target = None if len(candidates) == 1: # exactly one pokemon of this id found target = candidates[0] elif len(candidates) > 1: # multiple pokemon found, pick one with lowest distance to search position loc = (job.lat, job.lng) min_dist = False for pkm in candidates: d = geopy.distance.distance(loc, (pkm["latitude"], pkm["longitude"])).meters if not min_dist or d < min_dist: min_dist = d target = pkm # no pokemon found if target is None: self.log_info("No wild {} found at {}, {}.".format(job.pokemon_name, lat, lng)) return False # now set encounter id and spawn point id self.log_info("Got encounter_id for {} at {}, {}.".format(job.pokemon_name, target['latitude'], target['longitude'])) job.encounter_id = target['encounter_id'] job.spawn_point_id = target["spawn_point_id"] return True def scout_by_encounter_id(self, job): (lat, lng, alt) = self.jittered_location(job) self.log_info("Performing encounter request at {}, {}".format(lat, lng)) response = self.encounter_request(job.encounter_id, job.spawn_point_id, lat, lng) return self.parse_encounter_response(response, job) def parse_encounter_response(self, response, job): if response is None: return self.scout_error("Encounter response is None.") if has_captcha(response): return self.scout_error("Scout account captcha'd.") encounter = response.get('responses', {}).get('ENCOUNTER', {}) if encounter.get('status', None) == 3: return self.scout_error("Pokemon already despawned.") if 'wild_pokemon' not in encounter: return self.scout_error("No wild pokemon info found.") scout_level = get_player_level(response) if scout_level < cfg_get("require_min_trainer_level"): return self.scout_error( "Trainer level {} is too low. Needs to be {}+".format(scout_level, cfg_get("require_min_trainer_level"))) pokemon_info = encounter['wild_pokemon']['pokemon_data'] cp = pokemon_info['cp'] pokemon_level = calc_pokemon_level(pokemon_info['cp_multiplier']) probs = encounter['capture_probability']['capture_probability'] at = pokemon_info.get('individual_attack', 0) df = pokemon_info.get('individual_defense', 0) st = pokemon_info.get('individual_stamina', 0) iv = calc_iv(at, df, st) moveset_grades = get_moveset_grades(job.pokemon_id, job.pokemon_name, pokemon_info['move_1'], pokemon_info['move_2']) response = { 'success': True, 'encounter_id': job.encounter_id, 'encounter_id_b64': b64encode(str(job.encounter_id)), 'height': pokemon_info['height_m'], 'weight': pokemon_info['weight_kg'], 'gender': pokemon_info['pokemon_display']['gender'], 'iv_percent': iv, 'iv_attack': at, 'iv_defense': df, 'iv_stamina': st, 'move_1': pokemon_info['move_1'], 'move_2': pokemon_info['move_2'], 'rating_attack': moveset_grades['offense'], 'rating_defense': moveset_grades['defense'], 'cp': cp, 'cp_multiplier': pokemon_info['cp_multiplier'], 'level': pokemon_level, 'catch_prob_1': probs[0], 'catch_prob_2': probs[1], 'catch_prob_3': probs[2], 'scout_level': scout_level, 'encountered_time': time.time() } # Add form of Unown if job.pokemon_id == 201: response['form'] = pokemon_info['pokemon_display'].get('form', None) self.log_info( u"Found a {:.1f}% ({}/{}/{}) L{} {} with {} CP (scout level {}).".format( iv, at, df, st, pokemon_level, job.pokemon_name, cp, scout_level)) inc_for_pokemon(job.pokemon_id) return response def check_login(self): # Logged in? Enough time left? Cool! if self.api._auth_provider and self.api._auth_provider._ticket_expire: remaining_time = self.api._auth_provider._ticket_expire / 1000 - time.time() if remaining_time > 60: self.log_debug( 'Credentials remain valid for another {} seconds.'.format(remaining_time)) return # Try to login. Repeat a few times, but don't get stuck here. num_tries = 0 # One initial try + login_retries. while num_tries < 3: try: self.api.set_authentication( provider=self.auth, username=self.username, password=self.password) break except AuthException: num_tries += 1 self.log_error( 'Failed to login. ' + 'Trying again in {} seconds.'.format(6)) time.sleep(6) if num_tries >= 3: self.log_error( 'Failed to login for {} tries. Giving up.'.format( num_tries)) raise TooManyLoginAttempts('Exceeded login attempts.') wait_after_login = cfg_get('wait_after_login') self.log_info('Login successful. Waiting {} more seconds.'.format(wait_after_login)) time.sleep(wait_after_login) def encounter_request(self, encounter_id, spawn_point_id, latitude, longitude): req = self.api.create_request() req.encounter( encounter_id=encounter_id, spawn_point_id=spawn_point_id, player_latitude=float(latitude), player_longitude=float(longitude)) return self.perform_request(req) def perform_request(self, req, delay=12): req.check_challenge() req.get_hatched_eggs() if self.inventory_timestamp: req.get_inventory(last_timestamp_ms=self.inventory_timestamp) else: req.get_inventory() req.check_awarded_badges() req.get_buddy_walked() # Wait before we perform the request d = float(delay) if self.last_request and time.time() - self.last_request < d: time.sleep(d - (time.time() - self.last_request)) response = req.call() self.last_request = time.time() # Update inventory timestamp try: self.inventory_timestamp = \ response['GET_INVENTORY']['inventory_delta']['new_timestamp_ms'] except KeyError: pass return response def scout_error(self, error_msg): self.log_error("Error: {}".format(error_msg)) return { 'success': False, 'error': error_msg } def jittered_location(self, job): (lat, lng, alt) = jitter_location([job.lat, job.lng, job.altitude]) self.api.set_position(lat, lng, alt) return lat, lng, alt
class pgoapiSession(object): def __init__(self, account={}): self.account = account self.seshdata = None self.invdata = None self._api = None def refresh(self): self.seshdata = None self.invdata = None @property def api(self): if self._api is None: self._api = PGoApi(provider=self.account.get('auth') or 'google', username=self.account.get('user'), password=self.account.get('pass')) if POKEHASH is not None: self._api.activate_hash_server(POKEHASH) return self._api def get_seshdata(self, forcerefresh=False): if self.seshdata is None or forcerefresh: self.api.set_position(0, 0) req = self.api.create_request() req.get_player() req.get_inventory() self.seshdata = req.call() return self.seshdata @property def inventory_items(self): if self.get_seshdata(): return self.get_seshdata().get('responses', {}).get( 'GET_INVENTORY', {}).get('inventory_delta', {}).get('inventory_items', []) @property def player_data(self): if self.get_seshdata(): return self.get_seshdata().get('responses', {}).get('GET_PLAYER', {}).get('player_data', {}) def getInventory(self): if self.invdata is None: return self.checkInventory() return self.invdata def evolvePokemon(self, pokemon): self.api.set_position(0, 0) request = self.api.create_request() request.evolve_pokemon(pokemon_id=pokemon.id) response = request.call() return response def releasePokemon(self, pokemon): self.api.set_position(0, 0) request = self.api.create_request() request.release_pokemon(pokemon_id=pokemon.id) response = request.call() return response def favoritePokemon(self, pokemon, is_favorite=True): self.api.set_position(0, 0) request = self.api.create_request() request.set_favorite_pokemon( pokemon_id=pokemon.id if pokemon.id < 2**63 else pokemon.id - 2**64, is_favorite=is_favorite) #request.set_favorite_pokemon(pokemon_id=pokemon.id,is_favorite=is_favorite) response = request.call() return response def releaseMultiplePokemon(self, pokemen, max_batch_release=25): self.api.set_position(0, 0) batches = [] responses = [] for idx in xrange( int(math.ceil((0.0 + len(pokemen)) / max_batch_release))): batches.append(pokemen[idx * max_batch_release:(idx + 1) * max_batch_release]) for batch in batches: pokemon_ids = [] for pokemon in batch: pokemon_ids.append(pokemon.id) request = self.api.create_request() request.release_pokemon(pokemon_ids=pokemon_ids) responses.append(request.call()) if len(batches) > 1: time.sleep(0.5) return responses def is_pokemon_buddy(self, pokemon): return pokemon.id == self.player_data.get('buddy_pokemon', {}).get('id', -1) def checkInventory(self): self.invdata = {} self.invdata["incubators"] = [] self.invdata["pokedex"] = {} self.invdata["candies"] = {} self.invdata["stats"] = {} self.invdata["party"] = [] self.invdata["eggs"] = [] self.invdata["bag"] = {} seshdata = self.get_seshdata(forcerefresh=True) seshinvdata = seshdata.get('responses', {}).get('GET_INVENTORY', {}).get('inventory_delta', {}).get( 'inventory_items', []) for item in seshinvdata: data = item['inventory_item_data'] if "player_stats" in data: self.invdata["stats"] = InventoryItem(data['player_stats']) continue pokedexEntry = data.get('pokedex_entry', None) if pokedexEntry: self.invdata["pokedex"][ pokedexEntry['pokemon_id']] = InventoryItem(pokedexEntry) continue pokemonFamily = data.get('candy', None) if pokemonFamily: self.invdata["candies"][pokemonFamily.get( 'family_id')] = pokemonFamily.get('candy', 0) continue pokemonData = data.get("pokemon_data", None) if pokemonData: if pokemonData.get('is_egg'): self.invdata["eggs"].append(InventoryItem(pokemonData)) else: pokemon = PokemonItem(pokemonData) pokemon['is_buddy'] = self.is_pokemon_buddy(pokemon) self.invdata["party"].append(pokemon) continue incubators = data.get("egg_incubators", None) if incubators: self.invdata["incubators"] = incubators.get('egg_incubator') continue bagItem = data.get("item", None) if bagItem: self.invdata["bag"][bagItem.get('item_id')] = bagItem.get( 'count') continue #self.invdata['party'] = [InventoryItem(item.get('inventory_item_data').get('pokemon_data')) for item in seshinvdata if 'pokemon_data' in item.get('inventory_item_data')] return self.invdata
def main(): try: if hasattr(config, 'AUTHKEY'): authkey = config.AUTHKEY else: authkey = b'm3wtw0' if hasattr(config, 'HASH_KEY'): HASH_KEY = config.HASH_KEY else: HASH_KEY = None class AccountManager(BaseManager): pass AccountManager.register('captcha_queue') AccountManager.register('extra_queue') manager = AccountManager(address=get_address(), authkey=authkey) manager.connect() captcha_queue = manager.captcha_queue() extra_queue = manager.extra_queue() middle_lat = (config.MAP_START[0] + config.MAP_END[0]) / 2 middle_lon = (config.MAP_START[1] + config.MAP_END[1]) / 2 middle_alt = random_altitude() driver = webdriver.Chrome() driver.set_window_size(803, 807) while not captcha_queue.empty(): account = captcha_queue.get() username = account.get('username') location = account.get('location') if location and location != (0, 0, 0): lat, lon, alt = location else: lat = uniform(middle_lat - 0.001, middle_lat + 0.001) lon = uniform(middle_lon - 0.001, middle_lon + 0.001) alt = uniform(middle_alt - 10, middle_alt + 10) try: device_info = get_device_info(account) api = PGoApi(device_info=device_info) if HASH_KEY: api.activate_hash_server(HASH_KEY) api.set_position(lat, lon, alt) authenticated = False if account.get('provider') == 'ptc' and account.get('refresh'): api._auth_provider = AuthPtc() api._auth_provider.set_refresh_token( account.get('refresh')) api._auth_provider._access_token = account.get('auth') api._auth_provider._access_token_expiry = account.get( 'expiry') if api._auth_provider.check_access_token(): print(username, 'already authenticated') api._auth_provider._login = True authenticated = True if not authenticated: print(username) api.set_authentication(username=username, password=account.get('password'), provider=account.get('provider')) request = api.create_request() request.download_remote_config_version(platform=1, app_version=5301) request.check_challenge() request.get_hatched_eggs() request.get_inventory() request.check_awarded_badges() request.download_settings() response = request.call() account['time'] = time() responses = response.get('responses', {}) challenge_url = responses.get('CHECK_CHALLENGE', {}).get('challenge_url', ' ') timestamp = responses.get('GET_INVENTORY', {}).get('inventory_delta', {}).get('new_timestamp_ms') account['location'] = lat, lon, alt account['inventory_timestamp'] = timestamp if challenge_url == ' ': account['captcha'] = False extra_queue.put(account) else: if resolve_captcha(challenge_url, api, driver, timestamp): account['time'] = time() account['captcha'] = False extra_queue.put(account) else: account['time'] = time() print('failure') captcha_queue.put(account) except (KeyboardInterrupt, Exception) as e: print(e) captcha_queue.put(account) break except (exceptions.AuthException, exceptions.AuthTokenExpiredException, exceptions.AuthTokenExpiredException): print('Authentication error') captcha_queue.put(account) sleep(2) finally: try: driver.close() except Exception: pass
def solveCaptchas(mode, username, password, location, captchakey2): try: captchatimeout=1000 max_login_retries = 5 user_agent = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36") location = location.replace(" ", "") location = location.split(",") api = PGoApi() if not config.hash_key is None: api.activate_hash_server(config.hash_key) if config.proxy: api.set_proxy({'http': config.proxy, 'https': config.proxy}) #Need this? Proxy is not setted with set_authentication? api.set_position(float(location[0]), float(location[1]), 0.0) # Try to login (a few times, but don't get stuck here) i = 0 while i < max_login_retries: try: if config.proxy: api.set_authentication(provider=mode, username=username, password=password, proxy_config={'http': config.proxy, 'https': config.proxy}) else: api.set_authentication(provider=mode, username=username, password=password) break except AuthException: if i >= max_login_retries: print_info('Exceeded login attempts. Skipping to next account.', username) return else: i += 1 print_error('Failed to login. Trying again in 10 seconds. Check the account if this error persist.', username) time.sleep(10) print_info("Login OK [{num} attempt(s)]".format(num = (i + 1)), username) time.sleep(1) response = api.check_challenge() captcha_url = None try: captcha_url = response['responses']['CHECK_CHALLENGE']['challenge_url']; except Exception: print_info('Something wrong happened getting captcha. Check account. Skipping...', username) return if len(captcha_url) == 1: print_info("No captcha required", username) #skip, captcha not necessary else: print_info("Captcha required", username) print_debug("CaptchaURL: {}".format(captcha_url), username) if captchakey2 != "": dcap = dict(DesiredCapabilities.PHANTOMJS) dcap["phantomjs.page.settings.userAgent"] = user_agent driver = webdriver.PhantomJS(desired_capabilities=dcap) else: print_info("You did not pass a 2captcha key. Please solve the captcha manually.", username) try: chrome_options = Options() if not config.chromedir is None: chrome_options.binary_location = config.chromedir driver = webdriver.Chrome(chrome_options=chrome_options) driver.set_window_size(600, 600) except Exception: print_error("Chromedriver seems to have some problem. Do you have the latest version?") return driver.get(captcha_url) if captchakey2 == "": #Do manual captcha entry elem = driver.find_element_by_class_name("g-recaptcha") driver.execute_script("arguments[0].scrollIntoView(true);", elem) # Waits 1 minute for you to input captcha try: WebDriverWait(driver, 60).until(EC.text_to_be_present_in_element_value((By.NAME, "g-recaptcha-response"), "")) print_info("Solved captcha", username) token = driver.execute_script("return grecaptcha.getResponse()") driver.close() print_debug("Recaptcha token: {}".format(token)) activateUser(api, token, username) time.sleep(1) except TimeoutException, err: print_info("Timed out while manually solving captcha", username) else:
class Torch(object): def __init__(self, auth, username, password, latitude, longitude): self.auth = auth self.username = username self.password = password self.latitude = latitude self.longitude = longitude self.player_stats = {} self.pokemon = {} # Player State self.warned = None self.banned = None # Things needed for requests self.inventory_timestamp = None # instantiate pgoapi self.api = PGoApi() self.api.activate_hash_server(cfg_get('hash_key')) self.api.set_position(self.latitude, self.longitude, random.randrange(3, 170)) self.last_request = None if have_proxies(): self.proxy = get_new_proxy() self.log_info("Using Proxy: {}".format(self.proxy)) self.api.set_proxy({'http': self.proxy, 'https': self.proxy}) def run(self): while True: self.check_login() self.scan_location() def scan_location(self): try: self.api.set_position(self.latitude, self.longitude, random.randint(12, 108)) cell_ids = get_cell_ids(self.latitude, self.longitude) timestamps = [ 0, ] * len(cell_ids) req = self.api.create_request() req.get_map_objects(latitude=f2i(self.latitude), longitude=f2i(self.longitude), since_timestamp_ms=timestamps, cell_id=cell_ids) response = self.perform_request(req) self.wild_pokemon = self.count_pokemon(response) except Exception as e: pass def count_pokemon(self, response): self.pokemon = {} cells = response.get('responses', {}).get('GET_MAP_OBJECTS', {}).get('map_cells', []) for cell in cells: for p in cell.get('wild_pokemons', []): pid = p['pokemon_data']['pokemon_id'] self.pokemon[pid] = self.pokemon.get(pid, 0) + 1 if cfg_get('include_nearby'): for p in cell.get('nearby_pokemons', []): pid = p['pokemon_id'] self.pokemon[pid] = self.pokemon.get(pid, 0) + 1 # Updates warning/banned flags and tutorial state. def update_player_state(self): request = self.api.create_request() request.get_player(player_locale={ 'country': 'US', 'language': 'en', 'timezone': 'America/Denver' }) response = request.call().get('responses', {}) get_player = response.get('GET_PLAYER', {}) self.warned = get_player.get('warn', False) self.banned = get_player.get('banned', False) time.sleep(4) def check_login(self): # Logged in? Enough time left? Cool! if self.api._auth_provider and self.api._auth_provider._ticket_expire: remaining_time = self.api._auth_provider._ticket_expire / 1000 - time.time( ) if remaining_time > 60: self.log_debug( 'Credentials remain valid for another {} seconds.'.format( remaining_time)) return # Try to login. Repeat a few times, but don't get stuck here. num_tries = 0 # One initial try + login_retries. while num_tries < 3: try: self.api.set_authentication(provider=self.auth, username=self.username, password=self.password) break except AuthException: num_tries += 1 self.log_error('Failed to login. ' + 'Trying again in {} seconds.'.format(6)) time.sleep(6) if num_tries >= 3: self.log_error( 'Failed to login for {} tries. Giving up.'.format(num_tries)) raise TooManyLoginAttempts('Exceeded login attempts.') wait_after_login = cfg_get('wait_after_login') self.log_info('Login successful. Waiting {} more seconds.'.format( wait_after_login)) time.sleep(wait_after_login) self.update_player_state() def perform_request(self, req, delay=12): req.check_challenge() req.get_hatched_eggs() if self.inventory_timestamp: req.get_inventory(last_timestamp_ms=self.inventory_timestamp) else: req.get_inventory() req.check_awarded_badges() req.get_buddy_walked() # Wait before we perform the request d = float(delay) if self.last_request and time.time() - self.last_request < d: time.sleep(d - (time.time() - self.last_request)) response = req.call() self.last_request = time.time() # Update player stats self.player_stats = get_player_stats(response) # Update inventory timestamp try: self.inventory_timestamp = \ response['GET_INVENTORY']['inventory_delta']['new_timestamp_ms'] except KeyError: pass return response def log_info(self, msg): self.last_msg = msg log.info(msg) def log_debug(self, msg): self.last_msg = msg log.debug(msg) def log_warning(self, msg): self.last_msg = msg log.warning(msg) def log_error(self, msg): self.last_msg = msg log.error(msg)
while not captcha_queue.empty(): account = captcha_queue.get() username = account.get('username') location = account.get('location') if location and location != (0, 0, 0): lat, lon, alt = location else: lat = uniform(middle_lat - 0.001, middle_lat + 0.001) lon = uniform(middle_lon - 0.001, middle_lon + 0.001) alt = uniform(middle_alt - 10, middle_alt + 10) try: device_info = get_device_info(account) api = PGoApi(device_info=device_info) if HASH_KEY: api.activate_hash_server(HASH_KEY) api.set_position(lat, lon, alt) authenticated = False if account.get('provider') == 'ptc' and account.get('refresh'): api._auth_provider = AuthPtc() api._auth_provider.set_refresh_token(account.get('refresh')) api._auth_provider._access_token = account.get('auth') api._auth_provider._access_token_expiry = account.get('expiry') if api._auth_provider.check_access_token(): print(username, 'already authenticated') api._auth_provider._login = True authenticated = True if not authenticated: print(username)
class POGOAccount(object): def __init__(self, auth_service, username, password, hash_key=None, hash_key_provider=None, proxy_url=None, proxy_provider=None): self.auth_service = auth_service self.username = username self.password = password # Initialize hash keys self._hash_key = None if hash_key_provider and not hash_key_provider.is_empty(): self._hash_key_provider = hash_key_provider elif hash_key: self._hash_key_provider = CyclicResourceProvider(hash_key) self._hash_key = hash_key else: self._hash_key_provider = None # Initialize proxies self._proxy_url = None if proxy_provider and not proxy_provider.is_empty(): self._proxy_provider = proxy_provider elif proxy_url: self._proxy_provider = CyclicResourceProvider(proxy_url) self._proxy_url = proxy_url else: self._proxy_provider = None self.cfg = _mr_mime_cfg.copy() # Tutorial state and warn/ban flags self.player_state = {} # Trainer statistics self.player_stats = {} self.captcha_url = None # Inventory information self.inventory = None self.inventory_balls = 0 self.inventory_total = 0 # Location self.latitude = None self.longitude = None self.altitude = None # Last log message (for GUI/console) self.last_msg = "" # --- private fields self._api = PGoApi(device_info=self._generate_device_info()) self._download_settings_hash = None self._asset_time = 0 self._item_templates_time = 0 # Timestamp when last API request was made self._last_request = 0 # Timestamp of last get_map_objects request self._last_gmo = self._last_request # Timestamp for incremental inventory updates self._last_timestamp_ms = None # Timestamp when previous user action is completed self._last_action = 0 self._map_cells = [] @property def hash_key(self): return self._hash_key @hash_key.setter def hash_key(self, new_key): if self._hash_key_provider is None: self._hash_key_provider = CyclicResourceProvider(new_key) else: self._hash_key_provider.set_single_resource(new_key) self._hash_key = new_key @property def proxy_url(self): return self._proxy_url @proxy_url.setter def proxy_url(self, new_proxy_url): if self._proxy_provider is None: self._proxy_provider = CyclicResourceProvider(new_proxy_url) else: self._proxy_provider.set_single_resource(new_proxy_url) self._proxy_url = new_proxy_url def set_position(self, lat, lng, alt): """Sets the location and altitude of the account""" self._api.set_position(lat, lng, alt) self.latitude = lat self.longitude = lng self.altitude = alt def perform_request(self, add_main_request, download_settings=False, buddy_walked=True, get_inbox=True, action=None, jitter=True): request = self._api.create_request() # Add main request add_main_request(request) # Standard requests with every call request.check_challenge() request.get_hatched_eggs() # Check inventory with correct timestamp if self._last_timestamp_ms: request.get_inventory(last_timestamp_ms=self._last_timestamp_ms) else: request.get_inventory() # Always check awarded badges request.check_awarded_badges() # Optional: download settings (with correct hash value) if download_settings: if self._download_settings_hash: request.download_settings(hash=self._download_settings_hash) else: request.download_settings() # Optional: request buddy kilometers if buddy_walked: request.get_buddy_walked() if get_inbox: request.get_inbox(is_history=True) return self._call_request(request, action, jitter) # Use API to check the login status, and retry the login if possible. def check_login(self): # Check auth ticket if self._api.get_auth_provider() and self._api.get_auth_provider().check_ticket(): return True try: if not self.cfg['parallel_logins']: login_lock.acquire() # Set proxy if given. if self._proxy_provider: self._proxy_url = self._proxy_provider.next() self.log_debug("Using proxy {}".format(self._proxy_url)) self._api.set_proxy({ 'http': self._proxy_url, 'https': self._proxy_url }) # Try to login. Repeat a few times, but don't get stuck here. num_tries = 0 # One initial try + login_retries. while num_tries < self.cfg['login_retries']: try: num_tries += 1 self.log_info("Login try {}.".format(num_tries)) if self._proxy_url: self._api.set_authentication( provider=self.auth_service, username=self.username, password=self.password, proxy_config={ 'http': self._proxy_url, 'https': self._proxy_url }) else: self._api.set_authentication( provider=self.auth_service, username=self.username, password=self.password) self.log_info("Login successful after {} tries.".format(num_tries)) break except AuthException: self.log_error( 'Failed to login. Trying again in {} seconds.'.format( self.cfg['login_delay'])) time.sleep(self.cfg['login_delay']) if num_tries >= self.cfg['login_retries']: self.log_error( 'Failed to login in {} tries. Giving up.'.format(num_tries)) return False if self.cfg['full_login_flow'] is True: try: return self._initial_login_request_flow() except BannedAccountException: self.log_warning("Account most probably BANNED! :-(((") self.player_state['banned'] = True return False except CaptchaException: self.log_warning("Account got CAPTCHA'd! :-|") return False except Exception as e: self.log_error("Login failed: {}".format(repr(e))) return False return True finally: if not self.cfg['parallel_logins']: login_lock.release() def is_logged_in(self): # Logged in? Enough time left? Cool! if self._api.get_auth_provider() and self._api.get_auth_provider().has_ticket(): remaining_time = self._api.get_auth_provider()._ticket_expire / 1000 - time.time() return remaining_time > 60 return False def is_warned(self): return self.player_state.get('warn') def is_banned(self): return self.player_state.get('banned') def has_captcha(self): return None if not self.is_logged_in() else ( self.captcha_url and len(self.captcha_url) > 1) def uses_proxy(self): return self._proxy_url is not None and len(self._proxy_url) > 0 def req_get_map_objects(self): """Scans current account location.""" # Make sure that we don't hammer with GMO requests diff = self._last_gmo + self.cfg['scan_delay'] - time.time() if diff > 0: time.sleep(diff) # We jitter here because we need the jittered location NOW lat, lng = jitter_location(self.latitude, self.longitude) self._api.set_position(lat, lng, self.altitude) cell_ids = get_cell_ids(lat, lng) timestamps = [0, ] * len(cell_ids) responses = self.perform_request( lambda req: req.get_map_objects(latitude=f2i(lat), longitude=f2i(lng), since_timestamp_ms=timestamps, cell_id=cell_ids), get_inbox=True, jitter=False # we already jittered ) self._last_gmo = self._last_request self._map_cells = responses['GET_MAP_OBJECTS']['map_cells'] return responses def req_encounter(self, encounter_id, spawn_point_id, latitude, longitude): return self.perform_request(lambda req: req.encounter( encounter_id=encounter_id, spawn_point_id=spawn_point_id, player_latitude=latitude, player_longitude=longitude), get_inbox=True) def req_catch_pokemon(self, encounter_id, spawn_point_id, ball, normalized_reticle_size, spin_modifier): return self.perform_request(lambda req: req.catch_pokemon( encounter_id=encounter_id, pokeball=ball, normalized_reticle_size=normalized_reticle_size, spawn_point_id=spawn_point_id, hit_pokemon=1, spin_modifier=spin_modifier, normalized_hit_position=1.0), get_inbox=True) def req_release_pokemon(self, pokemon_id): return self.perform_request( lambda req: req.release_pokemon(pokemon_id=pokemon_id), get_inbox=True) def req_fort_search(self, fort_id, fort_lat, fort_lng, player_lat, player_lng): return self.perform_request(lambda req: req.fort_search( fort_id=fort_id, fort_latitude=fort_lat, fort_longitude=fort_lng, player_latitude=player_lat, player_longitude=player_lng), get_inbox=True) def req_get_gym_details(self, gym_id, gym_lat, gym_lng, player_lat, player_lng): return self.perform_request( lambda req: req.get_gym_details(gym_id=gym_id, player_latitude=f2i(player_lat), player_longitude=f2i(player_lng), gym_latitude=gym_lat, gym_longitude=gym_lng, client_version=API_VERSION), get_inbox=True) def req_recycle_inventory_item(self, item_id, amount): return self.perform_request(lambda req: req.recycle_inventory_item( item_id=item_id, count=amount), get_inbox=True) def req_level_up_rewards(self, level): return self.perform_request( lambda req: req.level_up_rewards(level=level), get_inbox=True) def req_verify_challenge(self, captcha_token): req = self._api.create_request() req.verify_challenge(token=captcha_token) responses = self._call_request(req) if 'VERIFY_CHALLENGE' in responses: response = responses['VERIFY_CHALLENGE'] if 'success' in response: self.captcha_url = None self.log_info("Successfully uncaptcha'd.") return True else: self.log_warning("Failed verifyChallenge") return False def get_forts(self): forts = [] for cell in self._map_cells: for fort in enumerate(cell['forts']): forts.append(fort) return forts def get_pokemon(self): pokemons = [] for cell in self._map_cells: for pokemon in enumerate(cell['catchable_pokemons']): pokemons.append(pokemon) return pokemons def get_spawn_points(self): spawns = [] for cell in self._map_cells: for spawnpoint in enumerate(cell['spawn_points']): spawns.append(spawnpoint) return spawns # ======================================================================= def _generate_device_info(self): identifier = self.username + self.password md5 = hashlib.md5() md5.update(identifier) pick_hash = int(md5.hexdigest(), 16) iphones = { 'iPhone5,1': 'N41AP', 'iPhone5,2': 'N42AP', 'iPhone5,3': 'N48AP', 'iPhone5,4': 'N49AP', 'iPhone6,1': 'N51AP', 'iPhone6,2': 'N53AP', 'iPhone7,1': 'N56AP', 'iPhone7,2': 'N61AP', 'iPhone8,1': 'N71AP', 'iPhone8,2': 'N66AP', 'iPhone8,4': 'N69AP', 'iPhone9,1': 'D10AP', 'iPhone9,2': 'D11AP', 'iPhone9,3': 'D101AP', 'iPhone9,4': 'D111AP' } ios8 = ('8.0', '8.0.1', '8.0.2', '8.1', '8.1.1', '8.1.2', '8.1.3', '8.2', '8.3', '8.4', '8.4.1') ios9 = ('9.0', '9.0.1', '9.0.2', '9.1', '9.2', '9.2.1', '9.3', '9.3.1', '9.3.2', '9.3.3', '9.3.4', '9.3.5') ios10 = ('10.0', '10.0.1', '10.0.2', '10.0.3', '10.1', '10.1.1') device_info = { 'device_brand': 'Apple', 'device_model': 'iPhone', 'hardware_manufacturer': 'Apple', 'firmware_brand': 'iPhone OS' } devices = tuple(iphones.keys()) device = devices[pick_hash % len(devices)] device_info['device_model_boot'] = device device_info['hardware_model'] = iphones[device] device_info['device_id'] = md5.hexdigest() if device.startswith('iPhone9'): ios_pool = ios10 elif device.startswith('iPhone8'): ios_pool = ios9 + ios10 else: ios_pool = ios8 + ios9 + ios10 device_info['firmware_type'] = ios_pool[pick_hash % len(ios_pool)] self.log_info("Using an {} on iOS {} with device ID {}".format(device, device_info['firmware_type'], device_info['device_id'])) return device_info def _call_request(self, request, action=None, jitter=True): # Wait until a previous user action gets completed if action: now = time.time() # wait for the time required, or at least a half-second if self._last_action > now + .5: time.sleep(self._last_action - now) else: time.sleep(0.5) if jitter: lat, lng = jitter_location(self.latitude, self.longitude) self._api.set_position(lat, lng, self.altitude) success = False while not success: try: # Set hash key for this request old_hash_key = self._hash_key self._hash_key = self._hash_key_provider.next() if self._hash_key != old_hash_key: self.log_debug("Using hash key {}".format(self._hash_key)) self._api.activate_hash_server(self._hash_key) response = request.call() self._last_request = time.time() success = True except HashingQuotaExceededException as e: if self.cfg['retry_on_hash_quota_exceeded'] or self.cfg['retry_on_hashing_error']: self.log_warning("{}: Retrying in 5s.".format(repr(e))) time.sleep(5) else: raise except (HashingOfflineException, HashingTimeoutException) as e: if self.cfg['retry_on_hashing_error']: self.log_warning("{}: Retrying in 5s.".format(repr(e))) time.sleep(5) else: raise # status_code 3 means BAD_REQUEST, so probably banned if 'status_code' in response and response['status_code'] == 3: self.log_warning("Got BAD_REQUEST response.") raise BannedAccountException if not 'responses' in response: self.log_error("Got no responses at all!") return {} # Set the timer when the user action will be completed if action: self._last_action = self._last_request + action # Return only the responses responses = response['responses'] self._parse_responses(responses) return responses def _update_inventory_totals(self): ball_ids = [ ITEM_POKE_BALL, ITEM_GREAT_BALL, ITEM_ULTRA_BALL, ITEM_MASTER_BALL ] balls = 0 total_items = 0 for item_id in self.inventory: if item_id in ball_ids: balls += self.inventory[item_id] total_items += self.inventory[item_id] self.inventory_balls = balls self.inventory_total = total_items def _parse_responses(self, responses): for response_type in responses.keys(): response = responses[response_type] if response_type == 'GET_INVENTORY': api_inventory = response # Set an (empty) inventory if necessary if self.inventory is None: self.inventory = {} # Update inventory (balls, items) inventory_delta = parse_inventory_delta(api_inventory) self.inventory.update(inventory_delta) self._update_inventory_totals() # Update stats (level, xp, encounters, captures, km walked, etc.) self.player_stats.update(parse_player_stats(api_inventory)) # Update last timestamp for inventory requests self._last_timestamp_ms = api_inventory[ 'inventory_delta'].get('new_timestamp_ms', 0) # Cleanup del responses[response_type] # Get settings hash from response for future calls elif response_type == 'DOWNLOAD_SETTINGS': if 'hash' in response: self._download_settings_hash = response['hash'] # TODO: Check forced client version and exit program if different elif response_type == 'GET_PLAYER': self.player_state = { 'tutorial_state': response.get('player_data', {}).get('tutorial_state', []), 'warn': response.get('warn', False), 'banned': response.get('banned', False) } if self.player_state.get('banned', False): self.log_warning("GET_PLAYER has the 'banned' flag set.") raise BannedAccountException # Check for captcha elif response_type == 'CHECK_CHALLENGE': self.captcha_url = response.get('challenge_url') if self.has_captcha(): raise CaptchaException def _initial_login_request_flow(self): self.log_info("Performing full login flow requests") # Empty request ----------------------------------------------------- self.log_debug("Login Flow: Empty request") # ===== empty request = self._api.create_request() self._call_request(request) time.sleep(random.uniform(.43, .97)) # Get player info --------------------------------------------------- self.log_debug("Login Flow: Get player state") # ===== GET_PLAYER (unchained) request = self._api.create_request() request.get_player( player_locale=self.cfg['player_locale']) self._call_request(request) time.sleep(random.uniform(.53, 1.1)) # Download remote config -------------------------------------------- self.log_debug("Login Flow: Downloading remote config") asset_time, template_time = self._download_remote_config_version() time.sleep(1) # Assets and item templates ----------------------------------------- if self.cfg['download_assets_and_items'] and asset_time > self._asset_time: self.log_debug("Login Flow: Download asset digest") self._get_asset_digest(asset_time) else: self.log_debug("Login Flow: Skipping asset digest download") if self.cfg['download_assets_and_items'] and template_time > self._item_templates_time: self.log_debug("Login Flow: Download item templates") self._download_item_templates(template_time) else: self.log_debug("Login Flow: Skipping item template download") # TODO: Maybe download translation URLs from assets? Like pogonode? # Checking tutorial ------------------------------------------------- if (self.player_state['tutorial_state'] is not None and not all(x in self.player_state['tutorial_state'] for x in (0, 1, 3, 4, 7))): self.log_debug("Login Flow: Completing tutorial") self._complete_tutorial() else: # Get player profile self.log_debug("Login Flow: Get player profile") # ===== GET_PLAYER_PROFILE self.perform_request(lambda req: req.get_player_profile(), download_settings=True) time.sleep(random.uniform(.2, .3)) # Level up rewards -------------------------------------------------- self.log_debug("Login Flow: Get levelup rewards") # ===== LEVEL_UP_REWARDS self.perform_request( lambda req: req.level_up_rewards(level=self.player_stats['level']), download_settings=True) # Check store ------------------------------------------------------- # TODO: There is currently no way to call the GET_STORE_ITEMS platform request. self.log_info('After-login procedure completed.') time.sleep(random.uniform(.5, 1.3)) return True def _set_avatar(self, tutorial=False): player_avatar = avatar.new() # ===== LIST_AVATAR_CUSTOMIZATIONS self.perform_request(lambda req: req.list_avatar_customizations( avatar_type=player_avatar['avatar'], # slot=tuple(), filters=2), buddy_walked=not tutorial, action=5, get_inbox=False) time.sleep(random.uniform(7, 14)) # ===== SET_AVATAR self.perform_request( lambda req: req.set_avatar(player_avatar=player_avatar), buddy_walked=not tutorial, action=2, get_inbox=False) if tutorial: time.sleep(random.uniform(.5, 4)) # ===== MARK_TUTORIAL_COMPLETE self.perform_request( lambda req: req.mark_tutorial_complete( tutorials_completed=1), buddy_walked=False, get_inbox=False) time.sleep(random.uniform(.5, 1)) self.perform_request( lambda req: req.get_player_profile(), action=1, get_inbox=False) def _complete_tutorial(self): tutorial_state = self.player_state['tutorial_state'] if 0 not in tutorial_state: # legal screen self.log_debug("Tutorial #0: Legal screen") # ===== MARK_TUTORIAL_COMPLETE self.perform_request(lambda req: req.mark_tutorial_complete( tutorials_completed=0), buddy_walked=False, get_inbox=False) time.sleep(random.uniform(.35, .525)) # ===== GET_PLAYER self.perform_request( lambda req: req.get_player( player_locale=self.cfg['player_locale']), buddy_walked=False, get_inbox=False) time.sleep(1) if 1 not in tutorial_state: # avatar selection self.log_debug("Tutorial #1: Avatar selection") self._set_avatar(tutorial=True) starter_id = None if 3 not in tutorial_state: # encounter tutorial self.log_debug("Tutorial #3: Catch starter Pokemon") time.sleep(random.uniform(.7, .9)) # ===== GET_DOWNLOAD_URLS self.perform_request(lambda req: req.get_download_urls(asset_id= ['1a3c2816-65fa-4b97-90eb-0b301c064b7a/1487275569649000', 'aa8f7687-a022-4773-b900-3a8c170e9aea/1487275581132582', 'e89109b0-9a54-40fe-8431-12f7826c8194/1487275593635524']), get_inbox=False) time.sleep(random.uniform(7, 10.3)) starter = random.choice((1, 4, 7)) # ===== ENCOUNTER_TUTORIAL_COMPLETE self.perform_request(lambda req: req.encounter_tutorial_complete( pokemon_id=starter), action=1, get_inbox=False) time.sleep(random.uniform(.4, .5)) # ===== GET_PLAYER responses = self.perform_request( lambda req: req.get_player(player_locale=self.cfg['player_locale']), get_inbox=False) try: inventory = responses[ 'GET_INVENTORY'].inventory_delta.inventory_items for item in inventory: pokemon = item.inventory_item_data.pokemon_data if pokemon.id: starter_id = pokemon.id break except (KeyError, TypeError): starter_id = None if 4 not in tutorial_state: # name selection self.log_debug("Tutorial #4: Set trainer name") time.sleep(random.uniform(12, 18)) # ===== CLAIM_CODENAME self.perform_request( lambda req: req.claim_codename(codename=self.username), action=2, get_inbox=False) time.sleep(.7) # ===== GET_PLAYER self.perform_request( lambda req: req.get_player(player_locale=self.cfg['player_locale']), get_inbox=False) time.sleep(.13) # ===== MARK_TUTORIAL_COMPLETE self.perform_request(lambda req: req.mark_tutorial_complete( tutorials_completed=4), buddy_walked=False, get_inbox=False) if 7 not in tutorial_state: # first time experience self.log_debug("Tutorial #7: First time experience") time.sleep(random.uniform(3.9, 4.5)) # ===== MARK_TUTORIAL_COMPLETE self.perform_request(lambda req: req.mark_tutorial_complete( tutorials_completed=7), get_inbox=False) # set starter as buddy if starter_id: self.log_debug("Setting buddy Pokemon") time.sleep(random.uniform(4, 5)) # ===== SET_BUDDY_POKEMON self.perform_request( lambda req: req.set_buddy_pokemon(pokemon_id=starter_id), action=2, get_inbox=False) time.sleep(random.uniform(.8, 1.2)) time.sleep(.2) return True def _download_remote_config_version(self): # ===== DOWNLOAD_REMOTE_CONFIG_VERSION responses = self.perform_request( lambda req: req.download_remote_config_version(platform=1, app_version=APP_VERSION), download_settings=True, buddy_walked=False, get_inbox=False) if 'DOWNLOAD_REMOTE_CONFIG_VERSION' not in responses: raise Exception("Call to download_remote_config_version did not" " return proper response.") remote_config = responses['DOWNLOAD_REMOTE_CONFIG_VERSION'] return remote_config['asset_digest_timestamp_ms'] / 1000000, \ remote_config['item_templates_timestamp_ms'] / 1000 def _get_asset_digest(self, asset_time): i = random.randint(0, 3) result = 2 page_offset = 0 page_timestamp = 0 while result == 2: # ===== GET_ASSET_DIGEST responses = self.perform_request(lambda req: req.get_asset_digest( platform=1, app_version=APP_VERSION, paginate=True, page_offset=page_offset, page_timestamp=page_timestamp), download_settings=True, buddy_walked=False, get_inbox=False) if i > 2: time.sleep(1.45) i = 0 else: i += 1 time.sleep(.2) try: response = responses['GET_ASSET_DIGEST'] except KeyError: break result = response['result'] page_offset = response.get('page_offset') page_timestamp = response['timestamp_ms'] self._asset_time = asset_time def _download_item_templates(self, template_time): i = random.randint(0, 3) result = 2 page_offset = 0 page_timestamp = 0 while result == 2: # ===== DOWNLOAD_ITEM_TEMPLATES responses = self.perform_request(lambda req: req.download_item_templates( paginate=True, page_offset=page_offset, page_timestamp=page_timestamp), download_settings=True, buddy_walked=False, get_inbox=False) if i > 2: time.sleep(1.5) i = 0 else: i += 1 time.sleep(.25) try: response = responses['DOWNLOAD_ITEM_TEMPLATES'] except KeyError: break result = response['result'] page_offset = response.get('page_offset') page_timestamp = response['timestamp_ms'] self._item_templates_time = template_time def log_info(self, msg): self.last_msg = msg log.info("[{}] {}".format(self.username, msg)) def log_debug(self, msg): self.last_msg = msg log.debug("[{}] {}".format(self.username, msg)) def log_warning(self, msg): self.last_msg = msg log.warning("[{}] {}".format(self.username, msg)) def log_error(self, msg): self.last_msg = msg log.error("[{}] {}".format(self.username, msg))
class POGOAccount(object): def __init__(self, auth_service, username, password, hash_key=None, hash_key_provider=None, proxy_url=None, proxy_provider=None): self.auth_service = auth_service self.username = username self.password = password # Get myself a copy of the config self.cfg = _mr_mime_cfg.copy() # Initialize hash keys self._hash_key = None if hash_key_provider and not hash_key_provider.is_empty(): self._hash_key_provider = hash_key_provider elif hash_key: self._hash_key_provider = CyclicResourceProvider(hash_key) self._hash_key = hash_key else: self._hash_key_provider = None # Initialize proxies self._proxy_url = None if proxy_provider and not proxy_provider.is_empty(): self._proxy_provider = proxy_provider elif proxy_url: self._proxy_provider = CyclicResourceProvider(proxy_url) self._proxy_url = proxy_url else: self._proxy_provider = None # Captcha self.captcha_url = None # Inventory information self.inbox = {} self.inventory = None self.inventory_balls = 0 self.inventory_lures = 0 self.inventory_total = 0 self.incubators = [] self.pokemon = {} self.eggs = [] # Current location self.latitude = None self.longitude = None self.altitude = None # Count number of rareless scans (to detect shadowbans) self.rareless_scans = None self.shadowbanned = None # Last log message (for GUI/console) self.last_msg = "" # --- private fields self._reset_api() # Will be set to true if a request returns a BAD_REQUEST response which equals a ban self._bad_request_ban = False # Tutorial state and warn/ban flags self._player_state = {} # Trainer statistics self._player_stats = None # PGPool self._pgpool_auto_update_enabled = mrmime_pgpool_enabled( ) and self.cfg['pgpool_auto_update'] self._last_pgpool_update = time.time() self.callback_egg_hatched = None def _reset_api(self): self._api = PGoApi(device_info=self._generate_device_info()) self._download_settings_hash = None self._asset_time = 0 self._item_templates_time = 0 # Timestamp when last API request was made self._last_request = 0 # Timestamp of last get_map_objects request self._last_gmo = self._last_request # Timestamp for incremental inventory updates self._last_timestamp_ms = None # Timestamp when previous user action is completed self._last_action = 0 @property def hash_key(self): return self._hash_key @hash_key.setter def hash_key(self, new_key): if self._hash_key_provider is None: self._hash_key_provider = CyclicResourceProvider(new_key) else: self._hash_key_provider.set_single_resource(new_key) self._hash_key = new_key @property def proxy_url(self): return self._proxy_url @proxy_url.setter def proxy_url(self, new_proxy_url): if self._proxy_provider is None: self._proxy_provider = CyclicResourceProvider(new_proxy_url) else: self._proxy_provider.set_single_resource(new_proxy_url) self._proxy_url = new_proxy_url def set_position(self, lat, lng, alt): """Sets the location and altitude of the account""" self._api.set_position(lat, lng, alt) self.latitude = lat self.longitude = lng self.altitude = alt def release(self, reason="No longer in use"): if mrmime_pgpool_enabled(): self.update_pgpool(release=True, reason=reason) self._api._session.close() auth_provider = self._api.get_auth_provider() if isinstance(auth_provider, AuthPtc): auth_provider._session.close() del self._api # Maybe delete more stuff too? # del self._player_state # del self._player_stats # del self.inventory # del self.incubators # del self.pokemon # del self.eggs def perform_request(self, add_main_request, buddy_walked=True, get_inbox=True, action=None): failures = 0 while True: try: request = self._api.create_request() # Add main request add_main_request(request) # Standard requests with every call request.check_challenge() request.get_hatched_eggs() # Check inventory with correct timestamp if self._last_timestamp_ms: request.get_holo_inventory( last_timestamp_ms=self._last_timestamp_ms) else: request.get_holo_inventory() # Always check awarded badges request.check_awarded_badges() # Optional: download settings (with correct hash value) if self._download_settings_hash: request.download_settings( hash=self._download_settings_hash) else: request.download_settings() # Optional: request buddy kilometers if buddy_walked: request.get_buddy_walked() if get_inbox: request.get_inbox(is_history=True) return self._call_request(request, action) except NotLoggedInException as e: failures += 1 if failures < 3: self.log_warning("{}: Trying to reset API".format(repr(e))) time.sleep(3) self._reset_api() self.set_position(self.latitude, self.longitude, self.altitude) self.check_login() time.sleep(1) else: self.log_error( "Failed {} times to reset API and repeat request. Giving up." .format(failures)) raise # Use API to check the login status, and retry the login if possible. def check_login(self): # Check auth ticket if self._api._auth_provider and self._api._auth_provider._access_token: remaining_time = self._api._auth_provider._access_token_expiry - time.time( ) if remaining_time > 60: self.log_debug( 'Credentials remain valid for another {} seconds.'.format( remaining_time)) return True try: if not self.cfg['parallel_logins']: login_lock.acquire() # Set proxy if given. if self._proxy_provider: self._proxy_url = self._proxy_provider.next() self.log_debug("Using proxy {}".format(self._proxy_url)) self._api.set_proxy({ 'http': self._proxy_url, 'https': self._proxy_url }) # Try to login. Repeat a few times, but don't get stuck here. num_tries = 0 # One initial try + login_retries. while num_tries < self.cfg['login_retries']: try: num_tries += 1 self.log_info("Login try {}.".format(num_tries)) if self._proxy_url: self._api.set_authentication( provider=self.auth_service, username=self.username, password=self.password, proxy_config={ 'http': self._proxy_url, 'https': self._proxy_url }) else: self._api.set_authentication( provider=self.auth_service, username=self.username, password=self.password) self.log_info( "Login successful after {} tries.".format(num_tries)) break except AuthException as ex: self.log_error( 'Failed to login. {} - Trying again in {} seconds.'. format(repr(ex), self.cfg['login_delay'])) # Let the exception for the last try bubble up. if num_tries >= self.cfg['login_retries']: raise time.sleep(self.cfg['login_delay']) if num_tries >= self.cfg['login_retries']: self.log_error( 'Failed to login in {} tries. Giving up.'.format( num_tries)) return False if self.cfg['full_login_flow'] is True: try: return self._initial_login_request_flow() except BannedAccountException: self.log_warning("Account most probably BANNED! :-(((") return False except CaptchaException: self.log_warning("Account got CAPTCHA'd! :-|") return False except Exception as e: self.log_error("Login failed: {}".format(repr(e))) return False return True finally: if not self.cfg['parallel_logins']: login_lock.release() def rotate_proxy(self): if self._proxy_provider: old_proxy = self._proxy_url self._proxy_url = self._proxy_provider.next() if self._proxy_url != old_proxy: self.log_info("Rotating proxy. Old: {} New: {}".format( old_proxy, self._proxy_url)) proxy_config = { 'http': self._proxy_url, 'https': self._proxy_url } self._api.set_proxy(proxy_config) self._api._auth_provider.set_proxy(proxy_config) def rotate_hash_key(self): # Set hash key for this request if not self._hash_key_provider: msg = "No hash key configured!" self.log_error(msg) raise NoHashKeyException() old_hash_key = self._hash_key self._hash_key = self._hash_key_provider.next() if self._hash_key != old_hash_key: self.log_debug("Using hash key {}".format(self._hash_key)) self._api.activate_hash_server(self._hash_key) def is_logged_in(self): # Logged in? Enough time left? Cool! if self._api._auth_provider and self._api._auth_provider._access_token: remaining_time = self._api._auth_provider._access_token_expiry - time.time( ) return remaining_time > 60 return False def is_warned(self): return self._player_state.get('warn') def is_banned(self): return self._bad_request_ban or self._player_state.get('banned', False) def has_captcha(self): return None if not self.is_logged_in() else ( self.captcha_url and len(self.captcha_url) > 1) def uses_proxy(self): return self._proxy_url is not None and len(self._proxy_url) > 0 def get_stats(self, attr, default=None): return getattr(self._player_stats, attr, default) if self._player_stats else default def get_state(self, key, default=None): return self._player_state.get(key, default) def needs_pgpool_update(self): return self._pgpool_auto_update_enabled and ( time.time() - self._last_pgpool_update >= self.cfg['pgpool_update_interval']) def update_pgpool(self, release=False, reason=None): data = { 'username': self.username, 'password': self.password, 'auth_service': self.auth_service, 'system_id': self.cfg['pgpool_system_id'], 'latitude': self.latitude, 'longitude': self.longitude } # After login we know whether we've got a captcha if self.is_logged_in(): data.update({'captcha': self.has_captcha()}) if self.rareless_scans is not None: data['rareless_scans'] = self.rareless_scans if self.shadowbanned is not None: data['shadowbanned'] = self.shadowbanned if self._bad_request_ban: data['banned'] = True if self._player_state: data.update({ 'warn': self.is_warned(), 'banned': self.is_banned(), 'ban_flag': self.get_state('banned') #'tutorial_state': data.get('tutorial_state'), }) if self._player_stats: data.update({ 'level': self.get_stats('level'), 'xp': self.get_stats('experience'), 'encounters': self.get_stats('pokemons_encountered'), 'balls_thrown': self.get_stats('pokeballs_thrown'), 'captures': self.get_stats('pokemons_captured'), 'spins': self.get_stats('poke_stop_visits'), 'walked': self.get_stats('km_walked') }) if self.inventory: data.update({ 'balls': self.inventory_balls, 'total_items': self.inventory_total, 'pokemon': len(self.pokemon), 'eggs': len(self.eggs), 'incubators': len(self.incubators), 'lures': self.inventory_lures }) if self.inbox: data.update({ 'email': self.inbox.get('EMAIL'), 'team': self.inbox.get('TEAM'), 'coins': self.inbox.get('POKECOIN_BALANCE'), 'stardust': self.inbox.get('STARDUST_BALANCE') }) if release and reason: data['_release_reason'] = reason try: cmd = 'release' if release else 'update' url = '{}/account/{}'.format(self.cfg['pgpool_url'], cmd) r = requests.post(url, data=json.dumps(data)) if r.status_code == 200: self.log_info("Successfully {}d PGPool account.".format(cmd)) elif r.status_code == 503: self.log_warning( "Could not update PGPool account: {} Try increasing 'pgpool_update_interval' in MrMime config." .format(r.content)) else: self.log_warning( "Got status {} from PGPool while updating account: {}". format(r.status_code, r.content)) except Exception as e: self.log_error("Could not update PGPool account: {}".format( repr(e))) self._last_pgpool_update = time.time() def req_get_map_objects(self): """Scans current account location.""" # Make sure that we don't hammer with GMO requests diff = self._last_gmo + self.cfg['scan_delay'] - time.time() if diff > 0: time.sleep(diff) # Jitter if wanted if self.cfg['jitter_gmo']: lat, lng = jitter_location(self.latitude, self.longitude) else: lat, lng = self.latitude, self.longitude cell_ids = get_cell_ids(lat, lng) timestamps = [ 0, ] * len(cell_ids) responses = self.perform_request( lambda req: req.get_map_objects(latitude=f2i(lat), longitude=f2i(lng), since_timestamp_ms=timestamps, cell_id=cell_ids), get_inbox=True) self._last_gmo = self._last_request return responses def req_encounter(self, encounter_id, spawn_point_id, latitude, longitude): return self.perform_request( lambda req: req.encounter(encounter_id=encounter_id, spawn_point_id=spawn_point_id, player_latitude=latitude, player_longitude=longitude), action=2.25) def req_catch_pokemon(self, encounter_id, spawn_point_id, ball, normalized_reticle_size, spin_modifier): response = self.perform_request(lambda req: req.catch_pokemon( encounter_id=encounter_id, pokeball=ball, normalized_reticle_size=normalized_reticle_size, spawn_point_id=spawn_point_id, hit_pokemon=1, spin_modifier=spin_modifier, normalized_hit_position=1.0), action=6) if ('CATCH_POKEMON' in response): catch_pokemon = response['CATCH_POKEMON'] catch_status = catch_pokemon.status capture_id = catch_pokemon.captured_pokemon_id # Determine caught Pokemon from inventory self.last_caught_pokemon = None if catch_status == 1: if capture_id in self.pokemon: self.last_caught_pokemon = self.pokemon[capture_id] return response def req_release_pokemon(self, pokemon_id, pokemon_ids=None): return self.perform_request(lambda req: req.release_pokemon( pokemon_id=pokemon_id, pokemon_ids=pokemon_ids)) def req_fort_details(self, fort_id, fort_lat, fort_lng): return self.perform_request(lambda req: req.fort_details( fort_id=fort_id, latitude=fort_lat, longitude=fort_lng), action=1.2) def req_fort_search(self, fort_id, fort_lat, fort_lng, player_lat, player_lng): return self.perform_request( lambda req: req.fort_search(fort_id=fort_id, fort_latitude=fort_lat, fort_longitude=fort_lng, player_latitude=player_lat, player_longitude=player_lng), action=2) def req_add_fort_modifier(self, modifier_id, fort_id, player_lat, player_lng): response = self.perform_request( lambda req: req.add_fort_modifier(modifier_type=modifier_id, fort_id=fort_id, player_latitude=player_lat, player_longitude=player_lng), action=1.2) return response def req_gym_get_info(self, gym_id, gym_lat, gym_lng, player_lat, player_lng): return self.perform_request( lambda req: req.gym_get_info(gym_id=gym_id, player_lat_degrees=f2i(player_lat), player_lng_degrees=f2i(player_lng), gym_lat_degrees=gym_lat, gym_lng_degrees=gym_lng)) def req_recycle_inventory_item(self, item_id, amount): return self.perform_request(lambda req: req.recycle_inventory_item( item_id=item_id, count=amount), action=2) def req_level_up_rewards(self, level): return self.perform_request( lambda req: req.level_up_rewards(level=level)) def req_verify_challenge(self, captcha_token): responses = self.perform_request( lambda req: req.verify_challenge(token=captcha_token), action=4) if 'VERIFY_CHALLENGE' in responses: response = responses['VERIFY_CHALLENGE'] if response.HasField('success'): self.captcha_url = None self.log_info("Successfully uncaptcha'd.") return True else: self.log_warning("Failed verifyChallenge") return False def req_use_item_egg_incubator(self, incubator_id, egg_id): return self.perform_request(lambda req: req.use_item_egg_incubator( item_id=incubator_id, pokemon_id=egg_id)) def seq_spin_pokestop(self, fort_id, fort_lat, fort_lng, player_lat, player_lng): # We first need to tap the Pokestop before we can spin it, so it's a sequence of actions self.req_fort_details(fort_id, fort_lat, fort_lng) return self.req_fort_search(fort_id, fort_lat, fort_lng, player_lat, player_lng) # ======================================================================= def _generate_device_info(self): identifier = self.username + self.password md5 = hashlib.md5() md5.update(identifier.encode('utf-8')) pick_hash = int(md5.hexdigest(), 16) iphones = { 'iPhone5,1': 'N41AP', 'iPhone5,2': 'N42AP', 'iPhone5,3': 'N48AP', 'iPhone5,4': 'N49AP', 'iPhone6,1': 'N51AP', 'iPhone6,2': 'N53AP', 'iPhone7,1': 'N56AP', 'iPhone7,2': 'N61AP', 'iPhone8,1': 'N71AP', 'iPhone8,2': 'N66AP', 'iPhone8,4': 'N69AP', 'iPhone9,1': 'D10AP', 'iPhone9,2': 'D11AP', 'iPhone9,3': 'D101AP', 'iPhone9,4': 'D111AP', 'iPhone10,1': 'D20AP', 'iPhone10,2': 'D21AP', 'iPhone10,3': 'D22AP', 'iPhone10,4': 'D201AP', 'iPhone10,5': 'D211AP', 'iPhone10,6': 'D221AP' } ios9 = ('9.0', '9.0.1', '9.0.2', '9.1', '9.2', '9.2.1', '9.3', '9.3.1', '9.3.2', '9.3.3', '9.3.4', '9.3.5') ios10 = ('10.0', '10.0.1', '10.0.2', '10.0.3', '10.1', '10.1.1') ios11 = ('11.0.1', '11.0.2', '11.0.3', '11.1', '11.1.1') device_info = { 'device_brand': 'Apple', 'device_model': 'iPhone', 'hardware_manufacturer': 'Apple', 'firmware_brand': 'iPhone OS' } devices = tuple(iphones.keys()) device = devices[pick_hash % len(devices)] device_info['device_model_boot'] = device device_info['hardware_model'] = iphones[device] device_info['device_id'] = md5.hexdigest() if device.startswith('iPhone10'): ios_pool = ios11 elif device.startswith('iPhone9'): ios_pool = ios10 + ios11 elif device.startswith('iPhone8'): ios_pool = ios9 + ios10 + ios11 else: ios_pool = ios9 + ios10 device_info['firmware_type'] = ios_pool[pick_hash % len(ios_pool)] self.log_debug("Using an {} on iOS {} with device ID {}".format( device, device_info['firmware_type'], device_info['device_id'])) return device_info def _call_request(self, request, action=None): # Wait until a previous user action gets completed if action: now = time.time() # wait for the time required, or at least a half-second if self._last_action > now + .5: time.sleep(self._last_action - now) else: time.sleep(0.5) req_method_list = copy.deepcopy(request._req_method_list) response = {} rotate_proxy = False while True: try: self.rotate_hash_key() if rotate_proxy: self.rotate_proxy() rotate_proxy = False response = request.call(use_dict=False) self._last_request = time.time() break except NianticIPBannedException as ex: if not self.uses_proxy(): # IP banned and not using proxies... we should quit self.log_error(repr(ex)) sys.exit(ex) except NotLoggedInException: # We need to re-login and re-post the request but we do it one level up raise except PgoapiError as ex: defaultRetryDelay = float(self.cfg['request_retry_delay']) # Rotate proxy if it's part of the error if self.uses_proxy() and exception_caused_by_proxy_error(ex): rotate_proxy = True # Shorten retry delay according to number of available proxies retryDelay = defaultRetryDelay / self._proxy_provider.len() else: # Shorten retry delay according to number of available hash keys retryDelay = defaultRetryDelay / self._hash_key_provider.len( ) self.log_warning("{}: Retrying in {:.1f}s.".format( repr(ex), retryDelay)) time.sleep(retryDelay) except Exception as ex: # No PgoapiError - this is serious! raise if not 'envelope' in response: msg = 'No response envelope. Something is wrong!' self.log_warning(msg) raise PgoapiError(msg) # status_code 3 means BAD_REQUEST, so probably banned status_code = response['envelope'].status_code if status_code == 3: log_suffix = '' if self.cfg['dump_bad_requests']: with open('BAD_REQUESTS.txt', 'a') as f: f.write(repr(req_method_list)) f.close() log_suffix = ' Dumped request to BAD_REQUESTS.txt.' self.log_warning( "Got BAD_REQUEST response. Possible Ban!{}".format(log_suffix)) self._bad_request_ban = True raise BannedAccountException # Clean up del response['envelope'] if not 'responses' in response: self.log_error("Got no responses at all!") return {} # Set the timer when the user action will be completed if action: self._last_action = self._last_request + action # Return only the responses responses = response['responses'] self._parse_responses(responses) if self.needs_pgpool_update(): self.update_pgpool() return responses def _update_inventory_totals(self): ball_ids = [ ITEM_POKE_BALL, ITEM_GREAT_BALL, ITEM_ULTRA_BALL, ITEM_MASTER_BALL ] lure_ids = [ITEM_TROY_DISK] balls = 0 lures = 0 total_items = 0 for item_id in self.inventory: if item_id in ball_ids: balls += self.inventory[item_id] if item_id in lure_ids: lures += self.inventory[item_id] total_items += self.inventory[item_id] self.inventory_balls = balls self.inventory_lures = lures self.inventory_total = total_items def _parse_responses(self, responses): for response_type in responses.keys(): response = responses[response_type] if response_type == 'GET_INBOX': self._parse_inbox_response(response) del responses[response_type] elif response_type == 'GET_HOLO_INVENTORY': api_inventory = response # Set an (empty) inventory if necessary if self.inventory is None: self.inventory = {} # Update inventory (balls, items) self._parse_inventory_delta(api_inventory) self._update_inventory_totals() # Update last timestamp for inventory requests self._last_timestamp_ms = api_inventory.inventory_delta.new_timestamp_ms # Clean up del responses[response_type] # Get settings hash from response for future calls elif response_type == 'DOWNLOAD_SETTINGS': if response.hash: self._download_settings_hash = response.hash # TODO: Check forced client version and exit program if different # Clean up del responses[response_type] elif response_type == 'GET_PLAYER': self._player_state = { 'tutorial_state': response.player_data.tutorial_state, 'buddy': response.player_data.buddy_pokemon.id, 'warn': response.warn, 'banned': response.banned } # Clean up del responses[response_type] if self._player_state['banned']: self.log_warning("GET_PLAYER has the 'banned' flag set.") raise BannedAccountException # Check for captcha elif response_type == 'CHECK_CHALLENGE': self.captcha_url = response.challenge_url # Clean up del responses[response_type] if self.has_captcha() and self.cfg['exception_on_captcha']: raise CaptchaException elif response_type == 'GET_MAP_OBJECTS': if is_rareless_scan(response): if self.rareless_scans is None: self.rareless_scans = 1 else: self.rareless_scans += 1 else: self.rareless_scans = 0 elif response_type == 'GET_HATCHED_EGGS': if self.callback_egg_hatched and response.success and len( response.hatched_pokemon) > 0: for i in range(0, len(response.pokemon_id)): hatched_egg = { 'experience_awarded': response.experience_awarded[i], 'candy_awarded': response.candy_awarded[i], 'stardust_awarded': response.stardust_awarded[i], 'egg_km_walked': response.egg_km_walked[i], 'hatched_pokemon': response.hatched_pokemon[i] } self.callback_egg_hatched(self, hatched_egg) def _parse_inbox_response(self, response): vars = response.inbox.builtin_variables for v in vars: if v.name in ('POKECOIN_BALANCE', 'STARDUST_BALANCE'): self.inbox[v.name] = int(v.literal) elif v.name == 'EMAIL': self.inbox[v.name] = v.literal elif v.name == 'TEAM': self.inbox[v.name] = v.key def _parse_inventory_delta(self, inventory): for item in inventory.inventory_delta.inventory_items: item_data = item.inventory_item_data if item_data.HasField('player_stats'): self._player_stats = item_data.player_stats elif item_data.HasField('item'): item_id = item_data.item.item_id item_count = item_data.item.count self.inventory[item_id] = item_count elif item_data.HasField('egg_incubators'): incubators = item_data.egg_incubators.egg_incubator for incubator in incubators: if incubator.pokemon_id == 0: self.incubators.append({ 'id': incubator.id, 'item_id': incubator.item_id, 'uses_remaining': incubator.uses_remaining }) elif item_data.HasField('pokemon_data'): p_data = item_data.pokemon_data p_id = p_data.id if not p_data.is_egg: self.pokemon[p_id] = { 'pokemon_id': p_data.pokemon_id, 'move_1': p_data.move_1, 'move_2': p_data.move_2, 'individual_attack': p_data.individual_attack, 'individual_defense': p_data.individual_defense, 'individual_stamina': p_data.individual_stamina, 'height': p_data.height_m, 'weight': p_data.weight_kg, 'costume': p_data.pokemon_display.costume, 'form': p_data.pokemon_display.form, 'gender': p_data.pokemon_display.gender, 'shiny': p_data.pokemon_display.shiny, 'cp': p_data.cp, 'cp_multiplier': p_data.cp_multiplier, 'is_bad': p_data.is_bad } else: # Incubating egg if p_data.egg_incubator_id: continue # Egg self.eggs.append({ 'id': p_id, 'km_target': p_data.egg_km_walked_target }) def _initial_login_request_flow(self): self.log_info("Performing full login flow requests") # Empty request ----------------------------------------------------- self.log_debug("Login Flow: Empty request") # ===== empty request = self._api.create_request() self._call_request(request) time.sleep(random.uniform(.43, .97)) # Get player info --------------------------------------------------- self.log_debug("Login Flow: Get player state") # ===== GET_PLAYER (unchained) request = self._api.create_request() request.get_player(player_locale=self.cfg['player_locale']) self._call_request(request) time.sleep(random.uniform(.53, 1.1)) # Download remote config -------------------------------------------- self.log_debug("Login Flow: Downloading remote config") asset_time, template_time = self._download_remote_config_version() time.sleep(1) # Assets and item templates ----------------------------------------- if self.cfg[ 'download_assets_and_items'] and asset_time > self._asset_time: self.log_debug("Login Flow: Download asset digest") self._get_asset_digest(asset_time) else: self.log_debug("Login Flow: Skipping asset digest download") if self.cfg[ 'download_assets_and_items'] and template_time > self._item_templates_time: self.log_debug("Login Flow: Download item templates") self._download_item_templates(template_time) else: self.log_debug("Login Flow: Skipping item template download") # TODO: Maybe download translation URLs from assets? Like pogonode? # Checking tutorial ------------------------------------------------- if (self._player_state['tutorial_state'] is not None and not all(x in self._player_state['tutorial_state'] for x in (0, 1, 3, 4, 7))): self.log_info("Completing tutorial") self._complete_tutorial() else: # Get player profile self.log_debug("Login Flow: Get player profile") # ===== GET_PLAYER_PROFILE self.perform_request(lambda req: req.get_player_profile()) time.sleep(random.uniform(.2, .3)) # Level up rewards -------------------------------------------------- self.log_debug("Login Flow: Get levelup rewards") # ===== LEVEL_UP_REWARDS self.perform_request( lambda req: req.level_up_rewards(level=self._player_stats.level)) # Check store ------------------------------------------------------- # TODO: There is currently no way to call the GET_STORE_ITEMS platform request. self.log_info('After-login procedure completed.') time.sleep(random.uniform(.5, 1.3)) return True def _set_avatar(self, tutorial=False): player_avatar = avatar.new() # ===== LIST_AVATAR_CUSTOMIZATIONS self.perform_request( lambda req: req.list_avatar_customizations( avatar_type=player_avatar['avatar'], # slot=tuple(), filters=2), buddy_walked=not tutorial, action=5, get_inbox=False) time.sleep(random.uniform(7, 14)) # ===== SET_AVATAR self.perform_request( lambda req: req.set_avatar(player_avatar=player_avatar), buddy_walked=not tutorial, action=2, get_inbox=False) if tutorial: time.sleep(random.uniform(.5, 4)) # ===== MARK_TUTORIAL_COMPLETE self.perform_request( lambda req: req.mark_tutorial_complete(tutorials_completed=1), buddy_walked=False, get_inbox=False) time.sleep(random.uniform(.5, 1)) self.perform_request(lambda req: req.get_player_profile(), action=1, get_inbox=False) def _complete_tutorial(self): tutorial_state = self._player_state['tutorial_state'] if 0 not in tutorial_state: # legal screen self.log_debug("Tutorial #0: Legal screen") # ===== MARK_TUTORIAL_COMPLETE self.perform_request( lambda req: req.mark_tutorial_complete(tutorials_completed=0), buddy_walked=False, get_inbox=False) time.sleep(random.uniform(.35, .525)) # ===== GET_PLAYER self.perform_request(lambda req: req.get_player( player_locale=self.cfg['player_locale']), buddy_walked=False, get_inbox=False) time.sleep(1) if 1 not in tutorial_state: # avatar selection self.log_debug("Tutorial #1: Avatar selection") self._set_avatar(tutorial=True) starter_id = None if 3 not in tutorial_state: # encounter tutorial self.log_debug("Tutorial #3: Catch starter Pokemon") time.sleep(random.uniform(.7, .9)) # ===== GET_DOWNLOAD_URLS self.perform_request(lambda req: req.get_download_urls(asset_id=[ '1a3c2816-65fa-4b97-90eb-0b301c064b7a/1487275569649000', 'aa8f7687-a022-4773-b900-3a8c170e9aea/1487275581132582', 'e89109b0-9a54-40fe-8431-12f7826c8194/1487275593635524' ]), get_inbox=False) time.sleep(random.uniform(7, 10.3)) starter = random.choice((1, 4, 7)) # ===== ENCOUNTER_TUTORIAL_COMPLETE self.perform_request(lambda req: req.encounter_tutorial_complete( pokemon_id=starter), action=1, get_inbox=False) time.sleep(random.uniform(.4, .5)) # ===== GET_PLAYER responses = self.perform_request(lambda req: req.get_player( player_locale=self.cfg['player_locale']), get_inbox=False) try: inventory = responses[ 'GET_HOLO_INVENTORY'].inventory_delta.inventory_items for item in inventory: pokemon = item.inventory_item_data.pokemon_data if pokemon.id: starter_id = pokemon.id break except (KeyError, TypeError): starter_id = None if 4 not in tutorial_state: # name selection self.log_debug("Tutorial #4: Set trainer name") time.sleep(random.uniform(12, 18)) # ===== CLAIM_CODENAME self.perform_request( lambda req: req.claim_codename(codename=self.username), action=2, get_inbox=False) time.sleep(.7) # ===== GET_PLAYER self.perform_request(lambda req: req.get_player( player_locale=self.cfg['player_locale']), get_inbox=False) time.sleep(.13) # ===== MARK_TUTORIAL_COMPLETE self.perform_request( lambda req: req.mark_tutorial_complete(tutorials_completed=4), buddy_walked=False, get_inbox=False) if 7 not in tutorial_state: # first time experience self.log_debug("Tutorial #7: First time experience") time.sleep(random.uniform(3.9, 4.5)) # ===== MARK_TUTORIAL_COMPLETE self.perform_request( lambda req: req.mark_tutorial_complete(tutorials_completed=7), get_inbox=False) # set starter as buddy if starter_id: self.log_debug("Setting buddy Pokemon") time.sleep(random.uniform(4, 5)) # ===== SET_BUDDY_POKEMON self.perform_request( lambda req: req.set_buddy_pokemon(pokemon_id=starter_id), action=2, get_inbox=False) time.sleep(random.uniform(.8, 1.2)) time.sleep(.2) return True def _download_remote_config_version(self): # ===== DOWNLOAD_REMOTE_CONFIG_VERSION responses = self.perform_request( lambda req: req.download_remote_config_version( platform=1, app_version=PGoApi.get_api_version()), buddy_walked=False, get_inbox=False) if 'DOWNLOAD_REMOTE_CONFIG_VERSION' not in responses: raise Exception("Call to download_remote_config_version did not" " return proper response.") remote_config = responses['DOWNLOAD_REMOTE_CONFIG_VERSION'] return remote_config.asset_digest_timestamp_ms / 1000000, \ remote_config.item_templates_timestamp_ms / 1000 def _get_asset_digest(self, asset_time): i = random.randint(0, 3) result = 2 page_offset = 0 page_timestamp = 0 while result == 2: # ===== GET_ASSET_DIGEST responses = self.perform_request(lambda req: req.get_asset_digest( platform=1, app_version=PGoApi.get_api_version(), paginate=True, page_offset=page_offset, page_timestamp=page_timestamp), buddy_walked=False, get_inbox=False) if i > 2: time.sleep(1.45) i = 0 else: i += 1 time.sleep(.2) try: response = responses['GET_ASSET_DIGEST'] except KeyError: break result = response.result page_offset = response.page_offset page_timestamp = response.timestamp_ms self._asset_time = asset_time def _download_item_templates(self, template_time): i = random.randint(0, 3) result = 2 page_offset = 0 page_timestamp = 0 while result == 2: # ===== DOWNLOAD_ITEM_TEMPLATES responses = self.perform_request( lambda req: req.download_item_templates( paginate=True, page_offset=page_offset, page_timestamp=page_timestamp), buddy_walked=False, get_inbox=False) if i > 2: time.sleep(1.5) i = 0 else: i += 1 time.sleep(.25) try: response = responses['DOWNLOAD_ITEM_TEMPLATES'] except KeyError: break result = response.result page_offset = response.page_offset page_timestamp = response.timestamp_ms self._item_templates_time = template_time def log_info(self, msg): self.last_msg = msg log.info(u"[{}] {}".format(self.username, msg)) def log_debug(self, msg): self.last_msg = msg log.debug(u"[{}] {}".format(self.username, msg)) def log_warning(self, msg): self.last_msg = msg log.warning(u"[{}] {}".format(self.username, msg)) def log_error(self, msg): self.last_msg = msg log.error(u"[{}] {}".format(self.username, msg))
class accountManager(object): def __init__(self, username, password, hashkey, service, version, proxy=None): self.username = username self.password = password self.service = service self.hashkey = hashkey self.proxy = proxy self.version = version self.captcha = None self.isBanned = False self._api = PGoApi(device_info=self._generate_device_info()) self._lastTimestamp = 0 self._settingsHash = None self._playerLevel = 1 self._mapRefresh = None def isLoggedin(self): # check if we've logged in by checking if set provider & has a ticket if self._api.get_auth_provider() and self._api.get_auth_provider( ).has_ticket(): return True if self.proxy: self._api.set_proxy({'http': self.proxy, 'https': self.proxy}) self._api.activate_hash_server(self.hashkey) # not logged in, so lets do it. try: if self.proxy: self._api.set_authentication(provider=self.service, username=self.username, password=self.password, proxy_config={ 'http': self.proxy, 'https': self.proxy }) else: self._api.set_authentication(provider=self.service, username=self.username, password=self.password) except AuthException: print("Login failed. Wrong user/pass?") return False except PgoapiError: print("API Errored out. Trying again.") time.sleep(5) self.isLoggedin() except NoPlayerPositionSetException: print("Set a location first.") return False except NoHashKeyException: print("Set a hash key.") return False self._fullLogin() return True def setPosition(self, lat, lng, alt): self._api.set_position(lat, lng, alt) def get_gym_info(self, g_id, p_lat, p_lng, g_lat, g_lng): return self._api.request( lambda req: req.get_gym_info(gym_id=g_id, player_lat_degrees=p_lat, player_lng_degrees=p_lng, gym_lat_degrees=g_lat, gym_lng_degrees=g_lng)) def spin_pokestop(self, f_id, p_lat, p_lng, f_lat, f_lng): self.request(lambda req: req.fort_search(fort_id=f_id, fort_latitude=f_lat, fort_longitude=f_lng, player_latitude=p_lat, player_longitude=p_lng), walked=True, inbox=True) def dump_items(self, itemid, drop): self.request(lambda req: req.recycle_inventory_items(item_id=itemid, count=drop), walked=True, inbox=True) def request(self, reqType, walked=False, inbox=False): request = self._api.create_request() # Insert our request. reqType(request) # default requests request.check_challenge() # no longer used request.get_hatched_eggs() request.get_holo_inventory(last_timestamp_ms=self._lastTimestamp) request.check_awarded_badges() if self._settingsHash: request.download_settings(hash=self._settingsHash) else: request.download_settings() if walked: request.get_buddy_walked() if inbox: request.get_inbox(is_history=True) return self._sendRequest(request) def solveCaptcha(self, challenge_token): request = self._api.create_request() request.verify_challenge(token=challenge_token) response = self._sendRequest(request) if 'VERIFY_CHALLENGE' in response: if response['VERIFY_CHALLENGE']['success']: return True else: return False else: return False def _fullLogin(self): # from https://raw.githubusercontent.com/sLoPPydrive/MrMime/master/research/login_flow.txt # empty request request = self._api.create_request() self._sendRequest(request) # get player request = self._api.create_request() request.get_player(player_locale={ 'country': 'US', 'language': 'en', 'timezone': 'America/New_York' }) self._sendRequest(request) # download_remote_config_version self.request(lambda req: req.download_remote_config_version( platform=1, app_version=self.version)) # get_player_profile self.request(lambda req: req.get_player_profile(), walked=True) # level_up_rewards self.request(lambda req: req.level_up_rewards(), walked=True) def _sendRequest(self, request): # Make API request. retry if hashing service is f****d. success = False while not success: try: time.sleep(random.uniform(0.4, 0.9)) response = request.call() success = True except (HashingOfflineException, HashingTimeoutException, NianticOfflineException): print( "Hash service down. Sleeping for a bit and trying again.") time.sleep(5) except HashingQuotaExceededException: print("Exceeded hash per minute quota. Trying again.") time.sleep(random.uniform(40, 50)) except NianticThrottlingException: print("We went over 88mph.") time.sleep(3) except KeyError: print("Encounterd a KeyError. Retrying.") time.sleep(3) return self._parseResponse(response['responses']) def _parseResponse(self, response): # claen up the shit we don't care about. if 'LEVEL_UP_REWARDS' in response: del response['LEVEL_UP_REWARDS'] if 'GET_HATCHED_EGGS' in response: del response['GET_HATCHED_EGGS'] if 'CHECK_AWARDED_BADGES' in response: del response['CHECK_AWARDED_BADGES'] if 'GET_BUDDY_WALKED' in response: del response['GET_BUDDY_WALKED'] # Grab info we care about before deleting it. if 'CHECK_CHALLENGE' in response: challenge_url = response['CHECK_CHALLENGE'].get('challenge_url') if int(len(challenge_url)) > 1: print(challenge_url) self.captcha = challenge_url raise CaptchaException else: self.captcha = None del response['CHECK_CHALLENGE'] if 'GET_HOLO_INVENTORY' in response: inventory = response['GET_HOLO_INVENTORY'].get('inventory_delta') self._lastTimestamp = inventory.get('new_timestamp_ms') for item in inventory.get('inventory_items', {}): if 'player_stats' in item['inventory_item_data']: stats = item.get('inventory_item_data', {}).get('player_stats') self._playerLevel = stats.get('level') del response['GET_HOLO_INVENTORY'] if 'DOWNLOAD_SETTINGS' in response: self._settingsHash = response['DOWNLOAD_SETTINGS'].get('hash') if response['DOWNLOAD_SETTINGS'].get('settings'): settings = response['DOWNLOAD_SETTINGS'].get('settings') self._mapRefresh = settings['map_settings'].get( 'get_map_objects_min_refresh_seconds') del response['DOWNLOAD_SETTINGS'] return response # print(response) # Borrowed from MrMime/mrmime/pogoaccount.py def _generate_device_info(self): identifier = self.username + self.password md5 = hashlib.md5() md5.update(identifier.encode('utf-8')) pick_hash = int(md5.hexdigest(), 16) iphones = { 'iPhone6,1': 'N51AP', 'iPhone6,2': 'N53AP', 'iPhone7,1': 'N56AP', 'iPhone7,2': 'N61AP', 'iPhone8,1': 'N71AP', 'iPhone8,2': 'N66AP', 'iPhone8,4': 'N69AP', 'iPhone9,1': 'D10AP', 'iPhone9,2': 'D11AP', 'iPhone9,3': 'D101AP', 'iPhone9,4': 'D111AP' } ios9 = ('9.0', '9.0.1', '9.0.2', '9.1', '9.2', '9.2.1', '9.3', '9.3.1', '9.3.2', '9.3.3', '9.3.4', '9.3.5') ios10 = ('10.0', '10.0.1', '10.0.2', '10.0.3', '10.1', '10.1.1', '10.2', '10.2.1', '10.3', '10.3.1', '10.3.2', '10.3.3') ios11 = ('11.0', '11.0.1', '11.0.2', '11.0.3', '11.1', '11.1.1', '11.1.2', '11.2') device_info = { 'device_brand': 'Apple', 'device_model': 'iPhone', 'hardware_manufacturer': 'Apple', 'firmware_brand': 'iPhone OS' } devices = tuple(iphones.keys()) device = devices[pick_hash % len(devices)] device_info['device_model_boot'] = device device_info['hardware_model'] = iphones[device] device_info['device_id'] = md5.hexdigest() if device.startswith('iPhone9'): ios_pool = ios10 + ios11 else: ios_pool = ios9 + ios10 + ios11 device_info['firmware_type'] = ios_pool[pick_hash % len(ios_pool)] print("{} Using an {} on iOS {} with device ID {}".format( self.username, device, device_info['firmware_type'], device_info['device_id'])) return device_info