コード例 #1
0
ファイル: banned.py プロジェクト: voxx/PoGoAccountCheck
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)
コード例 #2
0
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)
コード例 #3
0
ファイル: banned.py プロジェクト: exiva/poGoRaidBot
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)
コード例 #4
0
ファイル: main.py プロジェクト: tegob/PokemonGO-IV-Renamer
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)
コード例 #5
0
ファイル: scout.py プロジェクト: codename-art/RocketMap
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)
コード例 #6
0
ファイル: main.py プロジェクト: Boren/PokemonGO-IV-Renamer
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)
コード例 #7
0
ファイル: main.py プロジェクト: tegob/PokemonGO-IV-Renamer
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)
コード例 #8
0
ファイル: worker.py プロジェクト: adrianmateii/Monocle
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)
コード例 #9
0
ファイル: Scout.py プロジェクト: thunder123456/PGScout
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
コード例 #10
0
ファイル: pokebotflask.py プロジェクト: snicker/pokevaluator
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
コード例 #11
0
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
コード例 #12
0
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:
コード例 #13
0
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)
コード例 #14
0
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)
コード例 #15
0
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))
コード例 #16
0
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))
コード例 #17
0
ファイル: account.py プロジェクト: exiva/poGoRaidBot
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